拖放

拖放提供了一种简单的可视化机制,用户可以使用它在应用程序之间和应用程序内部传输信息。拖放功能类似于剪贴板的剪切和粘贴机制。
本文档描述了基本的拖放机制,并概述了在自定义控件中启用它的方法。Qt的许多控件也支持拖放操作,例如项目视图和图形视图框架,以及Qt Widgets和Qt Quick的编辑控件。关于项目视图和图形视图的更多信息可以在《使用拖放项目视图》和《图形视图框架》中找到。

拖放类

这些类处理拖放操作以及必要的mime类型编码和解码。

描述
QDrag 支持基于mime的拖放数据传输
QDragEnterEvent 事件,当拖放操作进入小部件时发送给它
QDragLeaveEvent 事件,当拖放操作离开小部件时发送给它
QDragMoveEvent 事件,当拖放操作在进行中时发送
QDropEvent 事件,当拖放操作完成时发送

配置

QStyleHints对象提供了一些拖放操作相关的属性:

  • QStyleHints::startDragTime() 描述了用户在拖动对象之前必须按住鼠标按钮的时间(以毫秒为单位)。
  • QStyleHints::startDragDistance() 表示用户按住鼠标按钮移动被识别为拖动之前必须移动的距离。
  • QStyleHints::startDragVelocity() 表示用户必须以多快的速度(像素/秒)移动鼠标来开始拖动。值为0意味着没有这样的限制。
    如果您在控件中提供拖放支持,这些数量提供了与底层窗口系统兼容的合理默认值,供您使用。

在Qt Quick中拖放

文档的其余部分主要关注如何在c++中实现拖放操作。在Qt Quick场景中使用拖放,请阅读Qt Quick drag, DragEvent和DropArea项目的文档,以及Qt Quick drag和drop的例子。

拖动

要开始拖动,创建一个QDrag对象,并调用它的exec()方法。在大多数应用程序中,只有在按下鼠标按钮并移动了一定距离后才开始拖放操作是一个好主意。然而,从一个小部件中启用拖放的最简单方法是重新实现小部件的mousePressEvent()方法,并且开始拖放操作:

void MainWindow::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton 
        && iconLabel->geometry().contains(event->pos())) {

        QDrag *drag = new QDrag(this);
        QMimeData *mimeData = new QMimeData;

        mimeData->setText(commentEdit->toPlainText());
        drag->setMimeData(mimeData);
        drag->setPixmap(iconPixmap);

        Qt::DropAction dropAction = drag->exec();
        ...
    }
}

尽管用户可能需要一些时间来完成拖动操作,但就应用程序而言,exec()函数是一个阻塞函数,它返回多个值中的一个。这些表示操作如何结束,并在下面进行更详细的描述。

注意,exec()函数不会阻塞主事件循环。

对于需要区分鼠标点击和拖动的小部件,重新实现小部件的mousePressEvent()函数来记录拖动的起始位置是有用的:

void DragWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
        dragStartPosition = event->pos();
}

下面,在mouseMoveEvent()函数中,我们可以确定拖动是否应该开始,并且构造一个拖动对象来处理该操作:

void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
    if (!(event->buttons() & Qt::LeftButton))
        return;
    if ((event->pos() - dragStartPosition).manhattanLength()
        < QApplication::startDragDistance())
        return;

    QDrag *drag = new QDrag(this);
    QMimeData *mimeData = new QMimeData;

    mimeData->setData(mimeType, data);
    drag->setMimeData(mimeData);
    
    Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
        ...
}

这种特殊的方法使用QPoint::manhattanLength()函数来获得鼠标点击位置与当前光标位置之间距离的粗略估计。该函数以精度换取速度,通常适合于此目的。

Dropping

为了能够接收小部件上drop的媒体,调用小部件的setAcceptDrops(true),并重新实现dragEnterEvent()和dropEvent()事件处理函数。

例如,下面的代码在QWidget子类的构造函数中启用drop事件,从而可以有效地实现drop事件处理程序:

Window::Window(QWidget *parent)
      : QWidget(parent)
  {
      ...
      setAcceptDrops(true);
  }

dragEnterEvent()函数通常用于通知Qt小部件接受的数据类型。如果想在重新实现的dragMoveEvent()dropEvent()函数的中接收QDragMoveEventQDropEvent事件,则必须重新实现此函数。

下面的代码展示了如何重新实现dragEnterEvent()函数,告诉拖放系统只处理纯文本:

 void Window::dragEnterEvent(QDragEnterEvent *event)
  {
      if (event->mimeData()->hasFormat("text/plain"))
          event->acceptProposedAction();
  }

dropEvent()用于解包被drop的数据,并以适合您的应用程序的方式处理它。

在下面的代码中,事件中的文本被传递给一个QTextBrowser,描述数据的MIME类型填充在一个QComboBox中:

  void Window::dropEvent(QDropEvent *event)
  {
      textBrowser->setPlainText(event->mimeData()->text());
      mimeTypeCombo->clear();
      mimeTypeCombo->addItems(event->mimeData()->formats());

      event->acceptProposedAction();
  }

