Qt Filesystembrowser: How to change root directory and update view - c++

I am trying to extend the qt filesystembrowser example (Qt-Creator -> Welcome -> Examples -> filesystembrowser). I added a button to main.qml
Button {
id: button
x: 28
y: 12
text: qsTr("rootPath")
onClicked: {
view.model.setRoot("/home/myusername/test/")
view.update()
}
}
which should change the root directory. For this, I also added the following function
Q_INVOKABLE QModelIndex setRoot(QString newPath) {
qInfo() <<"root path "<< this->rootPath();
newPath.replace(0,7,"");
setRootPath(newPath);
}
After hitting the button twice, qInfo tells me that the root path is now /home/myusername/test/ but the view is not updated. What am I missing here?

The problem is that the rootIndex of the TreeView does not change because it does not update the view.
One solution is to create a rootIndex property that returns the index that is placed in the TreeView, this must be changed when a new path is established, for it is going to overwrite the setRootPath method and eliminate the rootPathIndex property that was sent through setContextProperty():
main.cpp
...
class DisplayFileSystemModel : public QFileSystemModel {
Q_OBJECT
Q_PROPERTY(QModelIndex rootIndex READ rootIndex WRITE setRootIndex NOTIFY rootIndexChanged)
public:
...
Q_INVOKABLE QModelIndex setRootPath(const QString &newPath){
QModelIndex ix = QFileSystemModel::setRootPath(newPath);
setRootIndex(ix);
return ix;
}
QModelIndex rootIndex() const{
return mRootIndex;
}
void setRootIndex(const QModelIndex &rootIndex){
if(mRootIndex == rootIndex)
return;
mRootIndex = rootIndex;
Q_EMIT rootIndexChanged();
}
Q_SIGNAL void rootIndexChanged();
private:
QModelIndex mRootIndex;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
qmlRegisterUncreatableType<DisplayFileSystemModel>("io.qt.examples.quick.controls.filesystembrowser", 1, 0,
"FileSystemModel", "Cannot create a FileSystemModel instance.");
DisplayFileSystemModel *fsm = new DisplayFileSystemModel(&engine); // change
fsm->setRootPath(QDir::homePath());
fsm->setResolveSymlinks(true);
engine.rootContext()->setContextProperty("fileSystemModel", fsm);
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
#include "main.moc"
main.qml
...
Row {
...
Repeater {
model: [ "rootPath", "None", "Single", "Extended", "Multi", "Contig."]
Button {
text: modelData
exclusiveGroup: eg
checkable: modelData != "rootPath"
checked: index === 1
onClicked: {
if(modelData != "rootPath")
view.selectionMode = index
else{
view.model.setRootPath("/home/myusername/test/")
}
}
}
}
}
...
TreeView {
id: view
anchors.fill: parent
anchors.margins: 2 * 12 + row.height
model: fileSystemModel
rootIndex: fileSystemModel.rootIndex //change
selection: sel
...
The complete example can be found in the following link.

Related

Qml/Qt c++: How to automatically update items in Qml ListView when the underlying Qt c++ ListModel changes?

Based on an example video from Qt (the ToDo example), I have created a ListView based Qt/Qml application.
Data for the ListView in Qml comes from a c++ class based on QAbstractListModel. The c++ model class is populated with data from a database.
This all works fine.
I have added a pushbutton in the Qml file which invokes a method in the c++ code that fetches data from a remote source (i.e. makes an https request). The https response data is of course asynchronous with the method that makes the http request.
Debug lines in the c++ https response handler confirm that the response is received okay. I save the response to the database.
If I close the application and re-open it, the new data is shown in the Qml list because, once again, on opening the application the c++ model is populated from the db.
But what I really need is that, after saving the data to the db in the https response handler, I also push the new data to the Qml ListView so that I don't have to restart the application to refresh the updated list data.
Unfortunately I don't know how to push the new data from c++ to Qml. I have tried a number of ways (signals from c++, slots in Qml, reading the updated list from c++, etc) but nothing has worked so far.
I know it has to do with the fact that the pushbutton starts an http request in c++ which is not handled synchronously but in a slot function which is the http response handler.
But unfortunately I don't know how to resolve this issue.
I would appreciate some help with this.
Note:
The following example is in fact off of an excellent YouTube Video on Qt model/view by Mitch Curtis Using C++ Models in QML - To-Do List!
But my code is very similar except that I want to add a button which changes the descriptions based on an https response:
The c++ files providing the listdata:
todolist.h and todolist.cpp
=============================================================
#ifndef TODOLIST_H
#define TODOLIST_H
#include <QObject>
#include <QVector>
struct ToDoItem
{
bool done;
QString description;
};
class ToDoList : public QObject
{
Q_OBJECT
public:
explicit ToDoList(QObject *parent = nullptr);
QVector<ToDoItem> items() const;
bool setItemAt(int index, const ToDoItem &item);
signals:
void preItemAppended();
void postItemAppended();
void preItemRemoved(int index);
void postItemRemoved();
public slots:
void appendItem();
void removeCompletedItems();
private:
QVector<ToDoItem> m_Items;
};
=======================================================================
#include "todolist.h"
ToDoList::ToDoList(QObject *parent) : QObject(parent)
{
m_Items.append({ true, QStringLiteral("Wash the car") });
m_Items.append({ false, QStringLiteral("Fix the sink") });
m_Items.append({ true, QStringLiteral("Wash the dishes") });
}
QVector<ToDoItem> ToDoList::items() const
{
return m_Items;
}
bool ToDoList::setItemAt(int index, const ToDoItem &item)
{
if (index <0 || index >= m_Items.size()) {
return false;
}
const ToDoItem &oldItem = m_Items.at(index);
bool nothingChanged = oldItem.done == item.done
&& oldItem.description == item.description;
if(nothingChanged) {
return false;
}
m_Items[index] = item;
return true;
}
void ToDoList::appendItem()
{
emit preItemAppended();
ToDoItem item;
item.done = false;
m_Items.append(item);
emit postItemAppended();
}
void ToDoList::removeCompletedItems()
{
for (int i = 0; i < m_Items.size();) {
if(!m_Items[i].done) {
++i;
continue;
}
//otherwise...
emit preItemRemoved(i);
m_Items.removeAt(i);
emit postItemRemoved();
}
}
###################################################################
The c++ files implementing the listmodel:
todomodel.h and todomodel.cpp
==================================================
#ifndef TODOMODEL_H
#define TODOMODEL_H
#include <QAbstractListModel>
class ToDoList;
class TodoModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(ToDoList *list READ list WRITE setList)
public:
explicit TodoModel(QObject *parent = nullptr);
enum {
DoneRole = Qt::UserRole,
DescriptionRole
};
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// Editable:
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
virtual QHash<int, QByteArray> roleNames() const override;
ToDoList *list() const;
void setList(ToDoList *newList);
private:
ToDoList *m_List;
};
#endif // TODOMODEL_H
===================================================================
#include "todomodel.h"
#include "todolist.h"
TodoModel::TodoModel(QObject *parent)
: QAbstractListModel(parent)
, m_List(nullptr)
{
}
int TodoModel::rowCount(const QModelIndex &parent) const
{
// For list models only the root node (an invalid parent) should return the list's size. For all
// other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
if (parent.isValid() || !m_List)
return 0;
return m_List->items().size();
}
QVariant TodoModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || !m_List)
return QVariant();
const ToDoItem item = m_List->items().at(index.row());
switch (role) {
case DoneRole:
return QVariant(item.done);
case DescriptionRole:
return QVariant(item.description);
}
return QVariant();
}
bool TodoModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!m_List) {
return false;
}
ToDoItem item = m_List->items().at(index.row());
switch (role) {
case DoneRole:
item.done = value.toBool();
break;
case DescriptionRole:
item.description = value.toByteArray();
break;
}
if (m_List->setItemAt(index.row(), item)) {
emit dataChanged(index, index, QVector<int>() << role);
return true;
}
return false;
}
Qt::ItemFlags TodoModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable;
}
QHash<int, QByteArray> TodoModel::roleNames() const
{
QHash<int, QByteArray> names;
names[DoneRole] = "done";
names[DescriptionRole] = "description";
return names;
}
ToDoList *TodoModel::list() const
{
return m_List;
}
void TodoModel::setList(ToDoList *newList)
{
beginResetModel();
if(m_List) {
m_List->disconnect();
}
m_List = newList;
if(!m_List) {
endResetModel();
return;
}
connect(m_List, &ToDoList::preItemAppended, this, [=]() {
const int index = m_List->items().size();
beginInsertRows(QModelIndex(), index, index);
});
connect(m_List, &ToDoList::postItemAppended, this, [=]() {
endInsertRows();
});
connect(m_List, &ToDoList::preItemRemoved, this, [=](int index) {
beginRemoveRows(QModelIndex(), index, index);
});
connect(m_List, &ToDoList::postItemRemoved, this, [=]() {
endRemoveRows();
});
endResetModel();
}
#########################################################################
The View File: ToDoList.qml
Displays the data provided by the c++ classes
============================================
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.3
import ToDo 1.0
ColumnLayout {
Frame {
Layout.fillWidth: true
ListView {
implicitWidth: 250
implicitHeight: 250
clip: true
anchors.fill: parent
model: TodoModel {
list: toDoList
}
delegate: RowLayout {
width: parent.width
CheckBox {
checked: model.done
onClicked: model.done=checked
}
TextField {
text: model.description
onEditingFinished: model.desciption = text
Layout.fillWidth: true
}
}
}
}
RowLayout {
Button {
text: qsTr("Add new item")
onClicked: toDoList.appendItem()
Layout.fillWidth: true
}
Button {
text: qsTr("Remove Completed Items")
onClicked: toDoList.removeCompletedItems()
Layout.fillWidth: true
}
}
}
Whenever the Model changes we should notify the View. Refer to this link:
QML views are automatically updated when the model changes. Remember
the model must follow the standard rules for model changes and notify
the view when the model has changed by using
QAbstractItemModel::dataChanged(),
QAbstractItemModel::beginInsertRows(), and so on. See the Model
subclassing reference for more information.
Here's how you can enhance your example to achieve a similar result:
On click of Fetch data button, after 3 seconds, the first row's description changes to https.
todolist.h:
signals:
void updateData();
public slots:
void fetchData();
todolist.cpp:
void ToDoList::fetchData()
{
QTimer::singleShot(3000, (QObject*)this, SIGNAL(updateData()));
}
todomodel.cpp:
connect(mList, &ToDoList::postItemRemoved, this, [=]() {
endRemoveRows();
});
connect(mList, &ToDoList::updateData, this, [=]() {
QVariant value = "https";
QModelIndex index = createIndex(0,0);
setData(index, value, DescriptionRole);
});
ToDoList.qml:
Button {
text: qsTr("Remove completed")
onClicked: toDoList.removeCompletedItems()
Layout.fillWidth: true
}
Button {
text: qsTr("Fetch data")
onClicked: toDoList.fetchData()
Layout.fillWidth: true
}
I have now fixed my issue thanks to #ArunKumarB.
The main tip from #ArunKumarB's comment was QModelIndex index = createIndex(0,0);
That is, how to convert a row index to a QModelIndex object.
The rest was mostly plumbing.

