Exposing c++ model in qml - qt

I have created this c++ class and exposed to a Qml ListView however, it has some problems. I can view that there are items in the list, however I am not able to see any data (I see 25 empty buttons). In the console, the following message is displayed: "type is undefined". I have checked and type should correctly a full string;
ListView{
id: listView
anchors.fill: parent
model: postsModel
delegate: Component{
Button{
text: type
}
}
}
#include "listmodel.h"
ListModel::ListModel(QObject *parent)
: QAbstractListModel(parent)
{
}
QString ListItem::type() const
{
return m_type;
}
QVariantMap ListItem::dataMap() const
{
return m_dataMap;
}
void ListModel::addElement(const ListItem& element)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_elementsList << element;
endInsertRows();
}
QVariant ListModel::data(const QModelIndex & index, int role) const
{
if (index.row() < 0 || index.row() >= m_elementsList.count())
return QVariant();
const ListItem &item= m_elementsList[index.row()];
if (role == TypeRole)
return item.type();
else if (role == DataRole)
return item.dataMap();
return QVariant();
}
int ListModel::rowCount(const QModelIndex & parent) const {
return m_elementsList.count();
}
QHash<int, QByteArray> ListModel::roleNames()
{
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[DataRole] = "dataMap";
return roles;
}

Alright, I found the solution to my problem. The problem was that the ListView was not being updated by the model that I implemented. This was due to my mistake consisting in not calling beginInsertRows() and endInsertRows() functions.

Related

TableView does't update, but list into model is full

I am trying to update a table with data coming from an external process (into QML I lunch a CLI tool that returns a lot of strings and with that, I update the table). I am using TableView to show these data, and I wrote a Model class and a List class: the Model class extend QAbstractTableModel, indeed the list class extend QObject.
I am sure that the list is full (I print the contents with qDebug()), but the table is always empty! Can you help me?
I added some debug prints and I can see that the rowCount function of the model is called a lot of time and return the correct length of the list, but the data function is called only when index.row() is equal to zero! I don't understand why!
#ifndef PAYEELIST_H
#define PAYEELIST_H
#include <QObject>
#include <QList>
#include "payee.h"
class PayeeList : public QObject
{
Q_OBJECT
public:
explicit PayeeList(QObject *parent = nullptr);
QList<Payee> items() const;
// bool setItemAt (int index, const Payee &item);
void clear (void);
void append (const Payee& item);
signals:
void preItemAppended ();
void postItemAppended ();
void preItemRemoved (int index);
void postItemRemoved ();
//public slots:
// void appendItem ();
private:
QList<Payee> mItems;
};
#endif // PAYEELIST_H
#include "payeelist.h"
PayeeList::PayeeList(QObject *parent) : QObject(parent)
{
}
QList<Payee> PayeeList::items() const
{
return mItems;
}
void PayeeList::clear()
{
mItems.clear();
}
void PayeeList::append(const Payee &item)
{
emit preItemAppended();
mItems.append(item);
emit postItemAppended();
}
#ifndef PAYEEMODEL_H
#define PAYEEMODEL_H
#include <QAbstractTableModel>
#include "payeelist.h"
//class PayeeList;
class PayeeModel : public QAbstractTableModel
{
Q_OBJECT
Q_PROPERTY(PayeeList *list READ list WRITE setList)
public:
explicit PayeeModel(QObject *parent = nullptr);
enum
{
HeadingRole = Qt::UserRole + 1,
DataRole
};
enum
{
IdColumn = 0,
NameColumn,
TypeColumn
};
// 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;
virtual QHash<int, QByteArray> roleNames() const override;
PayeeList *list (void) const;
void setList (PayeeList *list);
private:
PayeeList *mList;
};
#endif // PAYEEMODEL_H
#include "payeemodel.h"
PayeeModel::PayeeModel(QObject *parent)
: QAbstractTableModel(parent),
mList(nullptr)
{
}
int PayeeModel::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() || !mList)
return 0;
qDebug() << "LIST SIZE:" << mList->items().size();
return mList->items().size() + 1;
}
int PayeeModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid() || !mList)
return 0;
return 3;
}
QVariant PayeeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || !mList)
return QVariant();
switch (role)
{
case DataRole:
{
qDebug() << "INDEX ROW:" << index.row();
if (index.row() > 0)
{
const Payee item = mList->items().at(index.row() - 1);
switch (index.column())
{
case IdColumn:
return QString::number(item.id());
break;
case NameColumn:
return QString(item.name());
break;
case TypeColumn:
return QString(item.type().name());
break;
}
}
else
{
switch (index.column())
{
case IdColumn:
return QString("Id");
break;
case NameColumn:
return QString("Name");
break;
case TypeColumn:
return QString("Type");
break;
}
}
}
break;
case HeadingRole:
if (index.row() == 0)
{
return true;
}
else
{
return false;
}
break;
default:
break;
}
return QVariant();
}
QHash<int, QByteArray> PayeeModel::roleNames() const
{
QHash<int, QByteArray> names;
names[HeadingRole] = "tableheading";
names[DataRole] = "tabledata";
return names;
}
PayeeList *PayeeModel::list (void) const
{
return mList;
}
void PayeeModel::setList (PayeeList *list)
{
beginResetModel();
if (mList)
{
mList->disconnect(this);
}
mList = list;
if (mList)
{
connect(mList, &PayeeList::preItemAppended, this, [=]()
{
const int index = mList->items().size();
beginInsertRows(QModelIndex(), index, index);
});
connect(mList, &PayeeList::postItemAppended, this, [=]()
{
endInsertRows();
});
}
endResetModel();
}
The QML file is:
import QtQuick 2.12
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.3
import PlutoTool 1.0
Rectangle {
id: payeePage
Layout.fillWidth: true
property var title: "TITLE"
TableView {
columnSpacing: 1
rowSpacing: 1
anchors.fill: parent
clip: false
property var columnWidths: [50, (parent.width - 220), 150]
columnWidthProvider: function (column) { return columnWidths[column] }
model: PayeeModel {
list: lPayeeList
}
delegate: Rectangle {
implicitWidth: 200
implicitHeight: 30
border.color: "black"
border.width: 0
color: (tableheading == true) ? "#990033":"#EEEEEE"
Text {
text: model.tabledata
color: (tableheading == true) ? "#FFFFFF":"#000000"
font.bold: (tableheading == true) ? true : false
anchors.centerIn: parent
}
Component.onCompleted: {
console.log(model.tabledata);
}
}
}
}

