上章,我们学习使用qmlRegisterType()注册C++类到QML中.本章我们来学习qmlRegisterSingletonType,如何注册单例类给QML使用。
1.qmlRegisterSingletonType函数介绍
qmlRegisterSingletonType函数模版声明如下:
template<typename T> int qmlRegisterSingletonType(const char *uri, int versionMajor, int versionMinor, const char *typeName, QObject *(*)(QQmlEngine *, QJSEngine *) callback); template<typename T> int qmlRegisterSingletonType(const QUrl &url, const char *uri, int versionMajor, int versionMinor, const char *qmlName);
- 第一个带callback参数的是用来将C++类实例化后,然后保存到QML全局环境中,供QML使用
- 第二个是用来将QML类型实例化为单例类给QML使用(这个更简单了,直接参考帮助文档即可,但是单例类无法创建可视化的QML类,因为未指定父类就已经生成了)
单例类在应用开发很常见,比如配置文件类、系统信息类等,因为可能在每个页面都可能去访问它.
本章我们来实现一个 Xml配置文件的C++类,然后通过qmlRegisterSingletonType来单例化给QML使用.
2.demo实现效果
如上图所示,我们这里创建了3个CfgRectangle控件,每个控件都是获取的XmlCfg单例类,当我们更改第1个CfgRectangle控件的值时,其它控件也跟着改变了.
最后当我们关闭应用程序后,便将更改后的配置信息写回cfg.xml配置文件中.
3.xmlcfg.h:
class XmlCfg : public QObject { Q_OBJECT public: enum XmlName{ Start =0, Addr , CurrentUser, End }; Q_ENUM(XmlName) explicit XmlCfg(QObject *parent = nullptr); ~XmlCfg(); Q_INVOKABLE QString read(XmlName name); //读出数据 Q_INVOKABLE bool write(XmlName name,QString value); //向name写入value数据 Q_INVOKABLE bool writeXmlFile(); // 更新配置文件(程序退出时调用一次) private: class XmlAttribute{ // 保存配置数据 public: XmlName name; QString value; XmlAttribute(XmlName name, QString value) { this->name = name; this->value = value; } }; private: static QString m_xmlFileName; QList<XmlAttribute> attrList; // 记录属性 QMetaEnum m_metaState; bool m_fileIsUpdate; // 文件是否刷新 bool readXmlFile(); // 读取配置文件 void initXmlList(); signals: void writeUpdate(XmlName name); }; // 定义一个回调类指针,用于接收回调. static QObject *xmlCfg_qobject_singletontype_provider(QQmlEngine *engine, QJSEngine *scriptEngine) { //Q_UNUSED: 向编译器指示参数未在函数的主体中使用。这可用于抑制编译器警告 Q_UNUSED(engine) Q_UNUSED(scriptEngine) XmlCfg *cfg = new XmlCfg(); return cfg; }
4.xmlcfg.cpp:
QString XmlCfg::m_xmlFileName = "cfg.xml"; XmlCfg::XmlCfg(QObject *parent) : QObject(parent), m_metaState(QMetaEnum::fromType<XmlCfg::XmlName>()) { initXmlList(); readXmlFile(); } XmlCfg::~XmlCfg() { if (m_fileIsUpdate) { qDebug()<<"~XmlCfg : writeXmlFile"; writeXmlFile(); } } void XmlCfg::initXmlList() // 初始化 { attrList.clear(); attrList<<XmlAttribute(XmlName::Addr, "127.0.0.1"); attrList<<XmlAttribute(XmlName::CurrentUser, "诺谦"); } QString XmlCfg::read(XmlName name) //读出数据 { QString ret(""); foreach (XmlAttribute attr, attrList) if(attr.name == name) { ret = attr.value; } return ret; } bool XmlCfg::write(XmlName name,QString value) //向name写入value数据 { bool ret(false); for(int i =0; i<attrList.count(); i++) if(attrList[i].name == name) { qDebug()<<"write:"<<m_metaState.valueToKey(name)<<value; if (attrList[i].value != value) { attrList[i].value = value; ret =true; m_fileIsUpdate = true; emit writeUpdate(name); } break; } return ret; } bool XmlCfg::readXmlFile() // 读取配置文件 { QFile file(QApplication::applicationDirPath() +"//"+ m_xmlFileName); // 打开失败 那就是都是默认值 if(!file.open(QFile::ReadOnly | QFile::Text)) { file.close(); writeXmlFile(); return false; } QXmlStreamReader* reader=new QXmlStreamReader(&file); while(!reader->atEnd()) { QString name = reader->name().toString(); if(reader->isStartElement()) { if(name=="index") { reader->readNext(); } else { //读取其它值 bool isOk = false; XmlName xmlName = (XmlName)m_metaState.keyToValue(name.toLocal8Bit(), &isOk); // 字符串转枚举 if (!isOk) { qDebug()<<"readXmlFile !isOk "<<name; } write(xmlName, reader->readElementText()); } } if(reader->isEndElement()) { } reader->readNext(); } file.close(); m_fileIsUpdate = false; if(reader->hasError()) { qDebug()<<"readXml ERR:"<<reader->errorString(); initXmlList(); //初始化设置 writeXmlFile(); return false; } return true; } bool XmlCfg::writeXmlFile() // 更新配置文件(程序退出时调用一次) { QFile file(QApplication::applicationDirPath() +"//"+ m_xmlFileName); QFileInfo info(file); if(!file.open(QFile::WriteOnly | QFile::Text)) { qDebug()<<"file write error"; return false; } QXmlStreamWriter* writer=new QXmlStreamWriter(&file); QXmlStreamAttributes attributes; writer->setCodec("utf-8"); writer->setAutoFormatting(true); writer->writeStartDocument(); writer->writeStartElement("index"); writer->writeComment("配置"); //写入所有存值 for(int i = (int)XmlName::Start + 1; i<(int)XmlName::End; i++) { QString name = m_metaState.valueToKey(i); // 枚举转字符串 QString value = read((XmlName)i); writer->writeTextElement(name,value); } writer->writeComment("配置完成"); writer->writeEndElement(); writer->writeEndDocument(); file.close(); return true; }
每次创建XmlCfg时,会从cfg.xml中读取出当前配置信息.并保存到attrList成员中.
当程序退出的时候,则会调用析构函数,如果m_fileIsUpdate为true,则更新内容到cfg.xml中
然后我们在main.cpp中注册单例类:
qmlRegisterSingletonType<XmlCfg>("Qt.Singleton", 1, 0, "XmlCfg", xmlCfg_qobject_singletontype_provider);
5.实现CfgRectangle.qml
然后我们实现一个CfgRectangle.qml文件,用来读写配置信息的控件:
import QtQuick 2.0 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 import Qt.Singleton 1.0 Rectangle {
... ...
function writeAddr(addr) { XmlCfg.write(XmlCfg.Addr, addr); } function writeUser(user) { XmlCfg.write(XmlCfg.CurrentUser, user); } GridLayout { ... ... TextArea { id: areaAddr implicitWidth: 200 text : XmlCfg.read(XmlCfg.Addr) // 读取XML文件中的 Addr元素的内容 color: "#0E0E0E" font.pixelSize: 14 font.family: "Microsoft Yahei" background: Rectangle { anchors.fill: parent border.color: "#E0E0E0" color: "#FFFFFF" radius: 4 } Keys.onPressed: { if ((event.key == Qt.Key_Return || event.key == Qt.Key_Enter ) ) { writeAddr(text) event.accepted = true } } } ... ... TextArea { id: areaCurrentUser implicitWidth: 200 text : XmlCfg.read(XmlCfg.CurrentUser) // 读取XML文件中的 CurrentUser元素的内容 color: "#0E0E0E" font.pixelSize: 14 font.family: "Microsoft Yahei" background: Rectangle { anchors.fill: parent border.color: "#E0E0E0" color: "#FFFFFF" radius: 4 } Keys.onPressed: { if ((event.key == Qt.Key_Return || event.key == Qt.Key_Enter ) ) { writeUser(text) event.accepted = true } } } } Connections { target: XmlCfg; onWriteUpdate:{ // 重新读取XML文件中元素的内容 switch (name) { case XmlCfg.Addr: areaAddr.text = XmlCfg.read(XmlCfg.Addr); case XmlCfg.CurrentUser: areaCurrentUser.text = XmlCfg.read(XmlCfg.CurrentUser); } } } }
最后我们在main.qml中,创建多个CfgRectangle控件
Column { anchors.centerIn: parent spacing: 3 Text { text: " 输入回车即可保存到配置文件中:"; font.pixelSize: 20 color: "#444" font.family: "Microsoft Yahei" } CfgRectangle { 300 height: 90 } CfgRectangle { 300 height: 90 } CfgRectangle { 300 height: 90 } }
未完待续~