QTableview: display data in specific column based on value in other column - c++

I have a sqlite table containing (among other things) a "position" and a "state" field.
I want to display this table in a QTableView with the position as the header column and the state in the right column like this :
id | 1 | 2 | 3
1 | A | |
2 | | | E
which represents the following database entries :
id | position | state
1 | 1 | A
2 | 3 | E
What would be the best way to do something like this?
Edit : not sure if this changes anything but I need the QTableView to be editable too (by overriding setData() method of QSqlQueryModel)

The simplest option for this case is to create a class that inherits from QSqlTableModel and modify the necessary functions as shown below:
sqltablemodel.h
#ifndef SQLTABLEMODEL_H
#define SQLTABLEMODEL_H
#include <QSqlTableModel>
class SqlTableModel : public QSqlTableModel
{
const QString stateName = "state";
const QString positionName = "position";
public:
int columnCount(const QModelIndex &parent = QModelIndex()) const;
void setTable(const QString &tableName);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
private:
int max_position;
int index_position;
int index_state;
void reset();
};
#endif // SQLTABLEMODEL_H
sqltablemodel.cpp
#include "sqltablemodel.h"
#include <QBrush>
#include <QSqlQuery>
#include <QSqlTableModel>
#include <QSqlRecord>
#include <QTimer>
int SqlTableModel::columnCount(const QModelIndex &parent) const
{
return QSqlTableModel::columnCount(parent)+ max_position;;
}
void SqlTableModel::setTable(const QString &tableName)
{
QSqlTableModel::setTable(tableName);
index_position = fieldIndex(positionName);
index_state = fieldIndex(stateName);
reset();
}
QVariant SqlTableModel::data(const QModelIndex &index, int role) const
{
if(role == Qt::ForegroundRole){
return QBrush(Qt::black);
}
const int number_of_columns = QSqlTableModel::columnCount();
if(index.column()>= number_of_columns){
if(role==Qt::DisplayRole){
int position = QSqlTableModel::data(this->index(index.row(), index_position), Qt::DisplayRole).toInt();
if(index.column() == number_of_columns + position - 1){
return QSqlTableModel::data(this->index(index.row(), index_state), Qt::DisplayRole).toString();
}
}
}
return QSqlTableModel::data(index, role);
}
QVariant SqlTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= QSqlTableModel::columnCount())
return section - QSqlTableModel::columnCount() + 1;
return QSqlTableModel::headerData(section, orientation, role);
}
Qt::ItemFlags SqlTableModel::flags(const QModelIndex &index) const
{
if(index.column() >= QSqlTableModel::columnCount()){
return Qt::ItemIsSelectable| Qt::ItemIsEditable| Qt::ItemIsEnabled;
}
return QSqlTableModel::flags(index);
}
bool SqlTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(role==Qt::EditRole){
const int number_of_columns = QSqlTableModel::columnCount();
if(index.column() >= number_of_columns){
bool result1 = QSqlTableModel::setData(this->index(index.row(), index_position), index.column()-number_of_columns +1, role);
bool result2 = QSqlTableModel::setData(this->index(index.row(), index_state), value, role);
return result1 && result2;
}
if(index.column() == index_position){
QTimer::singleShot(0, this, &SqlTableModel::reset);
}
}
return QSqlTableModel::setData(index, value, role);
}
void SqlTableModel::reset()
{
QSqlQuery q;
q.exec(QString("SELECT MAX(%1) FROM %2").arg(positionName).arg(tableName()));
int val;
while (q.next()) {
val = q.value(0).toInt();
}
if(val != max_position){
beginResetModel();
max_position = val;
endResetModel();
}
}
Input:
id |position |state
1 |1 |A
2 |2 |S
3 |1 |C
4 |4 |B
5 |3 |V
Output:
The complete example can be found in the following link

Related

QAbstractTableModel editing without clearing previous data in cell

