combine QAbstractItemModels - qt

I have a map = std::map<std::string, myItemModel *>, where myItemModel inherits QAbstractItemModel.
I want now to combine all myItemModel in one single myItemModel (every other item model would be fine too).
So that there is one big myItemModel.
Is there a 'qt-way' to do this?

It can be done, but it's not trivial. It depends on your implementation of QAbstractItemModel and that's why it hasn't been done in Qt.
Here are steps to implement a model which is a collection of models:
Create a new class inherited from QAbstractItemModel
Add methods to add other models to that
Process all signals from child models which contains indexes (you'll need to change them, look #10)
Forward all signals which doesn't contain indexes.
Implement rowCount and provide a sum of all models rows.
Implement columnCount and provide a number of columns in your models.
Implement index, return createIndex(row, column, NULL);
Implement parent, return QModelIndex(); I hope your models are not trees
Implement data,setData etc. addressing calls to the right model. Use methods from #10 to convert indexes.
Create methods to convert a child model index to a base model index and back.
Example (indexes):
BaseModel ChildModel1 ChildModel2
0,0 0,0
1,0 1,0
2,0 0,0
3,0 1,0
4,0 2,0
p.s. Think about creating a cache of indexes mapping.
This is an example of a method to convert a base model index to a child model index:
const QModelIndex childModelIndex(const QModelIndex& baseModelIndex) const
{
if (!baseModelIndex.isValid())
{
return QModelIndex();
}
int count = 0;
const int row = baseModelIndex.row();
for (QList<QAbstractTableModel*>::const_iterator it = m_models.begin();
it != m_models.end(); it++)
{
const int currentCount = (*it)->rowCount();
if (row >= count && row < count + currentCount)
{
return (*it)->index(row - count, 0);
}
count += currentCount;
}
ASSERT(false);
return QModelIndex();
}
This is an example of a method to convert a child model index to a base model index:
QModelIndex baseModelIndex(const QModelIndex& childModelIndex) const
{
int row = childModelIndex.row();
for (QList<QAbstractTableModel*>::const_iterator it = m_models.begin();
it != m_models.end(); it++)
{
if (childModelIndex.model() == *it)
{
return index(row, ind.column());
}
row += (*it)->rowCount();
}
return QModelIndex();
}

The KDE Frameworks project contains a module called KItemModels, which includes a class called KConcatenateRowsProxyModel. It does exactly what you want. The library is released every month as part of the [KDE Frameworks releases], the code is continuously unit tested on https://build.kde.org. All this is licensed under LGPL v2 or later.

Related

QT QtableWidgetItem display formated data and sort by original data

In a column in a QTableWidget I want to display some double values. By doing the following, I get my desired display:
double value = 1234.567;
QTableWidgetItem* qti = new QTableWidgetItem(QString::number(value , 'f', 4));
Now, if I enable sorting on the table and sort the values in this column, it will sort as strings. So 90.0000 will come be "larger" than 800.0000 for example (9 > 8).
If I do this instead:
QTableWidgetItem* qti = new QTableWidgetItem();
qti->setData(Qt::DisplayRole, value);
or
QTableWidgetItem* qti = new QTableWidgetItem(QString::number(value , 'f', 4));
qti->setData(Qt::DisplayRole, value);
I can sort my column correctly, but I "lose" the formmating (12.0000 is displayed as 12).
I've also tried like this:
QTableWidgetItem* qti = new QTableWidgetItem();
qti->setData(Qt::UserRole, value);
qti->setData(Qt::DisplayRole, QString::number(value, 'f', 4));
How can I format the display of the values, while still enabling sorting?
(In all code snippets above, the QTableWidgetItems' are added by:
table->setItem(rowNumber, colNumber, qti);
where table is a QTableWidget)
You do it wrong. Let's do it right. The solution is to subclass QTableWidgetItem.
class CustomTableWidgetItem : public QTableWidgetItem
{
public:
CustomTableWidgetItem(const QString txt = QString("0"))
:QTableWidgetItem(txt)
{
}
bool operator <(const QTableWidgetItem& other) const
{
qDebug() << "Sorting numbers";
return text().toFloat() < other.text().toFloat();
// text() is part of QTableWidgetItem, so you can write it as QTableWidgetItem::text().toFloat() as well
}
};
Example insertions
ui->tableWidget->setSortingEnabled(false);
ui->tableWidget->insertRow(0);
ui->tableWidget->setItem(0, 0, new CustomTableWidgetItem(QString::number(90.0005, 'f', 4)));
ui->tableWidget->insertRow(1);
ui->tableWidget->setItem(1, 0, new CustomTableWidgetItem(QString::number(800.0003, 'f', 4)));
ui->tableWidget->insertRow(2);
ui->tableWidget->setItem(2, 0, new CustomTableWidgetItem(QString::number(200.0010, 'f', 4)));
ui->tableWidget->insertRow(3);
ui->tableWidget->setItem(3, 0, new CustomTableWidgetItem(QString::number(200.0020, 'f', 4)));
ui->tableWidget->setSortingEnabled(true);
Sorting behaviour is as expected
ascending
90.0005
200.0010
200.0020
800.0003
descending
800.0003
200.0020
200.0010
90.0005
PS: Remember to turn off sorting before you insert bew items.
Another way of doing this is by using QStyledItemDelegate attached to the double column(s). It need not do much except implement the QStyledItemDelegate::displayText() function.
For further details, see QTableView and display of doubles at Qt Centre.
Here's what I came up with, based on Mangesh's answer, for a similar problem displaying a percentage:
class percentageItemC : public QStyledItemDelegate
{
QString displayText(const QVariant &value, const QLocale &locale) const
{
return QString::asprintf("%0.3lf%% ", value.toDouble());
}
} percentageItem;
That's any number of digits to the left of the decimal, 3 to the right, and a long float with a percent sign. Then, do a
ui->twMyTable->setItemDelegateForColumn(1, &percentageItem);
in yer form's class. I can also re-use them for other columns, and presumably rows.
This method doesn't slow things down with extra text to float conversions. You can also have nice extras, like commas, percent signs and currency signs in it. I have this for currency:
class currencyItemC : public QStyledItemDelegate
{
QString displayText(const QVariant &value, const QLocale &locale) const
{
return locale.toCurrencyString(value.toDouble()) + " ";
}
} currencyItem;
I use:
QTableWidgetItem *item = new QTableWidgetItem();
item->setData(Qt::EditRole, number);
to create the table items. It is smart about these: it creates a QVariant, and then sorts based on the type. This way, you don't have to set the underlying data to a string, but it can be any type it has a sort for.
The columns sort numerically like they're s'posed to, but they display in any format ya like. ;}
From my point of view the best option to solve this problem is to create new class with usage of setData() with Qt::UserRole to store unformatted data. In this setup you can store real data and text together. This has more memory consumption but you doesn't need to convert from string to int/float/etc.
I've tested many other roles Qt::EditRole, Qt::DisplayRole, etc and they are overwriting stored data/text and you can't store both values.
Here is my final solution of class displaying % percent values (python code)
from PyQt5 import QtWidgets, QtCore
class PercentTableWidgetItem(QtWidgets.QTableWidgetItem):
# Float value
value: float = 0
def __init__(self,
value: float = 0
):
''' Constructor.'''
super().__init__()
# Set variables
self.value = value
# Set item data
self.setData(QtCore.Qt.UserRole, value)
self.setText(f'{self.value:.2f}%')
def __lt__(self, other: QtWidgets.QTableWidgetItem):
''' Operation < for sorting.'''
value = other.data(QtCore.Qt.UserRole)
return (value is not None) and (self.value < value)
Usage in code as simple as below
table = QTableWidget()
table.setItem(0,0,PercentTableWidgetItem(35.67))

