Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

model.rowCount() won't bind to Item's property

I have a ListView which is initialized with a custom C++ model named rootModel inside Qml. The model inherits QAbstractListModel and defines a QVector<customType> as a private member to populate the model.

In my ApplicationWindow I have created a Dialog in which I change the model and call the setList() function to update it. This works fine.

I also want to connect the model's size to a ScrollView's int property. This property will define the children of a RowLayout.

The problem is that when I try to bind this property to the model's size, the application crashes.

FYI, all the modifications of the model follow the Qt's rules. rowCount() is Q_INVOKABLE. I have also tried using the onModelChanged handler but this didn't work (I have checked in the documentation that this signal is emitted when modelReset() is emitted, which is taking place inside setList() through endResetModel()

I believe this is a straightforward procedure (have already performed property bindings many times in my project) but doesn't work as expected.

I quote some sample code of my project.

//main.qml
ConfiguredChannels{
    id: configuredList
    anchors{
        left: parent.left
        top: devices.bottom
        right: tabs.left
        bottom: parent.bottom
    }
}

TabArea {
    id: tabs
    y: toolBar.height
    x: parent.width / 8
    anchors {
        top: toolBar.bottom
    }
    width: 3 * parent.width / 4
    height: 3 * parent.height / 4
    countPWM: configuredList.model.rowCount() //This is where I want to bind.
}


//ConfiguredChannels.qml
id: confChanView
header: confChanHeader
model: ChannelModel{
    id: rootModel
    list: channelList
}

//TabArea.qml
Item{
id: tabAreaRoot
property alias channelsPWM: channelsPWM
property int countPWM
ScrollView{
            id: scrollPWM
            anchors.fill: parent
            contentItem: channelsPWM.children
            horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOn
            RowLayout{
                id: channelsPWM
                spacing: 0
                Layout.fillHeight: true
                Layout.fillWidth: true
                Component.onCompleted: {
                    var namesPWM = [];
                    for (var i=0; i<countPWM; i++){
                        namesPWM.push("Channel"+(i+1));
                    }
                    createChannels(countPWM, "PWM", channelsPWM, namesPWM);
                }
            }
}

[EDIT 1] After looking closer I realized that with my current implementation, even if I bind properly to the model's size I would still not be able to create the desired amount of RowLayout's children on demand (after I change the model in the Dialog Configuration.qml).

That is because I have put their creation inside RowLayout's Component.onCompleted handler. The content of this handler will be executed once the Configuration.qml will be initialized inside main.qmlfor the first time. After that, any other change to the countPWM will not make a difference since the Component is already Completed! Please correct me if I am wrong at this point.

Based on that, I have followed another implementation. I have created a "wrapper" function of createChannels, named createStrips(countPWM). This way, to update properly the RowLayout's children I have to call this function.

\\Configuration.qml
\\more code
currentModel.setList(newList)
tabs.createStrips(tableModel.count) \\tableModel is used to populate the newList that will be set to the model
newList.clear()
\\more code

\\TabArea.qml
function createStrips(countPWM){
    var namesPWM = [];
    for (var i=0; i<countPWM; i++){
        namesPWM.push("Channel"+(i+1));
    }
    createChannels(countPWM, "PWM", channelsPWM, namesPWM);
}


function createChannels(counter, channelType, channelParent, channelMapping){
    if ( channelParent.children.length !== 0){
        console.log("destroying");
        for ( var j = channelParent.children.length; j > 0 ; j--){
            channelParent.children[j-1].destroy();
        }
    }

    for (var i=0;i<counter;i++){
        var component = Qt.createComponent(channelType+".qml");
        if( component.status !== Component.Ready )
        {
            if( component.status === Component.Error )
                console.debug("Error:"+ component.errorString() );
            return; // or maybe throw
        }
        var channels =component.createObject(channelParent, { "id": channelType+(i+1), "channelText.text": channelMapping[i]});
    }

[EDIT 2] Although the solution in EDIT 1 works and produces the right children for my ScrollView I don't think it is good enough and I believe the best implementation would be to bind the model's size change with the call to the createStrips(countPWM) function. Something like:

\\main.qml
ConfiguredChannels{
id: configuredList
anchors{
    left: parent.left
    top: devices.bottom
    right: tabs.left
    bottom: parent.bottom
}
onModelChanged: tabs.createStrips(model.rowCount) //or an appropriate signal handler defined on C++ side
}

And perhaps even better, make the creation of the children as a custom qml signal handler that will be emitted every time the model's size is changed. (I tried the onModelChanged as above but didn't work. Probably I am missing in which case is this signal emitted)

[SOLUTION]

I followed the instructions of the accepted answer as well as this link.

I added a Q_PROPERTY to my model's definition inside the header file named rowCount with the NOTIFY rowCountChanged as well as the signal void rowCountChanged(); . Also, inside the function setList(newList) which I use to update the model, I added at the end of its implementation the emit rowCountChanged(); . Last I connected this signal with my function createStrips(count) inside QML. Now every time the model's size is changed, my ScrollView will update automatically the strips shown as the RowLayout's children.

\\ChannelModel.h
...
Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged)
...
signals:
void rowCountChanged();

\\ChannelModel.cpp
void ChannelModel::setList(ChannelList *list)
{
beginResetModel();
...
endRestModel();
emit rowCountChanged();
}

\\main.qml
Connections {
    target: configuredList.model
    onRowCountChanged: tabs.createStrips(configuredList.model.rowCount)
}
like image 780
K.Tsakalis Avatar asked Sep 18 '25 16:09

K.Tsakalis


1 Answers

Only the q-property allow a binding, in your case the Q_INVOKABLE is not, so you will have to create it, for this we use the signal rowsInserted and rowsRemoved as shown below:

*.h

    Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged)
public:
    ...
signals:
    void rowCountChanged();

*.cpp

//constructor
connect(this, &QAbstractListModel::rowsInserted, this, &YourModel::rowCountChanged);
connect(this, &QAbstractListModel::rowsRemoved, this, &YourModel::rowCountChanged);

*.qml

countPWM: configuredList.model.rowCount // without ()

Note:

I'm assuming that when you add an element or remove it, you're using:

beginInsertRows(QModelIndex(), rowCount(), rowCount());
//append data
endInsertRows();

Or:

beginRemoveRows(QModelIndex(), from, to)
// remove
endRemoveRows();
like image 152
eyllanesc Avatar answered Sep 21 '25 05:09

eyllanesc