Managing Settings
-
Hi,
I'm wondering how to manage settings in my application.
For QML there only sems to be Qt.labs.settings 1.0 which is based on QSettings. Using it I get persistent properties, but as far as i can tell I can't e.g. a list of all settings. It's also not (yet) possible to change how data is stored (like custom formats in QSettings).
If I use a custom class I won't be able to use bindings (unless i make custom classes with all properties every time...), at least I don't know how to. I would have to do updates manually every time, but I could use a QMap (or QSettings) to access all settings. That would make it much easier to create a settings editor since settings will be spread across files.
Right now I would probably go with either QSettings or a custom class to manage them in c++.
Does someone have any advice or another strategy for this?
-
Use Settings and expose the same to QML using setContextProperty.
-
-
Hi @Larvae.
I found an old topic where we have talked about how to expose a QSettings derived class and become it usable inside of qml context.
https://forum.qt.io/topic/91783/access-qml-elements-from-c-classes/4 -
@Larvae QSettings is QObject and you will be able to expose the same to QML as indicated by previous post. Issue is that Settings does not have any slots or functions which can be accessed from QML. So it is better your own class & expose the same to QML. Access all the property from your custom class using Q_PROPERTY
-
So, after some looking around and experimenting, this is what I got.
The singleton is used to collect all settings. This is where a c++ backend will be used to actually store the settings an disk, probably using QSettings. Through this it will (probably) be possible to config the settings (format, path, file name, stuff like that).
MySingleton.qml
pragma Singleton import QtQuick 2.0 Item { // holding all settings as key-value pairs property var dataMap : ({}) // list to filter out all the inherited base properties property var filterObject : { "x":"", "y":"", "z":"", "width":"", "height":"",....... } }
This element is the base for the settings elements. It's purpose is to sync to the singleton, writing the settings to the dataMap and, if they already exist, read on startup (the reading is missing atm). For this it will perform reading and writing on construction and destruction and (later) provide signals and slots for this.
MySettings .qmlimport QtQuick 2.11 import QtQml 2.11 Item{ id:base Component.onCompleted: { console.log("Settings Created") for(var prop in this){ if(!MySingleton.filterObject.hasOwnProperty(prop) && typeof this[prop] !=="function" ) { MySingleton.dataMap[prop] = this[prop]; var component = Qt.createComponent("BidirecionalBinding.qml"); if (component.status === Component.Ready) { var binding = component.createObject(this, {"first": this, "second": MySingleton.dataMap, "firstProperty": prop, "secondProperty": prop} ); console.log() } } } } Component.onDestruction: { console.log("Destructing Settings") for(var prop in this){ if(!MySingleton.filterObject.hasOwnProperty(prop) && typeof this[prop] !=="function" ) console.log(prop + "(" + typeof this[prop] +"): " + this[prop]) } console.log(JSON.stringify(MySingleton.dataMap)) } }
The BidirectionBinding uses two Binding elements to make one bidirectional binding. I tested it using two buttons and bound the labels, worked without problem. It's intention is to keep the map and the settings elements properties in sync.
BidirectionBinding.qml
import QtQuick 2.11 import QtQuick.Controls 2.4 import QtQml 2.11 Item { id:bidirectionalBinding property alias first: firstBinding.target property alias second: secondBinding.target property alias firstProperty: firstBinding.property property alias secondProperty: secondBinding.property property bool delayed: true property alias firstWhen: firstBinding.when property alias secondWhen: secondBinding.when property alias firstValue: firstBinding.value property alias secondValue: secondBinding.value Binding { id: firstBinding; when: true; delayed: bidirectionalBinding.delayed; value: second[secondProperty] } Binding { id: secondBinding; when: true; delayed: bidirectionalBinding.delayed; value: first[firstProperty] } }
The last file it the main file. This is for testing and demonstation. Its just four buttons for printing and changing the values.
main.qml
import QtQuick 2.11 import QtQuick.Window 2.11 Window { id: window title: "Hello Settings" visible: true width: 280 height: 250 property MySettings settings: MySettings{ id: settings property alias windowTitle: window.title property alias rectWidth: rect.width } Rectangle { id: rect width: 80;height: 50;x: 50;y: 50;color: 'green' Text { text: "Set Title" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } MouseArea { anchors.fill: parent onClicked: window.title = "Hello New Title" } } Rectangle { width: 80;height: 50;x: 150;y: 50;color: 'green' Text { text: "Set Alias" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } MouseArea { anchors.fill: parent onClicked: settings.windowTitle = "Hello New Alias" } } Rectangle { width: 80;height: 50;x: 50;y: 150;color: 'green' Text { text: "Set Map" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } MouseArea { anchors.fill: parent onClicked: MySingleton.dataMap["windowTitle"] = "Hello New Setting" } } Rectangle { width: 80;height: 50;x: 150;y: 150;color: 'green' Text { text: "Print" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } MouseArea { anchors.fill: parent onClicked: { console.log(settings.windowTitle) console.log(JSON.stringify(MySingleton.dataMap)) console.log() } } } }
Thats it so far. The advantage of this approach is, that it can be used like the Settings element from Qt. Also, it can easily be added to existing code later, the original code won't have to be changed (most likely).
The problem right know, aside from missing things like the backend, are the bidirectional bindings. I get the error
qrc:/BidirecionalBinding.qml:21: TypeError: Cannot read property 'rectWidth' of null
qrc:/BidirecionalBinding.qml:21: TypeError: Cannot read property 'windowTitle' of null(line 21 is where the first Binding is declared)
What do you think about this so far?