Qt拖拽机制学习笔记

Drag 和 Drop 的学习笔记

记录对QDrag以及相关类的理解

初次接触Qt的拖放机制,在此记录下笔记以及一些未解决的问题

本次实现一个简单的文本拖放功能,但碍于对Qt事件机制的不熟悉,折腾了三天才勉强完成(惭愧)

实现拖放功能涉及几个类,包括QDrag,QDragEnterEvent,QDragMoveEvent等。

QDrag 用于支持基于MIME数据传输,继承自QObject。

QDragEnterEvent:拖放操作进入部件时,此事件被发送。

QDragMoveEvent:在拖放的过程中,此事件被发送。

QDropMent:拖动操作完成时,此事件被发送。

拖放的操作被分为两个步骤:拖动和放置。首先,当某个item被拖动时,理应也携带item的数据一起到目标位置,该数据被存储为MIME类型对象。而QMimeData提供一个MIME类型数据的容器,保证数据安全地在应用之间或应用内传输。如果想要过滤一些类型,比如只需要text或uri-list,可以用以下代码实现:

if (event->mimeData()->hasFormat("text/uri-list"))
{
  //do something you want
}

一.拖放的大致过程的记录
  1. 启动拖放, 想要启动拖放,需要调用QDrag实例的exec()函数
Qt::DropAction QDrag::exec(Qt::DropActions supportedActions = Qt::MoveAction)

Qt::DropAction QDrag::exec(Qt::DropActions supportedActions, Qt::DropAction defaultDropAction)//

Starts the drag and drop operation and returns a value indicating the requested drop action when it is completed. The drop actions that the user can choose from are specified in supportedActions. The default proposed action will be selected among the allowed actions in the following order: Move, Copy and Link.

Qt::DropAction 是该函数的返回类型,且该函数是一个不会阻塞主事件循环的阻塞函数,只有当拖放操作结束后,才会返回一个Qt::DropAction的值

  1. 拖动开始后(即鼠标按住item并移动),松开鼠标按键表示拖动结束。从拖动开始到放置item完成拖放期间,会有产生,发送,处理以下几个事件(默认情况下,部件不接受放下事件,需要调用QWidget::setAcceptDrops()函数来设置此部件是否接受放下操作,参数类型为bool): QDragEnterEvent

The QDragEnterEvent class provides an event which is sent to a widget when a drag and drop action enters it.A widget must accept this event in order to receive the drag move events that are sent while the drag and drop action is in progress. The drag enter event is always immediately followed by a drag move event.

QDragEnterEvent继承自QDragMoveEvent事件类,当部件中有拖动操作时,该事件被发送,如果一个部件想要接受QDragMoveMent事件,必须接受QDragEnterEvent事件。

dragenterevent事件 通常后面紧跟一个dragmoveevent事件

The QDragMoveEvent class provides an event which is sent while a drag and drop action is in progress.A widget will receive drag move events repeatedly while the drag is within its boundaries, if it accepts drop events and enter events. The widget should examine the event to see what kind of data it provides, and call the accept() function to accept the drop if appropriate.

QDragMoveEvent 继承自QDropMent,拖动移动事件,想要部件接受此事件,此部件就必须接受QDragEnterEvent事件。

The QDropEvent class provides an event which is sent when a drag and drop action is completed.

When a widget accepts drop events, it will receive this event if it has accepted the most recent QDragEnterEvent or QDragMoveEvent sent to it.

QDropEvent,继承自QEvent,当用户在部件上放下部件时,发送此事件给部件,部件想要接收此事件,就必须接受QDragEnterEvent 以及QDragMoveEvent

二.具体实现步骤

在这里插入图片描述

该例子来自于 C++ GUI Programming with Qt4

首先被拖动的控件需要接收以上几个事件,所以必须要重写这个部件类的五个函数,即重写这个部件类,而不是直接用已有的标准QWidget。

设这个自定义的部件继承自QListWidget类,名字叫作“mylistwidget”.

mylistwidget::mylistwidget(QWidget *parent) : QListWidget(parent)
{


this->setAcceptDrops(true);

}

调用mylistwidget类的setAcceptDrops函数,设置这个控件接收放置事件,否则后面的几个事件都无法被这个控件接收。

接下来要重写几个事件处理函数,以便完成我们所需要的拖放功能,这几个事件处理函数都是protectedvirtual 的。

