本页内容

拖放

拖放提供了一种简单的可视化机制,用户可通过它在大应用之间及同一应用内部传输信息。拖放的功能与剪贴板的“剪切”和“粘贴”机制类似。

本文档描述了基本的拖放机制,并概述了在自定义控件中启用该功能的方法。 许多 Qt 控件也支持拖放操作,例如项目视图和图形视图框架,以及用于Qt Widgets 和Qt Quick 的编辑控件。有关项目视图和图形视图的更多信息,请参阅《在项目视图和 图形视图框架中 使用拖放》。

拖放类

这些类负责处理拖放操作以及必要的 MIME 类型编码和解码。

QDrag

对基于 MIME 的拖放数据传输的支持

QDragEnterEvent

当拖放操作进入某个控件时,发送给该控件的事件

QDragLeaveEvent

当拖放操作离开小部件时发送给该小部件的事件

QDragMoveEvent

在拖放操作进行过程中发送的事件

QDropEvent

拖放操作完成时发送的事件

QUtiMimeConverter

在 MIME 类型与统一类型标识符 (UTI) 格式之间进行转换

配置

QStyleHints 对象提供了一些与拖放操作相关的属性:

  • QStyleHints::startDragTime() 描述了用户必须在对象上按住鼠标按钮多长时间(以毫秒为单位),拖拽操作才会开始。
  • QStyleHints::startDragDistance() 表示用户在按住鼠标按钮的同时需要移动鼠标多远,系统才会将该移动解释为拖拽。
  • QStyleHints::startDragVelocity() 表示用户必须以多快的速度(单位为像素/秒)移动鼠标才能开始拖拽。值为0 表示没有此类限制。

这些参数提供了合理的默认值,这些值符合底层窗口系统的规范,供您在控件中提供拖放支持时使用。

拖放功能在Qt Quick

本文档的其余部分主要介绍如何在 C++ 中实现拖放功能。若要在Qt Quick 场景中使用拖放功能,请阅读Qt Quick Drag 、DragEvent 以及DropArea 相关项目的文档,并参考Qt Quick 中的拖放示例。

拖拽

要开始拖动,请创建一个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() 函数是一个阻塞函数,它会返回one of several values 。这些参数表示操作的结束方式,下文将对此进行更详细的说明。

请注意,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 填充为用于描述数据的MIME类型列表:

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 ”操作,则应存储其对原始信息的引用;源小部件无需对数据进行任何进一步处理。拖放操作最常见的用途是在同一小部件内执行“移动”操作;有关此功能的更多信息,请参阅“Drop Actions”一节。

拖动操作的另一主要用途是在使用“text/uri-list”等引用类型时,此时被拖动的数据实际上是文件或对象的引用。

添加新的拖放类型

拖放操作不仅限于文本和图像。任何类型的信息都可以通过拖放操作进行传输。要在应用程序之间拖放信息,这些应用程序必须能够相互告知各自可以接受和生成的数据格式。这通过MIME 类型来实现。 源应用程序构造的 `QDrag ` 对象包含一个 MIME 类型列表,用于表示数据(按从最合适到最不合适的顺序排列),而目标应用程序则使用其中一种类型来访问数据。 对于常见数据类型,便捷函数会透明地处理所使用的 MIME 类型;但对于自定义数据类型,则必须显式指定。

要为QDrag 便捷函数未涵盖的信息类型实现拖放操作,第一步也是最重要的一步是查找合适的现有格式:互联网编号分配机构(IANA)在信息科学研究所(ISI)提供了一个分层的MIME媒体类型列表。 使用标准 MIME 类型可最大限度地提高您的应用程序与当前及未来其他软件的互操作性。

要支持额外的媒体类型,只需使用setData()函数在QMimeData 对象中设置数据,并提供完整的MIME类型以及包含适当格式数据的QByteArray 。以下代码从标签中获取一张位图,并将其作为可移植网络图形(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 对象中的数据量。

请注意,项目视图中使用的自定义数据类型必须声明为meta objects ,并且必须为其实现流操作符。

拖放操作

在剪贴板模型中,用户可以剪切或 复制源信息,然后稍后将其粘贴。同样,在拖放模型中,用户可以拖动信息的副本,也可以将信息本身拖动到新位置(即移动)。 对于程序员而言,拖放模型还存在一个额外的复杂之处:在操作完成之前,程序无法知道用户是想剪切还是复制信息。在应用程序之间拖放信息时,这通常没有区别,但在单个应用程序内部,检查使用了哪种放置操作非常重要。

我们可以为控件重新实现 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();
        // 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()设置放置操作,并调用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 类中的函数类似,不同之处在于它们还额外接受一个参数来控制数据的存储位置:如果指定Clipboard ,数据将被放入剪贴板;如果指定Selection ,数据将被放入鼠标选择区(仅限X11)。默认情况下,数据会被放入剪贴板。

例如,我们可以使用以下代码将QLineEdit 的内容复制到剪贴板:

QGuiApplication::clipboard()->setText(lineEdit->text(), QClipboard::Clipboard);

具有不同 MIME 类型的数据也可以复制到剪贴板。按照上一节所述的方法,创建一个 `QMimeData ` 对象并使用 `setData()` 函数设置数据;随后,可以通过 `setMimeData()` 函数将该对象复制到剪贴板。

QClipboard 类可以通过其dataChanged() 信号向应用程序通知其所包含数据的变更。例如,我们可以将此信号连接到小部件中的一个槽来监视剪贴板:

    connect(clipboard, &QClipboard::dataChanged,
            this, &ClipWindow::updateClipboard);

连接到该信号的槽可以使用一种用于表示剪贴板数据的 MIME 类型来读取剪贴板上的数据:

void ClipWindow::updateClipboard()
{
    mimeTypeCombo->clear();

    QStringList formats = clipboard->mimeData()->formats();
    if (formats.isEmpty())
        return;

    for (const auto &format : formats) {
        QByteArray data = clipboard->mimeData()->data(format);
        // ...
    }

在 X11 环境下,selectionChanged() 信号可用于监视鼠标选择区域。

示例

与其他应用程序的互操作

在 X11 环境下,使用公开的XDND 协议;而在 Windows 环境下,Qt 使用 OLE 标准;macOS 版的 Qt 则使用 Cocoa Drag Manager。在 X11 环境下,XDND 使用 MIME,因此无需进行转换。无论在何种平台上,Qt API 均保持一致。 在 Windows 上,支持 MIME 的应用程序可以通过使用 MIME 类型的剪贴板格式名称进行通信。一些 Windows 应用程序已经为其剪贴板格式采用了 MIME 命名约定。

在 Windows 上,可通过重写 `QWindowsMimeConverter ` 方法,或在 macOS 上重写 `QUtiMimeConverter ` 方法,注册用于转换专有剪贴板格式的自定义类。

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.