PDA

View Full Version : How to manage buttonless dialog boxes?



jcbaraza
9th July 2012, 02:58
Hi everyone,

I'm implementing an application that shows the status of the initialization process in a buttonless window, just like the one in the figure.

7971

This window must show different messages depending on the initialization step.

I've created a "secondary thread" which performs the actual configuration tasks, whereas the "main thread" shows the info window, implemented as a buttonless dialog box. During configuration process, the "secondary thread" can modify the "Text" field of the dialog box and so update the info shown by the info window. However, at the end of the work, I'm not able to close the window, as it is buttonless. Obviously, I've tried to "close()" the dialog, but it gets a Runtime error:

7970

How can I get the "secondary thread" closes the window? Or how can I implement the same idea in another way?

These are the codes of both the main and secondary threads.

Main thread:

void VFITW::ConfigureTool()
{
if (Initialized)
return ;

// Create information window
QMessageBox info(0);
info.setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint);
info.setWindowTitle("Info");
info.setIcon(QMessageBox::Information);
info.setStandardButtons(QMessageBox::NoButton);
info.setText(tr("Initializing tool."));
info.setInformativeText(tr("This operation can last some time. Please wait ..."));

// Create secondary thread
FormActiveThread *thread = new FormActiveThread(&info, ..., 0);
// connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();

// Launch information window
info.exec();
// info.~QDialog();

// Wait for the end of secondary thread
// thread->wait();
}

Secondary thread (header):

#ifndef FORMACTIVETHREAD_H
#define FORMACTIVETHREAD_H

#include <QObject>
#include <QThread>
#include <QMessageBox>

class FormActiveThread : public QThread
{
Q_OBJECT

public:
// I pass a pointer to the info window to let the "secondary thread" update the information shown.
FormActiveThread(QMessageBox *info, ..., QObject *parent = 0);
~FormActiveThread();

protected:
void run();

private:
QMessageBox *infoT;
...
};

#endif // FORMACTIVETHREAD_H

Secondary thread (source code):

#include "FormActiveThreads.h"

FormActiveThread::FormActiveThread(QMessageBox *info, ..., QObject *parent)
: QThread(parent)
{
infoT = info;
...
}

FormActiveThread::~FormActiveThread()
{
infoT = NULL;
...
}

void FormActiveThread::run()
{
// Initialize application parameters

...

// Initialize connections
(*infoT).setText(tr("Initializing connections."));
(*infoT).repaint();

...

// (*infoT).close();
}


Besides this, I have additional problems:
1.- How can I get that the the dialog box expands as to have both Text and InformativeText in one line (see that InformativeText occupies two lis) as I've got in other dilog boxes that I've used?
2.- I can't make the "seconday thread" to self-delete. If I uncomment this sentence in the main thread:

connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
I get a Runtime error. Why?
3.- I can't wait for the end of secondary thread, because if I uncomment the sentence in the main thread:

thread->wait();
I get a Runtime error. Why?

Thank you very much in advance.

high_flyer
9th July 2012, 09:15
I don't get why do you use threads in this situation?
Why signal/slots are not enough?

jcbaraza
9th July 2012, 22:29
It's a good question high_flyer. I didn't mention in my post, but I first tried to show() the dialog box (and continue with the configuration tasks and refreshing the info), but the window didn't appear (well, actually the window appeared, but with no text inside).

So, I supposed that I should exec() instead. And that's why I needed the help. So do you mean that I should make up back my mind, and go back to the first idea? That is, should I remove the thread management, replace the exec() in line 21 of the "Main thread" with a show(), and put all the configuration code in the "secondary thread" after the show()? I'll try again, and tell about the result.

Thanks!

high_flyer
10th July 2012, 09:16
Threads are there for cases where concurrent execution is needed.
From what I can tell from what you have posted so far, I don't see any need for concurrency - thus, no need for threads.

Now, if you just explain in simple terms as you can, what it is you want to achieve, we might be able to help you.

jcbaraza
11th July 2012, 01:06
Hi again,

First of all thanks again for your interest. What I'm trying to do is to show an info window (with no buttons) to show the evolution of my application's configuration process. This should happen when the main window has been created and shown (with "showMaximized()"), and just before the control is given to the user. I've been looking for the appropriate event to capture and then emit a signal whose associated slot runs a function (lets call it "FormActivate") which performs the configuration tasks (initializing internal structures, reading files, checking a license, etc.). I've opted for "ShowToParent" event. I've customized "event()" function to catch this event and emit a signal which runs "FormActivate" function.

