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

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

Related

Calculating number of nodes in BST using recursion c++

I'm trying to find the number of nodes in a BST using recursion. Here is my code
struct Node{
int key;
struct Node* left;
struct Node* right;
Node(){
int key = 0;
struct Node* left = nullptr;
struct Node* right = nullptr;
}
};
src_root is the address of the root node of the tree.
int BST::countNodes(Node* src_root, int sum){
if((src_root==root && src_root==nullptr) || src_root==nullptr)
return 0;
else if(src_root->left==nullptr || src_root->right==nullptr)
return sum;
return countNodes(src_root->left, sum + 1) + countNodes(src_root->right, sum + 1) + 1;
}
However my code only seems to work if there are 3 nodes. Anything greater than 3 gives wrong answer. Please help me find out what's wrong with it. Thanks!
It is a long time ago since I made anything in C/C++ so if there might be some syntax errors.
int BST::countNodes(Node *scr_root)
{
if (scr_root == null) return 0;
return 1 + countNodes(scr_root->left) + countNodes(scr_root->right);
}
I think that will do the job.
You have several logical and structural problems in your implementation. Casperah gave you the "clean" answer that I assume you already found on the web (if you haven't already done that research, you shouldn't have posted your question). Thus, what you're looking for is not someone else's solution, but how to fix your own.
Why do you pass sum down the tree? Lower nodes shouldn't care what the previous count is; it's the parent's job to accumulate the counts from its children. See how that's done in Casperah's answer? Drop the extra parameter from your code; it's merely another source for error.
Your base case has an identically false clause: src_root==root && src_root==nullptr ... if you make a meaningful call, src_root cannot be both root and nullptr.
Why are you comparing against a global value, root? Each call simply gets its own job done and returns. When your call tree crawls back to the original invocation, the one that was called with the root, it simply does its job and returns to the calling program. This should not be a special case.
Your else clause is wrong: it says that if either child is null, you ignore counting the other child altogether and return only the count so far. This guarantees that you'll give the wrong answer unless the tree is absolutely balanced and filled, a total of 2^N - 1 nodes for N levels.
Fix those items in whatever order you find instructive; the idea is to learn. Note, however, that your final code should look a lot like the answer Casperah provided.

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.

"Index out of range" when removing last item in QAbstractTableModel

I implemented the removeRows() method according to the documentation. My data is stored in a QList. I can remove items just fine using:
bool MeasurementManager::removeRows(int row, int count, const QModelIndex &m) {
if(count > 1) {
qDebug() << "MeasurementManager: cannot remove more than one measurement";
return false;
}
beginRemoveRows(QModelIndex(), row, row+count-1);
list.removeAt(row);
endRemoveRows();
return true
}
However, when removing the last item I get the following error message, when executing beginRemoveRows():
ASSERT failure in QList<T>::at: "index out of range"
When removing the last item (leading to the crash) it obviously has to be in row 0, but as long as there are other items in the model I can remove the item in row 0 without any problems.
If I comment out the actual removal of my data like this
beginRemoveRows(QModelIndex(), row, row+count-1);
//list.removeAt(row);
endRemoveRows();
no crash occurs, so my assumption it, that something tries to access one of list's elements after the removal. However when stepping through the function the beginRemoveRows() method clearly is the culprit.
Any help where to start debugging would be appreciated!
I found the solution, my bad. I had connected to the ´selectionChanged()´ signal to a custom slot. This tried to access the recently deleted item in the table model.
I overlooked, that deselection a table item emits an selectionChanged() signal, too.
What you see is probably an artifact of code reordering introduced by optimizing the code.
Compile the code again with all optimizations disabled to avoid confusing the debugger.
Just had the same problem. Simply call reset() and remove the rows afterwards.
void QItemSelectionModel::reset() [virtual slot]
Clears the selection model. Does not emit any signals.

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