头阵子遇到一个需要修改QtTreePropertyBrowser控件的需求,QT开发做这么久了,这个控件倒是第一次用,费了点时间研究,在这里做个简单的总结。
QtTreePropertyBrowser控件 是 Qt 解决方案 (Qt Solutions) 中的一个组件,用于创建和管理属性浏览器界面。它提供了一个树形结构的属性编辑器,能实现自定义属性的编辑,支持大部分QVariant数据类型,
以前实现属性编辑器这种功能我都是使用的QTreeWidget实现的,但是有了QtTreePropertyBrowser控件这个控件,功能实现起来就简单了。
目录导读
- 前言
- 源码修改
- Qss样式支持 修改
- QComboBox 支持qss修改项行高
- 修改QtTreePropertyBrowser控件的每行高度
- 修改控件
- 添加标题允许勾选,添加设置按钮
- 设置修改自定义属性表 表头名称,获取QtTreeWidget控件等
- 根据鼠标坐标 获取选中属性
- 调用示例
前言
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样式支持 修改
修改qteditorfactory.cpp 文件 1919行
添加 editor->setView(new QListView());
如图示:
让多选项的行高能够修改
QSS:QComboBox QAbstractItemView::item{height:30px;}
QtTreePropertyBrowser控件默认是不支持设置行高的,即使我通过修改内部变量的QTreeWidget控件修改行高,也不支持。因为内置的QtPropertyEditorDelegate 委托阻止了设置行高。
修改 qttreepropertybrowser.cpp文件 382行
修改 sizeHint(const QStyleOptionViewItem &option,const QModelIndex &index) const函数
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按钮
修改源码:
- 修改qtpropertybrowser.cpp文件,修改QtPropertyPrivate 私有类
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;
};
- 修改 qtpropertybrowser.h文件,修改QtProperty类,添加修改属性方法
//追加属性
//! 因为使用的是 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();
}
- 修改qttreepropertybrowser.cpp文件 563行 修改QtTreePropertyBrowserPrivate类中的propertyInserted方法用于启用勾选,并根据状态判断是否插入按钮
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);
}
- 添加绑定itemChanged信号槽,用于监控勾选状态改变
修改QtTreePropertyBrowser类和QtTreePropertyBrowserPrivate类
//! 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());
}
}
}
}
QTreeWidget* QtTreePropertyBrowser::getPropertyTreeWidget()
{
return d_ptr->treeWidget();
}
//! 修改标题
//! ui->widget_AttriTree->setHeaderLabels(QStringList()<<"属性"<<"业务值");
void QtTreePropertyBrowser::setHeaderLabels(QStringList Headers)
{
return d_ptr->m_treeWidget->setHeaderLabels(Headers);
}
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;
}
如图示:
调用示例
- 绑定编辑工厂和属性节点管理
//! 属性节点管理
//! 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){
});
}
- 绑定数据示例:
//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);
}