QML ListView and Segmentation fault - c++

I'm using a QML ListView with a SectionScroller & QAbstractListModel. I noticed that I get a segmentation fault in memcpy (never called explicitly) when I'm normally scrolling (without usage of SectionScroller)
Do you have any idea why it's happening?
I tried to reproduce it, and now the segmentation fault is
0x402f9c3a in ?? () from /usr/lib/libQtScript.so.4
0x402f9c3a: ldrh r1, [r7, r3]
The debug symbols are there, though no valuable info is dumped. The other time the Segfault was
0x0000cab8 in QBasicAtomicInt::ref (this=0x0)
at /usr/include/QtCore/qatomic_armv6.h: 119
It's strange since AFAIK N900's processor is armv7 /edit: on N950 it uses the same and in Qt sources are only for ARM qatomic_arm.h and qatomic_armv6.h so it should be ok.
ListView{
id: irrview
width: parent.width
model: irregulars
anchors.top: caption.bottom
anchors.bottom: parent.bottom
spacing: 5
clip: true
section.criteria: ViewSection.FirstCharacter
section.property: "form0"
delegate: Rectangle{
id: del
property int fontSize: 20
height: 60
width: parent.width
color: "#E0E1E2"
Row{
height: parent.height
width: parent.width - 10
anchors.horizontalCenter: parent.horizontalCenter
property real columnWidth: (width - 10) / 3
property int rad: 10
spacing: 5
Rectangle{
height: parent.height
width: parent.columnWidth
radius: parent.rad
color: "lightsteelblue"
Text{
anchors.centerIn: parent
text: form0
font.pointSize: del.fontSize
}
}
Rectangle{
height: parent.height
width: parent.columnWidth
radius: parent.rad
color: "lightsteelblue"
Text{
anchors.centerIn: parent
text: form1
font.pointSize: del.fontSize
}
}
Rectangle{
height: parent.height
width: parent.columnWidth
radius: parent.rad
color: "lightsteelblue"
Text{
anchors.centerIn: parent
text: form2
font.pointSize: del.fontSize
}
}
}
}
}
The model is:
#ifndef IRREGULARLISTWRAPPER_H
#define IRREGULARLISTWRAPPER_H
#include <QObject>
#include <QList>
#include <QAbstractListModel>
#include <QMap>
#include "IrregularVerb.h"
#include "AbstractIrregularList.h"
class IrregularListWrapper : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString langName READ getLangName NOTIFY langChanged)
Q_PROPERTY(int count READ rowCount NOTIFY langChanged)
Q_ENUMS(Language)
public:
Q_INVOKABLE int rowCount(const QModelIndex& = QModelIndex()) const { return db->count(); }
Q_INVOKABLE QObject* get(int index) const {return db->at(index);}
QVariant data(const QModelIndex &index, int role) const;
enum Language
{
English = 0,
German = 1
};
enum IrregularVerbRoles
{
Form0Role = Qt::UserRole + 1,
Form1Role,
Form2Role
};
IrregularListWrapper();
// ~IrregularListWrapper() { delete db; }
// QList<QObject*> getdb() const { return *db; }
QString getLangName() const { return langName; }
Q_INVOKABLE void changeLang(Language l) { beginResetModel(); db = 0; /*QList<IrregularVerb*>();*/ setLang(l); endResetModel(); }
static QMap<Language, QString> plugins;
signals:
void langChanged();
protected:
void setLang(Language);
//QList<IrregularVerb*> db;
QString langName;
AbstractIrregularList * db;
};
#endif // IRREGULARLISTWRAPPER_H
QMap<IrregularListWrapper::Language, QString> IrregularListWrapper::plugins;
IrregularListWrapper::IrregularListWrapper()
{
QHash<int, QByteArray> roles;
roles[Form0Role] = "form0";
roles[Form1Role] = "form1";
roles[Form2Role] = "form2";
const QString pluginPath = "/opt/MeeIrregulars/share/lib%1.so";
plugins[English] = pluginPath.arg("english");
plugins[German] = pluginPath.arg("german");
setRoleNames(roles);
setLang(German);
}
QVariant IrregularListWrapper::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) return QVariant();
const IrregularVerb* verb = db->at(index.row());
switch (role)
{
case Form0Role:
return verb->getForm0();
break;
case Form1Role:
return verb->getForm1();
break;
case Form2Role:
return verb->getForm2();
break;
}
return QVariant();
}
void IrregularListWrapper::setLang(Language l)
{
QPluginLoader loader(plugins[l]);
db = qobject_cast<AbstractIrregularList*>(loader.instance());
if (db == 0) db = new AbstractIrregularList;
switch (l)
{
case English:
langName = "English";
break;
case German:
langName = "German";
break;
}
emit langChanged();
}
class IrregularVerb : public QObject
{
Q_OBJECT
Q_PROPERTY(QString form0 READ getForm0 NOTIFY formChanged)
Q_PROPERTY(QString form1 READ getForm1 NOTIFY formChanged)
Q_PROPERTY(QString form2 READ getForm2 NOTIFY formChanged)
public:
QString forms[3];
QString getForm0() const { return forms[0]; }
QString getForm1() const { return forms[1]; }
QString getForm2() const { return forms[2]; }
IrregularVerb(QString a, QString b, QString c) { forms[0] = a; forms[1] = b; forms[2] = c; }
signals:
void formChanged();
};
Backtrace:
#0 QBasicAtomicInt::ref (this=0x18)
#1 QString (this=0xbe88d2a0, other=...)
#2 IrregularVerb::getForm2 (this=0x9e6de8)
#3 IrregularVerbWrapper::data(this=0x9e31b8, index=..., role=35) // the model// some calls to libQtDeclarative
Thanks.

