qmlproperty-binding

Controling an Item "enabled" property in a repeater from a checkbox in another repeater


This sounded as an easy task, but it doesn't work like I expected.

I've got one repeater with a checkbox, laid out vertically. Then I've a grid of Combo-boxes. I'd like to have the combo-boxes disabled when their corresponding checkbox is checked.

This is an extract from the code:

Repeater {
    id: idChkUseChordNotes
    model: _max_patterns

    SmallCheckBox {
        onClicked: {
            console.log("Clicked at " + index + "!! ==> " + idChkUseChordNotes.itemAt(index).checked);
            // attempt to force a refresh of the idStepNotes repeater => No effect
            //resetP=false;
            //resetP=true;
        }
    }
}

Repeater {
    id: idStepNotes
    model: getPatterns(resetP)

    StackLayout {
        width: parent.width
        currentIndex: modeIndex()

        property int stepIndex: index % _max_steps
        property int patternIndex: Math.floor(index / _max_steps)

        Label {
            text: "dummy"
        }

        ComboBox {
            id: lstGStep
            property var step: patterns[patternIndex * _max_steps + stepIndex]
            editable: false
            enabled: !idChkUseChordNotes.itemAt(patternIndex).checked
            model: _ddGridNotes
            currentIndex: find(step.degree, Qt.MatchExactly)
            onCurrentIndexChanged: {
                step.degree = model[currentIndex];
                // debug
                console.log(idChkUseChordNotes.itemAt(patternIndex).checked); // <-- working fine
            }
        }
    }
}

Though the combobox has access to the right checkbox (see "debug" in the "onCurrentIndexChanged") checking on&off the checkbox doesn't affect the combox-box property.

I know QML has some limitations with the level property-binding. I don't know if this is the issue here.

IMPORTANT: I'm limited to these versions : QtQuick 2.9 and QtQuick.Controls 2.2


Solution

  • You should be seeing a bunch of warning like this: TypeError: Cannot read property 'checked' of null. It is very surprising that (according to your comments) you don't. The checkboxes are simply not yet instantiated at the time the binding gets evaluated. Fixing this requires procedural code in various Component.onCompleted handlers, as you have already noted in the comments. This is, however, not the way QML is intended to be used.

    Instead of thinking about your problem in terms of UI elements interacting with eachother, consider a more data-centric approach. Both the checkboxes and the comboboxes are really just different views or aspects of the same set of data. Every data point has a boolean aspect (or property in Qt terms) "Use Chord Notes" and another aspect ("degree"?) with another meaning. The checkboxes are visual and interactable representations of the boolean aspect and the comboboxes do the same for the other one, but using the first to decide interactability.

    The Qt/QML way of representing such a structure in code is through views, bindings and models. I have mocked up an example of how this may look using your snippet as a starting point. Notice how there is no declarative code (except the button's onClicked handler, but that is for illustrative purposes).

    A couple of thing to note:

    Code

    import QtQuick 2.9
    import QtQuick.Layouts 1.2
    import QtQuick.Controls 2.2
    import QtQuick.Window 2.9
    
    Window {
        width: 640
        height: 480
        visible: true
    
        ListModel {
            id: patterns
    
            ListElement {
                useChordNotes: false
                gridNote: "1"
            }
    
            ListElement {
                useChordNotes: true
                gridNote: "3"
            }
    
            ListElement {
                useChordNotes: false
                gridNote: "2"
            }
        }
    
        RowLayout {
            anchors.fill: parent
    
            ColumnLayout {
                Repeater {
                    id: idChkUseChordNotes
    
                    model: patterns
    
                    CheckBox {
                        // Use a two-way binding here, in case something else in the program changes the model (e.g. loading a file)
    
                        // #1: change model value on user interaction
                        onCheckedChanged: useChordNotes = checked
    
                        // #2: update value on model change from another source
                        Binding on checked {
                            value: useChordNotes
                        }
                    }
                }
            }
    
            ColumnLayout {
                Repeater {
                    id: idStepNotes
    
                    model: patterns
    
                    ComboBox {
                        id: lstGStep
    
                        enabled: !useChordNotes
                        model: ["1", "2", "3"]
    
                        onActivated: gridNote = currentValue
    
                        Binding on currentIndex {
                            // Replace this to something that works with whatever type lstGStep.model might be
                            value: lstGStep.model.indexOf(gridNote)
                        }
                    }
                }
            }
    
            ColumnLayout {
                Repeater {
                    model: patterns
    
                    Button {
                        text: "useChordNotes: " + (useChordNotes ? "true" : "false") + "; gridNote: " + gridNote
                        onClicked: {
                            gridNote = "3";
                            useChordNotes = !useChordNotes;
                        }
                    }
                }
            }
        }
    }