Qt 支持自定义数据类型的托拽(Supporting Custom Drag Types)

时间:2022-11-13 16:26:06
我们使用类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直接得到表格中拖动数据。如果没有得到正确的指针,则按照标准方式处理。