QT控件 修改QtTreePropertyBrowser自定义属性编辑器源码,添加第一列标题勾选,按钮,右键菜单事件等功能

头阵子遇到一个需要修改QtTreePropertyBrowser 控件的需求,QT开发做这么久了,这个控件倒是第一次用,费了点时间研究,在这里做个简单的总结。
QtTreePropertyBrowser 控件 是 Qt 解决方案 (Qt Solutions) 中的一个组件,用于创建和管理属性浏览器界面。它提供了一个树形结构的属性编辑器,能实现自定义属性的编辑,支持大部分QVariant数据类型,

以前实现属性编辑器这种功能我都是使用的QTreeWidget实现的,但是有了QtTreePropertyBrowser控件这个控件,功能实现起来就简单了。

目录导读

前言

QtTreePropertyBrowser控件 不在QT的的UI设计器里面,如果要使用QtTreePropertyBrowser控件,需要注意到安装目录下的Src文件夹中查找(前提是安装qt的时候下载了Src源码文件)

  • 参考目录:

(在Pro头文件夹中添加)
include($$[QT_INSTALL_PREFIX]/../Src/qttools/src/shared/qtpropertybrowser/qtpropertybrowser.pri)

  • 支持的属性数据类型:

大部分QVariant数据类型,包括点,矩形,时间,字体,颜色等都支持在线编辑修改

默认已处理类型:
QVariant::Int
QVariant::Double
QVariant::Bool
QVariant::String
QVariant::Date
QVariant::Time
QVariant::DateTime
QVariant::KeySequence
QVariant::Char
QVariant::Locale
QVariant::Point
QVariant::PointF
QVariant::Size
QVariant::SizeF
QVariant::Rect
QVariant::RectF
QVariant::Color
QVariant::SizePolicy
QVariant::Font
QVariant::Cursor

  • 实现效果:

通过对QtTreePropertyBrowser控件 的源码简单修改,实现添加标题勾选,单击按钮,右键事件等功能。

需要注意的是 不同QT版本下的QtTreePropertyBrowser控件源码,不一定能直接编译 ,

使用时最好使用同一版本下的源码文件,

例如我使用的Qt5.13.1版本的QtTreePropertyBrowser控件源码在Qt5.15.2版本下Msvc2019编译器无法编译!
相关参考:
官方案例 qtpropertybrowser/examples
详解Qt5.12.9属性表控件:QtPropertyBrowser的使用示例/折叠/展开/小数位数/QSS样式/标题修改/选中行号等

源码修改

因为新添加的标题勾选,按钮,右键菜单这些功能改动不大,

不需要动QtTreePropertyBrowser控件的关键代码,只需要简单修改几行就可以了。

Qss样式支持 修改

QComboBox 支持qss修改项行高

修改qteditorfactory.cpp 文件 1919行

添加 editor->setView(new QListView());
如图示:
让多选项的行高能够修改

QSS:QComboBox QAbstractItemView::item{height:30px;}

修改QtTreePropertyBrowser控件的每行高度

QtTreePropertyBrowser控件默认是不支持设置行高的,即使我通过修改内部变量的QTreeWidget控件修改行高,也不支持。因为内置的QtPropertyEditorDelegate 委托阻止了设置行高。

修改 qttreepropertybrowser.cpp文件 382行

修改 sizeHint(const QStyleOptionViewItem &option,const QModelIndex &index) const函数

cpp 复制代码
QSize QtPropertyEditorDelegate::sizeHint(const QStyleOptionViewItem &option,
                                         const QModelIndex &index) const
{
    //add to 2025-04-17 为了获取qss的heigth高度兼容
    // 获取 QTreeView 的样式表
    QTreeView *treeView = qobject_cast<QTreeView*>(parent());
    if (!treeView) {
        return QItemDelegate::sizeHint(option, index) + QSize(3,4); // 默认大小
    }
    // 获取样式表中的高度
    QStyleOptionViewItem opt = option;
    // 从样式中获取大小
    QSize size = treeView->style()->sizeFromContents(
                QStyle::CT_ItemViewItem, &opt, QSize(), treeView);

    return size;
}

如图示:

