Qt 5.4/Qml: Prevent binding loop

前端 未结 5 1339
孤街浪徒
孤街浪徒 2021-02-19 04:04

I have a global singleton \"Settings\" which holds application settings. When I try to run the following code I get a QML CheckBox: Binding loop detected for property \"ch

相关标签:
5条回答
  • 2021-02-19 04:31

    If you don't want to make a binding loop - don't make a binding, use a proxy variable, for example. Other simple solution can be to check the value:

    CheckBox {
        checked: Settings.someSetting                         
        onCheckedChanged: {
            if (checked !== Settings.someSetting) {
                Settings.someSetting = checked;
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-19 04:34

    You can also make two-way binding to resolve this issue:

    CheckBox {
        id: checkBox
    
        Binding { target: checkBox; property: "checked"; value: Settings.someSetting }
        Binding { target: Settings; property: "someSetting"; value: checkBox.checked }
    }
    
    0 讨论(0)
  • 2021-02-19 04:40

    Don't bind it. Because the check box does not fully depend on Setting.someSetting.

    When a user clicked the checkbox, the CheckBox.checked is changed by itself. At the same time, the property binding is no longer valid. Settings.someSetting cannot modify the CheckBox after it is clicked by user. Therefore, the checked: Settings.someSetting binding is wrong.

    If you want to assign an initial value to the check box when the component is ready, use Component.onCompleted to assign it:

    CheckBox {
        id: someSettingCheckBox 
    
        Component.onCompleted: checked = Settings.someSetting
        onCheckedChanged: Settings.someSetting = checked; 
    }
    

    If you are working on a more complex scenario, the Setting.someSetting may be changed by some other things during runtime and the state of the check box is required to be changed simultaneously. Catch onSomeSettingChanged signal and explicitly changed the check box. Submit the value of someSettingCheckBox to Settings only when the program/widget/dialog/xxx finished.

    CheckBox { id: someSettingCheckBox }
    
    //within the Settings, or Connection, or somewhere that can get the signal.
    onSomeSettingChanged: someSettingCheckBox.checked = someSetting
    
    0 讨论(0)
  • 2021-02-19 04:48

    Sometimes it is useful to separate input and output values in control. In this case control always displays real value and it can also show a delay to the user.

    CheckBox {
        checked: Settings.someSetting
        onClicked: Settings.someSetting = !checked
    }
    
    0 讨论(0)
  • 2021-02-19 04:49

    I prefer this solution

    // Within the model
    Q_PROPERTY(bool someSetting READ getSomeSetting WRITE setSomeSetting NOTIFY someSettingChanged)
    
    void SettingsModel::setSomeSetting(bool checkValue) {
        if (m_checkValue != checkValue) {
            m_checkValue = checkValue;
            emit someSettingChanged();
        }
    }
    
    // QML
    CheckBox {
        checked: Settings.someSetting                         
        onCheckedChanged: Settings.someSetting = checked
    }
    

    The trick is you protect the emit with an if check in the model. This means you still get a binding loop but only a single one, not an infinite one. It stops when that if check returns false thereby not emitting to continue the loop. This solution is very clean, you do not get the warning, and yet you still get all the benefits of the binding.

    I want to talk about the limitations of the other solutions presented

    CheckBox {    
        Component.onCompleted: checked = Settings.someSetting
        onCheckedChanged: Settings.someSetting = checked; 
    }
    

    In this solution you lose your binding. It can only have a default setting on creation and be changed by the user. If you expand your program such that other things change the values in your model, this particular view will not have a way to reflect those changes.

    Settings {
        id: mySettings
        onSomeSettingChanged: checkBox.checked = someSetting
    }
    CheckBox {
        id: checkBox
        onCheckedChanged: mySettings.someSetting = checked
    }
    

    This solution was mentioned to address these problems but never written out. It is functionally complete. Model changes are reflected, the user can change the data, and there are no binding loops because there are no bindings; only two discrete assignments. (x: y is a binding, x = y is an assignment)

    There are a couple problems with this. The first is that I think its ugly and inelegant, but that is arguably subjective. It seems fine here but if you have a model representing 10 things in this view, this turns into signal spaghetti. The bigger problem is that it does not work well with delegates because they only exist on demand.

    Example:

    MyModel {
        id: myModel
    
        // How are you going to set the check box of a specific delegate when
        // the model is changed from here?
    }
    ListView {
        id: listView
        model: myModel.namesAndChecks
        delegate: CheckDelegate {
            id: checkDelegate
            text: modelData.name
            onCheckStateChanged: modelData.checkStatus = checked
        }
    }
    

    You can actually do it. I've made up custom QML signals and connections to do it, but the code complexity makes me want to hurl, and even worse you could possibly be forcing creation of a delegate when it is not necessary.

    0 讨论(0)
提交回复
热议问题