qml TableView itemdelegate not firing (using a QAbstractTableModel)

Im trying to get my first QML TableView to work in Qt 5.2 (since we are stuck on that
version right now at work) using a QAbstractTableModel on the backend.
My main issue is that for some reason the itemDelegate is never firing so
I never see anything in the View except the outline of the TableView.
I have also verified that theData_ is filled with 2 dimensional numbers
in every row/column in the constructor and I do an emit layoutChanged()
as well as an emit dataChanged() in the constructor.
I realize I have no error checking for an invalid QModelIndex in the data() call
at this time.
I also did not implement index() at all.
Also is there any need to use a ROLE here?
The data Im displaying is a single integer (as a QString) per cell, nothing more at this time.
Thanks for your help
qml:
TableView {
width: 600
height: 600
model: myModel
visible: true
itemDelegate: Rectangle {
color: "lightgray"
width: 100
height: 20
Text {
text: styleData.value
color: "black"
}
}
}
relevant code from subclassed QAbstractTableModel:
int MyModel::rowCount(const QModelIndex&) const
{
return 10;
}
int MyModel::columnCount(const QModelIndex&) const
{
return 3;
}
QVariant MyModel::data(const QModelIndex& index, int role) const
{
const int row = index.row();
const int col = index.column();
return QString("%1").arg(this->theData_[col][row]);
}
Before Qt 5.12 there was only a TableView component that belongs to Qt QuickControl 1 that only supports a list type model where each column reflects the information of a role so this is probably your problem since you have not created any TableViewColumn. On the other hand, as of >= Qt5.12, another TableView already exists, which it supports as a table type model.
mymodel.h
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QAbstractListModel>
struct Data{
int number;
QString text;
};
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
enum CustomRoles{
NumberRole = Qt::UserRole,
TextRole
};
explicit MyModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
private:
QList<Data> m_data;
};
#endif // MYMODEL_H
mymodel.cpp
#include "mymodel.h"
MyModel::MyModel(QObject *parent)
: QAbstractListModel(parent)
{
// for test
for(int i=0; i< 10; i++){
Data d{i, QString::number(i)};
m_data.push_back(d);
}
}
int MyModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_data.count();
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if(role == NumberRole)
return m_data.at(index.row()).number;
if(role == TextRole)
return m_data.at(index.row()).text;
return QVariant();
}
QHash<int, QByteArray> MyModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NumberRole] = "number";
roles[TextRole] = "text";
return roles;
}
main.qml
// ...
TableView {
width: 600
height: 600
model: myModel
visible: true
TableViewColumn {
role: "number"
title: "Number"
width: 100
}
TableViewColumn {
role: "text"
title: "Text"
width: 200
}
itemDelegate: Rectangle {
color: "lightgray"
width: 100
height: 20
Text {
text: styleData.value
color: "black"
}
}
}
// ...

