1. 程式人生 > 其它 >30.qt quick-使用qmlRegisterSingletonType註冊單例類給QML使用

30.qt quick-使用qmlRegisterSingletonType註冊單例類給QML使用

上章,我們學習使用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 {
            width: 300
            height: 90
        }
        CfgRectangle {
            width: 300
            height: 90
        }
        CfgRectangle {
            width: 300
            height: 90
        }
}

未完待續~