QSS:QTreeWidget::item{ background: #1d1f20; height:30px; }

修改控件

添加标题允许勾选,添加设置按钮

添加标题勾选本质上是启用QTreeWidgetItem类的setCheckState方法,并且监控itemChanged(QTreeWidgetItem* ,int )事件响应,

添加按钮是在QTreeWidgetItem添加一个QPushButton按钮

修改源码:

  1. 修改qtpropertybrowser.cpp文件,修改QtPropertyPrivate 私有类
cpp 复制代码
class QtPropertyPrivate
{
public:
    QtPropertyPrivate(QtAbstractPropertyManager *manager) : m_enabled(true), m_modified(false),m_ischecked(Qt::PartiallyChecked),m_ispushbutton(false), m_manager(manager) {}
    QtProperty *q_ptr;

    QSet<QtProperty *> m_parentItems;
    QList<QtProperty *> m_subItems;

    QString m_valueToolTip;
    QString m_descriptionToolTip;
    QString m_statusTip;
    QString m_whatsThis;
    QString m_name;
    bool m_enabled;
    bool m_modified;

    //! 是否可选
    Qt::CheckState m_ischecked;
    //! 是否作为一个按钮
    bool m_ispushbutton;

    QtAbstractPropertyManager * const m_manager;
};
  1. 修改 qtpropertybrowser.h文件,修改QtProperty类,添加修改属性方法
cpp 复制代码
//追加属性
//! 因为使用的是 QTreeWidgetItem 的勾选事件,修改此属性不会触发信号
//!Qt::CheckState isChecked() const;
//!void setChecked(Qt::CheckState checked);
//! 设置作为一个button按钮 -后面的按钮使用
//!bool isPushbutton() const;
//!void SetPushbutton();

Qt::CheckState QtProperty::isChecked() const
{
    return  d_ptr->m_ischecked;
}
void QtProperty::setChecked(Qt::CheckState checked)
{
     d_ptr->m_ischecked=checked;
     //propertyChanged();
}

bool QtProperty::isPushbutton() const
{
    return  d_ptr->m_ispushbutton;
}
void QtProperty::SetPushbutton()
{
     d_ptr->m_ispushbutton=true;
     //propertyChanged();
}
  1. 修改qttreepropertybrowser.cpp文件 563行 修改QtTreePropertyBrowserPrivate类中的propertyInserted方法用于启用勾选,并根据状态判断是否插入按钮
cpp 复制代码
void QtTreePropertyBrowserPrivate::propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex)
{
    //! 因为绑定了item修改事件 所以刚开始时禁止信号传递
    m_treeWidget->blockSignals(true);
    QTreeWidgetItem *afterItem = m_indexToItem.value(afterIndex);
    QTreeWidgetItem *parentItem = m_indexToItem.value(index->parent());

    QTreeWidgetItem *newItem = 0;
    if (parentItem) {
        newItem = new QTreeWidgetItem(parentItem, afterItem);
    } else {
        newItem = new QTreeWidgetItem(m_treeWidget, afterItem);
    }

    //add 是否启用勾选
    if(index->property()->isChecked()!=Qt::PartiallyChecked)
    {
        Qt::CheckState checkstate=index->property()->isChecked();
        newItem->setCheckState(0,checkstate);
    }

    m_itemToIndex[newItem] = index;
    m_indexToItem[index] = newItem;

    newItem->setFlags(newItem->flags() | Qt::ItemIsEditable);
    newItem->setExpanded(true);

    
    updateItem(newItem);

    //add pushbutton 插入一个按钮
    if(index->property()->isPushbutton())
    {
        newItem->setText(0,"");
        QPushButton* pubtton=new QPushButton();
        const QString descriptionToolTip  = index->property()->descriptionToolTip();
        const QString propertyName = index->property()->propertyName();

        pubtton->setToolTip(descriptionToolTip.isEmpty() ? propertyName : descriptionToolTip);
        pubtton->setStatusTip(index->property()->statusTip());
        pubtton->setWhatsThis(index->property()->whatsThis());
        pubtton->setText(propertyName);
        m_indexToPushButton.insert(pubtton,newItem);
        //! 绑定 点击信号槽
        QObject::connect(pubtton,&QPushButton::pressed,[=](){
            emit q_ptr->itemPressedupdate(m_itemToIndex[m_indexToPushButton[pubtton]]->property());
        });
        m_treeWidget->setItemWidget(newItem,1,pubtton);
    }

    m_treeWidget->blockSignals(false);
}
  1. 添加绑定itemChanged信号槽,用于监控勾选状态改变
    修改QtTreePropertyBrowser类和QtTreePropertyBrowserPrivate类
cpp 复制代码
//! hpp
//! 追加内容
class QtTreePropertyBrowser : public QtAbstractPropertyBrowser
{

Q_SIGNALS:
    //! 选中状态改变
    void itemCheckStateupdate(QtProperty *item);
    //! 按钮按下 -某个按钮被按下触发事件
    void itemPressedupdate(QtProperty *item);
private:
    //! 获取勾选状态改变
    Q_PRIVATE_SLOT(d_func(), void slotitemChanged(QTreeWidgetItem* item,int col))

}

