QAbstractTableModel + QML TableView: How to call setData?



  • Hello there,
    I have a custom model class subclassing the QAbstractTableModel. On the QML side, I have a TableView component. I am trying to connect those 2 things. As for now, I have the TableView reading the data correctly using the data method on C++ side.

    Now, I am editing the cells on the QML side and after I am done, I dont know how to make the TreeView call the setData method. I could call it by hand, but the problem is that it requires a valid QModelIndex object:

    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
    

    I dont know how to create one one the QML side. I think there should be a method to call it indirectly from QML, but I dont know how. I would appreciate all help regarding this issue. This is my QML test component:

    import QtQuick 2.12
    import QtQuick.Controls 2.3
    import QtQuick.Controls 1.4
    import QtGraphicalEffects 1.0
    import "."
    
    Popup
    {
    	id: thePopup;
    	modal: true;
    	focus: true;
    	closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside;
    
    	leftPadding: 0;
    	rightPadding: 0;
    	topPadding: 0;
    	bottomPadding: 0;
    
    	property real closeMargin: 20;
    	property real localScale: 1;
    	property real fontSizeVal: 15;
    	property Ucolors cg;
    	property variant tableModel;
    
    	Rectangle
    	{
    		id: popRect;
    
    		width: parent.width;
    		height: parent.height;
    		color: cg.canvas;
    
    		TableView
    		{
    			anchors.fill: parent;
    			model: tableModel;
    
    			// TEMP solution
    			TableViewColumn {title: "1"; role: tableModel ? tableModel.role(0) : ""; width: 70 }
    			TableViewColumn {title: "2"; role: tableModel ? tableModel.role(1) : "";  width: 70   }
    			TableViewColumn {title: "3"; role: tableModel ? tableModel.role(2) : "";  width: 70 }
    
    			itemDelegate: Rectangle
    			{
    				id: delegateRec;
    				color: colGlob.canvas;
    
    				Text
    				{
    					id: theCellText;
    
    					anchors
    					{
    						verticalCenter: parent.verticalCenter;
    						left: parent.left;
    						margins: 10;
    					}
    
    					//color: colGlob.text;
    					text: tableModel ? styleData.value : "";
    					//font.pixelSize: fontSize;
    					//font.family: Uconsts.roboFont.name;
    				}
    
    				MouseArea
    				{
    					id: cellMouseArea
    
    					anchors.fill: parent;
    					onClicked:
    					{
    						theCellText.visible = false;
    						theLoader.visible = true;
    						theLoader.item.forceActiveFocus();
    					}
    				}
    
    				Loader
    				{
    					id: theLoader;
    
    					anchors
    					{
    						verticalCenter: parent.verticalCenter;
    						left: parent.left;
    						margins: 10;
    					}
    
    					height: parent.height;
    					width: parent.width;
    					visible: false;
    					sourceComponent: visible ? theInputComp : undefined;
    
    					Component
    					{
    						id: theInputComp;
    
    						TextInput
    						{
    							id: textInputId;
    							anchors.fill: parent;
    
    							onEditingFinished:
    							{
    								console.log("Edited");
    								theLoader.visible = false;
    								theCellText.visible = true;
    
    								// TODO how to call setData?
    							}
    						}
    					}
    				}
    			}
    		}
    	}
    }
    

    I also dont know how to force the QML TreeView to call the C++ headerData method, but this is of less importance than setData...
    I would appreciate all help.


  • Moderators

    @Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:

    Now, I am editing the cells on the QML side and after I am done, I dont know how to make the TreeView call the setData method.

    it's a long time ago since i tried it myself, but wit worked when you assign a value to the role name variable (the same variable you would use when you would display a value for a specific role)
    Did you reimplement QAbstractItemModel::roleNames() in your custom model?



  • @raven-worx Hi, thanks for answer. yes, I do re implement it. Its used in order for the data to work. Your answer is similar to this: https://stackoverflow.com/questions/56441036/call-qabstracttablemodel-setdata-method-from-qml?noredirect=1#comment99475440_56441036 But I simply cannot get ahead of it without an example. Doing:

    onEditingFinished:
    {
    	theLoader.visible = false;
    	theCellText.visible = true;
    	tableModel.roleName = text; // or model.roleName = text;
    }
    

    Doesnt work.
    So far I have utilized the following hack: I added a setData overload method:

    /**
     * @brief	A wrapper for actual \ref setData method subclassed from
     *			QAbstractTableModel class. It is needed whenever a QModelIndex is
     *			not available (ie. QML side).
     * @param	row: cell row number.
     * @param	column: cell column number.
     * @param	value: value to assign.
     * @return	Non zero on success.
     */
    bool CVarTableModel::setData(const int row, const int column, const QVariant& value)
    {
    	return setData(index(row, column), value);
    }
    

    And I call it like this on QML side:

    onEditingFinished:
    {
    	theLoader.visible = false;
    	theCellText.visible = true;
    	tableModel.setData(styleData.row,  styleData.column, text);
    }
    

    It does work, but now, for some reason, after the dataChanged is emited inside the actual setData override, the cell on QML side does not update...


  • Qt Champions 2018

    Starting from this example: https://wiki.qt.io/How_to_Use_a_Custom_Class_in_C%2B%2B_Model_and_QML_View

    Changing the delegate to something like:

    delegate: TextInput  {
                    text: edit.name
                    onEditingFinished: {
                        edit.name = text
                    }
                }
    

    is enough.
    Regarding how it works, see the very last paragraph of the wiki



  • @VRonin Hi, Thanks for answer. I have no idea where does 'edit` id come from, even after reviewing your link...


  • Qt Champions 2018

    From https://wiki.qt.io/How_to_Use_a_Custom_Class_in_C%2B%2B_Model_and_QML_View

    accessing our data is very easy we just use the roleName.propertyName syntax. Since we saved our data in Qt::EditRole we used edit as role name (a list of the default role names is In QAbstractItemModel's documentation)

    basically edit is the name of the role as returned by QAbstractItemModel::roleNames(). The example doesn't overload it so the default name coresponding to Qt::EditRole is used



  • @VRonin Thanks for clarification, this gets complicated. My override looks as follows:

    /**
     * @brief	Obtains the role names needed on the QML side.
     * @return	A hash of role names.
     */
    QHash<int, QByteArray> CVarTableModel::roleNames() const
    {
    	QHash<int, QByteArray> roles;
    	const int colCount = columnCount();
    
    	for (int col = 0; col < colCount; col++)
    		roles[Qt::UserRole + col] = role(col).toByteArray();
    
    	return roles;
    }
    

    And the role method is:

    /**
     * @brief	Obtains the role name string assiociated to the provided \p column
     * @param	column: Column number/ index.
     * @return	Role name string (as Qvariant) in case \p column is valid.
     */
    QVariant CVarTableModel::role(const int column) const
    {
    	if (!isColumnValid(column))
    		return QString();
    
    	return QVariant::fromValue(static_cast<Columns>(column));
    }
    

    Columns is a simple enum:

    	/**
    	 * @brief	An enumeration class providing the columns and the amount of
    	 *			columns as well (use ZCOUNT as always last member).
    	 */
    	enum class Columns
    	{
    		Name = 0,
    		Unit,
    		Value,
    
    		ZCOUNT,
    	};
    	Q_ENUM(Columns)
    

    So my role names would be Name, Unit and Value. How would this be consistent with the edit way if its custom?
    I would appreciate further help.


  • Qt Champions 2018

    So my role names would be Name, Unit and Value. How would this be consistent with the edit way if its custom?

    easy, replace edit with Name, Unit or Value. For example, if the Name role will contain a QString you can use:

    delegate: TextInput  {
                    text: Name
                    onEditingFinished: {
                        Name = text
                    }
                }
    

    My override looks as follows:

    This is a very convoluted way to go about it. You can just use QMetaEnum::fromType<Derived::Columns>().valueToKeys(col);

    for (int col = 0; col < colCount; col++)

    Looks like columns and roles are the same thing here but since 1 year ago they are now 2 distinct things (as they are in QtWidgets): https://blog.qt.io/blog/2018/08/29/tableview/



  • @VRonin Thank you for follow-up. As for the method optimization, I will get back to that. But for the main problem, I did:

    Component
    {
    	id: theInputComp;
    
    	TextInput
    	{
    		id: textInputId;
    		anchors.fill: parent;
    
    		onEditingFinished:
    		{
    			theLoader.visible = false;
    			theCellText.visible = true;
    			//tableModel.setData(styleData.row,
    							   //styleData.column, text);
    			Name = text;
    		}
    	}
    }
    

    And received: qrc:/UBasicTable.qml:109: Error: Invalid write to global property "Name"
    Am I missing something?


  • Qt Champions 2018

    Name might be a reserved word. Try changing Name = 0, to colName = 0, and use text: colName and colName = text



  • @VRonin Doesnt matter how I call it:

    qrc:/UBasicTable.qml:109: Error: Invalid write to global property "colName"
    qrc:/UBasicTable.qml:109: Error: Invalid write to global property "colName1" // this doesnt exist
    

    Any other hints :/?



  • Just BTW... Figured out why doesnt the dataChanged signal fire the table update. I was calling it like this:

    emit dataChanged(index, index, {role});
    

    and role was always Qt::EditRole. Now I call it without roles parameter:

    emit dataChanged(index, index);
    

    And the TableView cells are being updated! Thanks for the help @VRonin, it led me to the right place for this one.
    I still havent figured out how to automatically call setData from the QML though (using the presented workaround). If you have any more ideas please let me know.


  • Qt Champions 2018

    Are you sure you are inside delegate:?
    This is a minimal working example:

    #include <QGuiApplication>
    #include <QQmlContext>
    #include <QQmlApplicationEngine>
    #include <QStringListModel>
    #include <QDebug>
    class MyModel : public QStringListModel{
    public:
        MyModel(QObject *parent = nullptr) : QStringListModel(parent)
        {}
        QHash<int, QByteArray> roleNames() const override
        {
            QHash<int, QByteArray> roles;
    
                roles[Qt::EditRole] = QByteArrayLiteral("TestRole");
                roles[Qt::DisplayRole] = QByteArrayLiteral("AnotherRole");
    
            return roles;
        }
    };
    
    int main(int argc, char *argv[])
    {
    #if defined(Q_OS_WIN)
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    #endif
        QGuiApplication app(argc, argv);
        QQmlApplicationEngine engine;
        MyModel model;
        QObject::connect(&model,&MyModel::dataChanged,[](const QModelIndex& idx){
            qDebug() << "Changed: " << idx.row() << " " << idx.data().toString();
        });
        model.setStringList(QStringList{"aaa","bbb","ccc"});
        engine.rootContext()->setContextProperty("mymodel", &model);
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
        return app.exec();
    }
    
    import QtQml 2.2
    import QtQuick 2.7
    import QtQuick.Window 2.2
    import QtQuick.Controls 2.3
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Test Edit")
    
            ListView {
                anchors.fill: parent;
                width: 200; height: 250
                model: mymodel
                delegate: TextInput  {
                    text: TestRole
                    onEditingFinished: {
                        TestRole = text
                    }
                }
            }
    }
    


  • @VRonin Hmmm... Here is the thing- I am inside itemDelegate not delegate. delegate is available in different QtQuick.Controls version. If I switch to it, I will no longer have my other components available, ie Popup. Isnt itemDelegate and delegate the same thing?

    Look at the docs, TableView has no delegate member: https://doc.qt.io/qt-5/qml-qtquick-controls-tableview.html#rowDelegate-prop


  • Qt Champions 2018

    Looks like you are still using Qt Quick controls 1.
    Qt Quick controls 2 is already 3 years old.

    If I switch to it, I will no longer have my other components available, ie Popup

    Qt Quick Controls 2 has https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html



  • @VRonin QtQuick.Controls 2.5 do have delegate but dont have TableViewColumn which I am using for columns setting. Is there a better way now?


  • Qt Champions 2018

    @Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:

    Is there a better way now?

    @VRonin said in QAbstractTableModel + QML TableView: How to call setData?:

    Looks like columns and roles are the same thing here but since 1 year ago they are now 2 distinct things (as they are in QtWidgets): https://blog.qt.io/blog/2018/08/29/tableview/



  • @VRonin Ok... So now everything I got ahead of with styleData is no longer working. This time I am really confused. Those links threat about animations the most and what I need for now is to be able to send/ receive data from the C++ model. Is there any example for that?


  • Qt Champions 2018

    @Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:

    So now everything I got ahead of with styleData is no longer working.

    What is styleData?



  • @VRonin It was available in itemDelegate: https://doc.qt.io/qt-5/qml-qtquick-controls-tableview.html#itemDelegate-prop
    One could use it to locate itself in the table as well as read model data.


  • Qt Champions 2018

    @Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:

    One could use it to locate itself in the table as well as read model data.

    now you don't need it anymore. you just access role names directly to read data and you can use index, row and column directly to locate yourself within the model

    Btw, this doesnt call setData:

    Yes it does. If the dataChanged signal is not enough to convince you you can add the below to MyModel

    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override{
            qDebug() << "Called setData";
            return QStringListModel::setData(index,value,role);
        }
    


  • @VRonin Ok, you are right! Thanks.
    It feels like we are really close this time. So this is the components now:

    		TableView
    		{
    			anchors.fill: parent;
    			model: tableModel;
    
    			delegate: TextInput
    			{
    				text: Name;
    
    				onEditingFinished:
    				{
    					Name = text
    				}
    			}
    		}
    

    My columns and roles are still mixed together because of the obsolete behavior- this needs to be disconnected. In this case, what should the roleNames override method return actually?


  • Qt Champions 2018

    @Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:

    hat should the roleNames override method return actually?

    I would not override it at all and just use the default edit

    				text: edit
    
    				onEditingFinished:
    				{
    					edit = text
    				}
    


  • @VRonin , yeah I just came to realize that too. I did:

    delegate: TextInput
    			{
    				text: display;
    
    				onEditingFinished:
    				{
    					edit = text
    				}
    			}
    

    Which I believe in this context gives the same effect.
    Alright... Thank you very much for your feedback, you helped me a lot.
    Now I only need to figure out either headerData can be utilized and that would be it.


  • Qt Champions 2018

    @Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:

    Now I only need to figure out either headerData can be utilized and that would be it.

    I'm afraid that part has not been developed by Qt yet

    We’re also working on a TableHeader, TableModel, as well as a DelegateChooser.



  • @VRonin So as for now, is there a way to set the column header at all?


  • Qt Champions 2018

    Not exactly what I would call a header. There's this: https://doc.qt.io/qt-5/qml-qtquick-tableview.html#overlays-and-underlays but it's far from anything that is actually useful



  • @VRonin Yes, I have just checked this. It overlaps my first row... Do you know either rows inserting and removing work? Meaning- If I remove or insert a row at runtime, will it update the view?
    This seems to still be a very beta thing, I wonder either old or new controls should be used.


  • Moderators

    @Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:

    I also dont know how to force the QML TreeView to call the C++ headerData method

    since you use QtQuick.Controls 1 TableView you need to use the TableViewColumn element, which only supports roles.
    So you can add custom role names and forward those header roles to the headerData() method in the data() method.

    virtual QHash<int, QByteArray> roleNames() const {
        QHash<int, QByteArray> rn = QAbstractItemModel::roleNames();
        rn[MyHeaderRole1] = QByteArrayLiteral("header1");
        return rn;
    }
    
    QVariantdata(const QModelIndex &index, int role = Qt::DisplayRole) const {
         switch( role )
         {
            case MyHeaderRole1:  return headerData(0, Qt::Horizontal);
            ...
         }
         return QVariant();
    }
    


  • @raven-worx thanks for answer. The thing is that at this point I am not certain anymore which version TableView I should opt for. On one hand I dont want to use the obsolete component which has columns and roles functionality mixed, and on the other this.new one doesnt seem to be ready for usage.


  • Moderators

    @Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:

    and on the other this.new one doesnt seem to be ready for usage.

    Based on the example from the docs (untested though):

    TableView {
        id: tableView
    
        columnWidthProvider: function (column) { 
                var colWidth = ...;
                var headerItem = headerRepeater.itemAt(column)
                if( headerItem )
                    headerItem.width = colWidth;
                return colWidth; 
          }
    
        topMargin: header.height
    
        Row {
            id: header
            height: 40
            width: implicitWidth
            padding: 0
            spacing: 0
    
            Repeater {
                id: headerRepeater
                model: tableView.columns 
                Item {
                       height: parent.height
                      // your column delegate
                }
            }
        }
    }
    

    Could be easier/built-in indeed, but i think this should work (with minor adaptions probably).

    QAbstractItemModel::headerData() is directly invokable from within QML


  • Qt Champions 2018

    @Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:

    On one hand I dont want to use the obsolete component which has columns and roles functionality mixed, and on the other this.new one doesnt seem to be ready for usage.

    I'm not aware of a possibility to use headers directly even in the old tebleview



  • @raven-worx Thanks for answer, but I am really lost at this one. What is supposed to be my column delegate?

    I am playing with this new TableView for over an hour now and I am not even able to set variable columns width (when the container width change). I wonder either this is a bug or a feature.



  • @VRonin But would you say the new one or old one is more fit to use...?


  • Moderators

    @Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:

    What is supposed to be my column delegate?

    what every you want it to look like. A Rectangle with a Text inside maybe?



  • @raven-worx I tried a simple Text component and all that was visible was a single text (not sure either they were stacked one on another).

    Arent you guys aware any single code example showing how to do this all from ground up?


  • Qt Champions 2018

    I understand and share your frustration on the state of views in QML, development has been focused on components that work well on mobile platforms (as QtWidgets is not really an option there) so views have been neglected.

    @Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:

    Do you know either rows inserting and removing work?

    It does, the example I linked actually does it

    I have just checked this. It overlaps my first row

    looks like you forgot the topMargin: header.implicitHeight part


  • Moderators

    @Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:

    I tried a simple Text component and all that was visible was a single text (not sure either they were stacked one on another).

    maybe there is only 1 column?
    is your model's columnCount() method called?



  • @raven-worx It is. I will give it another go, thank you.



  • @VRonin Thanks for answer. I did snap a little bit, sorry for that...
    I will give the new TableView another go according to yours and @raven-worx hints but as for now I think the Version 1 is more complete. The only lacking thing is to make the TreeView to call setData automatically (I only got this working in the new version). In the 1st version I am still calling a wrapper for it.

    Thank you for help guys.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.