文章参考《Qt Creator快速入门(第三版)》。
拖放操作分为拖动Drag和放下Drop,Qt提供了强大的拖放机制,可在帮助文档中通过Drag and Drop关键字查看。
在Qt中,数据拖动时会被存储为MIME类型(Multipurpose Internet Mail Extensions)。Qt提供QMimeData类表示MIME类型的数据,并使用QDrag类完成数据的转移,整个拖放操作是在几个鼠标事件和拖放事件中完成的。
拖放事件:
dragEnterEvent()
拖动进入事件;dropEvent()
放下事件;当鼠标拖拽一个数据进入主窗口时, 会触发dragEnterEvent()
事件,可以使用event->mimeData()
获取其中的MIME数据;然后使用QMimeData::hasUrls()
可以查看是否包含URL路径,如果是拖入文件实际就是拖入了它的路径。如果包含URL就接收event->acceptProposedAction()
,否则就忽略该事件event->ignore()
。
acceptProposedAction()
表示将放置操作设置位建议的操作。
当松开鼠标左键,将数据放到主窗口中就会触发dropEvent()
事件,使用event->mimeData()
获取MIME数据,判断数据中是否有URL,如果有的话就获取URL列表。获取到URL后就可以使用QFile文件操作读取文件并显示到textEdit中。
注意需要在构造函数中添加一行语句:setAcceptDrops(true);
表示当前部件接收放下事件。
具体实现步骤:
首先在头文件中添加拖放事件的声明。
void dragEnterEvent(QDragEnterEvent *event) override;
void dropEvent(QDropEvent *event) override;
在.cpp文件中实现这两个函数。
void Widget::dragEnterEvent(QDragEnterEvent *event)
{
// 拖动进入事件
if(event->mimeData()->hasUrls()) // 数据中是否包含URL
{
event->acceptProposedAction(); // 如果数据中包含URL,就接收动作
}
else
{
event->ignore(); // 如果数据中不包含URL,就忽略该事件
}
}
void Widget::dropEvent(QDropEvent *event)
{
// 放下事件
const QMimeData *mimeData = event->mimeData(); // 获取MIME数据
if(mimeData->hasUrls()) // 如果数据中包含URL
{
QList<QUrl> urlList = mimeData->urls(); // 获取URL列表
// 将其中的第一个URL表示为本地文件路径
QString fileName = urlList.at(0).toLocalFile(); // toLocalFile()转换未本地文件路径
if(!fileName.isEmpty())
{
// 文件路径不为空
QFile file(fileName);
if(!file.open(QIODevice::ReadOnly))
return;
QTextStream in(&file);
ui->textEdit->setText(in.readAll()); // 将文件中的所有内容读入到控件中
}
}
}
QMimeData类提供了几个函数处理常见的MIME数据,如下:
测试函数 获取函数 设置函数 MIME类型 hasText() text() setText() text/plain hasHtml() html() setHtml() text/plain hasUrls() urls() setUrls() text/url-list hasImage() imageData() setImageData() image/* hasColor() colorData() setColorData() application/x-color
如下实例实现随意移动窗口中的图片。
首先,重写几个事件处理函数。
// 重写事件处理函数
void mousePressEvent(QMouseEvent *event) override; // 鼠标按下事件
void dragEnterEvent(QDragEnterEvent *event) override; // 拖动进入事件
void dragMoveEvent(QDragMoveEvent *event) override; // 拖动事件
void dropEvent(QDropEvent *event) override; // 放下事件
然后,在构造函数中编写如下代码。
// 设置窗口部件可以接收拖入操作
setAcceptDrops(true);
// 标签添加图片
QPixmap pix(":/logo");
ui->label->setPixmap(pix);
// 标签大小设置为图片大小
ui->label->resize(pix.size());
// 移动标签
ui->label->move(100,100);
// 设置当窗口关闭时销毁图片
ui->label->setAttribute(Qt::WA_DeleteOnClose);
最后,实现上面的几个事件处理函数
Ⅰ、先实现鼠标按下事件,在鼠标按下事件处理函数中主要实现自定义的MIME类型数据,并执行拖动操作。处理过程大致分为6步:
inherits()
判断是否是QLabel标签,如果不是直接返回。setHotSpot()
指定鼠标在图片上单机的位置,如果不设置这个,在拖动图片过程中指针会位于图片的左上角。QDrag::exec()
,它不会影响主事件循环,这时的界面不会被冻结,exec()函数可以设定支持的放下动作和默认的放下动作,比如支持移动Qt::MoveAction,支持复制操作Qt::CopyAction。调用acceptProposedAction()
函数时使用的默认的操作。exec()的返回值由dropEvent()中的设置决定。示例代码如下:
void MainWindow::mousePressEvent(QMouseEvent *event) // 鼠标按下事件
{
// 1. 获取图片
// 将鼠标指针所在位置的部件强制转换为QLabel类型
QLabel *child = static_cast<QLabel*>(childAt(event->pos())); // childAt()返回指定坐标处的可见子部件,如果指定位置没有可见的子部件,就返回nullptr
if(child == Q_NULLPTR)
{
qDebug() << tr("位置(%1,%2)处没有子部件!").arg(event->pos().x()).arg(event->pos().y());
return;
}
if(!child->inherits("QLabel")) // 判断得到的这个部件是不是继承QLabel
{
qDebug() << tr("当前部件不是QLabel标签部件");
return;
}
// 获取QLabel中的标签
QPixmap pix = *child->pixmap();
// 2. 自定义MIME类型
QByteArray itemData; // 创建字节数组
QDataStream dataStream(&itemData,QIODevice::WriteOnly); // 创建数据流
// 将图片信息、位置信息输入到字节数组中
dataStream << pix << 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(pix);
//设置拖动时鼠标指针的位置不变
drag->setHotSpot(event->pos() - child->pos());
// 5. 给原始图片添加阴影
QPixmap tmpPixmap = pix;
QPainter painter;
painter.begin(&tmpPixmap);
// 在图片的外界觉醒中添加一层透明的淡黑形成阴影效果
painter.fillRect(pix.rect(),QColor(127,127,127,127));
painter.end();
// 在移动图片过程中,让原图片添加一层黑色阴影
child->setPixmap(tmpPixmap);
// 6. 执行拖放操作
// 设置拖放可以是移动和复制操作,默认是复制操作
if(Qt::MoveAction == drag->exec(Qt::CopyAction | Qt::MoveAction,Qt::CopyAction))
{
child->close(); // 如果是移动操作,拖放完成后关闭原标签
}
else
{
child->show(); // 如果是复制操作,拖放完成后显示标签
child->setPixmap(pix); // 显示原标签,不再使用阴影
}
}
Ⅱ、再处理拖动进入和拖动事件,在这两个事件处理函数中,先判断拖动的数据中是否有自定义的MIME类型的数据,如果有,就执行移动动作。示例代码:
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();
}
}
Ⅲ、最后处理放下事件,使用字节数组获取拖放的数据,然后获取图片数据和位置信息,并使用这些数据设置新建的标签。示例代码:
void MainWindow::dropEvent(QDropEvent *event) // 放下事件
{
// 放下事件,判断是否有自定义的MIME类型的数据,如果有就获取这些数据并将图片显示出来
if(event->mimeData()->hasFormat("myimage/png"))
{
QByteArray itemData = event->mimeData()->data("myimage/png");
QDataStream out(&itemData,QIODevice::ReadOnly);
QPixmap pix;
QPoint ptOffset;
// 将MIME数据读入到QPixmap和QPoint中
out >> pix >> ptOffset;
// 新建标签,添加图片,并根据图片大小设置标签大小
QLabel *label = new QLabel(this);
label->setPixmap(pix);
label->resize(pix.size());
// 让图片移动到放下的位置,如果不设置,图片会默认显示在窗口左上角
label->move(event->pos() - ptOffset);
label->show();
label->setAttribute(Qt::WA_DeleteOnClose);
event->setDropAction(Qt::MoveAction); // 设置移动过程中的操作,Qt::MoveAction是移动,如果需要复制操作,改成Qt::CopyAction
event->accept();
}
else
{
event->ignore();
}
}
运行程序显示结果:
将拖动进入、拖动、放下事件中的Qt::MoveAction改为Qt::CopyAction运行后显示结果:
完整代码可下载:https://download.csdn.net/download/sinat_41752325/87378359。