カスタムQSettings :: ReadFuncおよびQSettings :: WriteFunc、または設定ファイルをRussifyするためのクラッチを作成したとき

前書き



こんにちは、Habr!



私の仕事の一部は、小さなデスクトップアプリケーションの開発です。特に、これらは、機器の現在の状態の追跡、テスト、構成パラメーターの設定、ログの読み取り、または2つのデバイス間の通信チャネルの確認を可能にするプログラムです。タグからわかるように、私はC ++ / Qtを使用してアプリケーションを作成しています。



問題



最近、構成設定をファイルに保存してそこからロードするという課題に直面しました。今回は自転車を設計せずに、最小限の費用でクラスを利用していきたいと思います。



パラメータはデバイスモジュールに応じてグループに分割されているため、最終バージョンは「グループ-キー-値」構造になります。QSettingsが適切になりました(ただし、このタスク用に設計されています)。「ペン」の最初の試みは、私が直面することを予期していなかった大失敗をもたらしました。



パラメータはプログラム内でユーザーにロシア語で表示されるので、同じ形式で保存したいと思います(英語の知識がほとんどない人でもファイルの内容を表示できるようにするため)。



    //   (   : 
    // C:\Users\USER_NAME\AppData\Roaming\)
    QSettings parameters(QSettings::IniFormat, QSettings::UserScope,
                         QString(""), QString(""));

    // 
    const QString group = QString(" ");
    const QString key = QString(" №1");
    const QString value = QString(" №1");

    //   -  - 
    parameters.beginGroup(group);
    parameters.setValue(key, value);
    parameters.endGroup();

    //  
    parameters.sync();


見たかったファイルコンテンツ:



[ ]
 №1= №1


そしてそれはPrilozhenie.iniを含んでいました



[%U041E%U0441%U043D%U043E%U0432%U043D%U044B%U0435%20%U043F%U0430%U0440%U0430%U043C%U0435%U0442%U0440%U044B]
%U041F%U0430%U0440%U0430%U043C%U0435%U0442%U0440%20%U21161=\x417\x43d\x430\x447\x435\x43d\x438\x435 \x2116\x31


同時に、何が面白いのか。逆の読み取り手順を実行すると、値を表示するときに、正しく読み取られたことがわかります。



    // ...   
    
    // 
    const QString group = QString(" ");
    const QString key = QString(" №1");
    const QString value = QString(" №1");

    //   -  - 
    parameters.beginGroup(group);
    QString fileValue = parameters.value(key).toString();
    parameters.endGroup();

    //    
    qDebug() << value << fileValue << (value == fileValue);


コンソール出力:



" №1" " №1" true


「古い」ソリューション



私はグーグル(Yandex)に行きました。問題がエンコーディングにあることは明らかですが、なぜ自分でそれを理解するのか、すぐに答えを見つけることができるのに:)明確に書かれた解決策がないことに驚きました(ここをクリックして、これを書き留めて、生きて幸せになってください)。



[解決済み]というタイトルの数少ないトピックの1つ:www.prog.org.ru/topic_15983_0.html。しかし、スレッドの読み取り中に判明したように、Qt4ではエンコーディングの問題を解決できましたが、Qt5ではもはやありません:www.prog.org.ru/index.php?topic = 15983.msg182962#msg182962



フォーラムから「サンプル」コードの先頭にソリューションの行を追加しました(内部には、Qtクラスのすべての可能なエンコーディングと機能が関連付けられた非表示の「ゲーム」があります)。これで問題が部分的にしか解決されないことに気付きました。



    // 
    QTextCodec *codec = QTextCodec::codecForName("UTF-8");
    QTextCodec::setCodecForLocale(codec);
    //    Qt5
    // QTextCodec::setCodecForTr(codec);
    // QTextCodec::setCodecForCStrings(codec);

    //   (   :
    // C:\Users\USER_NAME\AppData\Roaming\)
    QSettings parameters(QSettings::IniFormat, QSettings::UserScope,
                         QString(""), QString(""));
    parameters.setIniCodec(codec);

    // ...   


Application.iniの 小さな変更(現在、パラメーター値はCyrillicに保存されています):



[%U041E%U0441%U043D%U043E%U0432%U043D%U044B%U0435%20%U043F%U0430%U0440%U0430%U043C%U0435%U0442%U0440%U044B]
%U041F%U0430%U0440%U0430%U043C%U0435%U0442%U0440%20%U21161= №1


