自定义QSettings :: ReadFunc和QSettings :: WriteFunc,或者在我写拐杖来粗暴化设置文件时

介绍



哈Ha!



我的工作之一是开发小型桌面应用程序。特别是,这些程序使您可以跟踪设备的当前状态,对其进行测试,设置配置参数,读取日志或检查两个设备之间的通信通道。从标签可以理解,我使用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中)。显然问题出在编码,但是为什么要自己弄清楚,当您在一分钟内就已经可以找到答案时:)我很惊讶没有清晰书面的解决方案(单击此处,写下来,过着幸福的生活)。



标题为[已解决]的几个主题之一: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和设备(链接到“ I / O设备”)和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会将其存储在其内存中,但不会将其保存到文件中(您需要在const QSettings容器的键名中找不到分隔符“ /”的位置的readParameters函数中添加代码。 :: SettingsMap和地图)。



我更喜欢编写自己的函数来解析文件中的数据,以便能够灵活地控制数据存储的类型(例如,组名不是用方括号括起来,而是用其他识别字符括起来)。另一个原因是要显示事物如何与自定义读取和写入功能一起工作。请参阅



文档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