QSqlTableModel, QTableView change columns not updated

I have a SQL model that connects to a single table, this table will change the number of columns depending on certain conditions during the execution of the program. The model is connected to a QTableView.
I have a function that controls the number of columns at the end of the function i have a call to model->select(), to update the information of the model and tableView->reset(), to what i thought would rearrange the view adding or taking away columns.
The problem is that the view does not change from the original number of columns that it had. If i reduce the number i can see that the data change and show empty on the missing columns. Is there a command for the tableView to resize it self?
Editing the question
in the constructor of the class i'm reading the table and setting it to the view:
header = new QSqlTableModel(parent,data->m_db);
header->setTable("C"+QString::number(markTime.toSecsSinceEpoch())+"T");
header->select();
ui->heading->setModel(header);
ui->heading->show();
Every time that that the number of columns is changed is an SQL procedure to change the number of columns:
void ImportProcess::copyTable(QString oldTable, QString newTable)
{
QSqlQuery queryOld, queryNew;
queryOld.prepare("select * from :oldTable");
queryOld.bindValue(":oldTable",oldTable);
queryOld.exec();
if(queryOld.record().isEmpty()==true) return; //Old table was empty, nothing to copy
int oldColumn=queryOld.record().count();
QString replaceLine="insert into "+newTable+" values(";
while(queryOld.next()==true)
{
replaceLine.append(QString::number(queryOld.value(0).toInt()));
replaceLine.append(", "+queryOld.value(1).toString());
for(int y=0;y<(oldColumn < ui->columns->value() ? oldColumn : ui->columns->value());y++)
{
replaceLine.append(", "+QString::number(queryOld.value(y+2).toFloat()));
}
replaceLine.append(")");
queryNew.exec(replaceLine);
}
}
Then the header file is updated, and here is where I thought that the tableview will be redrawn:
void ImportProcess::updateHeadingTable()
{
QSqlQuery query;
query.exec("delete from C"+QString::number(markTime.toSecsSinceEpoch())+"T");
QString description= ui->Week->isChecked() == true ? "Week" : "Size";
query.exec("insert into C"+QString::number(markTime.toSecsSinceEpoch())+"T (id, description) values (101, '"+description+"')");
for(int x=0;x<ui->columns->value();x++)
{
query.exec("update C"+QString::number(markTime.toSecsSinceEpoch())+"T set col"+QString::number(x)+" = '30'");
}
header->select();
ui->heading->reset();
}
I believe you forgot about some protected methods:
void QAbstractItemModel::beginInsertColumns(const QModelIndex &parent, int first, int last);
void QAbstractItemModel::beginRemoveColumns(const QModelIndex &parent, int first, int last);
void QAbstractItemModel::endInsertColumns();
void QAbstractItemModel::endRemoveColumns();
Every time the number of column about to be change you should call first or second method. After change you should call third or fours method.
You can read about these methods in Qt documentation.

