Skip to content
正常

TreeDragMove 手搓手册

source:成品答案在 model/01-mv-pattern/tree-drag-move/(做完对照)· related拖放系统(入门)QTreeWidget(入门)

这是「手搓手册」

不是参考手册(查完走),是 workbook(跟着搓)。每个 step 给目标 → 提示 → 检查点,成品 repo 当答案钥匙——卡住了去对照,别整段复制。

0. 你将学到

搓完这个 TreeDragMove,你会打通这几样 Qt 能力(每样后面都有教程深挖,这里先用起来):

  • QAbstractItemView 拖放四件套标志位setDragEnabled / setAcceptDrops / setDragDropMode / setDefaultDropAction——拖放的总开关
  • 拖放协议四个虚函数mimeTypes / mimeData / supportedDropActions / dropMimeData——框架按固定顺序回调,接管它们就能自定义拖拽数据
  • 自定义 MIME 类型 + QDataStream 序列化:进程内用指针编码零歧义定位源节点
  • dropEvent 落点判定itemAt(pos) 区分「成为子节点」与「同级插入」
  • InternalMove 的删源时序:协议层只复制、dropEvent 统一删源,避开 double-free

1. 起点

先有个能跑、能填数据的 QTreeWidget 空壳。继承 QTreeWidget

C++
#include <QApplication>
#include <QTreeWidget>
#include <QTreeWidgetItem>

class MyTree : public QTreeWidget {
  public:
    MyTree(QWidget* p = nullptr) : QTreeWidget(p) {
        setHeaderLabel("拖我");
        addTopLevelItem(new QTreeWidgetItem({"A"}));
    }
};

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);
    MyTree t;
    t.show();
    return app.exec();
}

跑出来看到一棵树、有数据 = 环境通了,往下走。QTreeWidget 不熟先看 QTreeWidget(入门)

2. 任务清单

分 6 步,每步:目标 → 提示 → 检查点。卡住翻 卡住怎么办

Step目标
1开拖放标志位,节点能拖动01
2重写 mimeTypes + mimeData(拖出端序列化源)01
3重写 supportedDropActions + dropMimeData(拖入端落地)02
4dropEvent 落点判定 + 删源(InternalMove 收尾)02
5深拷贝 cloneItem(带子节点的父节点一起搬)03
6选中态 / 展开态维护 + demo 双树03

成品对照:model/01-mv-pattern/tree-drag-move/(按 成品导览 的「怎么读」顺序对照)。

3. 进阶挑战(可选)

搓完基础版想再深一层:

  • 跨进程拖拽:当前用指针编码(只在进程内有效)。真要跨进程,得把整棵子树(含数据)序列化成字节流,drop 端凭字节重建——想想需要哪些字段。
  • 限定可拖节点:某些节点(如根节点)不允许拖。提示:重写 mimeData 时按节点的 UserRole 标记过滤,或重写 startDrag
  • 撤销拖拽:把每次移动记进 QUndoStack,拖完能 Ctrl+Z 回去。提示:参考本仓库 model/11-command-pattern/undo-redo-framework/ 的 MoveCommand(命令模式),把「删源 + 插目标」包成一个 command push 进栈。
  • 下一站:QTreeView + 自定义模型(QAbstractItemModel)——那套要重写 mimeData/dropMimeData 在 model 层,比 widget 层更底层,但能支持百万级节点。

AwesomeQt v0.3.0-13-g31f4d47 · 31f4d47 · 2026-09-22 · CHECKED-BY: CI