Search code examples
qtqmlrepeaterproperty-binding

Show hide component in a repeater


I've some instances of the following that I'm displaying in a repeater through a loaded component. And I'm trying to control the visibility of the repeated component:

property var modes : [new modeClass("Mode 1", "mode1", noteC1)];

function modeClass(name, representation, note) {
    var active = true;
    this.name = name;
    this.representation = representation;
    this.note = note;
    this.activated = true;

    Object.defineProperty(this, "activated", {
        get : function () {
            return active;
        },
        set : function (newActive) {
            active = newActive;
            if (!active)
                note.selected = false;
            this.dummy = active;
            console.log(name, this.dummy);
        },
        enumerable : true
    });

    this.dummy = active;
}

And the repeater:

// Repeater pour les notes des modes
Repeater {
    id : repModes
    model : modes 
    //delegate : holeComponent - via Loader, to specify the "note" to display
    Loader {
        id : loaderModes
        Binding {
            target : loaderModes.item
            property : "note"
            value : modes[model.index].note
        }
        Binding {
            target : loaderModes.item
            property : "visible"
            value : modes[model.index].activated
        }
        sourceComponent : holeComponent
    }
}

I'm controlling the activated property of the modeClass instance through some checkbox.

I don't manage to have the property-binding for the visibility to work. It works only a the instantiation of the component and does not react on the property changes.

I've tried several ways to do the binding:

Alternative 1

With the property with the getter and setter

        Binding {
            target : loaderModes.item
            property : "visible"
            value : modes[model.index].activated
        }

Alternative 2

With "direct" property (no getter, no setter)

        Binding {
            target : loaderModes.item
            property : "visible"
            value : modes[model.index].dummy
        }

Alternative 3

With a "bottom-up" approach

    Loader {
        id : loaderModes
        ...
        property bool showhide: modes[model.index].activated
        //property bool showhide: modes[model.index].dummy
        sourceComponent : holeComponent
    }

and in the component:

Component {
    id : holeComponent

    Image {
        id : img
        property var note
        visible : showhide

Alternative 4

Work at the model level:

Repeater {
    id : repModes
    model : modes.filter(function(m) { return m.activated; })
    //model : modes.filter(function(m) { return m.dummy; })

None of these works. The visibility of the holeComponents remains all the time the one at instantiation.

Any other approach ?

====================== Edit 27/10: a KISS version:

ColumnLayout {
    id : layConfig

    Repeater {
        model : modes
        delegate : CheckBox {
            id : chkConfig
            property var __mode : modes[model.index]
            Layout.alignment : Qt.AlignLeft | Qt.QtAlignBottom
            text : __mode.name + (__mode.activated ? "++" : "--")
            checked : __mode.activated;
            onClicked : {
                console.log("onClik",__mode.name,modelData.name);
                __mode.activated = !__mode.activated;
                console.log("mode.activated ==> ",__mode.activated);
                console.log("modelData.activated ==> ",modelData.activated);
            }
        }

    }
}

I expect that when click the checkbox, the checkbox text is changed automaticaly by property-binding. IT DOES NOT. I guess it might be related to the property var __mode : modes[model.index]. I should use instead modelData. But with modelData I don't have access to updated underlying object properties. And so it doesn't work neither.

Log output:

Debug: onClik Mode 1 Mode 1
Debug: mode.activated ==>  **true**
Debug: modelData.activated ==>  **false**

Debug: onClik Mode 1 Mode 1
Debug: mode.activated ==>  false
Debug: modelData.activated ==>  false

Solution

  • I found this hack on internet and have applied it to my case with success :

    QML trick: force re-evaluation of a property binding

    The issue lays in the fact, that qml does only re-evaluate the modelData upon re-evaluation of the model what it does only when there is an obvious change in the model. So a change in an underlying object of the model is not detected. And this is what I was trying to achieve.

    So the hack is basically:

    And the repeater:

    // Repeater pour les notes des modes
    Repeater {
        id : repModes
        model : ready?getActivatedModes():[] // awful hack 
        //delegate : holeComponent - via Loader, to specify the "note" to display
        Loader {
            id : loaderModes
            Binding {
                target : loaderModes.item
                property : "note"
                value : modes[model.index].note
            sourceComponent : holeComponent
        }
    }
    

    And the repeater for the controlling checkboxes :

    Repeater {
        model : __config 
        delegate : CheckBox {
            id : chkConfig
            property var __mode : __config[model.index]
            Layout.alignment : Qt.AlignLeft | Qt.QtAlignBottom
            text : __mode.name
            checked : __mode.activated // init only
            onClicked : {
                __mode.activated = !__mode.activated;
                ready= false; // awful trick to force the refresh
                ready= true;
            }
        }
    }
    

    The ready=false; ready=true; is, for qml, an obvious change in the model, so qml re-evaluates the model and repaints everything correctly.