Qt 使用自定义UI部件在Qt布局中实现拖放

一、问题描述

        在程序日常使用中,往往会觉得这个窗口的布局不尽人意,为了适配大部分人的需求,让布局里的控件可以随用户的喜好拖动是有必要的。

二、解决思路

        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);
}
实现效果:

参考资料:

http://t.csdnimg.cn/5ft6R

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值