【Qt开发流程】之拖放操作1:介绍
  yQAl4kecrO8W 2023年12月23日 17 0


描述

Drag and drop 提供了一种简单的可视化机制,用户可以使用它在应用程序之间和应用程序内部传输信息。拖放的功能类似于剪贴板的剪切和粘贴机制。

本文描述了基本的拖放机制,并概述了在自定义控件中启用该机制的方法。Qt的许多控件也支持拖放操作,例如the item views and graphics view framework项目视图和图形视图框架,以及Qt Widgets和Qt Quick的编辑控件。

Drag 和 Drop 类

以下类处理拖放和必要的mime类型编码和解码。


描述

解释

QDrag

Support for MIME-based drag and drop data transfer

支持基于MIME的拖放数据传输

QDragEnterEvent

Event which is sent to a widget when a drag and drop action enters it

当拖放操作进入窗口部件时发送的事件

QDragLeaveEvent

Event that is sent to a widget when a drag and drop action leaves it

当拖放操作离开窗口部件时发送的事件

QDragMoveEvent

Event which is sent while a drag and drop action is in progress

在拖放操作进行中时发送的事件

QDropEvent

Event which is sent when a drag and drop action is completed

当拖放操作完成时发送的事件

配置

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

  • QStyleHints::startDragTime()描述用户在按住鼠标按钮在一个对象上多长时间后,拖动操作开始的毫秒数。
  • QStyleHints::startDragDistance()指示用户在按住鼠标按钮时移动鼠标的距离,这之后移动才会被解释为拖动。
  • QStyleHints::startDragVelocity()指示用户必须移动鼠标的速度(以像素/秒计)以启动拖动。值为0意味着没有这样的限制。

这些量提供了合理的默认值,如果控件中提供拖放支持,可以使用它们,这些值符合底层窗口系统的要求。

Drag

要开始拖动,请创建一个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()函数是一个阻塞函数,返回下面几个值之一。这些值表示操作的结束方式,并在下面更详细地描述。

常量


描述

解释

Qt::CopyAction

0x1

Copy the data to the target.

将数据复制到目标。

Qt::MoveAction

0x2

Move the data from the source to the target.

将数据从源移到目标。

Qt::LinkAction

0x4

Create a link from the source to the target.

从源到目标创建一个链接。

Qt::ActionMask

0xff

Qt::IgnoreAction

0x0

Ignore the action (do nothing with the data).

忽略动作(不对数据执行任何操作)。

Qt::TargetMoveAction

0x8002

On Windows, this value is used when the ownership of the D&D data should be taken over by the target application, i.e., the source application should not delete the data. On X11 this value is used to do a move. TargetMoveAction is not used on the Mac.

在Windows上,当目标应用程序应该接管D&D数据的所有权时使用此值,即,源应用程序不应删除数据。在X11上,此值用于执行移动操作。TargetMoveAction在Mac上不使用。

请注意,exec()函数不会阻塞主事件循环。
对于需要区分鼠标点击和拖动的小部件,重新实现小部件的mousePressEvent()函数以记录拖动的起始位置非常有用:

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

随后,在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);
      ...
  }

这种方法使用QPoint::manhattanLength()函数来获取鼠标单击发生位置和当前光标位置之间距离的大致估计值。此函数以精度为代价提高了速度,并且通常适用于此目的。

Drop

要能够接收放置在小部件上的媒体,为小部件调用setAcceptDrops(true),并重新实现dragEnterEvent()dropEvent()事件处理程序函数。
例如,以下代码在QWidget子类的构造函数中启用了放置事件,使其可以有用地实现放置事件处理程序:

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

dragEnterEvent()函数通常用于通知Qt小部件接受的数据类型。如果想在dragMoveEvent()dropEvent()的重新实现中接收QDragMoveEvent或QDropEvent,则必须重新实现此函数。
以下代码显示了如何重新实现dragEnterEvent()以告知拖放系统我们仅能处理纯文本:

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

