引言
此处说的模型和之前所说的QStandardItemModel并不是一个概念,是一个更广泛的概念,在QML中model/view的实现方式并不仅限于QStandardItemModel这类,也可以是继承QObject使用属性系统的简单类,将界面上使用到的属性映射到该类上即可,如一个文件导出弹窗,界面上由两个编辑栏,一个用于输入文件名fileName,另一个用于输入文件路径filePath,而这两个则对应模型类内的两个属性,通过属性绑定显示在QML中UI组件。
而这类界面模型类,通常需要有保存成配置文件的功能,能够快速将原有属性进行序列化和反序列化,此处我们选择Json,因为Json在QML中更为通用。该类的特点总结如下:
- 使用属性系统进行映射
- 提供FromJson和ToJson接口
- 提供统一的属性变化信号,方便触发文件存储
核心代码
快捷宏
用过Qt属性系统的应该都感受到较为范围,需要有属性名、读函数、写函数以及变化信号,而上述模型类中的属性通常是非常多的,这里会增加编程负担,因此增加一个快捷宏,将定义封装成宏使用,如下所示:
#define ADD_PROPERTY(type, name) \
Q_PROPERTY(type name READ name WRITE name##Config NOTIFY name##Changed) \
public: \
type name(); \
void name##Config(type value); \
Q_SIGNAL \
void name##Changed(); \
\
private: \
type name##_;
这里没有把函数的定义包裹在宏中,因为那样后续就没办法调试,会增加维护成本。
序列化、反序列化
前文提到,该模型类使用的是属性系统,那么在序列化时只需要遍历所有的属性,将它们组装为Json即可,如下所示:
bool JsonInfoBase::FromJson(const QJsonObject &json_object)
{
for(const auto &name : PropertyNames()){
if(!json_object.contains(name)){
qDebug()<<__FUNCTION__<<"error:"<<name;
continue;
}
setProperty(name, json_object.value(name));
}
return true;
}
QJsonObject JsonInfoBase::ToJson() const
{
QJsonObject json_object;
for(const auto &name : PropertyNames()){
if(!property(name).isValid() || property(name).isNull()){
continue;
}
json_object.insert(name, QJsonValue::fromVariant(property(name)));
}
return json_object;
}
QList<QByteArray> JsonInfoBase::PropertyNames() const
{
QList<QByteArray> property_names;
auto meta_object = this->metaObject();
for (int i=0; i < meta_object->propertyCount(); i++){
property_names << meta_object->property(i).name();
}
property_names.removeAll("objectName");// 移除自带的
return property_names;
}
属性变化信号
解决了序列化和反序列化的问题,还需要解决何时触发数据的存储,当然是属性发生变化就触发存储,可以逐个属性变化信号进行关联,但这就意味着后续每增加一个属性就需要关联一次,很容易遗忘,增加维护难度,需要一种一劳永逸的方式,自动进行信号关联,这里同样使用到Qt的反射机制,在运行时通过属性名称获得对于属性的变化信号函数,将其关联至同一个信号,代码如下:
void JsonInfoBase::ObservePropertyChanged(const QStringList &ignore_propertys)
{
auto interested_propertys = PropertyNames();
auto meta_object = this->metaObject();
for (int i=0; i < meta_object->propertyCount(); i++){
QMetaProperty meta_property = meta_object->property(i);
if(!interested_propertys.contains(meta_property.name())){
continue;
}
if(ignore_propertys.contains(meta_property.name())){
continue;
}
if(meta_property.hasNotifySignal()){
const QMetaMethod notify_signal = meta_property.notifySignal();
const QMetaMethod property_changed = meta_object->method(meta_object->indexOfMethod("propertyChanged()"));
connect(this, notify_signal, this, property_changed, Qt::UniqueConnection);
}
}
}
完整代码
#ifndef JSONINFOBASE_H
#define JSONINFOBASE_H
#include <QObject>
#include <QJsonObject>
#define ADD_PROPERTY(type,name) \
Q_PROPERTY(type name READ name WRITE name##Config NOTIFY name##Changed) \
public: \
type name(); \
bool name##Config(type value); \
Q_SIGNAL \
void name##Changed(); \
private: \
type name##_;
class JsonInfoBase : public QObject
{
Q_OBJECT
public:
explicit JsonInfoBase(QObject *parent = nullptr);
public:
// json_object转换为类的属性
virtual bool FromJson(const QJsonObject &json_object);
// 属性转换为json_object
virtual QJsonObject ToJson();
void ObservePropertyChanged(const QStringList &ignore_propertys = QStringList());
Q_SIGNALS:
// 使用前需要在子类构造函数中调用ObservePropertyChanged
void propertyChanged();
protected:
QList<QByteArray> PropertyNames() const;
};
#endif // JSONINFOBASE_H
#include "json_info_base.h"
#include <QMetaProperty>
JsonInfoBase::JsonInfoBase(QObject *parent)
: QObject{parent}
{
}
bool JsonInfoBase::FromJson(const QJsonObject &json_object)
{
foreach (const QByteArray &name, PropertyNames()){
if(!json_object.contains(name)){
qDebug()<<__FUNCTION__<<"error:"<<name;
continue;
}
setProperty(name, json_object.value(name));
}
return true;
}
QJsonObject JsonInfoBase::ToJson()
{
QJsonObject json_object;
foreach(const QByteArray &name, PropertyNames()){
if(!property(name).isValid() || property(name).isNull()){
continue;
}
json_object.insert(name, QJsonValue::fromVariant(property(name)));
}
return json_object;
}
void JsonInfoBase::ObservePropertyChanged(const QStringList &ignore_propertys)
{
auto interested_propertys = PropertyNames();
auto meta_object = this->metaObject();
for (int i=0; i < meta_object->propertyCount(); i++){
QMetaProperty meta_property = meta_object->property(i);
if(!interested_propertys.contains(meta_property.name())){
continue;
}
if(ignore_propertys.contains(meta_property.name())){
continue;
}
if(meta_property.hasNotifySignal()){
const QMetaMethod notify_signal = meta_property.notifySignal();
const QMetaMethod property_changed = meta_object->method(meta_object->indexOfMethod("propertyChanged()"));
connect(this, notify_signal, this, property_changed, Qt::UniqueConnection);
}
}
}
QList<QByteArray> JsonInfoBase::PropertyNames() const
{
QList<QByteArray> property_names;
auto meta_object = this->metaObject();
for (int i=0; i < meta_object->propertyCount(); i++){
property_names << meta_object->property(i).name();
}
property_names.removeAll("objectName");// 移除自带的
return property_names;
}