Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Use setData() from QML



  • Hi community,
    I reimplemented QAbstractListModel in order to display a list of my custom objects.
    C++ side works fine, but I have some problems using setData(), data() and others function directly from QML.
    What am I doing wrong? Below my relevant code.

    DataModel.cpp

    DataModel::DataModel(QObject *parent)
        : QAbstractListModel(parent)
    {
        for (int i = 1; i < 8; i++)
            mList.append({ i, 0, 0.0, 0, false });
    
        setData(index(3,0), true, PlottedRole);
    }
    
    Qt::ItemFlags DataModel::flags(const QModelIndex &index) const
    {
        if (!index.isValid())
            return Qt::NoItemFlags;
    
        return Qt::ItemIsEditable | Qt::ItemIsUserCheckable;
    }
    
    QVariant DataModel::data(const QModelIndex &index, int role) const
    {
        if (!index.isValid())
            return QVariant();
    
        if (index.row() >= mList.size())
            return QVariant();
    
        const DataItem item = mList.at(index.row());
    
        switch (role)
        {
        case ChannelRole:
            return QVariant(item.channel);
        case SensorRole:
            return QVariant(item.sensor);
        case TemperatureRole:
            return QVariant(item.temperature);
        case AlarmRole:
            return QVariant(item.alarm);
        case PlottedRole:
            return QVariant(item.plotted);
        default:
            return QVariant();
        }
    
        return QVariant();
    }
    
    bool DataModel::setData(const QModelIndex &index, const QVariant &value, int role)
    {
        if (data(index, role) != value)
        {
            DataItem item = mList.at(index.row());
    
            switch (role)
            {
            case ChannelRole:
                item.channel = value.toInt();
                break;
            case SensorRole:
                item.sensor = value.toInt();
                break;
            case TemperatureRole:
                item.temperature = value.toDouble();
                break;
            case AlarmRole:
                item.alarm = value.toInt();
                break;
            case PlottedRole:
                item.plotted = value.toBool();
                break;
            default:
                return false;
            }
    
            mList.replace(index.row(), item);
    
            emit dataChanged(index, index, QVector<int>() << role);
            return true;
        }
        return false;
    }
    
    int DataModel::rowCount(const QModelIndex &parent) const
    {
        if (parent.isValid())
            return 0;
    
        return mList.size();
    }
    
    bool DataModel::insertRows(int row, int count, const QModelIndex &parent)
    {
        if (row < 0 || row >= mList.size())
            return false;
    
        beginInsertRows(parent, row, row + count - 1);
    
        for (int i = 0; i < count; i++)
            mList.insert(row, { row, 0, 0.0, 0, false });
    
        endInsertRows();
    
        return true;
    }
    
    bool DataModel::removeRows(int row, int count, const QModelIndex &parent)
    {
        if (row < 0 || row >= mList.size() || count > mList.size())
            return false;
    
        beginRemoveRows(parent, row, row + count - 1);
    
        for (int i = 0; i < count; i++)
            mList.removeAt(row);
    
        endRemoveRows();
    
        return true;
    }
    
    QHash<int, QByteArray> DataModel::roleNames() const
    {
        QHash<int, QByteArray> roles;
        roles[ChannelRole] = "channel";
        roles[SensorRole] = "sensor";
        roles[TemperatureRole] = "temperature";
        roles[AlarmRole] = "alarm";
        roles[PlottedRole] = "plotted";
    
        return roles;
    }
    

    DataList.qml

    GroupBox {
        id: root
        title: qsTr("<b> CHANNEL DATA</b>")
        implicitWidth: 1180
        implicitHeight: 500
    
        ColumnLayout {
            anchors.fill: parent
    
            DataHeader {
                id: header
                Layout.fillWidth: true
    
                onHeaderPlotChecked: {
                    console.log(dmodel.index(3,1)); // THIS NOT WORK
                }
            }
    
            ListView {
                id: listView
                clip: true
                Layout.fillWidth: true
                Layout.fillHeight: true
    
                delegate: DataDelegate {
                    id: ddelegate
                    width: listView.width
                }
    
                model: DataModel {
                    id: dmodel
                }
    
                ScrollIndicator.vertical: ScrollIndicator {}
            }
        }
    }
    

    DataDelegate.qml

    ItemDelegate {
        id: root
    
        signal delegatePlotChecked(int row, bool checked)
    
        RowLayout {
            anchors.fill: parent
            spacing: 0
    
            Frame {
                Label {
                    text: channel
                    ...
                }
            }
    
            Frame {
                Label {
                    text: sensor
                    ...
                }
            }
    
            Frame {
                Label {
                    text: temperature
                    ...
                }
            }
    
            Frame {
                Label {
                    text: alarm
                    ...
                }
            }
    
            Frame {
                CheckBox {
                    checked: plotted
                    ...
                    onCheckStateChanged: root.delegatePlotChecked(index, checked)
                }
            }
        }
    }
    

  • Moderators

    ... and your problem is? Your code looks OK. If the delegate does not see the data (since it's in another file), you can try model.channel instead of channel.



  • @sierdzio
    First of all, thanks for your quick response.
    The problem is that I don't know how to use setData, index, or others model-related functions from QML. When I try console.log(model.channel) I alwasy get undefined.

    My final goal is to check/uncheck all the delegates' checkboxes when the header checkbox has been checked. My idea was somethig similar

    onHeaderPlotChecked: {
        console.log("check all " + checked);
       
        for (var i = 0; i < model.rowCounts(); < i++) {
            var index = model.index(i, 1); // get row
            model.setData(index, checked, model.PlottedRole); // replace data
        }
    }
    

  • Moderators

    The standard approach in QML is to not use data() and setData(). You should use model.roleName instead - bot for reading and writing.

    If you have to use setData(), this should work:

    modelId.setData(modelId.index(row, column), value, role)
    

    You need to create the index from the model. Using just integer won't work.



  • @sierdzio said in Use setData() from QML:

    The standard approach in QML is to not use data() and setData(). You should use model.roleName instead - bot for reading and writing.

    Ok, got it. But something is not working and I don't understand where and why.
    When I read data using model.plotted or similar, I always get undefined.
    Instead, when I try to replace some data, e.g. with model.plotted = true, the function setData() is never entered (I observed that on debug).

    If you have to use setData(), this should work:

    modelId.setData(modelId.index(row, column), value, role)
    

    You need to create the index from the model. Using just integer won't work.

    I've tried also this approach. In my previous response I used var index = model.index(i, 1) to iterate through indexes, but without success.

    May be a problem that my DataDelegate is in another QML file?



  • DONE!!
    I was picking an invalid index using 1 instead of 0 for column. Also the role values was wrong.
    Here below the "solution".

    // Read
    dmodel.data(dmodel.index(n, 0), DataModel.PlottedRole)
    
    // Write: 
    dmodel.setData(dmodel.index(n, 0), checked, DataModel.PlottedRole)
    

    Last question. By using index(row, column) is the only way to access to the n item of the list? Or I should implement my "custom getter" like this?

    DataItem DataModel::get(int row)
    {
        return mList.at(row);
    }
    


  • This post is deleted!

  • Moderators

    @ed_robcont_ said in Use setData() from QML:

    DONE!!
    I was picking an invalid index using 1 instead of 0 for column. Also the role values was wrong.

    Thanks for posting.

    DataItem DataModel::get(int row)
    {
        return mList.at(row);
    }
    

    Since you're not using the standard approach anyway, I'd say it's up to you. A simple getter like this makes absolute sense.



  • @sierdzio

    It's working, so it's ok for me. Anyway, I'm still not understanding how the standard approach works.

    Task: Modify the temperature field in row 3 when a signal is emitted from another QML component.
    How to access this field in the standard way, without using setData()?


  • Moderators

    @ed_robcont_ said in Use setData() from QML:

    Task: Modify the temperature field in row 3 when a signal is emitted from another QML component.
    How to access this field in the standard way, without using setData()?

    Signal emitted from QML should go to your model. Then model should emit dataChanged() and this will update all affected delegates on QML side.



  • @sierdzio said in Use setData() from QML:

    Signal emitted from QML should go to your model. Then model should emit dataChanged() and this will update all affected delegates on QML side.

    Ok, it works, thank you!

    One more thing, it'll be the last I promise.
    Actually, DataModel is the owner of my list. What I want is to add it from another class, i.e. DataServer. I tried this code but is not working. Any suggestion?

    @DataServer.cpp

    void DataServer::showList()
    {
        QVector<DataItem*> list;
        list.append(new DataItem(...);
        list.append(new DataItem(...);
        list.append(new DataItem(...);
    
        emit listChanged(QVariant::fromValue(list));
    }
    

    @DataModel

    void DataModel::setList(QVariant list)
    {
        m_list = list;
    }
    

    @QML

    Button {
        id: btn
        onPressed: dserver.showList()
    }
    
    DataServer {
        id: dserver
        onListChanged: dmodel.setList(list)
    }
    

  • Moderators

    void DataServer::setList(QVariant list)
    {
    m_list = list;
    }

    You don't have emit dataChanged() here. Or, in this case, you should probably do:

    beginResetModel();
    m_list = list;
    endResetModel();
    


  • @sierdzio said in Use setData() from QML:

    You don't have emit dataChanged() here. Or, in this case, you should probably do:

    beginResetModel();
    m_list = list;
    endResetModel();
    

    Yes. No signal dataChanged() should be emitted.
    I did the same you suggested, but I've getting this error.

    qrc:/main.qml:21: Error: Unknown method parameter type: QVector<DataItem*>


  • Done, by inserting rows one by one.


Log in to reply