I have created a model based off of QAbstractTableModel that allows the user to edit data in that model. The model is displayed in a QTableView in a QMainWindow. So far in my model I am able to make the cells editable, and save whatever the user types in after editing is finished.
The issue is that when the user begins editing, it 'clears' the previous contents of that cell. So if for example I only wanted to change the spelling of a string in a cell, I have to re-type the entire value. I would like when editing that the editor would start with the data that is already in the model, rather than empty.
How can I do that?
Example of the issue:
Before I begin editing a cell:
As soon as I begin editing, the cell is empty. I would like it to star with the previous value already in the model:
Here is a minimal example of my model. My actual model is much larger and uses a struct instead of just a 2D array of QVariants to store the data.
Header:
const int COLS= 2;
const int ROWS= 6;
class EditableTableModel : public QAbstractTableModel
{
Q_OBJECT
private:
QVariant tableData[ROWS][COLS];
public:
EditableTableModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
signals:
void editCompleted(QString);
};
Implementation:
EditableTableModel::EditableTableModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
int EditableTableModel::rowCount(const QModelIndex & /*parent*/) const
{
return ROWS;
}
int EditableTableModel::columnCount(const QModelIndex & /*parent*/) const
{
return COLS;
}
QVariant EditableTableModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();
switch (role) {
case Qt::DisplayRole:
return tableData[row][col];
}
return QVariant();
}
bool EditableTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::EditRole) {
if (!checkIndex(index))
return false;
tableData[index.row()][index.column()] = value;
return true;
}
return false;
}
QVariant EditableTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (section) {
case 0:
return QString("First Name");
case 1:
return QString("Last Name");
}
}
return QVariant();
}
Qt::ItemFlags EditableTableModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}
Data should be returned for Qt::EditRole in the data() method. The following should work:
QVariant EditableTableModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole: // <-- add this line
return tableData[row][col];
}
return QVariant();
}
Note that the above switch-case uses something known as fallthrough, so that the switch-case will match for both Qt::DisplayRole and Qt::EditRole.

Delete rows from QTreeView with custom model

