四时宝库

程序员的知识宝库

Qt编程进阶(91):窗体内拖拽图标效果的基本流程详解

本文介绍实现拖拽效果的基本流程,包括QDrag、QDragEnterEvent、QDropEvent、QDragMoveEvent类的使用方法。

本实例实现窗体内图标的拖拽效果,排列在窗体中的图标可以拖拽至窗体的任意位置。图标也可在两个独立运行的实例程序间相互拖拽图标,此时拖拽的图标将复制一份到拖拽目的程序窗体中。实例在窗体内拖拽的效果如下图所示。

在窗体间拖拽的效果如下图:


拖拽机制的实现类似于剪贴板的机制,Qt提供的与拖拽相关的类有QDrag、QDragEnterEvent、QDropEvent、QDragMoveEvent等。

下面以实例为基础逐步分析拖拽的实现过程。

1.可拖拽的图标类Draglcon

在本实例中,为了实现图标的拖拽效果,首先需要构建一个可拖拽的图标类Draglcon。

class DragIcon : public QLabel
{
	Q_OBJECT
public:
  DragIcon(QPixmap pix,QWidget *parent=0);
  void mousePressEvent(QMouseEvent *e);
  void mouseMoveEvent(QMouseEvent *e);
private:
	QPoint startPos;
};

Draglcon类继承自QLabel类,此类重定义mousePressEvent(QMouseEvent *)事件函数和mouseMoveEvent(QMouseEvent *)事件函数,响应鼠标的按下和移动事件。私有变量startPos用于保存鼠标按下时的位置信息。

Draglcon类的构建函数中初始化图标的图片,并设置图标标签符合图片的尺寸。具体代码如下:

DragIcon::DragIcon(QPixmap pix,QWidget *parent):QLabel(parent)
{
  setScaledContents(true);
  setPixmap(pix);
}

响应鼠标按下事件函数中,首先判断按下的是否是鼠标的左键,若是则记录当时点的位置信息startPos,为后面的判断作准备。具体代码如下:

void DragIcon::mousePressEvent(QMouseEvent *e)
{
  if(e->button()==Qt::LeftButton){
  	startPos=e->pos();
  }
}

创建拖拽类的工作主要在响应鼠标移动事件函数mouseMoveEvent()中完成,代码如下:

void DragIcon::mouseMoveEvent(QMouseEvent *e)
{
  if(!e->buttons()&Qt::LeftButton) // (a)
  	return;
  if((e->pos()-startPos).manhattanLength()
  	<QApplication::startDragDistance()) // (b)
  	return;
  QPixmap pix=*pixmap(); // 获得可拖拽图标的图片
  QByteArray data;
  QDataStream stream(&data,QIODevice::WriteOnly);
  stream<<pix<<QPoint(e->pos()-rect().topLeft());
  QMimeData *mineData=new QMimeData;
  mineData->setData("Drag-Icon",data); // (c)
  QDrag *drag=new QDrag(this); // 生成一个可拖拽QDrag对象
  drag->setMimeData(mineData); // 设置QDrag对象的数据值
  drag->setHotSpot(QPoint(e->pos()-rect().topLeft())); // (d)
  drag->setPixmap(pix); // (e)
  hide(); // 隐藏原位置上的图标
  Qt::DropAction dropAct=drag->exec(Qt::CopyAction | Qt::MoveAction); // (f)
  if(dropAct==Qt::MoveAction) // (g)
  	close();
  else
  	show();
}

其中:

  • (a):判断当前移动时是否是左侧按钮按下,若不是则不响应。
  • (b):判断响应鼠标移动事件时移动的距离是否超过判断为拖拽事件的最小距离,(e->pos()-startPos).manhattanLength()为按住鼠标移动的距离,manhattanLength()为QPoint类的函数,返回某两点间曼哈顿长度,大致相当于如下图所示的距离值。

QApplication::startDragDistance()表示产生拖拽事件的最小距离值(默认为10个像素值),用户按住鼠标移动大于QApplication::startDragDistance()的距离,则认为是一个拖拽事件,可避免由于用户手抖动而引起的误操作。与QApplication::startDragDistance()有同样作用的还有一个QApplication::startDragTime()值,表示产生拖拽事件的最短时间值,可根据实际情况选择合适的值作为产生拖拽事件的判断依据。

  • (c):生成一个QMimeData对象,为下面创建可拖拽QDrag对象作准备,首先创建一个QByteArray对象用于存储需拖拽图标的信息,包含图片信息以及鼠标按下点位置相对图片左上角的偏移量。随后,利用QStreamData类向QByteArray对象中写入数据。最后,调用QMimeData类的setData()函数把包含拖拽图标信息的QByteArray对象data放入QMimeData对象中,setData()函数的第一个参数用于说明数据的格式名,可为自定义的任意格式。
  • (d):调用setHotSpot()函数设置拖拽图标的热点,也即拖动时鼠标相对于拖拽图标左上角的位置。
  • (e):设置鼠标拖动时,QDrag对象显示的图片,一般设置为与需拖动图标一致的图片。
  • (f):调用QDrag类的exec()函数,函数参数设置此拖拽对象可使用的DropAction(放动作),可为复制CopyAction、移动MoveAction、LinkAction等,并等待拖拽结束时返回的DropAction值,默认为CopyAction。
  • (g):对返回的DropAction值进行判断,若返回的为CopyAction,则调用原图标的show()函数,重新在原位置显示图标;若返回的为MoveAction,则表示是一个移动操作,则调用原图标的close()函数关闭原图标。