松葉杖



深刻なことを扱っている別の部門の同僚から、Cyrillicでグループ、キー、およびそれらの値をサポートするQSettingsのエンコーディングを扱うか、カスタムの読み取りおよび書き込み関数を作成するようにアドバイスされました。最初の選択肢は実を結ばなかったので、私は2番目の選択肢に進みました。



公式ドキュメントdoc.qt.io/qt-5/qsettings.htmlから判明したように、データを格納するための独自の形式を登録できます:doc.qt.io/qt-5/qsettings.html#registerFormat。必要なのは、データが保存されるファイル拡張子( "* .habr"とします)を選択し、上記の関数を書き込むことだけです。



これで、main.cppの「スタッフィング」は次のようになります。



bool readParameters(QIODevice &device, QSettings::SettingsMap &map);
bool writeParameters(QIODevice &device, const QSettings::SettingsMap &map);

int main(int argc, char *argv[])
{
    //  
    const QSettings::Format habrFormat = QSettings::registerFormat(
                "habr", readParameters, writeParameters, Qt::CaseSensitive);
    if (habrFormat == QSettings::InvalidFormat) {
        qCritical() << "  -";
        return 0;
    }

    //   (   :
    // C:\Users\USER_NAME\AppData\Roaming\)
    QSettings *parameters = new QSettings(habrFormat, QSettings::UserScope,
                                          QString(""), QString(""));

    // ...   

    return 0;
}


ファイルにデータを書き込むための関数を作成することから始めましょう(データを保存する方が、データを解析するよりも簡単です)。doc.qt.io/qt-5/qsettings.html#WriteFunc-typedefのドキュメントには、関数がキーと値のペアのセットを書き込むと記載されています。一度呼び出されるので、一度にデータを保存する必要があります。関数パラメーターは、QIODevice&device(「I / Odevice」へのリンク)およびQSettings :: SettingsMap(QMap <QString、QVariant>コンテナー)です。



キーの名前は「グループ/パラメーター」(タスクの解釈)の形式でコンテナーに格納されるため、最初にグループの名前とパラメーターを分離する必要があります。次に、パラメータの次のグループが開始された場合は、セパレータを空の行として挿入する必要があります。



//     
bool writeParameters(QIODevice &device, const QSettings::SettingsMap &map)
{
    // ,   
    if (device.isOpen() == false) {
        return false;
    }

    //  ,   
    QString lastGroup;

    //       
    QTextStream outStream(&device);

    //    
    // (      )
    for (const QString &key : map.keys()) {
        //        "/"
        int index = key.indexOf("/");
        if (index == -1) {
            //      
            //   (,   "")
            continue;
        }

        //     , 
        //      
        QString group = key.mid(0, index);
        if (group != lastGroup) {
            //   ()  . 
            //        
            if (lastGroup.isEmpty() == false) {
                outStream << endl;
            }
            outStream << QString("[%1]").arg(group) << endl;
            lastGroup = group;
        }

        //    
        QString parameter = key.mid(index + 1);
        QString value = map.value(key).toString();
        outStream << QString("%1=%2").arg(parameter).arg(value) << endl;
    }

    return true;
}


カスタム読み取り関数なしで実行して結果を確認できます。QSettingsのフォーマット初期化文字列を置き換える必要があります。



    //  
    const QSettings::Format habrFormat = QSettings::registerFormat(
                "habr", QSettings::ReadFunc(), writeParameters, Qt::CaseSensitive);

    // ...  


ファイル内のデータ:



[ ]
 №1= №1


コンソール出力:



" №1" " №1" true


これは終わったかもしれません。 QSettingsは、すべてのキーを読み取り、ファイルに保存する機能を実行します。グループなしでパラメータを書き込むと、QSettingsはそれをメモリに保存しますが、ファイルには保存しないというニュアンスがあります(constQSettingsコンテナのキー名に区切り文字「/」が見つからない場所のreadParameters関数にコードを追加する必要があります) :: SettingsMap&map)。



データストレージのタイプを柔軟に制御できるようにするために、ファイルからデータを解析するための独自の関数を作成することを好みました(たとえば、グループ名は角括弧ではなく他の認識文字で囲まれています)。もう1つの理由は、カスタムの読み取り関数と書き込み関数の両方で物事がどのように機能するかを示すことです。