I'm new to Qt. I'm trying to create custom model for tree view with support of rows deletion. I've implemented it according to examples http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html and http://doc.qt.io/qt-5/qtwidgets-itemviews-editabletreemodel-example.html. Also, I've made context menu with option to remove row after pressing with right mouse button on the row.
Now, I have hardly reproducible error (there is no exact pattern, but it is easy to obtain). When I start to delete rows from model randomly, sometimes my program crashes, sometimes I receive following messages to output:
QAbstractItemModel::endRemoveRows: Invalid index ( 1 , 0 ) in model QAbstractItemModel(0x55555580db10)
When program crashes, I almost always in fuction
QModelIndex TreeModel::parent(const QModelIndex &child) const
which is inherited from
QModelIndex QAbstractItemModel::parent(const QModelIndex &child) const
Stack of function calls shows that this function is called from
void QAbstractItemModel::beginRemoveRows(const QModelIndex &parent, int first, int last)
which I call in overrided
bool TreeModel::removeRows(int row, int count, const QModelIndex &parent)
When I compared adresses of child.indernalPointer() (where I store pointer to internal tree Nodes, representing my model) with already deleted Nodes, It became clear, that by some reason beginRemoveRows() using already invalid indices.
There is question with very similar error: QModelIndex becomes invalid when removing rows, howerer I can't understand why and where I use invalid indices.
So, I place the minimal example with this behavior (I've put a lot of effort to minimize it to this size and make the code clear, sorry for it is nevertheless long).
tree.pro
QT += core gui widgets
TARGET = tree
TEMPLATE = app
SOURCES += main.cpp widget.cpp treemodel.cpp
HEADERS += widget.h treemodel.h
treemodel.h
#ifndef TREEMODEL_H
#define TREEMODEL_H
#include <QAbstractItemModel>
class TreeModel : public QAbstractItemModel
{
public:
TreeModel();
~TreeModel();
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool removeRows(int row, int count, const QModelIndex &parent) override;
private:
class Impl;
Impl* impl = nullptr;
};
#endif // TREEMODEL_H
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
private slots:
void projectTreeMenuRequested(const QPoint& point);
void eraseItem();
private:
class Impl;
Impl* impl;
};
#endif // WIDGET_H
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
treemodel.cpp
#include "treemodel.h"
#include <cassert>
#include <string>
#include <list>
#include <memory>
namespace {
struct Node {
Node(const std::string& name)
: text(name)
{
}
~Node() {
}
Node& append(const std::string& name) {
child.emplace_back(name);
Node& n = child.back();
n.parent = this;
return n;
}
size_t getChildNum() const {
return child.size();
}
bool hasParent() const {
return parent != nullptr;
}
Node& getParent() {
assert(hasParent());
return *parent;
}
size_t getIndexInParent() const {
if (parent) {
size_t index = 0;
Childs::iterator it = parent->child.begin();
while (it != parent->child.end()) {
if (&*it == this) {
return index;
}
++it;
++index;
}
}
return 0;
}
Node& getChild(size_t i) {
assert(i < child.size());
Childs::iterator it = child.begin();
std::advance(it, i);
return *it;
}
void setText(std::string name) {
this->text = std::move(name);
}
std::string getText() const {
return text;
}
void remove() {
assert(hasParent());
Node& p = getParent();
for (Childs::iterator it = p.child.begin(); it != p.child.end(); ++it) {
if (&*it == this) {
p.child.erase(it);
return;
}
}
assert(0); // Child for remove not found
}
bool removeChilds(size_t start, size_t end) {
if (start < end && end <= child.size()) {
Childs::iterator it1 = child.begin();
assert(it1 != child.end());
std::advance(it1, start);
assert(it1 != child.end());
Childs::iterator it2 = it1;
std::advance(it2, end - start);
child.erase(it1, it2);
return true;
} else {
return false;
}
}
static const int Columns = 1;
private:
using Childs = std::list<Node>;
std::string text;
Node* parent = nullptr;
Childs child;
};
} // namespace
struct TreeModel::Impl {
Impl()
: root("Root")
{
fill(root);
}
void fill(Node& from, std::string str = "", int depth = 0) {
if (depth == 10) return;
for (int j = 0; j != 5; ++j) {
std::string name = str + std::to_string(j);
fill(from.append(name), name, depth+1);
}
}
Node root;
};
TreeModel::TreeModel()
: impl(new Impl)
{
}
TreeModel::~TreeModel()
{
delete impl;
}
QModelIndex
TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent)) {
return QModelIndex();
} else {
Node* node = nullptr;
if (!parent.isValid()) {
node = &impl->root;
} else {
node = static_cast<Node*>(parent.internalPointer());
}
return createIndex(row, column, &node->getChild(row));
}
}
QModelIndex TreeModel::parent(const QModelIndex &child) const
{
if (!child.isValid()) {
return QModelIndex();
}
Node* node = static_cast<Node*>(child.internalPointer());
if (!node->hasParent()) {
return QModelIndex();
}
return createIndex(node->getIndexInParent(),
child.column(),
&node->getParent());
}
int TreeModel::rowCount(const QModelIndex &parent) const
{
Node* p = nullptr;
if (parent.isValid()) {
p = static_cast<Node*>(parent.internalPointer());
} else {
p = &impl->root;
}
return p->getChildNum();
}
int TreeModel::columnCount(const QModelIndex &) const
{
return Node::Columns;
}
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (index.isValid()) {
Node* node = static_cast<Node*>(index.internalPointer());
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
return QString::fromUtf8(node->getText().data(),
node->getText().size());
break;
}
}
return QVariant();
}
bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role != Qt::EditRole)
return false;
Node* node = nullptr;
if (index.isValid()) {
node = static_cast<Node*>(index.internalPointer());
} else {
node = &impl->root;
}
node->setText(value.toString().toStdString());
emit dataChanged(index, index);
return true;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}
bool TreeModel::removeRows(int row, int count, const QModelIndex &parent)
{
Node* node = nullptr;
QModelIndex correctParent;
if (parent.isValid()) {
node = static_cast<Node*>(parent.internalPointer());
correctParent = parent;
} else {
node = &impl->root;
correctParent = QModelIndex();
}
beginRemoveRows(correctParent, row, row + count - 1); // [row, row + count - 1]
bool success = node->removeChilds(row, row + count); // [row, row + count)
endRemoveRows();
return success;
}
widget.cpp
#include "widget.h"
#include <QVBoxLayout>
#include <QTreeView>
#include <QPoint>
#include <QMenu>
#include "treemodel.h"
struct Widget::Impl {
QVBoxLayout* layout;
QTreeView* treeView;
TreeModel* treeModel;
};
Widget::Widget(QWidget *parent)
: QWidget(parent)
, impl(new Impl)
{
impl->layout = new QVBoxLayout(this);
impl->treeView = new QTreeView;
impl->treeModel = new TreeModel;
impl->layout->addWidget(impl->treeView);
impl->treeView->setModel(impl->treeModel);
impl->treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
impl->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(impl->treeView, SIGNAL(customContextMenuRequested(const QPoint&)),
this, SLOT(projectTreeMenuRequested(const QPoint&)));
}
Widget::~Widget()
{
delete impl->treeModel;
delete impl;
}
void Widget::projectTreeMenuRequested(const QPoint &point)
{
QPoint globalPos = impl->treeView->mapToGlobal(point);
QMenu myMenu;
myMenu.addAction("Erase", this, SLOT(eraseItem()));
myMenu.exec(globalPos);
}
void Widget::eraseItem()
{
for (QModelIndex index : impl->treeView->selectionModel()->selectedIndexes()) {
impl->treeModel->removeRow(index.row(), index.parent());
}
}
EDIT
I think about two ways to solve the problem. The first is direct approach when somebody point me to the incorrect use of Qt API. The second approach is if somebody write independent implementation of this functionality (tree with infinity nesting and ability to remove) and I will try to figure out what I am doing wrong compared to another implementation.
EDIT 2
After thorough analysis of QStandardItemModel I come to conclusion, that it is important to store in internalPointer of indices parent of actual Node, but in my example I use internalPointer to store Node itself. So it seems correct for Qt internal implementation to call parent() on indices of already deleted elements, assuming that the information in indernalPointer is not related to element and remains correct. (Please, correct me if I am wrong.)
Confirmed, that after rewriting implementation to store pointers to parent node in internal nodes, this bug was eliminated. Correction of other bugs is provided in accepted answer.
You are using for(index: selectedIndexes()) in Widget::eraseItem(), but after removing something, indexes changes, so your index in for becomes invalid. Also, it is a bad practice, to change container while you iterating through it.
In removeChilds() try changing the conditional from end <= child.size() to end < child.size()

