QDrag简介

拖放提供了一种用户可以在应用程序之间或之内传递信息的一种简单视觉效果机制。(在术语中,这被称为“直接操作模型”。)拖放和剪贴板的剪切和粘贴的机制相似。

对于拖放的实例,请参考(随着复杂度的增加的顺序):qt/examples/iconview/simple_ddqt/examples/dragdropqt/examples/fileiconview。也可以参考QMultiLineEdit窗口部件的源码。

拖动

开始一个拖动,比如是在鼠标移动事件,创建一个适合你的媒体的QDragObject的子类的对象,比如对于文本使用QTextDrag,对于图片使用QImageDrag。然后调用drag()方法。这就是我们对现有类型简单的拖动所要做的一切。

例如,从一个窗口部件中开始拖动一些文本:

  void MyWidget::startDrag()
  {
    QDragObject *d = new QTextDrag( myHighlightedText(), this );
    d->dragCopy();
    // 不要删除d。
  }
  

注意在拖动之后,QDragObject没有被删除。这个QDragObject需要被保存直到拖放明显完成——它也许还要和其它进程通讯。最后Qt会删除这个对象。如果拥有拖动对象的窗口部件在这之前被删除,任何没有完成的放下将会被取消并且拖动对象会被删除。因为这个原因,你应该注意对象引用的是什么。

放下

为了能在一个窗口部件中接收被放下的媒体,在这个窗口部件中调用setAcceptDrops(TRUE)(比如在它的构造函数中),并且重载事件处理方法dragEnterEvent()dropEvent()。对于更复杂的应用程序,重载dragMoveEvent()dragLeaveEvent()也是必需的。

例如,为了接受被放下的文本和图片:

  MyWidget::MyWidget(...) :
    QWidget(...)
  {
    ...
    setAcceptDrops(TRUE);
  }

  void MyWidget::dragEnterEvent(QDragEnterEvent* event)
  {
    event->accept(
        QTextDrag::canDecode(event) ||
        QImageDrag::canDecode(event)
    );
  }

  void MyWidget::dropEvent(QDropEvent* event)
  {
    QImage image;
    QString text;

    if ( QImageDrag::decode(event, image) ) {
      insertImageAt(image, event->pos());
    } else if ( QTextDrag::decode(event, text) ) {
      insertTextAt(text, event->pos());
    }
  }
  

剪贴板

QDragObjectQDragEnterEventQDragMoveEventQDropEvent类都是QMimeSource(提供类型信息的类)的子类。如果你在QDragObject中基于你的数据进行传递,你不仅要使用拖放,而且要使用使用传统的剪切和粘贴——QClipboard有两个函数:

      setData(QMimeSource*)
      QMimeSource* data()const
  
使用这些函数,你就可以很容易地把你的拖放初始信息放到剪贴板中:
  void MyWidget::copy()
  {
    QApplication::clipboard()->setData(
        new QTextDrag(myHighlightedText())
    );
  }

  void MyWidget::paste()
  {
    QString text;
    if ( QTextDrag::decode(QApplication::clipboard()->data(), text) )
        insertText( text );
  }
  
你甚至可以使用 QDragObject的子类作为文件输入输出的部分。例如,如果你的程序有一个QDragObject的子类来把CAD设计编码为DXF格式,你的保存和载入代码也许可以是:
  void MyWidget::save()
  {
    QFile out(current_file_name);
    out.open(IO_WriteOnly);
    MyCadDrag tmp(current_design);
    out.writeBlock( tmp->encodedData( "image/x-dxf" ) );
  }

  void MyWidget::load()
  {
    QFile in(current_file_name);
    in.open(IO_ReadOnly);
    if ( !MyCadDrag::decode(in.readAll(), current_design) ) {
        QMessageBox::warning( this, "Format error",
            tr("The file \"%1\" is not in any supported format")
             .arg(current_file_name)
        );
    }
  }
  
注意QDragObject的子类叫做“MyCadDrag”,而不是“MyDxfDrag”——因为以后也许你要把它扩展为DXF、DWG、SVF、WMF,或者甚至是 QPicture数据到其它应用程序中。

拖放操作

