Skip to content



QTreeWidget-View

1 QTreeWidget 和 QTreeView 区别,应该使用哪一个

Qt 图表:QTreeWidget 和 QTreeView。

QTreeWidget 继承自 QTreeView。

Qt 表格显示使用的是 view/mode 模式,界面和数据分开,两者使用代理链接。QTreeView 就是界面,如果需要修改数据则应该通过代理。

比如 Qt 封装好的 QFileSystemModel在view中显示,就是典型的 mode/view 结构。

如果你需要给 QTreeWidget 增加新建后删除后各种 ui 变化,选中后各种 ui 变化等等特殊效果/事件,建议不要用 QTreeWidget 和 Item,使用 mode/view。

QTreeWidget 最好仅用在表格变化不大的地方。比如固定的列表信息、固定尺寸报表等。

QTreeView 提供了一个接口,setModel 用来设置 mode(也就是数据)。

QTreeWidget 作用就是默认包含了一个 mode,并增加了如果操作这个默认 mode 的接口。

QTableWidget 类提供具有默认模型的基于项目的表视图。

这样当使用 QTreeWidget 时候会简单很多,当然他也引入了一写新的问题,比如默认带了表视图,当我图表视图 ui 很复杂时候不太方便实现,而且数据没有分开逻辑不清晰。

自己的项目中应该使用那个看情况,如果你的图表 ui 不复杂,跟 QTableWidget 默认的图表视图差异不大,而且不需要跟这个 Mode 关联(比如 QSql、QFile),应该使用 QTableWidget,反之则用 QTreeView。

2 常用样式表

QTreeWidget {
    border-radius:5px;
    font-size:20px;
    background: rgb(79, 79, 83);
    outline:0px;
}
QTreeWidget::item {
    color:rgb(233, 233, 233);
    background: rgb(79, 79, 83);
    min-height: 30px;
}
QTreeWidget::item:alternate {
    background: rgb(79, 79, 83);
}
QHeaderView {
    color: white;
}
QHeaderView::section {
    background-color: rgb(105, 106, 111);
    border:none;
    font-size:20px;
}
QTreeWidget {
    border-radius:5px;
    font-size:14px;
    background: rgb(79, 79, 83);
    outline:0px;
}
QTreeWidget::item {
    color:rgb(233, 233, 233);
    background: rgb(87, 87, 91);
    padding:0px 14px;
    min-height: 40px;
}
QTreeWidget::item:alternate {
    background: rgb(79, 79, 83);
}
QTreeWidget::item:selected, QTreeWidget::item:hover  {
    background: rgb(104, 104, 108);
}
QHeaderView {
    color: white;
    text-align:center;
}
QHeaderView::section {
    background-color: rgb(105, 106, 111);
    border:none;
    font-size:14px;
    padding:0px 14px;
}
QTreeView{
border:1px solid #0F1F2F;
selection-background-color:#265687;
selection-color:#4894C6;
alternate-background-color:#265687;
gridline-color:#0F1F2F;
}
QTreeView::branch:closed:has-children{
margin:4px;
border-image:url(:/qss/blackblue/branch_open.png);
}

QTreeView::branch:open:has-children{
margin:4px;
border-image:url(:/qss/blackblue/branch_close.png);
}

QTreeView,QTreeView::branch{
background:#1B3149;
}
QTreeView::item:selected{
color:#4894C6;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #243D5B,stop:1 #243D5B);
}
QTreeView::item:hover,QHeaderView{
color:#4894C6;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #265687,stop:1 #265687);
}
QTreeView::item{
padding:1px;
margin:0px;
}

3 常用属性

QTreeview 有的接口 QTreeWidget 基本都可以使用,下边这里几个是 QTreeWidget 可以使用 QTreeview 常用接口

