I am a QT and C++ newbie and I am trying to bind QTreeView to a QStandardItemModel. The code below compiles, but the widget doesn't get populated with the childItem. Anyone know why?
#include <QStandardItemModel>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QTreeView *qtree = ui->treeView;
QStandardItemModel model;
QStandardItem *item = model.invisibleRootItem();
QStandardItem *childItem = new QStandardItem(QString("child item"));
item->appendRow(childItem);
qtree->setModel( &model );
}
You create the instance of the model, and then promptly destroy it when the MainWindow constructor returns. Your model is a local variable!
You want the model to be a value member in the widget. I also suggest to disregard the silly Qt Creator template code and not hold the ui member by pointer, but directly by value. The extra pointer indirection is pointless and a premature pessimization. It made sense 15 years ago when compilers and disks were 1-2 orders of magnitude slower than they are now.
// mainwindow.h
#include <QMainWindow>
#include "ui_mainwindow.h"
class MainWindow : public QMainWindow {
Ui::MainWindow ui; // by value!
QStandardItemModel model; // by value!
public:
explicit MainWindow(QWidget * parent == nullptr);
// Let the compiler do the hard work: the default destructor is just fine!
}
// mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget * parent) :
QMainWindow{parent}
{
ui.setupUi(this);
auto root = model.invisibleRootItem();
root->appendRow(new QStandardItem{QStringLiteral("child item")});
ui.treeView->setModel(&model);
}
The code in my posting doesn't work because the model variable is destroyed when the constructor of the MainWindow class returns.
Related
I want to display the content I wrote in the QLineEdit widget after clicking on the QPushButton with the function ShowMessage(). How can I access that content outside of the constructor?
Tried putting the QLineEdit object I created in to a private variable.
My CPP file
#include "manualwidget.h"
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QHBoxLayout>
#include <QMessageBox>
ManualWidget::ManualWidget(QWidget *parent) : QWidget(parent)
{
QLabel *label = new QLabel(this);
QLineEdit *lineEdit = new QLineEdit(this);
QPushButton *pushButton = new QPushButton(this);
QHBoxLayout *layout = new QHBoxLayout();
label->setText("Enter text:");
pushButton->setText("Ok");
layout->addWidget(label);
layout->addWidget(lineEdit);
layout->addWidget(pushButton);
setLayout(layout);
connect(pushButton,SIGNAL(clicked()),this ,SLOT(showMessage()));
connect(lineEdit, SIGNAL(returnPressed()),this, SLOT(showMessage()));
}
void ManualWidget::showMessage(){
QMessageBox::information(this, "Message", "The text entered in the "
"manual widget window is:\n" + m_lineEdit->text());
}
My header file
#ifndef MANUALWIDGET_H
#define MANUALWIDGET_H
#include <QWidget>
#include <QLineEdit>
class ManualWidget : public QWidget
{
Q_OBJECT
public:
explicit ManualWidget(QWidget *parent = nullptr);
signals:
public slots:
private slots:
void showMessage();
private:
QLineEdit m_lineEdit;
};
#endif // MANUALWIDGET_H
#eyllanesc suggestion might work, but it should not be a prefered approach. Qt has its' own memory model, and usage of it should be prefered. Thus, "QLineEdit m_lineEdit" should be changed to e.g. "QLineEdit* m_lineEdit", and in constructor you should initialize it in the following way:
// Instance of the QLineEdit will be owned by the ManualWidget which is part of Qt memory management now.
m_lineEdit = new QLineEdit(this);
Then, the following line:
layout->addWidget(lineEdit);
Can be changed to:
layout->addWidget(m_lineEdit);
Why is it bad to use "QLineEdit m_lineEdit"? Because Qt might want to destroy this object for some reason (you still can call m_lineEdit.deleteLater()) and you might end up in the "double destruction" situation which would result in app being crashed. You can say that in this way conflicting memory models would interact.
I created a small prototype which contains a QGraphicsView that I bind to a GraphicsScene to which I can add or remove QGraphicsTextItem. Here is the the cpp file that does the job
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QGraphicsScene>
#include <QGraphicsTextItem>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QGraphicsScene* scene = new QGraphicsScene();
ui->graphicsView->setScene(scene);
connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(addGraphicsItem()));
connect(ui->pushButton_2,SIGNAL(clicked()),this,SLOT(removeGraphicsItem()));
}
MainWindow::~MainWindow()
{
auto scene = ui->graphicsView->scene();
ui->graphicsView->setScene(nullptr);
delete scene;
delete ui;
}
void MainWindow::addGraphicsItem()
{
QGraphicsTextItem* item = new QGraphicsTextItem("jgfkdljkdj");
_items.push_back(item);
ui->graphicsView->scene()->addItem(_items.back());
}
void MainWindow::removeGraphicsItem()
{
auto item = _items.back();
ui->graphicsView->scene()->removeItem(item);
_items.erase(_items.end()-1);
delete item;
}
I have trouble to understand how to manage safely my memory when creating/deleting the scene and/or its underlying items. Reading many posts about this such as this one, I came up to the aformentionned code but I have the feeling that it is overkilling code and that Qt might do the job without it. Is that really the way to do especially in MainWindow::removeGraphicsItem slot when removing and deleting one item from the scene and MainWindow::~MainWindow destructor when deleting the scene ?
The simplest thing to do is to let the language and the framework manage the memory for you.
Store QGraphicsScene by value, and leverage the fact that the scene is a resource manager, tightly coupled to the items. It manages the lifetime of the items and guarantees that no items will outlive the scene, i.e. it will take care of disposing of any items that weren't disposed before its destructor runs.
To remove an item from the scene, or from a parent item, simply delete it. The item will inform the scene and any parents that it's about to vanish, and the scene will remove it from its list of items. That's how QObject memory management works as well.
You may also wish to use QGraphicsScene::items or QGraphicsItemGroup::childItems instead of managing the object list manually. Using a manual list requires paying attention to synchronizing the list's contents with object lifetimes. A scene or an item group does it automatically.
If were to write it, I'd do it as follows. I also show how to mix different item lifetimes in a scene, and how to hold items by value.
// mainwindow.h
#pragma once
#include "ui_mainwindow.h"
#include <QGraphicsScene>
class MainWindow : public QMainWindow {
Q_OBJECT
Ui::MainWindow ui;
QGraphicsScene m_scene;
QGraphicsTextItem m_text{tr("foo")};
QGraphicsItemGroup m_dynamicItems;
template <typename T, typename ...Ar> T* newItem(Ar&&... args);
public:
MainWindow(QWidget *parent = {});
Q_SLOT void addItem();
Q_SLOT void removeItem();
Q_SLOT void removeAllItems();
};
// mainwindow.cpp
#include "mainwindow.h"
#include <utility>
template <typename T, typename ...Ar>
T* MainWindow::newItem(Ar&&... args) {
return new T{&this->m_dynamicItems, std::forward<Ar>(args)...};
}
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
ui.setupUi(this);
ui.graphicsView->setScene(&m_scene);
m_scene.addItem(&m_text);
m_scene.addItem(&m_dynamicItems);
auto const clicked = &QPushButton::clicked;
connect(ui.pushButton, clicked, this, &MainWindow::addItem);
connect(ui.pushButton2, clicked, this, &MainWindow::removeItem);
}
void MainWindow::addItem() {
newItem<QGraphicsTextItem>(tr("jfslkdfjd"));
}
void MainWindow::removeItem() {
auto const &items = std::as_const(m_dynamicItems.childItems());
if (!items.isEmpty())
delete items.back();
}
void MainWindow::removeAllItems() {
for (auto *item : std::as_const(m_dynamicItems.childItems()))
delete item;
// or (deprecated but you may run into such code)
qDeleteAll(std::as_const(m_dynamicItems.childItems()));
}
I am newbie to Qt 5.1.0. My problem with Qt is showed directly as follows. Data from serial port is received by readdata(), then call the draw() in mainwindow.cpp to deliver the data to newpaint() in paint.cpp. Two classes are created in this project:class mainwindow is mainly responsible for receiving data from serial port,class paint is for drawing the data (actually the data is coordinates of a mathematical function) on the scroll area widget which is on the mainwindow.
But in paint.cpp, paintevent is not invoked by update().UpdateEnabled() returns true depicting that the problem doesn't exist in the use of update().
Main part is pasted below.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
QPainterPath linepath;
linepath.moveTo(0,0);
ui->setupUi(this);
paintwiget=new paint(linepath,ui->scrollArea);
ui->scrollArea->setWidget(paintwiget);
paintwiget->show();
ui->closeserial->setEnabled(false);
ui->sendData->setEnabled(false);
serial=new QSerialPort(this);
connect(serial,SIGNAL(readyRead()),this,SLOT(readData()));
}
void paint::newpaint(qreal *aa1, int len1)
{
if (!aa.isEmpty())
aa.clear();
int i;
for (i=0;i<len1;i++)
aa.append(aa1[i]);
// qDebug()<<aa.size();
//qDebug()<<"aa[0:3]"<<aa[0]<<" "<<aa[1]<<" "<<aa[2]<<" "<<aa[3];
len=len1;
// this->setVisible(true);
qDebug()<<this->isVisible();//now returns true
this->update();
//qDebug()<<"okla";
}
void paint::paintEvent(QPaintEvent *)
{
qDebug()<<"ok~~";
QPointF currentpoi;
QPainter painter(this);
int ii=0;
qDebug()<<len;
for (ii=0;ii<len/2;ii++)
{
currentpoi=path.currentPosition();
qDebug()<<"currentpoi"<<currentpoi;
path.moveTo(currentpoi);
path.lineTo(aa[2*ii],aa[2*ii+1]);
// painter.setRenderHint(QPainter::Antialiasing);
painter.drawPath(path);
}
}
paint.h
#include <QApplication>
#include <QWidget>
#include <QPainter>
#include <QTabWidget>
//#include <mainwindow.h>
class paint : public QWidget
{
public:
explicit paint(QPainterPath &path,QWidget *parent=0);
void newpaint(qreal *,int);
QVector <qreal>aa;
int len;
QWidget *parent;
private:
QPointF point;
QPainterPath path;
protected:
void paintEvent(QPaintEvent *event);
};
Hard to say for sure that this is the only issues you are facing, but there are at least three issues:
Issue 1
The following code tells me that you have a member of MainWindow called paint * paintwiget:
void MainWindow::draw(qreal *aa,int len)
{
// ...
paintwiget->newpaint(dist,len);
}
Then, this code tells me that you are creating a new temporary variable of the same name:
MainWindow::MainWindow(QWidget *parent)
{
// ...
paint *paintwiget=new paint(linepath,ui->scrollArea->viewport());
}
The compiler should have probably given you a warning for this. Replace this line by:
paintwiget = new paint(linepath,ui->scrollArea->viewport());
(notice the lack of paint *), and tell me if this solves the problem.
Issue 2
You have to change this:
void paint::newpaint(qreal *aa, int len)
{
// ...
aa[i]=aa[i];
len=len;
}
by this:
void paint::newpaint(qreal *aa_new, int len_new)
{
// ...
aa[i]=aa_new[i];
len=len_new;
}
Otherwise, your aa[i]=aa[i] has no effect: it replaces the parameter of the method by itself, instead of replacing your member by the parameter of the method. Using a parameter with the same name than a member "hides" the member: you can't access it anymore, either at the left hand side or the right hand side of an assignment.
Issue 3
I'm not quite sure what you are doing with scrollArea->viewport() in the constructor of paint, but something tells me you are doing it wrong. The constructor of paint should NOT use it, but instead, you should do:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QPainterPath linepath;
paintwiget = new paint(linepath); // Do NOT pass viewport in parameter
ui->scrollArea->setWidget(paintwiget);
paintwiget->show();
// ...
}
I know there are a lot of questions regarding qt memory management but i couldn't find an answer on mine.
So for the qt widgets, if you pass this(which is the parent widget) as parameter at the object creation, the parent widget takes ownership of it and deletes it when the parent dies.
The same thing happens when you add a widget to a layout and set that layout on the widget.
But what happens if i declare a pointer to a QColor object for example? QColor can't be added as child for another widget or can't be added to a layout. Will the memory be released when the widget dies or i need to delete it manually in the destructor?
Here an example...what happens with m_pColor when Widget will be destroyed? Will it be destroyed or there will be a memory leak?
Sorry for any mistakes in the code, i didn't compile it, i just wrote it here as example.
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
class QHBoxLayout;
class QPushButton;
class QColor;
class Widget : public QWidget
{
Q_OBJECT
public:
Widget( QWidget *parent = NULL );
public:
QHBoxLayout *m_pLayout;
QPushButton *m_pButton;
QColor *m_pColor;
};
#endif // WIDGET_H
#include "widget.h"
#include <QHBoxLayout>
#include <QPushButton>
#include <QColor>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
m_pLayout = new QHBoxLayout;
m_pButton = new QPushButton( tr( "Button" ) );
m_pLayout->addWidget( m_pButton );
m_pColor = new QColor(0, 0, 0, 255);
setLayout( m_pLayout );
}
What you suggested is valid only for classes inheriting QObject (ie having QObject as one of their superclass). These properties don't apply to other classes (even built-inQt ). As QColor not a subclass of QObject, the object referenced by m_pColor will not be destroyed when Widget is destroyed. You will have to do it manually.
There is no reference for parent widget to destroy the m_pColor. you can use Valgrind to check for memory leak on this executable.
I have built an app in Qt that contains two buttons: an exit button and an import button. When the import button is pushed, a list of buttons is shown in a scrollarea on the screen (the file loggers.csv contains the data 1;2;3;4;5;).
It all works fine, but when I push the exit button (which of course should close everything), the app is not stopped properly (the stop button of Qt is still active, and the play button isn't). When I run the debugger and push the exit button it gives an error: Invalid address specified to RtlFreeHeap( 0ADF0000, 0028FE40 ). Can anybody help me?
main
#include <QtGui/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.showFullScreen();
return a.exec();
}
Mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtGui>
#include "logger.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
QPushButton exit_btn;
QPushButton import_btn;
private slots:
void createMenus();
void exit();
void import();
private:
int window_width;
int window_height;
int numLoggers;
int numSelected;
QVector<Logger*> loggers;
QScrollArea * scroll_area;
QVBoxLayout scrollLayout;
QWidget viewport;
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
Mainwindow.cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QtGui"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
window_width = QApplication::desktop()->width();
window_height = QApplication::desktop()->height();
createMenus();
connect(&exit_btn,SIGNAL(clicked()),this,SLOT(exit()));
connect(&import_btn,SIGNAL(clicked()),this,SLOT(import()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::createMenus()
{
import_btn.setParent(ui->centralWidget);
import_btn.setGeometry(400,300,100,100);
import_btn.setText("IMPORT");
exit_btn.setText("EXIT");
exit_btn.setParent(ui->centralWidget);
exit_btn.setGeometry(window_width-50,12,32,32);
viewport.setLayout(&scrollLayout);
viewport.resize(0,0);
scroll_area = new QScrollArea(ui->centralWidget);
scroll_area->setGeometry(0,66,317,window_height-116);
scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scroll_area->setWidget(&viewport);
scroll_area->setGeometry(0,97,317,window_height-228);
scrollLayout.setMargin(0);
scrollLayout.setSpacing(0);
}
void MainWindow::exit()
{
close();
qApp->quit();
}
void MainWindow::import()
{
numSelected=0;
QFile f("Loggers3.csv");
if (f.open(QIODevice::ReadOnly))
{
numLoggers=0;
QString data;
data = f.readAll();
QStringList vals = data.split(';');
while(vals.size()>=1)
{
Logger * logger = new Logger;
logger->setNumber(vals[0].toInt());
vals.removeAt(0);
loggers<<logger;
numLoggers++;
}
f.close();
for(int i=0; i<numLoggers;i++)
{
loggers[i]->createButtons();
scrollLayout.addWidget(loggers[i]->button);
}
viewport.resize(367,numLoggers*60);
}
}
logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <QtGui>
class Logger : public QWidget
{
Q_OBJECT
public:
explicit Logger(QWidget *parent = 0);
~Logger();
int number;
QLabel num;
QToolButton * button;
bool checked;
signals:
public slots:
void setNumber(int number);
void createButtons();
};
#endif // LOGGER_H
logger.cpp
#include "logger.h"
#include <QtGui>
Logger::Logger(QWidget *parent) :
QWidget(parent)
{
button = new QToolButton;
button->setCheckable(true);
button->setMinimumSize(317,60);
button->setStyleSheet("QToolButton{background-image: url(images/btn_bg); border:none}");
}
Logger::~Logger()
{
}
void Logger::setNumber(int logNumber)
{
number=logNumber;
}
void Logger::createButtons()
{
QLayout * layout = new QHBoxLayout;
QSpacerItem *spacer = new QSpacerItem(120, 31, QSizePolicy::Maximum, SizePolicy::Maximum);
num.setStyleSheet("color: white; font: bold 16px");
num.setText(QString::number(number));
layout->addWidget(&num);
layout->addItem(spacer);
button->setLayout(layout);
}
I'm not entirely certain about what you are trying to achieve... but your problem lies with these two lines:
viewport.setLayout(&scrollLayout);
viewport.resize(0,0);
In the documentation for the QWidget class it states that:
If there already is a layout manager installed on this widget, QWidget
won't let you install another. You must first delete the existing layout manager (returned by layout()) before you can call setLayout() with the new layout.
This is where your problem lies. Don't believe me, add this check before those two lines of code.
if(layout()){
qDebug() << "Another layout exists";
}
Source: QVBoxLayout Class Reference
The QVBoxLayout class lines up widgets vertically.
This class is used to construct vertical box layout objects. See QBoxLayout for details.
The simplest use of the class is like this:
QWidget *window = new QWidget;
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);
layout->addWidget(button4);
layout->addWidget(button5);
window->setLayout(layout);
window->show();
First, we create the widgets we want in the layout. Then, we create the QVBoxLayout object and add the widgets into the layout. Finally, we call QWidget::setLayout() to install the QVBoxLayout object onto the widget. At that point, the widgets in the layout are reparented to have window as their parent.
Critical source of error in your project:
Widgets should be constructed on the heap because they will be deleted automatically when their parents are deleted. You have a custom widget class that you instantiate on the heap. The members should also go on the heap. Also, you should consider using the parent /child hierarchy in your GUI code to ensure proper memory management and proper deletion.
In my experience, if your program stops in RtlFreeHeap it is a good sign of memory corruption.
When calling
import_btn.setParent(ui->centralWidget);
centralWidget takes ownership of import_btn. That means, when centralWidget is deleted (which happens as part of delete ui;in your MainWindow's destructor), it will call delete on your member variable!
This leads to the reported memory corruption.
You need to allocate your QPushButton's dynamically, not as a plain member variable. So make them QPushButton*.
Here's how I did it from mainwindow.cpp, thanks to and this question: How to create a correct exit button in qt
QPushButton * quit_btn = new QPushButton(this);
quit_btn->setGeometry(540,440,93,27);
quit_btn->setText("Exit");
QObject::connect(quit_btn,SIGNAL(clicked()),qApp,SLOT(quit()));
Works flawlessly :D