How to apply and view user roles?

I'm trying to create custom model, and want it to work with custom roles. But I dont really understand how to do it. Also, i want to use my model with a qt widget, not with QML View. How roles are applying to certain items?
How to setup ListView, so that could work with my custom roles?
I know that I need to create enum, and reimplement roleNames function
my model .h file
class ListModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
ListModel();
virtual ~ListModel() override;
enum CustomRoles{
RoleType=Qt::UserRole+1,
ButtonRole,
CheckboxRole,
};
protected:
QList<BaseItems*> itemList;
QHash<int, QByteArray> _roles;
// int _RowCount = 0;
public:
void Add(BaseItems* item);
BaseItems* getItem(int index);
void clear();
int count() const;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
Q_SIGNALS:
void countChanged();
};
my model .cpp file
ListModel::ListModel() : QAbstractListModel()
{
}
ListModel::~ListModel()
{
itemList.clear();
}
void ListModel::Add(BaseItems *item)
{
beginInsertRows(QModelIndex(),itemList.count(),itemList.count());
itemList.append(item);
endInsertRows();
Q_EMIT countChanged();
}
BaseItems* ListModel::getItem(int index)
{
return itemList.at(index);
}
void ListModel::clear()
{
qDeleteAll(itemList);
itemList.clear();
}
int ListModel::count() const
{
return rowCount();
}
int ListModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return itemList.count();
}
QVariant ListModel::data(const QModelIndex &index, int role) const
{
ItemButton *button = dynamic_cast<ItemButton*>(itemList.at(index.row()));
if (!index.isValid())
return QVariant();
if (index.row() >= itemList.count())
return QVariant();
switch (role)
{
case Qt::DisplayRole:{
return QVariant::fromValue(button->Text);}
case ButtonRole:{
return QVariant::fromValue(button->Text);}
}
return QVariant();
}
QHash<int, QByteArray> ListModel::roleNames() const {
QHash<int, QByteArray> role;
role[RoleType] = "first";
role[ButtonRole] = "last";
return role;
}
Instead of "first" and "last" better you name the roles meaningfully:
QHash<int, QByteArray> ListModel::roleNames() const {
QHash<int, QByteArray> role;
role[RoleType] = "roleType";
role[ButtonRole] = "buttonRole";
return role;
}
So these quoted names will be used. If you want to display the data from this model in QML you can do something like this:
ListView {
width: 100
height: 500
model: listModel
delegate: Text {
text: model.roleType + model.buttonRole
}
}
listModel object can be initialized in C++ and can be passed to QML using
view->rootContext()->setContextProperty("listModel", listModel);
or you can make an instance of the ListModel in QML, but in the cpp file you will have to register your ListModel type
qmlRegisterType<ListModel>("ListModel", 1, 0, "ListModel");
then in the qml file:
import ListModel 1.0
finally creating an instance of the model by
ListModel {
id: listModel
}

