QtTreePropertyBrowser 与 PropertyGrid 对比:在科学计算中的应用





5.00/5 (6投票s)
属性浏览是一项常见的任务,已有许多独立的库可用,但让我们探索一下 Qt 库内置的功能来处理这项任务。
引言
在科学计算和数值建模中,经常会出现这种情况:模型的行为由一组参数决定,例如重力加速度、摩擦系数和时间步长。其中大多数参数在模拟过程中保持不变,但最好能够快速地动态调整它们。此外,通常还需要显示其他数值输出,例如系统的总动能、当前时间步数等。
高级语言通常带有实现属性浏览和编辑的标准 UI 组件。例如,在 C# 中,可以使用一个连接到对象的 PropertyGrid
。由于 C# 具有广泛的元数据功能,PropertyGrid
可以拾取对象的属性并相应地呈现它们。在这种情况下,添加新的数值字段非常直接。
在 C++ 中实现相同的功能并非易事。常用的图形 API OpenGL 不直接实现文本输出或用户界面组件。有许多基于 OpenGL 构建的库实现了属性浏览。一个例子是 SPlisHSPlasH,它使用一个 自定义编写的库 来存储参数值,并使用另一个库 Anttweakbar 来实现 GUI。Nvidia 的 FleX 演示使用了一个不同的实现 Imgui 来实现用户界面。
但是,如果代码已经基于 Qt 呢?是否有办法显示属性列表?毕竟,Qt Designer 中有这样的组件。
我的任何 Qt 书籍都没有提到 QPropertyBrowser
——一个在 Designer 中使用的神秘小部件。要利用它的魔力,首先必须包含 qtpropertybrowser.pri
,它通常位于 ~/Qt/5.12.6/Src/qttools/src/shared/qtpropertybrowser/ 目录中。
如果您没有此文件夹,则需要通过 MaintenanceTool
安装 Qt 源代码。如果您的项目使用 cmake
而不是 qmake
,那么您就无能为力了。有关包含适当源文件的信息,请参阅 此解决方案。
实例化小部件
一种选择是直接使用 QPropertyBrowser
,但它不会提供所有期望的功能。这个小部件确实可以显示和编辑属性,但这些属性不是您对象的属性。小部件的创建和填充如下:
QtVariantPropertyManager *variantManager = new QtVariantPropertyManager;
QtTreePropertyBrowser browser;
browser.setFactoryForManager(variantManager, new QtVariantEditorFactory);
QtVariantProperty *p = variantManager->addProperty(QVariant::Int, "Property1");
browser.addProperty(p);
包含以下头文件
#include "qteditorfactory.h"
#include "qttreepropertybrowser.h"
#include "qtpropertymanager.h"
#include "qtvariantproperty.h"
这里提供了一个关于实例化此组件的深入教程 在此。请注意,此小部件在 Designer 中不受支持,必须在代码中实例化。此时,我们只手动填充了一个可编辑字段。
显示对象的属性
C++ 语言没有对象属性,也没有对元数据的访问。Qt 通过其自有的 属性系统 扩展了 C++,但我们如何通过 GUI 浏览它们呢?让我们创建一个 ObjectPropertyBrowser
。
class ObjectPropertyBrowser : public QtTreePropertyBrowser
{
Q_OBJECT
public:
ObjectPropertyBrowser(QWidget* parent);
void setActiveObject(QObject *obj); // connect to QObject using this
private:
QtVariantPropertyManager *variantManager;
QObject *currentlyConnectedObject = nullptr;
QMap<QtProperty *, const char*> propertyMap;
private slots:
void valueChanged(QtProperty *property, const QVariant &value);
public slots:
void objectUpdated(); // call this whenever currentlyConnectedObject is updated
};
我们扩展了原始小部件,增加了 setActiveObject(QObject*)
方法,该方法将任何 QObject
的属性与我们的窗口部件关联起来。这是最困难的部分,因为我们必须遍历对象的属性并将它们添加到字典中。列表中的第一项始终是对象的 name
,我们不太关心它,所以跳过它。如果您需要,可以随意包含它。
void ObjectPropertyBrowser::setActiveObject(QObject *obj)
{
clear();
variantManager->clear();
propertyMap.clear();
if(currentlyConnectedObject) currentlyConnectedObject->disconnect(this);
currentlyConnectedObject = obj;
if(!obj) return;
for(int i=1; i< obj->metaObject()->propertyCount();i++) {
QMetaProperty mp = obj->metaObject()->property(i);
QtVariantProperty *property = variantManager->addProperty(mp.type(),mp.name());
property->setEnabled(mp.isWritable());
propertyMap[property] = mp.name();
addProperty(property);
}
connect(obj, SIGNAL(propertyChanged()), this, SLOT(objectUpdated()));
objectUpdated();
}
每当用户通过 UI 更改值时,就会调用 valueChanged()
插槽。更改应传递给 currentlyConnectedObject
,否则将对存储的数据没有影响。幸运的是,这只需一行代码即可完成,因为我们的 propertyMap
告诉我们哪个 QtVariantProperty
属性对应于哪个对象的属性。
void ObjectPropertyBrowser::valueChanged(QtProperty *property, const QVariant &value)
{
currentlyConnectedObject->setProperty(propertyMap[property], value);
objectUpdated();
}
请注意,一个属性的更改可能会导致其他属性的更改,因此在更新后,我们通过调用 objectUpdated()
来刷新整个 UI。该方法会遍历 propertyMap
并将更新后的值传输到浏览器。当对象的數據(例如,由单独的线程)外部更改时,可以手动调用它来更新 UI。我们暂时断开了信号连接,以避免循环通知。
void ObjectPropertyBrowser::objectUpdated()
{
disconnect(variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)),
this, SLOT(valueChanged(QtProperty*, QVariant)));
QMapIterator<qtproperty*, char="" const=""> i(propertyMap);
while(i.hasNext()) {
i.next();
variantManager->setValue(i.key(), currentlyConnectedObject->property(i.value()));
}
connect(variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)),
this, SLOT(valueChanged(QtProperty*, QVariant)));
}
</qtproperty*,>
理想情况下,我们的 QObject
在属性更新时会发出 propertyChanged
信号。但这需要一个专门设计的 QObject
class TestData : public QObject
{
Q_OBJECT
Q_PROPERTY(double Value1 MEMBER m_value1 NOTIFY propertyChanged)
signals:
void propertyChanged();
private:
double m_value1;
};
如果 QObject
没有发出此信号,则不会出现错误,但 UI 不会自动更新,并且程序员将负责在需要刷新 UI 时调用浏览器的 objectUpdated()
。屏幕截图和包含的源代码显示了最终结果,其中对象的属性是完全可编辑的,并且任何外部更改都会立即反映在 UI 中。
结论
编辑对象的属性是一项常见任务,在 C++ 中实现这一点并非易事,因为该语言是在图形用户界面出现之前设计的。各种基于 Qt 的解决方案最早可以追溯到 2008 年,但并未得到维护,尽管仍然运行良好。其他解决方案,例如 QtnProperty,非常复杂且学习曲线陡峭。由于 Qt Designer 已包含属性浏览器,因此在 Qt 框架内,它似乎是最直接的选择,并保证了长期的维护。如果需要,可以为建议的 ObjectPropertyBrowser
创建一个 Designer 插件,以便在 Qt Designer 中直观地处理此小部件。希望您发现这些信息很有帮助。感谢您的任何评论。
历史
- 2020 年 1 月 13 日:初始版本