QAbstractTableModel + QML TableView: How to call setData?
-
Hello there,
I have a custom model class subclassing theQAbstractTableModel. On the QML side, I have aTableViewcomponent. I am trying to connect those 2 things. As for now, I have the TableView reading the data correctly using thedatamethod 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
setDatamethod. I could call it by hand, but the problem is that it requires a validQModelIndexobject: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++
headerDatamethod, but this is of less importance thansetData...
I would appreciate all help. -
Hello there,
I have a custom model class subclassing theQAbstractTableModel. On the QML side, I have aTableViewcomponent. I am trying to connect those 2 things. As for now, I have the TableView reading the data correctly using thedatamethod 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
setDatamethod. I could call it by hand, but the problem is that it requires a validQModelIndexobject: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++
headerDatamethod, but this is of less importance thansetData...
I would appreciate all help.@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 reimplementQAbstractItemModel::roleNames()in your custom model? -
@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 reimplementQAbstractItemModel::roleNames()in your custom model?@raven-worx Hi, thanks for answer. yes, I do re implement it. Its used in order for the
datato 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 asetDataoverload 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
dataChangedis emited inside the actualsetDataoverride, the cell on QML side does not update... -
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 -
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 -
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.propertyNamesyntax. Since we saved our data inQt::EditRolewe usededitas role name (a list of the default role names is In QAbstractItemModel's documentation)basically
editis the name of the role as returned byQAbstractItemModel::roleNames(). The example doesn't overload it so the default name coresponding toQt::EditRoleis used -
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.propertyNamesyntax. Since we saved our data inQt::EditRolewe usededitas role name (a list of the default role names is In QAbstractItemModel's documentation)basically
editis the name of the role as returned byQAbstractItemModel::roleNames(). The example doesn't overload it so the default name coresponding toQt::EditRoleis 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
rolemethod 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)); }Columnsis 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,UnitandValue. How would this be consistent with theeditway if its custom?
I would appreciate further help. -
@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
rolemethod 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)); }Columnsis 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,UnitandValue. How would this be consistent with theeditway if its custom?
I would appreciate further help.So my role names would be Name, Unit and Value. How would this be consistent with the edit way if its custom?
easy, replace
editwithName,UnitorValue. For example, if theNamerole will contain aQStringyou 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/
-
So my role names would be Name, Unit and Value. How would this be consistent with the edit way if its custom?
easy, replace
editwithName,UnitorValue. For example, if theNamerole will contain aQStringyou 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? -
Namemight be a reserved word. Try changingName = 0,tocolName = 0,and usetext: colNameandcolName = text -
Just BTW... Figured out why doesnt the
dataChangedsignal fire the table update. I was calling it like this:emit dataChanged(index, index, {role});and
rolewas alwaysQt::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 callsetDatafrom the QML though (using the presented workaround). If you have any more ideas please let me know. -
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 } } } } -
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
itemDelegatenotdelegate.delegateis available in differentQtQuick.Controlsversion. If I switch to it, I will no longer have my other components available, iePopup. IsntitemDelegateanddelegatethe same thing?Look at the docs,
TableViewhas nodelegatemember: https://doc.qt.io/qt-5/qml-qtquick-controls-tableview.html#rowDelegate-prop -
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
-
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.5do havedelegatebut dont haveTableViewColumnwhich I am using for columns setting. Is there a better way now?@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/
-
@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
styleDatais 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? -
@VRonin Ok... So now everything I got ahead of with
styleDatais 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? -
@Bremenpl said in QAbstractTableModel + QML TableView: How to call setData?:
So now everything I got ahead of with
styleDatais no longer working.What is
styleData?