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
Related
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();
}
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().
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");
...
I am new to Qt. I have a table with a delegate combo box as the second column.
I wanted to detect click on the combo box.
I thought of one approach : add the combo box as a private variable in the ComboBoxDelegate and adding a public slot as
void on_cb_currentIndexChanged ( const QString & text );
However, since createEditor is const and I cannot re-assign the variable cb in this method.
Is there any alternative way this can be done?
class ComboBoxDelegate : public QItemDelegate
{
Q_OBJECT
public:
ComboBoxDelegate(std::vector<std::string> values, QObject *parent = 0);
~ComboBoxDelegate();
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;
private:
std::vector<std::string> values;
}
As you said, you can have the combobox as a member of delegate class and connect the currentIndexChanged signal of combobox to some slot when creating the combobox in createEditor. You should declare combobox as mutable for newing it in createEditor which is constant. If a data member is declared mutable, then it is legal to assign a value to this data member from a const member function :
#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)
{
comboItems<<"Item 1"<<"Item 2"<<"Item 3";
}
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);
}
Here i connect the currentIndexChanged signal of combobox to setData slot which commits the data to the model. You can also connect that signal to any slot you wish.
I have a Problem using the Qt translation:
In a tableView with I'm using a Delegate to get a Combo Box as Edit function:
this->gndDelegate = new GenderDelegate(this);
ui->tableView->setItemDelegateForColumn(AthleteModel::GENDER_COLUMN, this->gndDelegate);
The ComboBox hast to values, which I want to translate with the tr() command.
All other Translations works fine, but this two added Items aren't translated:
QWidget *GenderDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QComboBox *cmbBox = new QComboBox(parent);
cmbBox->addItem(tr("male"), "male");
cmbBox->addItem(tr("female"), "female");
return cmbBox;
}
A representation in the qm file exists for this two values
Thanks for your help ...
You must add the directive Q_OBJECT in your delegate implementation. E.g:
class KeyConfigurationDelegate : public QItemDelegate
{
Q_OBJECT //Add This directive !!!
public:
explicit KeyConfigurationDelegate(QObject *parent = 0);
~KeyConfigurationDelegate();
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;
private:
QStringList list;
QStringListModel model;
};