How to construct a QModelIndex with valid parent?

I'm trying to unittest my implementation of QAbstractTableModel. I have implemented rowCount(), columnCount() and data() methods.
After instantiating my model, no matter how many nestings deep, the parent index is always invalid:
parent = model->index(0, 0);
i = model->index(0, 0, parent); // i.parent().IsValid() == false!
Now, i is valid. But i.parent() is not.
Even if I do further nesting:
ancestor = model->index(0, 0);
parent = model->index(0, 0, ancestor);
i = model->index(0, 0, parent); // i.parent().IsValid() == false!
even then, i is valid but i.parent() is not.
I have unit tested the rowCount and columnCount methods and I've asserted that the model is a tree model that has one row with, nested, two rows. Also, the column count is nonzero.
Why is my parent index always invalid?
It's a table. It's not supposed to be a tree. Because of that, the parent will always be invalid. The QAbstractTableModel::index implementation always sets an invalid parent, and it's supposed to.
Your expectations apply to a tree model, not a table model. And they only apply to a tree model if the given parent element has children. Your test wrongly assumes that the parent it is using has children, when it has none. You can easily check that: model->hasChildren(parent) will always return false for a table. Trying to create an index with a childless parent is undefined. Ideally your model should assert on it. Thus, your test would be generally wrong for a tree too.
If you want to implement a tree, derive from QAbstractItemModel. You'll then be forced to correctly implement bool hasChildren(const QModelIndex& parent) const - that's the method that the tree view (and your tests!) should use to know whether it's valid to request an index for a given parent's child.
Generally speaking, if model.hasChildren(parent) == false then you're never supposed to call model.index(row, col, parent). In fact, your model should assert that it is so:
QModelIndex MyModel::index(int row, int col, const QModelIndex & parent) {
Q_ASSERT(hasChildren(parent));
Q_ASSERT(row >= 0 && row < rowCount(parent));
Q_ASSERT(col >= 0 && col < columnCount(parent));
void * ptr = ...; // or quintptr ptr = ...;
return createIndex(row, col, ptr);
}

