我们使用类QMimeData表示普通的MIME类型。调用QMimeData::setText()进行对文本的托拽,调用QMimeData::urls()得到托拽到url文本列表。如果我们想托拽普通的文本,HTML文本,图片,URLs,颜色等,都可以使用QMimeData类。但是 如果我们需要对自定义的类型进行托拽,就需要使用下面的方法:
1. 用QByteArray表示任意数据,调用函数QMimeData::setData(),通过QMimeData::data()得到数据。
2. 定义QMimeData子类,重新实现函数formats()和retrieveData()处理自定义的数据类型。.
3. 对于只发生在一个应用程序内部的托拽,定义QMimeData子类,在这个子类中保存任意的数据类型。
第一个方法不需要继承类,但是有些缺点:即使托拽最终不被接受,我们也需要把数据类型转换为QByteArray,如果我们希望提供的MIME类型能够和大量的应用程序进行交互,我们需要把交互的数据保存多份(每一类MIME类型保存一份)。要是交互的数据量很大,那么程序的执行速度肯定会慢下来。第二种和第三种方法能够避免或者尽量减少这些问题。
我们用一个例子说明这些方法,让QTableWidget控件支持托拽。能够进行拖动的数据类型为:text/plain,text/html,text/csv。第一种方法实现如下:
void MyTableWidget::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
int distance = (event->pos() - startPos).manhattanLength();
if (distance >= QApplication::startDragDistance())
startDrag();
}
QTableWidget::mouseMoveEvent(event);
}
void MyTableWidget::startDrag()
{
QString plainText = selectionAsPlainText();
if (plainText.isEmpty())
return;
QMimeData *mimeData = new QMimeData;
mimeData->setText(plainText);
mimeData->setHtml(toHtml(plainText));
mimeData->setData("text/csv", toCsv(plainText).toUtf8());
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
if (drag->start(Qt::CopyAction | Qt::MoveAction) == Qt::MoveAction)
deleteSelection();
}
函数mouseMoveEvent()中调用startDrag()开始进行拖动。我们调用函数setText(),setHtml()设置MIME类型为text/plain,text/html的数据,函数setData()保存text/csv类型的数据,setData()把任意的MIME数据类型保存为一个QByteArray类型的数据。函数selectionAsString()的实现与第四章中Spreadsheet::copy()相似。
QString MyTableWidget::toCsv(const QString &plainText)
{
QString result = plainText;
result.replace("//", "////");
result.replace("/"", "///"");
result.replace("/t", "/", /"");
result.replace("/n", "/"/n/"");
result.prepend("/"");
result.append("/"");
return result;
}
QString MyTableWidget::toHtml(const QString &plainText)
{
QString result = Qt::escape(plainText);
result.replace("/t", "<td>");
result.replace("/n", "/n<tr><td>");
result.prepend("<table>/n<tr><td>");
result.append("/n</table>");
return result;
}
函数toCsv()和toHtml()将tabs和newlines字符转换为vcsv格式的字符(用逗号分割数据)或者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>
使用函数QString::replace(),使转换尽可能简单。函数Qt::escape()用来解释HTML中的特殊字符。
void MyTableWidget::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasFormat("text/csv")) {
QByteArray csvData = event->mimeData()->data("text/csv");
QString csvText = QString::fromUtf8(csvData);
...
event->acceptProposedAction();
} else if (event->mimeData()->hasFormat("text/plain")) {
QString plainText = event->mimeData()->text();
...
event->acceptProposedAction();
}
}
虽然我们提供了三种不同的数据格式,但在dropEvent()中我们只接受了其中的两种。如果用户从QTableWidget的一个网格中拖动一串HTML字符到一个HTML编译器,则把网格中的数据转换为HTML。如果用户拖动一个HTML到QTableWidget控件中,我们不想接受它。
要让这个例子顺利实现托拽,在MyTableWidget构造函数中还要调用setAcceptDrops(true)和setSelectionMode(ContiguousSelection)。
现在我们重新实现这个例子,这次,我们用QMineData作为基类,用QMineData的子类实现数据转换,避免了QTableWidgetItem和QByteArray之间的转换。下面是子类的定义:
class TableMimeData : public QMimeData
{
Q_OBJECT
public:
TableMimeData(const QTableWidget *tableWidget,
const QTableWidgetSelectionRange &range);
const QTableWidget *tableWidget() const { return myTableWidget; }
QTableWidgetSelectionRange range() const { return myRange; }
QStringList formats() const;
protected:
QVariant retrieveData(const QString &format,
QVariant::Type preferredType) const;
private:
static QString toHtml(const QString &plainText);
static QString toCsv(const QString &plainText);
QString text(int row, int column) const;
QString rangeAsPlainText() const;
const QTableWidget *myTableWidget;
QTableWidgetSelectionRange myRange;
QStringList myFormats;
};
在类中,我们保存了一个QTableWidgetSelectionRange对象和一个QTableWidget指针,用来得到托拽的数据。函数formats()和retriveData()是对QMineData函数的重写。在构造函数中实现对私有变量的初始化。
TableMimeData::TableMimeData(const QTableWidget *tableWidget,
const QTableWidgetSelectionRange &range)
{
myTableWidget = tableWidget;
myRange = range;
myFormats << "text/csv" << "text/html" << "text/plain";
}
QStringList TableMimeData::formats() const
{
return myFormats;
}
函数formats()返回一个能够支持的MIME类型的列表。类型之间的顺序对程序没有影响,但是把“最好”的格式放在第一个是很好的习惯。支持多种格式的程序一般使用第一个能够匹配的格式。
QVariant TableMimeData::retrieveData(const QString &format,
QVariant::Type preferredType) const
{
if (format == "text/plain") {
return rangeAsPlainText();
} else if (format == "text/csv") {
return toCsv(rangeAsPlainText());
} else if (format == "text/html") {
return toHtml(rangeAsPlainText());
} else {
return QMimeData::retrieveData(format, preferredType);
}
}
指定一个MIME类型,函数retrieveData()把拖动的数据作为一个QVariant数据返回。参数format的值一般是formats()返回列表中之一。我们并没有做这样的假设,应用程序也不会在拖动时检查MIME类型。函数text(),html(),urls(),image-Data(),colorData()和data()都是由QMimeData提供的。
参数preferedType用来给出一个QVariant类型的建议,这里我们忽略这个参数由QMimeData处理。
void MyTableWidget::dropEvent(QDropEvent *event)
{
const TableMimeData *tableData =
qobject_cast<const TableMimeData *>(event->mimeData());
if (tableData) {
const QTableWidget *otherTable = tableData->tableWidget();
QTableWidgetSelectionRange otherRange = tableData->range();
...
event->acceptProposedAction();
} else if (event->mimeData()->hasFormat("text/csv")) {
QByteArray csvData = event->mimeData()->data("text/csv");
QString csvText = QString::fromUtf8(csvData);
...
event->acceptProposedAction();
} else if (event->mimeData()->hasFormat("text/plain")) {
QString plainText = event->mimeData()->text();
...
event->acceptProposedAction();
}
QTableWidget::mouseMoveEvent(event);
}
函数dropEvent()和本节前面写的dropEvent()相似。在这个函数中,我们首先把QMimeData安全转换为TableMimeData。如果qobject_cast<T>()返回了正确对指针,说明托拽发生在同一个应用程序中的MyTableWidget控件,我们根据QMimeData的API直接得到表格中拖动数据。如果没有得到正确的指针,则按照标准方式处理。