问题的提出:
近来要编写一个仿真液压、电力、机械的软件,如下为液压的:

可以看到液压图中很多液压元器件,这些元器件的id、名称等都是从json配置文件读取的,配置文件格式如下:
{
"ClassName":"CComponentLineWidget",
"Object": {
"displayname": "直线",
"drawoutline": false,
"id": "66c7673c-2826-339e-4b38-a67f840680bd",
"linetype": 1,
"pencolor": "#000000",
"penwidth": 4,
"treeicon": "直线.svg",
"viewicon": "直线.svg",
"growstep": 1,
"points": ""
}
}
其中ClassName是实现该元器件的类 ,如上面json中的CComponentLineWidget就是实现“直线”的类,"Object"节点是直线的属性。
最初的设想是:
定义一个CComponentWidget的基类,其它如上图液压业务中的各种油缸、电磁阀、压力表等都从 基类派生。然后定义一个类似如下的工厂模式,new出各种控件,如下:
CComponent* CComponent::createComponentFactory(const QString& strComponentId)
{
if (!strComponentId.compare(g_pszComposeComponentId, Qt::CaseInsensitive))
{
pComponent = new CComposeComponent(strComponentId);
}
else if (!strComponentId.compare(g_pszValveComponentId, Qt::CaseInsensitive))
{
pComponent = new CSingleValve(strComponentId);
}
else if (!strComponentId.compare(g_pszControlValveComponentId, Qt::CaseInsensitive))
{
pComponent = new CControlValve(strComponentId);
}
else if (!strComponentId.compare(g_pszHighPressureBallValveComponentId,
Qt::CaseInsensitive))
{
pComponent = new CHighPressureBallValve(strComponentId);
}
........ // 这里还有好多各种元器件的构建代码
}
其中CComponentWidget为元器件的基类,CComposeComponent、CSingleValve、CControlValve等都是表示上图的元器件实现类,是派生自CComponent的子类。
现实的情况是:单单就液压业务,各种元器件都有60多个,再加上后期的电力、机械等,各种元器件不下200种,那么这个工厂模式下的if、else将会无比巨大,且更要命的是:每种元器件的属性要弹出对话框,属性对话框中的属性就是读自上面json配置文件中的"Object"节点,以便实现让用户设置元器件属性,用户选择好后,这些属性要保存到本地,即更新上面json配置文件中的"Object"节点,后期加载时,这些用户设置好的属性要附加到元器件上来,这么下来,200多个元器件的属性设置、保存、加载也是巨大的工作量,为了解决这个问题,我们采用Qt的元对象技术,只要十几行代码就可以搞定工厂模式下的数百个if、else才搞定的工作。下面详细说明:
1)在基类CComponentWidget中定义一个构建各种子类对象的静态方法,如下:
// 加载
CComponentWidget* CComponentWidget::load(const QJsonObject& json)
{
QString className = json["ClassName"].toString(); // (1)
if (className.isEmpty())
return nullptr;
int type = QMetaType::type((className + "*").toLatin1()); // (2)
if (type == 0)
{
assert(0);
return nullptr;
}
const QMetaObject* metaObj = QMetaType::metaObjectForType(type);// (3)
if (metaObj == nullptr)
{
assert(0);
return nullptr;
}
QObject* obj = metaObj->newInstance(); // (4)
if (obj == nullptr)
{
assert(0);
return nullptr;
}
auto objHash = json["Object"].toObject().toVariantHash();
for (auto iter = objHash.constBegin(); iter != objHash.constEnd(); ++iter) // (5)
{
obj->setProperty(iter.key().toStdString().c_str(), iter.value());
}
return dynamic_cast<CComponentWidget*>(obj);
}
该段代码说明如下:
(1)句代码表示从json中读取ClassName类的名称,也就是元器件的实现类,这里是直线的实现类 CComponentLineWidget。
(2)句代码是利用Qt的元类型类取出字符串:“CComponentLineWidget*”即指向
CComponentLineWidget类指针的句柄,说白了也就是返回一个
CComponentLineWidget类的指针。
请注意:要使该句代码作用,也就是type不返回QMetaType::UnknownType即0,必须在该load函数调用之前,利用qRegisterMetaType函数对要创建的类进行注册,否则type因为没有注册该类,找不到该类,从而返回返回QMetaType::UnknownType导致后续流程都不能执行。以直线元件为例,调用如下:
// 本句必须保证在load函数之前调用
qRegisterMetaType<CComponentLineWidget*>();
(3):根据返回的指向某个类的句柄创建 Qt的元对象metaObj。
(4):再调用Qt的元对象metaObj的newInstance()方法,此时会调用元器件类的构造函数。请注意:元器件的构造函数必须标识Q_INVOKABLE,否则不会调用,如下为Qt官方对newInstance()方法说明:
Note that only constructors that are declared with the Q_INVOKABLE modifier are made available through the meta-object system.
本例CComponentLineWidget构造函数声明如下:
Q_INVOKABLE CComponentLineWidget(QGraphicsItem *parent = nullptr)
(5):读取json中该元器件的各种属性到Qt元对象属性中,请注意:要使Qt元对象属性起作用,必须用Q_PROPERTY在类的声明文件的private区将属性标识出来,且要提供相应的set、get函数,具体请参考Qt帮助手册中Q_PROPERTY使用说明。在(5)这个for循环中会自动调用你写好的set函数,从而实现将json中的属性设置到元器件上。
以上是元器件的创建及属性加载,再来看元器件的属性保存,如下:
// 保存
void CComponentWidget::save(QJsonObject& json)
{
QJsonObject obj;
auto metaObject = this->metaObject();(1)
for (int index = 0; index < metaObject->propertyCount(); ++index)(2)
{
auto metaProperty = metaObject->property(index);
QString name = metaProperty.name();
QVariant var = metaProperty.read(this);
obj[name] = QJsonValue::fromVariant(var);
}
json["Object"] = obj;
json["ClassName"] = metaObject->className();
}
这个就很简单了,(1)句代码就去取出元器件实现类的元对象,然后变量对元对象的所有属性遍历、更新相应的json属性节点。然后在元器件基类即CComponentWidget中相应双击事件,弹出属性对话框,如下:

https://blog.youkuaiyun.com/danshiming/article/details/119923840
2596

被折叠的 条评论
为什么被折叠?



