Find the right qt data model with variable fields - c++

I am looking for a datamodel. Qt offers a lot of options, so I would like your advice.
Here is the simpified example of the structure of my datamodel:
It has two fields: field 1 and field 2.
Field1 can be "none", "option" or "number"
If field1 is "none", field2 should be empty and contain no options.
If field1 is "option", field2 should give me an option for "a" or "b"
If field1 is "number", field2 should give me the option to enter any number between 1 and 100.
I would like to have a table like view of this data on a form, with combo or spinboxes to select the data. It should not be possible to enter any other data than specified.
Could you please point me in the right direction and maybe provide an example of how to implement this in the most simple way in Qt.
I want to be able to store this data as well to a file, for which I probably will use json.

Assumed you are aware of Qt's model/view system, and knows that you should use View to visualize your data model, Then what you are going to implement further more is delegate , which could offer extra interface other than text input for each field.
According to this tutorial , you should do these:
Subclassing QStyledItemDelegate , create your own delegate class , e.g MyDelegate
class MyDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
enum FIELDS
{
COL_FIELD1,
};
explicit MyDelegate(QObject *parent = nullptr);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const Q_DECL_OVERRIDE;
void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const Q_DECL_OVERRIDE;
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE;
};
Implement these functions
createEditor, define what kind of widget offering for user to do editing task
setEditorData, convert data from DataModel to widget
setModelData, convert data from Widget to DataModel
updateEditorGeometry, define widget looking in the field.
On following codes, i tried to figure out your need to offer options of field1.
#include "mydelegate.h"
MyDelegate::MyDelegate(QObject *parent) : QStyledItemDelegate(parent)
{
}
QWidget* MyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QComboBox* __comboBox = new QComboBox(parent);
if(index.column()==COL_FIELD1)
{
__comboBox->addItem("none");
__comboBox->addItem("option");
__comboBox->addItem("number");
}
else
{
//! according to your need...
}
return __comboBox;
}
void MyDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QComboBox* __comboBox = qobject_cast<QComboBox*>(editor);
if(index.column()==COL_FIELD1)
{
__comboBox->setCurrentText(index.model()->data(index).toString());
}
}
void MyDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QComboBox* __comboBox = qobject_cast<QComboBox*>(editor);
model->setData(index,QVariant::fromValue(__comboBox->currentText()));
}
void MyDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
instanized MyDelegate , attach to your View
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//! prepare a demo model
QStringListModel *model = new QStringListModel();
QStringList list;
list << "a" << "b" << "c";
model->setStringList(list);
//! attach a MyDelegate for Row1
ui->tableView->setModel(model);
ui->tableView->setItemDelegateForRow(0,new MyDelegate(this));
}
MainWindow::~MainWindow()
{
delete ui;
}
Demostrated effect would like this:
Just tried changed the contents on the implementation of MyDelegate to get the effects you need (e.g spinBox , or different options for Field2) , have fun!

Related

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

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

Make the delegate QtComboBox able to detect clicks

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.

Problems with translation in Qt

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