Dynamic translation of combobox qml

I've added translation to my qt/qml app using this tutorial
https://retifrav.github.io/blog/2017/01/04/translating-qml-app/
https://github.com/retifrav/translating-qml
And most seems to work well except that the values of the combobox dont get update with dynamic translate.
Im using qt 5.11.2.
By a combobox i mean this:
ComboBox {
textRole: "text"
Layout.fillWidth: true
model: ListModel {
Component.onCompleted: {
append({text: qsTr("None")})
append({text: qsTr("Subpanel")})
append({text: qsTr("All")})
}
}
}
ComboBox {
textRole: "text"
Layout.fillWidth: true
model: ListModel {
ListElement{text: qsTr("None")}
ListElement{text: qsTr("Subpanel")}
ListElement{text: qsTr("All")}
}
}
None of them gets updated.
I've done some research and found this on bug reports
https://bugreports.qt.io/browse/QTBUG-68350
This seems to get fixed on 5.12, but for various reason we need to keep the same version, is there a way i can fix it for this version? (5.11.2)
EDIT: I dont find a way to translate comboBox. Is there another way of doing translations? even if it means to open a new instance of the app? Can someone point me to a link? Can't find a way to do this.
EDIT2: Is there a way to force the model of the combobox to be updated by javascript? when the changeLanguage() method is called?
note: As a complaint i'm finding the support / community to get answers for Qt problems terrible, really bad, but maybe its my problem.
One option is to add a QAbstracstListModel which does the translation. I made myself a base class, which can be inherited. This also gives you a lot of flexibility for converting a selected item to a value (in this example I am using int, but you can make it anything), which is connected to a C++ backend (I used backend.selectedPanel for your example)
<< Edit: See answer of Felix for nice addition of dynamic translation >>
base header:
class baseEnum : public QAbstractListModel
{
Q_OBJECT
public:
virtual int rowCount(const QModelIndex &parent) const = 0;
virtual QVariant data(const QModelIndex &index, int role) const = 0;
QHash<int, QByteArray> roleNames() const;
Q_INVOKABLE int getIndex(int value);
Q_INVOKABLE int getValue(int index);
}
base cpp:
QHash<int, QByteArray> baseEnum::roleNames() const
{
QHash<int, QByteArray> result;
result.insert(Qt::DisplayRole, "text");
result.insert(Qt::UserRole + 1, "value");
return result;
}
int baseEnum::getIndex(int value)
{
for(int i=0;i<rowCount(QModelIndex());++i)
if(data(createIndex(i, 0, nullptr), Qt::UserRole + 1).toInt() == value)
return i;
return -1;
}
int baseEnum::getValue(int index)
{
return data(createIndex(index, 0, nullptr), Qt::UserRole + 1).toInt();
}
derived header:
class FancyEnum : public baseEnum
{
Q_OBJECT
public:
int rowCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
};
derived cpp:
int FancyEnum::rowCount(const QModelIndex &parent) const
{
if(!parent.isValid())
return 5;
return 0;
}
QVariant FancyEnum::data(const QModelIndex &index, int role) const
{
switch(index.row())
{
case 0: return role == Qt::DisplayRole ? QVariant(tr("None")) : QVariant(0);
case 1: return role == Qt::DisplayRole ? QVariant(tr("Subpanel")) : QVariant(1);
case 2: return role == Qt::DisplayRole ? QVariant(tr("All")) : QVariant(2);
}
return role == Qt::DisplayRole ? QVariant(QString("<%1>").arg(index.row())) : QVariant(0);
}
Register it somewhere:
qmlRegisterType<FancyEnum>("your.path.here", 1, 0, "FancyEnum");
usage in QML:
ComboBox {
model: FancyEnum { id: myEnum }
textRole: "text"
currentIndex: myEnum.getIndex(backend.selectedPanel) : 0
onActivated: backend.selectedPanel = myEnum.getValue(index) }
}
This is an addition to #Amfasis answer. It extends the very useful "baseEnum" model by adding the capability to detect and react to restranslation events
For the GUI to actually detect that the text was changed after a restranslation, the model must "notify" the gui that data has changed. However, to do that, the model must know when data changed. Thankfully, Qt has the LanguageChange event to do so. The following code catches that event and uses it to notify the gui of the data change.
// in the header file:
class baseEnum : public QAbstractListModel
{
Q_OBJECT
public:
// ... all the stuff from before
bool event(QEvent *event);
};
// and in the cpp file:
bool baseEnum::event(QEvent *ev)
{
if(ev) {
switch(ev->type()) {
case QEvent::LanguageChange:
// notifiy models that the display data has changed for all rows
emit dataChanged(index(0),
index(rowCount(QModelIndex{}) - 1),
{Qt::DisplayRole});
break;
}
}
return QAbstractListModel::event(ev);
}
Unfortunately, I can't test all the cases at the moment (and I don't have an access to the Qt 5.11.2), but I think this should work:
ComboBox {
textRole: "text"
Layout.fillWidth: true
displayText: qsTr(currentText)
model: ListModel {
ListElement{text: "None"}
ListElement{text: "Subpanel"}
ListElement{text: "All"}
}
delegate: Button {
width: ListView.view.width
text: qsTr(model.text)
}
}
Or, another way is to re-create a model when language is changed:
ComboBox {
id: combobox
textRole: "text"
Layout.fillWidth: true
model: ListModel {
dynamicRoles: true
}
Component.onCompleted: {
reload()
}
Connections {
target: trans // this is a translator from a git project you are referring to
onLanguageChanged: {
combobox.reload()
}
}
function reload() {
var i = combobox.currentIndex
combobox.model = [
{text: qsTr("None")},
{text: qsTr("Subpanel")},
{text: qsTr("All")}
]
combobox.currentIndex = i
}
}

