原理:
- 自定义MIME类型
- 重写相应的事件函数
示例:
效果:
在窗口上有一个图片,可以任意拖动它。
代码:
- 新建QWigets应用,基类选择QMainWindow
- 在mainwindow.h中添加如下内容:
protected:
void mousePressEvent(QMouseEvent * event); // 鼠标按下事件
void dragEnterEvent(QDragEnterEvent * event); // 拖动进入事件
void dragMoveEvent(QDragMoveEvent * event); // 拖动事件
void dropEvent(QDropEvent * event); // 拖动放下事件
- 在mainwindow.cpp中添加如下头文件:
#include <QLabel>
#include <QMouseEvent>
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QPainter>
#include <QMimeData>
#include <QDrag>
- 在构造函数添加如下代码:
setAcceptDrops(true); // 设置窗口部件可以接收拖入,默认不可以接收拖入动作的
QLabel * label = new QLabel(this); // 创建标签
QPixmap pix("../5-8/zoom.png"); // 这里使用了相当对路径,你也可以用 Qt资源文件
label->setPixmap(pix); // 设置图片
label->resize(pix.size()); // 设置标签大小为图片的大小
label->move(100, 100);
label->setAttribute(Qt::WA_DeleteOnClose); // 当窗口关闭时销毁图片
- 添加上述重写实现的事件函数定义:
void MainWindow::mousePressEvent(QMouseEvent *event) // 鼠标按下事件
{
// 第1步:获取图片
// 将鼠标指针所在位置的部件强制转为 QLabel 类型,childAt(event->pos())返回鼠标指针在位置的可见子部件,没有返回0
QLabel * child = static_cast <QLabel *> (childAt(event->pos()));
if(!child->inherits("QLabel")) return; // 如果部件不是QLabel则直接返回
QPixmap pixmap = * child->pixmap(); // 获取QLabel中的图片
// 第2步:自定义MIME类型
QByteArray itemData;
QDataStream dataStream(&itemData, QIODevice::WriteOnly); // 创建数据流,只写模式
// 将图片信息,位置信息写入到字节数组中
dataStream << pixmap << QPoint(event->pos() - child->pos());
// 第3步:将数据放入QMimeData中
QMimeData * mimeData = new QMimeData; // 创建QMimeData用来存放要移动的数据
// 将字节数组放入QMimeData中,这里的MIME类型是我们自己定义的
mimeData->setData("myimage/png", itemData);
// 第4步:将QMimeData数据放入QDrag中
QDrag * drag = new QDrag(this); // 创建QDrag,它用来移动数据
drag->setMimeData(mimeData);
drag->setPixmap(pixmap); // 在移动过程中显示图片,若不设置则默认显示一个小矩形
drag->setHotSpot(event->pos() - child->pos()); // 拖动时鼠标指针的位置不变,设置鼠标指针相对于图片左上角位置
// 第5步:给原图片添加阴影
QPixmap tempPixmap = pixmap; // 使原图片添加阴影
QPainter painter; // 创建QPainter,用来绘制QPixmap
painter.begin(&tempPixmap);
// 在图片的外接矩形中添加一层透明的淡黑色形成阴影效果
painter.fillRect(pixmap.rect(), QColor(127, 127, 127, 127)); // rgb + 透明度
painter.end();
child->setPixmap(tempPixmap); // 在图片移动过程中,让原图片添加一层黑色阴影
// 第6步:执行拖放操作
if(drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction)
== Qt::MoveAction) // 设置拖放可以使移动和复制操作,默认是复制操作
child->close(); // 如果是移动操作,那么拖放完成后关闭原标签
else {
child->show(); // 如果是复制操作,那么拖放完成后显示标签
child->setPixmap(pixmap); // 显示原图片,不再使用阴影
}
}
- 对于
mousePressEvent( )
中第6步的QDrag类的exec( )
函数,它不会影响主事件循环,所以这时界面不会被冻结。
这个函数设置了支持复制和移动动作,设置复制为默认放下动作。
exec( )
函数的返回类型由dropEvent( )
中的设置决定。
这里判断了到底进行了什么操作,如果是移动操作,那么就删除原来的图片;如果是复制操作,就恢复原来的图片。
void MainWindow::dragEnterEvent(QDragEnterEvent *event) // 拖动进入事件
{
// 如果有我们定义的MIME类型数据,则进行移动操作
if(event->mimeData()->hasFormat("myimage/png")) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->ignore();
}
}
void MainWindow::dragMoveEvent(QDragMoveEvent *event) // 拖动事件
{
// 如果有我们定义的MIME类型数据,则进行移动操作
if(event->mimeData()->hasFormat("myimage/png")) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->ignore();
}
}
- 在这2个事件处理函数中,先判断拖动的数据中是否包含自定义的MIME类型的数据,如果有,则执行移动动作Qt::MoveAction
void MainWindow::dropEvent(QDropEvent *event) // 拖动放下事件
{
if(event->mimeData()->hasFormat("myimage/png")) {
QByteArray itemData = event->mimeData()->data("myimage/png");
QDataStream dataStream(&itemData, QIODevice::ReadOnly); // 只读模式
QPixmap pixmap;
QPoint offset;
// 使用数据流将字节数组中的数据读入到QPixmap和QPoint
dataStream >> pixmap >> offset;
// 新建标签,为其添加图片,并根据图片的大小设置标签的大小
QLabel * newLabel = new QLabel(this);
newLabel->setPixmap(pixmap);
newLabel->resize(pixmap.size());
// 让图片移动到放下的位置,不设置的话,图片会默认显示在(0, 0)点,即窗口左上角
newLabel->move(event->pos() - offset);
newLabel->show();
newLabel->setAttribute(Qt::WA_DeleteOnClose);
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->ignore();
}
}
几点说明:
- 这个例子是对图片进行移动,如果想对图片进行复制,则只需要将
dragEnterEvent( )、dragMoveEvent( )、dropEvent( )
这三个函数中的event->setDropAction( )
函数中的参数改为Qt::CopyAction
即可。 - 对于拖放操作的其他应用,比如根据移动的距离判断是否开始一个拖放操作,还有剪贴板QClipboard类,都可以在帮助中通过Drag and Drop关键字查看。
- 上述代码中使用了很多相对位置,读者如果有一些疑惑,可以尝试使用
qDebug()
输出相关坐标信息,或者不使用相对坐标看看会怎么样,效果是很直观的,这都是一些小技巧!