RAM problems while creating and deleting custom QWidgets inside of QLayout - c++

I have created a custom QWidget (code below) [with a QHBoxLayout and two QPushButtons inside] and added it to a QVBoxLayout in GUI. This custom QWidget-object will be created and deleted several times (code below).
When I type top inside into the console (on embedded linux) there is a RAM-increase every time I add a new QWidget. That's Ok! But I can't see a decrease of RAM on deletion.
What is wrong with my code? I want, that the RAM decreases on deletion of the custom QWidgets.
myCustomWidget.h
class QCustomPushButton_withinIcon_LeftAndRight : public QWidget {
Q_OBJECT
public:
//functions
QCustomPushButton_withinIcon_LeftAndRight(QWidget *parent = 0);
~QCustomPushButton_withinIcon_LeftAndRight();
//other slots like:
// - virtual void mousePressEvent();
// - virtual void mouseReleaseEvent();
//other signals like:
// - void clicked();
//other functions like:
// - virtual void setEnabled(bool a);
// - virtual void paintEvent(QPaintEvent *);
// - ...
private:
//variables
QLayout *innerLayout; //!< Layout for two buttons
QPushButton *buttonLeft; //!< Button left
QPushButton *buttonRight; //!< Button right
//other variables like:
// - bool enabled;
// - ...
};
myCustomWidget.cpp
QCustomPushButton_withinIcon_LeftAndRight::QCustomPushButton_withinIcon_LeftAndRight(QWidget *parent) :
QWidget(parent),
mSVG (new svg),
mGradient (new gradient)
{
enabled = true;
//create innerLayout
innerLayout = new QHBoxLayout();
this->setLayout(innerLayout);
//create buttons
buttonLeft = new QPushButton();
buttonRight = new QPushButton();
innerLayout->addWidget(buttonLeft);
innerLayout->addWidget(buttonRight);
//create connections like:
// - connect (buttonLeft, SIGNAL(pressed()), this, SLOT(mousePressEvent()));
// - ...
//set some stylesheets
// - buttonLeft->setStyleSheet("...");
}
QCustomPushButton_withinIcon_LeftAndRight::~QCustomPushButton_withinIcon_LeftAndRight()
{
//I think, that this is right. If not, correct me.
delete buttonLeft;
delete buttonRight;
delete innerLayout;
}
//void QCustomPushButton_withinIcon_LeftAndRight::mousePressEvent() {}
//void QCustomPushButton_withinIcon_LeftAndRight::mouseReleaseEvent() {}
//void QCustomPushButton_withinIcon_LeftAndRight::setEnabled(bool a) {}
//void QCustomPushButton_withinIcon_LeftAndRight::paintEvent(QPaintEvent *) {} ...
gui.cpp (add QWidgets to a QLayout in the GUI)
QCustomPushButton_withinIcon_LeftAndRight *button = new QCustomPushButton_withinIcon_LeftAndRight();
//add button to layout
parentUiWindow->aLayoutNameInGui->addWidget(button);
//aLayoutNameInGui is type of QVBoxLayout
gui.cpp (delete QWidgets in a QLayout that is in the GUI)
//delete all buttons in layout
QLayoutItem *child;
while((child = parentUiWindow->aLayoutNameInGui->layout()->takeAt(0)) != 0) {
//parentUiWindow->aLayoutNameInGui->removeWidget(child->widget()); //already removed by ->takeAt()
//child->widget()->setParent(NULL);
delete child->widget();
delete child;
}

When you look at the memory usage with the top command, you can get false positives. As stated by some programmer dude, the OS doesn't always release the allocated memory from your process when you call delete on some objects.
However, you are creating two objects in your QCustomPushButton_withinIcon_LeftAndRight constructor with new:
mSVG (new svg),
mGradient (new gradient)
but you never seem to destroy these objects. So you might have a memory leak there.

Related

Passing arguments to a slot in qt5 c++ [duplicate]

