gtkmm - multiple windows/popup window - c++

I've been searching a lot and I still can't find a good example of how to have multiple windows inside the same application with GTK. My program is in C++ but I don't mind an example in C which would help me understand the principle anyway.
So, the basic idea is to create my own derived object from Gtk::Window as opposed to Gtk::Dialog. Dialog has a run method which works flawlessly to open a modal popup window, but it's not flexible enough for what I'm trying to do. Does anyone know how I'd go about spawning a new window when I click a button in my program?
For example:
void MainWindow::on_button_clicked()
{
NewWindow window;
//Some code to display that window and stay in a loop until told to return
}
Where NewWindow is derived from Gtk::Window as such:
class NewWindow : public Gtk::Window
{
//Normal stuff goes here
}
Anything will help...I'm really confused here!

Another way to have a new window is to create a pointer to a Gtk window variable(Gtk::Window* about_window_;) then set the Gtk window variable to a new instance of the other window (about_window_ = new Window;), after that show the new window (about_window_->show();). Below is a full example of this:
class AboutWindow : public Gtk::Window
{
public:
AboutWindow();
~AboutWindow();
protected:
Gtk::Label lbl_;
};
AboutWindow::AboutWindow()
{
this->set_default_size(100, 100);
this->set_title("About");
lbl_.set_label("About label");
this->add(lbl_);
this->show_all_children();
}
AboutWindow::~AboutWindow()
{
}
class MainWindow : public Gtk::Window
{
public:
MainWindow();
virtual ~MainWindow();
protected:
void onButtonClicked();
void aboutWinClose();
Gtk::Button button_;
Gtk::Label lbl_;
Gtk::Box box_;
Gtk::AboutWindow* aboutw_;
};
MainWindow::MainWindow()
{
this->set_default_size(100, 100);
box_.set_orientation(Gtk::ORIENTATION_VERTICAL);
this->add(box_);
box_.pack_start(lbl_);
lbl_.set_label("a test");
button_.set_label("Open About Window");
box_.pack_end(button_);
button_.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::onButtonClicked));
aboutw_ = 0;
this->show_all_children();
}
MainWindow::~MainWindow()
{
}
void MainWindow::onButtonClicked()
{
if(aboutw_ != 0)
return;
aboutw_ = new AboutWindow;
aboutw_->signal_hide().connect(sigc::mem_fun(*this, &MainWindow::aboutWinClose));
aboutw_->show();
}
void MainWindow::aboutWinClose()
{
aboutw_ = 0;
}
Added for reference.

If you don't want the new window to be modal, then simply create it, show() it, and return from your main window's method without entering a loop.

Related

gtkmm: add widgets that are not fields of class

I would like to add to gtkmm widgets that are not fields of class (I declare them in method).
I tried this:
class MyWidget : public Gtk::Window {
public:
MyWidget();
virtual MyWidget();
}
MyWidget::MyWidget() {
Gtk::Label label("my label");
add(label);
label.show();
}
But it does not shwo anthing.
But when i declare label in class and extend method by it, it works:
class MyWidget : public Gtk::Window {
public:
MyWidget();
virtual MyWidget();
protected:
Gtk::Label label;
}
MyWidget::MyWidget() : label("my label") {
add(label);
label.show();
}
What am I doing wrong?
A widget created in such way is destroyed with the closing bracket of the method. Please read this tutorial about memory management.
Personally I'd propose using dynamic allocation with make_managed() and add(). It's very easy to use and I have never had any problems when using that.
MyWidget::MyWidget() {
auto* label = Gtk::make_managed<Gtk::Label>("my label");
add(*label);
label->show();
}

Access MainWindow class members from another class

