Qt拖放(1):拖放基本原理(QDrag类)

Qt拖放(1):拖放基本原理(QDrag类)

本文为原创文章,转载请注明出处,或注明转载自“黄邦勇帅(原名:黄勇)

本文出自本人原创著作《Qt5.10 GUI完全参考手册》网盘地址:
https://pan.baidu.com/s/1iqagt4SEC8PUYx6t3ku39Q
《C++语法详解》网盘地址:https://pan.baidu.com/s/1dIxLMN5b91zpJN2sZv1MNg

若对C++语法不熟悉,建议参阅本人所著《C++语法详解》一书,电子工业出版社出版,该书语法示例短小精悍,对查阅C++知识点相当方便,并对语法原理进行了透彻、深入详细的讲解,可确保读者彻底弄懂C++的原理,彻底解惑C++,使其知其然更知其所以然。此书是一本全面了解C++不可多得的案头必备图书。

本章讲解的类及继承关系如图9-1所示
在这里插入图片描述

9.1 拖放原理

9.1.1 拖放基本原理

拖放操作包括两个动作:拖动(drag)和放下(drop或称为放置)。当被拖动时拖动的数据会被存储为MIME类型(见第6章文件对话框)的对象,MIME类型使用QMimeData类来描述。MIME类型通常由剪贴板和拖放系统使用,以识别不同类型的数据。
拖动点(drag site):拖动的起始位置。
放下点(drop site):被拖动的对象放下的位置,若部件不能接受拖动的对象,Qt会改变光标的形状(一个禁用形状)来向用户进行说明。
1、拖放的启动和结束
(1)、启动拖放:拖放通过调用QDrag::exec()函数而启动,该函数是一个阻塞函数(但不会阻塞主事件循环),这意味着在拖放操作结束之前,不会返回该函数,调用QDrag::exe()函数后,Qt拥有对拖动对象的所有权,并会在必要时将其删除。
2)、结束拖放:当用户放下拖动或取消拖动操作时结束拖放。
2、拖放产生的过程及事件
(1)、启动拖放后,会使数据被拖动,这时需要按住鼠标按键才能拖动需要拖动的数据,松开鼠标按键时意味着拖动结束。在这期间会产生如下事件
(2)、默认情况下,部件不接受放下事件。使用QWidget::setAcceptDrops()函数可设置部件是否接受放下事件(即,拖放完成时发送的事件)。只有在部件接受放下事件的情形下,才会产生以下事件。
 QDragEnterEvent:拖动进入事件。当拖动操作进入部件时,该事件被发送到部件,忽略该事件,将会导至后续的拖放事件不能被发送,此时在该部件上光标通常会在外观上显示为禁用的图形。
 QDragMoveEvnet:拖动移动事件。当拖动操作正在进行时,以及当具有焦点时按下键盘的修饰键(比如Ctrl)时,发送该事件,要使部件能接收到该事件,则该部件必须接受QDragEnterEvent事件。
 QDropEvent:放下事件。在完成拖放操作时发送该事件,即当用户在部件上放下一个对象时,发送此事件。要使部件能接收到该事件,则该部件必须接受QDragEnterEvent事件,且不能忽略QDragMoveEvnt事件。
 QDragLeaveEvent:当拖放操作离开部件时发送该事件,注意:要使部件能接收到该事件,必须要使拖动先进入该部件(即产生QDragEnterEvent事件),然后再离开该部件,才会产生QDragLeaveEvent事件。因很少使用该事件,因此本文不做重点介绍。
 注:必须接受是指必须重新实现该事件的处理函数并接受该事件,不能忽略是指在处件事理函数中不明确调用ignore()函数忽略该事件。
 以上事件产生的顺序为(详见图9-2):QDragEnterEvent、QDragMoveEvnet、QDropEvent
在这里插入图片描述

3、编写拖放程序的步骤
(1)、在需要接受放下数据的部件上调用QWidget::setAcceptDrops()函数以使该部件能接受拖放事件。
(2)、启动拖放:通常在mousePressEvent()或mouseMoveEvent()函数中启动拖放,记住启动拖放就是调用QDrag对象的exec()函数,因此也可以在keyPressEvent()等函数中启动拖放(因很少这样做,所以本文不予介绍)。在此步把需要拖动的数据保存在QMimeData对象中。
(3)、重新实现需要接受放下数据的部件的dragEnterEvent()事件处理函数。
(4)、根据需要重新实现dragMoveEvent或dropEvent()函数
下面以实现代码为例进行讲解
示例9.1:简单的拖放

