Memoryleak with QListWidget addItem() + setItemWidget() - c++

When I press a key there shall be a query to an engine. The results get put into a QListWidget by adding an item and setting the widget. Somehow this causes a massive memory overflow and even crashed my machine. But I dont get the error. Does clear() not delete the items passed to the QListWidget and the widgets set by setItemWidget(). I even tried to delete them on my own (comment), but still got a memoryleak. The error is in the if (!results.empty())-block, I guess, since commenting it out plugs the memoryleak.
void Widget::onTextEdited(const QString & text)
{
// QListWidgetItem * takenItem;
// while (takenItem = _results->takeItem(0)){
// delete _results->itemWidget(takenItem);
// delete takenItem;
// }
_results->clear(); _results->hide();
if (!text.isEmpty())
{
const std::vector<const Items::AbstractItem *> results = _engine.request(text);
if (!results.empty())
{
for (auto i : results){
QListWidgetItem *lwi = new QListWidgetItem;
_results->addItem(lwi);
ListItemWidget *w = new ListItemWidget;
w->setName(i->name());
w->setTooltip(i->path());
_results->setItemWidget(lwi, w);
}
_results->setFixedHeight(std::min(5,_results->count()) * 48); // TODO
_results->show();
}
}
this->adjustSize();
}

You should definitely use a memory leak detection tool instead of guessing around :)
UPDATE: clear() only deletes items but does not delete the widgets belonging to it. The widgets will be deleted if the QListWidget is deleted.
clear() does delete items and widgets belonging to it. And you mentioned that commenting out if(!results.empty()) solved the problem. I don't see any problem in the setItemWidget part. So I think the problem lies somewhere else, maybe ListItemWidget. How about you try replacing ListItemWidget with QLabel and see what happens. Eg:
QListWidgetItem *lwi = new QListWidgetItem;
_results->addItem(lwi);
//ListItemWidget *w = new ListItemWidget;
//w->setName(i->name());
//w->setTooltip(i->path());
QLabel *w = new QLabel;
w->setText("Hello");
_results->setItemWidget(lwi, w);

Related

Deleting a widget from QTableView

I have a delete button(QPushButton) in the last column of each row of my table view. I am creating these push buttons and directly setting them in view. Since I have allocated memory dynamically I wish to free this memory but I haven't stored pointers of these buttons anywhere so I am trying to obtain the widget at the time of clean up and deleting them.
SDelegate* myDelegate;
myDelegate = new SDelegate();
STableModel* model = new STableModel(1, 7, this);
myWindow->tableView->setModel(model);
myWindow->tableView->setItemDelegate(myDelegate);
for(int i = 0; i < no_of_rows; ++i) {
QPushButton* deleteButton = new QPushButton();
myWindow->tableView->setIndexWidget(model->index(i, 6), deleteButton);
}
exec();
// Cleanup
for(int i = 0; i < no_of_rows; ++i) {
// code works fine on removing this particular section
QWidget* widget = myWindow->tableView->indexWidget(model->index(i, 6));
if (widget)
delete widget;
}
delete model;
delete myDelegate;
I am getting a crash in qt5cored.dll (Unhandled exception) and application is crashing in qcoreapplication.h at the following code:
#ifndef QT_NO_QOBJECT
inline bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
{ if (event) event->spont = false; return self ? self->notifyInternal(receiver, event) : false; }
While debugging there is no issue in deleting these widgets but code crashes afterwards at some other point. I am using QTableView and custom class for model which has inherited QAbstractTableModel.
There's a Qt bug that manifests as follow: if there are any index widgets, and you invoke setModel(nullptr) on the view, it'll crash in an assertion on visualRow != -1 in qtableview.cpp:1625 (in Qt 5.6.0). Presumably this bug could be triggered when the model is being removed in some other fashion too.
But I can't reproduce it by merely destroying the model instance. So I doubt that it's relevant here unless you get the same assertion failure.
Given the style of your code, it's more likely that you have a memory bug elsewhere. If you think that the code above is crashing, you should have a self-contained test case that demonstrates the crash. Is your model or delegate to blame? Would it crash using no delegate? Would it crash using a stock model?
Your code excerpt seems to be fine, if mostly unnecessary. You could allocate the delegate and the model locally. The buttons are owned by the view: as soon as the need for the buttons goes away, such as when the model changes the row count or goes away, they will get appropriately deleted. So you don't have to delete them yourself, it's safe but completely unnecessary.
Here's an example that demonstrates that in all cases, the buttons will get disposed when the model gets destroyed or the view gets destroyed, whichever comes first. Tracking object lifetime is super simple in Qt: keep a set of objects, and remove them from the set using a functor attached to the object's destroyed signal. In Qt 4 you'd use a helper class with a slot.
// https://github.com/KubaO/stackoverflown/tree/master/questions/model-indexwidget-del-38796375
#include <QtWidgets>
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QSet<QObject*> live;
{
QDialog dialog;
QVBoxLayout layout{&dialog};
QTableView view;
QPushButton clear{"Clear"};
layout.addWidget(&view);
layout.addWidget(&clear);
QScopedPointer<QStringListModel> model{new QStringListModel{&dialog}};
model->setStringList(QStringList{"a", "b", "c"});
view.setModel(model.data());
for (int i = 0; i < model->rowCount(); ++i) {
auto deleteButton = new QPushButton;
view.setIndexWidget(model->index(i), deleteButton);
live.insert(deleteButton);
QObject::connect(deleteButton, &QObject::destroyed, [&](QObject* obj) {
live.remove(obj); });
}
QObject::connect(&clear, &QPushButton::clicked, [&]{ model.reset(); });
dialog.exec();
Q_ASSERT(model || live.isEmpty());
}
Q_ASSERT(live.isEmpty());
}
Check out QObject::deleteLater() , it usually helps with issues around deleting QObjects / QWidgets.