Dynamically filled combobox not showing text, list is undefined

I know there are a lot of different questions on stackoverflow and other forums about dynamically filling a combobox from c++, but out of all those questions i cant find an answer that i need. Currently i am fetching a list from my database in C++ and store that in my CompanyList class. My CompanyModel class uses that class and communicates to my qml ui. In my QML editor i set the model to CompanyModel.list and the textRole to the value i want from the struct.
The problem that im facing is that i am not getting any errors, but my combobox is still empty. I cant find the problem so i hope someone can look over the mistake i might have made.
My Company Struct
struct CompanyStruct {
int id;
QString name;
};
My Company List
CompanyList::CompanyList(QObject *parent) : QObject(parent)
{
appendItem({-1, "test company"});
appendItem({-2, "test company 2"});
}
QVector<CompanyStruct> CompanyList::items() const
{
return mItems;
}
bool CompanyList::setItemAt(int index, const CompanyStruct &item)
{
if (index < 0 || index >= mItems.size())
return false;
const CompanyStruct &oldItem = mItems.at(index);
if (item.id == oldItem.id && item.name == oldItem.name)
return false;
mItems[index] = item;
return true;
}
void CompanyList::appendItem()
{
emit preItemAppended();
CompanyStruct company;
company.id = -1;
company.name = "This is a test company!";
mItems.append(company);
emit postItemAppended();
}
void CompanyList::appendItem(CompanyStruct item)
{
emit preItemAppended();
mItems.append(item);
emit postItemAppended();
}
My Company Model
#include "companymodel.h"
#include "companylist.h"
CompanyModel::CompanyModel(QObject *parent)
: QAbstractListModel(parent)
, mList(nullptr)
{
}
int CompanyModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid() || !mList)
return 0;
return mList->items().size();
}
QVariant CompanyModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || !mList)
return QVariant();
const CompanyStruct item = mList->items().at(index.row());
switch (role) {
case IdRole:
return QVariant(item.id);
case NameRole:
return QVariant(item.name);
}
return QVariant();
}
bool CompanyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!mList)
return false;
CompanyStruct item = mList->items().at(index.row());
switch (role) {
case IdRole:
item.id = value.toInt();
break;
case NameRole:
item.name = value.toString();
break;
}
if (mList->setItemAt(index.row(), item)) {
emit dataChanged(index, index, QVector<int>() << role);
return true;
}
return false;
}
Qt::ItemFlags CompanyModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsSelectable;
}
QHash<int, QByteArray> CompanyModel::roleNames() const
{
QHash<int, QByteArray> names;
names[IdRole] = "id";
names[NameRole] = "name";
return names;
}
CompanyList *CompanyModel::list() const
{
return mList;
}
void CompanyModel::setList(CompanyList *list)
{
beginResetModel();
if (mList)
mList->disconnect(this);
mList = list;
if (mList) {
connect(mList, &CompanyList::preItemAppended, this, [=]() {
const int index = mList->items().size();
beginInsertRows(QModelIndex(), index, index);
});
connect(mList, &CompanyList::postItemAppended, this, [=]() {
endInsertRows();
});
}
endResetModel();
}
My Main.cpp
qmlRegisterType<CompanyModel>("Company", 1,0, "CompanyModel");
qmlRegisterUncreatableType<CompanyList>("Company", 1,0, "CompanyList", "CompanyList should not be created in QML");
CompanyList companyList;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("companyList", &companyList);
My combobox in QML
import Company 1.0
ComboBox {
Layout.column: 1
Layout.columnSpan: 3
Layout.row: 4
id: cbSelectKlant
implicitWidth: parent.width*0.6
implicitHeight: parent.height*0.05
background: Rectangle {
color: "white"
border.color: "#6abc93"
border.width: 3
width: parent.width
height: parent.height
}
textRole: "name"
model: CompanyModel.list
}
Normally you will create your models in C++ and expose them to QML.
You don't even have to use qmlRegisterType if you derive your presentation data from QObject.
For the sake of simplicity I made header only code except main.cpp
//company.h file:
#ifndef COMPANY_H
#define COMPANY_H
#include <QObject>
class Company : public QObject
{
Q_OBJECT
Q_PROPERTY(int id READ id WRITE setId CONSTANT)
Q_PROPERTY(QString name READ name WRITE setName CONSTANT)
Q_PROPERTY(QString displayName READ displayName CONSTANT)
public:
Company(QObject *parent=nullptr) : QObject(parent)
{}
int id() const { return mID; }
void setId(const int id) { mID = id; }
QString name() const { return mName; }
void setName(const QString& name) { mName = name; }
QString displayName() { return "Company: " + mName + ", ID: " + QString::number(mID); }
private:
int mID;
QString mName;
};
#endif // COMPANY_H
//companylistmodel.h file:
#ifndef COMPANYLISTMODEL_H
#define COMPANYLISTMODEL_H
#include "company.h"
#include <QAbstractListModel>
#include <vector>
class CompanyListModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles
{
CompanyRole = Qt::UserRole
};
CompanyListModel(QObject* parent = nullptr) : QAbstractListModel(parent)
{
mCompanies.push_back(new Company(this));
mCompanies.back()->setName("Google"); mCompanies.back()->setId(1);
mCompanies.push_back(new Company(this));
mCompanies.back()->setName("Microsoft"); mCompanies.back()->setId(2);
mCompanies.push_back(new Company(this));
mCompanies.back()->setName("CraftUnique"); mCompanies.back()->setId(3);
}
int rowCount(const QModelIndex& parent = QModelIndex()) const override { return mCompanies.size(); }
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { return QVariant::fromValue(mCompanies.at(index.row())); }
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override { return true; } // use property instead
virtual QHash<int, QByteArray> roleNames() const override
{
QHash<int, QByteArray> names;
names[CompanyRole] = "companyRole";
return names;
}
private:
std::vector<Company*> mCompanies;
};
#endif // COMPANYLISTMODEL_H
// main.cpp file:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQmlContext>
#include "companylistmodel.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQuickView view;
CompanyListModel clm;
view.rootContext()->setContextProperty("clm", &clm);
view.setSource(QStringLiteral("qrc:/main.qml"));
view.show();
return app.exec();
}
//main.qml file:
import QtQuick 2.12
import QtQuick.Controls 2.12
Rectangle {
anchors.fill: parent
color: "white"
Component.onCompleted: { console.log(clm) }
ComboBox {
id: cbSelectKlant
implicitWidth: parent.width
implicitHeight: 30
model: clm
textRole: "companyRole.displayName"
contentItem: Label {
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
text: cbSelectKlant.currentText
}
background: Rectangle {
color: "gray"
}
delegate: ItemDelegate {
implicitWidth: parent.width
implicitHeight: 30
contentItem: Label {
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
text: companyRole.name + " (" + companyRole.id + ")"
}
}
}
}
You can even assign values from QML like: companyRole.name = "SomeCompany"
And this won't call the setData method of your model, but directly update the property itself.
The first part of the problem is that you are not initializing mList in the code given (which I think is a bit short of a MRE ).
Change the constructor to the following:
CompanyModel::CompanyModel(QObject *parent)
: QAbstractListModel(parent)
, mList(new CompanyList(this))
{
}
And you are also allowing the qml-developer in you to instantiate the CompanyModel in QML (first line of main.cpp), which you are kinda doing, but not actually (last line of the ComboBox QML code). This way the CompanyModel is not instantiated, but you are still trying to get the list property from the class-definition, which will be undefined (IIC), leading to an empty ComboBox without error.
This can be solved by instantiating it in QML:
import Company 1.0
ComboBox {
Layout.column: 1
Layout.columnSpan: 3
Layout.row: 4
id: cbSelectKlant
implicitWidth: parent.width*0.6
implicitHeight: parent.height*0.05
background: Rectangle {
color: "white"
border.color: "#6abc93"
border.width: 3
width: parent.width
height: parent.height
}
textRole: "name"
model: theModel.list
CompanyModel {
id: theModel
}
}
However, I doubt this is what you want, since you will not be able to control theModel from C++. Which is probably also why you are setting the rootContext property, but you are not actually using it (last line of main.cpp).
So, change the QML to this:
import Company 1.0
ComboBox {
Layout.column: 1
Layout.columnSpan: 3
Layout.row: 4
id: cbSelectKlant
implicitWidth: parent.width*0.6
implicitHeight: parent.height*0.05
background: Rectangle {
color: "white"
border.color: "#6abc93"
border.width: 3
width: parent.width
height: parent.height
}
textRole: "name"
model: companyList.list
}
As a last advise, also change the first line of main.cpp to:
qmlRegisterUncreatableType<CompanyModel>("Company", 1,0, "CompanyModel", "CompanyModel should not be created in QML");

