Using QSortFilterProxyModel - qt

Please have a look into the following code:
CMyModel* myModel = new CMyModel(); //This is my main model sub-classing QAbstractListItemModel
CMySortModel* mySortModel = new CMySortModel(); //This is my sort filter proxy model sub-classing QSortFilterProxyModel
mySortModel->setSourceModel(myModel);
mySortModel->setDynamicSortFilter(true);
mySortModel->setSortRole(CMyModel::displayRole);
myModel->Initialize(); // This is working fine
mySortModel->sort(0);
// myModel->Initialize(); // This is not working
The above code is working fine when the data structure (basically a QList of integers linked to CMyModel) is initialized before, mySortModel->sort(0) is called.
If the list is initialized after the sort method call, the view is basically not showing any data.
Then I tried following
mySortModel->sort(0);
myModel->Initialize();
mySortModel->Invalidate(); // This is working fine.
But realized that, every time there is an update in the QList, I need to call mySortModel->Invalidate() to update my view. Otherwise the view is not updating. This is causing a complete view update and in turn a flickering in my screen.
As not much idea on Qt/QML model view coding, so bit stuck with the issue.
Please correct me, if I am doing anything wrong.
MyModel.cpp
#include "MyModel.h"
#include <QDateTime>
CMyModel::CMyModel(QObject *parent)
: QAbstractListModel(parent)
{
m_mapRoles.insert(rolesEnum::displayRole, "displayRole");
}
void CMyModel::Initialize()
{
qsrand(QDateTime::currentMSecsSinceEpoch() / 1000);
quint32 index = 0;
while (index < 200)
{
m_lstOfInts.append(qrand() % 1000);
index++;
}
}
int CMyModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_lstOfInts.count();
}
int CMyModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 1;
}
QVariant CMyModel::data(const QModelIndex &index, int role) const
{
Q_UNUSED(role)
int rowVal = index.row();
if (role == rolesEnum::displayRole)
{
if (rowVal >= 0 && rowVal < m_lstOfInts.count())
{
return m_lstOfInts.at(rowVal);
}
}
return QVariant();
}
QHash<int, QByteArray> CMyModel::roleNames() const
{
return m_mapRoles;
}
MyModel.h
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QAbstractListModel>
#include <QObject>
#include <QHash>
class CMyModel : public QAbstractListModel
{
Q_OBJECT
public:
enum rolesEnum {
displayRole
};
Q_ENUM(rolesEnum)
public:
explicit CMyModel(QObject *parent = NULL);
// QAbstractItemModel interface
public:
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
private:
QList<int> m_lstOfInts;
QHash<int, QByteArray> m_mapRoles;
// QAbstractItemModel interface
public:
QHash<int, QByteArray> roleNames() const;
void Initialize();
};
#endif // MYMODEL_H
MySortModel.cpp
#include "MySortModel.h"
#include "MyModel.h"
CMySortModel::CMySortModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
}
bool CMySortModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
return sourceModel()->data(source_left, CMyModel::displayRole).toInt() < sourceModel()->data(source_right, CMyModel::displayRole).toInt();
}
MySortModel.h
#ifndef MYSORTMODEL_H
#define MYSORTMODEL_H
#include <QSortFilterProxyModel>
class CMySortModel : public QSortFilterProxyModel
{
public:
explicit CMySortModel(QObject* parent = NULL);
// QSortFilterProxyModel interface
protected:
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
};
#endif // MYSORTMODEL_H
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQmlContext>
#include "MyModel.h"
#include "MySortModel.h"
int main(int argc, char *argv[])
{
#if defined(Q_OS_WIN)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
qmlRegisterType<CMySortModel>("CMySortModel", 1, 0, "CMySortModel");
CMyModel* myModel = new CMyModel();
CMySortModel* mySortModel = new CMySortModel();
mySortModel->setSourceModel(myModel);
mySortModel->setDynamicSortFilter(true);
mySortModel->setSortRole(CMyModel::displayRole);
// myModel->Initialize(); // This works
mySortModel->sort(0);
myModel->Initialize(); // This works when followed by a invalidate call on the proxy model
mySortModel->invalidate();
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("_myModel", mySortModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
{
delete mySortModel;
mySortModel = NULL;
delete myModel;
myModel = NULL;
return -1;
}
return app.exec();
}
main.qml
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.2
import QtGraphicalEffects 1.0
Window {
id: window
visible: true
width: 800
height: 480
title: qsTr("Hello World")
ScrollView {
anchors.fill: parent
width: parent.width
GridView {
anchors.fill: parent
//columns: 2
model: _myModel
delegate: Text {
id: _text
text: model.displayRole
}
}
}
}
Modified Initialize method as follows and it is working now
void CMyModel::Initialize()
{
qsrand(QDateTime::currentMSecsSinceEpoch() / 1000);
quint32 index = 0;
while (index < 200)
{
beginInsertRows(QModelIndex(), m_lstOfInts.size(), m_lstOfInts.size());
m_lstOfInts.append(qrand() % 1000);
index++;
endInsertRows();
}
}

Related

Text gets missaligned when using Qt TextTable with TextArea using QtQuick.Controls 1.12

I have a problem where I am using a TextArea in Qml. A C++ Model holds a reference to that TextArea.
When I insert a QTextTable in the C++ model it all fine until the user enters some text. After the user manually edits a few cells and writes some text in it, it gets all messed up. Does anyone know anything on how to solve it?
I also have other functions with are working perfectly. So I would guess there is nothing wrong with the connection between the c++ model and the textarea.
Here is the Documenthandler.h
#include <QQuickTextDocument>
#include <QtGui/QTextCharFormat>
#include <QtCore/QTextCodec>
#include <qqmlfile.h>
QT_BEGIN_NAMESPACE
class QTextDocument;
QT_END_NAMESPACE
class DocumentHandler : public QObject
{
Q_OBJECT
Q_ENUMS(HAlignment)
Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged)
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
public:
DocumentHandler();
Q_INVOKABLE void createTable(int columns ,int rows);
QQuickItem *target() { return m_target; }
void setTarget(QQuickItem *target);
QString text() const;
public Q_SLOTS:
void setText(const QString &arg);
Q_SIGNALS:
void targetChanged();
void textChanged();
void error(QString message);
private:
QTextCursor textCursor() const;
QQuickItem *m_target;
QTextDocument *m_doc;
QString m_text;
};
Here is the documenthandler.cpp
in the "createTable" function I create the table
#include "documenthandler.h"
#include <QtGui/QTextDocument>
#include <QtGui/QTextList>
#include <QtGui/QTextTable>
#include <QtGui/QTextCursor>
#include <QtGui/QFontDatabase>
#include <QtCore/QFileInfo>
DocumentHandler::DocumentHandler()
: m_target(0)
, m_doc(0)
{
}
void DocumentHandler::setTarget(QQuickItem *target)
{
m_doc = 0;
m_target = target;
if (!m_target)
return;
QVariant doc = m_target->property("textDocument");
if (doc.canConvert<QQuickTextDocument*>()) {
QQuickTextDocument *qqdoc = doc.value<QQuickTextDocument*>();
if (qqdoc)
m_doc = qqdoc->textDocument();
}
emit targetChanged();
}
void DocumentHandler::setText(const QString &arg)
{
if (m_text != arg) {
m_text = arg;
emit textChanged();
}
}
QString DocumentHandler::text() const
{
return m_text;
}
QTextCursor DocumentHandler::textCursor() const
{
if (!m_doc)
return QTextCursor();
QTextCursor cursor = QTextCursor(m_doc);
return cursor;
}
void DocumentHandler::createTable(int columns , int rows)
{
QTextCursor cursor = textCursor();
if (cursor.isNull())
return;
cursor.insertTable(rows,columns);
}
Here is the main qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import DocumentHandler 1.0
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Button
{
id:btn
text:"test"
onClicked: document.createTable(5,5);
}
TextArea {
Accessible.name: "document"
id: tooltip_area
selectByMouse: true
anchors.left:parent.left
anchors.right:parent.right
anchors.top: btn.bottom
anchors.bottom: parent.bottom
baseUrl: "qrc:/"
text: document.text
textFormat: Qt.RichText
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
Component.onCompleted: forceActiveFocus()
}
DocumentHandler{
id: document
target: tooltip_area
}
}
here is the main function, there is nothing special except I register the QML Type
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "documenthandler.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
qmlRegisterType<DocumentHandler>("DocumentHandler",1,0,"DocumentHandler");
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}
This is how it looks after filling up some cells
So I solved it by just selecting everything and deselecting when the text changes. Theoretically I only need to do it when I am in a Table Block.
onTextChanged:
{
if(recoursionlock)
{
recoursionlock =false;
var curpos = tooltip_area.cursorPosition;
var select_start = tooltip_area.selectionStart;
var select_end = tooltip_area.selectionEnd;
selectAll();
deselect();
if(curpos !== -1 && curpos >= tooltip_area.text.lenght)
tooltip_area.cursorPosition = curpos;
else
tooltip_area.select(select_start,select_end)
recoursionlock = true;
}
}

