QML TableView not calling QAbstractTableModel headerData()-function - c++

I'm currently working with a QML / QtQuick TableView. The data is shown -> great. The only thing I am currently not getting to work, are the headers: As far as I understand the Qt Model concept, the TableView should automatically call the C++ QAbstractTableModel::headerData(int section, Qt::Orientation orientation, int role)-function. However, when I add a qDebug() << "Hello World!" at the beginning of the function (or using the debugger ;)), this function never gets called and the TableView's header doesn't show any text.
Has anyone got this working? Or is this not implemented (yet?) for some reason (Qt 5.3)? What would the alternatives be?
EDIT: Additions
mymodel.cpp
QVariant myModel::headerData(int section, Qt::Orientation orientation, int role) const
{
qDebug() << "Hello World!";
}
[... I left out the implementation of the other functions
here as they work as expected ...]
mymodel.h
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QAbstractTableModel>
#include "inputvectors.h"
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
MyModel (QObject *parent = 0);
// InputVectors is just another class I use but it basically
// contains two `QList`s
MyModel (InputVectors *inputData, QObject *parent=0);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
InputVectors *inputData;
};
#endif // MYMODEL_H
main.qml
[...]
TableView {
id: myTableView
anchors.fill: parent
frameVisible: false
model: controller.getInputVectorsModel()
TableViewColumn {
role: "row_line_numbers"
width: 40
delegate: Item {
Text { text: styleData.value; anchors.left:parent.left;anchors.leftMargin: 12}
}
}
}
[...]

Related

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

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();
}

Adding and removing items from a C++ list in QML/QT 5.7

I'm working on a simple project to try and learn QT 5.7, QML, and C++. I want to create a simple interface that has a list of items that I can add and remove items from using a couple of buttons. I've been reading a bunch of different guides online trying to piece together something but I keep getting stuck. I've tried using QQmlListProperty<T> and QAbstractListModel but I have questions about both approaches:
For my project is QQmlListProperty<T> the right thing to use or I should use QAbstractListModel?
For either case how do I notify my QML view that the list has changed?
If I use 'QAbstractListModel', do I just need to create Q_INVOKABLE methods for adding and removing items from my list?
Below is my code for both QQmlListProperty<T> and QAbstractListModel so far. I've left out most of the implementation of the classes to keep this post short but if the implementation is needed I'm happy to add it.
QQmlListProperty Item Class
class PlaylistItemModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
public:
explicit PlaylistItemModel(QObject *parent = 0);
QString getName();
void setName(const QString &name);
signals:
void nameChanged();
public slots:
private:
QString _name;
};
QQmlListProperty List Class
class PlaylistModel : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<PlaylistItemModel> items READ getItems NOTIFY itemsChanged)
public:
explicit PlaylistModel(QObject *parent = 0);
QQmlListProperty<PlaylistItemModel> getItems() const;
Q_INVOKABLE void addItem(PlaylistItemModel *item);
Q_INVOKABLE void removeItem(PlaylistItemModel *item);
Q_INVOKABLE void clearItems();
static void append(QQmlListProperty<PlaylistItemModel> *list, PlaylistItemModel *item);
static PlaylistItemModel* at(QQmlListProperty<PlaylistItemModel> *list, int index);
static int count(QQmlListProperty<PlaylistItemModel> *list);
static void clear(QQmlListProperty<PlaylistItemModel> *list);
signals:
void itemsChanged();
public slots:
private:
QList<PlaylistItemModel*> _items;
};
Implementation of getItms():
QQmlListProperty<PlaylistItemModel> PlaylistModel::getItems() const
{
return QQmlListProperty<PlaylistItemModel>(this, _items, &append, &count, &at, &clear);
}
QAbstractListModel
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
enum ModelRoles
{
ItemRole = Qt::UserRole + 1
};
MyModel(QObject *parent = 0);
// QAbstractItemModel interface
int rowCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
QHash<int, QByteArray> roleNames() const;
private:
QList<QString> _listData;
QString itemAt(const QModelIndex &index) const;
};
I would generally suggest that QAbstractListModel is the right class to go with most of the time, unless you are sure you'll only be working with a simple list.
For either case how do I notify my QML view that the list has changed?
QAbstractItemModel (which QAbstractListModel inherits) has a number of different methods that you should call from your subclass to inform the view(s) connected to it that something has happened. When you're inserting items to it, you want QAbstractItemModel::beginInsertRows and QAbstractItemModel::endInsertRows.
If your model is representing something simple, like a list of names for instance, your insertion might look something like this:
Assuming:
class MyModel : public QAbstractListModel
{
public:
Q_INVOKABLE void addPerson(const QString &name);
private:
QVector<QString> m_names;
};
void MyModel::addPerson(const QString &name)
{
beginInsertRows(QModelIndex(), m_names.count(), m_names.count());
m_names.append(name);
endInsertRows();
}
You then need to implement QAbstractItemModel::rowCount, QAbstractItemModel::roleNames, and QAbstractItemModel::data at a minimum, but it looks like you've already got that handled. If you want to implement editing of data, you also want to implement QAbstractListModel::setData.
Once you've done that, register the type (using qmlRegisterType):
qmlRegisterType<MyModel>("MyModelImport", 1, 0, "MyModel");
And then import & instantiate it from QML, and use it on your view:
import MyModelImport 1.0
import QtQuick 2.6
ListView {
id: listView
anchors.fill: parent
model: MyModel {
}
delegate: TextInput {
text: model.text
onEditingFinished: {
// when the text is edited, save it back to the model (which will invoke the saveData we overloaded)
model.text = text
}
}
Rectangle {
height: 100
width: 400
color: "red"
Text {
text: "Add item"
}
MouseArea {
anchors.fill: parent
onClicked: listView.model.addName("Some new name")
}
}
}

