Qt的拖拽功能
拖放提供了一种简单的可视化机制,用户可以使用它在应用程序之间和应用程序内部传输信息。拖放在功能上类似于剪贴板的剪切和粘贴机制。
Qt提供了以下几个类来完成拖放工作:
类 | 功能 |
---|---|
QDrag | 支持进行MIME类型数据拖拽 |
QDragEnterEvent | 当拖放操作进入部件时会发送该事件 |
QDragLeaveEvent | 当拖放操作离开部件时会发送该事件 |
QDragMoveEvent | 当拖放动作在部件中移动时会发送该事件 |
QDropEvent | 当拖放操作完成时会发送该事件 |
Qt提供了以下几个函数获取一些拖放操作相关的属性:
函数 | 功能 |
---|---|
QStyleHints::startDragTime() | 描述了在拖动开始时,用户需要在对象上停留的鼠标时间 |
QStyleHints::startDragDistance() | 指示用户在按住鼠标按钮时需要移动鼠标的距离,这样移动才会被解释为拖动 |
QStyleHints::startDragVelocity() | 指示用户开始拖动时移动鼠标的速度(以像素/秒为单位)。值为0表示没有这样的限制 |
拖拽
当我们想要使用拖操作时,我们需要创建QDrag
对象,并调用exec()
函数来进行工作,例如在鼠标按下事件中创建QDrag
对象进入拖拽操作。
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("dragging");
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec();
}
}
当我们调用exec()
函数后,当前拖拽操作会阻塞(并不会阻塞主事件循环),最后我们结束拖拽后会返回Qt::DropAction
枚举,他表示了我们是如何结束的拖拽动作
动作 | 功能 |
---|---|
Qt::CopyAction | 将数据复制到目标当中 |
Qt::MoveAction | 将数据移动到目标 |
Qt::LinkAction | 创建从源到目标的链接 |
Qt::IgnoreAction | 忽略操作(不处理数据) |
Qt::TargetMoveAction | 在Windows上,当D&D数据的所有权应该被目标应用程序接管时,即源应用程序不应该删除数据时,使用这个值 |
我们也可以在鼠标移动事件中去进行拖拽操作
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);
}
放下
如果需要将某元素拖拽到部件中,需要在部件中设置setAcceptDrops(true)
,并重新实现dragEnterEvent()
和dropEvent()
事件处理函数。
Window::Window(QWidget *parent)
: QWidget(parent)
{
setAcceptDrops(true);
}
void Window::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("text/plain"))
event->acceptProposedAction();
}
void Window::dropEvent(QDropEvent *event)
{
textBrowser->setPlainText(event->mimeData()->text());
mimeTypeCombo->clear();
mimeTypeCombo->addItems(event->mimeData()->formats());
event->acceptProposedAction();
}
添加新的拖拽或放下类型
拖拽操作不仅可以支持文本和图像,也可以支持任何类型的信息。如果想在应用程序通信中,使用拖拽信息,就需要指示拖拽可接受的数据格式以及它们能够产生的数据格式,这些操作是通过创建MIME类型数据来实现的。下面是实现一个MIME数据类型的示例。
QMimeData *mimeData = new QMimeData;
QByteArray output;
QBuffer outputBuffer(&output);
outputBuffer.open(QIODevice::WriteOnly);
//方法1
imageLabel->pixmap()->toImage().save(&outputBuffer, "PNG");
mimeData->setData("image/png", output);
//方法2
mimeData->setImageData(QVariant(*imageLabel->pixmap()));
可通过hasFormat(const QString &mimeType)
函数获取当前mime对象是否包含此类型数据,使用formats()
函数获取当前mime对象支持的格式
Qt官方示例
qt提供了以下有关于拖拽的官方示例
Draggable Icons
示例演示了如何在同一应用程序中的小部件之间以及在不同应用程序之间拖放图像数据。
主要展示了与拖拽事件有关的几个函数:
- dragEnterEvent
- dragMoveEvent
- dropEvent
- exec
class DragWidget : public QFrame
{
public:
explicit DragWidget(QWidget *parent = nullptr);
protected:
//拖拽进入、移动、放下、鼠标按下事件
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
};
第一步鼠标按下事件
void DragWidget::mousePressEvent(QMouseEvent *event)
{
//获取当前鼠标按下位置的标签
QLabel *child = static_cast<QLabel*>(childAt(event->pos()));
if (!child)
return;
QPixmap pixmap = *child->pixmap();
QByteArray itemData;
QDataStream dataStream(&itemData, QIODevice::WriteOnly);
//将数据进行反序列化用于存在QMimeData
dataStream << pixmap << QPoint(event->pos() - child->pos());
QMimeData *mimeData = new QMimeData;
//设置属性,类似于键值对,通过"application/x-dnditemdata",去拿到数据元素
mimeData->setData("application/x-dnditemdata", itemData);
/*****************************进行拖拽************************************/
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(pixmap);
//设置热点位置
drag->setHotSpot(event->pos() - child->pos());
//绘制一个灰白色的朦层
QPixmap tempPixmap = pixmap;
QPainter painter;
painter.begin(&tempPixmap);
painter.fillRect(pixmap.rect(), QColor(127, 127, 127, 127));
painter.end();
child->setPixmap(tempPixmap);
//会进行阻塞,但不会阻塞主进程事件循环,依旧会进入到dragEnterEvent,最后执行到dropEvent后,exec会进行返回
if (drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction) == Qt::MoveAction) {
child->close();
} else {
child->show();
child->setPixmap(pixmap);
}
/*************************************************************************************/
}
第二步进入拖拽事件
void DragWidget::dragEnterEvent(QDragEnterEvent *event)
{
qDebug() << QMetaEnum::fromType<QEvent::Type>().valueToKey((int)event->type());
if (event->mimeData()->hasFormat("application/x-dnditemdata")) {
if (event->source() == this) { //判断是否当前widget,如果不是则将事件定义为移动事件
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->acceptProposedAction();
}
} else {
event->ignore();
}
}
第三步进行移动
void DragWidget::dragMoveEvent(QDragMoveEvent *event)
{
qDebug() << "dragMoveEvent : " <<QMetaEnum::fromType<QEvent::Type>().valueToKey((int)event->type());
if (event->mimeData()->hasFormat("application/x-dnditemdata")) {
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->acceptProposedAction();
}
} else {
event->ignore();
}
}
最后将拖拽对象放下
void DragWidget::dropEvent(QDropEvent *event)
{
qDebug() << "dropEvent : " <<QMetaEnum::fromType<QEvent::Type>().valueToKey((int)event->type());
if (event->mimeData()->hasFormat("application/x-dnditemdata")) {
QByteArray itemData = event->mimeData()->data("application/x-dnditemdata");
QDataStream dataStream(&itemData, QIODevice::ReadOnly);
QPixmap pixmap;
QPoint offset;
dataStream >> pixmap >> offset;
QLabel *newIcon = new QLabel(this);
newIcon->setPixmap(pixmap);
newIcon->move(event->pos() - offset);
newIcon->show();
newIcon->setAttribute(Qt::WA_DeleteOnClose);
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->acceptProposedAction();
}
} else {
event->ignore();
}
}
Draggable Text
示例演示了如何在小部件之间拖放文本。
Drop Site
该示例展示了如何区分拖放操作中可用的各种MIME格式。
Fridge Magnets
示例说明了如何通过拖放来移动几种mime编码的数据。
Drag and Drop Puzzle
拖放拼图示例演示了一种使用项目视图小部件的拖放系统的方法。