QIdentityProxyModel display empty rows

I've implemented QIdentityProxyModel like this:
class ProxyModel : public QIdentityProxyModel
{
Q_OBJECT
public:
ProxyModel(QObject *parent)
{
entriesPerPage = 3;
page = 1;
}
inline int getEntriesPerPage() const
{ return entriesPerPage; }
inline void setEntriesPerPage(int value)
{ entriesPerPage = value; }
inline qint64 getPage() const
{ return page; }
inline void setPage(const qint64 &value)
{ page = value; }
inline int rowCount(const QModelIndex &parent = QModelIndex()) const override{
Q_UNUSED(parent)
if(!sourceModel())
return 0;
return entriesPerPage * page <= sourceModel()->rowCount()
? entriesPerPage
: sourceModel()->rowCount() - entriesPerPage * (page - 1);
}
inline int columnCount(const QModelIndex &parent = QModelIndex()) const override{
Q_UNUSED(parent);
return 6;
}
QModelIndex ProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
if(!sourceModel() && !proxyIndex.isValid())
return QModelIndex();
return sourceIndex.isValid()
? createIndex(sourceIndex.row() % entriesPerPage, sourceIndex.column(), sourceIndex.internalPointer())
: QModelIndex();
}
QModelIndex ProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
if(!sourceModel() && !proxyIndex.isValid())
return QModelIndex();
QModelIndex remapped = createIndex(proxyIndex.row() ,
proxyIndex.column(),
proxyIndex.internalPointer());
return QIdentityProxyModel::mapToSource(remapped);
}
private:
int entriesPerPage;
qint64 page;
};
When I insert a row in the sourceModel() with index more then entriesPerPage, view displays empty rows, so that row number is more then entriesPerPage, although rowCount() return number equal to entriesPerPage.
How can I get rid of empty rows?
First. It is a bad practice to override rowCount/mapFromSource for QIdentityProxyModel. I propose you to use QAbstractProxyModel to have more clear code.
Main. Your problem is in getEntriesPerPage/setPage methods. You need to call beginResetModel/endResetModel after updating such data.
inline void setPage(const qint64 &value)
{
beginResetModel();
page = value;
endResetModel();
}
Offtopic: it is pretty cool, that you have to code with Qt in BSUIR. Who is your teacher?

