I use the Qt example for QAbstractItemModel and I try to update an Item to a given index.
I tried to use emit DataChangedbut it doesn't work, the view is not updated.
Here is an example:
What I want: When you click on the button, it will update Data at index 0, the type of the animal will be changed, it will become a Lion.
#include <QAbstractListModel>
#include <QStringList>
#include <qqmlcontext.h>
//![0]
class Animal
{
public:
Animal(const QString &type, const QString &size);
//![0]
QString type() const;
QString size() const;
void setType(QString q) {
m_type = q;
}
private:
QString m_type;
QString m_size;
//![1]
};
class AnimalModel : public QAbstractListModel
{
Q_OBJECT
public:
Q_INVOKABLE void test() ;
void setName(const QString &name);
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AnimalModel(QObject *parent = 0);
//![1]
//!
//!
void setContext(QQmlContext *ctx) {
m_ctx = ctx;
}
void addAnimal(const Animal &animal);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
QHash<int, QByteArray> roleNames() const;
protected:
private:
QList<Animal> m_animals;
QQmlContext* m_ctx;
signals:
void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight);
//![2]
};
//![2]
model.h
#include "model.h"
#include "qDebug"
Animal::Animal(const QString &type, const QString &size)
: m_type(type), m_size(size)
{
}
QString Animal::type() const
{
return m_type;
}
QString Animal::size() const
{
return m_size;
}
AnimalModel::AnimalModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void AnimalModel::addAnimal(const Animal &animal)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_animals << animal;
endInsertRows();
}
void AnimalModel::test() {
m_animals[0].setType("Lion");
emit dataChanged(QModelIndex(),QModelIndex());
//I also tried:
QModelIndex topLeft = createIndex(0,0);
emit dataChanged(topLeft, topLeft);
}
int AnimalModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_animals.count();
}
QVariant AnimalModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_animals.count())
return QVariant();
const Animal &animal = m_animals[index.row()];
if (role == TypeRole)
return animal.type();
else if (role == SizeRole)
return animal.size();
return QVariant();
}
//![0]
QHash<int, QByteArray> AnimalModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
//![0]
model.cpp
#include "model.h"
#include <QGuiApplication>
#include <qqmlengine.h>
#include <qqmlcontext.h>
#include <qqml.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
//![0]
int main(int argc, char ** argv)
{
QGuiApplication app(argc, argv);
AnimalModel model;
model.addAnimal(Animal("Wolf", "Medium"));
model.addAnimal(Animal("Polar bear", "Large"));
model.addAnimal(Animal("Quoll", "Small"));
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", &model);
//![0]
view.setSource(QUrl("qrc:view.qml"));
view.show();
return app.exec();
}
main.cpp
import QtQuick 2.0
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.2
import QtQml.Models 2.1
import QtQuick.Controls.Styles 1.2
//![0]
ListView {
width: 200; height: 250
model: myModel
delegate: Text { text: "Animal: " + type + ", " + size }
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
}
}
Button {
anchors.bottom: parent.bottom
width:50; height:50
text:"click"
onClicked: {
myModel.test()
}
}
}
//![0]
View.qml
Do you have any idea why it doesn't work ?
Thanks a lot !
Don't redefine signals in subclasses.
Remove the following lines (in model.h) and it should work as expected :
signals:
void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight);
Also this when calling dataChanged() you have to specify a valid QModelIndex. This is correct :
// I also tried:
QModelIndex topLeft = createIndex(0,0);
emit dataChanged(topLeft, topLeft);
You need create topLeft index and bottom right index and emit dataChanged. This update all model data in view.
QModelIndex topLeft = createIndex(0,0);
QModelIndex bottomRight = createIndex( rows count ,0);
emit dataChanged( topLeft, bottomRight );
Related
I am trying to make a listview component using QQuickItem and load its model using QAbstractListModel. Below are the steps which i tried.
listviewComponent.qml
ListView {
required model
delegate: Text {
required property string type
required property string size
text: type + ", " + size
}
}
Model.h
class Model
{
public:
Model(const QString& type, const QString& size);
QString type() const;
QString size() const;
private:
QString m_type;
QString m_size;
};
class CustomModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
CustomModel(QObject* parent = 0);
void addElement(const Model& pElement);
int rowCount(const QModelIndex& parent = QModelIndex()) const;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<Model> m_list;
};
Model.cpp
Model::Model(const QString& type, const QString& size)
: m_type(type), m_size(size)
{
}
QString Model::type() const
{
return m_type;
}
QString Model::size() const
{
return m_size;
}
CustomModel::CustomModel(QObject* parent)
: QAbstractListModel(parent)
{
}
void CustomModel::addElement(const Model &pElement)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_list << pElement;
endInsertRows();
}
int CustomModel::rowCount(const QModelIndex& parent) const {
Q_UNUSED(parent);
return m_list.count();
}
QVariant CustomModel::data(const QModelIndex& index, int role) const {
if (index.row() < 0 || index.row() >= m_list.count())
return QVariant();
const Model& mod = m_list[index.row()];
if (role == TypeRole)
return mod.type();
else if (role == SizeRole)
return mod.size();
return QVariant();
}
QHash<int, QByteArray> CustomModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
Myclass.h
class myclass : public QObject
{
Q_OBJECT
public:
myclass();
inline int createUI(QQmlApplicationEngine &engine){
QQuickWindow *window = qobject_cast<QQuickWindow*>(engine.rootObjects().at(0));
if (!window) {
qFatal("Error: Your root item has to be a window.");
return -1;
}
QQuickItem *root = window->contentItem();
window->setWidth(600);
window->setHeight(500);
QQmlComponent listviewComp(&engine, QUrl(QStringLiteral("qrc:/listviewComponent.qml")));
CustomModel model;
model.addElement(Model("Wolf", "Medium"));
model.addElement(Model("Polar bear", "Large"));
model.addElement(Model("Quoll", "Small"));
QQuickItem *listview = qobject_cast<QQuickItem*>(listviewComp.createWithInitialProperties({ {"model", QVariant::fromValue(&model)} }));
QQmlEngine::setObjectOwnership(listview, QQmlEngine::CppOwnership);
listview->setParentItem(root);
listview->setParent(&engine);
listview->setProperty("objectName", "lv");
listview->setWidth(200);
listview->setHeight(100);
listview->setX(250);
listview->setY(30);
window->show();
return 0;
}
};
main.cpp
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
engine.load(url);
QQmlContext *item = engine.rootContext();
myclass myClass;
item->setContextProperty("_myClass", &myClass);
myClass.createUI(engine);
return app.exec();
}
Problem: Listview is getting displayed but with only one row, but there are 3 rows added in CustomModel. I am assuming some problem with createWithInitialProperties, but couldnt able to crack it.
The problem is caused because "model" is a local variable that will be destroyed as soon as createUI is finished executing, and what you see as the first row is just a cache, you shouldn't actually see a row (it looks like a bug). The solution is to use the heap memory:
// ...
CustomModel *model = new CustomModel(window);
model->addElement(Model("Wolf", "Medium"));
model->addElement(Model("Polar bear", "Large"));
model->addElement(Model("Quoll", "Small"));
QQuickItem *listview = qobject_cast<QQuickItem*>(listviewComp.createWithInitialProperties({ {"model", QVariant::fromValue(model)} }));
// ...
Please have a look into the following code:
CMyModel* myModel = new CMyModel(); //This is my main model sub-classing QAbstractListItemModel
CMySortModel* mySortModel = new CMySortModel(); //This is my sort filter proxy model sub-classing QSortFilterProxyModel
mySortModel->setSourceModel(myModel);
mySortModel->setDynamicSortFilter(true);
mySortModel->setSortRole(CMyModel::displayRole);
myModel->Initialize(); // This is working fine
mySortModel->sort(0);
// myModel->Initialize(); // This is not working
The above code is working fine when the data structure (basically a QList of integers linked to CMyModel) is initialized before, mySortModel->sort(0) is called.
If the list is initialized after the sort method call, the view is basically not showing any data.
Then I tried following
mySortModel->sort(0);
myModel->Initialize();
mySortModel->Invalidate(); // This is working fine.
But realized that, every time there is an update in the QList, I need to call mySortModel->Invalidate() to update my view. Otherwise the view is not updating. This is causing a complete view update and in turn a flickering in my screen.
As not much idea on Qt/QML model view coding, so bit stuck with the issue.
Please correct me, if I am doing anything wrong.
MyModel.cpp
#include "MyModel.h"
#include <QDateTime>
CMyModel::CMyModel(QObject *parent)
: QAbstractListModel(parent)
{
m_mapRoles.insert(rolesEnum::displayRole, "displayRole");
}
void CMyModel::Initialize()
{
qsrand(QDateTime::currentMSecsSinceEpoch() / 1000);
quint32 index = 0;
while (index < 200)
{
m_lstOfInts.append(qrand() % 1000);
index++;
}
}
int CMyModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_lstOfInts.count();
}
int CMyModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 1;
}
QVariant CMyModel::data(const QModelIndex &index, int role) const
{
Q_UNUSED(role)
int rowVal = index.row();
if (role == rolesEnum::displayRole)
{
if (rowVal >= 0 && rowVal < m_lstOfInts.count())
{
return m_lstOfInts.at(rowVal);
}
}
return QVariant();
}
QHash<int, QByteArray> CMyModel::roleNames() const
{
return m_mapRoles;
}
MyModel.h
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QAbstractListModel>
#include <QObject>
#include <QHash>
class CMyModel : public QAbstractListModel
{
Q_OBJECT
public:
enum rolesEnum {
displayRole
};
Q_ENUM(rolesEnum)
public:
explicit CMyModel(QObject *parent = NULL);
// QAbstractItemModel interface
public:
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
private:
QList<int> m_lstOfInts;
QHash<int, QByteArray> m_mapRoles;
// QAbstractItemModel interface
public:
QHash<int, QByteArray> roleNames() const;
void Initialize();
};
#endif // MYMODEL_H
MySortModel.cpp
#include "MySortModel.h"
#include "MyModel.h"
CMySortModel::CMySortModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
}
bool CMySortModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
return sourceModel()->data(source_left, CMyModel::displayRole).toInt() < sourceModel()->data(source_right, CMyModel::displayRole).toInt();
}
MySortModel.h
#ifndef MYSORTMODEL_H
#define MYSORTMODEL_H
#include <QSortFilterProxyModel>
class CMySortModel : public QSortFilterProxyModel
{
public:
explicit CMySortModel(QObject* parent = NULL);
// QSortFilterProxyModel interface
protected:
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
};
#endif // MYSORTMODEL_H
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQmlContext>
#include "MyModel.h"
#include "MySortModel.h"
int main(int argc, char *argv[])
{
#if defined(Q_OS_WIN)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
qmlRegisterType<CMySortModel>("CMySortModel", 1, 0, "CMySortModel");
CMyModel* myModel = new CMyModel();
CMySortModel* mySortModel = new CMySortModel();
mySortModel->setSourceModel(myModel);
mySortModel->setDynamicSortFilter(true);
mySortModel->setSortRole(CMyModel::displayRole);
// myModel->Initialize(); // This works
mySortModel->sort(0);
myModel->Initialize(); // This works when followed by a invalidate call on the proxy model
mySortModel->invalidate();
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("_myModel", mySortModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
{
delete mySortModel;
mySortModel = NULL;
delete myModel;
myModel = NULL;
return -1;
}
return app.exec();
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.2
import QtGraphicalEffects 1.0
Window {
id: window
visible: true
width: 800
height: 480
title: qsTr("Hello World")
ScrollView {
anchors.fill: parent
width: parent.width
GridView {
anchors.fill: parent
//columns: 2
model: _myModel
delegate: Text {
id: _text
text: model.displayRole
}
}
}
}
Modified Initialize method as follows and it is working now
void CMyModel::Initialize()
{
qsrand(QDateTime::currentMSecsSinceEpoch() / 1000);
quint32 index = 0;
while (index < 200)
{
beginInsertRows(QModelIndex(), m_lstOfInts.size(), m_lstOfInts.size());
m_lstOfInts.append(qrand() % 1000);
index++;
endInsertRows();
}
}
I'm making a folding list of three items: "Hey", "Whats", and "Up?". I want to put it into a tree view. I know this list will only ever contain these three items. Therefore, I would like to know how to "nest" these items together.
I know there are implementation for agile systems that support adding and removing parent/child objects, finding indexes... powerful models. However, I literally only need to display these items in an expandable/collapsable view. Here is what I've read through relating to C++ and QAbstractItemModels:
QML Treeview
QML QAbstractItemModel
This Question by My_Cat_Jessica
This question by kavaliero which was based on:
This 'working' example by Qt themselves (Doesn't actually work for TreeView. Works for QTreeView though!)
Here is the simplest viable code to implement a treeview with model:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: mywindow
visible: true
width: 640
height: 480
TreeView {
id: treeview
anchors.fill: parent
TableViewColumn {
title: "Phrase"
role: "phrase"
}
model: phraseModel
}
ListModel {
id: phraseModel
ListElement { phrase: "Hey"; }
ListElement { phrase: "What's"; }
ListElement { phrase: "Up?"; }
}
}
I would like for the output to result in a nested stack like this:
Hey
What's
Up?
But I am getting everything in a single column all aligned with each other:
Hey
What's
Up?
I know I haven't assigned parents, and I'm not entirely sure how to do that - But I'm not even sure if that's what needs done to this code. So my question is this: What's the final step missing to stack these three elements into an expandable/collapsible view?
There is no native QML model that can use the TreeView, so I have implemented a model that tries to be generic:
TreeElement
// treeelement.h
#ifndef TreeElement_H
#define TreeElement_H
#include <QObject>
#include <QQmlListProperty>
class TreeElement : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(QQmlListProperty<TreeElement> items READ items)
Q_CLASSINFO("DefaultProperty", "items")
TreeElement(QObject *parent = Q_NULLPTR);
Q_INVOKABLE TreeElement *parentItem() const;
bool insertItem(TreeElement *item, int pos = -1);
QQmlListProperty<TreeElement> items();
TreeElement *child(int index) const;
void clear();
Q_INVOKABLE int pos() const;
Q_INVOKABLE int count() const;
private:
static void appendElement(QQmlListProperty<TreeElement> *property, TreeElement *value);
static int countElement(QQmlListProperty<TreeElement> *property);
static void clearElement(QQmlListProperty<TreeElement> *property);
static TreeElement *atElement(QQmlListProperty<TreeElement> *property, int index);
QList<TreeElement *> m_childs;
TreeElement *m_parent;
};
#endif // TreeElement_H
// treeelement.cpp
#include "treeelement.h"
TreeElement::TreeElement(QObject *parent) :
QObject(parent),
m_parent(nullptr) {}
TreeElement *TreeElement::parentItem() const{
return m_parent;
}
QQmlListProperty<TreeElement> TreeElement::items(){
return QQmlListProperty<TreeElement> (this,
this,
&TreeElement::appendElement,
&TreeElement::countElement,
&TreeElement::atElement,
&TreeElement::clearElement);
}
TreeElement *TreeElement::child(int index) const{
if(index < 0 || index >= m_childs.length())
return nullptr;
return m_childs.at(index);
}
void TreeElement::clear(){
qDeleteAll(m_childs);
m_childs.clear();
}
bool TreeElement::insertItem(TreeElement *item, int pos){
if(pos > m_childs.count())
return false;
if(pos < 0)
pos = m_childs.count();
item->m_parent = this;
item->setParent(this);
m_childs.insert(pos, item);
return true;
}
int TreeElement::pos() const{
TreeElement *parent = parentItem();
if(parent)
return parent->m_childs.indexOf(const_cast<TreeElement *>(this));
return 0;
}
int TreeElement::count() const{
return m_childs.size();
}
void TreeElement::appendElement(QQmlListProperty<TreeElement> *property, TreeElement *value){
TreeElement *parent = qobject_cast<TreeElement *>(property->object);
parent->insertItem(value);
}
int TreeElement::countElement(QQmlListProperty<TreeElement> *property){
TreeElement *parent = qobject_cast<TreeElement *>(property->object);
return parent->count();
}
void TreeElement::clearElement(QQmlListProperty<TreeElement> *property){
TreeElement *parent = qobject_cast<TreeElement *>(property->object);
parent->clear();
}
TreeElement *TreeElement::atElement(QQmlListProperty<TreeElement> *property, int index){
TreeElement *parent = qobject_cast<TreeElement *>(property->object);
if(index < 0 || index >= parent->count())
return nullptr;
return parent->child(index);
}
TreeModel
// treemodel.h
#ifndef TreeModel_H
#define TreeModel_H
#include <QAbstractItemModel>
#include <QQmlListProperty>
class TreeElement;
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
Q_PROPERTY(QQmlListProperty<TreeElement> items READ items)
Q_PROPERTY(QVariantList roles READ roles WRITE setRoles NOTIFY rolesChanged)
Q_CLASSINFO("DefaultProperty", "items")
TreeModel(QObject *parent = Q_NULLPTR);
~TreeModel() override;
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QQmlListProperty<TreeElement> items();
QVariantList roles() const;
void setRoles(const QVariantList &roles);
Q_INVOKABLE QModelIndex indexFromElement(TreeElement *item);
Q_INVOKABLE bool insertElement(TreeElement *item, const QModelIndex &parent = QModelIndex(), int pos = -1);
TreeElement *elementFromIndex(const QModelIndex &index) const;
private:
TreeElement *m_root;
QHash<int, QByteArray> m_roles;
signals:
void rolesChanged();
};
#endif // TreeModel_H
// treemodel.cpp
#include "treemodel.h"
#include "treeelement.h"
TreeModel::TreeModel(QObject *parent) :
QAbstractItemModel(parent){
m_root = new TreeElement;
}
TreeModel::~TreeModel(){
delete m_root;
}
QHash<int, QByteArray> TreeModel::roleNames() const{
return m_roles;
}
QVariant TreeModel::data(const QModelIndex &index, int role) const{
if (!index.isValid())
return QVariant();
TreeElement *item = static_cast<TreeElement*>(index.internalPointer());
QByteArray roleName = m_roles[role];
QVariant name = item->property(roleName.data());
return name;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const{
if (!index.isValid())
return nullptr;
return QAbstractItemModel::flags(index);
}
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const{
if (!hasIndex(row, column, parent))
return QModelIndex();
TreeElement *parentItem = elementFromIndex(parent);
TreeElement *childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
QModelIndex TreeModel::parent(const QModelIndex &index) const{
if (!index.isValid())
return QModelIndex();
TreeElement *childItem = static_cast<TreeElement*>(index.internalPointer());
TreeElement *parentItem = static_cast<TreeElement *>(childItem->parentItem());
if (parentItem == m_root)
return QModelIndex();
return createIndex(parentItem->pos(), 0, parentItem);
}
int TreeModel::rowCount(const QModelIndex &parent) const{
if (parent.column() > 0)
return 0;
TreeElement *parentItem = elementFromIndex(parent);
return parentItem->count();
}
int TreeModel::columnCount(const QModelIndex &parent) const{
Q_UNUSED(parent)
return 1;
}
QQmlListProperty<TreeElement> TreeModel::items(){
return m_root->items();
}
QVariantList TreeModel::roles() const{
QVariantList list;
QHashIterator<int, QByteArray> i(m_roles);
while (i.hasNext()) {
i.next();
list.append(i.value());
}
return list;
}
void TreeModel::setRoles(const QVariantList &roles){
static int nextRole = Qt::UserRole + 1;
for(QVariant role : roles) {
m_roles.insert(nextRole, role.toByteArray());
nextRole ++;
}
emit rolesChanged();
}
QModelIndex TreeModel::indexFromElement(TreeElement *item){
QVector<int> positions;
QModelIndex result;
if(item) {
do{
int pos = item->pos();
positions.append(pos);
item = item->parentItem();
} while(item != nullptr);
for (int i = positions.size() - 2; i >= 0 ; i--)
result = index(positions[i], 0, result);
}
return result;
}
bool TreeModel::insertElement(TreeElement *item, const QModelIndex &parent, int pos){
TreeElement *parentElement = elementFromIndex(parent);
if(pos >= parentElement->count())
return false;
if(pos < 0)
pos = parentElement->count();
beginInsertRows(parent, pos, pos);
bool retValue = parentElement->insertItem(item, pos);
endInsertRows();
return retValue;
}
TreeElement *TreeModel::elementFromIndex(const QModelIndex &index) const{
if(index.isValid())
return static_cast<TreeElement *>(index.internalPointer());
return m_root;
}
main.cpp
#include "treemodel.h"
#include "treeelement.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
static void registertypes(){
qmlRegisterType<TreeElement>("foo", 1, 0, "TreeElement");
qmlRegisterType<TreeModel>("foo", 1, 0, "TreeModel");
}
Q_COREAPP_STARTUP_FUNCTION(registertypes)
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
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();
}
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.4
import foo 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TreeModel {
id: treemodel
roles: ["phrase"]
TreeElement{
property string phrase: "Hey"
TreeElement{
property string phrase: "What's"
TreeElement{
property string phrase: "Up?"
}
}
}
}
TreeView {
anchors.fill: parent
model: treemodel
TableViewColumn {
title: "Name"
role: "phrase"
width: 200
}
}
}
Output:
The complete example you find here
I created a collapsible frame in QML (a group box with a title and a content). If you are sure that you will never change the structure, you can use for your purpose:
I simplified the code by removing the useless parts (animations, decorations, etc.). So the code below could be improved. But, I kept the idea:
// CollapsibleGroupBox.qml
Item {
property alias contentItem: content.contentItem;
property string title: ""
Item {
id: titleBar
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 30
Row {
anchors.fill: parent
CheckBox {
Layout.alignment: Qt.AlignLeft
id: expand
checked: true;
}
Text {
Layout.alignment: Qt.AlignLeft
text: title
}
}
}
Pane {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: titleBar.bottom
anchors.bottom: parent.bottom
topPadding: 0
visible: expand.checked
id: content
}
}
// Main.qml
Item {
height: 500
width: 500
CollapsibleGroupBox {
anchors.fill: parent
title: "Hey!"
contentItem: CollapsibleGroupBox {
title: "What's"
contentItem: CollapsibleGroupBox {
title: "up?"
}
}
}
}
You will get:
You can replace the checkbox by a MouseArea, also.
I've also created a model that only uses QML components:
import QtQuick 2.9
import QtQuick.Window 2.2
import UISettings 1.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls 1.4 as SV
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Flickable {
id: flick
anchors.fill: parent
clip: true
contentHeight: col.implicitHeight
property var mymodel: {
"animals": {
"big": {
"land": "elephants",
"water": "whales"
},
"small": {
"land": "mice",
"water": "fish"
}
},
"plants": {
"trees": "evergreens"
}
}
Column {
id: col
Component.onCompleted: componentListView.createObject(this, {"objmodel":flick.mymodel});
}
Component {
id: componentListView
Repeater {
id: repeater
property var objmodel: ({})
model: Object.keys(objmodel)
ColumnLayout {
Layout.leftMargin: 50
Button {
property var sprite: null
text: modelData
onClicked: {
if(sprite === null) {
if(typeof objmodel[modelData] === 'object')
sprite = componentListView.createObject(parent, {"objmodel":objmodel[modelData]});
}
else
sprite.destroy()
}
}
}
}
}
}
}
I am trying to get the Qt model/view-architecture working with a QML-View, but for whatever reason it is only partially working.
What works:
rowCount
data
roleNames
Not working:
columnCount (the method is called, but seems to have no effect, as long is it is > 0)
headerData (is this actually supposed to set the column headers? All examples set the headers in QML)
flags
setData
What I am trying to do (for some weeks already), is to create a simple ApplicationView with a TableView and a C++-model, which is editable by the view.
Right now only a whole row is selectable, not a single cell. The table data doesn't seem to be editable at all. Can anyone give me a hint?
main.qml
import QtQuick 2.3
import QtQuick.Controls 1.2
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
TableView {
model: theModel
TableViewColumn {
role: "nameRole"
width: 75
}
TableViewColumn {
role: "ageRole"
width: 50
}
}
}
ModelItem.hpp
#ifndef MODELITEM
#define MODELITEM
#include <QString>
struct ModelItem {
ModelItem(QString name_, int age_)
: name(name_), age(age_) {}
QString name;
int age;
};
#endif // MODELITEM
TableModel.hpp
#ifndef TABLEMODEL_HPP
#define TABLEMODEL_HPP
#include <QAbstractTableModel>
#include "ModelItem.hpp"
class TableModel : public QAbstractTableModel
{
Q_OBJECT
public:
TableModel(QObject *parent = 0);
//works
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
//does not work
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
QHash<int, QByteArray> roleNames() const;
enum Roles {
NameRole = Qt::UserRole + 1,
AgeRole
};
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
private:
QList<ModelItem*> items;
};
#endif // TABLEMODEL_HPP
TableModel.cpp
#include "TableModel.hpp"
TableModel::TableModel(QObject *parent)
: QAbstractTableModel(parent) {
items.append(new ModelItem("Hugo",33));
items.append(new ModelItem("Egon",34));
items.append(new ModelItem("Balder",66));
qDebug("TableModel initialisiert");
}
int TableModel::columnCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
qDebug("columnCount");
return 2;
}
int TableModel::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
qDebug("rowCount");
return items.count();
}
QVariant TableModel::data(const QModelIndex &index, int role) const {
qDebug("data");
switch (role) {
case NameRole: return items[index.row()]->name;
case AgeRole: return items[index.row()]->age;
}
}
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const {
qDebug("headerData");
switch (role) {
case NameRole: return "1";
case AgeRole: return "2";
};
return QVariant();
}
QHash<int, QByteArray> TableModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[NameRole] = "nameRole";
roles[AgeRole] = "ageRole";
qDebug("roleNames initialised");
return roles;
}
Qt::ItemFlags TableModel::flags(const QModelIndex &index) const {
qDebug("--flags called--");
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}
bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role) {
qDebug("setData called");
switch (role) {
case NameRole: items[index.row()]->name = value.toString();
case AgeRole: items[index.row()]->age = value.toInt();
}
emit dataChanged(index, index);
return true;
}
main.cpp
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "TableModel.hpp"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
TableModel model;
engine.rootContext()->setContextProperty("theModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
columnCount and rowCount called in QAbstractItemModel::index method, which is called by TableView before data method.
QModelIndex QAbstractTableModel::index(int row, int column, const QModelIndex &parent) const
{
return hasIndex(row, column, parent) ? createIndex(row, column, 0) : QModelIndex();
}
bool QAbstractItemModel::hasIndex(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column < 0)
return false;
return row < rowCount(parent) && column < columnCount(parent);
}
columnCount has no effect, as long is it is > 0, because TableView called the index method with column always equal to 0.
headerData and flags did not affect the QML TableView. You can only set the headers on the QML side. To create an editable TableView, you should implement your custom itemDelegate
main.qml
ApplicationWindow {
visible: true
id: root
Component {
id: editableDelegate
Item {
Text {
width: parent.width
anchors.margins: 4
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
elide: styleData.elideMode
text: styleData.value !== undefined ? styleData.value : ""
color: styleData.textColor
visible: !styleData.selected
}
Loader {
id: loaderEditor
anchors.fill: parent
anchors.margins: 4
Connections {
target: loaderEditor.item
onEditingFinished: {
theModel.setData(styleData.row, styleData.column, loaderEditor.item.text)
}
}
sourceComponent: styleData.selected ? editor : null
Component {
id: editor
TextInput {
id: textinput
color: styleData.textColor
text: styleData.value
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: textinput.forceActiveFocus()
}
}
}
}
}
}
TableView {
id: table
anchors.fill: parent
model: theModel
itemDelegate: editableDelegate;
TableViewColumn {
role: "nameRole"
width: 75
title: "name"
}
TableViewColumn {
role: "ageRole"
width: 50
title: "age"
}
}
}
To apply the changes to your model, you should implement the setData method, like this:
TableModel.h
bool setData(const QModelIndex &index, const QVariant &value, int role) {
switch (role) {
case NameRole: items[index.row()]->name = value.toString(); break;
case AgeRole: items[index.row()]->age = value.toInt(); break;
}
emit dataChanged(index, index);
return true;
}
Q_INVOKABLE bool setData(int row, int column, const QVariant value)
{
int role = Qt::UserRole + 1 + column;
return setData(index(row,0), value, role);
}
The above answer covers things nicely. I just want to add a couple of notes:
in QML you can simply do model.role = value within an item delegate instead of having to call setData() by hand
several QAbstractItemModel methods, including setData(), have been made Q_INVOKABLE in Qt 5.5: https://codereview.qt-project.org/#/c/107171/
I have a QT QML project. (still very small)
I started by binding a listview on my UScenario model, by subclassing QAbstractListModel and it worked fined.
Now, each UScenario has a list of UTask, which also have a list of UCondition (so, Utask also subclasses QAbstractListModel). But then, QT Creator gives me an error:
Core/Tasks/utask.h:6: erreur : base class 'QAbstractListModel' has private copy constructor
class UTask: public QAbstractListModel
^
So I'm not sure where is my problem. I tried reading the doc about QAbstractListModel vs QAbstractItemModel, but I have no clue.
I also tried to see if I ever constructed a UTask in a wrong way; I think not.
// USCENARIO.h
#ifndef USCENARIO_H
#define USCENARIO_H
#include <QAbstractListModel>
#include "../Tasks/utask.h"
class UScenario : public QAbstractListModel
{
Q_OBJECT
public slots:
void cppSlot() { // Used to test the insertion from UI
this->addTask(UTask());
}
public:
enum TaskRoles {
IdRole = Qt::UserRole + 1
};
UScenario(QObject *parent = 0);
private:
QList<UTask> m_tasks;
public:
void addTask(const UTask &task);
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QHash<int, QByteArray> roleNames() const;
};
#endif // USCENARIO_H
// USCENARIO.CPP
#include "uscenario.h"
UScenario::UScenario(QObject *parent)
: QAbstractListModel(parent)
{
}
void UScenario::addTask(const UTask &task)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_tasks.append(task);
endInsertRows();
}
int UScenario::rowCount(const QModelIndex & parent) const {
return m_tasks.count();
}
QVariant UScenario::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_tasks.count())
return QVariant();
const UTask &task = m_tasks[index.row()];
if (role == IdRole)
return task.id();
return QVariant();
}
QHash<int, QByteArray> UScenario::roleNames() const {
QHash<int, QByteArray> roles;
roles[IdRole] = "id";
return roles;
}
// UTASK.H
#ifndef UTASK_H
#define UTASK_H
#include <QAbstractListModel>
#include "../Conditions/ucondition.h"
class UTask: public QAbstractListModel
{
Q_OBJECT
public:
enum TaskRoles {
typeRole = Qt::UserRole + 1
};
UTask(QObject *parent = 0);//:m_id(0){}
int id() const{return m_id;}
private:
int m_id;
QList<UCondition> m_conditions;
// QAbstractItemModel interface
public:
void addCondition(const UCondition &cond);
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QHash<int, QByteArray> roleNames() const;
};
#endif // UTASK_H
// UTASK.cpp
#include "utask.h"
UTask::UTask(QObject *parent):
QAbstractListModel(parent), m_id(0)
{
}
void UTask::addCondition(const UCondition &cond)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_conditions.append(cond);
endInsertRows();
}
int UTask::rowCount(const QModelIndex &parent) const
{
return m_conditions.count();
}
QVariant UTask::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_conditions.count())
return QVariant();
const UCondition &cond = m_conditions[index.row()];
if (role == typeRole)
return cond.type();
return QVariant();
}
QHash<int, QByteArray> UTask::roleNames() const
{
QHash<int, QByteArray> roles;
roles[typeRole] = "type";
return roles;
}
// MAIN
#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include <qqmlengine.h>
#include <qqmlcontext.h>
#include <qqml.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
#include "../uCtrlCore/Scenario/uscenario.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
UScenario scenarioModel;
scenarioModel.addTask(UTask());
scenarioModel.addTask(UTask());
scenarioModel.addTask(UTask());
QtQuick2ApplicationViewer viewer;
QQmlContext *ctxt = viewer.rootContext();
ctxt->setContextProperty("myScenarioModel", &scenarioModel);
viewer.setMainQmlFile(QStringLiteral("qml/uCtrlDesktopQml/main.qml"));
QObject *item = viewer.rootObject()->findChild<QObject*>("btn");
QObject::connect(item, SIGNAL(qmlSignal()), &scenarioModel, SLOT(cppSlot()));
viewer.showExpanded();
return app.exec();
}
There problem is with how you're storing the UTask objects in your UScenario class
QList<UTask> m_tasks
In simple terms when you call m_tasks.append it is attempting to allocate a new UTask object in the QList by copying the source UTask object via the default copy constructor. In the case of QAbstractListModel it is private. This is why you're at getting the error.
A simply solution is to change the storage type to a list of UTask pointers, QList< UTask* > along with the supporting code to properly release the memory when your UScenario object is destroyed.
For example here are some but not all of the changes but should point you in the right direction. Just make sure to change m_tasks to QList< UTask* > first:
int main(int argc, char *argv[])
{
...
UScenario scenarioModel;
scenarioModel.addTask( new UTask() );
scenarioModel.addTask( new UTask() );
scenarioModel.addTask( new UTask() );
...
return app.exec();
}
void UScenario::cppSlot()
{
// Used to test the insertion from UI
this->addTask( new UTask() );
}
// Change the signature to take a pointer
void UScenario::addTask( UTask* task )
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_tasks.append(task);
endInsertRows();
}
// Make sure you define a destructor for UScenario
UScenario::~UScenario()
{
QList< UTask* >::iterator task = m_tasks.begin();
while( m_tasks.end() != task )
{
// Release the memory associated with the task.
delete (*task);
++task;
}
m_tasks.clear();
}