//! cpp
class QtTreePropertyBrowserPrivate
{
 //! 追加修改QtTreePropertyBrowserPrivate类
 
public:
    //! 勾选改变
    void slotitemChanged(QTreeWidgetItem *item, int column);
    
private:
    //! 创建一个变量 用于保存点击的属性项
    QMap<QPushButton *, QTreeWidgetItem *> m_indexToPushButton;
}

//! init 方法绑定信号
void QtTreePropertyBrowserPrivate::init(QWidget *parent)
{

    //! 添加-勾选改变状态事件
    QObject::connect(m_treeWidget, SIGNAL(itemChanged(QTreeWidgetItem* ,int )), q_ptr, SLOT(slotitemChanged(QTreeWidgetItem* ,int )));
}

//状态改变发送信号
void QtTreePropertyBrowserPrivate::slotitemChanged(QTreeWidgetItem *item, int column)
{
    if(column==0)
    {
        QtBrowserItem *browserItem = m_itemToIndex[item];
        if(browserItem && !browserItem->property()->hasValue())
        {
            if(browserItem->property()->isChecked() != Qt::PartiallyChecked)
            {
                browserItem->property()->setChecked(item->checkState(column));
                emit q_ptr->itemCheckStateupdate(browserItem->property());
            }
        }
    }
}
设置修改自定义属性表 表头名称,获取QtTreeWidget控件等
cpp 复制代码
QTreeWidget* QtTreePropertyBrowser::getPropertyTreeWidget()
{
    return d_ptr->treeWidget();
}

//! 修改标题
//! ui->widget_AttriTree->setHeaderLabels(QStringList()<<"属性"<<"业务值");
void QtTreePropertyBrowser::setHeaderLabels(QStringList Headers)
{
    return d_ptr->m_treeWidget->setHeaderLabels(Headers);
}
根据鼠标坐标 获取选中属性
cpp 复制代码
QtProperty * QtTreePropertyBrowser::getPropertybyPointf(QPoint pos)
{
   QTreeWidgetItem * item= d_ptr->m_treeWidget->itemAt(QPoint(pos.x(),pos.y()-d_ptr->m_treeWidget->header()->height()));
   if(item)
   {
        if(d_ptr->m_itemToIndex[item])
            return d_ptr->m_itemToIndex[item]->property();
        else
        {
            if(d_ptr->m_itemToIndex[item->parent()])
                 return d_ptr->m_itemToIndex[item->parent()]->property();
        }
   }
    return nullptr;
}

如图示:

调用示例

  • 绑定编辑工厂和属性节点管理
cpp 复制代码
   //! 属性节点管理
   //! QtVariantPropertyManager *m_pVarMgrEdit;
   //! 修改数据类型 工厂
   //! QtVariantEditorFactory *m_pVarFactory;
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);


    m_pVarMgrEdit = new QtVariantPropertyManager(ui->widget_AttriTree);//关联factory,属性可以修改
    m_pVarFactory = new QtVariantEditorFactory(ui->widget_AttriTree);
    //connect(m_pVarMgrEdit,&QtVariantPropertyManager::valueChanged,this, &ProPertyWindow::onValueChanged);//绑定信号槽,当值改变的时候会发送信号

    //! LoadXml();
    //! qDebug()<<"XML 文件解析完毕! ! ! ";
    //将一个工厂与manger关联起来,即可修改内容。
    ui->widget_AttriTree->setFactoryForManager(m_pVarMgrEdit,m_pVarFactory);
//    ui->widget_AttriTree->setAlternatingRowColors(false);
    //! 修改标题
    ui->widget_AttriTree->setHeaderLabels(QStringList()<<"属性"<<"业务值");

    //! 默认事件
    connect(m_pVarMgrEdit,&QtVariantPropertyManager::valueChanged,this,[&](QtProperty *property, const QVariant &value){
        qDebug()<<"propertyName: "<< property->propertyName()<<" value: "<<value;
    });
    //! 勾选状态改变
    connect(ui->widget_AttriTree,&QtTreePropertyBrowser::itemCheckStateupdate,this,[&](QtProperty *item){

    });
    //! 点击事件
    connect(ui->widget_AttriTree,&QtTreePropertyBrowser::itemPressedupdate,this,[&](QtProperty *item){

    });
}
  • 绑定数据示例:
