Qt学习之路(54): 自定义拖放数据对象

前面的例子都是使用的系统提供的拖放对象 QMimeData 进行拖放数据的存储,比如使用 QMimeData::setText() 创建文本,使用 QMimeData::urls() 创建 URL 对象。但是,如果你希望使用一些自定义的对象作为拖放数据,比如自定义类等等,单纯使用 QMimeData 可能就没有那么容易了。为了实现这种操作,我们可以从下面三种实现方式中选择一个:

  1. 将自定义数据作为 QByteArray 对象,使用 QMimeData::setData() 函数作为二进制数据存储到 QMimeData 中,然后使用 QMimeData::Data() 读取;
  2. 继承 QMimeData,重写其中的 formats() 和 retrieveData() 函数操作自定义数据;
  3. 如果拖放操作仅仅发生在同一个应用程序,可以直接继承 QMimeData,然后使用任意合适的数据结构进行存储。

第一种方法不需要继承任何类,但是有一些局限:即是拖放不会发生,我们也必须将自定义的数据对象转换成 QByteArray 对象;如果你希望支持很多种拖放的数据,那么每种类型的数据都必须使用一个 QMimeData 类,这可能会导致类爆炸;如果数据很大的话,这种方式可能会降低系统的可维护性。然而,后两种实现方式就不会有这些问题,或者说是能够减小这种问题,并且能够让我们有完全控制权。

我们先来看一个应用,使用 QTableWidget 来进行拖放操作,拖放的类型包括 plain/text,plain/html 和 plain/csv。如果使用第一种实现方法,我们的代码将会如下所示:

  1. void MyTableWidget::mouseMoveEvent(QMouseEvent *event)
  2. {
  3. if (event->buttons() & Qt::LeftButton) {
  4. int distance = (event->pos() - startPos).manhattanLength();
  5. if (distance >= QApplication::startDragDistance())
  6. performDrag();
  7. }
  8. QTableWidget::mouseMoveEvent(event);
  9. }
  10. void MyTableWidget::performDrag()
  11. {
  12. QString plainText = selectionAsPlainText();
  13. if (plainText.isEmpty())
  14. return;
  15. QMimeData *mimeData = new QMimeData;
  16. mimeData->setText(plainText);
  17. mimeData->setHtml(toHtml(plainText));
  18. mimeData->setData("text/csv", toCsv(plainText).toUtf8());
  19. QDrag *drag = new QDrag(this);
  20. drag->setMimeData(mimeData);
  21. if (drag->exec(Qt::CopyAction | Qt::MoveAction) == Qt::MoveAction)
  22. deleteSelection();
  23. }

对于这段代码,我们应该已经很容易的理解:在 performDrag() 函数中,我们调用 QMimeData 的 setText() 和 setHTML() 函数存储 plain/text 和 plain/html 数据,使用 setData() 将 text/csv 类型的数据作为二进制 QByteArray 类型存储。

  1. QString MyTableWidget::toCsv(const QString &plainText)
  2. {
  3. QString result = plainText;
  4. result.replace("\\", "\\\\");
  5. result.replace("\"", "\\\"");
  6. result.replace("\t", "\", \"");
  7. result.replace("\n", "\"\n\"");
  8. result.prepend("\"");
  9. result.append("\"");
  10. return result;
  11. }
  12. QString MyTableWidget::toHtml(const QString &plainText)
  13. {
  14. QString result = Qt::escape(plainText);
  15. result.replace("\t", "<td>");
  16. result.replace("\n", "\n<tr><td>");
  17. result.prepend("<table>\n<tr><td>");
  18. result.append("\n</table>");
  19. return result;
  20. }

toCsv() 和 toHtml() 函数将数据取出并转换成我们需要的 csv 和 html类型的数据。例如,下面的数据

Red   Green   Blue
Cyan  Yellow  Magenta

转换成 csv 格式为:

"Red", "Green", "Blue"
"Cyan", "Yellow", "Magenta"

转换成 html 格式为:

<table>
<tr><td>Red<td>Green<td>Blue
<tr><td>Cyan<td>Yellow<td>Magenta
</table>

在放置的函数中我们像以前一样使用:

  1. void MyTableWidget::dropEvent(QDropEvent *event)
  2. {
  3. if (event->mimeData()->hasFormat("text/csv")) {
  4. QByteArray csvData = event->mimeData()->data("text/csv");
  5. QString csvText = QString::fromUtf8(csvData);
  6. // ...
  7. event->acceptProposedAction();
  8. } else if (event->mimeData()->hasFormat("text/plain")) {
  9. QString plainText = event->mimeData()->text();
  10. // ...
  11. event->acceptProposedAction();
  12. }
  13. }

