前话
Qt中的拽拖操作详细介绍。
Demo
图片拽拖
控件拽拖
窗口拽拖
拽托框架(高级开发)
拖放(Drag and Drop)
拖放提供了一种简单的可视机制,用户可以使用它在应用程序之间和内部传输信息。拖放的功能类似于剪贴板的剪切和粘贴机制。
本文档描述了基本的拖放机制,并概述了在自定义控件中启用该机制的方法。许多qt的控件也支持拖放操作,例如项目视图和图形视图框架,以及为qt小部件和qt quick编辑控件。有关项目视图和图形视图的详细信息,请参见使用项目视图和图形视图框架的拖放。
拖放类
这些类处理拖放和必要的mime类型编码和解码。
配置
QStyleHints对象提供了一些与拖放操作相关的属性:
- QStyleHints::startDragTime():描述在开始拖动之前,用户必须在对象上按住鼠标按钮的时间量(毫秒)。
- QStyleHints::StartDragDistance():表示在移动被解释为拖动之前,按住鼠标按钮时用户必须移动鼠标的距离。
- QStyleHints::StartDragVelocity():表示用户移动鼠标开始拖动的速度(以像素/秒为单位)。值为0表示没有这样的限制。
如果在控件中提供拖放支持,这些数量将提供与基础窗口系统兼容的合理默认值,供您使用。
在Qt Quick种的拖放
文档的其余部分主要关注如何在C++中实现拖放。要在Qt快速场景中使用拖放,请阅读Qt Quick拖放、DragEvent和DropArea项的文档,以及Qt快速拖放示例。
拖曳(Dragging)
要开始拖动,请创建一个QDrag对象,并调用其exec()函数。在大多数应用程序中,只有在按下鼠标按钮并移动光标一定距离后,才能开始拖放操作。但是,启用小部件拖动最简单方法是重新实现小部件的mousePressEvent(),并启动拖放操作:
尽管用户可能需要一些时间来完成拖动操作,但就应用程序而言,exec()函数是一个带有多个值之一的阻塞函数。这些说明操作是如何结束的,下面将详细介绍。
注意,exec()函数不会阻塞主事件循环。
对于需要区分鼠标单击和拖动的小部件,重新实现小部件的mousePressEvent()函数以记录拖动的开始位置是很有用的:
稍后,在mouseMoveEvent()中,我们可以确定是否应该开始拖动,并构造一个拖动对象来处理该操作:
这种特殊的方法使用QPoint::manhattanlength()函数粗略估计鼠标单击位置和当前光标位置之间的距离。此函数以精度换取速度,通常适用于此目的。
放下(Dropping)
要能够接收小部件上丢弃的媒体,请为小部件调用setAcceptDrops(true),并重新实现dragEnterEvent()和dropEvent()事件处理程序函数。
例如,以下代码启用了QWidget子类的构造函数中的Drop事件,从而可以有效地实现Drop事件处理程序:
dragEnterEvent()通常用于通知qt小部件接受的数据类型。如果要在DragMoveEvent()和dropEvent()的重新实现中接收QDragMoveEvent或QDropEvent,则必须重新实现此函数。
报错
下面的代码显示如何重新实现DragEnterEvent(),以告诉拖放系统我们只能处理纯文本:
dropEvent()用于解包丢弃的数据,并以适合您的应用程序的方式对其进行处理。
在以下代码中,事件中提供的文本将传递给QTextBrowser,QComboBox将填充用于描述数据的mime类型列表:
在这种情况下,我们接受建议的操作,而不检查它是什么。在实际应用程序中,可能需要从dropEvent()函数返回,而不接受建议的操作,或者在操作不相关的情况下处理数据。例如,如果我们不支持到应用程序中外部源的链接,我们可以选择忽略Qt::LinkAction操作。
覆盖提议的行动
也可以忽略提议的操作,并对数据执行其他操作。为此,我们将在调用accept()之前使用Qt::dropAction中的首选操作调用事件对象的setDropAction()。这样可以确保使用替换删除操作而不是建议的操作。
对于更复杂的应用程序,重新实现dragMoveEvent()和dragLeaveEvent()将使小部件的某些部分对放置事件敏感,并使您能够更好地控制应用程序中的拖放。
复杂小部件的子类化
某些标准Qt小部件为拖放提供了自己的支持。在对这些小部件进行子类化时,除了DragCenterEvent()和DropEvent()之外,可能还需要重新实现DragMoveEvent(),以防止基类提供默认的拖放处理,并处理您感兴趣的任何特殊情况。
拖放操作
在最简单的情况下,拖放操作的目标将接收正在拖动的数据的副本,源将决定是否删除原始数据。这由CopyAction操作描述。目标还可以选择处理其他操作,特别是MoveAction和LinkAction操作。如果源调用QDrag::exec(),并返回MoveAction,则如果源选择删除任何原始数据,则该源将负责删除。不应删除源小部件创建的QMimeData和QDrag对象-它们将被Qt销毁。 目标负责获取在拖放操作中发送的数据的所有权;这通常通过保留对数据的引用来实现。
如果目标理解LinkAction操作,它应该存储自己对原始信息的引用;源不需要对数据执行任何进一步的处理。拖放操作的最常见用法是在同一个小部件中执行移动;有关此功能的详细信息,请参阅有关拖放操作的部分。
拖动操作的另一个主要用途是在使用引用类型(如text/uri-list)时,其中拖动的数据实际上是对文件或对象的引用。
添加新的拖放类型
拖放不限于文本和图像。任何类型的信息都可以在拖放操作中传输。要在应用程序之间拖动信息, 应用程序必须能够相互指示可以接受哪些数据格式以及可以生成哪些数据格式,这是通过使用mime类型实现的。 由源构造的QDrag对象包含一个用于表示数据的mime类型列表(从最合适的到最不合适的顺序排列),drop目标使用其中一个来访问数据。对于常见的数据类型,便利函数处理透明使用的mime类型,但是对于自定义数据类型,必须显式地声明它们。
要对QDrag便利功能未涵盖的信息类型执行拖放操作,第一步也是最重要的一步是查找适当的现有格式:Internet分配号码管理局(IANA)在信息科学研究所(ISI)提供了MIME媒体类型的分层列表。使用标准的mime类型可以最大限度地提高应用程序与其他软件现在和将来的互操作性。
要支持其他媒体类型,只需使用setData()函数设置QMimeData对象中的数据,提供完整的mime类型和以适当格式包含数据的QByteArray。以下代码从标签中获取QPixmap,并将其存储为QMimeData对象中的可移植网络图形(PNG)文件:
对于这种情况,我们可以简单地使用setImageData()来提供各种格式的图像数据:
在这种情况下,QByteArray方法仍然很有用,因为它可以更好地控制QMimeData对象中存储的数据量。
请注意,在项视图中使用的自定义数据类型必须声明为元对象,并且必须实现它们的流运算符。
放下动作
在剪贴板模型中,用户可以剪切或复制源信息,然后粘贴它。同样,在拖放模型中,用户可以拖动信息的副本,也可以将信息本身拖动到新位置(移动信息)。拖放模型对于程序员来说还有一个额外的复杂之处:在操作完成之前,程序不知道用户是否想要剪切或复制信息。在应用程序之间拖动信息时,这通常没有什么区别,但在应用程序中,检查使用了哪个放置操作是很重要的。
可以为一个小部件重新实现mouseMoveEvent(),并通过可能的拖放操作组合启动拖放操作。例如,可能希望确保拖动始终移动小部件中的对象:
如果信息被放到另一个应用程序中,exec()函数返回的操作可能默认为copyAction,但是如果信息被放到同一个应用程序中的另一个小部件中,我们可能会获得不同的drop操作。
可以在小部件的dragMoveEvent()函数中筛选建议的放置操作。但是,可以接受DragEnterEvent()中所有建议的操作,并让用户稍后决定要接受哪个操作:
当小部件中发生放置时,将调用DropEvent()处理程序函数,我们可以依次处理每个可能的操作。首先,我们在同一个小部件中处理拖放操作:
在这种情况下,拒绝处理移动操作。接受的每种类型的跌落动作都会进行相应的检查和处理:
注意,在上面的代码中检查了单独的放置操作。如上所述,在覆盖建议的操作部分,有时需要覆盖建议的删除操作,并从可能的删除操作中选择不同的操作。为此,需要检查事件的possibleActions()提供的值中是否存在每个操作,使用setDropAction()设置Drop操作,并调用accept()。
矩形下落
小部件的dragMoveEvent()可用于通过仅在光标位于这些区域内时接受建议的放置操作来限制小部件的某些部分的放置。例如,当光标位于子小部件(DropFrame)上时,以下代码接受任何建议的放置操作:
如果您需要在拖放操作期间提供视觉反馈、滚动窗口或任何适当的操作,也可以使用DragMoveEvent()。
剪切板
应用程序还可以通过将数据放在剪贴板上进行通信。要访问这个,您需要从QApplication对象获取一个QClipboard对象。
QMimedata类用于表示在剪贴板中传输的数据。要将数据放在剪贴板上,可以使用setText()、setImage()和setPixmap()方便函数来处理常见的数据类型。这些函数与在QMimedata类中找到的函数类似,只是它们还带有一个控制数据存储位置的附加参数:如果指定了剪贴板,则数据将放置在剪贴板上;如果指定了选择,则数据将放置在鼠标选择中(仅在x11上)。默认情况下,数据放在剪贴板上。
例如,我们可以使用以下代码将QLineEdit的内容复制到剪贴板:
具有不同mime类型的数据也可以放在剪贴板上。构造一个qmimedata对象,并使用setData()函数按照前面部分描述的方式设置数据;然后可以使用setmimedata()函数将该对象放到剪贴板上。
QClipboard类可以通过其dataChanged()信号通知应用程序它所包含的数据的更改。例如,我们可以通过将此信号连接到小部件中的插槽来监视剪贴板:
连接到此信号的插槽可以使用可用于表示该信号的MIME类型之一读取剪贴板上的数据:
selectionChanged()信号可用于x11以监视鼠标选择。
与其他应用程序互操作
在x11上,使用公共XDND协议,而在Windows Qt上使用OLE标准,而Qt for MacOS使用Cocoa拖动管理器。在x11, XDND使用MIME,因此不需要翻译。无论平台如何,QT API都是相同的。在Windows上,支持MIME的应用程序可以使用MIME类型的剪贴板格式名称进行通信。一些Windows应用程序已经为其剪贴板格式使用了MIME命名约定。
用于转换专用剪贴板格式的自定义类可以通过在Windows上重新实现QwinMime或在MacOS上重新实现QMacPasteboardMime来注册。