How to delete an already existing layout on a widget? - qt

You must first delete the existing layout manager (returned by
layout()) before you can call setLayout() with the new layout.
from http://doc.qt.io/qt-5.9/qwidget.html#setLayout
Which function is used for deleting the previous layout?

Chris Wilson's answer is correct, but I've found the layout does not delete sublayouts and qwidgets beneath it. It's best to do it manually if you have complicated layouts or you might have a memory leak.
QLayout * layout = new QWhateverLayout();
// ... create complicated layout ...
// completely delete layout and sublayouts
QLayoutItem * item;
QLayout * sublayout;
QWidget * widget;
while ((item = layout->takeAt(0))) {
if ((sublayout = item->layout()) != 0) {/* do the same for sublayout*/}
else if ((widget = item->widget()) != 0) {widget->hide(); delete widget;}
else {delete item;}
}
// then finally
delete layout;

You just use
delete layout;
like you would with any other pointer you created using new.

This code deletes the layout, all its children and everything inside the layout 'disappears'.
qDeleteAll(yourWidget->findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly));
delete layout();
This deletes all direct widgets of the widget yourWidget. Using Qt::FindDirectChildrenOnly is essential as it prevents the deletion of widgets that are children of widgets that are also in the list and probably already deleted by the loop inside qDeleteAll.
Here is the description of qDeleteAll:
void qDeleteAll(ForwardIterator begin, ForwardIterator end)
Deletes all the items in the range [begin, end] using the C++ delete > operator. The item type must be a pointer type (for example, QWidget *).
Note that qDeleteAll needs to be called with a container from that widget (not the layout). And note that qDeleteAll does NOT delete yourWidget - just its children.
Now a new layout can be set.

I want to remove the current layout, replace it with a new layout but keep all widgets managed by the layout. I found that in this case, Chris Wilson's solution does not work well. The layout is not always changed.
This worked for me:
void RemoveLayout (QWidget* widget)
{
QLayout* layout = widget->layout ();
if (layout != 0)
{
QLayoutItem *item;
while ((item = layout->takeAt(0)) != 0)
layout->removeItem (item);
delete layout;
}
}

From Qt6's docs:
The following code fragment shows a safe way to remove all items from a layout:
QLayoutItem *child;
while ((child = layout->takeAt(0)) != nullptr) {
...
delete child->widget(); // delete the widget
delete child; // delete the layout item
}
This assumes takeAt() has been correctly implemented in the QLayout subclass. Follow link for details.

Related

QTreeWidgetItem::insertChild does not its work

I have a QTreeWidget and I would like to insert a child at a certain index. I tried with
void QTreeWidgetItem::insertChild ( int index, QTreeWidgetItem * child )
but any index I put, it inserts the child at the end of all children the parent has.
I found the solution:
My first code was this
QTreeWidgetItem* newVtxItem = new QTreeWidgetItem(parentItem);
newVtxItem->setText(0, "vtx 1");
newVtxItem->setText(1, "-1");
childOfNameProfile->insertChild(0,newVtxItem);
Then I correct it by deleting the parentItem of the new QTreeWidgetItem in this way:
QTreeWidgetItem* newVtxItem = new QTreeWidgetItem();
newVtxItem->setText(0, "vtx 1");
newVtxItem->setText(1, "-1");
childOfNameProfile->insertChild(0,newVtxItem);
The reason why the child was inserted after all children is because when you create a QTreeWidgetItem and defining a parent, Qt put it directly at the end of the children list of the parent. So insertChild, as the documentation said:
If the child has already been inserted somewhere else it won’t be inserted again.
I hope I could help someone. Enjoy Qt!

QT: changing the tab traversal order on runtime?