虽然我们接受三种数据类型,但是在这个函数中我们只接受两种类型。至于 html 类型,我们希望如果用户将 QTableWidget 的数据拖到一个 HTML 编辑器,那么它就会自动转换成 html 代码,但是我们不计划支持将外部的 html 代码拖放到 QTableWidget 上。为了让这段代码能够工作,我们需要在构造函数中设置 setAcceptDrops(true) 和 setSelectionMode(ContiguousSelection)。

好了,上面就是我们所说的第一种方式的实现。这里并没有给出完整的实现代码,大家可以根据需要自己实现一下试试。下面我们将按照第二种方法重新实现这个需求。

  1. class TableMimeData : public QMimeData
  2. {
  3. Q_OBJECT
  4. public:
  5. TableMimeData(const QTableWidget *tableWidget,
  6. const QTableWidgetSelectionRange &range);
  7. const QTableWidget *tableWidget() const { return myTableWidget; }
  8. QTableWidgetSelectionRange range() const { return myRange; }
  9. QStringList formats() const;
  10. protected:
  11. QVariant retrieveData(const QString &format,
  12. QVariant::Type preferredType) const;
  13. private:
  14. static QString toHtml(const QString &plainText);
  15. static QString toCsv(const QString &plainText);
  16. QString text(int row, int column) const;
  17. QString rangeAsPlainText() const;
  18. const QTableWidget *myTableWidget;
  19. QTableWidgetSelectionRange myRange;
  20. QStringList myFormats;
  21. };

为了避免存储具体的数据,我们存储 table 和选择区域的坐标的指针。

  1. TableMimeData::TableMimeData(const QTableWidget *tableWidget,
  2. const QTableWidgetSelectionRange &range)
  3. {
  4. myTableWidget = tableWidget;
  5. myRange = range;
  6. myFormats << "text/csv" << "text/html" << "text/plain";
  7. }
  8. QStringList TableMimeData::formats() const
  9. {
  10. return myFormats;
  11. }

构造函数中,我们对私有变量进行初始化。formats() 函数返回的是被 MIME 数据对象支持的数据类型列表。这个列表是没有先后顺序的,但是最佳实践是将“最适合”的类型放在第一位。对于支持多种类型的应用程序而言,有时候会直接选用第一个符合的类型存储。

  1. QVariant TableMimeData::retrieveData(const QString &format,
  2. QVariant::Type preferredType) const
  3. {
  4. if (format == "text/plain") {
  5. return rangeAsPlainText();
  6. } else if (format == "text/csv") {
  7. return toCsv(rangeAsPlainText());
  8. } else if (format == "text/html") {
  9. return toHtml(rangeAsPlainText());
  10. } else {
  11. return QMimeData::retrieveData(format, preferredType);
  12. }
  13. }

函数 retrieveData() 将给出的 MIME 类型作为 QVariant 返回。参数 format 的值通常是 formats() 函数返回值之一,但是我们并不能假定一定是这个值之一,因为并不是所有的应用程序都会通过 formats() 函数检查 MIME 类型。一些返回函数,比如 text(), html(), urls(), imageData(), colorData() 和 data() 实际上都是在 QMimeData 的 retrieveData() 函数中实现的。第二个参数 preferredType 给出我们应该在 QVariant 中存储哪种类型的数据。在这里,我们简单的将其忽略了,并且在 else 语句中,我们假定 QMimeData 会自动将其转换成所需要的类型。

  1. void MyTableWidget::dropEvent(QDropEvent *event)
  2. {
  3. const TableMimeData *tableData =
  4. qobject_cast<const TableMimeData *>(event->mimeData());
  5. if (tableData) {
  6. const QTableWidget *otherTable = tableData->tableWidget();
  7. QTableWidgetSelectionRange otherRange = tableData->range();
  8. // ...
  9. event->acceptProposedAction();
  10. } else if (event->mimeData()->hasFormat("text/csv")) {
  11. QByteArray csvData = event->mimeData()->data("text/csv");
  12. QString csvText = QString::fromUtf8(csvData);
  13. // ...
  14. event->acceptProposedAction();
  15. } else if (event->mimeData()->hasFormat("text/plain")) {
  16. QString plainText = event->mimeData()->text();
  17. // ...
  18. event->acceptProposedAction();
  19. }
  20. QTableWidget::mouseMoveEvent(event);
  21. }

在放置的函数中,我们需要按照我们自己定义的数据类型进行选择。我们使用 qobject_cast 宏进行类型转换。如果成功,说明数据来自同一应用程序,因此我们直接设置 QTableWidget 相关 数据,如果转换失败,我们则使用一般的处理方式。

本文出自 “豆子空间” 博客,请务必保留此出处http://devbean.blog.51cto.com/448512/288742

时间: 2024-10-22 09:24:25

Qt学习之路(54): 自定义拖放数据对象的相关文章

Qt 学习之路:自定义事件

