How do I delete a top level QTreeWidgetItem from a QTreeWidget? - c++

I'm attempting to remove a top level tree widget item if there are no child nodes within the top level item. What is the correct way to do this? I can't seem to find the API call within Qt's documentation. Is it safe to just call delete on the top level tree widget item? I haven't run into any issues yet, but I'd like to know if that's safe practice. Thanks much.
if(topLevelTreeWidgetItem->childCount() > 1) {
topLevelTreeWidgetItem->removeChild(childItem);
}
else
{
delete topLevelTreeWidgetItem;
}

deleteing a QTreeWidgetItem directly is perfectly safe.
According to the documentation for ~QTreeWidgetItem():
Destroys this tree widget item. The item will be removed from
QTreeWidgets to which it has been added. This makes it safe to delete
an item at any time.
I've used delete on many QTreeWidgetItems in practice and it works quite well.

To delete a top level item call QTreeWidget::takeTopLevelItem method and then delete the returned item:
delete treeWidget->takeTopLevelItem(index);
Where index is index of the item to be removed.

Function takeChild only works with QTreeWidgetItem. With QtreeWidget, you can use QtreeWidget::takeTopLevelItem(int index)

Related

Do I need to setParent to nullptr before deleting QWidget that's owned by QStackedWidget

I have a QStackedWidget which has a bunch of static "pages" but in a couple of cases one page needs to be recreated when it's switched to. Currently I have something like this:
void InsetNavigator::Navigate(InsetPage *page)
{
auto current_page = qobject_cast<InsetPage*>(stacked_widget_->currentWidget());
auto current_idx = stacked_widget_->currentIndex();
current_page->MadeHidden();
stacked_widget_->removeWidget(current_page);
current_page->setParent(nullptr);
delete current_page;
stacked_widget_->insertWidget(current_idx -1, page);
stacked_widget_->setCurrentWidget(page);
page->MadeVisible();
}
My question is, do I need to bother with reparenting the current page to a nullptr before deleting it, or can I just delete the current_page and the QStackedWidget will handle the fact that it's been deleted for me? I don't know if leaving the stacked widget as the parent but deleting the pointer will cause issues.
It depends on how the container-widget is implemented.
But in most-cases if not all, the container-widget (QStackedWidget in OP's case) updates own internal-state automatically once any child-widget is deleted.

How to remove widgets from layout in Qt

I have a piece of code which does this: a method named prepareUI makes the UI ready to be able to load search results that are fed into it. A method named onClear that is called when the results that are already showing needs to be cleared. And a method named populateSearchResults that takes the search data and loads the UI with it. The container that holds the data is a publicly available pointer, since it is needed to clear the results from onClear:
void MyClass::prepareSearchUI() {
//there may be many search results, hence need a scroll view to hold them
fResultsViewBox = new QScrollArea(this);
fResultsViewBox->setGeometry(28,169,224,232);
fSearchResultsLayout = new QGridLayout();
}
void MyClass::onClear() {
//I have tried this, this causes the problem, even though it clears the data correctly
delete fSearchResultContainer;
//tried this, does nothing
QLayoutItem *child;
while ((child = fSearchResultsLayout->takeAt(0)) != 0) {
...
delete child;
}
}
void MyClass::populateWithSearchesults(std::vector<std::string> &aSearchItems) {
fSearchResultContainer = new QWidget();
fSearchResultContainer->setLayout(fSearchResultsLayout);
for (int rowNum = 0; rowNum < aSearchItems.size(); rowNum++) {
QHBoxLayout *row = new QHBoxLayout();
//populate the row with some widgets, all allocated through 'new', without specifying any parent, like
QPushButton *loc = new QPushButton("Foo");
row->addWidget(loc);
fSearchResultsLayout->addLayout(row, rowNum, 0,1,2);
}
fResultsViewBox->setWidget(fSearchResultContainer);
}
Problem is, when I call onClear which internally calls delete, it does remove all the results that were showing. But after that, if I call populateWithSearchesults again, my app crashes, and the stack trace shows this method as where it crashed.
How do I fix this problem?
It seems that you have some misconceptions about ownership. A QLayout takes ownership of any item that is added to it: http://doc.qt.io/qt-5/qlayout.html#addItem
That means the QLayout is responsible for deleting these items. If you delete them then the QLayout will also try to delete them and then you get the crash you're seeing now.
QLayout doesn't have good functionality for deleting contents and re-adding them (for example removeWidget probably doesn't work as you would hope.) But there's a reason for this.
QLayout is not intended to be used as a list view.
What you do want is a, wait for it, QListView. Which will even handle the scroll functionality for you, and make adding and removing elements a possibility.
Actually you can solve this issue easily, even if you are using QGridLayout or any other Layouts :
subLayout->removeWidget(m_visibleCheckBox);//removes and then it causes some zigzag drawing at (0,0)
m_visibleCheckBox->setVisible(false);//asks to redraw the component internally
setLayout(subLayout);//for safety as we may use that layout again and again
If you just use the first line it will cause this :

