You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

拖动重排QTreeWidgetItem后,内嵌QWidget消失如何解决?

哈哈,这个坑我之前踩过!QTreeWidget的setItemWidget看起来好用,但内置的拖动重排逻辑根本不会管你挂在项上的自定义控件——因为这些控件是直接绑定在视图层面的,拖动时模型只处理数据,视图刷新就把控件的关联丢了,自然就变成空行啦。给你两个解决方案,按需选:

方案一:手动保存控件关联(快速修改现有代码)

这个方案不用大改你现有的逻辑,只要加个映射表保存控件和项的关联,拖动完成后重新绑定就行:

  1. 先在你的ToolsSelectorWidget类里加个成员变量,用来存每个项对应的所有列控件:
private:
    QMap<QTreeWidgetItem*, QList<QWidget*>> m_itemWidgetMap;
  1. 修改构造函数里创建项的代码,把控件存到映射表后再绑定:
ToolsSelectorWidget::ToolsSelectorWidget(QWidget *parent) : QTreeWidget(parent) {
    header()->hide();
    setSelectionMode(QAbstractItemView::SingleSelection);
    setDragEnabled(true);
    viewport()->setAcceptDrops(true);
    setDropIndicatorShown(true);
    setDragDropMode(QAbstractItemView::InternalMove);
    
    for(int i=0; i<4; ++i) {
        QTreeWidgetItem *part = new QTreeWidgetItem(this);
        // 简化flags的写法,效果和你原来的一样
        part->setFlags(part->flags() & ~Qt::ItemIsDropEnabled);
        
        // 先创建所有控件并存到列表
        QList<QWidget*> widgets;
        widgets << new QLabel("Part" + QString::number(i) + " Preview", this);
        widgets << new QLineEdit("Part" + QString::number(i) + " Name", this);
        widgets << new QCheckBox("Part" + QString::number(i) + " Visible", this);
        widgets << new QCheckBox("Part" + QString::number(i) + " Locked", this);
        
        // 绑定控件到项,同时存入映射表
        for(int col = 0; col < widgets.size(); ++col) {
            setItemWidget(part, col, widgets[col]);
        }
        m_itemWidgetMap[part] = widgets;
    }
}
  1. 修改dropEvent,在默认拖动完成后重新绑定控件:
void ToolsSelectorWidget::dropEvent(QDropEvent *event) {
    QModelIndex droppedIndex = indexAt(event->pos());
    if( !droppedIndex.isValid() || droppedIndex.parent().isValid()) {
        return;
    }
    
    // 先执行默认的拖动重排逻辑
    QTreeWidget::dropEvent(event);
    
    // 遍历所有顶层项,重新把控件绑定到对应的项上
    for(int i = 0; i < topLevelItemCount(); ++i) {
        QTreeWidgetItem* item = topLevelItem(i);
        if(m_itemWidgetMap.contains(item)) {
            QList<QWidget*> widgets = m_itemWidgetMap[item];
            for(int col = 0; col < widgets.size(); ++col) {
                if(col < columnCount()) {
                    setItemWidget(item, col, widgets[col]);
                }
            }
        }
    }
    
    // 强制刷新视图,确保控件正常显示
    viewport()->update();
}

这样拖动完成后,控件就会重新绑定到移动后的项上,不会消失了。

方案二:使用自定义ItemDelegate(符合Qt MVC规范,长期维护更稳定)

setItemWidget本质是绕开了Qt的MVC模式,直接在视图上挂控件,时间长了容易出现数据和视图不一致的问题。用自定义Delegate可以让控件和项的数据绑定,拖动时数据跟着项走,控件自然也会跟着渲染:

  1. 先创建一个自定义Delegate类:
#include <QStyledItemDelegate>
#include <QLabel>
#include <QLineEdit>
#include <QCheckBox>

class ToolsItemDelegate : public QStyledItemDelegate {
    Q_OBJECT
public:
    explicit ToolsItemDelegate(QWidget *parent = nullptr) : QStyledItemDelegate(parent) {}
    
