Dynamically create/destroy custom widgets in Qt - c++

I'm just starting with Qt. I would like to make a GUI with custom widgets that can be used to display/edit my custom datatypes . I would also like to be able to dynamically create/destroy these widgets (without destroying the underlying data).
I've tried to accomplish this by storing a list of my data items (dataContainer) in the main Window class, and passing a pointer to a dataContainer to the constructor of my widget (customWidget), which stores this pointer. Then customWidget can alter the underlying data via this pointer.
The code below allows the user to repeatedly add dataContainer instances to the list and edit each of their "names". When I run this, everything works fine as long I don't actually edit the name, but if I do edit the name and then click the "Add" button, I get a segfault. This segfault occurs during the destructor of customWidget during delete myWidget; in Window::add().
Two questions:
Why does this code crash? Am I not allowed to dynamically create/destroy widgets like this?
Is there a more appropriate way to accomplish this?
I apologize if there's something basic I'm missing here, but I've been up-and-down lots of forums and tutorials, and not found anything helpful.
main.cpp
#include <QApplication>
#include "window.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window mainWindow;
mainWindow.show();
return app.exec();
}
window.h
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
#include "customwidget.h"
class Window : public QWidget {
Q_OBJECT
public:
Window();
public slots:
void add();
private:
// the main data structure
QList<dataContainer> dataList;
// the widget that displays the custom data
customWidget *myWidget;
QVBoxLayout *mainLayout;
QPushButton *addButton;
};
#endif
window.cpp
#include <QtGui>
#include "window.h"
Window::Window(){
// start with a single piece of data in the list
dataContainer newData;
dataList.append(newData);
// create layout container
mainLayout = new QVBoxLayout(this);
mainLayout->setAlignment(Qt::AlignTop);
// make the Add button, and connect its clicked() SIGNAL
// to our add() SLOT
addButton=new QPushButton(tr("Add"),this);
connect(addButton,SIGNAL(clicked()),this,SLOT(add()));
mainLayout->addWidget(addButton);
// create a custom widget to display our data and
// give it a pointer to the data it will display/modify
myWidget = new customWidget(this,&(dataList.last()) );
mainLayout->addWidget(myWidget);
setLayout(mainLayout);
}
void Window::add(){
// create a new piece of data, and add to the list
dataContainer newData;
dataList.append(newData);
// debug: show the current list
qDebug() << "Data List(" << dataList.size() << ")";
for (int i=0;i<dataList.size();i++){
qDebug() << dataList[i].getName().c_str();
}
// delete the old widget
delete myWidget;
// and make a new widget with the new data
myWidget = new customWidget(this,&(dataList.last()) );
mainLayout->addWidget(myWidget);
}
#include "window.moc"
customwidget.h
#ifndef CUSTOMWIDGET_H
#define CUSTOMWIDGET_H
#include <QWidget>
#include <QtGui>
#include <string>
class dataContainer {
private:
std::string name;
public:
dataContainer(){name="oooh";};
std::string getName()const{return name;};
std::string setName(const std::string& n){name=n;};
};
class customWidget : public QWidget {
Q_OBJECT
private:
dataContainer *data;
public slots:
void nameChangedSlot(const QString&);
public:
customWidget(QWidget *parent, dataContainer *d);
};
#endif
customwidget.cpp
#include "customwidget.h"
customWidget::customWidget(QWidget *parent, dataContainer *d) : QWidget(parent) {
// store a pointer to the data that we want to alter
data = d;
// create an edit box and initialize it with the data name
QVBoxLayout *mainLayout=new QVBoxLayout(this);
QLineEdit *edit=new QLineEdit(QString::fromStdString(data->getName()),this);
mainLayout->addWidget(edit);
connect(edit,SIGNAL(textChanged(const QString&)),this,SLOT(nameChangedSlot(const QString&)));
}
void customWidget::nameChangedSlot(const QString& name){
// alter the contents of our data based on the changed string
data->setName(name.toStdString());
}
#include "customwidget.moc"

A widget that is added to a layout can not be deleted directly. Instead, you can try below code which deletes a specific widget and corresponding layout item from layout:
QLayoutItem* item;
while ( ( item = mainLayout->takeAt( 0 ) ) != NULL )
{
if(item->widget() == myWidget){
delete item->widget();
delete item;
}
}

