Getting data from C++ model inside delegate - c++

I have a model on C++ end and a treeview
TreeView {
id: view
itemDelegate: Rectangle{
property int indexOfThisDelegate: model.index
Text {
text:???
font.pixelSize: 14
}
}
model: myModel
onCurrentIndexChanged: console.log("current index", currentIndex)
TableViewColumn {
title: "Name"
role: "type"
resizable: true
}
onClicked: console.log("clicked", index)
onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index)
}
How can I get data from my TreeItem? The problem is that indexOfThisDelegate is integer instead of QModelIndex, so
I would like to have something like
Text {
text:model.getDescription(currentlyPaintedModelIndex)
font.pixelSize: 14
}
or should I have a mapping between integer and tree QModelIndex?

Ok, figured it out myself
In the model:
QHash<int, QByteArray> MenuTreeModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[TitleRole] = "Title";
return roles;
}
// of course it could me more complex with many roles
QVariant MenuTreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
MenuTreeItem *item = itemForIndex(index);
if (role != TitleRole)
{
return QVariant();
}
QString str = item->data(index.column()).toString();
return item->data(index.column());
}
Our custom tree item (for example):
class MenuTreeItem
{
// item data, contains title
QList<QVariant> m_itemData;
};
In qml:
TreeView {
id: view
itemDelegate: Rectangle{
Text {
text:model.Title
font.pixelSize: 14
}
}
model: myModel
}

Related

How to show large data set in qml ListView

Is it legal to write a list model which returns large data set for listview ?
Say a c++ model like:
int TestModel::rowCount(const QModelIndex &parent) const
{
return 50000000;
}
QVariant TestModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
if (role == 0x100) return row;
if (role == Qt::SizeHintRole) return QSize(300,30);
return QString("hello %1").arg(row);
}
QHash<int, QByteArray> TestModel::roleNames() const
{
return QHash<int, QByteArray>({
{0x100, "line"},
{0x101, "log"}
});
}
Qml:
Item {
Component {
id: simple
Rectangle{
height: 10
width: 10
color: 'blue'
}
}
ListView {
id: viewer
anchors.fill: parent
model: testmodel
delegate: simple
ScrollBar.vertical: ScrollBar{
}
}
}
The memory, cpu, rendering...everything goes fine:
But, when scroll to about 3500000 lines:
Some space appear between few lines.
Is there a Qt bug, or I'm making some mistakes ?

From random function in cpp to qml