//m.h文件的内容
#ifndef M_H
#define M_H
#include<QtWidgets>
class C:public QPushButton{Q_OBJECT
public:	 C(QString t="",QWidget *p=0):QPushButton(t,p){}
void  mousePressEvent(QMouseEvent *e){  	//在该事件中启动拖放
    		QDrag *dg=new QDrag(this);
      //将需要拖动的数据放入QMimedata对象中,该对象用于保存需要传递的数据,数据的内
//容完全由程序员自行设定。通常为界面上所选择内容。
    		QMimeData *md=new QMimeData;
    		md->setText("FFF");    			//这是md中存储的内容(即拖放时传递的数据)。
    		dg->setMimeData(md);   			//步骤1:设置拖动的数据。该函数会获得md的所有权。
    		dg->exec();         }   		//步骤2:启动拖放
 	void dragEnterEvent(QDragEnterEvent  * e){
//步骤3:处理是否接受拖动事件。
    			e->accept();  			//接受拖动进入事件
//e->ignore();  	/*若忽略该事件,则不会再发送之后的事件,拖放至此结束,这会导致鼠标光标显示为禁用的图形。*/
 }
 	void dropEvent(QDropEvent  * e){
		//步骤4:处理拖动中的数据(当然也可不作任何处理)
    		setText(e->mimeData()->text());	//设置此部件的文本为拖动对象中的文本。
    		//此事件不影响后续事件,可接受也可忽略。
    		//e->accept();
    		//e->ignore();
 		}		};
class B:public QWidget{    Q_OBJECT
public: B(QWidget *p=0):QWidget(p){
    C *pb1=new C("AAA",this);    pb1->move(22,22);  pb1->setIcon(QIcon("F:/1i.png"));
    C *pb2=new C("BBB",this);    pb2->move(99,22);
    pb2->setAcceptDrops(true);     		//pb2接受放下事件
pb1->setAcceptDrops(0);   }	};  		//pb1禁止放下事件。
#endif // M_H

//m.cpp文件的内容
#include "m.h"
int main(int argc, char *argv[]){    QApplication app(argc,argv);
    B w;    w.resize(444,355);    w.show();    return app.exec();  }

运行结果及说明见图9-3
在这里插入图片描述

9.1.2 拖放动作(或称为放置动作)

拖放动作是指用户希望怎样处理拖放的数据,比如移动、复制、还是创建由目标到源的链接等。拖放动作由Qt::DropAction枚举描述(其取值见第8章表8-3)
QDrag::exe()函数的原型之一如下,详见后文对QDrag类的讲解。

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

1、可能的拖放动作,实际的拖放动作,建议的拖放动作
可能的拖放动作是指用户在拖放时可能会执行的拖放动作,用户在拖放时通常可由用户选择,比如可以选择移动、复制或链接(可通过同时按住键盘修饰键进行改变)等动作中的一种,这些动作都是可能的拖放动作。可能的拖放动作在QDrag::exe()函数的第1个参数中指定,同时该函数的返回值是最终的实际拖放动作。
实际的拖放动作是指拖放被最终放置时实际执行的动作,实际拖放动作在dropEvent()函数中使用QDropEvent::setDropAction()函数(还需在之后调用accept()函数)设置,该函数会影响QDrag::exec()函数的返回值。
建议的拖放动作是指当用户执行拖动而不使用修饰键时的默认动作,建议拖放动作可在QDrag::exec()函数的第2个参数中指定,该参数的设置会影响拖动时鼠标光标右下角的外观,另外QDropEvent::acceptProposedAction()函数表示设置执行操作为建议操作并接受该事件。
以上三种拖放动作常常相互关联,比如用户在拖动时通常可以执行移动、复制或链接等动作(可能的拖放动作)中的一种,然而应用程序在拖放被最终放置时并不知道用户到底需要执行哪种操作,若用户未指定需要执行的可能的拖放动作中的哪一种动作时,应用程序可以使用设置的建议动作,作为需要执行的动作。
2、各拖放动作之间的关系
(1)、QDrag::exec()函数的规则
 若QDrag::exec()未指定建议拖放动作,则依顺序移动、复制、链接进行选择
 若QDrag::exec()函数在第2个参数上指定了建议拖放动作,但该动作不在可能的拖放动作组合之中,则使用默认的复制拖放动作。比如