you might be able to tell that I'm pretty new to QT...
My program contains a window with several Widgets in a QGridLayout. Some of these Widgets have a layout and child widgets themselves. Pressing the Tab key moves the focus like I expect it to, in the order I created the widgets.
Problems occur when a widget changes it's content (I do that by deleting all child widgets and then constructing new ones.) If I do that, new widgets are moved to the end of the tab chain (that indicates to me, that tab order is kind of global for a window). I have tried to use QWidget::setTabOrder() to reorder all widgets (I tried both, setting tab order for only the contends of the main window and setting it for the children too) but the actual order doesn't change. I did this by emitting a signal when the contend of a widget changes and connecting that to a slot on my main Window.
I think I understand the way the setTabOrder() function should work. I do something similar to this:
QWidget* a = firstWidget;
QWidget* b = secondWidget;
QWidget::setTabOrder(a,b);
for (int i = 0; i < widgets.size(); ++i) {
a = b;
b = widgets[i];
QWidget::setTabOrder(a,b);
}
Is there anything special one has to do when changing the tab order?
I also tried to reimplement focusNextPrevChild(bool next) and focusInEvent(QFocusEvent* e) similar to what can be found at this site. 1
I managed to mess up tab order a lot more like this... is this approach a step in the right direction?
I'm sorry if this is something simple that I managed to miss, but I'm struggling for a while now and I can't find a solution.
Any help is very appreciated.
I had the same problem, and resolved it using the info Tim Meyer provided in the comments.
Tab order is not hierarchical - calling setTabOrder on a parent widget that doesn't accept focus will not cause the focus to be passed to the child. You will need to fetch the appropriate children from the widget and set the order on them
In my case, a dynamically constructed QTreeWidget contained editable widgets, and I needed to reset setTabOrder to account for widgets being created out-of-order.
The following code is (most) of the implementation
// Perform a depth-first gather of the child widgets of this item
void gatherTabWidgets( QObject* item, QWidgetList& tabWidgets )
{
if (item->isWidgetType())
{
QWidget* itemWidget = static_cast<QWidget*>(item);
if (itemWidget->focusPolicy() & Qt::TabFocus)
tabWidgets.push_back( itemWidget );
}
const QObjectList& children = item->children();
for (QObjectList::const_iterator itr = children.begin(); itr != children.end(); itr++)
{
gatherTabWidgets( const_cast<QObject*>(*itr), tabWidgets );
}
}
// Perform a depth-first gather of the child items widgets;
void gatherTabWidgets( QTreeWidgetItem* item, QWidgetList& tabWidgets )
{
QWidget* itemWidget = fetchWidgetForItem( item );
gatherTabWidgets( itemWidget, tabWidgets );
for (int i = 0; i < item->childCount(); i++)
{
gatherTabWidgets( item->child( i ), tabWidgets );
}
}
void VETreeWidget::sortTree()
{
// Ensure ordering is maintained.
sortItems( 0, Qt::AscendingOrder );
// Once the items have been re-ordered, re-create the tab ordering
QTreeWidgetItem* baseItem = topLevelItem( 0 );
QWidgetList tabWidgets;
gatherTabWidgets( baseItem, tabWidgets );
if (!tabWidgets.empty())
{
QWidget* lastWidget = tabWidgets.first();
// Connect this treeview to the first widget. This ensures
// if the treeview is tabbed-to, it will tab to the correct sub-widget
setTabOrder( this, lastWidget );
for (QWidgetList::iterator itr = tabWidgets.begin() + 1; itr != tabWidgets.end(); itr++)
{
setTabOrder( lastWidget, *itr );
lastWidget = *itr;
}
}
}

Check whether a widget is in the layout Qt

