PDA

View Full Version : QTableView + QAbstractItemModel and adding rows



karlkar
5th July 2013, 20:51
I have a problem with QTableView. I want to have a table with QComboBox and QCheckBox in every row. That is why I created special class for checkbox and delegate.

BooleanWidget

#ifndef BOOLEANWIDGET_H
#define BOOLEANWIDGET_H

#include <QWidget>
#include <QCheckBox>

class BooleanWidget : public QWidget
{
Q_OBJECT
QCheckBox * checkBox;

public:
BooleanWidget(QWidget * parent = 0);

bool isChecked(){return checkBox->isChecked();}
void setChecked(bool value){checkBox->setChecked(value);}
};

#endif // BOOLEANWIDGET_H


#include "booleanwidget.h"
#include <QHBoxLayout>

BooleanWidget::BooleanWidget(QWidget *parent) :
QWidget(parent)
{
checkBox = new QCheckBox(this);
QHBoxLayout * layout = new QHBoxLayout(this);
layout->addWidget(checkBox,0, Qt::AlignCenter);
}


ComboBoxDelegate

#ifndef COMBOBOXDELEGATE_H
#define COMBOBOXDELEGATE_H

#include <QStyledItemDelegate>
#include <QTableView>

class ComboBoxDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit ComboBoxDelegate(QTableView *tableView, QObject *parent = 0);

virtual 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;
QRect CheckBoxRect(const QStyleOptionViewItem &view_item_style_options) const;
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;

private:
QTableView *tableView_;

signals:

public slots:

};

#endif // COMBOBOXDELEGATE_H



#include "comboboxdelegate.h"
#include <QComboBox>
#include <QCheckBox>
#include <QRect>
#include <QApplication>
#include <QSignalMapper>
#include "booleanwidget.h"

ComboBoxDelegate::ComboBoxDelegate(QTableView *tableView, QObject *parent) :
QStyledItemDelegate(parent), tableView_(tableView)
{
}

QWidget* ComboBoxDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
// ComboBox ony in column 1
if(index.column() == 0) {
QSignalMapper* signalMapper = new QSignalMapper(const_cast<ComboBoxDelegate*>(this));

// Create the combobox and populate it
QComboBox *cb = new QComboBox(parent);
cb->addItem(QString(""));
cb->addItem(QString("Produkt 1"));
cb->addItem(QString("Produkt 2"));
cb->addItem(QString("Produkt 3"));
connect(cb, SIGNAL(currentIndexChanged(int)), signalMapper, SLOT(map()));
signalMapper->setMapping(cb, index.row());
connect(signalMapper, SIGNAL(mapped(int)),
tableView_->model(), SLOT(changed(const int)));
return cb;
} else if (index.column() == 6)
return new BooleanWidget(parent);
else
return QStyledItemDelegate::createEditor(parent, option, index);
}

void ComboBoxDelegate::setEditorData ( QWidget *editor, const QModelIndex &index ) const
{
if(QComboBox *cb = qobject_cast<QComboBox *>(editor)) {
// get the index of the text in the combobox that matches the current value of the itenm
QString currentText = index.data(Qt::EditRole).toString();
int cbIndex = cb->findText(currentText);
// if it is valid, adjust the combobox
if(cbIndex >= 0)
cb->setCurrentIndex(cbIndex);
} if (BooleanWidget *cb = qobject_cast<BooleanWidget *>(editor)) {
cb->setChecked(index.data(Qt::DisplayRole).toInt() == 1);
} else {
QStyledItemDelegate::setEditorData(editor, index);
}
}

void ComboBoxDelegate::setModelData ( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
{
if(QComboBox *cb = qobject_cast<QComboBox *>(editor))
// save the current text of the combo box as the current value of the item
model->setData(index, cb->currentText(), Qt::EditRole);
else if (BooleanWidget *cb = qobject_cast<BooleanWidget *>(editor)) {
model->setData(index, cb->isChecked() ? 1 : 0);
} else
QStyledItemDelegate::setModelData(editor, model, index);
}

QRect ComboBoxDelegate::CheckBoxRect(const QStyleOptionViewItem &view_item_style_options) const {

QStyleOptionButton check_box_style_option;
QRect check_box_rect = QApplication::style()->subElementRect( QStyle::SE_CheckBoxIndicator, &check_box_style_option);
QPoint check_box_point(view_item_style_options.rect.x() + view_item_style_options.rect.width() / 2 - check_box_rect.width() / 2,
view_item_style_options.rect.y() + view_item_style_options.rect.height() / 2 -
check_box_rect.height() / 2);
return QRect(check_box_point, check_box_rect.size());
}

void ComboBoxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index) const {

editor->setGeometry(option.rect);
}

void ComboBoxDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {

if (index.column() == 6) {
int value = index.model()->data(index, Qt::DisplayRole).toInt();

QStyleOptionButton check_box_style_option;
check_box_style_option.state |= QStyle::State_Enabled;
if (value == 1) {
check_box_style_option.state |= QStyle::State_On;
} else {
check_box_style_option.state |= QStyle::State_Off;
}
check_box_style_option.rect = ComboBoxDelegate::CheckBoxRect(option);

QApplication::style()->drawControl(QStyle::CE_CheckBox, &check_box_style_option, painter);
} else
QStyledItemDelegate::paint(painter, option, index);
}


It works ok! But now I want to add new row to the table every time I change a value of combobox in last row. That I do in my Model:


#ifndef DATAMODEL_H
#define DATAMODEL_H

#include <QAbstractTableModel>
#include <QStringList>
#include <QList>
#include "product.h"

class DataModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit DataModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const ;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole);
Qt::ItemFlags flags(const QModelIndex & index) const ;

private:
QStringList headers_;
QList<Product*> dataBase_;

signals:
void editCompleted(const QString &);

public slots:
void changed(const int);
};

#endif // DATAMODEL_H



#include "datamodel.h"
#include <QStringList>
#include <iostream>

DataModel::DataModel(QObject *parent) :
QAbstractTableModel(parent)
{
headers_ = QStringList();
headers_ << "Nazwa, opis" << "Kod towaru" << "Ilość" << "JM" << "Cena netto" << "Wartość netto" << "Refaktura";
dataBase_.push_back(new Product);
}

int DataModel::rowCount(const QModelIndex & /*parent*/) const
{
return dataBase_.size();
}

int DataModel::columnCount(const QModelIndex & /*parent*/) const
{
return 7;
}

QVariant DataModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal) {
if( role == Qt::DisplayRole) {
return headers_.at(section);
}
} else {
if( role == Qt::DisplayRole) {
return section + 1;
}
}
}

QVariant DataModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole)
{
std::cout << index.row() << ", " << index.column() << std::endl;
return dataBase_.at(index.row())->getElement(index.column());
}
return QVariant();
}

bool DataModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
if (role == Qt::EditRole)
{
dataBase_.value(index.row())->setElement(index.column(), value.toString());
}
return true;
}

Qt::ItemFlags DataModel::flags(const QModelIndex &index) const
{
if (index.column() == 0 || index.column() == 2 || index.column() == 6)
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
else
return QAbstractTableModel::flags(index);
}

void DataModel::changed(const int row) {

dataBase_.value(row)->setElement(1, "set");
emit dataChanged(index(row, 1), index(row, 6));
}


I tried to make it in changed(const int row), but it doesn't work.

And here is code of product


#ifndef PRODUCT_H
#define PRODUCT_H

#include <QObject>
#include <QVariant>

class Product
{
public:
Product();
Product(QString indexName, QString productIndex, QString index,
QString unit, QString place, int quantity, double sellingPrice,
double buyingPrice, QString category);

QVariant getElement(int element) const;
void setElement(int element, QVariant value);

QString getIndexName() const {
return indexName_;
}

QString getProductIndex() const {
return productIndex_;
}

QString getIndex() const {
return index_;
}

QString getUnit() const {
return unit_;
}

QString getPlace() const {
return place_;
}

int getQuantity() const {
return quantity_;
}

double getSellingPrice() const {
return sellingPrice_;
}

double getBuyingPrice() const {
return buyingPrice_;
}

QString getCategory() const {
return category_;
}

bool getReInvoice() const {
return re_invoice_;
}

private:
QString indexName_;
QString productIndex_;
QString index_;
QString unit_;
QString place_;
int quantity_;
double sellingPrice_;
double buyingPrice_;
QString category_;
bool re_invoice_;

signals:

public slots:

};