QDrag *dg = new QDrag(this);
……
dg->exec(Qt::MoveAction|Qt::CopyAction, Qt::LinkAction); //建议动作为复制。
(2)、QDropEvent::setDropAction()函数的规则
 使用setDropAction()函数设置拖放动作之后应使用accept()函数,而不应使用QDropEvent::acceptProposedAction()函数(因为该函数会重置拖放动作为建议拖放动作)
 若QDropEvent::setDropAction()函数设置的拖放动作不在可能的拖放动作组合之中,则使用建议拖放动作。
(3)、dropEvent()函数的规则,该函数是否接受事件直接影响到QDrag::exec()函数的返回值,其规则如下
 若在该函数内调用ignore(),则exec()函数返回Qt::IgnoreAction
 若在该函数内调用accept(),则exec()函数返回在该函数中使用setDropAction()函数设置的拖放动作。详细规则见示例9.2。

示例9.2:各种拖放动作

Qt::DropAction d= QDrag::exe(Qt::MoveAction | Qt::CopyAction);   //可能的拖放动作(移动、复制)
   		…
 	void drogEvent(QDropEvent *e){
   		//下面分情形讲解该函数对exe()返回值(即以上语句的d)的影响
  		//情形1:d = Qt::MoveAction
  		if(…){	e->setDropAction(Qt::MoveAction);		e->accept();}
//情形2:d = Qt::CopyAction
  		else if(…){	e->setDropAction(Qt::CopyAction);	e->accept();}
//情形3:d = 建议的拖放动作
else if(…){	e->acceptProposedAction();	}
//情形4:d = Qt::IgnoreAction
else if(…){	e->ingore();	}   //该部件不接受放置动作
//情形5:d = 建议的拖放动作
else if(…){
e->setDropAction(Qt::MoveAction);
e->acceptProposedAction();  /*使用该函数会把最终拖放动作重置为建议的拖放动作,因此调用setDropAction()之后应调用accept()函数。*/
e->accept();}
		//情形6:d = Qt::IgnoreAction
else if(…){
e->setDropAction(Qt::MoveAction);
e->ingore();}    	//该函数表示不接受放置操作,这会使之前设置的拖放动无效。
//情形7:d = 建议的拖放动作,因为Qt::LindAction不是可能的拖放动作之一。
else if(…){e->setDropAction(Qt::LinkAction);e->accept();} 	}

3、拖放动作及拖放的程序设计原则
(1)、若在mouseMoveEvent()函数中启动拖放,则可以编写避免用户因为手握鼠标抖动而产生的拖动,这比在mousePressEvent()函数中启动拖放效果更好。
(2)、在QDrag::exe()函数的参数中指定可能的拖放动作,比如在其中同时指定移动、复制、链接等;但最终是否接受这些动作,由后续的事件处理函数进行判断,详见后文。另外需要注意的是QDrag::exec()函数虽是阻塞函数,但在执行完该函数(比如释放鼠标按钮完成拖放时)后程序会返回该函数,然后接着执行之后的语句,exec()函数返回的是用户实际执行的动作。
(3)、dragEnterEvent()函数通常根据该部件或实际使用情况进行筛选,比如若该部件不接受图片数据,则忽略对该动作的接受,从而阻止事件被进一步传递。
(4)、若重新实现了dragMoveEvent()函数,则还可以在该函数内进行进一步的设置,比如默认为复制动作,若用户拖动的同时按下了Shift键,则设置为移动动作,若按下了Ctrl键,则为复制动作,若按下了Alt键则为链接动作等,这些设置可以影响鼠标光标在外观上的显示,比如复制会在光标右下角显示一个“+”符号等。另外,在该函数内还可以限制用户拖动到该部件中的某一范围,比如拖动到某矩形范围内时,该部件才接受拖放,否则被忽略等。注意:若在dragEnterEvent()函数内也设置了拖放动作,同样会改变光标的外观但只会改变进入部件时的外观,光标最终的外观以dragMoveEvent()函数设置的为准(因为该函数位于dragEnterEvent()之后执行)。
(5)、在drogEvent()函数内最终决定对拖放的数据的处理,以及用户实际执行的拖放动作,因此该函数决定着QDrag::exe()函数的返回值。这里要注意的是,对于移动动作,通常原始数据应由源部件(启动拖放的部件)进行删除,因此当drogEvent()处理完数据之后,应把拖放动作设置为移动,QDrag::exec()函数会返回在drogEvent()函数中设置的动作,然后源部件根据QDrag::exe()的返回值是否是移动动作,而作出是否删除原始数据的决定。注:dragEnterEvent()和dragMoveEvent()对拖放动作的设置不会影响QDrag::exe()的返回值。
(6)、注意:在源部件中创建的QMimeData和QDrag对象不应由程序员销毁,因为Qt会自动销毁,若程序员销毁了,则可能会出现多次delete同一个指针的错误。
(7)、以上只是通常在各函数中的做法,当然你也可以不按照这些步骤来实现,从之前的拖放示例可以看到,对拖放的处理完全是任意的。
(8)、注意:Qt为某些部件提供了一些标准的拖放支持,在继承这些部件实现拖放时需要重新实现dragEnterEvent()、dropEvent(),另外还可能需要重新实现dragMoveEvent()函数,以避免与标准实现的拖放支持相冲突或产生预料之外的结果。