The idea of "FormActivate" function is to show a window indicating the execution of each particular task. Notice that "FormActivate" function is a mixture between the codes of the original "Main thread" and the "FormActiveThread::run()" function, as it creates the info window, runs the tasks and updates the ("Text" field of) info window. To implement such info window, I've used a "QMessageBox::Information" dialog box with no buttons at all (you can see the declaration in the code of "Main thread" in my first post). After creating this buttonless "dialog box", I "show()" it, and I get this:

7990

Remark that there are three errors:
1.- On the one hand, the main window is not maximized (perhaps I have taken the worng event? If so, which would be the right one?).
2.- The info window is shown, but empty. I've 'solved' this error by "repaint()"-ing the window just after "show()"-ing it. And now I get this:

7991

3.- The width of the info window is shorter than the "InformativeText" field. How can I get the info window to adapt to the length of (the longest between) "Text" and "InformativeText" fields?

As you can see, I've 'solved' the original trouble with closing an "exec()"-uted dialog box, because I don't need threads (at the moment), but I have three problems instead.

Can you please help me?

high_flyer
11th July 2012, 10:26
Few remarks to start with:
1. I don't know how you are attaching your attachments, but they don't work.
It is better to attach them using the "insert image" button you have in your post edit window, and to upload them to the forum, where they will also stay.
If you are doing this, then please let me know, and we will look in to why your attachments don't work.
2. Make it short and simple - many (as I am) read this at work and can't spend too much time reading complex posts.
If you have a complex problem, break it down to independent short points.
I don't know why many people here tend either to be so scarce with information that you can't help them, while others over do it.


1.- On the one hand, the main window is not maximized (perhaps I have taken the worng event? If so, which would be the right one?).
What do you mean by that?
Usually you call show() on youw main window - and you can control how it gets showed with setting window flags.


2.- The info window is shown, but empty. I've 'solved' this error by "repaint()"-ing the window just after "show()"-ing it. And now I get this:
Hard to tell with out seeing code.


3.- The width of the info window is shorter than the "InformativeText" field. How can I get the info window to adapt to the length of (the longest between) "Text" and "InformativeText" fields?
What is a "field" a QLabel?
You can use a layout and set size policies to your fields which will make your dialog adjust.

jcbaraza
11th July 2012, 18:45
Sorry for being so dense. I'll try to be concise.


Few remarks to start with:
1. I don't know how you are attaching your attachments, but they don't work.
It is better to attach them using the "insert image" button you have in your post edit window, and to upload them to the forum, where they will also stay.
If you are doing this, then please let me know, and we will look in to why your attachments don't work.

I actually did so. I'll try again.

8003

8004


2. Make it short and simple - many (as I am) read this at work and can't spend too much time reading complex posts.
If you have a complex problem, break it down to independent short points.
I don't know why many people here tend either to be so scarce with information that you can't help them, while others over do it.

1.- On the one hand, the main window is not maximized (perhaps I have taken the worng event? If so, which would be the right one?).

What do you mean by that?
Usually you call show() on youw main window - and you can control how it gets showed with setting window flags.
Hard to tell with out seeing code.

If you see the pictures, the main window appears incomplete (you can see beneath part of Qt), and the QMessageBox is empty. In VFITW.ui, both "Horizontal Policy" and "Vertical Policy" (from "sizePolicies") are set to "Maximum". This is the code that manages VFITW main window.

main.cpp (remark the showMaximized() call)

#include <QtGui/QApplication>
#include "VFIT_W.h"

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
VFITW w;
w.showMaximized();

return a.exec();
}

VFITW.h (main window header)

#ifndef VFIT_W_H
#define VFIT_W_H

#include <QMainWindow>
#include <QLabel>
#include <QActionGroup>
#include <QEvent>

namespace Ui {
class VFITW;
}

class VFITW : public QMainWindow
{
Q_OBJECT

public:
explicit VFITW(QWidget *parent = 0);
~VFITW();

protected:
bool event(QEvent *e);

private:
...

private slots:

...
void FormActivate();

signals:
void activateForm();
};

#endif // VFIT_W_H

VFITW.cpp (main window code)

#include <QtGui>
#include <QWidget>
#include <QAction>
#include <QShowEvent>
#include <QThread>