#endif // PRODUCT_H


#include "product.h"

Product::Product() : quantity_(0), sellingPrice_(0), buyingPrice_(0), re_invoice_(false) {

}

Product::Product(QString indexName, QString productIndex, QString index,
QString unit, QString place, int quantity, double sellingPrice,
double buyingPrice, QString category) :
indexName_(indexName), productIndex_(productIndex), index_(index),
unit_(unit), place_(place), quantity_(quantity), sellingPrice_(sellingPrice),
buyingPrice_(buyingPrice), category_(category), re_invoice_(false) {

}

QVariant Product::getElement(int element) const {
switch(element){
case 0:
return indexName_;
case 1:
return index_;
case 2:
return quantity_;
case 3:
return unit_;
case 4:
return sellingPrice_;
case 5:
return buyingPrice_;
case 6:
return re_invoice_;
}
return QVariant();
}

void Product::setElement(int element, QVariant value) {
switch(element){
case 0:
indexName_ = value.toString();
break;
case 1:
index_ = value.toString();
break;
case 2:
quantity_ = value.toInt();
break;
case 3:
unit_ = value.toString();
break;
case 4:
sellingPrice_ = value.toDouble();
break;
case 5:
buyingPrice_ = value.toDouble();
break;
case 6:
re_invoice_ = value.toBool();
break;
}
}

karlkar
5th July 2013, 22:15
Hi!
I have a TableView with my own model.
I want to add new row. My model contains QList<Product> where each row is one Product.
What I want to do is to add new row. I do it by adding new element to list and then calling insertRow(int). But it does not change a thing... How can it be done? Please give me exaple of code, because I've lost hope to find an answer on my own...

ChrisW67
6th July 2013, 00:27
You will need to provide more information about your QAbstractItemModel implementation and how you are updating the model.

If you are inserting a row via the QAbstractItemModel interface then you call insertRow() and then set the values in the model using setData(). Your implementation of insertRows() (used by insertRow()) should call beginInsertRows() and endInsertRows() around adding the new "row" the underlying data structures.

If something is updating the data structure outside of the QAbstractItemModel interface then you must ensure it does the equivalent of the insertRows(), removeRows(), or dataChaged() implementations so that views stay correctly in synchronisation.

anda_skoa
6th July 2013, 16:59
As Chris said, you first need to call beginInsertRows(), then add the product to the list and then call endInsertRows().

The two functions will take care of emitting the correct signals that the view is expecting.

Cheers,
_

sami1592
2nd February 2016, 15:22
If something is updating the data structure outside of the QAbstractItemModel interface

This is the case for me, my dataSouce/dataStructure for my QAbstractTableModel interface gets updated(added or removed item from dataStructure) outside of QAbstractTableModel. How can I implement insterRows() for this case?
As far as I know in insertRows() implementation we should do something like this



bool MyModelClass::insertRows(int position, int rows, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), position, position+rows-1);

// change the data structure.

endInsertRows();

return true;
}


But my MyModelClass does not hold the data structure nor it modifies it , it happens outside MyModelClass. How should I handle this situation? And should we call insertRows() ? Yes, as usual I am new at this.

Thanks in advance.

PS: If unclear, please do tell :)

anda_skoa
2nd February 2016, 15:51
See comment #4

Cheers,
_

d_stranz
2nd February 2016, 16:25
MyModelClass does not hold the data structure nor it modifies it

Your model class must have at least a pointer or reference to the actual data structure, otherwise where would it get the information it needs to pass to views where it is displayed? It doesn't matter if MyModelClass modifies the data or not, it still needs read-only access to display it.

In cases like yours where the QAbstractItemModel is just a shallow wrapper around another data structure, you need to add something so you can notify MyModelClass that the underlying data structure has been changed. If you don't have the ability to change the data structure class, then you can add some interface "glue" where you first update the data structure, then you update the model:



// Pseudocode, obviously
void MyGlueClass::insertRow( int row, MyRowData * data )
{
myDataStructure->insertRow( row, data );
myModelClass->insertRow( QModelIndex(), row );
}

and all MyModelClass needs is this:



bool MyModelClass::insertRow( int row, const QModelIndex & parent )
{
beginInsertRows( parent, row, row );
endInsertRows();
}