    // 创建对应列的编辑器控件
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
        switch(index.column()) {
            case 0: { // Preview标签(只读)
                QLabel* label = new QLabel(parent);
                label->setFrameStyle(QFrame::Box);
                return label;
            }
            case 1: { // 名称输入框
                QLineEdit* edit = new QLineEdit(parent);
                return edit;
            }
            case 2:
            case 3: { // 复选框
                QCheckBox* check = new QCheckBox(parent);
                return check;
            }
            default:
                return QStyledItemDelegate::createEditor(parent, option, index);
        }
    }
    
    // 把项的数据同步到控件上
    void setEditorData(QWidget *editor, const QModelIndex &index) const override {
        QVariant data = index.data(Qt::EditRole);
        switch(index.column()) {
            case 0: {
                if(auto label = qobject_cast<QLabel*>(editor)) {
                    label->setText(data.toString());
                }
                break;
            }
            case 1: {
                if(auto edit = qobject_cast<QLineEdit*>(editor)) {
                    edit->setText(data.toString());
                }
                break;
            }
            case 2:
            case 3: {
                if(auto check = qobject_cast<QCheckBox*>(editor)) {
                    check->setChecked(data.toBool());
                    check->setText(index.data(Qt::DisplayRole).toString());
                }
                break;
            }
            default:
                QStyledItemDelegate::setEditorData(editor, index);
        }
    }
    
    // 把控件的修改同步回项的数据
    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override {
        switch(index.column()) {
            case 0: // 只读标签,不用保存
                break;
            case 1: {
                if(auto edit = qobject_cast<QLineEdit*>(editor)) {
                    model->setData(index, edit->text(), Qt::EditRole);
                }
                break;
            }
            case 2:
            case 3: {
                if(auto check = qobject_cast<QCheckBox*>(editor)) {
                    model->setData(index, check->isChecked(), Qt::EditRole);
                }
                break;
            }
            default:
                QStyledItemDelegate::setModelData(editor, model, index);
        }
    }
    
    // 调整控件的位置和大小
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
        editor->setGeometry(option.rect);
    }
    
    // 非编辑状态下也绘制控件(默认Delegate只在编辑时显示)
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
        switch(index.column()) {
            case 0: {
                QStyleOptionViewItem opt = option;
                initStyleOption(&opt, index);
                QLabel label;
                label.setText(index.data(Qt::EditRole).toString());
                label.setFrameStyle(QFrame::Box);
                label.resize(opt.rect.size());
                painter->save();
                painter->translate(opt.rect.topLeft());
                label.render(painter);
                painter->restore();
                return;
            }
            case 2:
            case 3: {
                QStyleOptionViewItem opt = option;
                initStyleOption(&opt, index);
                QCheckBox check;
                check.setChecked(index.data(Qt::EditRole).toBool());
                check.setText(index.data(Qt::DisplayRole).toString());
                check.resize(opt.rect.size());
                painter->save();
                painter->translate(opt.rect.topLeft());
                check.render(painter);
                painter->restore();
                return;
            }
            default:
                QStyledItemDelegate::paint(painter, option, index);
        }
    }
};
  1. 修改ToolsSelectorWidget的构造函数,不用setItemWidget,改用Delegate并设置项的初始数据:
ToolsSelectorWidget::ToolsSelectorWidget(QWidget *parent) : QTreeWidget(parent) {
    header()->hide();
    setSelectionMode(QAbstractItemView::SingleSelection);
    setDragEnabled(true);
    viewport()->setAcceptDrops(true);
    setDropIndicatorShown(true);
    setDragDropMode(QAbstractItemView::InternalMove);
    
    // 设置自定义Delegate
    setItemDelegate(new ToolsItemDelegate(this));
    // 设置列数
    setColumnCount(4);
    
    for(int i=0; i<4; ++i) {
        QTreeWidgetItem *part = new QTreeWidgetItem(this);
        part->setFlags(part->flags() & ~Qt::ItemIsDropEnabled);
        
        // 设置每列的数据,Delegate会自动根据数据渲染控件
        part->setData(0, Qt::EditRole, "Part" + QString::number(i) + " Preview");
        part->setData(1, Qt::EditRole, "Part" + QString::number(i) + " Name");
        // 复选框需要同时设置显示文本和选中状态
        part->setData(2, Qt::EditRole, true);
        part->setData(2, Qt::DisplayRole, "Part" + QString::number(i) + " Visible");
        part->setData(3, Qt::EditRole, false);
        part->setData(3, Qt::DisplayRole, "Part" + QString::number(i) + " Locked");
    }
}
  1. 这时候你的dropEvent可以完全不用修改!因为Delegate是和模型数据绑定的,拖动项的时候数据跟着移动,Delegate会自动在新位置渲染对应的控件,完全不会出现控件消失的问题,而且后续要修改控件状态、保存配置也更方便。

内容的提问来源于stack exchange,提问作者tsragravorogh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:42:28