本页内容

拖放目标位置示例

该示例演示了如何在拖放操作中区分各种可用的 MIME 格式。

“Drop Site”示例的屏幕截图

“Drop Site”示例可接收来自其他应用程序的拖放操作,并显示拖放对象提供的 MIME 格式。

本示例中有两个类:DropArea 和DropSiteWindow ,以及一个main() 函数。在DropSiteWindow 中实例化了一个DropArea 对象;随后在main() 函数中调用了DropSiteWindow 对象。

DropArea 类定义

DropArea 类是QLabel 的子类,具有一个公共槽clear() 和一个changed() 信号。

class DropArea : public QLabel
{
    Q_OBJECT

public:
    explicit DropArea(QWidget *parent = nullptr);

public slots:
    void clear();

signals:
    void changed(const QMimeData *mimeData = nullptr);

此外,DropArea 还重新实现了QWidget 的四个事件处理程序:

  1. dragEnterEvent()
  2. dragMoveEvent()
  3. dragLeaveEvent()
  4. dropEvent()

关于这些事件处理程序的详细说明,请参见DropArea 类的实现部分。

protected:
    void dragEnterEvent(QDragEnterEvent *event) override;
    void dragMoveEvent(QDragMoveEvent *event) override;
    void dragLeaveEvent(QDragLeaveEvent *event) override;
    void dropEvent(QDropEvent *event) override;
};

DropArea 类的实现

在 `DropArea ` 的构造函数中,我们将 `minimum size ` 设置为 200x200 像素,将 `frame style ` 同时设置为 `QFrame::Sunken ` 和 `QFrame::StyledPanel`,并将内容对齐到中心。

DropArea::DropArea(QWidget *parent)
    : QLabel(parent)
{
    setMinimumSize(200, 200);
    setFrameStyle(QFrame::Sunken | QFrame::StyledPanel);
    setAlignment(Qt::AlignCenter);
    setAcceptDrops(true);
    setAutoFillBackground(true);
    clear();
}

此外,我们通过将acceptDrops 属性设置为true ,在DropArea 中启用拖放事件。随后,我们启用autoFillBackground 属性并调用clear() 函数。

当拖拽操作正在进行且鼠标进入DropArea 对象时,会调用dragEnterEvent() 事件处理程序。在DropSite 示例中,当鼠标进入DropArea 时,我们将该控件的文本设置为“<drop content>”,并高亮显示其背景。

void DropArea::dragEnterEvent(QDragEnterEvent *event)
{
    setText(tr("<drop content>"));
    setBackgroundRole(QPalette::Highlight);

    event->acceptProposedAction();
    emit changed(event->mimeData());
}

随后,我们在 `event` 上调用 `acceptProposedAction()` 方法,将释放操作设置为提议的操作。最后,我们发出 `changed() ` 信号,并将被释放的数据及其 MIME 类型信息作为参数传递。

对于dragMoveEvent()方法,我们只需通过acceptProposedAction()接受所提出的QDragMoveEvent 对象event 。

void DropArea::dragMoveEvent(QDragMoveEvent *event)
{
    event->acceptProposedAction();
}

DropArea 类对dropEvent()方法的实现会提取event 的MIME数据,并据此进行显示。