#include <dir.h>

#include "VFIT_W.h"
#include "ui_VFIT_W.h"

VFITW::VFITW(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::VFITW)
{
ui->setupUi(this);

this->setMinimumHeight(600);
this->setWindowState(Qt::WindowMaximized);

....

connect(this, SIGNAL(activateForm()), this, SLOT(FormActivate()));

...
}

VFITW::~VFITW()
{
delete ui;
}

bool VFITW::event(QEvent *e)
{
if (!e)
return false;

if (e->type() == QEvent::ShowToParent)
{
emit activateForm();
}

return QObject::event(e);
}

void VFITW::FormActivate()
{
if (Initialized)
return ;

QMessageBox info(0);
info.setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint);
info.setWindowTitle("Info");
info.setIcon(QMessageBox::Information);
info.setStandardButtons(QMessageBox::NoButton);
info.setInformativeText("This operation can last some time. Please wait ...");

// First configuration task
info.setText(tr("Doing first configuration task."));
info.show();
// info.repaint();

...

// Another configuration task
info.setText(tr("Doing something else."));
info.repaint();

...

... // Yet another configuration task
info.setText(tr("Doing another task."));
info.repaint();

...

// And so on til the end of the configuration
Initialized = true;
}

I hope it's clear now... Just one question. In "FormActivate" function (or slot) there is a commented "repaint()" after "show()-ing" info window. If I leave it un commented, it appears the first picture. If uncommented, I have the second one. In both cases, the main window is incomplete.


What is a "field" a QLabel?

It's my fault. In terms of C, a struct is composed by fields, and I was thinking of QMessageBoxes as "structs" instead of objects with properties. So, I meant QMessageBox' "Text" and "InformativeText" properties. Sorry!


You can use a layout and set size policies to your fields which will make your dialog adjust.

Yes, but I'm using a standard QMessageBox directly, and I don't know how to set those policies as to adjust to "Text" and "InformativeText" sizes. What really shocks me is that I'm using many other QMessageBoxes in the application, with even longer messages, and they automatically enlarge. Why this one doesn't?

Thanks again.

high_flyer
12th July 2012, 09:28
For the showMaximized() problem:
I think the problem is in the fact that you are showing your message box in the middle of the show sequence of the main window.
It could be that the main window didn't resize yet to the full screen size.
Things you might want to try:
1. What you are doing with your message box it traditionally done with a splash screen.
You might want to try it: QSplashScreen
2. If you insist to go with your approach try catching the Show event instead of ShowToparent.
If all above fails you can try:
Try setting setMaximSize() before show() and see if it shows to that size.
If it does, before you call showMaximize() you could get the screen size (QDesktopWidget) set it as maximum and show.

MessageBox:
I don't quite understand the use you are doing with ShowToParent.
ShowToParent just means a child widget has been shown.
If your main window would have been childless, you would not get this event.
I actually touched on that above.
Now - QMessageBox:
1. Size issues: under windows the standard flag used is :Qt::MSWindowsFixedSizeDialogHint
Probably if you omit it, it will adjust it self to the text size.
2. The reason for the repaint I think is due to the fact you are still inside an event (showToParent) so the even loop is not processing other queued events.
repaint() is a brute force call to force a repaint() and in normal conditions should not be use, and rather points to a problem in code design.
You doing you entire initialization process, which is obviously long (otherwise you would not need your message box) in side a trapped event.
Bad idea!
Again, splash screen would be better for this.
Otherwise I'd suggest instead of trapping events, working with a QTimer.

jcbaraza
13th July 2012, 13:09
Wow! How many changes to test! I'll try step by step.

I actually new about splash screens, but I don't like that they have to be shown before the main window, so the main window is not visible at all. The problem is that I'm a bit stubborn :), and I still prefer to show the main window (and preferably in its full size), and over this sight, the info window. If I'd have been able to use the splash screen after showing the main window, I would have tried yet. Well, I'll try to do my best.

Thanks so much for your advise, and sorry for being so dense! Bye!

high_flyer
13th July 2012, 13:27
If I'd have been able to use the splash screen after showing the main window, I would have tried yet.
I don't think QSplashCreen *has* to be shown before the main window, its just the way most applications do it.
At least I didn't see anything in the documentation that said so.
Try to popup your splash screen instead of your QMessageBox.

If you sty with your QMessageBox, don't do your initialization inside an event, give your QMessageBox the "stay on top flag" and make it not modal so that the application event loop can run (the reason for your repaint())