QTreeView QSortFilterProxyModel trigger filter

Here is my program:
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QTreeView>
class MySortFilterProxyModel : public QSortFilterProxyModel
{
public:
MySortFilterProxyModel();
void updateFilter(int filterType);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
bool lessThan(const QModelIndex &left,const QModelIndex &right) const;
private:
int _filterType;
};
MySortFilterProxyModel::MySortFilterProxyModel()
: _filterType(0)
{
}
bool MySortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QStandardItemModel* source = static_cast<QStandardItemModel*>(sourceModel());
QModelIndex modelIndex = source->index(sourceRow, 0, sourceParent);
QStandardItem* item = source->itemFromIndex(modelIndex);
QVariant v = item->data(Qt::UserRole);
int itemType = v.toInt();
if(itemType == _filterType)
return true;
return false;
}
bool MySortFilterProxyModel::lessThan(const QModelIndex &left,const QModelIndex &right) const
{
QVariant leftData = sourceModel()->data(left);
QVariant rightData = sourceModel()->data(right);
if(leftData.type() == QVariant::String && rightData.type() == QVariant::String)
{
QString leftString = leftData.toString();
QString rightString = rightData.toString();
return QString::localeAwareCompare(leftString, rightString) < 0;
}
return false;
}
void MySortFilterProxyModel::updateFilter(int filterType)
{
_filterType = filterType;
// how can i trigger filteracceptRows here ??
}
int main(int argc, char** argv)
{
QApplication qtApp(argc, argv);
MySortFilterProxyModel mySortFilterProxyModel;
QStandardItemModel standardModel;
QTreeView treeView;
mySortFilterProxyModel.setSourceModel(&standardModel);
treeView.setModel(&standardModel);
treeView.setSortingEnabled(true);
treeView.show();
return qtApp.exec();
}
Everytime i AppendRow to standardModel sort and filter works.
How can i trigger filtering without appending or removing something to standardModel?
I want to filter rows on QTreeView through right click but i couldn't find a way to triggger filterAcceptRows on my void MySortFilterProxyModel::updateFilter(int filterType) function.
Having multiple instances of MySortFilterProxyModel class for every possible filterType value and switching them according to filterType may work but is there a better solution?
calling invalidate() on updateFilter worked for me.
void MySortFilterProxyModel::updateFilter(int filterType)
{
_filterType = filterType;
invalidate();
}

Why doesn't my model show up in QListView?

I am trying to get a simple QAbstractItemModel to show up in a QListView but am struggling a bit. Here is my model implementation:
TestModel::TestModel(QObject *parent) : QAbstractItemModel(parent)
{
}
QVariant TestModel::data(const QModelIndex &index, int role) const
{
if(role == Qt::DisplayRole)
{
return QVariant("FRED");
}
return QVariant();
}
Qt::ItemFlags TestModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
}
QVariant TestModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(role == Qt::DisplayRole)
{
return QVariant("BING");
}
return QVariant();
}
QModelIndex TestModel::index(int row, int column, const QModelIndex &parent) const
{
return createIndex(row, column);
}
QModelIndex TestModel::parent(const QModelIndex &index) const
{
return createIndex(0, 0);
}
int TestModel::rowCount(const QModelIndex &parent) const
{
return 1;
}
int TestModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
If I set the model of my QListView to an instance of the above model then nothing shows up. However, if I use a QTableView instead then it is populated as expected.
What do I need to do to get this simple example to work with a QListView?
Thanks,
Alan
List model has no parent hierarchy.
Hence, parent method shall be implemented as,
QModelIndex TestModel::parent(const QModelIndex &index) const
{
return QModelIndex();
}