Crash while calling setItemWidget

I am using a QTreeWidget and setting a widget for the QTreeWidgetItem in the QTreeWidget. It is working fine but when I do the same for second time, the application is crashing.
The below is working fine.
QTreeWidget* treewidget = new QTreeWidget();
QTreeWidgetItem* item0 = new QTreeWidgetItem((QTreeWidget*)0, QStringList(QString("item0")));
treewidget->insertTopLevelItem(0,item0);
QSlider* slider0 = new QSlider();
treewidget->setItemWidget(item0, 0, slider0);
But if I add the last line once again, it is crashing when running the application.
The below is crashing.
QTreeWidget* treewidget = new QTreeWidget();
QTreeWidgetItem* item0 = new QTreeWidgetItem((QTreeWidget*)0, QStringList(QString("item0")));
treewidget->insertTopLevelItem(0,item0);
QSlider* slider0 = new QSlider();
treewidget->setItemWidget(item0, 0, slider0);
treewidget->setItemWidget(item0, 0, slider0); // Intentionally added to simulate the issue
The above is an example to show the issue, but in my application, based on some events, I delete the tree widget items and add it later. When I set the item widget (after adding the items later), I am getting the crash.
I could not figure out why. Any ideas? FYI, I am using Qt 5.3.2 MSVC 2010, 32 bit.
treewidget->setItemWidget(item0, 0, slider0);
treewidget->setItemWidget(item0, 0, slider0);// Intentionally added to simulate the issue
I look at Qt code (4.x):
void QTreeWidget::setItemWidget(QTreeWidgetItem *item, int column, QWidget *widget)
{
Q_D(QTreeWidget);
QAbstractItemView::setIndexWidget(d->index(item, column), widget);
}
and QAbstractItemView::setIndexWidget:
void QAbstractItemView::setIndexWidget(const QModelIndex &index, QWidget *widget)
{
Q_D(QAbstractItemView);
if (!d->isIndexValid(index))
return;
if (QWidget *oldWidget = indexWidget(index)) {
d->persistent.remove(oldWidget);
d->removeEditor(oldWidget);
oldWidget->deleteLater();
}
so if you add slider0 two times, then at first call it was added,
at seconds call Qt call for it deleteLater, and then added it,
are sure that this is what you want?
You have to set correct parent in the constructor of QTreeWidgetItem. Try this:
QTreeWidgetItem* item0 = new QTreeWidgetItem(treewidget);
Also it is important to understand who is owner of the slider0 after calling of setItemWidget(): the owner is your table, so 1) you don't need to delete this object; 2) the object will be deleted if you call setItemWidget for the same cell again. So, double call of treewidget->setItemWidget(item0, 0, slider0); seems very strange (second time you are setting the deleted object into that cell).

Removing item from QListWidget from inside a Widget