Related

How create method for get the child (QWebEngineView) of th curent view tab (QTabWidget)?

I'm trying to make some project with QTabWidget (a litle browser and a text editor with multiple tab like notepad++) but I'm stuck in 2 project when I try to edit a value of widget (QWebEngine or QTextEdit) inside of QTabWidget. This is the code for the litle browser project:
fp.h :
#ifndef FP_H
#define FP_H
#include <QMainWindow>
#include <QWebEngineView>
#include <QWidget>
#include <QLabel>
QT_BEGIN_NAMESPACE
namespace Ui { class fp; }
QT_END_NAMESPACE
class fp : public QMainWindow
{
Q_OBJECT
public:
fp(QWidget *parent = nullptr);
~fp();
public slots:
void newtab();
void deltab();
void newpage();
QWebEngineView *ap();
int cui();
private:
Ui::fp *ui;
QWebEngineView *webnav;
};
#endif // FP_H
fp.cpp
#include "fp.h"
#include "ui_fp.h"
fp::fp(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::fp)
{
ui->setupUi(this);
ui->in_url->setText("https://google.com");
}
fp::~fp()
{
delete ui;
}
void fp::newtab()
{
QWebEngineView *webnav = new QWebEngineView;
webnav->load(QUrl("https://google.com"));
webnav->setObjectName("webnav");
ui->in_url->setText("https://google.com");
ui->onglet->addTab(webnav,"Home");
}
int fp::cui()
{
return ui->onglet->currentIndex();
}
QWebEngineView *fp::ap()
{
return ui->onglet->currentWidget()->findChild<QWebEngineView *>("webnav");
}
void fp::deltab()
{
ui->onglet->removeTab(cui());
}
void fp::newpage()
{
QString use_url = ui->in_url->text();
ap()->load(use_url);
}
and there what look like the .ui
Image of .ui in Qdesigner
I try to make work the method "ap" it should return the QWebEngineView child of current viewed tab but when I call the slots "newpage" (it currently using "ap" method) it just crash the application. This slot is trigered when I enter a new URL in the QLineEdit in .ui
(before that I create a new tab whit a QWebEngineView inside whit the slot "newtab" )
void fp::newtab()
{
QWebEngineView *webnav = new QWebEngineView;
webnav->load(QUrl("https://google.com"));
webnav->setObjectName("webnav");
ui->in_url->setText("https://google.com");
ui->onglet->addTab(webnav,"Home");
}
So what wrong whit that line it the good method for get child widget of a QTabWidget? If it the good method what should be modified to make it work?
QWebEngineView *fp::ap()
{
return ui->onglet->currentWidget()->findChild<QWebEngineView *>("webnav");
}
Try something like:
QWebEngineView *view = qobject_cast<QWebEngineView *>(
this->ui->onglet->widget(0)
);
Note to put above somewhere in fp's methods (where you need the reference).
I could use ui->onglet->currentWidget(), but the difference is, that will not work once you have multiple tabs.

Close QWebEngineProcess.exe

