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
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:
ListModel
here for the sake of simplicity. In a real-world application this should most likely be implemented in C++.Binding
objects can be replace with simple binding expressions: checked: useChordNotes
.currentIndex
binding on the combobox uses the indexOf
method of the JS array used as a model here. In your code this must be replaced with something that works for whatever type _ddGridNotes
has. ComboBox.find()
does not work here because the initialization order. At the moment the Binding
is evaluated for the first time after construction, the combobox has not yet instantiated its items thereby always returning -1 for any call to find
. This is a similar problem to your original one.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;
}
}
}
}
}
}