cpp 复制代码
//QDomElement xml文件解析
void MainWindow::ParseItem(QDomElement ItemNode,QtVariantProperty *parent)
{
    if(!ItemNode.hasAttribute("name"))
        return;
    QString propertyName=ItemNode.attribute("name").trimmed();
    QString type=ItemNode.attribute("type","null").toLower().trimmed();
    QString check=ItemNode.attribute("check","null").toLower().trimmed();

    QtVariantProperty *item=nullptr;
    if(type=="float"||type=="double")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::Double,propertyName);
        item->setValue(ItemNode.text().toDouble());
    }
    else if(type=="comboxlist")
    {
        QStringList enumNames;
        int CurrentIndex=0;
        QDomNodeList childnode= ItemNode.childNodes();
        for(int j=0;j<childnode.count();j++)
        {
            if (childnode.item(j).isElement()){
                QDomElement element = childnode.item(j).toElement();
                if(element.tagName().toLower()=="data")
                {
                    enumNames<<element.text();
                    if(element.attribute("isSelect","false").toLower()=="true")
                        CurrentIndex=enumNames.count()-1;
                }
            }
        }
        item = m_pVarMgrEdit->addProperty(QtVariantPropertyManager::enumTypeId(), propertyName);
        item ->setAttribute(QLatin1String("enumNames"), enumNames);
        item ->setValue(CurrentIndex);
    }
    else if(type=="int")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::Int,propertyName);
        item->setValue(ItemNode.text().toInt());
    }
    else if(type=="datetime")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::DateTime,propertyName);
        item->setValue(QDateTime::fromString(ItemNode.text(),"yyyy/MM/dd hh:mm:ss"));
    }
    else if(type=="string")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::String,propertyName);
        item->setValue(ItemNode.text());
    }
    else if(type=="pointf")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::PointF,propertyName);
        QStringList poinfs=ItemNode.text().split(',');
        item->setValue(QPointF(poinfs[0].toDouble(),poinfs[1].toDouble()));
    }
    else if(type=="rectf")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::RectF,propertyName);
        QStringList poinfs=ItemNode.text().split(',');
        item->setValue(QRectF(poinfs[0].toDouble(),
                       poinfs[1].toDouble(),
                       poinfs[2].toDouble(),
                       poinfs[3].toDouble()));
    }
    else if(type=="bool")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::Bool,propertyName);
        item->setValue(ItemNode.text()=="false"?false:true);
    }
    else if(type=="color")
    {
        item = m_pVarMgrEdit->addProperty(QVariant::Color,propertyName);
        item->setValue(QColor(ItemNode.text()));
    }
    else if(type=="pushbutton")
    {
        //! 添加按钮事件
        item = m_pVarMgrEdit->addProperty(QtVariantPropertyManager::groupTypeId(),propertyName);
        item->SetPushbutton();
    }
    else
        item = m_pVarMgrEdit->addProperty(QtVariantPropertyManager::groupTypeId(),propertyName);


    if(_ISNULL_(item))
        return;

    //! 添加首项勾选
    if(check!="null"){
//        qDebug()<<"propertyName: "<<propertyName<<" check: "<<check;
        item->setChecked(check.toLower().trimmed()=="true"?Qt::Checked:Qt::Unchecked);
    }

    if(!_ISNULL_(parent))
        parent->addSubProperty(item);
    else
        ui->widget_AttriTree->addProperty(item);

}
相关推荐
hssfscv6 小时前
QT的学习笔记4——QMainWindow、资源文件以及主要控件的介绍
笔记·qt·学习
luoyayun3618 小时前
Qt + FFmpeg 音频工具:重采样实现
qt·ffmpeg·重采样
小短腿的代码世界1 天前
Qt Bluetooth源码深度解析:从HCI协议到跨平台API的完整架构
开发语言·qt·架构
小短腿的代码世界1 天前
Qt WebEngine多进程架构与IPC通信:从Chromium多进程到Qt信号槽融合
qt·架构·系统架构
尘中远1 天前
【Qwt 7.0 系列】快速入门与核心新特性概览 —— 更现代的 Qt 数据可视化库
qt·数据可视化·绘图·qwt·科学绘图
初阳7851 天前
【Qt】系统相关(3)——多线程
qt
初阳7851 天前
【Qt】系统相关(2)——文件
开发语言·qt
尘中远2 天前
【Qwt 7.0 系列】坐标轴与刻度系统 —— 刻度引擎、网格、图例与刻度朝内
qt·数据可视化·qcustomplot·qwt·工业软件·科学绘图
sycmancia2 天前
Qt——多线程间的互斥
开发语言·qt
尘中远2 天前
【Qwt 7.0 系列】常用图表类型实战 —— 柱状图、散点图、箱线图与直方图
qt·qwt·工业软件·科学绘图