How to combine modal dialog editor and in-place widget editor in QModelViews?

I have a model with simple and complex data and I would like to have in-place widgets to edit simple data, but modal dialogs to edit complex data... How can I achieve that in a clean way?
(I would really prefer to do everything via a sub class of QItemDelegate, and no view specific hack)
I think you have to subclass the view and override the QAbstractItemView::edit() function to handle different editing paths. For example:
class MyView : public QTreeView
{
[..]
protected:
bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event)
{
if (index.row() > 5) { // Use your own criteria for simple/complex data
// Simple data with default editor.
return QTreeView::edit(index, trigger, event);
} else {
// Edit complex data.
QDialog dialog;
dialog.exec();
return false;
}
}
[..]
};
Try next delegate. I show main idea in the example:
Header:
#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;
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index);
signals:
public slots:
};
#endif // ITEMDELEGATE_H
I show you only editorEvent because all another methods you can wrote by yourself, it will be custom delegate, but in editorEvent we create modal dialog.
bool ItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index)
{
if(index.row()%2)//specific items, you can use it another options, it is just example
{
QInputDialog* dia = new QInputDialog;//create dialog, just example, it can be your QDialog subclass
dia->setInputMode(QInputDialog::TextInput);
//dia->setAttribute(Qt::WA_DeleteOnClose);
dia->setModal(true);
connect(dia, &QInputDialog::finished,[=]()//connection which will take data from dialog
{
model->setData(index,dia->textValue());//provide some method in your dialog to get user data and set it in model
delete dia;//we don't want memory leaks
});
dia->show();
}
return QItemDelegate::editorEvent(event,model,option,index);
}
I used here C++11 (CONFIG += c++11 to .pro file) and new syntax of signals and slots
This is an old question, but given that both answers are production-unsatisfactory (crash when returning false, nested event loops, etc)...
Please note I'm not faulting the posters for saying "it can't be done cleanly that way".
How about an entirely different, supported approach?
I have an implementation like this before (pseudo-code):
Disable edit triggers for "complex" items
.
auto item = model->item(row, column);
item->setFlags(item->flags() & ~Qt::ItemFlag::ItemIsEditable); // not editable
Connect to QAbstractItemView::activated
Ignore event for "simple" items
connect(m_ui.tableView,
&QAbstractItemView::activated,
this,
&QtGuiApplication2::onDataActivated);
//...
void QtGuiApplication2::onDataActivated(const QModelIndex &index) {
if(!is_complex)
return;
EditDialog dlg;
if(dlg.exec() == QDialog::DialogCode::Accepted) {
updatemodel();
}
}
Item Delegate:
Ignore for "complex" items.
QWidget* VariableEditorDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const {
if(is_complex)
return nullptr; // same as base class
return new EditorWidget(parent);
}
Now of course this isn't as nice as having just the delegate, but at least it doesn't skirt so closesly along crashes.

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().

