QT官方示例 - fridgeMagnets控件拖动
官方示例地址
官方实现了一个自定义Label
控件,在widget
控件容器当中对label进行随意拖动。控件拖动实现主要围绕着几个关键事件,其中包括 mousePressEvent 、 dragEnterEvent 、 dragMoveEvent 和 dropEvent ,执行顺序是点击控件->拖动进入事件->拖动移动事件->拖动放下事件。
以下是代码分析:
mousePressEvent
首先,创建鼠标点击事件,头文件当中,+通过重写父类控件实现。
void DragWidget::mousePressEvent(QMouseEvent *event){}
鼠标点击事件块内,通过childAt()
方法,传入事件位置,定位获取到当前控件。
DragLabel *child = static_cast<DragLabel*>(childAt(event->pos())); if (!child) return;
获取热点,通过鼠标点击点相对于父控件的坐标,减去子控件左上角相对于父控件的坐标 。热点的目的是为了拖动时显示的图像中,哪个点应该与鼠标指针对齐。否则,默认是控件的左上角为鼠标指针对齐。
QPoint hotSpot = event->pos() - child->pos();
我们使用MimeData
存储控件信息,方便再拖动时获取。
QByteArray itemData; QDataStream dataStream(&itemData, QIODevice::WriteOnly); dataStream << child->labelText() << QPoint(hotSpot); QMimeData *mimeData = new QMimeData; mimeData->setData(frigetMagnetsMimeType(), itemData); mimeData->setText(child->labelText());
我们创建一个QDrag
类代表一次拖动操作,并绑定MimeData
,设置QDrag
拖动时图片为当前控件,并设置热点。
QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); drag->setPixmap(child->pixmap(Qt::ReturnByValue)); drag->setHotSpot(hotSpot);
当鼠标点击时,隐藏当前控件,模拟"控件被提起操作",发起拖动操作,等待用户在目标位置释放。 如果返回结果是 Qt::MoveAction
(即拖动完成且为移动操作), 否则显示当前控件,不执行其他操作。
child->hide(); if (drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction) == Qt::MoveAction) child->clear(); else child->show();
dragEnterEvent
拖动进入事件,代码就比较简单,首先是判断当前mimeData
是否为指定类型,再判断是否为当前子控件,如果是那么设置拖动动作为Qt::MoveAction
,否则接收默认动作。如果mimeData
是包含文本内容控件,则执行默认行为,否则忽略。
void DragWidget::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasFormat(frigetMagnetsMimeType())) { if (children().contains(event->source())) { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->acceptProposedAction(); } } else if (event->mimeData()->hasText()) { event->acceptProposedAction(); } else { event->ignore(); } }
dragMoveEvent
拖动移动事件内容与进入事件一致,没有额外操作。
void DragWidget::dragMoveEvent(QDragMoveEvent *event) { if (event->mimeData()->hasFormat(frigetMagnetsMimeType())) { if (children().contains(event->source())) { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->acceptProposedAction(); } } else if (event->mimeData()->hasText()) { event->acceptProposedAction(); } else { event->ignore(); } }
dropEvent
拖动放下事件,获取当前拖动对象的mimeData
数据,并重新实例化一个Label
对象替换之前存在的对象。
void DragWidget::dropEvent(QDropEvent *event) { if (event->mimeData()->hasFormat(frigetMagnetsMimeType())) { const QMimeData *mime = event->mimeData(); QByteArray itemData = mime->data(frigetMagnetsMimeType()); QDataStream dataStream(&itemData, QIODevice::ReadOnly); QString text; QPoint offset; dataStream >> text >> offset; DragLabel *newLabel = new DragLabel(text, this); newLabel->move(event->pos() - offset); newLabel->show(); newLabel->setAttribute(Qt::WA_DeleteOnClose); if (event->source() == this) { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->acceptProposedAction(); } } else if (event->mimeData()->hasText()) { QStringList pieces = event->mimeData()->text().split( QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts); QPoint position = event->pos(); for (const QString &piece : pieces) { DragLabel *newLabel = new DragLabel(piece, this); newLabel->move(position); newLabel->show(); newLabel->setAttribute(Qt::WA_DeleteOnClose); position += QPoint(newLabel->width(), 0); } event->acceptProposedAction(); } else { event->ignore(); } }