2.可拖拽图标的容器DragWidget类

实现可拖拽的图标后,则可构建一个DragWidget类作为放置可拖拽图标的容器。

DragWidget继承自QWidget类,在类中重定义dragEnterEvent(QDragEnterEvent *)函数响应拖拽进入事件,dragMoveEvent(QDragMoveEvent *)函数响应拖拽移动事件,dropEvent(QDropEvent *)函数响应拖拽的放事件。具体代码如下:

class DragWidget : public QWidget
{
	Q_OBJECT
public:
  DragWidget(QWidget *parent = nullptr);
  ~DragWidget();
  void dragEnterEvent(QDragEnterEvent *event);
  void dragMoveEvent(QDragMoveEvent *event);
  void dropEvent(QDropEvent *event);
};

DragWidget的构造函数:

DragWidget::DragWidget(QWidget *parent)
: QWidget(parent)
{
  setMinimumSize(400,400); // 设置窗体的最小尺寸
  setAcceptDrops(true); // 设置此窗体可接受拖拽事件
  DragIcon *icon1=new DragIcon(QPixmap(":/images/sheep.png"),this); // 新建3个DragIcon对象
  DragIcon *icon2=new DragIcon(QPixmap(":/images/heart.png"),this);
  DragIcon *icon3=new DragIcon(QPixmap(":/images/fish.png"),this);
  icon1->move(20,20); // 排列3个可拖拽图标的位置
  icon2->move(120,20);
  icon3->move(220,20);
}

dragEnterEvent()函数响应拖拽进入事件的代码如下:

void DragWidget::dragEnterEvent(QDragEnterEvent *e)
{
  if(e->mimeData()->hasFormat("Drag-Icon"))  {
    if(children().contains(e->source()))  {
      e->setDropAction(Qt::MoveAction);
      e->accept();
    }else{
    	e->acceptProposedAction();
    }
  }
}

该函数首先判断QDragEnterEvent的mimeData中是否包含数据格式名为Drag-Icon的数据,若包含则继续判断此拖拽对象是本窗体内部的对象还是外部拖入的拖拽对象,若为本窗体内部的对象则说明这是个移动操作MoveAction,则调用setDropAction()函数设置DropAction为MoveAction;否则说明这是个复制操作CopyAction,则调用acceptProposedAction()示接受默认的操作即CopyAction。这样产生拖拽对象的Draglcon类即可根据此时返回的DropAction (放动作)进行相应的操作。

dragMoveEvent()函数响应拖拽移动事件的代码如下:

void DragWidget::dragMoveEvent(QDragMoveEvent *e)
{
  if(e->mimeData()->hasFormat("Drag-Icon"))  {
    if(children().contains(e->source()))  {
      e->setDropAction(Qt::MoveAction);
      e->accept();
    }else{
    	e->acceptProposedAction();
    }
  }
}

该函数的实现与上面dragEnterEvent()函数实现基本一样,是为了对拖拽进行更细致的控制,在本实例中不实现此函数亦可。

dropEvent()函数响应拖拽的放事件,即拖拽动作的结束操作。具体代码如下:

void DragWidget::dropEvent(QDropEvent *e)
{
  if(e->mimeData()->hasFormat("Drag-Icon")) // (a)
  {
    QByteArray data=e->mimeData()->data("Drag-Icon");
    QDataStream stream(&data,QIODevice::ReadOnly);
    QPixmap pix;
    QPoint offset;
    stream>>pix>>offset; // (b)
    DragIcon *icon=new DragIcon(pix,this);
    icon->move(e->pos()-offset); // (c)
    icon->show();
    if(children().contains(e->source())){ // (d)
      e->setDropAction(Qt::MoveAction);
      e->accept();
    }else{
    	e->acceptProposedAction();
    }
  }else{
  	e->ignore();
  }
}

其中:

  • (a):判断事件的mimeData中是否包含格式名为Drag-Icon的数据,若包含则进行后续的工作,否则忽略此事件。
  • (b):利用QStreamData类从QByteArray对象中读取出所需的数据值,包括图片信息及位置偏移量值。
  • (c):新建一个Draglcon对象,并在减去偏移量的位置处显示。
  • (d):与前面dragEnterEvent()函数中一样对拖拽对象进行判断,是内部或外部对象,从而决定设置DropAction的类型。

可能有同学会认为没有必要在dragEnterEvent()函数与dropEvent()函数中都进行拖拽对象的判断工作,只用在dropEvent()函数中一次性完成即可。事实上在两个函数中都进行判断是有用处的,若不在dragEnterEvent()函数中进行判断设置DropAction,则当鼠标按住图标在本窗体内部拖动还未松开按钮时,箭头下方会显示一个“+”号,这一般是用于表示复制。

3.拖拽效果的步骤总结

以上即一个简单的拖拽实例的实现过程,总结实现的过程,拖拽效果的关键步骤可由下图概括。

由上图可知,实现拖拽功能的最基本的工作有3个部分:

  • (1) 在可拖拽对象的mousePressEvent()函数中构建QDrag对象,并调用exec()函数等待DropAction的返回。
  • (2) 在放置可拖拽对象的容器类中,实现dragEnterEvent()函数,判断并设置应采用何种DropAction,返回给exec()函数。
  • (3) 在放置可拖拽对象的容器类中,实现dragEnterEvent()函数,创建一个新的可拖拽对象并在当前鼠标位置进行显示,同时判断并设置应采用何种DropAction,返回给exec()函数。

——————————————————

对于本文实例完整代码有需要的朋友,可关注并在评论区留言!

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接