I have a problem that, after closing a QDialog with web content, the QWebEngineProcess.exe process is not closing.
Here is a minimal example:
#include <QApplication>
#include <QWebEngineView>
#include <QWebEngineSettings>
#include <QDialog>
#include <QMainWindow>
#include <QLayout>
#include <QtGui>
#include <QPushButton>
class Dialog : public QDialog
{
public:
Dialog() : QDialog(nullptr)
{
resize(512, 512);
setAttribute(Qt::WA_DeleteOnClose);
auto verticalLayout = new QVBoxLayout(this);
verticalLayout->setSpacing(0);
verticalLayout->setContentsMargins(0, 0, 0, 0);
m_webView = new QWebEngineView(this);
verticalLayout->addWidget(m_webView);
}
void openPage(const QUrl& url)
{
m_webView->setUrl(url);
}
private:
QWebEngineView* m_webView;
};
class MainWindow : public QMainWindow
{
public:
MainWindow() : QMainWindow(nullptr)
{
resize(512, 512);
QPushButton* btn = new QPushButton("open web dialog", this);
connect(btn, &QPushButton::clicked, [this] ()
{
if (m_dialog == nullptr)
{
m_dialog = new Dialog();
m_dialog->openPage(QUrl("https://www.qt.io"));
m_dialog->show();
}
});
}
private:
QPointer<Dialog> m_dialog;
};
int main(int argc, char *argv[])
{
QCoreApplication::setOrganizationName("QtExamples");
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
I'm expecting that, after the dialog is closed, QWebEngineProcess.exe will be closed too, because I'm not using webengine anymore.
P.S. During opening WebPage, I have 2 QWebEngineProcess.exe. One is disappearing, but the second one left.
For any QObject instances (such as QDialog and QWebEngineView), the destructor of a parent object destroys all child objects. This means, for every object you new, it must either have a parent, or you must delete it manually, otherwise you are creating a leak and objects will remain open (not deleted).
You are creating a QWebEngineView object with the correct parent, but the parent (the Dialog) doesn't have a parent (you initialize the parent to nullptr). This means the Dialog would delete the QWebEngineView, except it never is deleted itself.
To fix it, either delete the Dialog (stored in m_dialog) manually in the MainWindow destructor (not recommended), or initialize it correctly with the MainWindow as its parent.
This may also be related to a bug in Qt 5.11 and 5.12. If this is the problem, the recommended solution is to add QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); at the top of your main() function (before declaring the QApplication).

Docking a QDockWidget when application starts?

I have a class that constructs a widget that I am able to dock into my main application. This class inherits QDockWidget. This allows me to dock the widget if I so please. However, I would like for this widget to be docked by default instead of showing up as a separate floating window.
To give you an idea of how this class is laid out, here is the header for it. Consider that I want to keep the log and showMessage functions.
Logger.h
class Logger : public QDockWidget
{
Q_OBJECT
public:
explicit Logger(QWidget* parent = 0);
void log(QString message);
~Logger();
private:
QWidget* dockWidgetContents;
QGridLayout* gridLayout;
QTextEdit* LoggerEdit;
void showMessage(QString &message);
};
#endif // MESSAGES_H
In the .cpp file for my main application, I use loggerWidget = new Logger(this);. This works, and when I open my application, the Logger widget pops up. I can then dock it into the sides of the main window on any side.
The problem I run into is getting this widget to be docked to the right side of the main window upon opening.
I've looked around for a solution and found that something similar to the following should work in the main window .cpp file. I just don't know how to implement it correctly.
LoggerWidget = new Logger(this);
this->setWidget(LoggerWidget);
addDockWidget(Qt::RightDockWidgetArea, LoggerWidget);
LoggerWidget->setFloating(false);
I think the issue is that since my Logger class is inheriting QDockWidget but isn't actually a QDockWidget, so I can't just do an addDockWidget in the main .cpp file.
How can I make this work while keeping the functionality that the class provides?
Assuming that the second pice of code:
LoggerWidget = new Logger(this);
this->setWidget(LoggerWidget);
addDockWidget(Qt::RightDockWidgetArea, LoggerWidget);
LoggerWidget->setFloating(false);
is within the constructor of a class that inherits from QMainWindow (otherwise you will not have functionalities such as addDockWidget), you can expect a weird behaviour if you execute this code because you are adding the same widget (LoggerWidget) to the central part of the window as well as to the dockable area (if it works you will see the exact same thing in both). Please find in the attached code a simple example of a QMainWindow with a central widget and a docked widget that inherits from QDockWidget:
Logger header
#ifndef LOGGER_H
#define LOGGER_H
#include <QDockWidget>
#include <QTextEdit>
class Logger : public QDockWidget
{
Q_OBJECT
public:
explicit Logger(QTextEdit* source, QWidget* parent = 0);
~Logger();
public slots:
void log(QString message);
private:
QTextEdit* LoggerEdit;
QTextEdit* texteditSource;
void showMessage(QString message);
};
#endif // LOGGER_H
Logger cpp
#include "logger.h"
Logger::Logger(QTextEdit* source, QWidget* parent):
QDockWidget(parent),texteditSource(source)
{
QDockWidget::setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
LoggerEdit = new QTextEdit();
LoggerEdit->setReadOnly(true);
QDockWidget::setWidget(LoggerEdit);
}
Logger::~Logger()
{
delete LoggerEdit;
}
void Logger::log(QString message)
{
showMessage(message);
}
void Logger::showMessage(QString message)
{
LoggerEdit->setText(message);
}
Main window widget header
#ifndef CUSTOMMAINWINDOW_H
#define CUSTOMMAINWINDOW_H
#include <QMainWindow>
#include <QVBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QTextEdit>
#include "logger.h"
class MainWindow: public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget* parent = 0);
private slots:
void buttonClicked();
private:
QTextEdit* textEdit;
Logger* logger;
QPushButton* button;
};
#endif // CUSTOMMAINWINDOW_H
Main window widget cpp
#include "custommainwindow.h"
MainWindow::MainWindow(QWidget* parent):
QMainWindow(parent)
{
// Set window title
QMainWindow::setWindowTitle("Simple Example");
// Create central text
textEdit = new QTextEdit;// text edit to get text for the docked widget
// Create update button
button = new QPushButton("Update dockable widget");
// Create Logger inherited from QDockWidget
logger = new Logger(textEdit);
QMainWindow::addDockWidget(Qt::RightDockWidgetArea,logger);
// Set central widget
QMainWindow::setCentralWidget(new QWidget);
QMainWindow::centralWidget()->setLayout(new QVBoxLayout);
QMainWindow::centralWidget()->layout()->addWidget(textEdit);
QMainWindow::centralWidget()->layout()->addWidget(button);
// Connect for update docked wiget
QObject::connect(button,SIGNAL(clicked()),this,SLOT(buttonClicked()));
}
void MainWindow::buttonClicked()
{
logger->log(textEdit->toPlainText());
}