示例9.3:拖放动作
//m.h文件的内容  
#ifndef M_H
#define M_H
#include<QtWidgets>
#include<QDebug>
class C:public QPushButton{Q_OBJECT
public:    QPoint p;   										//用于保存点击鼠标的位置
    C(QString t="",QWidget *pp=0):QPushButton(t,pp){}
void  mousePressEvent(QMouseEvent *e){    p=e->pos(); }   	//第一次点击鼠标的位置
 	void  mouseMoveEvent(QMouseEvent *e){
      //若拖动的距离大于5个像素则启用拖放(避免抖动)。
    		if((e->pos()-p).x()>=5||(e->pos()-p).y()>=5) {
    			QDrag *dg=new QDrag(this);    QMimeData *md=new QMimeData;
    			md->setText("FFF");    		dg->setMimeData(md);
    //启动拖放,该数据可复制、移动、链接,具体是否接受这些动作,需由后续程序决定。
//若drogEvent()函数把拖放动作设置为移动,则需要对原始数据作进一步处理,注意:在完成拖放
//后会返回exec()函数继续执行其后的语句。。
    			if(dg->exec(Qt::CopyAction|Qt::MoveAction|Qt::LinkAction)==Qt::MoveAction)
   			 	qDebug()<<"AAA";  //本示例没有可删除的原始数据,因此只简单输出字符串用于测试。
  } 		//if结束
//注:鼠标移动距离也可使用以下语句判断,其中manhattanLength()表示曼哈顿距离,startDragDistance
//表示系统推荐的拖动起始距离。
    		//if((e->pos()-p).manhattanLength()>=QApplication::startDragDistance())
}    
 	void dragEnterEvent(QDragEnterEvent  * e){
//若拖动的数据中不包含文本FFF则忽略该事件,否则接受该事件。
   		if(e->mimeData()->text()!="FFF")   e->ignore();    else    e->accept();	}
 	void dragMoveEvent(QDragMoveEvent  * e){
/*以下设置会改变鼠标光标的外观。若拖动的同时按下了CTRL、ALT、SHIFT键,则把施放动作设置为复制、移动、链接,否则为复制。*/
 		if(e->keyboardModifiers()==Qt::CTRL)		e->setDropAction(Qt::CopyAction);
  		else if(e->keyboardModifiers()==Qt::SHIFT)  e->setDropAction(Qt::MoveAction);
  		else if(e->keyboardModifiers()==Qt::ALT)   	e->setDropAction(Qt::LinkAction);
  		else e->setDropAction(Qt::CopyAction);
//限制拖动的范围:若光标位于矩形r之内,则接受该事件,否则忽略该事件。
   		QRect r(0,0,111,33);	
if(r.contains(e->pos())){    e->accept()	;}    else e->ignore();		}
 	void dropEvent(QDropEvent  * e){
/*若拖动的源和目标在同一个部件,则什么也不做。注意应使用return;跳出函数,若使用ignore()或accept(),程序还会继续执行之后的语句。*/
		if(e->source()==this) return;
    		setText(e->mimeData()->text());
    		e->setDropAction(Qt::MoveAction); 	//把拖放动作设置为移动。
    		e->accept();  }};		//使用此步骤会把QDrag::exe()函数的返回值设置为上面设置的移动动作
class B:public QWidget{    Q_OBJECT
public:    
B(QWidget *p=0):QWidget(p){
    		C *pb1=new C("AAA",this);    pb1->move(22,22);
    		C *pb2=new C("BBB",this);    pb2->resize(111,66);   pb2->move(99,22);
    		pb1->setIcon(QIcon("F:/1i.png"));    
pb2->setAcceptDrops(true);    pb1->setAcceptDrops(0);       }};
#endif // M_H