I googled a lot before writing here. I found a couple of posts but I couldn't quite get them. So I'm starting a new post.
I am developing a simple UI. There is this main class (see below), which is a main dialogue box which is shown when the app is started.
class MainWindow : public QMainWindow
There are few buttons on the MainWindow dialogue box, when I click a button, it hides the MainWindow and opens another QDialog box. This dialogue box is of the class as shown below
class libinsert : public QDialog
I create the libinsert object this way:
void MainWindow::on_pushButton_clicked()
{
this->hide();
libinsert lib; // I create libinsert object this way
lib.setModal(true);
lib.exec();
}
This is libinsert.cpp:
libinsert::libinsert(QWidget *parent) :
QDialog(parent),
ui(new Ui::libinsert)
{
ui->setupUi(this);
}
When the libinsert dialogue box is open, I need to access a socket variable which is a member of class MainWindow. How do I achieve this ?
My main app looks like this:
{
QApplication a(argc, argv);
MainWindow w(sock);
w.show();
return a.exec();
};
Alternatively: in general how are multiple dialogue boxes built, as parent-child relation?
libinsert will not be a child of MainWindow. QDialog-based windows are always top-level windows, regardless of what you pass as their parent when you construct them.
If there will only ever exist a single instance (per process) of MainWindow at any given time, you can give MainWindow a static function which returns a pointer to the window:
class MainWindow : public QMainWindow
{
public:
MainWindow()
{
theWindow = this;
}
virtual ~MainWindow()
{
theWindow = nullptr;
}
static get()
{
return theWindow;
}
static MainWindow *theWindow;
}
MainWindow *MainWindow::theWindow = nullptr;
Your dialogs can then access the main window's members thusly:
MainWindow::get()->someMemberFunction();
A more robust approach is to have libinsert store a pointer to the main window when it is created:
class MainWindow;
class libinsert : public QDialog
{
public:
libinsert(MainWindow *mw)
: mainWindow(mw)
{ ... }
MainWindow *getMainWindow()
{
return mainWindow;
}
MainWindow *mainWindow;
}
When MainWindow creates the dialog, it passes a pointer to itself to the constructor:
void MainWindow::createDialog()
{
libinsert *dialog = new libinsert(this):
}
This approach will work even if there are multiple instances of MainWindow active at the same time.

QWidget loses its parent

In my application I have a QDialog which itself contains a complex, QWidget-derived GUI element. The QDialog is modal and opened with exec() and the embedded GUI element handles all user interactions.
So only this child QWidget knows when the QDialog can be closed, which is done this way:
QDialog* parent=qobject_cast<QDialog*>(parentWidget());
if (parent) parent->close();
This is necessary because the QDialog has to be closed and not only the QWidget.
Now a user reported a situation where QDialog::exec() has returned but where the dialog (or only the GUI element?) was still visible. From the log files I can see QDialog::exec() really has returned and the code right after this call was executed.
So my current assumption: the GUI element has lost its parent so that the close() call shown above was not called because "parent" was null.
Any idea how this can happen? Is there a regular way where the parent of a QWidget can disappear?
Generally speaking, using QDialog::exec to reenter the event loop will cause trouble, because suddenly all the code that runs in the main thread must be reentrant. Most likely you're facing fallout from that. Don't reenter the event loop, and you'll be fine or the problem will become reproducible.
If you need to react to the dialog being accepted or rejected, connect code to the relevant slots. I.e. change this:
void do() {
MyDialog dialog{this};
auto rc = dialog.exec();
qDebug() << "dialog returned" << rc;
}
to that:
class Foo : public QWidget {
MyDialog dialog{this};
...
Foo() {
connect(&dialog, &QDialog::done, this, &Foo::dialogDone);
}
void do() {
dialog.show();
}
void dialogDone(int rc) {
qDebug() << "dialog returned" << rc;
}
};
or, if you want to lazily initialize the dialog:
class Foo : public QWidget {
MyDialog * m_dialog = nullptr;
MyDialog * dialog() {
if (! m_dialog) {
m_dialog = new MyDialog{this};
connect(m_dialog, &QDialog::done, this, &Foo::dialogDone);
}
return m_dialog;
}
...
void do() {
dialog()->show();
}
void dialogDone(int rc) {
qDebug() << "dialog returned" << rc;
}
};
It is a horrible antipattern for the child widget to attempt to meddle with the parent. The knowledge that the widget has a parent should not leak into the widget, it should be localized to the parent. Thus, the child widget should emit a signal that indicates that e.g. the data was accepted. When you create the dialog, connect this signal to the dialog's accept() or close() slots:
class MyWidget : public QWidget {
Q_OBJECT
public:
Q_SIGNAL void isDone();
...
};
class MyDialog : public QDialog {
QGridLayout layout{this};
MyWidget widget;
public:
MyDialog() {
layout.addWidget(&widget, 0, 0);
connect(&widget, &MyWidget::isDone, this, &QDialog::accepted);
}
};