Accessing a delegate's input value from the model

I'm using a custom QTableView with a custom QAbstractTableModel and a QItemDelegate. I'd need to access the contents of the delegate's editor while the user is editing it, and after several attempts, I couldn't find anything satisfying.
Indeed, I've tried several things.
First: trying to access the delegate's current input (created through createEditor) through a property defined in QItemDelegate but... it seems that none exists. That's why I tried to add a QWidget* editor property and setting it in the createEditor.
Unfortunately, QItemDelegate's createEditor is supposed to be const, which makes me unable to set my property there (and since I don't control what calls createEditor, I can't do it before or after).
I don't really know what to do here. Actually, I also needed to know when the user started (or stopped) editing the cell content, which I eventually achieved by creating two const signals (editingStarted and editingStopped). I could probably create a const editorOpened(QWidget*) signal but it just feels bad and ugly...
I can't believe nothing "official" exists to achieve what I'm trying to do, hence this question. If I have everything wrong from the beginning, I'd be glad to know. If you have any other ideas, please suggest.
EDIT: Here is a minimal working example
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "mainwindow.h"
#include <QTableView>
#include "mytableview.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
auto tableView = new MyTableView(this);
setCentralWidget(tableView);
}
MainWindow::~MainWindow()
{
}
MyItemDelegate.h
#ifndef MYITEMDELEGATE_H
#define MYITEMDELEGATE_H
#include <QItemDelegate>
#include <QLineEdit>
#include <QStandardItemModel>
class MyItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
MyItemDelegate(QObject* parent);
virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
virtual void onCloseEditor();
virtual ~MyItemDelegate() = default;
signals:
// Const signals trick
void editingStarted() const;
void editingFinished() const;
void editorOpened(const QWidget*) const;
};
#endif // MYITEMDELEGATE_H
MyItemDelegate.cpp
#include "myitemdelegate.h"
MyItemDelegate::MyItemDelegate(QObject* parent) : QItemDelegate(parent)
{
connect(this, &QItemDelegate::closeEditor, this, &MyItemDelegate::onCloseEditor);
}
QWidget* MyItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
auto lineEdit = new QLineEdit(parent);
emit editingStarted();
emit editorOpened(lineEdit);
return lineEdit;
}
void MyItemDelegate::onCloseEditor()
{
emit editingFinished();
}
MyTableView.h
#ifndef MYTABLEVIEW_H
#define MYTABLEVIEW_H
#include <QTableView>
#include <QDebug>
#include "myitemdelegate.h"
class MyTableView : public QTableView
{
Q_OBJECT
public:
explicit MyTableView(QWidget *parent = nullptr);
signals:
public slots:
};
#endif // MYTABLEVIEW_H
MyTableView.cpp
#include "mytableview.h"
MyTableView::MyTableView(QWidget *parent) : QTableView(parent)
{
MyItemDelegate* delegate = new MyItemDelegate(this);
QStandardItemModel* model = new QStandardItemModel(this);
setItemDelegate(delegate);
setModel(model);
QList<QList<QStandardItem*>> items;
for(int i = 0; i < 10; i++)
{
items << QList<QStandardItem*>();
for (int j = 'A'; j < 'E'; j++)
items[i] << new QStandardItem(QString("%1,%2").arg(i).arg(static_cast<char>(j)));
}
for (const auto& row : items)
model->appendRow(row);
connect(delegate, &MyItemDelegate::editingStarted, []() {
qDebug() << "Editing started";
});
connect(delegate, &MyItemDelegate::editingFinished, []() {
qDebug() << "Editing finished";
});
connect(delegate, &MyItemDelegate::editorOpened, [](const QWidget* editor) {
auto lineEdit = qobject_cast<const QLineEdit*>(editor);
connect(lineEdit, &QLineEdit::textChanged, [](const QString& text) {
qDebug() << text;
});
});
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
The following solution maybe fits your needs. I just defined a new signal inside the delegate and connected to it inside the class owning the delegate.
MyItemDelegate.h
#ifndef MYITEMDELEGATE_H
#define MYITEMDELEGATE_H
#include <QStyledItemDelegate>
class MyItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MyItemDelegate(QObject* parent);
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
virtual ~MyItemDelegate() = default;
signals:
void valueChanged(const QString&);
};
#endif // MYITEMDELEGATE_H
MyItemDelegate.cpp
#include "myitemdelegate.h"
#include <QLineEdit>
MyItemDelegate::MyItemDelegate(QObject* parent) : QStyledItemDelegate(parent)
{
}
QWidget* MyItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
auto lineEdit = new QLineEdit(parent);
connect(lineEdit, &QLineEdit::textChanged, this, &MyItemDelegate::valueChanged);
return lineEdit;
}
MyTableView.cpp
#include "mytableview.h"
#include <QStandardItemModel>
MyTableView::MyTableView(QWidget *parent) : QTableView(parent)
{
MyItemDelegate* delegate = new MyItemDelegate(this);
QStandardItemModel* model = new QStandardItemModel(this);
setItemDelegate(delegate);
setModel(model);
QList<QList<QStandardItem*>> items;
for(int i = 0; i < 10; i++)
{
items << QList<QStandardItem*>();
for (int j = 'A'; j < 'E'; j++)
items[i] << new QStandardItem(QString("%1,%2").arg(i).arg(static_cast<char>(j)));
}
for (const auto& row : items)
model->appendRow(row);
connect(delegate, &MyItemDelegate::valueChanged, [](auto v) { qDebug() << v; });
}