QML,How to dynamicaly change Item of Repeater from C++

I have QML Repeater on Grid, when i clicked on item i emit signal which processed by C++ class, and then changed array in C++ which then assign as model of QML Repeater. Is there a way to change just two elements of C++ model, not whole model as i did?
that's my qml file
Grid{
height:width
rows:8
columns: 8
Repeater{
id: chessPiecesRptr
...
signal chessfigureSelected(int index)
delegate: Item{
id:chessPiecesItm
Image{
...
}
MouseArea
{
anchors.fill:parent
onClicked: {
chessPiecesRptr.chessfigureSelected(index)
}
}
}
}
C++ method which update model of QML Repeater
void ChessFiguresClass::changeModel()
{
QStringList dataList;
for(int i=0;i<64;i++)
dataList.append(QChar(posArray[i]));
QQmlProperty::write(chessPiecesRptr, "model", QVariant::fromValue(dataList));
}
Contrary to the accepted answer it is indeed possible without going all the way to implement an entire QAbstractListModel.
It is true that QStringList doesn't have any way to notify for changes, but all you have to do is wrap it up in a property, something like this:
Q_PROPERTY(QVariant myModel READ myModel NOTIFY myModelChanged)
...
QVariant myModel() { return QVariant::fromValue(myStringList); }
...
void myModelChanged(); // signal
And just emit myModelChanged every time you want to reflect a change in the model, be that replacing it with a different model or changing it internally. And use the myModel property for the Repeater's model.
However, if you have a lot of items in the model or the delegates are complex, it is always a good idea to implement your own QAbstractListModel, because with the approach above, the repeater will recreate the entire model every time it "changes", whereas the QAbstractListModel will only update the actual changes.
I'm afraid it is not possible. QList (and QStringList) does not have inner mechanisms to notify Qml items about its changes. Only when model property from QML item is changed, the whole list is read again.
I had faced the same problem before and I implemented a string list using QAbstractListModel as base class. The header looks like this:
#ifndef _SIMPLEMODEL_H_
#define _SIMPLEMODEL_H_
#include <QtCore>
class SimpleStringModel : public QAbstractListModel
{
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_DISABLE_COPY(SimpleStringModel)
Q_OBJECT
public:
explicit SimpleStringModel(QObject* parent = 0);
SimpleStringModel(const QList<QString>& initList, QObject* parent = 0);
virtual ~SimpleStringModel();
private:
enum Roles{
ModelDataRole = Qt::UserRole+1
};
public:
int count() const;
public:
void append(const QString& item);
void insert(int index, const QString& item);
void append(const QList<QString>& items);
void insert(int index, const QList<QString>& items);
bool removeOne(const QString& item);
int removeAll(const QString& item);
void removeAt(int index);
QList<QString> list() const;
signals:
void countChanged();
// QAbstractItemModel interface
public:
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
virtual QHash<int, QByteArray> roleNames() const;
private:
QList<QString> m_data;
};
#endif //_SIMPLEMODEL_H_
You can get all the code here. I hope this help you.