C++ GUI QSpinBox's not showing up

My program compiles and displays my push buttons correctly but for some reason its not displaying the QSpinBox's. I'm pretty new at C++ qt GUI so any input would be greatly appreciated. I also checked if the spinners were being overlapped by the buttons but they were not.
//
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtGui>
#include <QPushButton>
#include <QLabel>
#include <QSpinBox>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void clearx();
void equalsx();
void addx();
void subtractx();
void multiplyx();
void dividex();
void firstnumberx();
void secondnumberx();
private:
QLabel *label;
QPushButton *equal;
QPushButton *clear;
QPushButton *equals;
QPushButton *add;
QPushButton *subtract;
QPushButton *multiply;
QPushButton *divide;
QSpinBox *spinner;
QSpinBox *spinner2;
};
#endif // MAINWINDOW_H
//
// mainwindow.cpp
#include "mainwindow.h"
#include <QTCore/QCoreApplication>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
label = new QLabel("0,this");
label -> setGeometry(QRect(QPoint(75,75),QSize(50,200)));
clear = new QPushButton("Clear", this);
clear -> setGeometry(QRect(QPoint(80,300),QSize(50,50)));
connect(clear,SIGNAL(released()),this,SLOT(clearx()));
equal = new QPushButton("Equal", this);
equal -> setGeometry(QRect(QPoint(110,300),QSize(50,50)));
connect(equal,SIGNAL(released()),this,SLOT(equalx()));
add = new QPushButton("Add", this);
add -> setGeometry(QRect(QPoint(140,300),QSize(50,50)));
connect(add,SIGNAL(released()),this,SLOT(addx()));
subtract = new QPushButton("Subtract", this);
subtract -> setGeometry(QRect(QPoint(170,300),QSize(50,50)));
connect(subtract,SIGNAL(released()),this,SLOT(subtractx()));
multiply = new QPushButton("Multiply", this);
multiply -> setGeometry(QRect(QPoint(200,300),QSize(50,50)));
connect(multiply,SIGNAL(released()),this,SLOT(multiplyx()));
divide = new QPushButton("Divide", this);
divide -> setGeometry(QRect(QPoint(230,300),QSize(50,50)));
connect(divide,SIGNAL(released()),this,SLOT(dividex()));
spinner = new QSpinBox;
spinner -> setGeometry(QRect(QPoint(130,150),QSize(50,50)));
connect(divide,SIGNAL(released()),this,SLOT(firstnumberx()));
spinner->setRange(1,10);
spinner2 = new QSpinBox;
spinner2 -> setGeometry(QRect(QPoint(190,150),QSize(50,50)));
connect(divide,SIGNAL(released()),this,SLOT(secondnumberx()));
spinner2->setRange(1,10);
}
void MainWindow::clearx() {}
void MainWindow::equalsx() {}
void MainWindow::addx() {}
void MainWindow::subtractx() {}
void MainWindow::multiplyx() {}
void MainWindow::dividex() {}
void MainWindow::firstnumberx() {}
void MainWindow::secondnumberx() {}
MainWindow::~MainWindow() {}
In order for a widget to display inside another widget, you need to set up a parent-child relationship. The simplest way to do this is to pass a pointer to the parent to the child's constructor as an argument.
For your QSpinBox objects, this is done like so:
spinner = new QSpinBox(this);
spinner2 = new QSpinBox(this);
The rest of your code is very laden with hard-coded geometries. For small GUIs, this might not be a problem but can become a bit of a nightmare to maintain for larger ones. Have you considered using QtDesigner to design your GUI? You might also find the layout management classes helpful in making your GUI designs better maintenance of positions and sizing of child widgets.
Add this as parameter of QSpinBox constructor call.