Cannot refresh QT's view while changing model

I am learning how to integrate qml with c++.
I've implemented a custom model class StringListModel, which inherits QAbstratListModel.
And, I have a main.qml to use StringListModel.
QML view can show initial values correctly.
I have another thread to change model periodically.
I do use beginResetModel() and endResetModel() to indicate model changed.
However, while the model keeps been changed, the view didn't update.
Here is my source code.
Please teach me what went wrong.
THANKS!
=== main.qml ===
Rectangle {
width: 360
height: 360
Grid {
id: gridview
columns: 2
spacing: 20
Repeater {
id: repeater
model: StringListModel {
id:myclass
}
delegate: Text {
text: model.title
}
}
}
=== custom class.h ===
class StringListModel : public QAbstractListModel{
Q_OBJECT
public:
StringListModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
QHash<int, QByteArray> roleNames() const;
void newItem();
private:
QStringList stringList;
};
class UpdateThread : public QThread {
Q_OBJECT
StringListModel *mMyClass;
public:
UpdateThread(StringListModel * myClass);
protected:
void run();
};
=== custom class.cpp ===
StringListModel::StringListModel(QObject *parent) : QAbstractListModel(parent)
{
stringList << "one" << "two" << "three";
QThread *thread = new UpdateThread(this);
thread->start();
}
int StringListModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
}
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= stringList.size())
return QVariant();
if (role == Qt::UserRole + 1)
return stringList.at(index.row());
else
return QVariant();
}
QHash<int, QByteArray> StringListModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[Qt::UserRole + 1] = "title";
return roles;
}
void StringListModel::newItem()
{
qDebug() << "size: " << stringList.size();
beginResetModel();
stringList << "new";
endResetModel();
}
UpdateThread::UpdateThread(StringListModel * myClass)
{
mMyClass = myClass;
}
void UpdateThread::run()
{
while (true) {
mMyClass->newItem();
msleep(1000);
}
}
You're dutifully ignoring the matters of synchronizing access to the model. When you have any object that's accessed from more than one thread (even something as "simple" as a raw pointer), you must deal with the ramifications of such access.
I suggest you don't mess with threads unless you have measurements showing that you'll benefit.

Resources