void DropArea::dropEvent(QDropEvent *event)
{
    const QMimeData *mimeData = event->mimeData();

mimeData 对象可以包含以下任一对象:图片、HTML 文本、Markdown 文本、纯文本或一组 URL。

    if (mimeData->hasImage()) {
        setPixmap(qvariant_cast<QPixmap>(mimeData->imageData()));
    } else if (mimeData->hasColor()) {
        const auto color = mimeData->colorData().value<QColor>();
        setText(tr("Color: %1").arg(color.name()));
        setPalette(QPalette(color));
        setBackgroundRole(QPalette::Button);
    } else if (mimeData->hasFormat(u"text/markdown"_s)) {
        setText(QString::fromUtf8(mimeData->data(u"text/markdown"_s)));
        setTextFormat(Qt::MarkdownText);
    } else if (mimeData->hasHtml()) {
        setText(mimeData->html());
        setTextFormat(Qt::RichText);
    } else if (mimeData->hasText()) {
        setText(mimeData->text());
        setTextFormat(Qt::PlainText);
    } else if (mimeData->hasUrls()) {
        QList<QUrl> urlList = mimeData->urls();
        QString text;
        for (qsizetype i = 0, count = qMin(urlList.size(), qsizetype(32)); i < count; ++i)
            text += urlList.at(i).path() + u'\n';
        setText(text);
    } else {
        setText(tr("Cannot display data"));
    }
  • 如果mimeData 包含一张图片,我们会通过setPixmap() 在DropArea 中显示它。
  • 如果 `mimeData ` 包含 HTML,我们将使用 `setText()` 显示它,并将 `DropArea` 的文本格式设置为 `Qt::RichText`。
  • 如果mimeData 包含 Markdown,则使用setText() 显示它,并将DropArea 的文本格式设置为Qt::MarkdownText 。
  • 如果mimeData 包含纯文本,我们将使用setText() 显示该内容,并将DropArea 的文本格式设置为Qt::PlainText 。如果mimeData 包含 URL,我们将遍历 URL 列表,并将它们分别显示在单独的行上。
  • 如果mimeData 包含其他类型的对象,我们会通过setText()将DropArea 的文本设置为“无法显示数据”,以告知用户。

然后,我们将DropArea 的backgroundRole 设置为QPalette::Dark ,并接受event 建议的操作。

    if (!mimeData->hasColor()) {
        setPalette({});
        setBackgroundRole(QPalette::Dark);
    }
    event->acceptProposedAction();
}

当拖拽操作正在进行且鼠标离开控件时,会调用dragLeaveEvent()事件处理程序。

void DropArea::dragLeaveEvent(QDragLeaveEvent *event)
{
    clear();
    event->accept();
}

对于DropArea 的实现,我们先调用clear() ,然后接受该事件。

clear() 函数将DropArea 中的文本设置为“<drop content>”,并将backgroundRole 设置为QPalette::Dark 。最后,它会发出changed() 信号。

void DropArea::clear()
{
    setText(tr("<drop content>"));
    setBackgroundRole(QPalette::Dark);

    emit changed();
}

DropSiteWindow 类定义

DropSiteWindow 类包含一个构造函数和一个公共槽updateFormatsTable() 。

class DropSiteWindow : public QWidget
{
    Q_OBJECT

public:
    DropSiteWindow();

public slots:
    void updateFormatsTable(const QMimeData *mimeData);
    void copy();

private:
    DropArea *dropArea;
    QLabel *abstractLabel;
    QTableWidget *formatsTable;

    QPushButton *clearButton;
    QPushButton *copyButton;
    QPushButton *quitButton;
    QDialogButtonBox *buttonBox;
};

该类还包含DropArea 的私有实例、dropArea 、QLabel 、abstractLabel 、QTableWidget 、formatsTable 、QDialogButtonBox 、buttonBox ,以及两个QPushButton 对象:clearButton 和quitButton 。

DropSiteWindow 类的实现

在DropSiteWindow 的构造函数中,我们实例化abstractLabel ,并将它的wordWrap 属性设置为true 。我们还调用adjustSize()函数,根据abstractLabel 的内容调整其大小。

DropSiteWindow::DropSiteWindow()
{
    abstractLabel = new QLabel(tr("This example accepts drags from other "
                                  "applications and displays the MIME types "
                                  "provided by the drag object."));
    abstractLabel->setWordWrap(true);
    abstractLabel->adjustSize();

接着,我们实例化dropArea 并将其changed() 信号连接到DropSiteWindow 的updateFormatsTable() 槽。

    dropArea = new DropArea;
    connect(dropArea, &DropArea::changed,
            this, &DropSiteWindow::updateFormatsTable);

现在我们配置QTableWidget 对象,即formatsTable 。其水平标题通过QStringList 对象labels 进行设置。列数设为两列,且该表格不可编辑。此外,formatTable 的水平标题经过格式化处理,以确保其第二列会向外扩展以占用可用的额外空间。

    formatsTable = new QTableWidget;
    formatsTable->setColumnCount(2);
    formatsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
    formatsTable->setHorizontalHeaderLabels({tr("Format"),  tr("Content")});
    formatsTable->horizontalHeader()->setStretchLastSection(true);

三个QPushButton 对象——clearButton 、copyButton 和quitButton ——被实例化并添加到buttonBox (一个QDialogButtonBox 对象)中。此处使用QDialogButtonBox 确保按钮以符合平台样式的布局呈现。

    clearButton = new QPushButton(tr("Clear"));
    copyButton = new QPushButton(tr("Copy"));
    quitButton = new QPushButton(tr("Quit"));

    buttonBox = new QDialogButtonBox;
    buttonBox->addButton(clearButton, QDialogButtonBox::ActionRole);
    buttonBox->addButton(copyButton, QDialogButtonBox::ActionRole);
#if !QT_CONFIG(clipboard)
    copyButton->setVisible(false);
#endif

    buttonBox->addButton(quitButton, QDialogButtonBox::RejectRole);

    connect(quitButton, &QAbstractButton::clicked, this, &QWidget::close);
    connect(clearButton, &QAbstractButton::clicked, dropArea, &DropArea::clear);
    connect(copyButton, &QAbstractButton::clicked, this, &DropSiteWindow::copy);

clicked() 方法中,针对copyButton 、clearButton 和quitButton 的信号分别连接至copy() 、clear() 和close()。

在布局方面,我们使用QVBoxLayout 和mainLayout 将控件垂直排列。同时,我们将窗口标题设置为“Drop Site”,最小尺寸设为350×500像素。

    QVBoxLayout *mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(abstractLabel);
    mainLayout->addWidget(dropArea);
    mainLayout->addWidget(formatsTable);
    mainLayout->addWidget(buttonBox);

    setWindowTitle(tr("Drop Site"));
    resize(700, 500);
}