ドキュメントを参照してくださいdoc.qt.io/qt-5/qsettings.html#ReadFunc-typedefこの関数は、キーと値のペアのセットを読み取ると言われています1回のパスですべてのデータを読み取り、関数パラメーターとして指定されているコンテナーにすべてのデータを返す必要があります。最初は空です。



//     
bool readParameters(QIODevice &device, QSettings::SettingsMap &map)
{
    // ,   
    if (device.isOpen() == false) {
        return false;
    }

    //       
    QTextStream inStream(&device);

    //  
    QString group;

    //    
    while (inStream.atEnd() == false) {
        // 
        QString line = inStream.readLine();

        //       
        if (group.isEmpty()) {
            //      
            if (line.front() == '[' && line.back() == ']') {
                //   
                group = line.mid(1, line.size() - 2);
            }
            //  ,   
            //    
        }
        else {
            //  ,   
            if (line.isEmpty()) {
                group.clear();
            }
            //    
            else {
                // : =
                int index = line.indexOf("=");
                if (index != -1) {
                    QString name = group + "/" + line.mid(0, index);;
                    QVariant value = QVariant(line.mid(index + 1));
                    //   
                    map.insert(name, value);
                }
            }
        }
    }

    return true;
}


カスタム読み取り関数を返して、QSettingsの形式を初期化し、すべてが機能することを確認します。



    //  
    const QSettings::Format habrFormat = QSettings::registerFormat(
                "habr", readParameters, writeParameters, Qt::CaseSensitive);

    // ...  


コンソール出力:



" №1" " №1" true


クラッチワーク



タスクの関数の実装を「シャープ」にしたので、結果の「子孫」の使用方法を示す必要があります。前に述べたように、グループなしでパラメーターを書き込もうとすると、QSettingsはそれをメモリーに保存し、allKeys()メソッドが呼び出されたときに表示します。



    //  
    const QSettings::Format habrFormat = QSettings::registerFormat(
                "habr", readParameters, writeParameters, Qt::CaseSensitive);
    if (habrFormat == QSettings::InvalidFormat) {
        qCritical() << "  -";
        return 0;
    }

    //   (   :
    // C:\Users\USER_NAME\AppData\Roaming\)
    QSettings *parameters = new QSettings(habrFormat, QSettings::UserScope,
                                          QString(""), QString(""));

    //  
    const QString firstGroup = " ";
    parameters->beginGroup(firstGroup);
    parameters->setValue(" №1", " №1");
    parameters->setValue(" №2", " №2");
    parameters->endGroup();

    //  
    const QString secondGroup = " ";
    parameters->beginGroup(secondGroup);
    parameters->setValue(" №3", " №3");
    parameters->endGroup();

    //   
    parameters->setValue(" №4", " №4");

    //   
    parameters->sync();

    qDebug() << parameters->allKeys();
    delete parameters;

    //    
    parameters = new QSettings(habrFormat, QSettings::UserScope,
                               QString(""), QString(""));

    qDebug() << parameters->allKeys();
    delete parameters;


コンソール出力(「パラメーター#4」は明らかにここでは不要です):



(" / №3", " №4", " / №1", " / №2")
(" / №3", " №4", " / №1", " / №2")


この場合、ファイルの内容は次のとおりです。



[ ]
 №3= №3

[ ]
 №1= №1
 №2= №2


単独キーの問題の解決策は、QSettingsを使用するときにデータがどのように書き込まれるかを制御することです。グループの開始と終了がないパラメータ、または名前にグループ名が含まれていないフィルタキーを保存しないでください。



結論



グループ、キー、およびそれらの値を正しく表示する問題が解決されました。作成した機能を使用することには微妙な違いがありますが、正しく使用すればプログラムの動作には影響しません。



作業が終わったら、QFileのラッパーを書いて幸せに暮らせることは完全に可能だと思われます。ただし、その一方で、同じ読み取りおよび書き込み機能に加えて、QSettingsにすでにある追加機能(すべてのキーの取得、グループの操作、未保存データの書き込み、および記事に表示されていないその他の機能)を書き込む必要があります。



使用は何ですか?同様の問題に直面している人や、読み取りおよび書き込み機能を実装および統合する方法をすぐに理解していない人は、この記事が役立つと思うかもしれません。いずれにせよ、コメントであなたの考えを読むのはいいでしょう。



All Articles