Unable to focus and give input to widgets in Qt5

Issue Resolved: Q_OBJECT macro was necessary and proper signal slot declarations are also important for any other handles.
I am unable to focus on any input type widgets like QTextEdit,QListWidget etc.
Note: There are no compile time or runtime errors.
Update: QSplitter is working properly! I have a QListWidget, whose items I click but they are highlighted only when I make the next move with the splitter.
I have a MainWindow class derived from QMainWindow as declared in main_window.h:
class MainWindow : public QMainWindow{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
//some other members like menu and statusbar here
}
I have another class called Stack derived from QWidget defined in stack.h:
class Stack: public QWidget{
public:
Stack(QWidget *parent=0);
//some other members
}
Constructor of Stack as in stack.cpp :
Stack::Stack(QWidget *parent):QWidget(parent){
main = new QHBoxLayout;
handle = new QSplitter;
setupList();
setupScreens();
//above functions add the widgets to the handle splitter
main->addWidget(handle);
setLayout(main);
}
If i open up this widget in a separate window from the MainWindow using test->show(), the things work as expected/as i want.
But doing this in the MainWindow constructor, renders it unclickable.
MainWindow::MainWindow(QWidget *parent):QMainWindow(parent){
Stack *test = new Stack(this);
//test->show();
setCentralWidget(test);
}
This is strange. Why am i not able to focus any widget that can take input e.g. QTextEdit,QListWidget or click any QPushButton widget?
Please compile following code, it was working..you are getting focus and edit on QTextEdit...
stack.h
#include <QWidget>
class Stack: public QWidget
{
Q_OBJECT
public:
Stack(QWidget *parent = 0);
~Stack(void);
};
stack.cpp
#include "Stack.h"
#include<QTextEdit>
#include<QHBoxLayout>
Stack::Stack(QWidget *parent):QWidget(parent){
QHBoxLayout* main = new QHBoxLayout;
QTextEdit *test = new QTextEdit;
main->addWidget(test);
//other things added to main layout
setLayout(main);
}
Stack::~Stack(void)
{
}
mainwindow1.h
#ifndef MAINWINDOW1_H
#define MAINWINDOW1_H
#include <QtGui/QMainWindow>
//#include "ui_mainwindow1.h"
class Mainwindow1 : public QMainWindow
{
Q_OBJECT
public:
Mainwindow1(QWidget *parent = 0, Qt::WFlags flags = 0);
~Mainwindow1();
private:
//Ui::Mainwindow1Class ui;
};
#endif // MAINWINDOW1_H
mainwindow1.cpp
#include "mainwindow1.h"
#include "Stack.h"
#include <QTextEdit>
Mainwindow1::Mainwindow1(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
Stack *test = new Stack;
setCentralWidget(test);
}
Mainwindow1::~Mainwindow1()
{
}
main.cpp
#include "mainwindow1.h"
#include <QtGui/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Mainwindow1 w;
w.show();
return a.exec();
}
If some1 would find this looking for answer on how to set focus on input widget from UI in QT5 you can just use:
ui->plainTextEdit->setFocus();