dropEvent()用于解包放置的数据并以适合于您的应用程序的方式处理它。
在以下代码中,事件中提供的文本被传递到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()之前使用 Qt::DropAction 中的首选操作调用事件对象的 setDropAction()。这样确保使用替换的放置操作而不是建议的操作。
对于更复杂的应用程序,重新实现 dragMoveEvent() dragLeaveEvent()将使您能够使组件的某些部分对拖放事件敏感,并在应用程序中更好地控制拖放。

子类化控件

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

Drag and Drop 动作

在最简单的情况下,拖放操作的目标将接收被拖动的数据的副本,源则决定是否删除原始数据。这由 CopyAction 操作描述。目标还可以选择处理其他操作,特别是 MoveAction LinkAction 操作。如果源调用 QDrag::exec(),并且返回 MoveAction,则源负责删除任何原始数据(如果选择删除)。源窗口小部件创建的 QMimeData QDrag 对象不应被删除 - 它们将被 Qt 销毁。目标负责接管拖放操作中发送的数据的所有权;通常是通过保留对数据的引用来完成的。
如果目标理解 LinkAction 操作,则应将自己的引用存储到原始信息中;源无需进一步处理数据。拖放操作最常见的用法是在同一个小部件中执行 Move 操作;
拖动操作的另一个主要用途是使用ext/uri-list等引用类型,其中拖动的数据实际上是文件或对象的引用。

添加新的 Drag and Drop 类型

拖放并不仅限于文本和图像。任何类型的信息都可以在拖放操作中传输。要在应用程序之间拖动信息,这些应用程序必须能够互相指示它们可以接受哪些数据格式和可以生成哪些数据格式。这是使用 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对象中的数据量。
请注意,item view 中使用的自定义数据类型必须声明为元对象,并且必须实现它们的流操作符。

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。但是如果它被拖放到同一应用程序中的另一个小部件中,则可能会获得不同的放置操作。
建议的放置操作可以在小部件的 dragMoveEvent() 函数中进行过滤。但是,可以在 dragEnterEvent() 中接受所有建议的操作,让用户稍后决定想要接受哪个:

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

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

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

在这种情况下,拒绝处理移动操作。会逐一检查和处理每种接受的拖放操作:

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;
      }
      ...
  }

请注意,上面的代码中检查了各个单独的拖放操作。如上面在关于重写拖放操作的建议中提到的,有时需要重写所建议的拖放操作,并从可能的拖放操作中选择不同的操作。为此,需要检查事件possibleActions()返回值中是否存在每个操作,使用setDropAction()设置拖放操作,并调用accept()函数。

Drop 区域

widgetdragMoveEvent()可以用于通过仅在光标位于那些区域时接受建议的放置操作,来限制放置操作到小部件的某些部分。例如,以下代码在光标悬停在子组件(dropFrame)上时接受任何建议的放置操作:

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,则数据被放置在鼠标选择中(仅在X11上)。默认情况下,数据被放入剪贴板。
例如,可以使用以下代码将QLineEdit的内容复制到剪贴板中:

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

还可以在剪贴板上放置具有不同MIME类型的数据。构造一个QMimeData对象,并按照前面部分所述的方式使用setData()函数设置数据;然后可以使用setMimeData()函数将此对象放入剪贴板。
QClipboard类可以通过其dataChanged()信号通知应用程序其所包含数据的更改。例如,可以通过将此信号连接到窗口小部件中的插槽来监视剪贴板:

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

连接到此信号的槽可以使用可用于表示其的MIME类型之一之一读取剪贴板上的数据:

void ClipWindow :: updateClipboard()
{
     QStringList formats = clipboard->mimeData()->formats();
     QByteArray data = clipboard->mimeData()->data(format);
     …
}

在X11上,可以使用selectionChanged()信号来监视鼠标选择。


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月23日 0

暂无评论

推荐阅读
yQAl4kecrO8W