jcbaraza
13th July 2012, 14:03
Hi,


I don't think QSplashCreen *has* to be shown before the main window, its just the way most applications do it.
At least I didn't see anything in the documentation that said so.

Well, I've read about this in the QSplashScreen Class Reference (see the main.cpp code), and in Blanchette&Summerfield's C++ GUI Programming with Qt 4, Second Edition (Chapter 3. Creating Main Windows : Splash Screens)


Try to popup your splash screen instead of your QMessageBox.

If you sty with your QMessageBox, don't do your initialization inside an event, give your QMessageBox the "stay on top flag" and make it not modal so that the application event loop can run (the reason for your repaint())

Thank, I'll try (starting for the second choice ;)).

wysota
14th July 2012, 18:28
I don't see anything in the docs that would claim you couldn't show a splashscreen after some other window is visible.

jcbaraza
16th July 2012, 02:08
Hi high_flyer and wysota,

I got it! Thanks to high_flyer's comments, I've solved my problems. I'd like to explain here how I've solved, in case is useful for other people, and to thank you for your suggestions.

As high_flyer mentionned, my private slot was being actually executed in the middle of the event management. So, in the event process, I've just swapped the event management and the private signal emission:


bool VFITW::event(QEvent *e)
{
if (!e)
return false;

bool ret = QObject::event(e);

if (e->type() == QEvent::ShowToParent)
{
emit activateForm();
}

return ret;
}

In this way, I don't need to repaint() the info window after show()-ing it any more.

On the other hand, I've tracked the sequence of events issued at the startup, and I've decided that the event that I should "capture" in the event function (to detect the main window activation and, thus emit my private signal) should be either "Show" or "ShowToParent" (after this one, a #67 event occurs, but I don't know what is for; any idea?). If I tried "Show", the configuration (that is, the "ActivateForm" private slot) was executed (and thus the info windows were issued) before the main window is visible. Instead, if I tried "ShowToParent", the main window is visible in its full size. So I've opted for this last (as I showed in my previous posts).

The problem with the resize of QMessageBox has been solved in a not very subtle way. First, I already confirmed that I had the Qt::MSWindowsFixedSizeDialogHint flag deactivated. Then, after some tests, I've discovered that QMessageBoxes resize when changing "Text" property, but they ignore the size of "informativeText" property. For this reason, I've used only "text" property, by joining the two messages that I intended to show (and inserting a couple of "\n"'s between them). In this way, updating the info message is relatively easy.

Regarding wysota's comment (by the way, thank you too for your interest in my post):
1.- In my opinion, the following main code, which appears in the detailed description of QSplashScreen Class Reference (from Qt Creator 2.4.1 Help), suggests that the the QSplahScreen is run before showing the main window. Obviously, nothing is said about to be compulsory, you're right.


int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPixmap pixmap(":/splash.png");
QSplashScreen splash(pixmap);
splash.show();
app.processEvents();
...
QMainWindow window;
window.show();
splash.finish(&window);
return app.exec();
}

2.- However, in Blanchette&Summerfield's C++ GUI Programming with Qt 4, Second Edition (Chapter 3. Creating Main Windows : Splash Screens), they say explicitly: "The QSplashScreen class shows an image before the main window appears."

Perhaps it should be cleared in the reference whether splashscreens can be run at any execution time or not.

And finally, I want to apologize again for being so dense to explain myself. English is not my first language and alway try to do in a too formal way. Anyway, thank you all.

high_flyer
19th July 2012, 13:49
In this way, I don't need to repaint() the info window after show()-ing it any more.
I am actually surprised this works (differently).
My guess is that you also changed something in the initialization sequence.
Emitting a signal IS a function call to the slot its connected to.
Unless this slot is in another thread, it will be executed immediately.
Thus emitting a signal or calling the slot directly is equivalent in terms of execution order and should change nothing in the time it takes it to be executed (omitting the metaObject overhead).
This also means that your initialization still runs inside the trapped event - and that in this regard you have changed nothing.

However, in Blanchette&Summerfield's C++ GUI Programming with Qt 4, Second Edition (Chapter 3. Creating Main Windows : Splash Screens), they say explicitly: "The QSplashScreen class shows an image before the main window appears."
I can't verify that at the moment, but this *probably* is an explanation of what some sample code does, and not rule or constraint.