在这种情况下,我们接受操作,而不检查它是什么。在实际应用程序中,可能需要从dropEvent()函数返回,而不接受建议的操作或处理不相关的数据(如果操作不相关的话)。例如,我们可以选择忽略Qt::LinkAction操作,如果我们在应用程序中不支持到外部源的链接。

重写推荐的操作

我们也可以忽略推荐的操作,并对数据执行其他操作。为了做到这一点,我们在调用accept()之前调用事件对象的setDropAction()函数来设置Qt::DropAction。这确保使用替换的drop操作而不是建议的操作。

对于更复杂的应用程序,重新实现dragMoveEvent()dragLeaveEvent()将使小部件的某些部分对拖放事件敏感,并使您对应用程序中的拖放操作有更多的控制。

子类化复杂的小部件

某些标准Qt小部件提供了它们自己的拖放支持。当子类化这些小部件时,除了dragEnterEvent()和dropEvent()外,可能需要重新实现dragMoveEvent(),以防止基类提供默认的拖放处理,并处理您感兴趣的任何特殊情况。

拖放操作

在最简单的情况下,拖放操作的目标接收被拖放数据的副本,然后操作的源决定是否删除原始数据。这是由CopyAction动作描述的。操作目标也可以选择处理其他操作,特别是MoveActionLinkAction操作。如果源调用QDrag::exec(),并且它返回MoveAction,那么源负责删除任何原始数据,如果它选择这样做的话。由源窗口小部件创建的QMimeDataQDrag对象不应该被删除——它们将被Qt销毁,目标负责获取在拖放操作中发送的数据的所有权;这通常通过保持对数据的引用来实现。

如果目标理解了LinkAction操作,它应该存储自己对原始信息的引用;源不需要对数据执行任何进一步的处理。拖放操作最常见的用法是在同一个小部件中执行Move;有关该特性的更多信息,请参阅Drop Actions一节。

拖动操作的另一个主要用途是使用text/uri-list等引用类型时,拖动的数据实际上是对文件或对象的引用。

添加新的拖放类型

拖放不仅仅局限于文字或图片,任何类型的信息都可以在拖放操作中传输。要在应用程序之间拖动信息,应用程序必须能够相互指示它们可以接受和生成哪些数据格式。这通过使用MIME类型来实现。由源构造的QDrag对象包含用于表示数据的MIME类型列表(从最合适到最不合适排序),而拖放目标使用其中的一种类型来访问数据。对于普通数据类型,函数透明地处理MIME类型,但是对于自定义数据类型,需要显式地声明它们。

要实现对QDrag没有涵盖的信息类型的拖放操作,第一步也是最重要的一步是寻找合适的现有格式:互联网编号分配机构(IANA)在信息科学研究所(ISI)提供了一个分级的MIME媒体类型列表。使用标准MIME类型可以最大化您的应用程序现在和将来与其他软件的互操作性。

要支持额外的媒体类型,只需使用setData()函数在QMimeData对象中设置数据,提供完整的MIME类型和包含适当格式数据的QByteArray。下面的代码从标签中获取一个pixmap,并将其作为一个PNG文件存储在QMimeData对象中:

QByteArray output;
QBuffer outputBuffer(&output);
outputBuffer.open(QIODevice::WriteOnly);
imageLabel->pixmap()->toImage().save(&outputBuffer, "PNG");
mimeData->setData("image/png", output);

当然,对于这种情况,我们可以简单地使用setImageData()来提供各种格式的图像数据:

 mimeData->setImageData(QVariant(*imageLabel->pixmap()));

在这种情况下,QByteArray方法仍然很有用,因为它可以更好地控制存储在QMimeData对象中的数据量。

注意,ItemView中使用的自定义数据类型必须声明为元对象,并且必须实现它们的流操作符。

Drop操作

在剪贴板模型中,用户可以剪切或复制源信息,然后粘贴它。类似地,在拖放模型中,用户可以拖动信息的副本,也可以将信息本身拖到一个新的位置(移动它)。对程序员来说,拖放模型还有一个额外的复杂性:在操作完成之前,程序不知道用户是要剪切还是复制信息。在应用程序之间拖动信息时,这通常没有什么区别,但在应用程序内部,检查使用了哪个拖放操作是很重要的。

我们可以为小部件重新实现mouseMoveEvent(),并使用可能的拖放操作组合启动拖放操作。例如,我们可能想要确保拖动总是移动小部件中的对象:

  void DragWidget::mouseMoveEvent(QMouseEvent *event)
  {
      if (!(event->buttons() & Qt::LeftButton))
          return;
      if ((event->pos() - dragStartPosition).manhattanLength()
           < QApplication::startDragDistance())
          return;

      QDrag *drag = new QDrag(this);
      QMimeData *mimeData = new QMimeData;

      mimeData->setData(mimeType, data);
      drag->setMimeData(mimeData);

      Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
      ...
  }