It was a problem with ownership. The elements returned by get were owned by JS and automatically destroyed. See this answer for more information.

Related

Is there a way to call the tableview again from qml?

I want to output the db value corresponding to that value in the table view when the button for that value is pressed.
It is possible to receive the db from the cpp file and input it into a vector, but I do not know how to call the table view where this vector value will be entered again when the button is pressed....
producttable.h
#ifndef PRODUCTTABLE_H
#define PRODUCTTABLE_H
#include <QObject>
#include <QAbstractTableModel>
#include <QSqlTableModel>
#include "sqlquerymodel.h"
class ProductTable : public QAbstractTableModel
{
Q_OBJECT
enum TableRoles{
TableDataRole = Qt::UserRole + 1,
HeadingRole
};
public:
explicit ProductTable(QObject *parent = nullptr);
int rowCount(const QModelIndex & = QModelIndex()) const override;
int columnCount(const QModelIndex & = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
signals:
void qmlSignal(QString msg);
public slots:
void cppSlot(const QString &msg);
private:
QVector<QVector<QString>> productTable;
SqlQueryModel *db;
QString c_name=nullptr;
};
#endif // PRODUCTTABLE_H
producttable.cpp
#include "producttable.h"
#include <QQmlApplicationEngine>
ProductTable::ProductTable(QObject *parent)
: QAbstractTableModel{parent}
{
productTable.clear();
QSqlQuery query;
QString c_code;
if(c_name==nullptr){
query.prepare("select product_name, product_sale from product");
query.exec();
QVector<QString> e;
int i=0;
while(query.next()){
e.append(query.value(0).toString() + "\n" + query.value(1).toString() + "원");
i++;
if(i==4){
productTable.append(e);
e.clear();
i=0;
}
}
}
else{
query.prepare("select cartegory_code from cartegory where cartegory_name = '"+c_name+"'");
query.exec();
query.next();
c_code=query.value(0).toString();
query.prepare("select product_name, product_sale from product where cartegory_code = "+c_code+"");
query.exec();
QVector<QString> e;
int i=0;
while(query.next()){
e.append(query.value(0).toString() + "\n" + query.value(1).toString() + "원");
i++;
if(i==4){
productTable.append(e);
e.clear();
i=0;
}
}
}
}
int ProductTable::rowCount(const QModelIndex &) const{
return productTable.size();
}
int ProductTable::columnCount(const QModelIndex &) const{
return productTable.at(0).size();
}
QVariant ProductTable::data(const QModelIndex &index, int role) const{
switch(role){
case TableDataRole:
return productTable.at(index.row()).at(index.column());
case HeadingRole:
if(index.row()==0){
return true;
} else{
return false;
}
default:
break;
}
return QVariant();
}
QHash<int, QByteArray> ProductTable::roleNames() const{
QHash<int, QByteArray> roles;
roles[TableDataRole] = "producttabledata";
roles[HeadingRole] = "heading";
return roles;
}
void ProductTable::cppSlot(const QString &msg){
c_name=msg;
productTable.clear();
QString c_code;
QSqlQuery query;
query.prepare("select cartegory_code from cartegory where cartegory_name = '"+c_name+"'");
query.exec();
query.next();
c_code=query.value(0).toString();
query.prepare("select product_name, product_sale from product where cartegory_code = "+c_code+"");
query.exec();
QVector<QString> e;
int i=0;
while(query.next()){
e.append(query.value(0).toString() + "\n" + query.value(1).toString() + "원");
i++;
if(i==4){
productTable.append(e);
e.clear();
i=0;
}
}
//qDebug() << productTable;
//QQmlApplicationEngine engine;
//engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
//productTable.refresh();
qDebug()<< c_name;
//beginResetModel();
//endResetModel();
}
main.qml
TableView {
id: producttableview
x: 5
y: 5
width: 380
height: 435
columnSpacing: 10
rowSpacing: 80
clip: true
property var columnWidths: [87.5, 87.5, 87.5, 87.5]
columnWidthProvider: function (column) { return columnWidths[column] }
property alias tableVerticalBar: productVerticalBar
ScrollBar.vertical: ScrollBar {
id: productVerticalBar
policy:ScrollBar.AlwaysOn
}
model: ProductTable {}
delegate: Rectangle{
height: 100
Button {
width: 88
height: 115
Text {
text: producttabledata
font.pointSize: 9
anchors.bottom: parent.bottom
}
onClicked: {
//saletext2.text = categorytabledata
}
}
}
}
When clicking a button in the table view below, I want the table view above to change to the vector value changed in cpp.
TableView {
x: 5
y: 5
width: 380
height: 100
columnSpacing: 10
rowSpacing: 10
clip: true
property var columnWidths: [65, 65, 65, 65, 65]
columnWidthProvider: function (column) { return columnWidths[column] }
property alias tableVerticalBar: categoryVerticalBar
ScrollBar.vertical: ScrollBar {
id: categoryVerticalBar
policy:ScrollBar.AlwaysOn
}
model: CategoryTable {}
delegate: Rectangle{
border.color: "gray"
border.width: 0.5
Button {
id:category_btn
width: 70
height: 50
text: categorytabledata
font.pointSize: 9
onClicked: {
saletext2.text = categorytabledata
//wwwindow.close()
qmlSignal(categorytabledata)
//salelistwindow.show()
}
}
}
}
For example, I want to display 10 digits in the table view when button 1 is clicked and 20 digits when button 2 is clicked.
In my code, when a button corresponding to the category of convenience store is clicked, for example, beverage, I want to receive only product information corresponding to the beverage category in the product table and print it on the table.
It works fine until I get it from the db, but I don't know how to call the table again...

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");

How I can i have access role of QAbstractListModel that is an Array?

Maybe my question isn't clear but i hope that with the code my problem become more clear.
I have a created these two structs and i use them in FormModel class :
struct Tile
{
QString name;
QString color;
}; Q_DECLARE_METATYPE(Tile)
struct Form
{
QString nameForm;
Tile grid[9] ; // i want for each form 4 tile that in qml are 4 rectangles
}; Q_DECLARE_METATYPE(Form)
class FormModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit FormModel(QObject *parent = 0);
~FormModel();
enum dashBoardRoles {
NameForm=Qt::UserRole+1,
Grid,
};
int rowCount(const QModelIndex &parent=QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
Q_INVOKABLE bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE;
Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE;
QHash<int, QByteArray> roleNames() const;
signals:
public slots:
private:
QList<Form> dashboard;
};
This is data method of QAbstractListModel:
QVariant FormModel::data(const QModelIndex &index, int role) const
{
if(index.row()>0 && index.row() >= dashboard.count())
return QVariant();
Form dashTemp = dashboard[index.row()];
if(role== NameForm)
return dashTemp.nameForm;
if(role== Grid)
{
QVariant gridVect = QVariant::fromValue(dashTemp.grid);
return gridVect;
}
return QVariant();
}
QHash<int, QByteArray> FormModel::roleNames() const
{
QHash <int,QByteArray> roles;
roles [NameForm]="nameForm";
roles [Grid]="grid";
return roles;
}
this is the qml code:
Window {
id:app
visible: true
width: 480
height: 800
title: qsTr("Hello World")
ListView {
model: myForms // i have defined the name in main.cpp
anchors.fill: parent
delegate: ColumnLayout{
spacing: 30
// nome Form
Text{
text: nameForm
font.pointSize:20
color: "red"
font.capitalization: Font.Capitalize
}
// grigla mattonelle
GridView {
id:grid
width: 300; height: 300
cellWidth: 100 ; cellHeight: 100
model: 9
delegate: Item{
width : grid.cellWidth
height: grid.cellHeight
Rectangle{
anchors.centerIn: parent
width:parent.width-10
height: parent.height-10
color: grid[index].color
}
}
}
}
}
}
How i can get the color from the data of role Grid? that in qml i call with grid[index].color....
QML does not directly recognize the struct, for this you have to use Q_GADGET with Q_PROPERTY, it is also advisable to use QVariantList instead of an array since QML can cast it directly. All the above I have implemented it in the following code:
formmodel.h
#ifndef FORMMODEL_H
#define FORMMODEL_H
#include <QAbstractListModel>
struct Tile
{
Q_GADGET
Q_PROPERTY(QString name MEMBER name)
Q_PROPERTY(QString color MEMBER color)
public:
QString name;
QString color;
Tile(const QString& name="", const QString& color=""){
this->name = name;
this->color = color;
}
};
Q_DECLARE_METATYPE(Tile)
struct Form
{
Q_GADGET
Q_PROPERTY(QString nameForm MEMBER nameForm)
Q_PROPERTY(QVariantList grid MEMBER grid)
public:
Form(){
}
QString nameForm;
QVariantList grid;
};
Q_DECLARE_METATYPE(Form)
class FormModel : public QAbstractListModel
{
enum dashBoardRoles {
NameForm=Qt::UserRole+1,
Grid
};
public:
FormModel(QObject *parent = Q_NULLPTR);
QHash<int, QByteArray> roleNames() const;
int rowCount(const QModelIndex &parent=QModelIndex()) const;
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const;
private:
QList<Form> dashboard;
};
#endif // FORMMODEL_H
formmodel.cpp
#include "formmodel.h"
#include <QColor>
FormModel::FormModel(QObject *parent):QAbstractListModel(parent)
{
for(int i=0; i<10; i++){
Form form;
form.nameForm = QString("name %1").arg(i);
for(int j=0; j<9; j++){
QColor color(qrand() % 256, qrand() % 256, qrand() % 256);
Tile tile{QString("name %1 %2").arg(i).arg(j), color.name()};
form.grid<< QVariant::fromValue(tile);
}
dashboard<< form;
}
}
QHash<int, QByteArray> FormModel::roleNames() const
{
QHash <int,QByteArray> roles;
roles [NameForm]="nameForm";
roles [Grid]="grid";
return roles;
}
int FormModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return dashboard.count();
}
QVariant FormModel::data(const QModelIndex &index, int role) const
{
if(index.row()<0 && index.row() >= dashboard.count())
return QVariant();
Form dashTemp = dashboard[index.row()];
if(role== NameForm)
return dashTemp.nameForm;
else if(role== Grid)
return dashTemp.grid;
return QVariant();
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
Window {
id:app
visible: true
width: 480
height: 800
title: qsTr("Hello World")
ListView {
model: myForms
anchors.fill: parent
delegate: ColumnLayout{
spacing: 30
Text{
text: nameForm
font.pointSize:20
color: "red"
font.capitalization: Font.Capitalize
}
GridView {
id:gr
width: 300; height: 300
cellWidth: 100 ; cellHeight: 100
model: grid // grid.length
delegate: Item{
width : gr.cellWidth
height: gr.cellHeight
Rectangle{
anchors.centerIn: parent
width:parent.width-10
height: parent.height-10
color: grid[index].color
Text {
id: name
anchors.fill: parent
text: grid[index].name
}
}
}
}
}
}
}
The complete example can be found in the following link.

Inserting/Deleting Items in a drag&drop QML listView with cpp model

I tried to add to a ListView in QML of N Items a way to add and delete a new Item at a given index.
I did the following example, but the problem is that when I move some Items, when I try to insert a new one, the position might be incorrect and I have no clue why. When I check my DataList in my cpp model, positions are correct, however, new or deleted items won't be inserted/deleted at the right position.
It seems that the error occurs when I insert a new Item, then I move it , and then I try to delete this Item or insert an Item next to this New Item.
Here is a simple example (you can run it if you need). I called my Items Data : Blocks
#include "mainwindow.h"
#include <QApplication>
#include <QtQml>
#include <QQuickView>
#include <QQuickWidget>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
main.cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "model.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
void addItem(int index);
~MainWindow();
private slots:
private:
QList<QObject*> dataList;
Ui::MainWindow *ui;
BlockModel model;
int cpt = 0;
};
#endif // MAINWINDOW_H
mainwindow.h
#include <QtQml>
#include <QQuickView>
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QQuickWidget"
#include <QStringList>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
int nbItems = 5;
for(; cpt < nbItems; cpt ++) {
Block a = Block(QString("Item ")+QString::number(cpt));
model.addBlock(a);
}
ui->setupUi(this);
QQuickWidget *view = new QQuickWidget;
QQmlContext *ctxt = view->rootContext();
ctxt->setContextProperty("myModel", &model);
view->setSource(QUrl::fromLocalFile("main.qml"));
view->setGeometry(0, 200, 600, 400);
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
ui->dockWidget_3->setWidget(view);
}
MainWindow::~MainWindow()
{
delete ui;
}
mainwindow.cpp
#include <QAbstractListModel>
#include <QStringList>
#include <qqmlcontext.h>
#include <QDebug>
#include <QStringList>
//![0]
class Block
{
public:
Block(){
}
Block(const QString &name);
QString nameBlock() const;
void setName(QString n) {
m_name = n;
}
private:
QString m_name;
};
class BlockModel : public QAbstractListModel
{
Q_OBJECT
public:
Block* getBlock(QString name);
Q_INVOKABLE void moveBlock(int from,int to);
Q_INVOKABLE void insertBlock(int index);
Q_INVOKABLE void deleteBlock(int index);
enum BlockRoles {
nameRole = Qt::UserRole + 1,
};
BlockModel(QObject *parent = 0);
void setContext(QQmlContext *ctx) {
m_ctx = ctx;
}
void setName(const QString &name);
void addBlock(const Block &Block);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
QHash<int, QByteArray> roleNames() const;
private:
QList<Block> m_blocks;
QQmlContext* m_ctx;
int cpt = 0;
};
mode.h
#include "model.h"
#include "qDebug"
Block::Block(const QString &name)
: m_name(name)
{
}
QString Block::nameBlock() const
{
return m_name;
}
BlockModel::BlockModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void BlockModel::addBlock(const Block &Block)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_blocks << Block;
endInsertRows();
}
int BlockModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_blocks.count();
}
void BlockModel::moveBlock(int from, int to) {
m_blocks.move(from,to);
}
void BlockModel::insertBlock(int index) {
Block b =(Block(QString("New Item ")+QString::number(cpt)));
beginInsertRows(QModelIndex(),index+1,index+1);
m_blocks.insert(index+1,b);
endInsertRows();
cpt++;
}
void BlockModel::deleteBlock(int index) {
beginRemoveRows(QModelIndex(),index,index);
m_blocks.removeAt(index);
endRemoveRows();
}
QVariant BlockModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_blocks.count())
return QVariant();
const Block &Block = m_blocks[index.row()];
if (role == nameRole)
return Block.nameBlock();
return QVariant();
}
//![0]
QHash<int, QByteArray> BlockModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[nameRole] = "nameBlock";
return roles;
}
model.cpp
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.2
import QtQml.Models 2.2
import QtQuick.Controls.Styles 1.4
Rectangle {
id : rootRectangle
visible: true
ScrollView {
anchors.fill:parent
ListView{
id: root
width: parent.width; height: parent.height
property int visualIndex: -1
displaced: Transition {
NumberAnimation { properties: "y"; easing.type: Easing.OutQuad }
}
model: DelegateModel {
id: visualModel
model: myModel
delegate: Component {
MouseArea {
id: delegateRoot
property int visualIndex: DelegateModel.itemsIndex
cursorShape: Qt.PointingHandCursor
width: root.width; height: 100
drag.target: icon
drag.axis: Drag.YAxis
Behavior on height {
PropertyAnimation { duration: 100 }
}
Rectangle {
anchors.top: delegateRoot.top
anchors.left: delegateRoot.left
id: icon
objectName: nameBlock
width: root.width-5; height: 100
color: "skyblue"
radius: 3
Text {
objectName: "rect"
id: title
anchors.fill: parent
anchors.margins: 10
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: nameBlock
}
Drag.active: delegateRoot.drag.active
Drag.source: delegateRoot
Drag.hotSpot.x: 36
Drag.hotSpot.y: 36
Button {
id : buttonAdd
text: "Add Block"
anchors{
right: parent.right
top: parent.top
bottom: parent.bottom
margins: 30
}
onClicked: {
myModel.insertBlock(visualIndex)
}
}
Button {
id : buttonDelete
text: "Delete Block"
anchors{
right: buttonAdd.left
top: parent.top
bottom: parent.bottom
margins: 30
}
onClicked: {
myModel.deleteBlock(visualIndex)
}
}
states: [
State {
when: icon.Drag.active
ParentChange {
target: icon
parent: root
}
AnchorChanges {
target: icon;
anchors.horizontalCenter: undefined;
anchors.verticalCenter: undefined
}
}
]
transitions: Transition {
// Make the state changes smooth
ParallelAnimation {
ColorAnimation { property: "color"; duration: 500 }
NumberAnimation { duration: 300; properties: "detailsOpacity,x,contentY,height,width,font.pixelSize,font.bold,visible" }
}
}
}
DropArea {
anchors { fill: parent; margins: 15 }
onEntered: {
visualModel.items.move(drag.source.visualIndex, delegateRoot.visualIndex)
myModel.moveBlock(drag.source.visualIndex,delegateRoot.visualInde)
}
}
}
}
}
}
}
}
main.qml
Do you have any idea of what I am doing wrong ?
Thanks a lot and have a good day !
There are two bugs when moving items. In DropArea.onEntered, if you print out both drag.source.visualIndex and delegateRoot.visualIndex before and after visualModel.items.move, you'll see that values are modified after moving. That means you are moving wrong rows when calling myModel.moveBlock. To fix the problem, save the value before moving items:
DropArea {
anchors { fill: parent; margins: 15 }
onEntered: {
var from = drag.source.visualIndex;
var to = delegateRoot.visualIndex;
myModel.moveBlock(from, to);
}
}
When moving items in C++ model, QAbstractItemModel::beginMoveRows should be called just like insert/remove items. Otherwise the QML DelegateModel cannot correctly display your model. Remember that when implementing BlockModel::moveBlock, the destination row for the model is different from the one for your source list m_blocks. See the last example in QAbstractItemModel::beginMoveRows documentation for detail.
void BlockModel::moveBlock(int from, int to) {
if (from == to)
return;
auto modelFrom = from;
auto modelTo = to + (from < to ? 1 : 0);
beginMoveRows(QModelIndex(), modelFrom, modelFrom, QModelIndex(), modelTo);
m_blocks.move(from,to);
endMoveRows();
}

