Qt5.2文档翻译:拖放,Drag and Drop
拖放,提供了一种简单的可视化的机制,让用户可以在不同程序之间传递信息。拖放,在功能上与剪贴板的剪切/粘贴机制类似。
此文档说明了基本的拖放机制,并且简要说明 了在自定义控件中启用 此功能的过程。Qt 中的狠多控件都支持拖放操作,例如那些条目视图 和图形视图框架、Qt Widgets 和Qt Quick 中的那些编辑控件。 在 在条目视图中使用拖放 和 图形视图框架 中可阅读到更多关于条目视图和图形视图的信息。
这些类用来处理拖放操作以及必要的多媒体类型编码/解码。
支持基于多媒体类型编码的拖放数据传递 |
|
当拖放动作完毕时,会发送这个事件 |
|
当拖放动作进入某个部件时,会向该部件发送这个事件 |
|
当某个拖放动作正在进行当中时,会发送这个事件 |
|
当拖放动作离开某个部件时,会向该部件发送这个事件 |
QStyleHints 对象提供 了一些与拖放操作相关的属性:
•. QStyleHints::startDragTime () ,用户需要 在某个对象上将鼠标按钮按住这么长时间,才会启动 一个拖放过程,以毫秒为单位。
•. QStyleHints::startDragDistance () ,用户需要 在按住鼠标按钮 的情况下 将鼠标移动这么远的距离,才会使得 此次移动 被解释为拖放动作。
•. QStyleHints::startDragVelocity () ,用户需要以这么快的速度 (像素/ 秒 ) 移动鼠标 ,才会启动拖放操作 。如果 值为 0 则表示不设此限制。
这些属性,提供了与底层窗口系统相兼容的有意义的默认值,可帮助妳在自己的控件中提供对拖放功能的支持。
此文档中剩下的部分专注于说明如何在C++中实现拖放。 欲了解关于Qt Quick场景中拖放的相关信息,则阅读Qt Quick的 Drag 、 DragEvent 和 DropArea 条目的文档。另外 还有一个示例。
要启动一个拖放过程,则创建一个 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 子类的构造函数中启用了拖放事件,使得 它可以实现拖放事件处理 器函数:
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 :
void Window::dropEvent( QDropEvent *event)
{
textBrowser->setPlainText(event->mimeData()->text());
mimeTypeCombo->clear();
mimeTypeCombo->addItems(event->mimeData()->formats());
event->acceptProposedAction();
}
在这个示例中,我们 不检查 所传递的内容就接收了预期的动作。 在实际的程序中,如果 该动作是与自身不相关的,则,可能 有必要 从 dropEvent() 函数中退出,并且不接受预期的动作,也不处理该数据。例如,假设 我们的程序中不支持指向外部数据的链接的话,则,我们可能选择忽略 Qt::LinkAction 动作。
我们还可 以 忽略掉事件中附带的预期动作,而对该数据做一些别的动作。 要做到这一点的话, 我们需要 在调用 accept() 之前调用 该事件对象的 setDropAction() 函数,并且带上 Qt::DropAction 中的适当的动作。 这样就能确保, 所替换进去的放下动作能够真正地替换掉原来预期的动作。
对于 更高端的程序呢, 可以覆盖 dragMoveEvent() 和 dragLeaveEvent() , 这样, 妳的某些部件就会对拖放事件更敏感, 妳就能够在自己的程序中对于拖放操作有更全面的控制。
某些标准 的 Qt部件自身 就提供了对于拖放操作的支持。 当妳对这些部件进行子类化时,除了覆盖 dragEnterEvent() 和 dropEvent() 函数之外,妳可能还需要覆盖 dragMoveEvent() 函数, 以阻止基类提供自身的默认拖放操作处理功能,并且处理那些 妳感兴趣的特殊情况。
在最简单的情况下,拖放 动作的目标会收到 被拖放的数据的一份副本, 而数据的来源自行决定 是否要删除原始数据。 这是由 CopyAction 动作来描述的。目标可能 还会处理其它动作,尤其是 MoveAction 和 LinkAction 动作。如果数据来源调用 了 QDrag::exec (),并且返回 MoveAction ,则,数据来源应当 在它认为必要的时候删除原始数据。 由数据来源部件创建的 QMimeData 和 QDrag 对象 不应当被删除 —— 它们会由 Qt 来删除。拖放 的目标部件要负责保管 好拖放操作 中发送的数据;通常 的做法是,保持 对该数据的引用。
如果目标部件理解 LinkAction 动作, 则, 它应当 对原始信息储存一份引用;数据来源 不需要对该数据做任何额外的操作。拖放动作 的最常见用途就是在同一个部件内部进行 移动 ;阅读 放下动作 小节 ,以了解关于这个特性的更多信息。
拖放动作的另一个主要用途就是,使用某种引用类型,例如text/uri-list。在这种情况下,被拖放的数据实际上是指向某些文件或对象的引用。
拖放操作 不仅限于文本和图片内容。任何类型 的信息都可以在拖放操作中传递。 为了在不同程序之间拖放信息,各个程序 都必须能够 相互之间告知,它们能够接受哪些数据格式 ,以及它们能够产生哪些数据格式。 这是利用 多媒体类型 来实现的。 由数据来源部件构造的那个 QDrag 对象,包含 了 一 个用来表达该数据的多媒体类型的列表( 顺序是,最适当的在最前,最不适当的在最后 ),拖放目标部件使用其中 的一个来访问到该数据。对于常见 的数据类型,那些便利 性函数就能够透明地处理好这些多媒体类型,但是,对于 自定义数据类型, 就有必要显式声明它们了。
要想为某种 不被 QDrag 的便利函数支持的信息提供拖放操作支持的话,第一 个同时也是最重要的一个步骤就是,寻找 一些适当的已有的格式:互联网 已分配号码管理局(The Internet Assigned Numbers Authority ( IANA ))提供 了一个 多媒体类型层级列表 ,用于信息科学学会 (Information Sciences Institute ( ISI ))。使用标准 的多媒体类型, 就能够最大化地提升妳的程序与其它软件在现在及将来进行互操作的可能性。
要支持某种额外的媒体类型,只需简单地使用 setData() 函数将数据设置给 QMimeData 对象,并且传入两个参数 :完整 的多媒体类型和一个 以适当的格式包含着 该数据的 QByteArray 。 以下代码, 从一个文本标签中取出 一张位图,然后 将它 以一个 可移植网络图形 (Portable Network Graphics (PNG))文件的形式储存在一个 QMimeData 对象中:
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 对象中存入的数据量。
注意 ,对于在条目视图中使用的自定义数据类型,必须 被声明为 元对象 ,并且,必须实 现针对它们的流操作符。
在剪贴板模型中,用户可以 剪切 或 复制 源信息,日后再粘贴它。类似地,在拖放模型中,用户可以将信息的一个 副本 拖走,或者,可将信息本身拖动到一个新的位置( 移动 它)。拖放模型,对于程序猿来说,有一点额外的复杂因素:只有在操作完成之后,程序才能够得知用户究竟是想要剪切还是复制该信息。当用户在不同程序之间拖放信息时,这两种结果通常不会有什么区别,但是,当用户在同一个程序中拖放时,就有必要检查一下究竟是使用了哪个放下动作。
我们可以覆盖某个部件的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();
}
当在某个部件中发生放下操作时,dropEvent()处理器函数会被调用,然后,我们就可以按顺序处理每个可能的动作。首先,我们处理同一个部件内部的拖放操作:
void DragWidget::dropEvent( QDropEvent *event)
{
if (event->source() == this && event->possibleActions() & Qt ::MoveAction)
return;
在这种情况下,我们拒绝执行移动操作。对于我们接受的每种类型的放下动作,我们都会检查,并且做出对应的处理:
if (event->proposedAction() == Qt ::MoveAction) {
event->acceptProposedAction();
// 处理事件 中的数据。
} else if (event->proposedAction() == Qt ::CopyAction) {
event->acceptProposedAction();
// 处理事件 中的数据。
} else {
// 忽略这个放下动作。
return;
}
...
}
注意 ,我们在上面的代码中对不同的放下动作进行了检查。正如前面 在 覆盖预期 的动作 小节中所说, 有些时候, 有必要覆盖掉预期的放下动作,而在可能的放下动作选择范围内选择某个不同的动作。 要想做到这一点,妳需要 在该事件的 possibleActions() 所返回的值中检查每个动作的存在性,使用 setDropAction() 来设置新的放下动作,然后调用 accept() 。
部件 的 dragMoveEvent()函数 可用来 将放下区域限制在 该部件内部一个特定区域 中,具体做法就是,只有当鼠标指针位于那些区域 中时才接受预期 的放下动作。例如, 在以下代码中,产生 的效果是,只有 当鼠标指针在某个子代部件区域( dropFrame )中时,才接受任何的预期放下动作:
void Window::dragMoveEvent( QDragMoveEvent *event)
{
if (event->mimeData()->hasFormat("text/plain")
&& event->answerRect().intersects(dropFrame->geometry()))
event->acceptProposedAction();
}
如果妳需要在拖放操作过程中提供视觉反馈的话,也可使用dragMoveEvent()函数,例如,滚动窗口,或者做别的什么反馈。
程序之间 也可以通过向剪贴板中放入数据 的方式来进行通信。 要想做到这一点,妳需要从 QApplication 对象中获取到一个 QClipboard 对象:
clipboard = QApplication ::clipboard();
QMimeData 类用来表达那些通过剪贴板传递的数据。 要想向剪贴板中放入数据, 妳可以使用 setText() 、 setImage() 和 setPixmap() 这些用于支持常见数据类型的便利函数。 这些函数与 QMimeData 类中的同名函数类似,只有 一个不同之处:它们 还接受一个额外的参数,控制着数据的储存位置:如果指定 为 Clipboard ,则数据被存储在剪贴板中;如果指定 为 Selection ,则数据被存储在鼠标选中内容存储 区( 只支持X11 )中。默认情况 下,数据是存储在剪贴板中。
例如, 我们可以使用以下代码来将某个 QLineEdit 的内容复制到剪贴板中:
clipboard->setText(lineEdit->text(), QClipboard ::Clipboard);
具有 不同多媒体类型的数据也可被放入剪贴板。构造 一个 QMimeData 对象,然后按照 前一小节中说明的方式使用setData()函数 来设置数据;然后 ,可使用 setMimeData() 函数来将这个对象放入剪贴板。
QClipboard 类可通过 dataChanged() 信号来向程序告知,它所包含的数据发生了变化。例如, 我们可将这个信号连接到某个部件的一个信号槽上,以监听剪贴板:
connect(clipboard, SIGNAL(dataChanged()), this, SLOT(updateClipboard()));
被连接到这个信号的那个信号槽,可使用其中一种能够表达该数据的多媒体类型来读出剪贴板中的数据:
void ClipWindow::updateClipboard()
{
QStringList formats = clipboard->mimeData()->formats();
QByteArray data = clipboard->mimeData()->data(format);
...
}
在 X11 上,可使用 selectionChanged() 信号来监听鼠标选中内容的变化。
在 X11 上,会使用公有的 X窗口拖放协议 ;在 Windows 上, Qt使用OLE标准 ; 在 Mac OS X 上,Qt 使用Cocoa Drag Manager。 在 X11 上, XDND 会使用 MIME ,所以,不需要 做协议 翻译。 Qt API 在不同平台上都是相同的。 在 Windows 上,兼容 MIME 的程序可以使用符合MIME 类型标准 的剪贴板格式名字来互相通信。已经 有一些 Windows程序使用MIME 的命名习惯 来表达剪贴板格式。 在内部实现中, Qt使用QWindowsMime 和 QMacPasteboardMime 来 在 私有的剪贴板格式 和MIME 类型之间进行翻译。
纹身
春节期间,进餐时,晚辈不要先筷,要等长辈拍完照,才能夹菜。
Your opinionsHxLauncher: Launch Android applications by voice commands