How to call up a window only once with Qt

I want to know if there is any other way to call up a window once and not to call the desired window over again. This is the code I used :
void secondWindow::on_pushButton_clicked()
{
MainWindow *mainwindow;
mainwindow = new MainWindow(this);
mainwindow->show();
}
When you click the pushButton, it keeps on reproducing a new window. So is there a way it can show the same MainWindow whenever the pushButton is clicked?
I would solve this problem in one of the following ways:
1) Disable the pushButton button once it is clicked, and the MainWindow is created,
2) Declare the pointer to MainWindow as a secondWindow class' member variable:
class secondWindow : public ...
{
public:
secondWindow()
{
m_mainWindow = new MainWindow(this);
}
private slots:
void secondWindow::on_pushButton_clicked()
{
m_mainwindow->show();
}
private:
MainWindow *m_mainWindow;
};
Following will do what you want
void secondWindow::on_pushButton_clicked()
{
static MainWindow* mainwindow = new MainWindow(this);
mainwindow->show();
mainwindow->raise();
}
You should only initialize a new object once. Otherwise you only have to display the Window object.
class MainWindowFactory {
private:
MainWindow* mainWindow;
public:
MainWindowFactory();
MainWindow* getMainWindow();
};
MainWindowFactory::MainWindowFactory() {
mainWindow = NULL;
}
MainWindow* MainWindowFactory::getMainWindow() {
if (!mainWindow) {
mainWindow = new MainWindow(this);
}
mainWindow->show();
}

Drawing lines programmatically with Qt

I want to add lines programmatically in a QLabel between two points. From what I found, it seems that the only way to do it is to subclass a QWidget to change the PaintEvent() protected method.
So, I create a new class 'QLineObject' from QWidget. This is my header file :
class QLineObject : public QWidget
{
Q_OBJECT
public:
QLineObject();
QLineObject(Point from, Point to);
protected:
void paintEvent(QPaintEvent *event);
private:
Point fromPoint;
Point toPoint;
};
And the implementation file :
QLineObject::QLineObject()
{
Point point;
point.x = 0.0;
point.y = 0.0;
fromPoint = point;
toPoint = point;
}
QLineObject::QLineObject(Point from, Point to)
{
fromPoint = from;
toPoint = to;
}
void QLineObject::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawLine(fromPoint.x, fromPoint.y, toPoint.x, toPoint.y);
}
Here's come the problem. I can't find how to add this widget in my main window. If I create a new instance of QLineObject and call show(), it popup a new window. I'm sure I'm just missing something. Is there someone who want to help me? I would like to know how to create and add it from somewhere else that my main window constructor.
Thank you!
You shouldn't be calling show on the QLineObject. Instead, pass the main window as the parent to your constructor and pass that to the inherited QWidget. Then call show on the main widget, which in this case is the main window...
class QLineObject : public QWidget
{
public:
QLineObject(QWidget* parent);
};
QLineObject::QLineObject(QWidget* parent)
: QWidget(parent)
{
}
QWidget* pWidget = new QWidget;
QLineObject* pLineObject = new QLineObject(pWidget);
pWidget->show();
Alternatively, you can use the QLabel as the parent.
QLabel* pLabel = new QLabel(pWidget);
QLineObject* pLineObject = new QLineObject(pLabel);
pWidget->show();
Also, you probably want to be calling QWidget::paintEvent in your overridden paintEvent.
I would do the following:
QMainWindow mw;
QLineObject lo;
mw.setCentralWidget(&lo);
mw.show();