QSettings المخصصة :: ReadFunc و QSettings :: WriteFunc ، أو كما كتبت عكازًا لترويس ملف الإعدادات

المقدمة



مرحبا هبر!



جزء من عملي هو تطوير تطبيقات سطح المكتب الصغيرة. على وجه الخصوص ، هذه هي البرامج التي تتيح لك تتبع الحالة الحالية للمعدات ، أو اختبارها ، أو تعيين معلمات التكوين ، أو قراءة السجلات ، أو التحقق من قناة الاتصال بين جهازين. كما يمكنك أن تفهم من العلامات ، أستخدم 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


الحل "القديم"



ذهبت إلى google (في Yandex). من الواضح أن المشكلة تكمن في الترميزات ، ولكن لماذا تكتشفها بنفسك ، في حين يمكنك بالفعل العثور على الإجابة في غضون دقيقة :) لقد فوجئت بعدم وجود حلول مكتوبة بوضوح (انقر هنا ، اكتب هذا ، وعِش وكن سعيدًا).



أحد الموضوعات القليلة التي تحمل العنوان [تم الحل]: 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 (الآن يتم حفظ قيمة المعلمة باللغة السيريلية):



[%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


عكاز



نصحني زميل من قسم آخر ، يتعامل مع الأمور الجادة ، بالتعامل مع الترميزات أو كتابة وظائف قراءة وكتابة مخصصة لـ QSettings التي من شأنها دعم المجموعات والمفاتيح وقيمها باللغة السيريلية. نظرًا لأن الخيار الأول لم يؤتي ثماره ، فقد انتقلت إلى الخيار الثاني.



كما اتضح من الوثائق الرسمية 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 / O device") و 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 بتخزينها في ذاكرتها ، لكن لن تحفظها في ملف (تحتاج إلى إضافة الرمز في وظيفة readParameters في مكان لا يوجد فيه الفاصل "/" في اسم مفتاح حاوية QSettings const :: إعدادات الخريطة والخريطة).



فضلت أن أكتب وظيفتي الخاصة لتحليل البيانات من ملف لكي أتمكن من التحكم بمرونة في نوع تخزين البيانات (على سبيل المثال ، أسماء المجموعات مؤطرة ليس بأقواس مربعة ، ولكن بأحرف التعرف الأخرى). سبب آخر هو إظهار كيفية عمل الأشياء مع وظائف القراءة والكتابة المخصصة. راجع



الوثائق doc.qt.io/qt-5/qsettings.html#ReadFunc-typedefيُقال عن الوظيفة لقراءة مجموعة من أزواج المفاتيح / القيمة . يجب أن يقرأ كل البيانات في مسار واحد ويعيد جميع البيانات إلى الحاوية ، والتي تم تحديدها كمعامل دالة ، وهي فارغة في البداية.



//     
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