I have a QListWidget in my MainWindow that displays a list of VideoWidgets (a custom QWidget).
VideoWidget has a clickable label where on clicking the label it should delete a file and then remove the QListItem which holds the VideoWidget from the QListWidget. Here is my VideoWidget class:
VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent)
{
ClickableLabel *smallRed = new ClickableLabel(this)
//...
QObject::connect(smallRed,SIGNAL(clicked()),this,SLOT(removeVideo()));
}
void VideoWidget::removeVideo(){
//...code to remove a file
QListWidget* list = myParent->getList();
QListWidgetItem* item = list->takeItem(list->currentIndex().row());
myList->removeItemWidget(item);
}
The problem is that clicking the smallRed label will not select its item in the QListWidget which means that list->currentIndex().row() will return -1. Clicking anywhere else in the Widget does select the current item. For the code to work I currently have to first click anywhere in the VideoWidget and then click its ClickableLabel. Is there any way I can achieve the same effect with one single click on my ClickableLabel?
From your previous qestion, we suggested use signal and slots. For example:
for(int r=0;r<3;r++)
{
QListWidgetItem* lwi = new QListWidgetItem;
ui->listWidget->addItem(lwi);
QCheckBox *check = new QCheckBox(QString("checkBox%1").arg(r));
check->setObjectName("filepath");
connect(check,SIGNAL(clicked()),this,SLOT(echo()));
ui->listWidget->setItemWidget(lwi,check);
}
Slot:
void MainWindow::echo()
{
qDebug() << sender()->objectName() << "should be remmoved";
}
It is not unique way to solve this problem, but it shows all main things, with signals and slots mechanism, objectName and sender() you can achieve all what you need.
sender() return object which send signal, you can cast it, but if you need only objectName you should not cast.

QCheckbox name access

I generate checkboxes as follows:
foreach(QString filt, types){
QCheckBox *checkbox = new QCheckBox(filt, this);
checkbox->setChecked(true);
vbox->addWidget(checkbox);
}
I need to get access to these checkboxes by name but they are all called the same?
I need to read the text they display.
How can I go about this?
Is it possible to run a for loop and attach the value of i onto the end of the checkbox. So in effect, the checkbox would be called checkbox[0], checkbox [1], etc?
EDIT:
I've changed the code to the following:
for(int i=0; i<types.count(); ++i)
{
QString filt = types[i];
*checkboxCount = *checkboxCount + 1;
QCheckBox *typecheckbox[i] = new QCheckBox(filt, this);
typecheckbox[i]->setChecked(true);
vbox->addWidget(typecheckbox[i]);
}
I thought this was a way to dynamically name the checkboxes so I can loop through them to get the text value from them.
I'm getting the error 'variable-sized object may not be initialized' on this line QCheckBox *typecheckbox[i] = new QCheckBox(filt, this);
Any ideas to a solution/ alternate approach?
If you want to access the checkboxes later, you can just use the find children method as follows:
QStringList myStringList;
QList<QCheckBox *> list = vbox->findChildren<QCheckBox *>();
foreach (QCheckBox *checkBox, list) {
if (checkBox->isChecked())
myStringList.append(checkBox->text());
}

How to make a QMdiArea subwindow widget non-resizeable?

So the non-QMdiArea version of my code,
MyWidget::MyWidget(QWidget* parent)
{
...
layout()->setSizeConstraint( QLayout::SetFixedSize );
}
MainWindow::MainWindow(...)
{
...
MyWidget* wgt = new MyWidget(NULL);
wgt->show();
}
works just fine and produces a widget that the user can't resize. But when the MainWindow code is replaced with
MainWindow::MainWindow(...)
{
...
MyWidget* wgt = new MyWidget(ui->mdiArea); //Or MyWidget(NULL), same result
ui->mdiArea->addSubWindow(wgt);
}
the window, now within the QMdiArea, is resizeable. It doesn't seem to be an issue of Qt::WindowFlags, they don't handle resize policy. Surely there is a way to do this? NB I cant use something like setFixedSize(ht, wd) since the size of the widget can change programmatically (subwidgets are added and removed). But the user should not be able to resize it.
Even though MyWidget is not resizeable, when you call:
ui->mdiArea->addSubWindow(wgt);
The widget is put in a QMdiSubWindow which is resizeable. All you have to do is get the window that's created and fix its size:
QMdiSubWindow* subWindow = ui->mdiArea->addSubWindow(wgt);
subWindow->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
This should work, but I haven't tried this code myself.
EDIT: well... apparently that doesn't fix the size. :(
The following worked for me:
MyWidget* wgt = new MyWidget(ui->mdiArea);
QMdiSubWindow* subWindow = ui->mdiArea->addSubWindow(wgt);
subWindow->setFixedSize(wgt->size());
wgt->show();