//m.cpp文件的内容
#include "m.h"
int main(int argc, char *argv[]){    QApplication app(argc,argv);
    B w;    w.resize(444,355);    w.show();    return app.exec();  }

运行结果及说明(见图9-4),当拖放被成功放置到按钮BBB上时,程序会输出字符串AAA。
在这里插入图片描述

9.1.3 使用拖放打开文件

拖放文件的基本步骤:文件需要使用QFile类来打开,然后才能读取或存入其内容,因此对拖放的文件进行处理,其实就是获取文件的地址,而地址是使用URL来表示的,因此首先需要判断拖放的数据是否含有URL,然后读取出URL中保存的文件的地址,再打开文件,然后读取文件的内容,有关流和文件的内容本章暂时不用深入了解,明白示例9.4代码的作用即可。

示例9.4:拖放文件
//m.h文件的内容
#ifndef M_H
#define M_H
#include<QtWidgets>
class C:public QPushButton{Q_OBJECT
public:    C(QString t="",QWidget *p=0):QPushButton(t,p){}
 	void dragEnterEvent(QDragEnterEvent  * e){
		//若拖动的数据包含一个URL则接受该事件,否则忽略该事件。
   		if(e->mimeData()->hasUrls()){	e->accept();   }   	else e->ignore();	 }
 	void dropEvent(QDropEvent  * e){
   		const QMimeData *pm=e->mimeData();
    		QList<QUrl> u=pm->urls();    				//读取出URL的地址列表。
   	 	QString pth=u.at(0).toLocalFile();   			//将地址转换为QString
    		if(!pth.isEmpty()){     					//判断地址pth是否为空
        		QFile file(pth);     					//创建文件file
            	if(!file.open(QIODevice::ReadOnly));   	//以只读方式打开文件
        			QTextStream in(&file);     			//创建流用于读取文件的内容。
          		setText(in.readAll());    } } }; 	//读出文件的内容,并设置为该部件的文本
class B:public QWidget{    Q_OBJECT
public:
B(QWidget *p=0):QWidget(p){
/*注:实际编程时只需把按钮替换为QTexeEdit之类的部件即可,此处为避免复杂性及明白其原理,使用简单的按钮就可以了。*/
    		C *pb1=new C("AAA",this);    pb1->move(22,22);   pb1->setAcceptDrops(true);   }		};
#endif // M_H

//m.cpp文件的内容
#include "m.h"
int main(int argc, char *argv[]){    QApplication app(argc,argv);
    B w;    w.resize(444,355);    w.show();    return app.exec();  }

运行结果及说明见图9-5
在这里插入图片描述

9.2 与拖放事件有关的类及函数

本小节所讲的内容在上一小节几乎都已介绍了,此处仅列出其原型并作一简要介绍。

9.2.1 QDropEvent类

1、QDropEvent继承自QEvent,在完成拖放操作时发送该事件,具体规则见前文
2、QDropEvent类中的函数
在这里插入图片描述

9.2.2 QDragMoveEvent类

QDragMoveEvent继承自QDropEvent,当拖动操作正在进行时,以及当具有焦点时按下键盘的修饰键(比如Ctrl)时,发送该事件,具体规则见前文
QDragMoveEvent类中的函数如下
在这里插入图片描述
9.2.3 QDragEnterEvent类和QDragLeaveEvent类

QDragEnterEvent继承自QDragMoveEvent,当拖动操作进入部件时,该事件被发送到该部件,具体规则见前文
QDragEnterEvent类仅有一个构造函数,原型如下所示
在这里插入图片描述

9.2.4 QWidget类中与拖放有关的函数

QWidget类中与拖放有关的函数就是拖放事件的处理函数,这些函数都是受保护的虚拟函数,现列出如下:

virtual QWidget::dragEnterEvent(QDragEnterEvent *e);   	//受保护的,虚拟的
   virtual QWidget::dragMoveEvent(QDragMoveEvent *e);   	//受保护的,虚拟的
   virtual QWidget::dropEvent(QDropEvent *e);   			//受保护的,虚拟的
   virtual QWidget::dragLeaveEvent(QDragLeaveEvent *e);   	//受保护的,虚拟的

9.3 QDrag类

QDrag继承自QObject,该类支持基于QMimeData对象数据的拖放。该类主要成员函数的使用方式在前文已介绍过,该类的其他成员函数可设置拖放时的外观等其他设置,比如拖放时显示一个图标而不是鼠标光标等。
QDrag类中的函数如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
本文作者:黄邦勇帅(原名:黄勇)

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/hyongilfmmm/article/details/83238239