I have a class and I can get data from the constructor to show it in the TableView in qml, but the problem is when I want to show data from anoher function from that class. For example:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQmlContext>
#include "myclass.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
MyModel *myTable=new MyModel;
engine.rootContext()->setContextProperty("myTable", myTable);
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
MyModel.h
struct Info
{
QString name;
QString address;
QString values;
QString salary;
};
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
MyModel(QObject *parent=nullptr);
.
////some variables declared////
.
Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override;
Q_INVOKABLE int columnCount(const QModelIndex &parent = QModelIndex()) const override;
Q_INVOKABLE QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override;
Q_INVOKABLE QHash<int,QByteArray> roleNames() const override;
private:
QList<Votes> m_model;
MyModel.cpp
MyModel::MyModel(QObject *parent)
: QAbstractTableModel{parent}
{
m_model.append({"name1","add1","value1","salary1"});
m_model.append({"name2", "add2","value2","salary2"});
m_model.append({"name3","add3","value3","salary3"});
} ////I can see this in TableView
void MyModel::mapTable(QVector<QStringList> mapGroup)
{
for(int i=0;i<mapGroup.count();i++)
{
m_model.append ({mapGroup[i][0], mapGroup[i][1], mapGroup[i][2], mapGroup[i][3});
}
} ////I cannot see this in TableView
...
MyModel.qml
import QtQuick 2.0
import QtQuick.Controls 1.4 as CI
CI.TableView
{
model: myTable
CI.TableViewColumn
{
role: "name"
title:"Name"
width: 100
}
CI.TableViewColumn
{
role: "add"
title:"Address"
width: 100
}
CI.TableViewColumn
{
role: "value"
title:"Value"
width: 100
}
CI.TableViewColumn
{
role: "salary"
title:"Salary"
width: 100
}
}
Dialog.qml
...
Rectangle
{
id: tableRec
width: parent.width/1.5
height: parent.height/2
MyModel
{
id: infoTable
width: tableRec.width
height: tableRec.height
anchors.fill: parent
rowDelegate: Rectangle
{
id: rowTable
height: 15
SystemPalette
{
id: myPalette
colorGroup: SystemPalette.Active
}
color:
{
var baseColor = styleData.alternate?myPalette.alternateBase:myPalette.base
return styleData.selected?myPalette.highlight:baseColor
}
}
itemDelegate: Item
{
Text
{
// anchors.centerIn: parent
anchors.verticalCenter: parent.verticalCenter
text: styleData.value
font.pixelSize: Math.round(infoTable.height)/16
}
}
}
How can I pass the data from mapTable function to TableView? I get that it has something to do with QAbstractTableModel, but I cannot make it work.
When you create a custom model based on an inner data model, you have to notify the views that you inserted/removed rows.
The model cannot know when you change its inner model. That's why you have to use beginInsertRows and endInsertRow in your mapTable method.
A quick example:
class MyModel: public QAbstractListModel
{
Q_OBJECT
public:
MyModel(QObject* parent=nullptr): QAbstractListModel (parent)
{
innerModel.append("Bob");
innerModel.append("Patrick");
innerModel.append("Alice");
}
Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
if (parent.isValid())
return 0;
return innerModel.size();
}
Q_INVOKABLE int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
if (parent.isValid())
return 0;
return 2;
}
Q_INVOKABLE QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
{
qDebug() << index << role;
switch (role)
{
case Qt::DisplayRole:
case Qt::UserRole + 1:
return innerModel.at(index.row());
case Qt::UserRole + 2:
return QString::number(index.row() + 1);
}
return QVariant();
}
Q_INVOKABLE QHash<int,QByteArray> roleNames() const override
{
QHash<int,QByteArray> roles;
roles.insert(Qt::UserRole + 1, "name");
roles.insert(Qt::UserRole + 2, "index");
return roles;
}
Q_INVOKABLE void add(QString const& name)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
innerModel.append(name);
endInsertRows();
}
private:
QList<QString> innerModel;
};
Item {
anchors.fill: parent
TextEdit {
width: 100
id: name
anchors.left: parent.left
anchors.top: parent.top
}
Button {
text: "save"
onClicked: theModel.add(name.text)
anchors.left: name.right
anchors.top: parent.top
}
ListView {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.top: name.bottom
model: theModel
delegate: Text {
text: name
}
}
}
If you comment beginInsertRows(QModelIndex(), rowCount(), rowCount()); and endInsertRows in the method add, you will not be able to see the new rows int the list because the model cannot know when its inner model has changed.

Edit QStandardItemModel via TableView with Custom Delegate