ui->treeWidget->setAutoExpandDelay(-1);//节点展开鼠标悬停时间
ui->treeWidget->setIndentation(10);//缩进字节(添加自定义图标用的)
ui->treeWidget->setRootIsDecorated(1);//第一列是否缩进(添加自定义图标用的)
ui->treeWidget->setUniformRowHeights(1);//强制所有项等高
ui->treeWidget->setItemsExpandable(1);//是否支持展开折叠
ui->treeWidget->setSortingEnabled(1);////是否支持自动排序(如果需要排序的表格一般都常常改变,建议别用QTreeWidget了)
ui->treeWidget->setAnimated(1);//是否支持动画,这个需要单独在定义显示动画
ui->treeWidget->setAllColumnsShowFocus(1);//选中是焦点在本格还是本行(节点)
ui->treeWidget->setWordWrap(1);//自动换行,如果文字太多想用...省略的话,用矩形框把qstring封一下在画
ui->treeWidget->setHeaderHidden(0);//是否显示标题,下边有一个Visible,区别在于Visible是隐藏Hidden是在内存销毁
ui->treeWidget->setExpandsOnDoubleClick(0);//双击是否可以展开子节点。其实如果有节点的话还是建议用mode/view


QTreeWidget 专有的
1. Ui 上 Widget 显示几列

ui->treeWidget->setColumnCount(2);


QTreeWidget 的 head 专有的

  1. Widget 表头是否可见
    ui->treeWidget->header()->setVisible(1);
    
  2. Widget 表头顺序是否可以拖动改变
    ui->treeWidget->header()->setCascadingSectionResizes(1);
    
  3. Widget 表头文字属性(居中?靠左?靠右?)
    ui->treeWidget->header()->setDefaultAlignment(Qt::Alignment alignment);
    
  1. Widget 表头宽度
     ui->treeWidget->header()->setDefaultSectionSize(100);
    
  2. Widget 被选部分是否高亮显示
     ui->treeWidget->header()->setHighlightSections(1);
    
  3. Widget 各个区域的最小值
     ui->treeWidget->header()->setMinimumSectionSize(50);
    
  4. Widget 排序按钮是否显示
      ui->treeWidget->header()->setSortIndicatorShown(1));
    
  5. Widget 最后一个区域是否占满表格余下的所有部分
      ui->treeWidget->header()->setStretchLastSection(1);
    
    for (qint32 i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) {
        QTreeWidgetItem *item = ui->treeWidget->topLevelItem(i);
        if (item) {
            item->setFlags(item->flags() | Qt::ItemIsEditable);
        }
    }

4 设置表格状态:编辑、选中等

    for (qint32 i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) {
        QTreeWidgetItem *item = ui->treeWidget->topLevelItem(i);
        if (item) {
            item->setFlags(item->flags() | Qt::ItemIsEditable);
        }
    }
Qt.NoItemFlags          0   没有设置任何属性
Qt.ItemIsSelectable     1   可以选择
Qt.ItemIsEditable       2   可以编辑
Qt.ItemIsDragEnabled    4   可以拖动它
Qt.ItemIsDropEnabled    8   它可以用作放置目标
Qt.ItemIsUserCheckable  16  用户可以选中或取消选中它
Qt.ItemIsEnabled        32  用户可以与项目交互
Qt.ItemIsTristate       64  该项可通过三个独立的状态进行检查

可以看到上面没有不可以编辑这个选项,我现在用法是。如果需要把某几个设置为不可以编辑的话,直接自定义一个 QStyledItemDelegate,让后把 DisableEditor 返回空算了(比较傻哈),比如下边是设置第一列可以编辑,后边几列不可以编辑。其实如果你要是都不可以编辑的话,初始化默认就是不可编辑的,什么都不用加。

class DisableEditor : public QStyledItemDelegate {
  public:
    explicit DisableEditor(QWidget *parent = nullptr);

    virtual QWidget *createEditor(
        QWidget *parent,
        const QStyleOptionViewItem &option,
        const QModelIndex &index) const override ;
};
DisableEditor::DisableEditor(QWidget *parent)
    : QStyledItemDelegate(parent) {
}
QWidget *DisableEditor::createEditor(
    QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
    Q_UNUSED(parent);
    Q_UNUSED(option);
    Q_UNUSED(index);
    return nullptr;
}
    for (qint32 i = 1; i < ui->treeWidget->columnCount(); ++i) {
        ui->treeWidget->setItemDelegateForColumn(
            i, new DisableEditor(ui->treeWidget));
    }

