QAbstractTableModel + QML TableView: How to call setData?
-
Hello there,
I have a custom model class subclassing theQAbstractTableModel
. On the QML side, I have aTableView
component. I am trying to connect those 2 things. As for now, I have the TableView reading the data correctly using thedata
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 validQModelIndex
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 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? -
@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 asetData
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 actualsetData
override, 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 -
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 inQt::EditRole
we usededit
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 byQAbstractItemModel::roleNames()
. The example doesn't overload it so the default name coresponding toQt::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
andValue
. How would this be consistent with theedit
way 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
edit
withName
,Unit
orValue
. For example, if theName
role will contain aQString
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? -
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 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 callsetData
from 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 } } } }
-
@VRonin Hmmm... Here is the thing- I am inside
itemDelegate
notdelegate
.delegate
is available in differentQtQuick.Controls
version. If I switch to it, I will no longer have my other components available, iePopup
. IsntitemDelegate
anddelegate
the same thing?Look at the docs,
TableView
has nodelegate
member: 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
-
@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?