This question already has answers here:
Passing an argument to a slot
(6 answers)
Closed 6 years ago.
I'm creating a ToDo list app in c++ qt. When a plus button is pressed, it adds a QHBoxLayout containing a QLabel and a QToolButton to a vertical layout inside my gui, so I get new boxes with the 'ToDos' inside them, and buttons to remove them next to them. I have set up the various widgets inside my slot which is called when the add button is clicked. However, I need to pass them as arguments to the slot which is called when a remove button is pressed. I have researched already, and all I have found is QSignalMapper. However, I cannot find any cases close enough to mine to replicate, and I have read it only works with certain arguments, and not the three I need (QHBoxLayout, QLineEdit and QToolButton).
Some of the code for the slot which is called when the 'add' button is pressed is:
//Creates a read only LineEdit which the user will add
QLineEdit *toDoBox = new QLineEdit(this);
toDoBox->setText(ui->lineEdit->text());
toDoBox->setReadOnly(true);
//Creates a new X button for removal of ToDo's
QToolButton *removeButton = new QToolButton;
removeButton->setText("X");
//Adds a horizontal layout with the ToDo and the remove button in it, to keep them together
QHBoxLayout *toDoLayout = new QHBoxLayout;
toDoLayout->addWidget(toDoBox);
toDoLayout->addWidget(removeButton);
//Removes a ToDo when the remove button is clicked
connect(removeButton, SIGNAL(clicked()), this, SLOT(on_removeButton_clicked()));
My code is hosted on GitHub if you want to see the whole project:
https://github.com/DanWilkes02/ToDoList
Thanks for bearing with me- I struggle explaining things that are so clear in my head!
If I understand well your problem, you want to get the allocated objects which represent a todo in order to free them and to update your View.
You could achieve this by simply wrapping your QLineEdit, QToolButton and QHBoxLayout objects into a class, and use a container (a vector for instance) in your ToDoList class. That way, you push_back your "todo object" each time you press the on_toolButton_clicked method.
Then, you simply have to use a signal with an index triggering an on_delete_todo slot which deletes a "todo object" from your vector and update the view.
Also, take a look at this Qt Model-View Programming
Here is a sample (tested and working under QT5):
Your Todo Widget
#ifndef TODOVIEW_H
#define TODOVIEW_H
#include <QString>
class QLineEdit;
class QToolButton;
class QHBoxLayout;
#include <QWidget>
class TodoView : public QWidget
{
Q_OBJECT
private:
QLineEdit* frame;
QToolButton* removeButton;
QHBoxLayout* toDoLayout;
int index;
public:
TodoView(const QString& what, int index, QWidget* parent);
~TodoView();
inline void setIndex(int i) { index = i; }
inline int getIndex(){ return index; }
private slots:
void emitIndex();
signals:
void selectedIndex(int);
};
#endif // TODOVIEW_H
#include "todoview.h"
#include <QLineEdit>
#include <QToolButton>
#include <QHBoxLayout>
TodoView::TodoView(const QString& what, int index, QWidget* parent) : QWidget(parent), index(index)
{
frame = new QLineEdit(this);
frame->setText(what);
frame->setReadOnly(true);
removeButton = new QToolButton(this);
removeButton->setText("X");
toDoLayout = new QHBoxLayout(this);
toDoLayout->addWidget(frame);
toDoLayout->addWidget(removeButton);
connect(removeButton, SIGNAL(clicked()), this, SLOT(emitIndex()));
}
TodoView::~TodoView() {}
void TodoView::emitIndex()
{
emit selectedIndex(getIndex());
}
Your MainWindow
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <vector>
class TodoView;
class QVBoxLayout;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void addTodo();
void delTodo(int);
private:
Ui::MainWindow* ui;
QVBoxLayout* vBoxLayout;
std::vector<TodoView*> todoView;
int max = -1;
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "todoview.h"
#include <QVBoxLayout>
#include <QAction>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
vBoxLayout = new QVBoxLayout(centralWidget());
QAction* add = new QAction(ui->mainToolBar);
ui->mainToolBar->addAction(add);
connect(add, SIGNAL(triggered()), this, SLOT(addTodo()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::addTodo()
{
if(max > 9)
{
// Error msg.
}
else
{
TodoView* tdV = new TodoView("Yolo", max, centralWidget());
connect(tdV, SIGNAL(selectedIndex(int)), this, SLOT(delTodo(int)));
vBoxLayout->addWidget(tdV);
todoView.push_back(tdV);
++max;
}
}
void MainWindow::delTodo(int i)
{
// check if i < todoView.size().
delete todoView.at(i);
// update vector indexes !!!
--max;
}
I have edited this piece of code rapidly, I may have made several mistakes, but you have an idea of at least one solution.
It is also possible to use a fixed size for the vector (better solution). Setting the TodoView deleted objects to nullptr in the vector and search for nullptr when you want to add new Todo view components:
In the MainWindow constructor
todoView.reserve(10);
for(std::size_t i = 0; i < 10; ++i)
{
todoView[i] = nullptr;
}
In the addTodo Slot
// Do not use push back.
// retrieve the max index.
// if < 10
for(std::size_t i = 0; i < todoView.size(); ++i)
{
if(todoView[i] == nullptr)
{
// allocate TodoView and affect it to the i° element
}
}
In the delTodo slot
delete todoView[i];
todoView[i] = nullptr;
Using a vector of pair is also possible (a pair of int TodoView).
I can think of two approaches you can go with.
Using QObject::sender().
Using QSignalMapper.
QObject::sender()
This static method returns QObject * of the sender object who emitted the signal. In this case QToolButton, so you can use this reference to find its parent (i.e. QHBoxLayout) and its sibling (i.e. QLineEdit).
QSingalMapper
First define a list for saving references to all rows, then assign each row an unique identifier (e.g. row number), then according to the official documentation's example use this identifier as QSignalMapper key for each button. Now if user clicks a button, in your slot, you will be given the same identifier, use it and lookup the whole row in the list of rows.

Can I show an object (subclass of QLabel ) created as needed? (the issue I have is deleting it)

I have created a label to show some info.. the way it works, if I need to show an image I set it on the label.
class PreviewLabel : public QLabel
{
public:
explicit PreviewLabel(QWidget *parent = 0) :
QLabel(parent)
{
this->setWindowFlags(Qt::Popup);
}
protected:
virtual void leaveEvent(QEvent *)
{
this->close();
}
};
void showImageSlot()
{
QImage preview = getSomeImage();
if(preview.isNull())
return;
PreviewLabel* previewShow = new PreviewLabel();
previewShow->setPixmap(QPixmap::fromImage(preview));
previewShow->show();
}
This has a memory leak though - the PreviewLabel object never gets deleted.
(Which is probably why it works.)
I tried to add a previewShow->deleteLater(); after the show() and the image didn't show.
I also tried not to make it a pointer, but the image didn't show (see code below).
Technically it shows but gets killed instantly... because the program reaches the end of the showImageSlot function.
void showImageSlot()
{
QImage preview = getSomeImage();
if(preview.isNull())
return;
PreviewLabel previewShow;
previewShow.setPixmap(QPixmap::fromImage(preview));
previewShow.show();
}
So the previewShow object needs a lifetime longer than that function... but how ?
I preferred not to make it a member variable - but I also tried to make it one (deleted in class destructor).
if(m_previewShow == NULL)
m_previewShow = new PreviewLabel();
m_previewShow->setPixmap(QPixmap::fromImage(preview));
m_previewShow->show();
This... crashes on first call.
How can I show it, preferably without having to make it a class member variable ?
I was thinking of making the object kill itself in the leaveEvent - is that possible ?
Edit - Two people have suggested the same thing - I add this->deleteLater(); in the leaveEvent.
I tried and the program seems to run fine -
But I also ran it in Valgrind and the program crashes, the log file says
--15595-- memcheck GC: 32768 nodes, 30902 survivors ( 94.3%)
--15595-- memcheck GC: increase table size to 65536
==15595== Invalid free() / delete / delete[] / realloc()
==15595== at 0x402ACFC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==15595== by 0x805D495: MainWindow::~MainWindow() (mainwindow.cpp:234)
==15595== by 0x4E7ED02: qDeleteInEventHandler(QObject*) (in /usr/lib/i386-linux-gnu/libQtCore.so.4.8.1)
(the referenced line number is at the end of the destructor of the main class containing showImageSlot() function)
Ok, list all variants.
First is as is initial code. Just add deleteLater() to leaveEvent() and the label will be destroyed when mouse leaves the widget.
class PreviewLabel : public QLabel
{
public:
explicit PreviewLabel(QWidget *parent = 0) :
QLabel(parent)
{
this->setWindowFlags(Qt::Popup);
}
protected:
virtual void leaveEvent(QEvent *)
{
this->close();
this->deleteLater();
}
};
void showImageSlot()
{
QImage preview = getSomeImage();
if(preview.isNull())
return;
PreviewLabel* previewShow = new PreviewLabel();
previewShow->setPixmap(QPixmap::fromImage(preview));
previewShow->show();
}
Second, with member variable that destroys itself after mose leaving the widget. Note, that the member variable must be guarded with QPointer so you always know if it exists or not.
class PreviewLabel : public QLabel
{
public:
explicit PreviewLabel(QWidget *parent = 0) :
QLabel(parent)
{
this->setWindowFlags(Qt::Popup);
}
protected:
virtual void leaveEvent(QEvent *)
{
this->close();
this->deleteLater();
}
};
// somewhere in member declaration
// this should be QPointer in order to detect label destroy
QPointer<QLabel> m_previewShow;
...
void showImageSlot()
{
if(!m_previewShow)
m_previewShow = new PreviewLabel();
m_previewShow->setPixmap(QPixmap::fromImage(preview));
m_previewShow->show();
}

Do I have to delete these pointers?

This is the MainWindow class which I call and use the function show() to make it visible to the user.
class MainWindow : public QMainWindow
{
Q_OBJECT
QWidget *centralWidget;
QGridLayout* gridLayout;
QGridLayout* infoBoxLayout;
QHBoxLayout* buttonGroup;
QHBoxLayout* subCategoryLayout;
//... more widgets
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
void setupUi();
void setupConnections();
private slots:
void add();
void edit();
void remove();
void find();
void clearAll();
void screenshotDesktop();
void screenshotApp();
void currentSubCategoryChanged( const QString& );
void curretCategoryChanged( const int );
void keyPressEvent( QKeyEvent * );
};
I created for each widget (those pointers after the macro Q_OBJECT) a new object on the heap with new. However, I did not delete them anywhere in the program. Does this cause a memory leak in Qt? Or does something from Qt delete them automatically when destroying the class?
If a widget has a parent set, then Qt will handle deleting the widget.
In the case of a MainWindow, when you close it, the MainWindow and its children will be cleaned up, so pass the parent to the widget's constructor: -
QHBoxLayout* buttonGroup = new QHBoxLayout(this); // where this is the parent (MainWindow)
If you create a Widget such as this: -
QHBoxLayout* buttonGroup = new QHBoxLayout;
And haven't passed in the parent, then it will not be cleaned up and you'll have to handle that yourself.
if you add them to the gui hierarchy then they will be cleaned up when the MainWindow is deleted
this is because parents assume ownership over their children (which is set with the various adds of the gui)
so a this->add(centralWidget); will call centralWidget->setParent(this); which will let centralWidget be deleted when MainWindow is deleted
you are free to delete QObjects yourself but beware dangling pointers (QPointer will help here). though I suggest using deleteLater() to ensure no strange behavior when a pointer still lives on the stack.
for more info about the object tree see here
The automatic memory management through parent-child relationships is done by the QObject. QWidget happens to be a QObject, and it so happens that widgets that have parent widgets have the same underlying QObjects as parents.
A QObject with children automatically deletes its children in its destructor.
A QObject or a QWidget may be adopted by another object. For example, adding widgets to a layout will automatically reparent them to the widget the layout is set on. Even if a layout doesn't have a widget set on it yet, the reparenting will be done at the time you add the layout to a widget (or to a layout that has a widget set). It's pretty clever and saves a lot of typing and reduces possibilities for mistakes.
The idiomatic, minimum-typing way of adding widgets to another widget is:
MyWidget() {
QLayout * layout = new QHBoxLayout(this); // set a layout on this widget
layout->addWidget(new QLabel("foo")); // the label is reparented to this
layout->addWidget(new QPushButton("bar")); // the button is reparented to this
}

QDialog deletes its QLayout?

I have a Qt4 application, that has numerous dialogs. I am curious to know whether or not a QDialog deletes its Layout. Take for example:
class MyDialog : public QDialog {
public:
MyDialog(QWidget* _parent = 0) : QDialog(_parent) {
//instantiate some widgets
m_layout = new QGridLayout(this);
setLayout(m_layout)
//add some widgets to the layout
}
~MyDialog() {
//Do I need this code? or will the parent delete the layout?
//delete m_layout;
}
private:
QGridLayout* m_layout;
}
So do I need to write my own destructor? or will the QDialog take care of the memory management of m_layout?
The QDialog will delete the QLayout upon destruction. You do not need to delete the layout in the destructor.
See also: http://doc-snapshot.qt-project.org/qt5-stable/qtwidgets/qwidget.html#setLayout

Accessing a widget from outside of its class?

In Qt Creator, I have a couple of widgets declared like so:
Header File:
class MapViewer : public QGraphicsView
{
Q_OBJECT
public:
explicit MapViewer(QGraphicsScene *scene, QWidget *parent = 0);
~MapViewer();
public slots:
void mousePressEvent(QMouseEvent *event);
};
// Declaration for the map editor window.
class MapEditor : public QMainWindow
{
Q_OBJECT
public:
explicit MapEditor(QWidget *parent = 0);
~MapEditor();
public:
QLayout *editorLayout;
QPushButton *btn;
QGraphicsScene *mapScene;
MapViewer *mapView;
private:
Ui::MapEditor *ui;
};
CPP File:
MapEditor::MapEditor(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MapEditor)
{
ui->setupUi(this);
this->setWindowTitle("2DXY :: Map Editor");
this->setGeometry(10,10,1170,750);
editorLayout = new QVBoxLayout; // Create a new layout
this->setLayout(editorLayout); // Set the widget's layout to our newly created layout.
mapScene = new QGraphicsScene(); // Create a new graphics scene to draw upon.
mapView = new MapViewer(mapScene,this); // Create a new graphics view to display our scene - set its parent to 'this' so that it doesn't open in a new window.
mapView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
mapView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
mapView->setGeometry(20,20,1178,546); // Width first, then height.
AND:
void MapViewer::mousePressEvent(QMouseEvent *event)
{
// Show an empty message box, just to check that the event handler works!
QMessageBox *notification = new QMessageBox();
notification->show();
notification->exec();
// Some how access the same QGraphicsScene and View (mapScene, mapView) as above, so
// I can update their contents on the open form / window.
}
And as you can see, I wish to access the Graphics Scene again to update it, then redraw it (or whatever). But I'm not able to access the graphics scene at all, despite a few hours of trial and error with declaring widgets in different ways.
I know that the listener itself works, because if it's set to open a new message box, or output to the debug window, then it works, it's just that I can't access the widgets I've already defined.
I feel that there is a (relatively) easy solution to this problem, and that I'm just missing something obvious.
You passed the QGraphicsScene to your MapRender object's constructor. What do you do with the scene within its constructor? Ideally, you should be storing it as a data member of MapRender. For example:
class MapRender {
public:
MapRender(QGraphicsScene* scene)
: scene_(scene)
{
}
public slots:
void mousePressEvent(QMouseEvent *event);
private:
QGraphicsScene* scene_;
}
Now in your implementation of mousePressEvent, you can access to the scene member:
void MapRender::mousePressEvent(QMouseEvent *event) {
int CursorX = event->globalX();
int CursorY = event->globalY();
QGraphicsRectItem *clickedBox = new QGraphicsRectItem(40,40,32,32);
clickedBox->setBrush(QBrush(Qt::blue));
scene_->addItem(clickedBox);
}
Keep in mind you should ideally be putting the implementation of the constructor in your cpp file, but my example does it in the declaration for brevity.
void MapViewer::mousePressEvent(QMouseEvent *event)
{
// Show an empty message box, just to check that the event handler works!
QMessageBox *notification = new QMessageBox();
notification->show();
notification->exec();
// To add something whenever the user clicks, you don't need the view,
// just the scene.
scene()->addItem( new MyItem() );
}
Remember MapViewer derives from QGraphicsView and the view must know about the scene it belongs to - so it has a scene() method to return it, which you inherited.