How to update QAbstractItemModel view when a Data is updated

I use the Qt example for QAbstractItemModel and I try to update an Item to a given index.
I tried to use emit DataChangedbut it doesn't work, the view is not updated.
Here is an example:
What I want: When you click on the button, it will update Data at index 0, the type of the animal will be changed, it will become a Lion.
#include <QAbstractListModel>
#include <QStringList>
#include <qqmlcontext.h>
//![0]
class Animal
{
public:
Animal(const QString &type, const QString &size);
//![0]
QString type() const;
QString size() const;
void setType(QString q) {
m_type = q;
}
private:
QString m_type;
QString m_size;
//![1]
};
class AnimalModel : public QAbstractListModel
{
Q_OBJECT
public:
Q_INVOKABLE void test() ;
void setName(const QString &name);
enum AnimalRoles {
TypeRole = Qt::UserRole + 1,
SizeRole
};
AnimalModel(QObject *parent = 0);
//![1]
//!
//!
void setContext(QQmlContext *ctx) {
m_ctx = ctx;
}
void addAnimal(const Animal &animal);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
QHash<int, QByteArray> roleNames() const;
protected:
private:
QList<Animal> m_animals;
QQmlContext* m_ctx;
signals:
void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight);
//![2]
};
//![2]
model.h
#include "model.h"
#include "qDebug"
Animal::Animal(const QString &type, const QString &size)
: m_type(type), m_size(size)
{
}
QString Animal::type() const
{
return m_type;
}
QString Animal::size() const
{
return m_size;
}
AnimalModel::AnimalModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void AnimalModel::addAnimal(const Animal &animal)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_animals << animal;
endInsertRows();
}
void AnimalModel::test() {
m_animals[0].setType("Lion");
emit dataChanged(QModelIndex(),QModelIndex());
//I also tried:
QModelIndex topLeft = createIndex(0,0);
emit dataChanged(topLeft, topLeft);
}
int AnimalModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_animals.count();
}
QVariant AnimalModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_animals.count())
return QVariant();
const Animal &animal = m_animals[index.row()];
if (role == TypeRole)
return animal.type();
else if (role == SizeRole)
return animal.size();
return QVariant();
}
//![0]
QHash<int, QByteArray> AnimalModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[TypeRole] = "type";
roles[SizeRole] = "size";
return roles;
}
//![0]
model.cpp
#include "model.h"
#include <QGuiApplication>
#include <qqmlengine.h>
#include <qqmlcontext.h>
#include <qqml.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
//![0]
int main(int argc, char ** argv)
{
QGuiApplication app(argc, argv);
AnimalModel model;
model.addAnimal(Animal("Wolf", "Medium"));
model.addAnimal(Animal("Polar bear", "Large"));
model.addAnimal(Animal("Quoll", "Small"));
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myModel", &model);
//![0]
view.setSource(QUrl("qrc:view.qml"));
view.show();
return app.exec();
}
main.cpp
import QtQuick 2.0
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.2
import QtQml.Models 2.1
import QtQuick.Controls.Styles 1.2
//![0]
ListView {
width: 200; height: 250
model: myModel
delegate: Text { text: "Animal: " + type + ", " + size }
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
}
}
Button {
anchors.bottom: parent.bottom
width:50; height:50
text:"click"
onClicked: {
myModel.test()
}
}
}
//![0]
View.qml
Do you have any idea why it doesn't work ?
Thanks a lot !
Don't redefine signals in subclasses.
Remove the following lines (in model.h) and it should work as expected :
signals:
void dataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight);
Also this when calling dataChanged() you have to specify a valid QModelIndex. This is correct :
// I also tried:
QModelIndex topLeft = createIndex(0,0);
emit dataChanged(topLeft, topLeft);
You need create topLeft index and bottom right index and emit dataChanged. This update all model data in view.
QModelIndex topLeft = createIndex(0,0);
QModelIndex bottomRight = createIndex( rows count ,0);
emit dataChanged( topLeft, bottomRight );

