How do I interact with a model in an extended QQuickItem? - c++

There are many good resources on dealing with models and views in the Qt doc, like: http://doc.qt.io/qt-5/model-view-programming.html, but I can't seem to find any that link to dealing with models in QtQuick. There are some basic chapters on extending qml with c++, like in http://doc.qt.io/qt-5/qtqml-tutorials-extending-qml-example.html, and on using models: http://doc-snapshots.qt.io/qt5-5.11/qtquick-modelviewsdata-modelview.html, but I can't quite find a way to use an actual model in extended qml.
Currently I have this model:
class LayoutModel : public QAbstractItemModel {
Q_OBJECT
public:
explicit LayoutModel(const QString &data, QObject *parent = 0);
~LayoutModel();
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
private:
void setupModelData(const QStringList &lines, LayoutItem *parent);
LayoutItem *rootItem;
};
And I'm trying to access it from this view class:
class Layout : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(LayoutModel model READ model WRITE setModel NOTIFY modelChanged)
private:
LayoutModel & m_model;
public:
explicit Layout(QQuickItem * parent = nullptr);
LayoutModel & model() const;
void setModel(const LayoutModel & model);
void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData & value) override;
void geometryChanged(const QRectF & newGeometry, const QRectF & oldGeometry) override;
signals:
void modelChanged();
};
But I can't find a way to actual use the model. I can't even properly set up read and write to the model, since QAbstractItemModels (and models in Qt in general) have their copy constructor deleted to enforce entity singularity. Here's my current broken implementation:
Layout::Layout(QQuickItem * parent) : QQuickItem(parent) {}
LayoutList & Layout::model() const
{
return m_model;
}
void Layout::setModel(const LayoutList & model)
{
if (m_model == model)
return;
m_model = model;
emit modelChanged();
}
So, how can I make it so that I can use this extended qml class with my LayoutModel?

QObjects do not have a copy constructor, so you must use a pointer:
*.h
class Layout : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(LayoutModel *model READ model WRITE setModel NOTIFY modelChanged)
private:
LayoutModel *m_model;
public:
explicit Layout(QQuickItem * parent = nullptr);
LayoutModel *model();
void setModel(LayoutModel * model);
...
signals:
void modelChanged();
};
*.cpp
...
LayoutModel *Layout::model()
{
return m_model;
}
void Layout::setModel(LayoutModel *model)
{
if (m_model == model)
return;
m_model = model;
emit modelChanged();
}

Related

Why I get an "use of delete function" from a class that inherits from QAbstractListModel?