接下来我们处理updateFormatsTable() 函数。该函数会更新formatsTable ,显示拖放到DropArea 对象上的对象的MIME格式。首先,我们将QTableWidget 的rowCount 属性设置为0。然后,我们进行验证以确保传入的QMimeData 对象是有效的。

void DropSiteWindow::updateFormatsTable(const QMimeData *mimeData)
{
    formatsTable->setRowCount(0);
    copyButton->setEnabled(false);
    if (!mimeData)
        return;

一旦确认mimeData 有效,我们就遍历其支持的格式。

注意: formats() 函数返回一个 QStringList 对象,其中包含mimeData 支持的所有格式。

    const QStringList formats = mimeData->formats();
    for (const QString &format : formats) {
        QTableWidgetItem *formatItem = new QTableWidgetItem(format);
        formatItem->setFlags(Qt::ItemIsEnabled);
        formatItem->setTextAlignment(Qt::AlignTop | Qt::AlignLeft);

在每次迭代中,我们创建一个QTableWidgetItem 、formatItem ,并将它的flags 设置为Qt::ItemIsEnabled ,将其text alignment 设置为Qt::AlignTop 和Qt::AlignLeft 。

一个QString 对象(text )经过自定义,可根据format 的内容显示数据。我们调用QString 的simplified() 函数并传入text 作为参数,以获取一个在单词前后及单词之间均无额外空格的字符串。

        QString text;
        if (format == u"text/plain") {
            text = mimeData->text().simplified();
        } else if (format == u"text/markdown") {
            text = QString::fromUtf8(mimeData->data(u"text/markdown"_s));
        } else if (format == u"text/html") {
            text = mimeData->html().simplified();
        } else if (format == u"text/uri-list") {
            QList<QUrl> urlList = mimeData->urls();
            for (qsizetype i = 0, count = qMin(urlList.size(), qsizetype(32)); i < count; ++i)
                text.append(urlList.at(i).toString() + u' ');
        } else {
            QByteArray data = mimeData->data(format);
            if (data.size() > 32)
                data.truncate(32);
            text = QString::fromLatin1(data.toHex(' ')).toUpper();
        }

如果format 包含一组 URL,我们会遍历这些 URL,并使用空格作为分隔符。另一方面,如果format 包含一张图片,我们会将文本转换为十六进制,然后显示该数据。

        int row = formatsTable->rowCount();
        formatsTable->insertRow(row);
        formatsTable->setItem(row, 0, new QTableWidgetItem(format));
        formatsTable->setItem(row, 1, new QTableWidgetItem(text));
    }

    formatsTable->resizeColumnToContents(0);
#if QT_CONFIG(clipboard)
    copyButton->setEnabled(formatsTable->rowCount() > 0);
#endif
}

在将text 定制为包含相应数据后,我们使用setItem() 将format 和text 同时插入到formatsTable 中。最后,我们对formatsTable 的第一列调用resizeColumnToContents()。

main() 函数

在main() 函数中,我们实例化DropSiteWindow 并调用其show() 函数。

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    DropSiteWindow window;
    window.show();
    return app.exec();
}

示例项目 @ code.qt.io

© 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.