如果信息被拖放到另一个应用程序中,exec()函数返回的操作可能默认为CopyAction,但是,如果信息被拖放到同一应用程序中的另一个小部件中,我们可能会获得不同的drop操作。

可以在小部件的dragMoveEvent()函数中过滤建议的drop操作。然而,在dragEnterEvent()中接受所有提议的操作并让用户决定他们以后想要接受的操作是可能的:

void DragWidget::dragEnterEvent(QDragEnterEvent *event)
{
    event->acceptProposedAction();
}

当小部件中发生drop操作时,将调用dropEvent()处理程序函数,我们可以依次处理每个可能的操作。首先,我们在同一个小部件中处理拖放操作:

 void DragWidget::dropEvent(QDropEvent *event)
  {
      if (event->source() == this && event->possibleActions() & Qt::MoveAction)
          return;

在这个例子中,我们拒绝处理移动操作。我们接受的每一种drop动作都会被检查并相应地处理:

      if (event->proposedAction() == Qt::MoveAction) {
          event->acceptProposedAction();
          // Process the data from the event.
      } else if (event->proposedAction() == Qt::CopyAction) {
          event->acceptProposedAction();
          // Process the data from the event.
      } else {
          // Ignore the drop.
          return;
      }
      ...
  }

注意,我们在上面的代码中检查了单个的drop操作。正如上面关于《重写推荐的操作》一节所提到的,有时有必要重写推荐的drop操作,并从可能的drop操作选择中选择一个不同的操作。为此,您需要检查事件的possibleActions()提供的值中是否存在每个操作,使用setDropAction()设置删除操作,并调用accept()

Drop 矩形

小部件的dragMoveEvent()函数可以用于将拖放限制在小部件的某些部分,方法是当光标位于这些区域时,只接受建议的拖放操作。例如下面的代码,在光标停留在子部件(dropFrame)上时接受任何建议的drop操作:

  void Window::dragMoveEvent(QDragMoveEvent *event)
  {
      if (event->mimeData()->hasFormat("text/plain")
          && event->answerRect().intersects(dropFrame->geometry()))
          event->acceptProposedAction();
  }

如果需要在拖放操作期间提供视觉反馈、滚动窗口或任何适当的操作,也可以使用dragMoveEvent()

剪贴板

应用程序还可以通过将数据放在剪贴板上来相互通信。要访问它,您需要从QApplication对象获得一个QClipboard对象。

QMimeData类用于表示与剪贴板之间传输的数据。要将数据放到剪贴板上,可以使用setText()setImage()setPixmap()函数来处理常见的数据类型。这些函数类似于在QMimeData类中找到的那些函数,除了它们还接受一个额外的参数来控制数据存储的位置:如果指定了Clipboard,数据将放在剪贴板上;如果指定了Selection,则数据被放置在鼠标选区中。默认情况下,将数据放置在剪贴板上。

例如,我们可以用以下代码将QLineEdit的内容复制到剪贴板:

QGuiApplication::clipboard()->setText(lineEdit->text(), QClipboard::Clipboard);

不同MIME类型的数据也可以放在剪贴板上。按照上一节描述的方式,构造一个QMimeData对象并使用setData()函数设置数据;然后可以使用setMimeData()函数将该对象放到剪贴板上。

QClipboard类可以通过其datachchanged()信号通知应用程序它所包含的数据的更改。例如,我们可以通过将这个信号连接到小部件中的一个槽来监视剪贴板:

connect(clipboard, SIGNAL(dataChanged()), this, SLOT(updateClipboard()));

连接到该信号的槽可以使用一种MIME类型来表示剪贴板上的数据:

  void ClipWindow::updateClipboard()
  {
      QStringList formats = clipboard->mimeData()->formats();
      QByteArray data = clipboard->mimeData()->data(format);
      ...
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容

  • 一、拖放 拖放(Drag 和 drop)是 HTML5 标准的组成部分。拖放是一种常见的特性,即抓取对象以后拖到另...
    wangjianuo阅读 1,927评论 0 2
  • 拖放概述 教程数据 本教程概述了NoesisGUI中的拖放支持。拖放通常是指一种数据传输方法,该方法包括使用鼠标(...
    YottaYuan阅读 820评论 0 1
  • Drag and Drop 拖放提供了一个简单的可视化机制,用户可以使用它来在应用程序之间和应用程序内部传输数据....
    托尼章阅读 3,701评论 0 4
  • 今天与你分享的是 redux 作者 Dan 的另外一个很赞的项目 react-dnd (github 9.6k s...
    binggg_booker阅读 35,869评论 1 13
  • 一、什么是拖拽和释放? 拖拽:Drag释放:Drop拖拽指的是鼠标点击源对象后一直移动对象不松手,一但松手即释放了...
    栀子花wish阅读 3,012评论 1 3