Custom QSettings :: ReadFunc dan QSettings :: WriteFunc, atau saat saya menulis kruk untuk Russify file pengaturan

pengantar



Halo, Habr!



Bagian dari pekerjaan saya adalah mengembangkan aplikasi desktop kecil. Secara khusus, ini adalah program yang memungkinkan Anda melacak status peralatan saat ini, mengujinya, mengatur parameter konfigurasi, membaca log, atau memeriksa saluran komunikasi antara dua perangkat. Seperti yang Anda pahami dari tag, saya menggunakan C ++ / Qt untuk membuat aplikasi.



Masalah



Saya baru-baru ini menghadapi tugas untuk menyimpan pengaturan konfigurasi ke file dan memuatnya dari sana. Saya ingin kali ini melakukan tanpa merancang sepeda dan menggunakan beberapa kelas dengan biaya minimal untuk penggunaannya.



Karena parameter dibagi menjadi beberapa grup berdasarkan modul perangkat, versi terakhir adalah struktur "Grup - Kunci - Nilai". QSettings menjadi cocok (tetapi dirancang untuk tugas ini). Percobaan pertama dari "pena" menghasilkan kegagalan, yang tidak saya duga akan saya hadapi.



Parameter tersebut ditampilkan dalam program kepada pengguna dalam bahasa Rusia, jadi kami ingin menyimpannya dalam bentuk yang sama (sehingga orang yang memiliki sedikit pengetahuan bahasa Inggris dapat melihat konten file).



    //   (   : 
    // 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();


Konten file apa yang ingin saya lihat:



[ ]
 №1= №1


dan itu berisi 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


Pada saat yang sama, apa yang menarik. Jika Anda melakukan prosedur pembacaan terbalik, maka saat menampilkan nilai, Anda dapat melihat bahwa nilai telah dibaca dengan benar.



    // ...   
    
    // 
    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);


Keluaran konsol:



" №1" " №1" true


Solusi "lama"



Saya pergi ke google (di Yandex). Jelas bahwa masalahnya adalah dengan pengkodean, tetapi mengapa mencari tahu sendiri, padahal dalam satu menit Anda sudah dapat menemukan jawabannya :) Saya terkejut bahwa tidak ada solusi yang ditulis dengan jelas (klik di sini, tulis ini, hidup dan bahagia).



Salah satu dari sedikit topik dengan judul [TERSELESAIKAN]: www.prog.org.ru/topic_15983_0.html . Tapi, ternyata saat membaca utas, di Qt4 dimungkinkan untuk memecahkan masalah dengan pengkodean, tetapi di Qt5 tidak lagi: www.prog.org.ru/index.php?topic=15983.msg182962#msg182962 .



Setelah menambahkan baris dengan solusi dari forum ke awal kode "sampel" (di bawah tenda tersembunyi "game" dengan semua kemungkinan pengkodean dan fungsi kelas Qt yang terkait dengannya), saya menyadari bahwa ini hanya sebagian menyelesaikan masalah.



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

    // ...   


Perubahan kecil di Application.ini (sekarang nilai parameter disimpan dalam Sirilik):



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


Kruk



Seorang kolega dari departemen lain, yang menangani hal-hal serius, menyarankan saya untuk menangani pengkodean atau menulis fungsi baca dan tulis kustom untuk QSettings yang akan mendukung grup, kunci, dan nilainya dalam Sirilik. Karena opsi pertama tidak membuahkan hasil, saya melanjutkan ke opsi kedua.



Ternyata dari dokumentasi resmi doc.qt.io/qt-5/qsettings.html, Anda dapat mendaftarkan format Anda sendiri untuk menyimpan data: doc.qt.io/qt-5/qsettings.html#registerFormat . Semua yang diperlukan adalah memilih ekstensi file (biarkan "* .habr") dimana data akan disimpan dan menulis fungsi di atas.



Sekarang "isian" dari main.cpp terlihat seperti ini:



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;
}


Mari kita mulai dengan menulis fungsi untuk menulis data ke file (menyimpan data lebih mudah daripada menguraikannya). Dokumentasi doc.qt.io/qt-5/qsettings.html#WriteFunc-typedef mengatakan bahwa fungsi tersebut menulis sekumpulan pasangan kunci / nilai . Ini dipanggil sekali, jadi Anda perlu menyimpan data pada satu waktu. Parameter fungsi adalah QIODevice & device (link ke "I / O device") dan QSettings :: SettingsMap (container QMap <QString, QVariant>).



