Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to load settings in Qt app with QSettings

Tags:

c++

qt

qsettings

There are two possible ways:

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

Which approach is better?

like image 907
user1180567 Avatar asked Jan 16 '13 18:01

user1180567


3 Answers

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);
}
like image 199
Kévin Renella Avatar answered Oct 15 '22 09:10

Kévin Renella


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

like image 40
cgmb Avatar answered Oct 15 '22 09:10

cgmb


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.

like image 34
V.K. Avatar answered Oct 15 '22 09:10

V.K.