Should QAbstractItemModel::index(row, column, parent) check for invalid inputs?

Subclassing QAbstractItemModel, I have implemented my own index() method as required. I currently check for valid inputs, but I'm wondering if that's correct. I'm wondering if it's ever valid to create an index for a non-existent piece of data? Perhaps while inserting a row or column?
Code:
QModelIndex LicenseDataModel::index(int row, int column, const QModelIndex & /*parent*/) const
{
/// TODO: Is this necessary? Should we avoid creating invalid indexes? Or could this
/// be a bug?
if (validRowColumn(row, column))
return createIndex(row, column);
return QModelIndex();
}
[ I have a better answer. :-) ]
Although I merely repeat what Giuseppe D'Angelo from KDAB has to say about this...
You must distinguish between an invalid and an ill-formed QModelIndex:
About invalid indices (from Navigation and model index creation in Qt's Model/View Programming guide):
QAbstractItemModel::parent(): Provides a model index corresponding to the parent of any given child item. If the model index specified corresponds to a top-level item in the model, or if there is no valid parent item in the model, the function must return an invalid model index, created with the empty QModelIndex() constructor.
This explains the meaning of index.isValid():
A valid index refers to an existing item, the invalid index refers to the root of all items.
Giuseppe D'Angelo first notes that an invalid index (with .isValid() returning false) is still a valid input to functions like rowCount(), hasChildren() etc.
But a QModelIndex can also be ill-formed. It can have a non-existing row or column index, and it can even be from a different model. And QModelIndex::isValid() does not check that.
Giuseppe D'Angelo says:
I personally maintain quite a strong point of view about this issue: passing such indices is a violation of the API contract. A model should never be assumed to be able to handle illegal indices. In other words, in my (not so humble) opinion, the QAbstractItemModel API has a narrow contract.
But since All Programmers Make Mistakes (TM), it facilitates the debugging process to check indices for being well-formed. For this purpose, Giuseppe D'Angelo introduced QAbstractItemModel::checkIndex() into Qt 5.11.
If you're still using a lower Qt version, you can simply write that function yourself.
[ If anyone has a better answer, I'll gladly accept it. ]
Looking at the source for QListWidget, it seems that checking the inputs is what Qt does itself:
QModelIndex QListModel::index(int row, int column, const QModelIndex &parent) const
{
if (hasIndex(row, column, parent))
return createIndex(row, column, items.at(row));
return QModelIndex();
}
It also appears that I didn't know about hasIndex() which will do what my validRowColumn() method does.
bool QAbstractItemModel::hasIndex(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column < 0)
return false;
return row < rowCount(parent) && column < columnCount(parent);
}
For that matter, I'm not sure why the documentation uses index.isValid() everywhere when hasIndex(index.row(), index.column(), index.parent()) would be more appropriate. Then, I'm sure a hasIndex(QModelIndex &) method would be added. hasIndex() does the same check as QModelIndex::isValid() and more:
inline bool isValid() const { return (r >= 0) && (c >= 0) && (m != 0); }

Create class by inheriting from QTreeView

I am starting a project using Qt. I am trying two approaches to get a View to do some of the following. This question involves the approach of inheriting from QTreeView.
I like what QTreeView does. I just want some added features.
First, what I want is to make a hierarchy tree view that will allow me to see categories containing other categories, the further right the columns go the more specific they are until it gets to the most specific. The metrics are shown on the row containing the most specific column. The view rows containing each generalized column will be bold and contain a summary of each metric, calculated by the model (or view?). The metrics will be in the model on each row in terms of the most specific column.
For example, consider a model with the following data (the last 3 columns containing numbers):
Country|Province-State|County-Parish|City-Town|Population|PerCapitaIncome|WalMarts
So my view would look similar to this:
Country Province-State County-Parish City-Town Population PerCapitaIncome Walmarts
+ USA 250000000 42000 2354
+ Alabama 9000000 23000 153
+ Barbour 15324 19032 1
Eufaula 6520 23000 1
+ Tennessee 14000000 29000 299
+ Hamilton 70000 41000 4
East Ridge 23000 32000 2
Second, I need it to work with QSqlTableModel. I have seen it show the model before, but it doesn't have any way to create the rows by a heirarchy, similar to above. That was going to be my second modification.
The third reason is bold headers are only an option if you have the sort turned on via:
view->setSortingEnabled(true);
When sort is on, the bold headers only works for the higher-up rows and then turns off on lower ones. I want to fix that bug.
The QTreeView::drawRow virtual method looks to be all I need to override to accomplish the first challenge (and perhaps the third). The second challenge dealing with QSqlTableModel, I'm not so sure about.
Anyhow, I built a simple class inheriting from QTreeView with a generic ctor and dtor that just calls the QTreeView methods. As for drawRow, however, I ran into the following problem. The QTreeView::drawRow function starts out like this:
QTreeView::drawRow(
QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const {
Q_D(const QTreeView);
QStyleOptionViewItemV4 opt = option;
const QPoint offset = d->scrollDelayOffset;
const int y = option.rect.y() + offset.y();
const QModelIndex parent = index.parent();
const QHeaderView *header = d->header;
const QModelIndex current = currentIndex();
const QModelIndex hover = d->hover;
const bool reverse = isRightToLeft();
const QStyle::State state = opt.state;
const bool spanning = d->spanning;
const int left = (spanning ? header->visualIndex(0) : d->leftAndRight.first);
const int right = (spanning ? header->visualIndex(0) : d->leftAndRight.second);
...
The function relies on have Q_D(const QTreeView) run succesfully and return "d", an instance of the QTreeViewPrivate class which contains important info related to layout and the remainder of the function. Since I am inheriting into my CustomTreeView class, CustomTreeViewPrivate will have to be defined and instantiated first if I am to run Q_D(const QTreeView) in CustomTreeView::drawRow().
Is creating this private class really necessary to inherit and make significant changes? What is the benefit of inheriting here if all I can do is some perfunctory processes and then call the QTreeView::drawRow to do the actual drawing?
I want to change how its drawing.
I will try to cover as much of your question as possible. The big thing throughout all of your problems is that much of what you are trying to accomplish should be done through the model, not the view (such as having certain entries in bold). Because of this, you will have to make your own model. You can inherit a QSqlTableModel and alter things as you wish. For example, if you want to bold certain items, in the data model, you could write
QVariant MyModel::data(const QModelIndex & index, int role) const
{
QVariant result = QSqlTableModel::data(index, role);
// add your own qualifications to the following if statement, checking the row
// and such
if(role == Qt::FontRole) {
QFont font = result.value<QFont>();
font.setBold(true);
return font;
}
return result;
}
The last thing you wrote was about Q_D. This is only for use in the Qt source code. If you are implementing your own paint function, you do not have to use this macro.
I would read up on models very heavily, you may need a lot of the stuff in the documentation.

Resources