EinfĂŒhrung
Hallo Habr!
Ein Teil meiner Arbeit ist die Entwicklung kleiner Desktop-Anwendungen. Dies sind insbesondere Programme, mit denen Sie den aktuellen Status von GerĂ€ten verfolgen, testen, Konfigurationsparameter einstellen, Protokolle lesen oder den Kommunikationskanal zwischen zwei GerĂ€ten ĂŒberprĂŒfen können. Wie Sie den Tags entnehmen können, verwende ich C ++ / Qt, um Anwendungen zu erstellen.
Problem
Vor kurzem stand ich vor der Aufgabe, Konfigurationseinstellungen in einer Datei zu speichern und aus dieser zu laden. Ich möchte diesmal auf das Entwerfen von FahrrĂ€dern verzichten und eine Klasse mit minimalen Kosten fĂŒr deren Verwendung verwenden.
Da die Parameter nach GerĂ€temodulen in Gruppen unterteilt sind, ist die endgĂŒltige Version die Struktur "Group - Key - Value". QSettings wurde geeignet (aber fĂŒr diese Aufgabe entwickelt). Der erste Versuch mit dem "Stift" fĂŒhrte zu einem Fiasko, mit dem ich nicht gerechnet hatte.
Die Parameter werden dem Benutzer im Programm auf Russisch angezeigt, daher möchten wir sie in derselben Form speichern (damit Personen, die mit Englisch nicht sehr vertraut sind, den Inhalt der Datei anzeigen können).
// ( :
// 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();
Welchen Dateiinhalt wollte ich sehen:
[ ]
â1= â1
und das enthielt 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
Zur gleichen Zeit, was interessant ist. Wenn Sie den umgekehrten Lesevorgang ausfĂŒhren, können Sie bei der Anzeige des Werts feststellen, dass er korrekt gelesen wurde.
// ...
//
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);
Konsolenausgabe:
" â1" " â1" true
Die "alte" Lösung
Ich ging zu Google (in Yandex). Es ist klar, dass das Problem bei den Codierungen liegt, aber warum sollten Sie es selbst herausfinden, wenn Sie in einer Minute bereits die Antwort finden können :) Ich war ĂŒberrascht, dass es keine klar geschriebenen Lösungen gab (klicken Sie hier, schreiben Sie dies auf, leben Sie und seien Sie glĂŒcklich).
Eines der wenigen Themen mit dem Titel [RESOLVED]: www.prog.org.ru/topic_15983_0.html . Wie sich beim Lesen des Threads herausstellte, war es in Qt4 möglich, das Problem mit Codierungen zu lösen, in Qt5 gibt es jedoch nicht mehr: www.prog.org.ru/index.php?topic=15983.msg182962#msg182962 .
Nachdem ich die Zeilen mit der Lösung aus dem Forum am Anfang des "Beispiel" -Codes hinzugefĂŒgt hatte (unter der Haube befinden sich versteckte "Spiele" mit allen möglichen Codierungen und Funktionen der damit verbundenen Qt-Klassen), wurde mir klar, dass dies das Problem nur teilweise löst.
//
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);
// ...
Kleine Ănderung in Application.ini (jetzt wird der Parameterwert in Kyrillisch gespeichert):
[%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
KrĂŒcke
Ein Kollege aus einer anderen Abteilung, der sich mit ernsten Dingen befasst, riet mir, mich mit Codierungen zu befassen oder benutzerdefinierte Lese- und Schreibfunktionen fĂŒr QSettings zu schreiben, die Gruppen, SchlĂŒssel und deren Werte in kyrillischer Sprache unterstĂŒtzen. Da die erste Option keine FrĂŒchte trug, ging ich zur zweiten ĂŒber.
Wie aus der offiziellen Dokumentation doc.qt.io/qt-5/qsettings.html hervorgeht, können Sie Ihr eigenes Format zum Speichern von Daten registrieren: doc.qt.io/qt-5/qsettings.html#registerFormat . Sie mĂŒssen lediglich die Dateierweiterung (sei es "* .habr") auswĂ€hlen, in der die Daten gespeichert werden, und die oben genannten Funktionen schreiben.
Jetzt sieht die "FĂŒllung" von main.cpp so aus:
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;
}
Beginnen wir mit dem Schreiben einer Funktion zum Schreiben von Daten in eine Datei (das Speichern von Daten ist einfacher als das Parsen). Die Dokumentation zu doc.qt.io/qt-5/qsettings.html#WriteFunc-typedef besagt, dass die Funktion eine Reihe von SchlĂŒssel / Wert- Paaren schreibt . Es wird einmal aufgerufen, daher mĂŒssen Sie die Daten gleichzeitig speichern. Die Funktionsparameter sind QIODevice & Device (Link zu "I / O Device") und QSettings :: SettingsMap (QMap <QString, QVariant> Container).
Da der Name des SchlĂŒssels in der Form "Gruppe / Parameter" (Interpretation Ihrer Aufgabe) im Container gespeichert ist, mĂŒssen Sie zuerst die Namen der Gruppe und den Parameter trennen. Wenn dann die nĂ€chste Gruppe von Parametern gestartet wurde, muss das Trennzeichen als leere Zeile eingefĂŒgt werden.
//
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;
}
Sie können das Ergebnis ohne benutzerdefinierte Lesefunktion ausfĂŒhren und anzeigen. Sie mĂŒssen nur die Formatinitialisierungszeichenfolge fĂŒr QSettings ersetzen:
//
const QSettings::Format habrFormat = QSettings::registerFormat(
"habr", QSettings::ReadFunc(), writeParameters, Qt::CaseSensitive);
// ...
Daten in Datei:
[ ]
â1= â1
Konsolenausgabe:
" â1" " â1" true
Dies hĂ€tte enden können. QSettings hat die Funktion, alle SchlĂŒssel zu lesen und in einer Datei zu speichern. Es gibt nur eine Nuance: Wenn Sie einen Parameter ohne Gruppe schreiben, speichert QSettings ihn in seinem Speicher, speichert ihn jedoch nicht in einer Datei (Sie mĂŒssen den Code in der Funktion readParameters an einer Stelle hinzufĂŒgen, an der das Trennzeichen "/" nicht im SchlĂŒsselnamen des const QSettings-Containers gefunden wird :: SettingsMap & Map).
Ich habe es vorgezogen, meine eigene Funktion zum Parsen von Daten aus einer Datei zu schreiben, um die Art der Datenspeicherung flexibel steuern zu können (z. B. werden Gruppennamen nicht mit eckigen Klammern, sondern mit anderen Erkennungszeichen eingerahmt). Ein weiterer Grund ist zu zeigen, wie die Dinge sowohl mit benutzerdefinierten Lese- als auch mit Schreibfunktionen funktionieren. Siehe
Dokumentation doc.qt.io/qt-5/qsettings.html#ReadFunc-typedefDie Funktion soll eine Reihe von SchlĂŒssel / Wert- Paaren lesen . Es sollte alle Daten in einem Durchgang lesen und alle Daten an den Container zurĂŒckgeben, der als Funktionsparameter angegeben ist, und er ist anfangs leer.
//
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;
}
Wir geben die benutzerdefinierte Lesefunktion zurĂŒck, um das Format fĂŒr QSettings zu initialisieren und zu ĂŒberprĂŒfen, ob alles funktioniert:
//
const QSettings::Format habrFormat = QSettings::registerFormat(
"habr", readParameters, writeParameters, Qt::CaseSensitive);
// ...
Konsolenausgabe:
" â1" " â1" true
KrĂŒckenarbeit
Da ich die Implementierung von Funktionen fĂŒr meine Aufgabe "geschĂ€rft" habe, muss ich zeigen, wie man die resultierenden "Nachkommen" verwendet. Wie ich bereits sagte, wenn Sie versuchen, einen Parameter ohne Gruppe zu schreiben, speichert QSettings ihn in seinem Speicher und zeigt ihn an, wenn die allKeys () -Methode aufgerufen wird.
//
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;
Konsolenausgabe ("Parameter # 4" ist hier offensichtlich ĂŒberflĂŒssig):
(" / â3", " â4", " / â1", " / â2")
(" / â3", " â4", " / â1", " / â2")
In diesem Fall ist der Inhalt der Datei:
[ ]
â3= â3
[ ]
â1= â1
â2= â2
Die Lösung fĂŒr das Problem der einzelnen SchlĂŒssel besteht darin, zu steuern, wie Daten bei Verwendung von QSettings geschrieben werden. Lassen Sie das Speichern von Parametern ohne Anfang und Ende der Gruppen- oder FilterschlĂŒssel nicht zu, deren Name nicht den Gruppennamen enthĂ€lt.
Fazit
Das Problem der korrekten Anzeige von Gruppen, SchlĂŒsseln und deren Werten wurde behoben. Es gibt eine Nuance der Verwendung der erstellten FunktionalitĂ€t, aber bei korrekter Verwendung hat dies keine Auswirkungen auf den Betrieb des Programms.
Nach der Arbeit scheint es durchaus möglich zu sein, einen Wrapper fĂŒr QFile zu schreiben und glĂŒcklich zu leben. Auf der anderen Seite mĂŒssten Sie zusĂ€tzlich zu den gleichen Lese- und Schreibfunktionen zusĂ€tzliche Funktionen schreiben, ĂŒber die QSettings bereits verfĂŒgt (Abrufen aller SchlĂŒssel, Arbeiten mit einer Gruppe, Schreiben nicht gespeicherter Daten und andere Funktionen, die nicht im Artikel enthalten sind).
Was ist der Nutzen? Vielleicht finden diejenigen, die mit einem Ă€hnlichen Problem konfrontiert sind oder nicht sofort verstehen, wie sie ihre Lese- und Schreibfunktionen implementieren und integrieren, den Artikel nĂŒtzlich. In jedem Fall wird es schön sein, Ihre Gedanken in den Kommentaren zu lesen.