qt 实现C# propertygrid
qt 中的 QtTreePropertyBrowser
很多使用C# 的小伙伴都知道propertygrid控件,这个控件可以直接将我们自定义的属性和页面关联起来,在一些需要在UI修改属性值时的需求里使用起来非常方便。但是在Qt常用的控件中,却找不到类似的控件。实际上在qt源码中存在这个控件,但并未应用到常用控件中。
导入项目
路径:D:\qt6.2.4\6.2.4\Src\qttools\src\shared\qtpropertybrowser
该控件qt的安装路径下,使用时只需要将整个文件夹复制在你的项目路径下,并在该文件夹中添加 .pri文件
然后.pri文件中需要将这些 .h,.cpp文件加载
HEADERS += \
$$PWD/qtbuttonpropertybrowser.h \
$$PWD/qteditorfactory.h \
$$PWD/qtgroupboxpropertybrowser.h \
$$PWD/qtpropertybrowser.h \
$$PWD/qtpropertybrowserutils_p.h \
$$PWD/qtpropertymanager.h \
$$PWD/qttreepropertybrowser.h \
$$PWD/qtvariantproperty.h
SOURCES += \
$$PWD/qtbuttonpropertybrowser.cpp\
$$PWD/qteditorfactory.cpp\
$$PWD/qtgroupboxpropertybrowser.cpp\
$$PWD/qtpropertybrowser.cpp\
$$PWD/qtpropertybrowserutils.cpp\
$$PWD/qtpropertymanager.cpp\
$$PWD/qttreepropertybrowser.cpp\
$$PWD/qtvariantproperty.cpp
之后只需要在项目的.pro文件中添加 这个.pri,等待一会,就会将这些源码加载在你的项目中
// .pro文件增加这一句
include(qtpropertybrowser/qtpropertybrowser.pri)
使用
将该控件加入在你的项目之后,只需要在你的UI中新建一个widget,然后将这个widget提升为 QtTreePropertyBrowser 即可。
控件提升
控件创建好之后,我们则需要开始在这个表格中添加我们需要可视化的属性,但是在添加属性之前,我们还需要知道两种类,一种是管理者类,一个是工厂类。
管理者类
控件的管理者类,只有通过这个类才可以在空间中添加我们需要的属性
// 注意 Variant ,这个类对应的则是我们常用的类型,int,bool,doublt,flot等等
class QtVariantPropertyManager : public QtAbstractPropertyManager
// 对应的是枚举类,也就是C# 中对应的 combox 的使用
class QtEnumPropertyManager : public QtAbstractPropertyManager
// 可以看到以上两个类都继承QtAbstractPropertyManager,还有其他的manager类,则各位自己查阅源码
工厂类
管理者类,可以给控件添加属性,而工厂类需要和管理者类绑定,才可以让可视化属性可以修改,如果不绑定工厂类,则无法修改。
因此如果我们有些属性不需要修改,有些属性需要修改,则可以创建两个管理类,一个绑定工厂类,一个不绑定工厂类。
// 针对常用类型管理者类的工厂
class QtVariantEditorFactory : public QtAbstractEditorFactory<QtVariantPropertyManager>
// 针对枚举类型管理者对应的工厂
class QtEnumEditorFactory : public QtAbstractEditorFactory<QtEnumPropertyManager>
// 同理,这两个类都是继承 QtAbstractEditorFactory
初始化
在明确需要上述类之后,则可以在初始化中定义
private:
Ui::MainWindow *ui;
// 表格的管理对象
// 在表格中添加信息都需要通过这个普通类型管理对象
QtVariantPropertyManager* m_pVarManager;
// 枚举类型管理对象
QtEnumPropertyManager *enumManager;
// 和普通管理对象绑定的工厂,只有绑定了对象才可以在UI界面的表格修改值,否则无法修改
QtVariantEditorFactory* m_pVarFactory;
// 枚举管理对象关联的工厂
QtEnumEditorFactory* enumFactory;
// 用于将表格中的子节点和自定义参数的属性名一一对应,方便根据属性名修改参数对象中的值,后续会说到
QMap<QtProperty*,QString> paramMap = QMap<QtProperty*,QString>();
// 自定义属性对象,用于验证功能
Students* stu;
init()
// 创建表格的管理对象
m_pVarManager = new QtVariantPropertyManager(ui->widget);
// 创建和管理对象的绑定工厂
m_pVarFactory = new QtVariantEditorFactory(ui->widget);
enumManager = new QtEnumPropertyManager(ui->widget);
enumFactory = new QtEnumEditorFactory(ui->widget);
stu = new Students();
// 设置表格可以拉伸
// QtTreePropertyBrowser::Interactive //交互
// QtTreePropertyBrowser::Fixed //固定
// QtTreePropertyBrowser::ResizeToContents //自动调整内容
// QtTreePropertyBrowser::Stretch //拉伸
ui->widget->setResizeMode(QtTreePropertyBrowser::Interactive);
我的自定义结构体 Students 如下
#ifndef STUDENTS_H
#define STUDENTS_H
#include <QObject>
enum Hobby
{
footBall,
basketBall,
pingpang
};
class Students : public QObject
{
Q_OBJECT
// 后续添加到 paramMap 中的 value 字符串必须和 age,name 这些类型一样,方便后续序列化修改对象的值
Q_PROPERTY(int age READ getAge WRITE setAge)
Q_PROPERTY(QString name READ getName WRITE setName)
Q_PROPERTY(bool isLeagueMember READ getLeagueMember WRITE setLeagueMember)
Q_PROPERTY(Hobby hobby READ getHobby WRITE setHobby )
public:
explicit Students(QObject* parent = nullptr);
const QString &getName() const;
void setName(const QString &newName);
bool getLeagueMember() const;
void setLeagueMember(bool newIsLeagueMember);
Hobby getHobby() const;
void setHobby(Hobby newHobby);
void setAge(int newAge);
int getAge() const;
private:
int m_age = 10;
QString m_name = "十二号少年";
bool m_isLeagueMember = true;
Hobby m_hobby = Hobby::footBall;
};
#endif // STUDENTS_H
添加表格属性
// 新建组1
QtProperty *groupItem1 = m_pVarManager->addProperty(QtVariantPropertyManager::groupTypeId(),"基本信息");
// 新建字段 参数1:当前字段类型 参数2:当前字段显示名称
QtVariantProperty *nameItem = m_pVarManager->addProperty(QVariant::String,"姓名");
// map 用于存储 字段对象和自定义结构体中的属性参数名,方便后续修改UI页面的值之后修改 对象中的值
// 因此map中的 value 必须和 属性名相同
paramMap.insert(nameItem,"name");
// 字段赋值,也可以赋值 stu对象中的默认值
nameItem->setValue("十二号少年");
QtVariantProperty *ageItem = m_pVarManager->addProperty(QVariant::Int,"年龄");
paramMap.insert(ageItem,"age");
ageItem->setValue(18);
// 将字段添加进组中
groupItem1->addSubProperty(nameItem);
groupItem1->addSubProperty(ageItem);
// 创建组2
QtVariantProperty *groupItem2 = m_pVarManager->addProperty(QtVariantPropertyManager::groupTypeId(),"详细信息");
// 创建字段
QtVariantProperty *boolItem = m_pVarManager->addProperty(QVariant::Bool,"是否共青团员");
boolItem->setValue(true);
paramMap.insert(boolItem,"isLeagueMember");
// 添加枚举类型,需要从枚举类型的管理者中创建
QtProperty *hobbyItem = enumManager->addProperty("爱好");
// 增加选择类型
QStringList hobbyslist;
hobbyslist << "足球" << "篮球" << "乒乓球";
// 枚举类型加入管理者,字段和结果对应
enumManager->setEnumNames(hobbyItem,hobbyslist);
// 选择最开始选择位置
enumManager->setValue(hobbyItem,0);
paramMap.insert(hobbyItem,"hobby");
// 将字段添加进组中
groupItem2->addSubProperty(boolItem);
groupItem2->addSubProperty(hobbyItem);
// 将组添加进表格
ui->widget->addProperty(groupItem1);
ui->widget->addProperty(groupItem2);
// 将管理者和工厂绑定
ui->widget->setFactoryForManager(m_pVarManager,m_pVarFactory);
ui->widget->setFactoryForManager(enumManager,enumFactory);
// 关联对象,UI修改属性的关键,后续会说明
connect(m_pVarManager,&QtVariantPropertyManager::valueChanged,this,&MainWindow::variantChangeSlot);
connect(enumManager,&QtEnumPropertyManager::valueChanged,this,&MainWindow::variantChangeSlot);
创建了好之后,我们就成功了一半,效果如下:
数据同步
如上图,我们建立的表格和students类中的属性都是一一对应的,那接下来则需要实现UI中修改对应的值,在底层中的Students对象stu中的属性也需要修改成对应的值。
这里则需要用到之前说到的管理者类中的信号
// QtVariantPropertyManager 类中 signals
// QtProperty* property 反馈当前修改参数的对象,即 我们之前存放在paramMap中的key
// QVariant &val 当前修改参数的值,正是我们需要的值
virtual void setValue(QtProperty *property, const QVariant &val);
// QtEnumPropertyManager 中的信号
void valueChanged(QtProperty *property, int val);
有了这个自带的信号,我们则只需要创建槽函数来关联,并在槽函数中修改我们需要的值。
// 相应页面参数修改的槽函数,用于修改对应的参数
void MainWindow::variantChangeSlot(QtProperty *property, const QVariant &val)
{
qDebug() << " age:" << stu->getAge() << " name:" << stu->getName()
<< " 团员:" << stu->getLeagueMember() << " 爱好:"
<< stu->getHobby();
// map中获取到对应的属性值
QString paramName = paramMap[property];
// 通过序列化直接修改对应的结果
stu->setProperty(paramName.toStdString().c_str(),val);
qDebug() << " age:" << stu->getAge() << " name:" << stu->getName() << " 团员:" << stu->getLeagueMember() << " 爱好:" << stu->getHobby();
}
运行结果如下:
至此,我们需要的功能均已实现。
你知道的越多,你不知道的越多,我们下期见。