I am trying to implement a ListView. So far, I have implemented a class named PacientModel that inherits of QAbstractListModel.
#ifndef PACIENTMODEL_H
#define PACIENTMODEL_H
#include <QAbstractListModel>
class PacientModel : public QAbstractListModel {
Q_OBJECT
public:
enum PacientRole {
CIRole,
NameRole,
LastNameRole
};
Q_ENUM(PacientRole)
PacientModel(QObject * parent = nullptr);
int rowCount(const QModelIndex & = QModelIndex()) const;
QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const;
QHash<int, QByteArray> roleNames() const;
Q_INVOKABLE QVariantMap get(int row) const;
Q_INVOKABLE void append (const QString &CI, const QString &name, const QString &lastName);
Q_INVOKABLE void set (int row, const QString & CI, const QString &name, const QString &lastName);
Q_INVOKABLE void remove (int row);
private:
struct Pacient {
QString CI;
QString name;
QString lastName;
};
QList<Pacient> m_pacients; };
#endif // PACIENTMODEL_H
I also have the implementation of the ListView, but when I compiled the code, I got this error.
C:\Qt\Qt5.8.0\5.8\android_armv7\include\QtCore\qmetatype.h:765: error: use of deleted function 'PacientModel::PacientModel(const
PacientModel&)'
return new (where) T(*static_cast(t));
How can I resolve this?
Per this Q&A QAbstractListModel's copy constructor is marked as private. That means that it is not copyable and therefore, as you inherited from it, your derived class is also not copyable by default.
If you want to make copies of your class then you need to manually define a copy constructor for the class instead of relying on the compiler doing it for you (since it won't).
After several days I discovered that the error wasn't in the class declaration, It was in the way I assigned the data to the listview... I was using this:
objetoLP->setProperty("modelList", QVariant::fromValue(pacientes));
instead of:
ctxt->setContextProperty("modelList", &pacientes);

how to add stylesheet in delegate class?

In my application I'm having a table widget. I created a new class to implement delegate for the table cells alignment as center. The delegate works fine, but stylesheet is not working properly. Below is how I implement the delegate class:
//i create new class with "QStyledItemDelegate" as base class
//QAlignmentDelegate.h
#ifndef QALIGNMENTDELEGATE_H
#define QALIGNMENTDELEGATE_H
#include <QStyledItemDelegate>
class QAlignmentDelegate : public QStyledItemDelegate
{
public:
QAlignmentDelegate(Qt::Alignment alignment);
virtual void paint(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const;
private:
Qt::Alignment m_alignment;
};
#endif // QALIGNMENTDELEGATE_H
//QAlignmentDelegate.cpp
#include "qalignmentdelegate.h"
QAlignmentDelegate::QAlignmentDelegate(Qt::Alignment alignment)
{
m_alignment=alignment;
}
void QAlignmentDelegate::
paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionViewItem alignedOption(option);
alignedOption.displayAlignment = m_alignment;
QStyledItemDelegate::paint(painter, alignedOption, index);
}
//main.cpp
QTableWidgetItem * protoitem = new QTableWidgetItem();
protoitem->setTextAlignment(Qt::AlignRight);
QTableWidgetItem * newitem = protoitem->clone();
tableWidget->setItemPrototype(newitem);
Now how to implement stylesheet in this delegate class?

How to get the old value when handling QAbstractItemModel::dataChanged() signal?

I have a QTableView which have set a QStandardItemModel. The user edits data in some index in the view and then the model emits the dataChanged() signal. In the SLOT where I am handling the SIGNAL I have the QModelIndex range of the user changes and thus I can get the new values the user have entered. How can I obtain the old values at that point?
After some research I figured out that there is no standard way to achive this behaviour. To solve the problem I had to inherit QStandardItemModel and reimplement setData() like this:
class RecallModel : public QStandardItemModel
{
public:
RecallModel (QObject * parent = 0) : QStandardItemModel(parent) {}
// Reimplemented
bool setData(const QModelIndex &index, const QVariant &value, int role= Qt::EditRole)
{
// backup the previous model data
if (role == Qt::EditRole || role == Qt::DisplayRole)
QStandardItemModel::setData(index, data(index), Qt::UserRole + 1);
return QStandardItemModel::setData(index, value, role);
}
};
And after that I can access the old data in the slot handling the dataChanged() signal:
void SomeObject::handleDataChange(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
...
const QVariant &vOldData = index.data(Qt::UserRole + 1); // here is the old data
const QVariant &vNewData = index.data(Qt::DisplayRole); // here is the new data
...
}
Since QStandardItemModel is a simple model, it does not have a signal with this feature. If you want such a feature you can subclass QAbstractItemModel and have your own custom class and implement setData and emit a custom signal which contains both the old and the new values.
As a workaround you can connect the itemChanged signal of QStandardItemModel to some slot :
connect(model,SIGNAL(itemChanged(QStandardItem*)),this, SLOT(onModelChanged(QStandardItem*)));
And store the new value as a Qt::UserRole in model to use it as the old value when the slot is called next time :
void MyClass::onModelChanged(QStandardItem *item)
{
disconnect(model,SIGNAL(itemChanged(QStandardItem*)),this, SLOT(onModelChanged(QStandardItem*)));
QVariant oldValue = item->data(Qt::UserRole);
item->setData(item->data(Qt::DisplayRole), Qt::UserRole); //Store the new value for next use
connect(model,SIGNAL(itemChanged(QStandardItem*)),this, SLOT(onModelChanged(QStandardItem*)));
}
User can change data with delegate so possible solution is:
#ifndef ITEMDELEGATE_H
#define ITEMDELEGATE_H
#include <QItemDelegate>
class ItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
explicit ItemDelegate(QObject *parent = 0);
protected:
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void setEditorData(QWidget * editor, const QModelIndex & index) const;
void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const;
void updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const;
signals:
void dataChanged(QString oldValue,QString newValue) const;
public slots:
private:
mutable QString old;//we want change member data in const method
};
#endif // ITEMDELEGATE_H
As you can see many methods are const by default so I did some tricks (such as mutable) to avoid problems. Also in my edited answer I doesn't store old data in UserRole+1 , all done with old mutable variable.
cpp:
#include "itemdelegate.h"
#include <QLineEdit>
#include <QDebug>
ItemDelegate::ItemDelegate(QObject *parent) :
QItemDelegate(parent)
{
}
QWidget *ItemDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QLineEdit *editor = new QLineEdit(parent);
return editor;
}
void ItemDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
old = index.model()->data(index, Qt::EditRole).toString();//store old data
QLineEdit *line = qobject_cast<QLineEdit*>(editor);
line->setText(old);
}
void ItemDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index)const
{
QLineEdit *line = static_cast<QLineEdit*>(editor);
QString data = line->text();
emit dataChanged(old, line->text());
model->setData(index, data);
}
void ItemDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
Usage:
ItemDelegate * del = new ItemDelegate;
connect(del,&ItemDelegate::dataChanged,[=](QString oldValue,QString newValue) {
qDebug() << "old" << oldValue<< "new" <<newValue ;
});
ui->tableView->setItemDelegate(del);
I tested it and it works. It will work with different models. QTableView uses lineEdit as delegate by default, so user will not see any changes in view.
I used here C++11 (CONFIG += c++11 to .pro file) and new syntax of signals and slots, but of course you can use old syntax if you want.
All solutions before this answer rely on Qt-specific features. But you can move INSERT, UPDATE, DELETE (not SQL, but common) functionality outside Qt framework. Look at Unit Of Work Design Pattern, and related Domain Object pattern from "Patterns of Enterprise Application Architecture".
You can modify this patterns to hold old values and get it when you modify during setData().