Using TableView as ListView-delegate

I have a model that contains several QStandardItemModels. Now I want to create a view that displays a TableView for each QStandardItemModel.
I had the idea to have a ListView that has a TableView as delegate, something like this:
ListView {
id: myListView
anchors {
fill: parent
margins: 5
}
model: testModel
delegate: CompareDelegate { }
}
And in CompareDelegate.qml:
Item {
id: base
width: 500
height: 300
TableView {
anchors.fill: parent
}
}
How do I get the TableView inside the delegate to use the appropriate QStandardItemModel within the model of the ListView?
I hope the question is somewhat clear.
Thanks in advance.
Interesting use case... I am still not sure if it's a good idea and am wondering if there's a better way to do it, but I couldn't think of reasons why it's bad, so I tried it out of curiosity:
main.cpp
#include <QApplication>
#include <QtQml>
#include <QtWidgets>
class IndividualModel : public QStandardItemModel
{
public:
IndividualModel(QObject* parent = 0) :
QStandardItemModel(4, 2, parent)
{
for (int row = 0; row < 4; ++row) {
QStandardItem *item0 = new QStandardItem;
item0->setData(QString("row %1, column 0").arg(row), TitleRole);
setItem(row, 0, item0);
QStandardItem *item1 = new QStandardItem;
item1->setData(QString("row %1, column 1").arg(row), AuthorRole);
setItem(row, 1, item1);
}
}
enum {
TitleRole = Qt::UserRole,
AuthorRole
};
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE
{
QHash<int, QByteArray> names;
names[TitleRole] = "title";
names[AuthorRole] = "author";
return names;
}
};
class CompositeModel : public QAbstractItemModel
{
public:
CompositeModel()
{
for (int i = 0; i < 3; ++i) {
QStandardItemModel *model = new IndividualModel(this);
mModels.append(model);
}
}
enum {
ModelRole = Qt::UserRole
};
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE
{
if (!index.isValid())
return QVariant();
if (role != ModelRole)
return QVariant();
return QVariant::fromValue(mModels.at(index.row()));
}
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE
{
if (!hasIndex(row, column, parent))
return QModelIndex();
return createIndex(row, column);
}
QModelIndex parent(const QModelIndex &) const Q_DECL_OVERRIDE
{
return QModelIndex();
}
int rowCount(const QModelIndex & = QModelIndex()) const Q_DECL_OVERRIDE
{
return mModels.size();
}
int columnCount(const QModelIndex & = QModelIndex()) const Q_DECL_OVERRIDE
{
return 1;
}
QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE
{
QHash<int, QByteArray> names;
names[ModelRole] = "individualModel";
return names;
}
private:
Q_DISABLE_COPY(CompositeModel)
QVector<QStandardItemModel*> mModels;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQmlApplicationEngine engine;
CompositeModel compositeModel;
engine.rootContext()->setContextProperty("compositeModel", QVariant::fromValue(&compositeModel));
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Controls 1.0
Window {
visible: true
width: 300
height: 600
ListView {
anchors.fill: parent
model: compositeModel
delegate: TableView {
model: individualModel
TableViewColumn {
role: "title"
title: "Title"
width: 100
}
TableViewColumn {
role: "author"
title: "Author"
width: 200
}
}
}
}
This works, but... it crashes on exit. :)
I've been unable to reproduce it while debugging with Creator, so I've only been able to get a stack trace manually on the command line with gdb, which I'm not too familiar with (have always used debuggers in IDEs :)). The crash is in QML's guts somewhere, while accessing some property. However, I've spent too much time on this to not post the answer, and I think it is still useful. Maybe some kind soul will find the problem and edit my post. :)

