How to load settings in Qt app with QSettings

你说的曾经没有我的故事 提交于 2019-12-03 06:18:41

问题


There are two possible ways:

  • load all settings into some struct
  • load values on-demand

Which approach is better?


回答1:


It depends on the way you will use your settings file. Do you want to allow the user of your application to dynamically change the settings in the file (.ini file for example) ? Or the settings have to be set by the GUI ?

If you are using some GUI to change the settings, I advice you you to load the main settings at the start of your application from a static class for example.

void SettingsManager::loadSettings()
{
    // .ini format example
    QSettings settings(FileName, QSettings::IniFormat);

    IntegerSetting = settings.value("SettingName", default).toInt();
    BooleanSetting = settings.value("SettingName", default).toBool();

    // ...
}

Then, there is no problem to save your changed values on-demand because of the QSettings optimization.

/**
  * key is your setting name
  * variant is your value (could be string, integer, boolean, etc.)
  */
void SettingsManager::writeSetting(const QString &key, const QVariant &variant)
{
    QSettings settings(FileName, QSettings::IniFormat);

    settings.setValue(key, variant);
}



回答2:


If you're concerned, you could put each logical group of settings behind an interface. Then, build a concrete class that uses QSettings to retrieve settings on demand.

If you find that to be a performance bottleneck, build a concrete class that caches the settings. (I never have needed to do so. QSettings has always been fast enough.)




回答3:


In the documentation of QSettings, it says that it has been optimized really well.

Internally, it keeps a map of QStrings to QVariants. All the accessor methods are extremely useful and are easy to use.

When I have used QSettings, I set it up similarly to their example with readSettings() and writeSettings() functions. See this example about half way down the page.

The moment I call readSettings() the QSettings object gets created it loads the values on demand and it keeps all the settings in some struct.

So in my main function I make sure I set up my application name and my organization name, and I also use QSettings::setFormat, and then after that whenever I want to access QSettings, I create an instance of QSettings with default parameters and access the settings.

QSettings s;
int val = s.value("Some_Group/some_setting", default_value).toInt();

// ...

s.setValue("Some_Group/some_setting", val);



回答4:


I will not answer your question exactly because you are asking a wrong question ;) You are asking about reading settings. Reading with constructing QSettings() and calling QSettings::value() is almost never a problem, all my measuring shows it is very very fast, fairly close to 0 ms. Regarding to your question: I would read data directly, i.e. no intermediary struct. Having another layer is just complication, it is not worthy the effort with possible synchronizations. And now to the real question.

What is much of a problem however is writing to settings. This is also quite fast on Windows if you are using native storage for settings, which is Windows Registry (the default on Windows). Registry is optimized by the OS, cached in RAM, and therefore writing to is does not cause delays either. However on Linux this seems to be a very much different story. What follows is related to Linux (Ubuntu and Kubuntu in my case).

I have not explored the source code of Qt in detail but all my measurements show that writing the settings to disk after any change takes at least about 50 ms on normal disk, SSD may be somewhat faster. It seems to me that saving operation is called when QSettings object data has been changed and the object is destroyed or when the application event loop is ready to do some work (i.e. is not busy redrawing or handling other events). Then the settings is flushed to disk.

Therefore I would warn against calling this QSettings().setValue(key, value); wherever speed is your concern. Because this will cause immediate save operation upon object destruction and will cause a delay.

Save time is not an issue if you call saving operation for the settings only once, for example when closing the application, you can pay 50 ms easily. But that is not usually what you want. You want your application state to be saved dynamically. In other words, when you change something in your application and then, without closing the first instance, you open another instance of that application and you would expect the new instance to already have the new settings. In that case you have to save everything as soon as any change is done, not just when application closes. And then the saving time becomes a big issue.

How I am doing it. I create a singleton class Settings which has static methods and provide similar API as QSettings object. In this singleton object, I create the QSettings object only once (just after I instantiate QApplication) and destroy it only once when application ends. In my code I call Settings::value(key) or Settings::setValue(key, value) whenever I need. The advantage is that the settings is saved only when the event loop is ready for it. Of course this still will take 50 ms but it is certain that it will be called only once and will save all changes which have been cached meanwhile. And this is a big improvement in comparison to QSettings().setValue(key, value) which will call save every time and can block your UI if you do multiple such calls.

You can certainly implement the singleton in many ways. The one which I use is this:

settings.h:

#pragma once

#include <QSettings>

/// Singleton! Create only one instance!
class Settings
{
public:
    Settings();
    ~Settings();

    static bool contains(const QString &key);
    static QVariant value(const QString &key, const QVariant &defaultValue = QVariant());
    static void setValue(const QString &key, const QVariant &value);

private:
    static Settings *s_instance;
    QSettings m_settings;
};

settings.cpp:

#include "settings.h"

Settings *Settings::s_instance = nullptr;

Settings::Settings()
{
    Q_ASSERT(s_instance == nullptr);
    s_instance = this;
}

Settings::~Settings()
{
    Q_ASSERT(s_instance != nullptr);
    s_instance = nullptr;
}

bool Settings::contains(const QString &key)
{
    return s_instance->m_settings.contains(key);
}

QVariant Settings::value(const QString &key, const QVariant &defaultValue)
{
    return s_instance->m_settings.value(key, defaultValue);
}

void Settings::setValue(const QString &key, const QVariant &value)
{
    s_instance->m_settings.setValue(key, value);
}

main.cpp:

...
Application application; // must be created before settings
Settings settings; // create settings singleton
application.exec() // runs event loop - settings is stored whenever event loop is ready
// settings destroyed here
// application destroyed here
...

And in the rest of the code just call Settings::setValue(key, value);.

Note that even this solution is not good enough for some very time critical usecases. Consider for example resizing of splitters or window by dragging a mouse. You want it to be smooth and to save the settings at the same time, right? To achieve smoothness you must not save it during the dragging but only after dragging is finished. Therefore do not save the settings in your mouse move event. You only want to change the settings after dragging is finished. To achieve this you will have to do some clever tricks with event filter, maybe inherit inherit and customize the stock Qt widgets or maybe something else, just according to your needs. But this is different story and different question.



来源:https://stackoverflow.com/questions/14365653/how-to-load-settings-in-qt-app-with-qsettings

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!