在一些简单的情况下,拖放的目标接收一个被拖动的数据的复制并且由源来决定是否删除初始的拖动对象。这就是QDropEvent中的“Copy”操作。目标也可以选择理解其它操作,特别是“Move”和“Link”操作。如果目标理解了“Move”操作,目标就会对复制和删除操作负责并且源自己不会再试图删除数据。如果目标理解了“Link”操作,它存储它自己的初始信息的引用,并且源也不会删除初始信息的。最通用的拖放操作是在同一个窗口部件中执行一个“Move”——请参考下面的增强的拖放一节。

拖动操作的另一个主要用法是当使用一个引用类型,比如text/uri-list,实际被拖动的数据引用了文件或对象。

添加新的拖放类型

就像上面的DXF例子中建议的一样,拖放不仅仅局限于文本和图片。任何信息都可以被拖放。在应用程序之间拖放信息,两个应用程序必须使用它们都能接受和产生的数据格式。这个可以使用MIME类型来达到——拖动的源提供一个它能缠身的MIME类型的类标(按从最常用的到比较少用到的顺序排列),并且拖动目标选择一种它能接受的。例如,QTextDrag提供了“text/plain”MIME类型(普通的没有格式的文本)和并且Unicode格式是“text/utf16”和“text/utf8”的支持,QImageDrag提供了“image/*”的支持,*QImageIO支持的任何一种图片格式,并且QUriDrag子类提供了“text/uri-list”的支持,一个文件(或URL)的列表传输的标准格式。

为了实现QDragObject子类中没有支持的拖放信息的类型,首先和最重要的步骤是查找一下现存的格式中哪个是适合的——IANA(Internet Assigned Numbers Authority)在ISI(Information Sciences Institute)提供了一个MIME媒体类型的分级列表。使用标准的MIME类型将会使你的应用程序和现在以及未来的其它软件在互相操作中能够最优化。

为了支持另外的媒体类型,继承QDragObject或QStoredDrag。当你需要提供对多类型进行支持,继承QDragObject。当一个类型就足够的时候,继承更加简单的QStoredDrag。

QDragObject的子类将会重载const char* format(int i) constQByteArray encodedData(const char* mimetype) const成员,并且提供对媒体数据进行编码和静态成员canDecode()和对进入的数据进行解码的decode()一套方法,和QImageDragbool canDecode(QMimeSource*) constQByteArray decode(QMimeSource*) const很相似。当然,你也可以通过省略其中的一些方法对一种媒体数据只支持拖或只支持放。

QStoredDrag的子类提供了对媒体数据进行编码和静态成员canDecode()和对进入的数据进行解码的decode()一套方法。

增强的拖放

在剪贴板模式中,用户可以剪切复制源信息,然后粘贴它。相似地,在拖放模式中,用户可以拖动信息的复制或者它们能够自己拖动信息到一个新的位置(移动它)。拖放模式无论如何对于程序员来说都是额外的复杂:程序直到放下(粘贴)完成才会知道用户是想剪切还是复制!在应用程序之间拖动,这个没有什么影响,但是在一个应用程序之内进行拖动,应用程序必须多加注意以免踩到自己的脚。例如,在文档中拖动文本,拖动的开始点和放下事件也许是这样:

  void MyEditor::startDrag()
  {
    QDragObject *d = new QTextDrag(myHighlightedText(), this);
    if ( d->drag() && d->target() != this )
      cutMyHighlightedText();
  }

  void MyEditor::dropEvent(QDropEvent* event)
  {
    QString text;

    if ( QTextDrag::decode(event, text) ) {
      if ( event->source() == this && event->action() == QDropEvent::Move ) {
        // 注意不要踩到自己的脚
        event->acceptAction();
        moveMyHighlightedTextTo(event->pos());
      } else {
        pasteTextAt(text, event->pos());
      }
    }
  }
  

一些窗口部件在数据被拖动到它们上面时会问一个“是”或“否”的细节。比如,一个CAD程序也许只接收在视图中的文本对象上放下的文本。在这种情况下,dragMoveEvent()被使用并且给定一个区域是接受或者忽略拖动:

  void MyWidget::dragMoveEvent(QDragMoveEvent* event)
  {
    if ( QTextDrag::canDecode(event) ) {
      MyCadItem* item = findMyItemAt(event->pos());
      if ( item )
        event->accept();
    }
  }
  
如果搜索对象的计算指令特别地慢,如果你告诉系统你允许那些区域接受请求的话,你也许可以达到更好的效果:
  void MyWidget::dragMoveEvent(QDragMoveEvent* event)
  {
    if ( QTextDrag::canDecode(event) ) {
      MyCadItem* item = findMyItemAt(event->pos());
      if ( item ) {
        QRect r = item->areaRelativeToMeClippedByAnythingInTheWay();
        if ( item->type() == MyTextType )
          event->accept( r );
        else
          event->ignore( r );
      }
    }
  }
  

如果你需要在拖动进程开始定时器、开始滚动窗口或者合适的情况(但是不要忘记在dragMoveEvent()中停止滚动和定时器)中给出视觉效果,dragMoveEvent()也可以被使用。

下面是一个使用 QDrag 实现拖动排序逻辑的简单示例: ```cpp #include <QtWidgets> class MyItemWidget : public QLabel { public: MyItemWidget(int index, QWidget *parent = nullptr) : QLabel(parent), itemIndex(index) { setText(QString("Item %1").arg(itemIndex)); setAutoFillBackground(true); setAlignment(Qt::AlignCenter); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setStyleSheet("QLabel { background-color: lightblue; border: 1px solid black; }"); setAcceptDrops(true); } void mousePressEvent(QMouseEvent *event) override { if (event->button() == Qt::LeftButton) { QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; mimeData->setData("application/x-myitemdata", QByteArray::number(itemIndex)); drag->setMimeData(mimeData); drag->setPixmap(QPixmap(":/icons/item.png")); drag->exec(Qt::MoveAction); } } void dragEnterEvent(QDragEnterEvent *event) override { if (event->mimeData()->hasFormat("application/x-myitemdata")) { event->acceptProposedAction(); } } void dropEvent(QDropEvent *event) override { if (event->mimeData()->hasFormat("application/x-myitemdata")) { QByteArray itemData = event->mimeData()->data("application/x-myitemdata"); int droppedIndex = itemData.toInt(); // 处理拖动排序逻辑 emit itemDropped(droppedIndex, itemIndex); event->acceptProposedAction(); } } signals: void itemDropped(int fromIndex, int toIndex); private: int itemIndex; }; class MainWindow : public QMainWindow { public: MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) { setWindowTitle("Item Sort Example"); QVBoxLayout *layout = new QVBoxLayout; QWidget *centralWidget = new QWidget; centralWidget->setLayout(layout); setCentralWidget(centralWidget); for (int i = 0; i < 5; ++i) { MyItemWidget *itemWidget = new MyItemWidget(i); layout->addWidget(itemWidget); connect(itemWidget, &MyItemWidget::itemDropped, this, &MainWindow::handleItemDropped); } } private: void handleItemDropped(int fromIndex, int toIndex) { // 处理拖动排序逻辑,例如交换 item 的位置等 QWidget *centralWidget = this->centralWidget(); QLayout *layout = centralWidget->layout(); QLayoutItem *fromItem = layout->takeAt(fromIndex); QLayoutItem *toItem = layout->takeAt(toIndex); layout->insertItem(toIndex, fromItem); layout->insertItem(fromIndex, toItem); } }; int main(int argc, char *argv[]) { QApplication app(argc, argv); // 加载图标资源 QPixmap::fromImage(QImage(":/icons/item.png")); MainWindow mainWindow; mainWindow.show(); return app.exec(); } ``` 在这个示例中,我们创建了一个简单的主窗口,其中包含了 5 个可拖动的 item。当拖动一个 item 到另一个 item 上时,会发出 `itemDropped` 信号,并在 `MainWindow` 的槽函数 `handleItemDropped` 中处理拖动排序逻辑。通过交换 item 在布局中的位置,实现了拖动排序的效果。 注意:这个示例是一个最简单的实现,仅用于演示拖动排序的基本思路。在实际应用中,你可能需要根据自己的需求进行适当的修改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值