How to check whether a widget is in a layout? I have a layout that may contain the widget or not.
Widget name: infoClient
Layer name: verticalLayout_3
Need to check if the widget exists in the layout, then do not add a new one.
And if does not exist, add a new one.
How to do it?
void MainWindow::slotPush1()
{
if <there is no infoClient> ui->verticalLayout_3->addWidget(new infoClient(this));
}
Few points:
The parent of a widget in a layout is the widget containing the top-level layout
Layouts can be nested.
A layout contains items (QLayoutItem), which are either layouts (layout() is not null) or widgets (widget() is not null). In the end you have a tree of layout items.
So You need to do a search from the parent widget (dfs, bfs).
bool checkWidgetInsideLayout(const QWidget* _someWidget){
return _someWidget != NULL &&
_someWidget->parent() != NULL &&
_someWidget->parent()->layout() != NULL &&
foundItem(_someWidget->parent()->layout(), _someWidget );
}
//clumsy dfs
bool foundItem(const QLayout* layout, const QWidget* _someWidget ){
for(int i = 0; i < layout->count(); i++){
QLayoutItem* item = layout->itemAt(i);
if(item->widget() == _someWidget )
return true;
if(item->layout() && foundItem(item->layout(), _someWidget) )
return true;
}
return false;
}
Use QObject::findChild to find a child by its name. For instance:
void MainWindow::slotPush1()
{
if (ui->verticalLayout_3->findChild<QWidget*>("infoClient")) // your code to add it here
}
Note: findChild is a template function. If you're not familiar with template functions, just know that you pass the type of object you want to find (in your example, it looks like you could use ui->verticalLayout_3->findChild<infoClient*>("infoClient")). If you want to find a QWidget, or anything that inherits from QWidget, you can just use findChild<QWidget*>() and you'll be safe.
There is no mechanism within Qt that will perform the check that you are looking for. You will have to implement it yourself:
void MainWindow::slotPush1()
{
if (doesLayoutContainInfoClient(ui->verticalLayout_3))
{
ui->verticalLayout_3->addWidget(new infoClient(this));
}
}
bool MainWindow::doesLayoutContainInfoClient(QLayout* layout)
{
const QString infoClientName("infoClient");
for (int i=0; i<layout->count(); i++)
{
QWidget* layoutWidget = layout->itemAt(i)->widget();
if (layoutWidget)
{
if (infoClientName == layoutWidget->metaObject()->className())
{
return true;
}
}
}
return false;
}
Despite what I've suggested above, I don't really recommend it. It makes more sense to store whether or not you've added infoClient to your layout as an independent boolean within your program somewhere. Querying the contents of layouts in this manner is somewhat unusual, and is messier than just using a bool.
I agree with Tom Panning's solution to find your child with the QObject::findChild() method.
But adding a Widget to QLayout will reparent it to the layout's parent. So you'll have to find it by calling it with the MainWindow object like that:
void MainWindow::slotPush1()
{
if (this->findChild<QWidget*>("infoClient")) {
// ...
}
}
If your infoClient widget was added in the QtDesigner, you won't have problems with this solution. The designer sets per default the object name.
If the infoClient was added to the layout in your code, you have to set the object name explicitly, otherwise you won't be able to find it because its name is empty:
(Assuming, m_client is a member variable of MainWindow)
void MainWindow::createWidgets()
{
if (infoClientShouldBeAdded) {
m_client = new infoClient(this);
m_client->setObjectName("infoClient");
ui->verticalLayout_3->addWidget(m_infoClient);
}
}
Another possibility would be to keep track of the pointers of your created widgets (e.g., by storing them in a std::vector or Qt equivalent). This allows to use indexof(QWidget*):
Searches for widget widget in this layout (not including child layouts).
Returns the index of widget, or -1 if widget is not found.

QListView & QStandardItemModel - Prevent duplicates

