一、问题描述
在程序日常使用中,往往会觉得这个窗口的布局不尽人意,为了适配大部分人的需求,让布局里的控件可以随用户的喜好拖动是有必要的。
二、解决思路
Qt有自带的拖放相关类如下:
- QDrag:支持基于MIME的拖放数据传输。
- QDragEnterEvent:拖放进入事件,拖放动作进入小部件时发送给小部件的事件。
- QDragLeaveEvent:拖动离开事件,拖放操作离开小部件时发送给小部件的事件。
- QDragMoveEvent:拖动移动事件,拖放进入小部件后移动时发送给小部件的事件。
- QDropEvent:拖动放下事件,拖放动作完成时发送的事件。
当鼠标在控件上按下,并且开始移动超过一定距离时,判定为控件开始拖动。
鼠标松开时,放下控件,并且从原来的布局中,删除控件,再根据控件放下的位置,计算控件重新插入的位置,再插入布局中。
三、代码
.h文件:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTextEdit>
#include <QDrag>
#include <QMimeData>
#include <QVBoxLayout>
#include <QMouseEvent>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class DraggableTextEdit;
class Widget : public QWidget{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QVBoxLayout *layout;
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
void adjust_layout(QWidget* ui_drag, const QPoint& position);
};
class DraggableTextEdit : public QTextEdit{
public:
DraggableTextEdit(const QString &text = "", QWidget *parent = nullptr)
: QTextEdit(text, parent), isDragging(false) {}
void mousePressEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
startPos = event->pos();
isDragging = true;
}
}
void mouseMoveEvent(QMouseEvent *event) override {
if (!(event->buttons() & Qt::LeftButton)) return ;
if ((event->pos() - startPos).manhattanLength() < 10) return;
if (!isDragging) return;
QDrag *drag = new QDrag(this);//创建拖放类
QMimeData *mimeData = new QMimeData;//创建拖放时要额外保存的数据
QByteArray itemData; // 创建控件标识信息
QDataStream dataStream(&itemData, QIODevice::WriteOnly);
dataStream << reinterpret_cast<qintptr>(this); // 存储控件的指针
mimeData->setData("flag", itemData);
drag->setMimeData(mimeData);
//可以设置一个可视化的拖拽提示,这里是截图
QPixmap pixmap(this->size());
this->render(&pixmap);
drag->setPixmap(pixmap);
isDragging = false;
if (drag->exec(Qt::MoveAction) == Qt::MoveAction) {}//阻塞,直到拖放操作完成
}
void mouseReleaseEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton)
isDragging = false;
}
private:
QPoint startPos;
bool isDragging;
};
#endif // WIDGET_H
.cpp文件
#include "widget.h"
#include "ui_widget.h"
#include <QVBoxLayout>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
setAcceptDrops(true);
this->resize(400,400);
layout=new QVBoxLayout();
DraggableTextEdit *textEdit1=new DraggableTextEdit("test1");
DraggableTextEdit *textEdit2=new DraggableTextEdit("test2");
DraggableTextEdit *textEdit3=new DraggableTextEdit("test3");
layout->addWidget(textEdit1);
layout->addWidget(textEdit2);
layout->addWidget(textEdit3);
this->setLayout(layout);
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::dragEnterEvent(QDragEnterEvent *event) {
if (event->mimeData()->hasFormat("flag")) {
event->acceptProposedAction();
}
}
void Widget::dragMoveEvent(QDragMoveEvent *event) {
if (event->mimeData()->hasFormat("flag")) {
event->acceptProposedAction();
}
}
void Widget::dropEvent(QDropEvent *event) {
if (event->mimeData()->hasFormat("flag")) {
//如果是带有 flag 的控件,读取flag,并且将数据转换为指针
QByteArray itemData = event->mimeData()->data("flag");
QDataStream dataStream(&itemData, QIODevice::ReadOnly);
qintptr widgetPtr;
dataStream >> widgetPtr;
DraggableTextEdit* draggable = reinterpret_cast<DraggableTextEdit*>(widgetPtr);
// 使用itemData等信息来确定哪个控件被拖放
QPoint position = event->pos();
// 更新布局
adjust_layout(draggable, position);
event->acceptProposedAction();
}
}
void Widget::adjust_layout(QWidget* ui_drag, const QPoint& position) {
int insertAt = -1;
for (int i = 0; i < layout->count(); ++i) {
QLayoutItem* item = layout->itemAt(i);
QWidget* widget = item->widget();
if (widget) {
QRect widgetFrame = widget->frameGeometry();
if (position.y() < widgetFrame.center().y()) {
insertAt = i;
break;
}
}
}
if (insertAt == -1) // 没有找到合适的位置,默认放到最后
insertAt = layout->count()-1;//因为待会儿要删除原来的控件,所以count的数量会变化
ui_drag->setParent(this);
layout->removeWidget(ui_drag);
layout->insertWidget(insertAt, ui_drag);
}