尽管 Qt 已经提供了很多事件,但对于更加千变万化的需求来说,有限的事件都是不够的.例如,我要支持一种新的设备,这个设备提供一种崭新的交互方式,那么,这种事件如何处理呢?所以,允许创建自己的事件 类型也就势在必行.即便是不说那种非常极端的例子,在多线程的程序中,自定义事件也是尤其有用.当然,事件也并不是局限在多线程中,它可以用在单线程的程序中,作为一种对象间通讯的机制.那么,为什么我需要使用事件,而不是信号槽呢?主要原因是,事件的分发既可以是同步的,又可以是异步的,而函数的调用或者说是槽的回调总

Qt 学习之路 :可视化显示数据库数据

前面我们用了两个章节介绍了 Qt 提供的两种操作数据库的方法.显然,使用QSqlQuery的方式更灵活,功能更强大,而使用QSqlTableModel则更简单,更方便与 model/view 结合使用(数据库应用很大一部分就是以表格形式显示出来,这正是 model/view 的强项).本章我们简单介绍使用QSqlTableModel显示数据的方法.当然,我们也可以选择使用QSqlQuery获取数据,然后交给 view 显示,而这需要自己给 model 提供数据.鉴于我们前面已经详细介绍过如何使用

Qt 学习之路 2 --- 读书笔记

一.文章来由 来自豆子老师非常好的一本Qt教程,但是只有网络版,所以用这个做笔记了,不动笔墨不读书嘛~~ 二.读书笔记 1.Qt 学习之路 2(2):Qt 简介 1.1 关于 Qt 的一站式解决 Qt 是一个著名的 C++ 应用程序框架.但并不只是一个 GUI 库,因为 Qt 十分庞大,并不仅仅是 GUI 组件.使用 Qt,在一定程度上你获得的是一个"一站式"的解决方案:不再需要研究 STL,不再需要 C++ 的,不再需要到处去找解析 XML.连接数据库.访问网络的各种第三方库,因为

Qt学习之路

  Qt学习之路_14(简易音乐播放器) Qt学习之路_13(简易俄罗斯方块) Qt学习之路_12(简易数据管理系统) Qt学习之路_11(简易多文档编辑器) Qt学习之路_10(Qt中statusBar,MessageBox和Timer的简单处理) Qt学习之路_9(Qt中Item Widget初步探索) Qt学习之路_8(Qt中与文件目录相关操作) Qt学习之路_7(线性布局和网格布局初步探索) Qt学习之路_6(Qt局域网聊天软件) Qt学习之路_5(Qt TCP的初步使用) Qt学习之路

QT学习之路(1):彩票绝对不中模拟器

//============================================//绝对不中,彩票开奖模拟器#include "mainwindow.h"#include "ui_mainwindow.h"#include <QHash>#include <QDebug>MainWindow::MainWindow(QWidget *parent) :    QMainWindow(parent),    ui(new Ui::M

QT学习之路--创建一个对话框

Q_OBJECT:这是一个宏,凡是定义信号槽的类都必须声明这个宏. 函数tr()全名是QObject::tr(),被他处理过的字符串可以使用工具提取出来翻译成其他语言,也就是做国际化使用. 对于QT学习之路:Qt学习之路(7):创建一个对话框(上)这个程序.编译出现 invalid use of incomplete type ‘class QPushButton’ findButton->setEnabled(!text.isEmpty()); ^ In file included from

Qt 学习之路:二进制文件读写

在上一章中,我们介绍了有关QFile和QFileInfo两个类的使用.我们提到,QIODevice提供了read().readLine()等基本的操作.同时,Qt 还提供了更高一级的操作:用于二进制的流QDataStream和用于文本流的QTextStream.本节,我们将讲解有关QDataStream的使用以及一些技巧.下一章则是QTextStream的相关内容. QDataStream提供了基于QIODevice的二进制数据的序列化.数据流是一种二进制流,这种流完全不依赖于底层操作系统.CP

Qt 学习之路 2笔记2

事件过滤器 QObject有一个eventFilter()函数,用于建立事件过滤器.这个函数的签名如下: virtual bool QObject::eventFilter ( QObject * watched, QEvent * event ); 事件过滤器:会检查接收到的事件.如果这个事件是我们感兴趣的类型,就进行我们自己的处理:如果不是,就继续转发.这个函数返回一个 bool 类型,如果你想将参数 event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回 false.事件

Qt 学习之路:Qt 简介

Qt 是一个著名的 C++ 应用程序框架.你并不能说它只是一个 GUI 库,因为 Qt 十分庞大,并不仅仅是 GUI 组件.使用 Qt,在一定程度上你获得的是一个“一站式”的解决方案:不再需要研究 STL,不再需要 C++ 的<string>,不再需要到处去找解析 XML.连接数据库.访问网络的各种第三方库,因为 Qt 自己内置了这些技术. Qt 是一个跨平台的框架.跨平台 GUI 通常有三种实现策略: API 映射:API 映射是说,界面库使用同一套 API,将其映射到不同的底层平台上面.大