How to show large data set in qml ListView - c++

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 ?

Related

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.

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
}
}

Getting data from C++ model inside delegate

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
}

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.

QML-Listview (Cpp-Model) Details-Dialog

EDIT: 26.08.2014 08:20 - Completely reworked question!
What I want to do is:
Fill a qml-listview with data from a cpp-listmodel (QAbstractListModel).
Open a Dialog that show's more data from the cpp-listmodel by click on a listview-item.
I have two cpp-Classes:
DataModelItem with two attributes (listData (displayed in the listview) and detailsData (displayed in the Dialog))
DataModel which inherits QAbstractListModel with an attribut QList itemList.
DataModel.cpp:
QVariant DataModel::data(const QModelIndex &index, int role) const
{
DataModelItem *item = m_itemList.at(index.row());
switch (role) {
case ListDataRole:
return QString().sprintf("%.2f", item->listData());
break;
case DetailsDataRole:
return QString().sprintf("%.4f", item->detailsData());
break;
default:
qDebug () << "role not handled";
}
return QVariant();
}
What I now wanna do is, to display the listData in the ListView. When I click on one ListItem a dialog should appear with the detailsData.
I figured out, that I can't write model.detailsData in my main application, but just detailsData works (I also tried listview.model.detailsData with no effect). Probably someone know why this does not work.
Anyway I found a solution.
Here's the working example:
main.qml
import QtQuick 1.1
Rectangle {
width: 200
height: 400
ListView {
id: listView
model: dataModel
delegate: listDelegate
}
Component {
id: listDelegate
Item {
id: delegateItem
width: listDataText.width
height: listDataText.height
Text {
id: listDataText
text: listData
}
MouseArea {
anchors.fill: parent
onClicked: {
console.log(detailsData)
itemDetails.details = model.detailsData
itemDetails.visible = true
}
}
}
}
DetailsDialog {
id: itemDetails
visible: false
anchors.centerIn: parent
}
}
DetailsDialog.qml
import QtQuick 1.1
Rectangle {
property alias details: detailsText.text
width: 100
height: 62
Text {
id: detailsText
}
}