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?


  • Qt Champions 2017

    Use Settings and expose the same to QML using setContextProperty.



  • Hi,

    thanks for your answer, but I'm not sure what you want me to do.

    I know how to expose a c++ class to qml, but how do I do this with Settings? Pull a reference and then push it to root?

    Could you maybe give me more information or a short example?



  • 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


  • Qt Champions 2017

    @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



  • Thanks.

    I will look into a custom approach and read the thread you posted.

    Any further advice from your experience I should take into account?



  • 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 .qml

    import 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?


Log in to reply