首先为了开始一个拖动操作,需要创建一个QDrag对象,并且调用exec函数。然后要考虑“拖动”动作的启动要写入哪个事件处理函数。在控件的mousePressEvent或者mouseMoveEvent中写都可以。在此列出两种方法

  1. 第一种方法比较直接,并不考虑我们是否需要拖动一小段距离后才启动拖动,如下所示:
   void mylistwidget::mousePressEvent(QMouseEvent *event){
  if(event->button() != Qt::LeftButton)
  {return;}
  QListWidget::mousePressEvent(event);
   }

 在重写mousePressEvent事件处理函数中,我们并不启动拖动,而是仅仅聚焦于我们点击的item。经实践可得,这就是QListWidget::mousePressEvent所做的事之一。

    void mylistwidget::mouseMoveEvent(QMouseEvent *event)
        {
         QListWidgetItem *current = this->currentItem();

         if(current)
           {
             QMimeData *mimeData = new QMimeData();
             mimeData->setText(current->text());
             QDrag *drag  = new QDrag(this);
             drag->setMimeData(mimeData);
             if(drag->exec(Qt::MoveAction) == Qt::MoveAction)
               {delete current;}  
           }
        }

这里可以看到 事件处理函数mouseMoveEvent当中,获取了当前聚焦的item,在item非空的情况下,创建一个QMimeData实例叫做mimeData,并将当前的item的内容(此时item的数据是QString类型)赋值给QMimeData实例。接着创建QDrap实例,如之前所记录,它用于支持MIME数据类型的拖放传输。所以QDrap实例理所应当获得QMimeData数据,并开始启动拖(通过exec) 这里有个细节就是用if判断exec返回的值是否为Qt::MoveAction,通过这个条件语句,可以避免我们将拖拽到中间松开手后源数据仍然被删除的后果,最终可能导致所有数据都消失(每次都拖到中间就放下不管了)。原因可能在于后面的dropEvent事件处理函数接收到了放下事件,并通过一系列的条件判断(比如source是否为this本身或者为空)设置了Action为Qt::MoveAction,此时拖放动作完成,drag->exec有了返回值,条件语句判断为true,这时才说明一个正确的拖放动作完成而不是在中间就放下,此时删除源item即current是合适的。

接下来就是重写dragEnterEvent以及dragMoveEvent函数的过程

   void mylistwidget::dragEnterEvent(QDragEnterEvent *event)
  {

  QListWidget *source = qobject_cast<QListWidget *>(event->source());
  if(source!=NULL && source!=this)
  {
      event->setDropAction(Qt::MoveAction);
      event->accept();
  }
  }

  void mylistwidget::dragMoveEvent(QDragMoveEvent *event)
  {
  QListWidget *source = qobject_cast<QListWidget *>(event->source());
  if(source!=NULL && source!=this)
  {
      event->setDropAction(Qt::MoveAction);
      event->accept();
  }
  }

dragEnterEvent和dragMoveEvent函数体内容差不多,判断源控件是否为空并且是否为this本身,然后将dropAction设置为Qt::MoveAction,然后接收这个QDragMoveEvent 或者QDragEnterEvent,否则后面的QDropEvent也无法被接收。

最后是dropEvent的重写
    void mylistwidget::dropEvent(QDropEvent *event)
{
    QListWidget *source = qobject_cast<QListWidget*>(event->source());
    if(source!=NULL && source != this)
    {
        QString str = event->mimeData()->text();
        QListWidgetItem *l1 = new QListWidgetItem(str);
        this->addItem(l1);
        this->setCurrentItem(l1);
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
}
    ```
    这个函数比较关键,它负责将携带的QMimeData从接收的QDropEvent事件中提取出来并放入当前控件(也就是目标控件)当中,并accept这个QDropEvent事件。至此拖放操作完成,QDrap::exec函数返回一个DropAction。

2. 先做距离判断,再拖动

  ```C++
  void ProjectListWidget::mousePressEvent(QMouseEvent *event)
  {
    if(event->button() == Qt::LeftButton)
        startPos = event->pos(); //记录鼠标按下之后的位置
    QListWidget::mousePressEvent(event);//为了响应通常的鼠标按下操作,需要在最后调用父类的事件函数
   }

这里不再只是聚焦于当前item,而是通过QMouseEvent的pos()函数记录下当前鼠标的坐标,为了方便使用这个坐标,可以为这个自定义控件设置一个QPoint类型的成员变量来存储坐标信息。接着是mouseMoveEvent函数内容

void ProjectListWidget::mouseMoveEvent(QMouseEvent *event)
{
  if(event->buttons() & Qt::LeftButton)
  {
      int distance = (event->pos() - startPos).manhattanLength();
      //计算当前鼠标的位置 和 鼠标按压时候的距离
      if(distance >= QApplication::startDragDistance())
          startDrag();
  }
  QListWidget::mouseMoveEvent(event);}

startDrag()函数中的内容就是上一个方法的内容,重点是我们在启动drag之前判断我们拖动item的距离是否大于最小拖动距离,这个最小拖动距离由QApplication::startDragDistance给出(默认为10px)。

后面部分与前面相同。


参考资料:
1.https://www.w3cschool.cn/learnroadqt/
2.Qtcreator参考文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值