I have a QStandardItemModel which I display via a QML Table view.
Here is the model:
class mystandardmodel: public QStandardItemModel
{
public:
mystandardmodel();
enum Role {
role1=Qt::UserRole,
role2
};
explicit mystandardmodel(QObject * parent = 0): QStandardItemModel(parent){}
//explicit mystandardmodel( int rows, int columns, QObject * parent = 0 )
// : QStandardItemModel(rows, columns, parent){}
QHash<int, QByteArray> roleNames() const{
QHash<int, QByteArray> roles;
roles[role1] = "one";
roles[role2] = "two";
return roles;
}
};
and this is how the model is displayed using custom delegates:
TableView {
id: tableView2
x: 69
y: 316
width: 318
height: 150
TableViewColumn {
title: "Parameter Name"
role: "one"
}
TableViewColumn {
title: "Value"
role: "two"
delegate: myDelegate
}
model: myTestModel
}
Component {
id: myDelegate
Loader {
property var roleTwo: model.two
sourceComponent: if(typeof(roleTwo)=='boolean') {
checkBoxDelegate}
else { stringDelegate}
}
}
Component {
id: checkBoxDelegate
CheckBox{text: roleTwo}
}
Component {
id: stringDelegate
TextEdit {text: roleTwo}
}
I populated the model like this:
mystandardmodel* mysmodel=new mystandardmodel(0);
QStandardItem* it = new QStandardItem();
it->setData("data1", mystandardmodel::role1);
it->setData(true, mystandardmodel::role2);
it->setCheckable(true);
it->setEditable(true);
mysmodel->appendRow(it);
QStandardItem* it2 = new QStandardItem();
it2->setData("data2",mystandardmodel::role1);
it2->setData("teststring",mystandardmodel::role2);
mysmodel->appendRow(it2);
How can I make the model editable, so that using the checkBox or editing the text is transfered back to the model?
Edit: I tried to follow the suggestion in In QML TableView when clicked edit a data (like excel) and use set model:
Component {
id: myDelegate
Loader {
property var roleTwo: model.two
property int thisIndex: model.index
sourceComponent: if(typeof(roleTwo)=='boolean') {
checkBoxDelegate}
else { stringDelegate}
}
}
Component {
id: checkBoxDelegate
CheckBox{text: roleTwo
onCheckedChanged: {
myTestModel.setData(0,"two",false)
console.log('called',thisIndex)
}
}
}
Component {
id: stringDelegate
TextEdit {text: roleTwo
onEditingFinished: {
myTestModel.setData(thisIndex,"two",text)
console.log('called',thisIndex)
}
}
}
The index is OK, but it seems that it does not have an effect (I added a second TableView with the same model, but the data there does not get updated if I edit it in the first TableView)
You can directly set a value to model.two and that will automatically call setData with the correct role and index:
import QtQuick 2.10
import QtQuick.Controls 2.0 as QQC2
import QtQuick.Controls 1.4 as QQC1
import QtQuick.Layouts 1.3
QQC2.ApplicationWindow {
visible: true
width: 640
height: 480
ColumnLayout {
anchors.fill: parent
Repeater {
model: 2
QQC1.TableView {
Layout.fillWidth: true
Layout.fillHeight: true
QQC1.TableViewColumn {
title: "Parameter Name"
role: "one"
}
QQC1.TableViewColumn {
title: "Value"
role: "two"
delegate: Loader {
property var modelTwo: model.two
sourceComponent: typeof(model.two) ==='boolean' ? checkBoxDelegate : stringDelegate
function updateValue(value) {
model.two = value;
}
}
}
model: myModel
}
}
}
Component {
id: checkBoxDelegate
QQC1.CheckBox {
text: modelTwo
checked: modelTwo
onCheckedChanged: {
updateValue(checked);
checked = Qt.binding(function () { return modelTwo; }); // this is needed only in QQC1 to reenable the binding
}
}
}
Component {
id: stringDelegate
TextEdit {
text: modelTwo
onTextChanged: updateValue(text)
}
}
}
And if that's still too verbose and not enough declarative for you (it is for me), you can use something like the following, where most of the logic is in the Loader and the specifics delegates just inform what is the property where the value should be set and updated from :
delegate: Loader {
id: loader
sourceComponent: typeof(model.two) ==='boolean' ? checkBoxDelegate : stringDelegate
Binding {
target: loader.item
property: "editProperty"
value: model.two
}
Connections {
target: loader.item
onEditPropertyChanged: model.two = loader.item.editProperty
}
}
//...
Component {
id: checkBoxDelegate
QQC1.CheckBox {
id: checkbox
property alias editProperty: checkbox.checked
text: checked
}
}
Component {
id: stringDelegate
TextEdit {
id: textEdit
property alias editProperty: textEdit.finishedText // you can even use a custom property
property string finishedText
text: finishedText
onEditingFinished: finishedText = text
}
}
Using setData() could be an option, but it requires an integer value that indicates the role that is not accessible in QML, or rather is not elegant.
A better option is to create a new one that is Q_INVOKABLE. As the update is given in the view it is not necessary to notify it besides causing strange events.
to obtain the row we use the geometry and the rowAt() method of TableView.
The following is an example:
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QStandardItemModel>
class MyStandardModel: public QStandardItemModel
{
Q_OBJECT
public:
enum Role {
role1=Qt::UserRole+1,
role2
};
using QStandardItemModel::QStandardItemModel;
QHash<int, QByteArray> roleNames() const{
QHash<int, QByteArray> roles;
roles[role1] = "one";
roles[role2] = "two";
return roles;
}
Q_INVOKABLE void updateValue(int row, QVariant value, const QString &roleName){
int role = roleNames().key(roleName.toUtf8());
QStandardItem *it = item(row);
if(it){
blockSignals(true);
it->setData(value, role);
Q_ASSERT(it->data(role)==value);
blockSignals(false);
}
}
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
MyStandardModel model;
for(int i=0; i< 10; i++){
auto item = new QStandardItem;
item->setData(QString("data1 %1").arg(i), MyStandardModel::role1);
if(i%2 == 0)
item->setData(true, MyStandardModel::role2);
else {
item->setData(QString("data2 %1").arg(i), MyStandardModel::role2);
}
model.appendRow(item);
}
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myTestModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "main.moc"
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TableView {
id: tableView2
anchors.fill: parent
TableViewColumn {
title: "Parameter Name"
role: "one"
}
TableViewColumn {
title: "Value"
role: "two"
delegate: myDelegate
}
model: myTestModel
}
Component {
id: myDelegate
Loader {
property var roleTwo: model.two
sourceComponent: typeof(roleTwo)=='boolean'? checkBoxDelegate: stringDelegate
}
}
Component {
id: checkBoxDelegate
CheckBox{
checked: roleTwo
onCheckedChanged:{
var pos = mapToGlobal(0, 0)
var p = tableView2.mapFromGlobal(pos.x, pos.y)
var row = tableView2.rowAt(p.x, p.y)
if(row >= 0)
myTestModel.updateValue(tableView2.row, checked, "two")
}
}
}
Component {
id: stringDelegate
TextField {
text: roleTwo
onEditingFinished: {
var pos = mapToGlobal(0, 0)
var p = tableView2.mapFromGlobal(pos.x, pos.y)
var row = tableView2.rowAt(p.x, p.y)
if(row >= 0)
myTestModel.updateValue(tableView2.row, text, "two")
}
}
}
}
The complete example can be found in the following link.