How to change image source based on state in Qt

I use qt quick 2.0. A gridview is bound to a c++ model as described in here. In the gridview's delegate, I use an image to show an icon. I try to use state property to change image source and bind state to the model.
The expected result would be on release the selected screen image should be changed to running icon.
The actual result it image doesn't change. If I use setName instead of setState in ScreenManager::setScreenState, it shows changed screen name correctly.
Is there any better solution?
Screen.h
class Screen
{
public:
Screen(QString name, int gridId, bool active = false);
QString name() const;
int gridId() const;
bool active() const;
QString state() const;
void setName(QString n);
void setActive(bool a);
void setState(QString s);
private:
QString m_name;
int m_gridId;
bool m_active;
QString m_state;
};
ScreenManager.h
class ScreenManager : public QAbstractListModel
{
Q_OBJECT
public:
enum ScreenRoles {
NameRole = Qt::UserRole + 1,
GridIDRole,
ActiveRole,
StateRole
};
ScreenManager();
void addScreen(const Screen& screen);
int rowCount(const QModelIndex& parent = QModelIndex()) const;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
QModelIndex getIndex(int row, int column = 0,
const QModelIndex &parent = QModelIndex()) const;
Q_INVOKABLE int getScreenGridId(int index);
Q_INVOKABLE bool getScreenActive(int index);
Q_INVOKABLE void swapScreens(int index1, int index2);
Q_INVOKABLE void setScreenState(int index, QString s);
Q_INVOKABLE QString getScreenState(int index);
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<Screen> m_screens;
};
ScreenManager.cpp
#include "ScreenManager.h"
#include "Screen.h"
ScreenManager::ScreenManager()
{
int index = 0;
for (;index < 15; index++) {
addScreen(Screen(QString ("Screen%1").arg(index), index, false));
}
m_screens[2].setActive(true);
m_screens[6].setActive(true);
m_screens[7].setActive(true);
m_screens[8].setActive(true);
m_screens[12].setActive(true);
}
void ScreenManager::addScreen(const Screen& screen)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_screens.append(screen);
endInsertRows();
}
int ScreenManager::rowCount(const QModelIndex& parent) const {
Q_UNUSED(parent);
return m_screens.count();
}
QVariant ScreenManager::data(const QModelIndex& index, int role) const
{
if (index.row() < 0 || index.row() >= m_screens.count())
return QVariant();
const Screen& screen = m_screens[index.row()];
if (role == NameRole)
return screen.name();
else if (role == GridIDRole)
return screen.gridId();
else if (role == ActiveRole)
return screen.active();
else if (role == StateRole)
return screen.state();
return QVariant();
}
QModelIndex ScreenManager::getIndex(int row, int column,
const QModelIndex &parent) const
{
return hasIndex(row, column, parent) ?
createIndex(row, column, (void*)&m_screens[row])
: QModelIndex();
}
QHash<int, QByteArray> ScreenManager::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[GridIDRole] = "gridId";
roles[ActiveRole] = "active";
roles[StateRole] = "state";
return roles;
}
int ScreenManager::getScreenGridId(int index)
{
return m_screens.at(index).gridId();
}
bool ScreenManager::getScreenActive(int index)
{
return m_screens.at(index).active();
}
void ScreenManager::swapScreens(int index1, int index2)
{
int min = index1 < index2 ? index1 : index2;
int max = index1 > index2 ? index1 : index2;
m_screens.swap(index1, index2);
beginMoveRows(QModelIndex(), max, max, QModelIndex(), min);
endMoveRows();
if (max - min > 1) {
beginMoveRows(QModelIndex(), min + 1, min + 1, QModelIndex(), max + 1);
endMoveRows();
}
}
void ScreenManager::setScreenState(int index, QString s)
{
// if use setName, the grid view can show the changed screen name
m_screens[index].setState(s);
dataChanged(getIndex(0), getIndex(rowCount() - 1));
}
QString ScreenManager::getScreenState(int index)
{
return m_screens[index].state();
}
QML
GridView {
id: gridView
x: 82
y: 113
width: cellWidth * 5
height: cellHeight * 3
clip: true
anchors.bottom: parent.bottom
anchors.bottomMargin: 70
anchors.topMargin: 100
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
flickableDirection: Flickable.HorizontalAndVerticalFlick
cellWidth: 90; cellHeight: 90;
property bool ignoreMovementAnimation: true
MouseArea {
id: gridViewMouseArea
hoverEnabled: true
preventStealing : true
property int currentGridId: -1
property int preIndex
property int index: gridView.indexAt(mouseX, mouseY)
anchors.fill: parent
onPressAndHold: {
currentGridId = screenManager.getScreenGridId(index)
preIndex = index
gridView.ignoreMovementAnimation = false
}
onReleased: {
currentGridId = -1
screenManager.setScreenState(index, "running");
}
onPositionChanged: {
if (currentGridId != -1 && index != -1 && index != preIndex) {
if (screenManager.getScreenActive(index)) {
screenManager.swapScreens(preIndex, index)
preIndex = index
}
}
}
}
model: screenManager
delegate: Component {
Item {
id: gridViewDelegate
width: gridView.cellWidth; height: gridView.cellHeight
state: state
states: [
State {
name: "running"
PropertyChanges {
target: itemImage
source: "qrc:/res/image/screen_icon_running.png"
}
},
State {
name: "idle"
PropertyChanges {
target: itemImage
source: "qrc:/res/image/screen_icon_idle.png"
}
}
]
Image {
id: itemImage
parent: gridView
x: gridViewDelegate.x + 5
y: gridViewDelegate.y + 5
width: gridViewDelegate.width - 10
height: gridViewDelegate.height - 10;
fillMode: Image.PreserveAspectFit
smooth: true
source: "qrc:/res/image/screen_icon.png"
visible: active
Text {
text: name
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
anchors.fill: parent;
border.color: "grey"
border.width: 6
color: "transparent"; radius: 5
visible: itemImage.state === "active"
}
// specify the movement's animation for non-active screen icons
Behavior on x {
enabled: !gridView.ignoreMovementAnimation && itemImage.state !== "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
Behavior on y {
enabled: !gridView.ignoreMovementAnimation && itemImage.state !== "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
// specify the shaking animation for non-active screen icons when hold one icon
SequentialAnimation on rotation {
NumberAnimation { to: 2; duration: 60 }
NumberAnimation { to: -2; duration: 120 }
NumberAnimation { to: 0; duration: 60 }
running: gridViewMouseArea.currentGridId != -1 && itemImage.state !== "active"
loops: Animation.Infinite
alwaysRunToEnd: true
}
// specify the active screen's new position and size
states: State {
name: "active"
when: gridViewMouseArea.currentGridId == gridId
PropertyChanges {
target: itemImage
x: gridViewMouseArea.mouseX - width/2
y: gridViewMouseArea.mouseY - height/2
scale: 0.5
z: 10
}
}
// specify the scale speed for the active screen icon
transitions: Transition {
NumberAnimation { property: "scale"; duration: 200}
}
}
}
}
}
You have a naming problem, because state is also know for the state property in your item delegate.
After i changed:
roles[StateRole] = "statetest";
in you c++
and:
state: statetest
in your qml,
it works.
Or simply:
state: model.state
in your qml
I solved the problem by binding image source to another property in Screen, stateIamge, rather than binding it to state directly.
But I'm still interested to see why binding to state doesn't work.