base class 'QAbstractListModel' has private copy constructor

I have a QT QML project. (still very small)
I started by binding a listview on my UScenario model, by subclassing QAbstractListModel and it worked fined.
Now, each UScenario has a list of UTask, which also have a list of UCondition (so, Utask also subclasses QAbstractListModel). But then, QT Creator gives me an error:
Core/Tasks/utask.h:6: erreur : base class 'QAbstractListModel' has private copy constructor
class UTask: public QAbstractListModel
^
So I'm not sure where is my problem. I tried reading the doc about QAbstractListModel vs QAbstractItemModel, but I have no clue.
I also tried to see if I ever constructed a UTask in a wrong way; I think not.
// USCENARIO.h
#ifndef USCENARIO_H
#define USCENARIO_H
#include <QAbstractListModel>
#include "../Tasks/utask.h"
class UScenario : public QAbstractListModel
{
Q_OBJECT
public slots:
void cppSlot() { // Used to test the insertion from UI
this->addTask(UTask());
}
public:
enum TaskRoles {
IdRole = Qt::UserRole + 1
};
UScenario(QObject *parent = 0);
private:
QList<UTask> m_tasks;
public:
void addTask(const UTask &task);
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QHash<int, QByteArray> roleNames() const;
};
#endif // USCENARIO_H
// USCENARIO.CPP
#include "uscenario.h"
UScenario::UScenario(QObject *parent)
: QAbstractListModel(parent)
{
}
void UScenario::addTask(const UTask &task)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_tasks.append(task);
endInsertRows();
}
int UScenario::rowCount(const QModelIndex & parent) const {
return m_tasks.count();
}
QVariant UScenario::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= m_tasks.count())
return QVariant();
const UTask &task = m_tasks[index.row()];
if (role == IdRole)
return task.id();
return QVariant();
}
QHash<int, QByteArray> UScenario::roleNames() const {
QHash<int, QByteArray> roles;
roles[IdRole] = "id";
return roles;
}
// UTASK.H
#ifndef UTASK_H
#define UTASK_H
#include <QAbstractListModel>
#include "../Conditions/ucondition.h"
class UTask: public QAbstractListModel
{
Q_OBJECT
public:
enum TaskRoles {
typeRole = Qt::UserRole + 1
};
UTask(QObject *parent = 0);//:m_id(0){}
int id() const{return m_id;}
private:
int m_id;
QList<UCondition> m_conditions;
// QAbstractItemModel interface
public:
void addCondition(const UCondition &cond);
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QHash<int, QByteArray> roleNames() const;
};
#endif // UTASK_H
// UTASK.cpp
#include "utask.h"
UTask::UTask(QObject *parent):
QAbstractListModel(parent), m_id(0)
{
}
void UTask::addCondition(const UCondition &cond)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_conditions.append(cond);
endInsertRows();
}
int UTask::rowCount(const QModelIndex &parent) const
{
return m_conditions.count();
}
QVariant UTask::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_conditions.count())
return QVariant();
const UCondition &cond = m_conditions[index.row()];
if (role == typeRole)
return cond.type();
return QVariant();
}
QHash<int, QByteArray> UTask::roleNames() const
{
QHash<int, QByteArray> roles;
roles[typeRole] = "type";
return roles;
}
// MAIN
#include <QtGui/QGuiApplication>
#include "qtquick2applicationviewer.h"
#include <qqmlengine.h>
#include <qqmlcontext.h>
#include <qqml.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickview.h>
#include "../uCtrlCore/Scenario/uscenario.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
UScenario scenarioModel;
scenarioModel.addTask(UTask());
scenarioModel.addTask(UTask());
scenarioModel.addTask(UTask());
QtQuick2ApplicationViewer viewer;
QQmlContext *ctxt = viewer.rootContext();
ctxt->setContextProperty("myScenarioModel", &scenarioModel);
viewer.setMainQmlFile(QStringLiteral("qml/uCtrlDesktopQml/main.qml"));
QObject *item = viewer.rootObject()->findChild<QObject*>("btn");
QObject::connect(item, SIGNAL(qmlSignal()), &scenarioModel, SLOT(cppSlot()));
viewer.showExpanded();
return app.exec();
}
There problem is with how you're storing the UTask objects in your UScenario class
QList<UTask> m_tasks
In simple terms when you call m_tasks.append it is attempting to allocate a new UTask object in the QList by copying the source UTask object via the default copy constructor. In the case of QAbstractListModel it is private. This is why you're at getting the error.
A simply solution is to change the storage type to a list of UTask pointers, QList< UTask* > along with the supporting code to properly release the memory when your UScenario object is destroyed.
For example here are some but not all of the changes but should point you in the right direction. Just make sure to change m_tasks to QList< UTask* > first:
int main(int argc, char *argv[])
{
...
UScenario scenarioModel;
scenarioModel.addTask( new UTask() );
scenarioModel.addTask( new UTask() );
scenarioModel.addTask( new UTask() );
...
return app.exec();
}
void UScenario::cppSlot()
{
// Used to test the insertion from UI
this->addTask( new UTask() );
}
// Change the signature to take a pointer
void UScenario::addTask( UTask* task )
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_tasks.append(task);
endInsertRows();
}
// Make sure you define a destructor for UScenario
UScenario::~UScenario()
{
QList< UTask* >::iterator task = m_tasks.begin();
while( m_tasks.end() != task )
{
// Release the memory associated with the task.
delete (*task);
++task;
}
m_tasks.clear();
}

Resources