Qt Qml Error: Unable to assign [undefined] to QString in a subclassed QAbstractItemModel for Qml TreeView

I try to create a custom module for the Qml TreeView and I used Simpel Treeview Example as foundation, but I always get "unabe to assign [undefined] to QString".
Here some infos u might need:
Displayed view
Qml:
import QtQuick 2.0
import QtQuick.Controls 1.4
import cci.screenshotCreator.StructureDataModel 1.0
TreeView {
id: root
state: "closed"
TableViewColumn {
role: "folderName"
title: "Ordnerstruktur"
}
model: StructureDataModel{
id: dataModel
}
itemDelegate: Item {
height: 30
Rectangle {
id: rectangle
color: styleData.selected ? "lightgray" : "#ffffff"
border.width: styleData.selected ? 2 : 1
border.color: styleData.selected ? "lightblue" : "#ababab"
anchors.fill: parent
}
Text {
id: folderText
text: dataModel.folderName
fontSizeMode: Text.Fit
anchors.fill: parent
font.pointSize: 12
verticalAlignment: Text.AlignLeft
}
C++:
#include "structuredatamodel.h"
structureDataModel::structureDataModel(QObject *parent)
: QAbstractItemModel(parent)
{
QList<QVariant> rootData;
rootData << "/";
rootItem = new StructureItem(rootData);
setupModelData(rootItem);
}
structureDataModel::~structureDataModel()
{
delete rootItem;
}
QModelIndex structureDataModel::index(int row, int column, const QModelIndex &parent) const
{
if(!hasIndex(row, column, parent))
return QModelIndex();
StructureItem *parentItem;
if(!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<StructureItem*>(parent.internalPointer());
StructureItem *childItem = parentItem->child(row);
if(childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
QHash<int, QByteArray> structureDataModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[structureRoles::folderName] = "folderName";
return roles;
}
QModelIndex structureDataModel::parent(const QModelIndex &index) const
{
if(!index.isValid())
return QModelIndex();
StructureItem *childItem = static_cast<StructureItem*>(index.internalPointer());
StructureItem *parentItem = childItem->parentItem();
if (parentItem == rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
int structureDataModel::rowCount(const QModelIndex &parent) const
{
StructureItem *parentItem;
if(parent.column() > 0)
return 0;
if(!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<StructureItem*>(parent.internalPointer());
return parentItem->childCount();
}
int structureDataModel::columnCount(const QModelIndex &parent) const
{
if(parent.isValid())
return static_cast<StructureItem*>(parent.internalPointer())->columnCount();
else
return rootItem->columnCount();
}
QVariant structureDataModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
if(role != structureRoles::folderName)
return QVariant();
StructureItem *item = static_cast<StructureItem*>(index.internalPointer());
return item->data(index.column()).toString();
}
Qt::ItemFlags structureDataModel::flags(const QModelIndex &index) const
{
if(!index.isValid())
return 0;
return QAbstractItemModel::flags(index);
}
QVariant structureDataModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == structureRoles::folderName)
return rootItem->data(section);
return QVariant();
}
void structureDataModel::setupModelData(StructureItem *parent)
{
QList<QVariant> data;
data << "Test";
StructureItem *child = new StructureItem(data, parent);
parent->appendChild(child);
}
The StructureItem is is the same as TreeItem (just a different name :) )
I made my own setupModelData so that model only have one row as root point.
Later I would like to have a editable treeview but for now a working read-only treeView should work right now.
I suspect that it has something to with the setupModelData or making the c++ visible for qml.
PS: I added qmlRegisterType("cci.screenshotCreator.StructureDataModel", 1, 0, "StructureDataModel"); to the main file to register the cpp class.
Minimal Application for testing:
https://pastebin.com/u/Klidrack (all files of my pastebin profile) :)
Oh, didn't saw it at first look.
There is diference between ListView's and TreeView's delegated components in QML.
In TreeView you have itemDelegate and all data are expanded through styleData properties.
in your case this should look like this:
itemDelegate: Item
{
height: 30
Rectangle
{
id: rectangle
color: styleData.selected ? "lightgray" : "#ffffff"
border.width: styleData.selected ? 2 : 1
border.color: styleData.selected ? "lightblue" : "#ababab"
anchors.fill: parent
}
Text
{
id: folderText
text: styleData.value
fontSizeMode: Text.Fit
anchors.fill: parent
font.pointSize: 12
verticalAlignment: Text.AlignLeft
}
}

QML TableView + QAbstractTableModel - how edit model data from QML?

I have C++ class inherited from QAbstractTableModel with next functions overriden:
virtual QHash<int, QByteArray> roleNames() const noexcept override;
virtual Qt::ItemFlags flags(const QModelIndex& index) const noexcept override;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const noexcept override;
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const noexcept override;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const noexcept override;
virtual bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) noexcept override;
virtual bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()) noexcept override;
virtual bool setData(const QModelIndex& index, const QVariant& data, int role = Qt::EditRole) noexcept override;
model has 3 columns, first is readonly, last is editable, so this is flags() method implementation:
Qt::ItemFlags ObjectInitialStateModel::flags(const QModelIndex& index) const noexcept
{
if (index.column() == 0)
{
return Qt::ItemIsEnabled | Qt::ItemNeverHasChildren;
}
else
{
return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemNeverHasChildren;
}
}
In QML part model is displayed fine, but i have no idea how i can edit model data for 2 and 3 columns in TableView. I've tried to write column delegate:
Item {
id: item
state: "labelMode"
Text {
id: textLabel
text: styleData.value
anchors.fill: parent
renderType: Text.NativeRendering
}
TextField {
id: textField
text: styleData.value
anchors.fill: parent
Keys.onEnterPressed: commit()
Keys.onReturnPressed: commit()
Keys.onEscapePressed: rollback()
function commit() {
item.state = "labelMode"
}
function rollback() {
item.state = "labelMode"
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
onDoubleClicked: item.state = "editMode"
}
states: [
State {
name: "labelMode"
PropertyChanges {
target: textLabel
visible: true
}
PropertyChanges {
target: mouseArea
visible: true
}
PropertyChanges {
target: textField
visible: false
}
},
State {
name: "editMode"
PropertyChanges {
target: textLabel
visible: false
}
PropertyChanges {
target: mouseArea
visible: false
}
PropertyChanges {
target: textField
visible: true
focus: true
}
}
]
}
but i don't know how to set new data to the model in commit() function correctly.
Or may be there are another right way to implement table in QML with editable columns and C++ model?
I've found one solution:
add property to the delegate:
property var cppModel
set this property in column definition:
TableViewColumn {
role: "u"
title: qsTr("u(t)")
width: initialStateTableView.width / 3
delegate: EditableDelegate {
cppModel: DataSetService.currentDataSet ? DataSetService.currentDataSet.initialStateModel : null
}
}
implement new method in C++ model:
Q_INVOKABLE bool setData(int row, int column, const QVariant& data) noexcept;
which calls default setData method
and call it from commit() function in delegate:
function commit() {
cppModel.setData(styleData.row, styleData.column, text)
item.state = "labelMode"
}
But i think this is big ugly hack and if anybody knows more elegant solution, please share it...
Besides the roleNames() and data(), the editable models must reimplement the setData() function to save changes to existing data. The following version of the method checks if the given model index is valid and the role is equal to Qt::EditRole, before executing the actual update. Depending on the model you can/must also call the parent class version of the function:
bool EditableModel::setData(const QModelIndex &item, const QVariant &value, int role)
{
if (item.isValid() && role == Qt::EditRole) {
// update logic
emit dataChanged(item, item);
return true;
}
return false;
}
It should be noted that unlike the C++ item views, such as QListView or QTableView, the setData() method must be explicitly invoked from QML whenever appropriate.