If you have more ability to change the data structure class, then you could add methods that allow it to directly communicate with MyModelClass or with the class that owns both the data structure and the MyModelClass instance (like your QMainWindow class, for example - which in this case could act as the "glue").

In other words, MyModelClass doesn't have to do anything in insertRow() / insertRows() except notify its views that the model has changed. This is what the begin / end methods do - they tell views that the model content is about to change (so they can freeze updating themselves) and that the changes have been completed so they can refresh their appearance.

sami1592
2nd February 2016, 16:52
First of all, thanks! Did not expect such quick and detailed answer.

Yes, my MyModelClass do have a pointer/reference of the dataStructure(in my case a list of user defined object). So when my dataStructure changes outside of my MyModelClass I just have to call the insertRows() function from there? And inside the insertRows() function I just have to call beginInsertRows() and endInsertRows() function? From the documentation I got the impression that after calling beginInsertRows() and before calling endInsertRows() I have to make changed to my internal data structure; in my case the data structure is not internal, and the change/modification of data structure does not happen in MyModelClass. Ques: What is the difference between insertRow() and insertRows(), can I manually call both of them?

From the documentation:

Used to add new rows and items of data to all types of model. Implementations must call beginInsertRows() before inserting new rows into any underlying data structures, and call endInsertRows() immediately afterwards.
Link: http://doc.qt.io/qt-4.8/model-view-programming.html#read-only-access


I know it's too much to ask, but do you have a working copy for this kind of scenario? Or point to some blog that has? I searched around but no luck so far except here

anda_skoa
2nd February 2016, 17:23
Yes, my MyModelClass do have a pointer/reference of the dataStructure(in my case a list of user defined object). So when my dataStructure changes outside of my MyModelClass I just have to call the insertRows() function from there?

You need to call a function that takes the new Product object and adds it to the dataBase_ list.
What you call that function is irrelevant.



And inside the insertRows() function I just have to call beginInsertRows() and endInsertRows() function?

If you give us a number of how many time we need to repeat that then we can do that in a single posting and not waste part of every posting to repeat it.



From the documentation I got the impression that after calling beginInsertRows() and before calling endInsertRows() I have to make changed to my internal data structure

Exactly!



in my case the data structure is not internal, and the change/modification of data structure does not happen in MyModelClass.

DataModel::dataBase_ looks very internal to DataModel: it is private and neither a reference or pointer. Doesn't get more "internal" than that.




Ques: What is the difference between insertRow() and insertRows(), can I manually call both of them?

You can avoid that by using a different name for the function you are implementing, e.g. insertProduct()

Cheers,
_

sami1592
2nd February 2016, 17:55
I am not the original poster. My case is a bit different that the OP. I think you mistook me for the OP

anda_skoa
2nd February 2016, 19:07
I am not the original poster. My case is a bit different that the OP. I think you mistook me for the OP

Oh boy, so instead of posting your actual problem you have resurrected an unrelated thread and wasted everybodies times?
Way to go!

Fortunately most of the stuff applies in any case, you're just not updating the data since it has already changed.

Cheers,
_

d_stranz
2nd February 2016, 22:45
Link: http://doc.qt.io/qt-4.8/model-view-p...ad-only-access

I know it's too much to ask, but do you have a working copy for this kind of scenario? Or point to some blog that has? I searched around but no luck so far except here

The scenario is exactly what you (and the OP) have been told: if you modify the data served up by the model (whether it is internal or external), your implementation of the model has to make the appropriate calls so that the signals to the views get generated.

In your case, where all changes to the data are made externally to the model, then you are in effect doing a "switcheroo": the data is getting changed by whatever means it is getting changed, and so all you need to do in the model class is get the signals generated. You don't need to follow the beginInsertRows() -> make changes to the data -> endInsertRows() sequence. In your case, you are reversing the first two steps; you change the data first, then call begin / end back to back. In actual practice, you could make dozens of changes to your data, but update the model only when you want the new information displayed.

The begin -> modify -> end sequence applies mostly when the model contains the data; if you are making calls to setData() and other model methods, then these could cause unwanted updates to the views while the model is being changed. In this case, you would call the begin method first, make the changes, then call the end method. This ensures that the views don't respond to unwanted signals until after all the changes are complete.