Create class by inheriting from QTreeView - qt

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.

Related

QTreeView with QAbstractItemModel: tree items collapse and expend when adding/updating new children

I have a QTreeView with my own model. When adding new items into the tree, some items expand or collapse. How can I preserve the expand state when modifying the tree?
Thank you, Martin.
It's quite late for the question's author but I had a similar problem and ended up here, so maybe it is worth posting a solution I came up with.
My understanding is that updating nodes is not a problem - indices are not invalidated and expansion is conserved. When you add a new node, however, the default seems to be to make the node collapsed. The fallowing small hack changes the default to expand all newly added indices:
// This is done at the point where the model is set to be used by the view
connect(&model, &QAbstractItemModel::rowsInserted,
[&](const QModelIndex &parent, int first, int last) {
for (; first <= last; ++first) {
tree_view->expand(
model.index(first, 0, parent));
}
});
In case you want to replace a node with a new version (remove it and add a new one in its place) you can use a similar approach: remember expansion by connecting to QAbstractItemModel::rowsAboutToBeRemoved and using QTreeView::isExpanded(). The state can be resored in a function/slot connected to QAbstractItemModel::rowsInserted.
I would like to share some code, but it is too long. I will explain where my problem was instead.
This is my tree structure
It is necessary to use following functions when inserting/deleting rows.
void QAbstractItemModel::beginInsertRows(const QModelIndex & parent, int first, int last);
void QAbstractItemModel::endInsertRows()
void QAbstractItemModel::beginRemoveRows(const QModelIndex & parent, int first, int last)
void QAbstractItemModel::endRemoveRows()
I found out that when inserting/deleting items A and C, it is required to use invalid model index as a parent index. An invalid model index is QModelIndex() without any parameters. At least it is what help in my case.
A simple tree model example is available here:
http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html

QTableView::scrollTo() (finding the right QModelIndex)

I'm trying to get PgDown clicks on a QTableView to scroll down a variable number of rows. I talk to my subclassed QSortFilterProxyModel which talks to the subclassed QAbstractTableModel to figure out what the next row is. That's all fine and dandy but I believe I'm faced with two caveats:
1: The row number inside the view doesn't do much. I need a QPoint on the screen to scroll to, and I'm not sure how to derive that from a cell.
2: I can create an index in the QSortFilterProxyModel but this generally causes crashes, as the parent is different... or I'm missing something.
int nextRow = getModel()->nextRow( indexAt( rect().topLeft() ) );
QModelIndex nextIndex = getModel()->index( nextRow, 0 );
scrollTo( nextIndex, QAbstractItemView::PositionAtTop );
Okay, I figured this out:
QModelIndex nextIndex = getModel()->index( nextRow, 0 );
scrollTo( nextIndex, QAbstractItemView::PositionAtTop );
I was having the QSortFilterProxyModel create and index which was a big no-no. I have issues when I have hidden rows, but should hopefully be able to figure that out.

combine QAbstractItemModels

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.

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

QTableView: how to edit non-editable cells in the program?

How should this be done by using the model->setData() method call?
I have derived a class called "MyStandardItemModel" from QStandardItemModel. I have made my third and fourth columns non-editable by overriding the protected virtual flags method. This is how it goes:
#define TX_PACKET_COLUMN (4u)
#define RX_PACKET_COLUMN (5u)
Qt::ItemFlags MyStandardItemModel::flags(const QModelIndex& index) const
{
if (index.column() == TX_PACKET_COLUMN || index.column() == RX_PACKET_COLUMN)
{
return (QStandardItemModel::flags(index) & ~Qt::ItemIsEditable);
}
else
{
return QStandardItemModel::flags(index);
}
}
...
//Set model
ui->testCaseTableView->setModel(model);
Having this done, I am not able to edit the cells in the third and fourth column.
Now, I want that when I double click on these cells, a pop-up dialog comes up. I will modify some data in the editable field of that dialog, and then copy it back to the non editable cells inside the code.
I tried to just write a doubleclick() handler for the QTreeView and just copy some data to the cells just to see if it is possible to copy data to the non-editable cells.
This operation is failing, and the data is not written into the non-editable cells.
Here you can find the double click handler:
void MainWindow::on_testCaseTableView_doubleClicked(const QModelIndex &index)
{
QVariant variant;
variant.toString() = "AA";
if((index.column() == TX_PACKET_COLUMN)||(index.column() == RX_PACKET_COLUMN))
{
model->setData(index, variant); // set new value
}
}
The setData(..) operation is clearing the already written data in the cells, but string "AA" is not getting written. Please suggest how to copy some data to non-editable cells inside the code.
QVariant set is empty. Nothing needs to be wrong in your model. Error is on this line:
variant.toString() = "AA";
change to:
QVariant variant("AA"); // just for testing anyway
As I indicated in my comment, you have to fix this first issue:
instead of:
QVariant variant;
variant.toString() = "AA";
you should write
QVariant variant = QLatin1String("AA");
In general, you would look into the setData(...) implementation for such cases whether you emit the data changed signal properly and so forth, but here you are entering a prior issue which can lead to problems, so let us fix that.
Note, you should use QLatin1String to avoid the unnecessary explicit conversion from raw char* to QString. This is a good practice in general, and this is available with Qt 4 as well as Qt 5.
Although, you could also use the QStringLiteral macro for creating a QString very efficiently with template magic out of your raw literal, but that requires C++11.

Resources