TableView and QAbstracTableModel when calls QQmlApplicationEngine from another class

I am trying to make the model QAbstractTableModel in cpp and connect to qml.
This code works well.
MyModel.h
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QAbstractTableModel>
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
explicit MyModel(QObject *parent = nullptr);
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<Animal> m_animals;
};
#endif // MYMODEL_H
MyModel.cpp
#include "MyModel.h"
#include <QDebug>
MyModel::MyModel(QObject *parent)
: QAbstractTableModel(parent)
{
qDebug() << __FUNCTION__;
addAnimal(Animal("Wolf", "Medium"));
addAnimal(Animal("Polar bear", "Large"));
addAnimal(Animal("Quoll", "Small"));
}
int MyModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_animals.size();
}
int MyModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 2;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
qDebug() << __FUNCTION__ << index.row() << index.column() << role;
if (!index.isValid())
return QVariant();
const Animal &animal = m_animals[index.row()];
switch (role) {
case TypeRole:
return animal.type();
case SizeRole:
return animal.size();
default:
break;
}
return QVariant();
}
void MyModel::addAnimal(const Animal &animal)
{
qDebug() << __FUNCTION__;
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_animals << animal;
endInsertRows();
}
QHash<int, QByteArray> MyModel::roleNames() const
{
qDebug() << __FUNCTION__;
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
main.cpp
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MyModel model;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
main_test.qml
import QtQuick 2.0
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Window {
id: main_view
width: 250
height: 600
visible: true
ListView {
id: list_view
width: 200; height: 250
model: myModel
delegate: Text { text: "Animal Test: " + type + ", " + size }
}
TableView {
id: table_view
objectName: "tableView"
width: 250; height: 250
anchors.top: list_view.bottom
model: myModel
TableViewColumn {
id: type_col
role: "type"
title: "Type"
width: 100
}
TableViewColumn {
id: size_col
role: "size"
title: "Size"
width: 100
}
}
}
It looks like this
But, if I change the main.cpp a little bit, the list view dispaly as normal, but not the table view.
main.cpp
#include "MainView.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MainView mainView;
return app.exec();
}
MainView.h
#ifndef MAINVIEW_H
#define MAINVIEW_H
#include <QObject>
#include <QQmlApplicationEngine>
class MainView: public QObject
{
Q_OBJECT
public:
explicit MainView(QObject *parent=nullptr);
void initializeView();
private:
QQmlApplicationEngine m_engine;
};
#endif // MAINVIEW_H
MainView.cpp
#include "MainView.h"
#include "MyModel.h"
#include <QQmlContext>
MainView::MainView(QObject *parent)
: QObject(parent)
{
initializeView();
}
void MainView::initializeView()
{
MyModel model;
m_engine.rootContext()->setContextProperty("myModel", &model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
It looks like this.
I really don't understand why this happens. What the difference between ListView and TableView in the second case? And how to fix it, i.e. make data display in the second case? Thank in advance.
The problem is in initializeView, model is a local variable in that scope, and every local variable is deleted from the memory when the function finishes executing. In the first case model will be eliminated when the application is closed, in the second case it will be eliminated when initializeView ends that is when the window is displayed, there are 2 possible solutions:
Create a pointer and to manage the memory we pass to MainView as a parent so that he removes it from memory (this last is a feature of the QObjects):
void MainView::initializeView()
{
MyModel *model = new MyModel(this);
m_engine.rootContext()->setContextProperty("myModel", model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
Make the model member of the class.
*.h
#ifndef MAINVIEW_H
#define MAINVIEW_H
#include <QObject>
#include <QQmlApplicationEngine>
class MainView: public QObject
{
Q_OBJECT
public:
explicit MainView(QObject *parent=nullptr);
void initializeView();
private:
QQmlApplicationEngine m_engine;
MyModel model{this};
};
#endif // MAINVIEW_H
*.cpp
...
void MainView::initializeView()
{
m_engine.rootContext()->setContextProperty("myModel", &model);
m_engine.load(QUrl(QStringLiteral("qrc:/resources/qmls/main_test.qml")));
}
To understand the behavior I have added more points of impression:
MyModel::~MyModel()
{
qDebug()<<"destructor";
}
TableView {
...
Component.onCompleted: {
console.log("completed table")
if(!timer.running)
timer.running = true
}
}
ListView {
...
Component.onCompleted: {
console.log("completed list")
if(!timer.running)
timer.running = true
}
}
Timer {
id: timer
interval: 0;
onTriggered: console.log(myModel, table_view.model, list_view.model)
}
And I get the following:
MyModel
addAnimal
addAnimal
addAnimal
roleNames
data 0 0 257
data 0 0 258
data 1 0 257
data 1 0 258
data 2 0 257
data 2 0 258
roleNames
qml: completed list
data 0 0 257
data 0 0 258
qml: completed table
destructor
qml: null null null
We note that ListView manages to load the data, whereas TableView in the middle of the load is called the destructor.
Possible explanation: I think that the ListView stores the data making a copy of them and only updates when the model notifies, should also be notified when the model is deleted to clean the data, it seems that this is a bug. On the other hand, TableView, being at the moment of loading and deleting the model, is null, so it is notified and cleans the data.
Doing another test:
void MainView::initializeView()
{
MyModel *model = new MyModel;
m_engine.rootContext()->setContextProperty("myModel", model);
m_engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
QTimer::singleShot(1000, model, &MyModel::deleteLater);
}
It is observed that both load the data correctly, and after a second the model is destroyed, but the one that is only notified is the TableView since it is the only one that cleans the data shown, and ListView does not.
my conclusion that it is a ListView bug.

Create Model for QML TreeView

I'm trying to use QML TreeView Model. The example from Qt doesn't include how to create the model. I read this post and tried to use the code from #Tarod but the result is not what I expected.
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "animalmodel.h"
#include <qqmlcontext.h>
#include <qqml.h>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
AnimalModel model;
model.addAnimal("wolf", "Medium");
model.addAnimal("Bear", "Large");
QQmlApplicationEngine engine;
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
animalmodel.h
#ifndef ANIMALMODEL_H
#define ANIMALMODEL_H
#include <QStandardItemModel>
class AnimalModel : public QStandardItemModel
{
Q_OBJECT //The Q_OBJECT macro must appear in the private section of a class definition that declares its own signals and slots or that uses other services provided by Qt's meta-object system.
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AnimalModel(QObject *parent = 0);
Q_INVOKABLE void addAnimal(const QString &type, const QString &size);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
protected:
QHash<int, QByteArray> roleNames() const;
};
#endif // ANIMALMODEL_H
animalmodel.cpp
#include "animalmodel.h"
AnimalModel::AnimalModel(QObject *parent)
: QStandardItemModel(parent)
{
}
void AnimalModel::addAnimal(const QString &type, const QString &size)
{
QStandardItem* entry = new QStandardItem();
entry->setData(type, TypeRole);
auto childEntry = new QStandardItem();
childEntry->setData(size, SizeRole);
entry->appendRow(childEntry);
appendRow(entry);
}
QVariant AnimalModel::data(const QModelIndex & index, int role) const {
QStandardItem *myItem = itemFromIndex(index);
if (role == TypeRole)
return myItem->data(TypeRole);
else if (role == SizeRole) {
if (myItem->child(0) != 0)
{
return myItem->child(0)->data(SizeRole);
}
}
return QVariant();
}
QHash<int, QByteArray> AnimalModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
menuBar: MenuBar {
Menu {
title: qsTr("&File")
MenuItem {
text: qsTr("&Open")
onTriggered: messageDialog.show(qsTr("Open Action Triggered"));
}
MenuItem {
text: qsTr("&Exit")
onTriggered: Qt.quit();
}
}
}
TreeView {
anchors.fill: parent
model: myModel
TableViewColumn {
title: "Name"
role: "type"
width: 300
}
TableViewColumn {
title: "Size"
role: "size"
width: 300
}
}
}
What I got is something like this:
Result
What I want to have is the animal size as a child of animal type.
Model sub-classing is one of the worst minefields in Qt. The advice is always to have it go through the model test (https://wiki.qt.io/Model_Test) to see if everything was implemented correctly.
On the other hand, in 90% of the cases you do not need to subclass a model at all as the default models provided by Qt work quite well. What I'd do is just use QStandardItemModel using, on the C++ side, only the QAbstractItemModel interface (i.e. force yourself to use QAbstractItemModel* model = new QStandardItemModel(/*parent*/);) this way, if in the future you feel like you really need to reimplement the model (for efficiency) you'll just need to change 1 line in your existing code.
In your case:
void AnimalModel::addAnimal(const QString &type, const QString &size)
{
if(columnCount()==0) insertColumn(0); // make sure there is at least 1 column
insertRow(rowCount()); // add a row to the root
const QModelIndex addedIdx = index(rowCount()-1,0);
setData(addedIdx, type, TypeRole); // set the root data
insertRow(rowCount(addedIdx),addedIdx ); // add 1 row ...
insertColumn(0,addedIdx ); // ... and 1 column to the added root row
setData(index(0,0,addedIdx), size, SizeRole); // set the data to the child
}

QML TreeView not updating at runtime after adding new data to the model

I have a QML TreeView that gets data through a QStandardItemModel. When the application is running, I press a button which adds a new entry. I know the data is changing, but the QML TreeView is not updating. I've also tried beginResetModel() and endResetModel(). The data is correctly displayed in the TreeView upon loading the application, but the TreeView does not change when modifying the data in the model.
treeviewmodel.cpp
#include <QDebug>
#include <QStandardItemModel>
#include "treeviewmodel.h"
TreeViewModel::TreeViewModel(QObject *parent) :
QStandardItemModel(parent)
{
m_roleNameMapping[TreeViewModel_Role_Name] = "name_role";
QStandardItem* entry;
entry = new QStandardItem(QString("my_entry"));
entry->setData("abc", TreeViewModel_Role_Name);
auto childEntry = new QStandardItem( "my_child_entry" );
childEntry->setData( "def",TreeViewModel_Role_Name);
entry->appendRow(childEntry);
appendRow( entry );
}
TreeViewModel& TreeViewModel::Instance()
{
static TreeViewModel instance; //Guaranteed to be destroyed
return instance;
}
void TreeViewModel::addEntry()
{
qDebug () << "Adding entry...";
QStandardItem* entry;
entry = new QStandardItem(QString("my_entry"));
entry->setData("Second Entry", TreeViewModel_Role_Name);
auto childEntry = new QStandardItem( "my_child_entry" );
childEntry->setData( "Second Entry Child",TreeViewModel_Role_Name);
entry->appendRow(childEntry);
appendRow( entry );
qDebug () << rowCount(); //Increases everytime I call the function
//Data is being added
}
QHash<int, QByteArray> TreeViewModel::roleNames() const
{
return m_roleNameMapping;
}
main.qml
import treeModel 1.0
...
MyTreeModel {
id: theModel
}
//Left Tree View
Rectangle {
id: leftView
Layout.minimumWidth: 50
width: 200
//Layout.fillWidth: true
color: "white"
TreeView {
id: treeView
anchors.fill: parent
model: theModel
TableViewColumn {
role: "name_role"
title: "Name"
}
TableViewColumn {
role: "description_role"
title: "Description"
}
}
}
ToolButton {
iconSource: "lock.png"
onClicked: {
treeviewmodel.addEntry()
}
}
main.cpp
QQmlContext* treeViewModelCtx = engine.rootContext();
treeViewModelCtx->setContextProperty("treeviewmodel", &TreeViewModel::Instance());
//Register types
qmlRegisterType<TreeViewModel>("treeModel", 1, 0, "MyTreeModel" );
I hope this example helps. Unfortunately, I don't have your whole code to see where the problem is.
Maybe the keys are the data and roleNames methods.
In this example, we also have a button called addButton to add new items.
main.cpp
#include "animalmodel.h"
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <qqmlcontext.h>
#include <qqml.h>
int main(int argc, char ** argv)
{
QGuiApplication app(argc, argv);
AnimalModel model;
model.addAnimal("Wolf", "Medium");
model.addAnimal("Polar bear", "Large");
model.addAnimal("Quoll", "Small");
QQmlApplicationEngine engine;
QQmlContext *ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &model);
engine.load(QUrl(QStringLiteral("qrc:/view.qml")));
return app.exec();
}
animalmodel.h
#ifndef ANIMALMODEL_H
#define ANIMALMODEL_H
#include <QStandardItemModel>
class AnimalModel : public QStandardItemModel
{
Q_OBJECT
public:
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AnimalModel(QObject *parent = 0);
Q_INVOKABLE void addAnimal(const QString &type, const QString &size);
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
protected:
QHash<int, QByteArray> roleNames() const;
};
#endif // ANIMALMODEL_H
animalmodel.cpp
#include "animalmodel.h"
AnimalModel::AnimalModel(QObject *parent)
: QStandardItemModel(parent)
{
}
void AnimalModel::addAnimal(const QString &type, const QString &size)
{
QStandardItem* entry = new QStandardItem();
entry->setData(type, TypeRole);
auto childEntry = new QStandardItem();
childEntry->setData(size, SizeRole);
entry->appendRow(childEntry);
appendRow( entry );
}
QVariant AnimalModel::data(const QModelIndex & index, int role) const {
QStandardItem *myitem = itemFromIndex(index);
if (role == TypeRole)
return myitem->data(TypeRole);
else if (role == SizeRole) {
if (myitem->child(0) != 0)
{
return myitem->child(0)->data(SizeRole);
}
}
return QVariant();
}
QHash<int, QByteArray> AnimalModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}