What is the way to go to prevent duplicates in a QListView which uses QStandardItemModel as its model? Data is added with drag & drop, so I tried to override QStandardItemModel::dropMimeData, which seems kind of odd since I need to override QStandardItemModel::mimeData (and reimplement encodeData/decodeData) as well. This has to be easier!
Well, I managed to solve this by overriding QListView::dataChanged, which checks if there is more than one item with the same data for Qt::DisplayRole in the model after dropping and deletes one of them if there is. It basically looks like that:
void MyListView::dataChanged(QModelIndex topLeft, QModelIndex bottomRight)
{
// there can be only one item dragged at once in my use case
if(topLeft == bottomRight)
{
QStandardItemModel* m = static_cast<QStandardItemModel*>(model());
// if theres already another item with the same DisplayRole...
if(m->findItems(topLeft.data().toString()).count() > 1)
{
// ... we get rid of it.
model()->removeRow(topLeft.row());
}
}
else
{
// let QListView decide
QListView::dataChanged(topLeft, bottomRight);
}
}
It's by far not perfect (e.g. if you can drop more than one item at once) but it works for that simple use case.
The easiest way I can see would be to create your own proxy model.
See http://doc.qt.io/qt-5/qabstractproxymodel.html

How to Clear all the Widgets in Parent Widgets?

I am using the constructor QWidget(QWidget *parent). This parent widget contains a lot of child widgets. I need to clear all the child widgets from the parent at runtime. How can I do this?
Previous answer is wrong!! You cannot use findChildren to delete a widget's children, because Qt4's findChildren recursively lists children. Therefore, you will delete children of children, which then may be deleted twice, potentially crashing your app.
More generally, in Qt, taking a list of QObject pointers and deleting them one by one is dangerous, as destroying an object may chain-destroy other objects, due to the parent ownership mechanism, or by connecting a destroyed() signal to a deleteLater() slot. Therefore, destroying the first objects in the list may invalidate the next ones.
You need to list children widgets either by:
Passing the Qt::FindDirectChildrenOnly flag to findChild if you are using Qt5 (which did not exist when the question was asked...)
Using QLayout functions for listing items,
Using QObject::children, and for each test if it is a widget using isWidgetType() or a cast
Using findChild() in a loop and delete the result until it returns a null pointer
To take care of the recursivity problem pointed out by #galinette you can just remove the widgets in a while loop
while ( QWidget* w = findChild<QWidget*>() )
delete w;
Summarizing and supplementing:
For Qt5 in one line:
qDeleteAll(parentWidget->findChildren<QWidget*>("", Qt::FindDirectChildrenOnly));
For Qt5 for a lot of children, using setUpdatesEnabled():
parentWidget->setUpdatesEnabled(false);
qDeleteAll(parentWidget->findChildren<QWidget*>("", Qt::FindDirectChildrenOnly));
parentWidget->setUpdatesEnabled(true);
Note that this is not exception safe! While Qt does not at this time appear to throw exceptions here, the signal destroyed() could be connected to code that does throw, or an overridden Object::childEvent(QChildEvent*) could throw.
Better would be to use a helper class:
class UpdatesEnabledHelper
{
QWidget* m_parentWidget;
public:
UpdatesEnabledHelper(QWidget* parentWidget) : m_parentWidget(parentWidget) { parentWidget->setUpdatesEnabled(false); }
~UpdatesEnabledHelper() { m_parentWidget->setUpdatesEnabled(true); }
};
...
UpdatesEnabledHelper helper(parentWidget);
qDeleteAll(parentWidget->findChildren<QWidget*>("", Qt::FindDirectChildrenOnly));
For Qt4:
QList<QWidget*> childWidgets = parentWidget->findChildren<QWidget*>();
foreach(QWidget* widget, childWidgets)
if (widget->parentWidget() == parentWidget)
delete widget;
Removing from the QLayout works in both Qt4 and Qt5:
QLayoutItem* child;
while (NULL != (child = layout->takeAt(0))) // or nullptr instead of NULL
delete child;
QObjects (and therefore QWidgets) remove themselves (automagically) from their parent in their (QObject) destructor.
From Qt docs
The following code fragment shows a safe way to remove all items from a layout:
QLayoutItem *child;
while ((child = layout->takeAt(0)) != 0) {
...
delete child;
}
You can use the following in your parent widget class:
QList<QWidget *> widgets = findChildren<QWidget *>();
foreach(QWidget * widget, widgets)
{
delete widget;
}

Resources