Qt AbstractItemModel removeRows and delete causes core

I have the following to insert nodes:
layoutAboutToBeChanged();
beginInsertRows(createIndex(p_parent->row(), 0, p_parent), start, end);
TreeNode* p_node = new TreeNode(p_parent, p_data);
p_parent->appendChild(start, p_node);
endInsertRows();
layoutChanged();
And to remove rows:
layoutAboutToBeChanged();
beginRemoveRows(createIndex(p_parent->row(), 0, p_parent), row, row);
p_parent->removeChildren(row, row+1, this);
endRemoveRows();
layoutChanged();
When removeChildren is called, for each node that is removed the following is done:
changePersistentIndex(createIndex(p_node->row(), 0, p_node), QModelIndex());
delete p_node;
It works. I can add nodes and remove nodes.
Terminology NOTE: I'm using nodes and rows interchangeably. Sorry for any confusion.
What doesn't work:
If a new row is inserted in front of a selected node. The newly
inserted node becomes selected. [This is not what I expect of want.]
If a row is selected and then later deleted, immediate core dump.
If mouse over a row that is deleted, immediate core dump.
If I don't delete p_node. Everything runs fine. But obviously that creates a memory leak.
What am I doing wrong?
For reference I'm using QT 5.0.2 on 64 bit Linux.
Do append and remove children methods update the rowCount?
It seems to be the problem.
Check how is done in QStandardItemModel
Updating rowcount should solve the 3 points without having to update the persistent indexes:
If rowcount is not updated, is normal that selected item changes to the inserted before, its where the index is pointing.
& 3. Indexes are pointing to a deleted item.
To fix crashes you should use deleteLater instead of delete, so your view won't die trying to access invalid objects.
Selected item seems a index problem. Looks like it's missing a notification to view.
to point 1: maybe
model->blockSignals(true);
...
model->blockSignals(false);
will fix that selection behaviour.
i guess selected indexes/rows will have some functions called from the framework. so if they are deleted, they cause the crash. if you want to delete them, set the selection to another row/index and it should run fine ... '
if you mouse over a deleted row' ... if the row should be just empty, why not set the text empty?

QTreeView::scrollTo not working