Karena nama kunci disimpan di penampung dalam bentuk "Grup / parameter" (menafsirkan tugas Anda), Anda harus memisahkan nama grup dan parameter terlebih dahulu. Kemudian, jika grup parameter berikutnya telah dimulai, maka perlu memasukkan pemisah sebagai baris kosong.



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


Anda dapat menjalankan dan melihat hasilnya tanpa fungsi baca khusus. Anda hanya perlu mengganti string inisialisasi format untuk QSettings:



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

    // ...  


Data dalam file:



[ ]
 №1= №1


Keluaran konsol:



" №1" " №1" true


Ini bisa saja berakhir. QSettings menjalankan fungsinya membaca semua kunci, menyimpannya dalam sebuah file. Hanya ada nuansa bahwa jika Anda menulis parameter tanpa grup, maka QSettings akan menyimpannya di memorinya, tetapi tidak akan menyimpannya ke file (Anda perlu menambahkan kode dalam fungsi readParameters di tempat di mana pemisah "/" tidak ditemukan di nama kunci dari kontainer const QSettings :: SettingsMap & map).



Saya lebih suka menulis fungsi saya sendiri untuk mem-parsing data dari file agar dapat secara fleksibel mengontrol jenis penyimpanan data (misalnya, nama grup tidak dibingkai dengan tanda kurung siku, tetapi dengan karakter pengenalan lainnya). Alasan lainnya adalah untuk menunjukkan cara kerja berbagai hal dengan fungsi baca dan tulis kustom. Lihat



dokumentasi doc.qt.io/qt-5/qsettings.html#ReadFunc-typedeffungsinya untuk membaca satu set pasangan kunci / nilai . Ini harus membaca semua data dalam satu lintasan dan mengembalikan semua data ke penampung, yang ditentukan sebagai parameter fungsi, dan awalnya kosong.



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


Kami mengembalikan fungsi baca kustom ke inisialisasi format untuk QSettings dan memeriksa apakah semuanya berfungsi:



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

    // ...  


Keluaran konsol:



" №1" " №1" true


Pekerjaan kruk



Karena saya "mempertajam" implementasi fungsi untuk tugas saya, saya perlu menunjukkan cara menggunakan "keturunan" yang dihasilkan. Seperti yang saya katakan sebelumnya, jika Anda mencoba menulis parameter tanpa grup, maka QSettings akan menyimpannya dalam memorinya dan akan menampilkannya saat metode allKeys () dipanggil.



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


Keluaran konsol ("Parameter # 4" jelas tidak berguna di sini):



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


Dalam hal ini, isi file:



[ ]
 №3= №3

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


Solusi untuk masalah kunci tunggal adalah mengontrol bagaimana data ditulis saat menggunakan QSettings. Jangan izinkan penyimpanan parameter tanpa awal dan akhir grup atau kunci filter yang tidak berisi nama grup dalam namanya.



Kesimpulan



Masalah menampilkan grup, kunci, dan nilainya dengan benar telah dipecahkan. Ada nuansa menggunakan fungsionalitas yang dibuat, tetapi jika digunakan dengan benar, itu tidak akan mempengaruhi pengoperasian program.



Setelah pekerjaan selesai, tampaknya sangat mungkin untuk menulis pembungkus untuk QFile dan hidup bahagia. Namun di sisi lain, selain fungsi baca dan tulis yang sama, Anda harus menulis fungsionalitas tambahan yang sudah dimiliki QSettings (mendapatkan semua kunci, bekerja dengan grup, menulis data yang belum disimpan, dan fungsionalitas lain yang tidak muncul di artikel).



Apa gunanya? Mungkin mereka yang menghadapi masalah serupa, atau yang tidak langsung mengerti bagaimana menerapkan dan mengintegrasikan fungsi baca dan tulis mereka, akan menemukan artikel bermanfaat. Bagaimanapun, akan menyenangkan untuk membaca pemikiran Anda di komentar.



All Articles