Refreshing the choices in a QComboBox used as an editor in a QTableView

I'm using a QComboxBox in a QTableView in Qt. When I add a new row then createEditor() of my delegate is called and I can instantiate the combobox with the correct set of choices available at that time. The problem is, that the user can load different files outside the table, and based on the content of the file the combobox would need to updated their items.
Is there a way that I can get the editor of a cell, so that I can update the choices accordingly? Since other cells of the table should not be destroyed, I can not simply recreate the table with the new data, I need only to update the comboboxes of certain cells.
I've been looking in the sourcecode of QAbstractItemView and there is a function editorForIndex() which would be exactly what I need, but this is implemented privately inside the view so it is not accessible even in a derived class.
Of course I can keep a record of the boxes that I create, so that I can update them accordingly later on, but I wonder if there is really no other way of doing this.
You can have the contents of your combobox as a class member of your delegate in a QStringList. Your item delegate can be like :
#include <QItemDelegate>
#include <QComboBox>
class ComboBoxDelegate: public QItemDelegate
{
Q_OBJECT
public:
ComboBoxDelegate(QObject *parent = 0);
QWidget *createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index ) const;
void setEditorData( QWidget *editor,
const QModelIndex &index ) const;
void setModelData( QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index ) const;
void updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index ) const;
QStringList comboItems;
mutable QComboBox *combo;
private slots:
void setData(int val);
};
ComboBoxDelegate::ComboBoxDelegate(QObject *parent ):QItemDelegate(parent)
{
}
QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
combo = new QComboBox( parent );
QObject::connect(combo,SIGNAL(currentIndexChanged(int)),this,SLOT(setData(int)));
combo->addItems(comboItems);
combo->setMaxVisibleItems(comboItems.count());
return combo;
}
void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QString text = index.model()->data( index, Qt::DisplayRole ).toString();
int comboIndex = comboItems.indexOf(QRegExp(text));
if(comboIndex>=0)
(static_cast<QComboBox*>( editor ))->setCurrentIndex(comboIndex);
}
void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
model->setData( index, static_cast<QComboBox*>( editor )->currentText() );
}
void ComboBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry( option.rect );
}
void ComboBoxDelegate::setData(int val)
{
emit commitData(combo);
//emit closeEditor(combo);
}
When you want to update the items in combobox somewhere in your code just get a pointer to the item delegate by calling itemDelegateForColumn and access the comboItems member :
ComboBoxDelegate * itemDelegate = (ComboBoxDelegate *)ui->tableView->itemDelegateForColumn(columnIndex);
//Updating combobox items
itemDelegate->comboItems.append("newItem");
...

How to correctly subclass and plug into view QAbstractProxyModel

What do I have to do in order to correctly subclass QAbstractProxyView and make it work with view such as QListView. So far I've did this:
class Proxy : public QAbstractProxyModel
{
Q_OBJECT
public:
explicit Proxy(QAbstractItemModel* source_model = 0, QObject *parent = 0);
QModelIndex mapToSource(const QModelIndex & proxyIndex) const;
QModelIndex mapFromSource(const QModelIndex & sourceIndex) const;
virtual QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const;
virtual QModelIndex parent(const QModelIndex &child) const;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex&, int = Qt::DisplayRole) const;
signals:
public slots:
};
Every functionin this class is implemented in separate cpp file. Unfortunately doing in my Ui class:
Proxy* p = new Proxy;
p->setSourceModel(model_);//model is of class Model
listView->setModel(p);
doesn't work; Data from model are not displayed in listView.
If it is meant to work with QListView, you should make your model inherit from QAbstractListModel. You only need to implement three methods: the constructor, rowCount and data. Also see this post for more information: How to create a custom model for a QListView in Qt