5 检索、右键菜单

  • 高自由度的 Tree 视图:QTreeView + QAbstractItemModel + QSortFilterProxyModel + QStyledItemDelegate
  • QTreeWidget = QTreeView + QTreeWidgetItem(无法增加自定义接口)
  • 图省事且明确知道需求不会增加,可以用 QTreeWidget 方便些。

QTreeWidget是真方便,就怕你照着需求做完,项目经理让你加代理。

5.1 增加表头和右键菜单

class KissQTreeWidget : public QTreeWidget {
    Q_OBJECT
  public:
    explicit KissQTreeWidget(QWidget *parent = nullptr);
    virtual ~KissQTreeWidget();
  protected:
    void Initial();
    virtual void contextMenuEvent(QContextMenuEvent *e);
    QMenu *context_menu_;
    QItemSelection selection_;
};
KissQTreeWidget::KissQTreeWidget(QWidget *parent): QTreeWidget(parent) {
    this->Initial();
}

KissQTreeWidget::~KissQTreeWidget() {
}

void KissQTreeWidget::Initial() {
    QStringList header_list = {"Tag ID", "VR", "VM", "Length", "Description", "value"};
    QList<int> headerwidth_list = {200, 70, 100, 50, 300, 300};
    this->setHeaderLabels(header_list);
    qint32 i = 0;
    foreach (auto var, headerwidth_list) {
        this->setColumnWidth(i, var);
        i++;
    }
    this->setGeometry(0, 0, 1200, 800);
    this->header()->setDefaultAlignment(Qt::AlignCenter);
    // 右键菜单
    this->context_menu_ = new QMenu(this);
    context_menu_->addAction(tr("Open the folder where DCM is located"), this, [ = ]() {
    });
    context_menu_->addAction(tr("Copy current selection"), this, [ = ]() {
    });
    context_menu_->addSeparator();
    context_menu_->addAction(tr("Copy all values"), this, [ = ]() {
    });
}

void KissQTreeWidget::contextMenuEvent(QContextMenuEvent *e) {
    if (indexAt(e->pos()).isValid()) {
        context_menu_->popup(e->globalPos());
    }
}

5.2 增加检索功能

void DicomTagsWidget::SlotFilterChanged() {
    QTreeWidgetItemIterator it(tree_wid_);
    QString str = ui.filter->text();
    if(str.isEmpty()) {
        while (*it) {
            (*it)->setHidden(false);
            ++it;
        }
        return;
    }
    while (*it) {
        (*it)->setHidden(true);
        ++it;
    }
    QList<QTreeWidgetItem *> items;
    for(qint32 i = 0; i < tree_wid_->columnCount(); i++) {
        items << tree_wid_->findItems(str, Qt::MatchContains | Qt::MatchRecursive, i);
    }
    foreach (auto var, items) {
        var->setHidden(false);
        while (var->parent()) {
            var->parent()->setHidden(false);
            var = var->parent();
        }
    }
}

6 代理退出立即修改数据

6.1 需求

列表上有一些 QComboBox 和 QDoubleSpinBox,留下来的代码是用的代理(createEditor + setEditorData + setModelData)

最新的需求需要实时修改。如果是用的代理(paint + editorEvent)或者 QTableWidget,这个需求很好实现。

代理(createEditor + setEditorData + setModelData)则是在列表的 editorEvent 后便判断是否完成修改来发送 commitData 和 sizeHintChanged 来实现退出编辑后修改 Model 的数据。
SetEditorData 和 createEditor 都是 const 修饰的,信号发不出去,index 也是 const 传入的,无法调用 setModelData。

不想大该原来的代码,干脆在代理初始化的时候把 Model 传进去吧。

xxx

6.2 代码

QWidget *ComboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /*option*/, const QModelIndex &index) const
{
    QComboBox *editor = new QComboBox(parent);
    editor->addItems(item_list_);
    editor->installEventFilter(const_cast<ComboxDelegate *>(this));

    connect(editor, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated),
            this, [&, index](int data) {
                model_->setData(index, data, Qt::EditRole);
            });

    return editor;
}

6.3 反思

感觉代理就是为了抽离 data 和 view,像上边那样操作就失去了代理的意义。直接用 QTableWidget 设置每个单元个的 Widget 就好了。