Qt 4.8
I have a QTreeView based class with an asociated QAbstractItemModel based class. If I reload the model with new information I want to expand/scroll the tree to a previous selected item.
Both clases, tree view and model are correctly created and connected using QTreeView::setSelectionModel(...) working everything properly.
After reloading the model, I get a valid index to the previous selected item and I scrollTo it:
myTreeView->scrollTo(index);
but the tree is not expanded. However, if I expand the tree manually, the item is really selected.
Tree view is initialized in contruct with:
header()->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
header()->setStretchLastSection(false);
header()->setResizeMode(0, QHeaderView::ResizeToContents);
Any idea about expanding the tree to the selection?
Even QTreeView::scrollTo documentation says:
Scroll the contents of the tree view until the given model item index is
visible. The hint parameter specifies more precisely where the item should
be located after the operation. If any of the parents of the model item
are collapsed, they will be expanded to ensure that the model item is visible.
That is not really true (I think)
If solved the problem expanding all previous tree levels manually:
// This slot is invoqued from model using last selected item
void MyTreeWidget::ItemSelectedManually(const QModelIndex & ar_index)
{
std::vector<std::pair<int, int> > indexes;
// first of all, I save all item "offsets" relative to its parent
QModelIndex indexAbobe = ar_index.parent();
while (indexAbobe.isValid())
{
indexes.push_back(std::make_pair(indexAbobe.row(), indexAbobe.column()));
indexAbobe = indexAbobe.parent();
}
// now, select actual selection model
auto model = _viewer.selectionModel()->model();
// get root item
QModelIndex index = model->index(0, 0, QModelIndex());
if (index.isValid())
{
// now, expand all items below
for (auto it = indexes.rbegin(); it != indexes.rend() && index.isValid(); ++it)
{
auto row = (*it).first;
auto colum = (*it).second;
_viewer.setExpanded(index, true);
// and get a new item relative to parent
index = model->index(row, colum, index);
}
}
// finally, scroll to real item, after expanding everything above.
_viewer.scrollTo(ar_index);
}
I just dealt with similar situation, setting model index via setModelIndex (which internally ends up with scrollTo) worked OK for one of my models, but badly for the other one.
When I forcefully expanded all level 1 items, the scrollTo worked just as described above (calling expandAll suffices).
The reason was a bug in my model class in:
QModelIndex MyBadModel::parent(const QModelIndex& index) const
and as I fixed that, things got normal there too. The bug was such that internalId of parent model index was not the same as when this same model index (for parent) is calculated "from other direction", therefore in this model index (returned by parent method) could not be found in the list of visual indices.
Everything was simple.
Just change autoExpandDelay property from -1 to 0(for example).
ui->treeView->setAutoExpandDelay(0);
QTreeView::scrollTo should expand the hierarchy appropriately.
It's likely that your QModelIndex object is being invalidated when the model is updated (and perhaps still selecting the correct row because the row information is still valid though the parentage is not, don't ask me how those internals work). From the QModelIndex documentation:
Note: Model indexes should be used immediately and then discarded. You should not rely on indexes to remain valid after calling model functions that change the structure of the model or delete items. If you need to keep a model index over time use a QPersistentModelIndex.
You can certainly look into the QPersistentModelIndex object, but like it says in its documentation:
It is good practice to check that persistent model indexes are valid before using them.
Otherwise, you can always query for that item again after the model refresh.
Recently I struggled with same problem.
It's most likely a bug in your model class implementation.
in my case the row() method (that supposed to return index of the index under its parent) was not implemented correctly.
Sadly QT doesnt complain about that, and even selects the index (if you expand manually you will notice).
So, just go though the model code and hunt for bugs in row() and parent() methods etc.
You may be calling scrollTo before the tree view has finished reacting to the changes in current index and which indices are expanded/collapsed. A possible solution may be to delay the call to scrollTo by connecting it to a single-shot timer like this:
QTimer::singleShot(0, [this]{scrollTo(index);});
Using the timer will delay the call until control is passed back to the event queue.

Deleting widget that is in layout

What will happen if we will run delete widget for widget that is in layout? If this case was written in documentation, please give me the link (I didn't find).
Code example:
QLabel *l1 = new QLabel("1st");
QLabel *l2 = new QLabel("2nd");
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(l1);
layout->addWidget(l2);
QWidget *mainWidget = new QWidget;
mainWidget->setLayout(layout);
mainWidget->show();
delete l1;
l2->deleteLater();
Can things that will happen be different for l1 and l2?
I believe what you are doing is almost same, though neither would properly remove from layout the way you should be doing it. They are still being left as bad references in the layout (if I remember correctly)
The first one simply deletes the item now. The second will delete it once the control returns back to the event loop. But really, the way people usually remove items from a layout is to take them from the layout (giving it a chance to adjust itself), then delete the item and its widget (if you want).
QLayoutItem *child;
while ((child = layout->takeAt(0)) != 0) {
delete child->widget();
delete child;
}
Again, the deleting of the widget (child->widget()) is only needed if you want to destroy the widget that was added, in addition to the layout item that was holding it.
QLayout's listen for events of type ChildRemoved and remove the items
accordingly. Simply deleting the widget is safe.
by #FrankOsterfeld here.
dont use delete l1 on Qobjects that has active slots connected to them, you will run into a crash.
Use:
l1->hide();
l1->deleteLater();
It works fine for me
Generally, I don't like to delete Qt widgets, rather remove them from the appropriate layout. (Qt will delete its own widgets if you set the Delete on close window attribute. ) The difference between calling delete and delete later is that delete is the normal C++ delete operation that will call the destructor and free the memory associated with the object.
The deleteLater() method, as discussed in the Qt documentation deletes the object when the event loop is entered.