Qt拖放
Qt拖放
拖放提供了一种简单的可视化机制,用户可以使用该机制在应用程序之间和内部传输信息。 拖放功能类似于剪贴板的剪切和粘贴机制。
本文档介绍了基本的拖放机制,并概述了在自定义控件中启用它的方法。 许多Qt的控件也支持拖放操作,例如项目视图和图形视图框架,以及Qt Widgets和Qt Quick的编辑控件。 有关项目视图和图形视图的更多信息,请参阅使用拖放项目视图和图形视图框架。
拖放类
这些类处理拖放和必要的mime类型编码和解码。
类 | 描述 |
---|---|
QDrag | 支持基于MIME的拖放数据传输 |
QDragEnterEvent | 拖放操作进入窗口小部件时发送到窗口小部件的事件 |
QDragLeaveEvent | 拖放操作离开时发送到窗口小部件的事件 |
QDragMoveEvent | 拖放操作正在进行时发送的事件 |
QDropEvent | 拖放操作完成时发送的事件 |
配置
QStyleHints
对象提供了与拖放操作相关的一些属性:
-
QStyleHints::startDragTime()
描述在拖动开始之前用户必须在对象上按住鼠标按钮的时间量(以毫秒为单位)。 -
QStyleHints::startDragDistance()
表示在将移动解释为拖动之前,用户在按住鼠标按钮的同时移动鼠标的距离。 -
QStyleHints::startDragVelocity()
表示用户移动鼠标,开始拖动的速度(以像素/秒为单位)。
如果您在控件中提供拖放支持,这些变量提供符合基础窗口系统的合理默认值。
在Qt Quick中拖放
本文档的其余部分主要关注如何在C++中实现拖放。 要在Qt Quick场景中使用拖放,请阅读Qt Quick Drag,DragEvent和DropArea项目的文档,以及Qt Quick Drag and Drop示例。
拖
要开始拖动,请创建一个QDrag
对象,并调用其exec()
函数。 在大多数应用程序中,最好只在按下鼠标按钮并将光标移动一定距离后才开始拖放操作。 但是,从窗口启用拖动的最简单方法是重新实现窗口的mousePressEvent()
并启动拖放操作:
void MainWindow::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton
&& iconLabel->geometry().contains(event->pos())) {
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setText(commentEdit->toPlainText());
drag->setMimeData(mimeData);
drag->setPixmap(iconPixmap);
Qt::DropAction dropAction = drag->exec();
...
}
}
虽然用户可能需要一些时间来完成拖动操作,但就应用程序而言,exec()
函数是一个阻塞函数,它返回多个值之一。 这些表示操作如何结束,并在下面更详细地描述。
请注意,exec()
函数不会阻止主事件循环。
对于需要区分鼠标单击和拖动的控件,重新实现控件的mousePressEvent()
函数以记录拖动的起始位置是很有用的:
void DragWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
dragStartPosition = event->pos();
}
稍后,在mouseMoveEvent()
中,我们可以确定是否应该开始拖动,并构造一个拖动对象来处理操作:
void DragWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!(event->buttons() & Qt::LeftButton))
return;
if ((event->pos() - dragStartPosition).manhattanLength()
< QApplication::startDragDistance())
return;
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setData(mimeType, data);
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
...
}
此特定方法使用QPoint::manhattanLength()
函数粗略估计鼠标单击发生的位置与当前光标位置之间的距离。 此功能可以精确地提高速度,通常适用于此目的。
放
为了能够接收窗口上的媒体,请为窗口调用setAcceptDrops(true)
,然后重新实现dragEnterEvent()
和dropEvent()
事件处理函数。
例如,以下代码在QWidget
子类的构造函数中启用drop事件,从而可以有效地实现drop事件处理程序:
Window::Window(QWidget *parent)
: QWidget(parent)
{
...
setAcceptDrops(true);
}
dragEnterEvent()
函数通常用于通知Qt窗口接受的数据类型。 如果要在重新实现dragMoveEvent()
和dropEvent()
时接收QDragMoveEvent
或QDropEvent
,则必须重新实现此函数。
以下代码显示了如何重新实现dragEnterEvent()
以告诉拖放系统我们只能处理纯文本:
void Window::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("text/plain"))
event->acceptProposedAction();
}
dropEvent()
用于解压缩已删除的数据并以适合您的应用程序的方式处理它。
在下面的代码中,事件中提供的文本传递给QTextBrowser
,QComboBox
填充了用于描述数据的MIME类型列表:
void Window::dropEvent(QDropEvent *event)
{
textBrowser->setPlainText(event->mimeData()->text());
mimeTypeCombo->clear();
mimeTypeCombo->addItems(event->mimeData()->formats());
event->acceptProposedAction();
}
在这种情况下,我们接受拖拽Action而不检查它是什么。 在实际应用程序中,可能需要从dropEvent()
函数返回,而不接受拖拽Action或在操作不相关时处理数据。 例如,如果我们不支持应用程序中外部源的链接,我们可能会选择忽略Qt::LinkAction
操作。
重写拖拽Action
我们也可能忽略拖拽Action,并对数据执行一些其他操作。 为此,我们将在调用accept()
之前使用Qt::DropAction
中的首选操作调用事件对象的setDropAction()
。 这可确保我们提供的替换Action而不是原来的Action。
对于更复杂的应用程序,重新实现dragMoveEvent()
和dragLeaveEvent()
将允许您使窗口的某些部分对放置事件敏感,并使您可以更好地控制应用程序中的拖放。
自定义复杂控件
某些标准Qt窗口提供了自己的拖放支持。 在对这些窗口进行子类化时,除了dragEnterEvent()
和dropEvent()
之外,可能还需要重新实现dragMoveEvent()
,以防止基类提供默认的拖放处理,并处理您感兴趣的任何特殊情况。
拖放操作
在最简单的情况下,拖放操作的目标接收被拖动数据的副本,并且源决定是否删除原始数据。这由CopyAction
操作描述。目标还可以选择处理其他操作,特别是MoveAction
和LinkAction
操作。如果源调用QDrag::exec()
,并且它返回MoveAction
,则源负责删除任何原始数据(如果它选择这样做)。不应删除源窗口创建的QMimeData
和QDrag
对象 - 它们将被Qt销毁。目标负责获取拖放操作中发送的数据的所有权;这通常是通过保持对数据的引用来完成的。
如果目标理解LinkAction
操作,它应该存储自己对原始信息的引用;源不需要对数据执行任何进一步处理。拖放操作最常见的用法是在同一个窗口小部件中执行Move
;有关此功能的详细信息,请参阅“删除操作”一节。
拖动操作的另一个主要用途是使用诸如text/uri-list
之类的引用类型,其中拖动的数据实际上是对文件或对象的引用。
添加新的拖放类型
拖放不仅限于文本和图像。可以通过拖放操作传输任何类型的信息。要在应用程序之间拖动信息,应用程序必须能够相互指示它们可以接受哪些数据格式以及它们可以生成哪些数据格式。这是使用MIME类型实现的。源构造的QDrag
对象包含用于表示数据的MIME类型列表(从最合适到最不合适的排序),并且放置目标使用其中一个来访问数据。对于常见数据类型,便捷函数处理透明使用的MIME类型,但对于自定义数据类型,必须明确说明它们。
要为QDrag便捷功能未涵盖的一类信息实施拖放操作,第一步也是最重要的一步是查找适当的现有格式:Internet Assigned Numbers Authority(IANA)提供了一个分层列表信息科学研究所(ISI)的MIME媒体类型。使用标准MIME类型可以最大化现在和将来应用程序与其他软件的互操作性。
要支持其他媒体类型,只需使用setData()
函数在QMimeData
对象中设置数据,提供完整的MIME类型和包含相应格式数据的QByteArray。以下代码从标签中获取像素图,并将其存储为QMimeData对象中的可移植网络图形(PNG)文件:
QByteArray output;
QBuffer outputBuffer(&output);
outputBuffer.open(QIODevice::WriteOnly);
imageLabel->pixmap()->toImage().save(&outputBuffer, "PNG");
mimeData->setData("image/png", output);
当然,对于这种情况,我们可以简单地使用setImageData()
来提供各种格式的图像数据:
mimeData->setImageData(QVariant(*imageLabel->pixmap()));
在这种情况下,QByteArray方法仍然有用,因为它可以更好地控制QMimeData对象中存储的数据量。
请注意,项视图中使用的自定义数据类型必须声明为元对象,并且必须实现它们的流操作符。
拖放 Actions
在剪贴板模型中,用户可以剪切或复制源信息,然后粘贴它。 类似地,在拖放模型中,用户可以拖动信息的副本,或者他们可以将信息本身拖动到新的位置(移动它)。 拖放模型给程序员带来了额外的复杂性:程序不知道用户是否想要剪切或复制信息,直到操作完成。 在应用程序之间拖动信息时,这通常没有区别,但在应用程序中,检查使用的放置操作非常重要。
我们可以为窗口重新实现mouseMoveEvent()
,并使用可能的放置操作组合启动拖放操作。 例如,我们可能希望确保拖动始终移动窗口中的对象:
void DragWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!(event->buttons() & Qt::LeftButton))
return;
if ((event->pos() - dragStartPosition).manhattanLength()
< QApplication::startDragDistance())
return;
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setData(mimeType, data);
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
...
}
如果将信息放入另一个应用程序中,则exec()
函数返回的操作可能默认为CopyAction
,但如果将其放入同一应用程序中的另一个窗口小部件中,我们可能会获得不同的放置操作。
可以在窗口的dragMoveEvent()
函数中过滤建议的放置操作。 但是,可以接受dragEnterEvent()
中的所有建议操作,并让用户决定以后要接受哪些操作:
void DragWidget::dragEnterEvent(QDragEnterEvent *event)
{
event->acceptProposedAction();
}
当窗口中发生drop时,将调用dropEvent()
处理函数,并且我们可以依次处理每个可能的操作。 首先,我们在同一个窗口中处理拖放操作:
void DragWidget::dropEvent(QDropEvent *event)
{
if (event->source() == this && event->possibleActions() & Qt::MoveAction)
return;
在这种情况下,我们拒绝处理移动操作。 我们接受的每种类型的放置操作都会相应地进行检查和处理:
if (event->proposedAction() == Qt::MoveAction) {
event->acceptProposedAction();
// Process the data from the event.
} else if (event->proposedAction() == Qt::CopyAction) {
event->acceptProposedAction();
// Process the data from the event.
} else {
// Ignore the drop.
return;
}
...
}
请注意,我们在上面的代码中检查了单个放置操作。 如上面有关覆盖建议操作的部分所述,有时需要覆盖建议的删除操作,并从可能的删除操作的选择中选择不同的操作。 要执行此操作,您需要检查事件的possibleActions()
提供的值中是否存在每个操作,使用setDropAction()
设置drop操作,并调用accept()
。
拖放矩形
窗口的dragMoveEvent()
可用于通过在光标位于这些区域内时接受建议的放置操作来限制窗口的某些部分。 例如,当光标位于子窗口控件(dropFrame)上时,以下代码接受任何建议的放置操作:
void Window::dragMoveEvent(QDragMoveEvent *event)
{
if (event->mimeData()->hasFormat("text/plain")
&& event->answerRect().intersects(dropFrame->geometry()))
event->acceptProposedAction();
}
如果您需要在拖放操作期间提供视觉反馈,滚动窗口或任何适当的内容,也可以使用dragMoveEvent()
。
剪贴板
应用程序还可以通过将数据放在剪贴板上来相互通信。 要访问它,您需要从QApplication
对象获取QClipboard
对象。 QMimeData
类用于表示传输到剪贴板和从剪贴板传输的数据。 要将数据放在剪贴板上,可以使用setText()
,setImage()
和setPixmap()
方便函数来处理常见数据类型。 这些函数类似于QMimeData
类中的函数,除了它们还采用了一个控制数据存储位置的附加参数:如果指定了剪贴板,则数据放在剪贴板上; 如果指定了Selection
,则数据将放在鼠标选择中(仅限X11)。 默认情况下,数据放在剪贴板上。
例如,我们可以使用以下代码将QLineEdit
的内容复制到剪贴板:
QGuiApplication::clipboard()->setText(lineEdit->text(), QClipboard::Clipboard);
具有不同MIME类型的数据也可以放在剪贴板上。 构造一个QMimeData
对象,并按照上一节中描述的方式使用setData()
函数设置数据; 然后可以使用setMimeData()
函数将此对象放在剪贴板上。 QClipboard
类可以通过dataChanged()
信号通知应用程序它包含的数据的更改。 例如,我们可以通过将此信号连接到窗口中的槽来监视剪贴板:
connect(clipboard, SIGNAL(dataChanged()), this, SLOT(updateClipboard()));
连接到此信号的槽可以使用表示它的MIME类型之一读取剪贴板上的数据:
void ClipWindow::updateClipboard()
{
QStringList formats = clipboard->mimeData()->formats();
QByteArray data = clipboard->mimeData()->data(format);
...
}
selectionChanged()
信号可用于X11以监控鼠标选择。
Examples
与其他应用程序交互
在X11上,使用公共XDND协议,而在Windows上,Qt使用OLE标准,而对于macOS,Qt使用Cocoa Drag Manager。 在X11上,XDND使用MIME,因此不需要翻译。 无论平台如何,Qt API都是相同的。 在Windows上,MIME感知应用程序可以使用MIME类型的剪贴板格式名称进行通信。 一些Windows应用程序已经为其剪贴板格式使用MIME命名约定。
可以通过在Windows上重新实现QWinMime或在macOS上重新实现QMacPasteboardMime
来注册用于翻译专有剪贴板格式的自定义类。
上一篇: Sed综合练习题