I have Main Dialog which has some buttons. Each button will generate an independent dialog as follows:
void MainDialog::onAButtonClicked()
{
Dialog *dial = new Dialog(pass some parameters);
dial->show();
}
The problem with this approach is when the user closes the Main Dialog, the running independent dialog is not closed. I don't want this issue to occur. Some solutions suggest to override closeEvent and reject, I've tried them but these require me to create *dial as a member data of Main Dialog. This works but I don't want this approach because I need to delete this dialog once the user closes the window completely. I've tried to allocate some memory using new and delete object on close or reject events but the app hangs. What is the proper approach to solve this issue?
connect(this,SIGNAL(finished(int)),dial,SLOT(close()));
or
connect(this,SIGNAL(rejected()),dial,SLOT(close()));
Related
I have an application which has several background tasks running in non-GUI threads which may time to time require some user interaction so they send signal to the main thread and the corresponding slot creates a dialog and shows it to the user. Meanwhile the task thread is waiting in a blocking event loop. Once the user answers the dialog and closes it, the task event loop is signaled to quit and the task resumes.
There is however a problem. In the GUI thread I can still use the application which time to time shows some modal dialogs. When there is a modal dialog already shown and then the background tasks requests another dialog to be opened, this task-related dialog is displayed in front of the modal dialog. But this new dialog is not modal and the modal dialog is hidden behind it. The non-modal one therefore is not responsive, the application feels like it got stuck.
My idea was to display the new dialog always behind the modal dialog, which I believe I can get with QApplication::activeModalWidget(). But I do not know how to do this. How can I show a dialog behind another dialog but still in front of the main window (which is a parent of both dialogs)? I tried to call QApplication::activeModalWidget()->activateWindow() after I show the non-modal one but his causes blinking of windows and moreover I can still click into the new non-modal dialog hiding the modal one. So this is not a perfect solution.
Or do you see any other solution?
Maybe I could implement a queue of dialogs, and once there is any modal dialog visible, then the new background task-related dialog would not be shown, only enqueued and shown once the modal dialog is closed. However this feels more fragile solution to me.
Any ideas?
UPDATE: I redefined the question by adding "or after it is closed" becasue this works for me too.
I found a solution which seems to work well and which shows the non-modal dialog only after the modal dialog is closed.
QWidget *nonModalDialog = ... // creates the non-modal dialog
nonModalDialog->setAttribute(Qt::WA_DeleteOnClose);
QWidget *modalDialog = qApp->activeModalWidget();
if (modalDialog == nullptr)
{
// no modal dialog, we can show the non-modal one now
dialog->show();
}
else
{
// we must wait until the modal one is closed
QObject::connect(modalDialog, &QWidget::destroyed, nonModalDialog, &QWidget::show);
}
This seems simple and robust.
I think you are looking for QWidget::raise(). You should be able to use QApplication::activeModalWidget()->raise() after calling dialog->show() on your non-modal dialog.
If you're getting into situations where you have multiple modal and non-modal dialogs, all launched in different order, this might not be enough to solve the problem. You might raise one modal dialog only to have others behind other non-modal dialogs and end up stuck again. You should consider keeping a reference to the collection of modal dialogs that are currently active so that you can make sure they are always on top of the non-modal dialogs.
I have a CFormView SDI which calls and opens up a CDialog.
In the CDialog I have a button which has a bitmap displayed.
All works well, until I close the CDialog.
When I open up the same CDialog (using create function), the button appears, and the button's functionality is there, however the bitmap disappears.
Can someone please tell me why the bitmap on the button disappears on subsequent calls to CDialog?
Thank you in advance.
Here is the code:
In the CFormView I have a button that creates the CDialog:
void CTest4View::OnButton1()
{
m_dialog_calculator.Create(IDD_DIALOG1, GetDesktopWindow());
m_dialog_calculator.ShowWindow(SW_SHOW);
}
In the CDialog I have the bitmap put on the button in the InitDialog:
BOOL CCalculator::OnInitDialog()
{
CDialog::OnInitDialog();
if(!bitmapNew.LoadBitmap(IDB_BITMAP_NEW)){
MessageBox("problem in loadbitmap");
}
if(!m_button.SetBitmap(bitmapNew)){
MessageBox("problem in SetBitmap");
}
bitmapOpen.LoadBitmap(IDB_BITMAP_OPEN);
m_buttonOpen.SetBitmap(bitmapOpen);
//==========================
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
Upon further investigation, the problem seems to be in: m_button.SetBitmap(bitmapNew) since this returns FALSE. Can someone please help me?
Quick fix:
void CTest4View::OnButton1()
{
// only create the dialog once
if (m_dialog_calculator.m_hWnd==NULL)
m_dialog_calculator.Create(IDD_DIALOG1, GetDesktopWindow());
m_dialog_calculator.ShowWindow(SW_SHOW);
}
Additional information 1:
The information that OP provided in his question/code is quite little, so I actually have to recreate a similar test project to guess what is wrong with the missing bitmap. What I found is that the CDialog and CBitmap is being created multiple times when the button is pressed, this causes subsequent creation api call to fail, other than the first creation call. The result is some unexpected behavior as you can see now.
The program is supposed to generate some assertion errors when run in debug mode due to the creation failure but I guess the OP compiled it in release mode so all warnings are being suppressed.
The problem occurred because the calculator dialog is created as a modeless dialog as compared to a normal DoModal way of activation. The usual way of doing such modeless dialog is to create the dialog only once, by monitoring the m_hWnd member variable. When the user want the dialog to be dismissed, it is simply being hidden instead of being destroyed. This will avoid all the multiple creation problems altogether.
I guess presently, the calculator dialog is assumed to be closed and destroy by clicking the "X" button on the top right of dialog, well, actually it is only hidden but not actually being destroyed by the default handling of CDialog. The correct way to dismiss the modeless calculator dialog is thus to overide the OnClose event to hide it using ShowWindow(SW_HIDE). To activate it again, use ShowWindow(SW_SHOWNORMAL).
I have a queer sort of problem. Consider the following scenario:
Main window creates a child dialog on click of some button.
Child dialog does some task in a worker thread.
An error occurs during that task causing a message box to be displayed saying something along the lines of "Yikes! Something went wrong.".
Child dialog's 'Cancel' button is clicked on causing the child dialog to be closed.
Message box is still active! Clicking on anything in the message box = crash.
Pseudocode of how things are happening: (please ignore syntactic correctness here)
MainWindowClass mainObj;
void MainWindowClass::OnSomeButtonClick()
{
SomeDialogClass someDialogObj;
someDialogObj.DoModal();
}
int MainWindowClass::doTask()
{
// Do work
if(ERROR)
{
MessageBox("Yikes! Something went wrong.", "Error", MB_OK);
return ERROR;
}
}
///////////////////////////////////////////////////////////////////
// Meanwhile, in another file,
extern MainWindowClass mainObj;
void SomeDialogClass::OnCancel()
{
// Do all cleanup and close dialog
}
int SomeDialogClass::workerThreadFunc()
{
return mainObj.doTask();
}
int SomeDialogClass::DoModal()
{
AfxBeginThread(workerThreadFunc);
// Do all other work and then wait for the worker thread
}
My question is twofold:
On cancel, is there a way to know if any message boxes/dialogs in
the same application are active?
Is my entire design wrong and should I be doing something else altogether?
I thought one of the main reasons to use a modal dialog was its ability to prevent focus from going to parent dialogs. Yet, when that error message box is shown, I can happily click on the dialog and then click on 'Cancel' and pretend that the error message box never showed.
Rather than have your worker thread bring up a modal message box, it should signal the error condition back to the UI thread. My experience of MFC and multi-threading is that having all UI handled in the same thread makes things much simpler and removes these types of error. Having two threads potentially bringing up modal dialogs at the same time is asking for trouble.
I'm kind of new at this, but my suggestions would be if the child is going to be cancelled when there is an error then destroy it before launching the error.
As far as I can tell, the SomeDialogClass is modal, then the parent launches another modal dialog, the MessageBox. Now, if SomeDialogClass was modeless then the message box would appear on top since SomeDialogClass would then be a child of the entire desktop and the sole child of MainWindowClass would be the MessageBox.
I'm under the impression that modal only prevents focus from going to things underneath the modal dialog, not what is launched on top or after it is open. I've done a modal dialog, launched a modeless from it, and could move both around the screen just fine. Additionally, I created a WM_MESSAGE from the modeless dialog back to the modal to announce when it was done so the modeless could finish.
My program use a modeless dialog to interact with the user and also has a tray icon.
A user can quit the app immediately by using the tray icon.
BOOL OnInitDialog()
{
init data...
}
void OnDestroy()
{
destroy data...
}
void OnSomeButton()
{
CFileDialog dlg;
...
dlg.DoModal(m_hWnd));
access data...
...
}
void OnMenuExit()
{
DestroyWindow();
}
The problem is that when I popup a modal dialog(OnSomeButton), and then quit using the tray icon menu, the main dialog is destroyed first, and then the modal one returns, trying to access some invalid data, causing a crash.
I know i can add some check code before accessing the data, but is there any other way?
Is there any way to ensure OnSomeButton() returns before window destroy?
You need to add your own application level code. There is no system support for this issue primarily because there can be so many pecularities that no generic approach is possible.
Yeah. When you quit from the tray menu, you can send a WM_CLOSE or similar message to your modal dialog that causes it to exit. Even if your main window is destroyed before that OnSomeButton returns you will be okay provided the remainder of that function does not access any class internals (member variables etc). You could ensure this by having the window proc of your modal dialog return an 'abort' code or something when it is closed in this way.
I am developing a project using Qt4 and I have run into a slight issue.
I am creating a modal window from the main window. I want this to disable the toolbar at the top.
I have a menu item that spawns the modal from the menu. What I want is that when the modal is spawned that menus is disabled. I have tried using the setEnabled(false) function but that doesn't reset it.
Here is the code:
void Main_Screen::Create_ViPro()
{
std::auto_ptr<ViPro_Dialog> modal(new ViPro_Dialog(this));
modal->show();
modal->exec();
}
So It is just a simple class that is triggered when a menu item is selected. I feel that the problem stems from the fact that i'm setting the parent to the main screen, however I don't know how to create a modal without a parent(it doesn't make sense to do that). Is there a way to disable the parents toolbar from the child? The only thing I have seen so far is _fileMenu->setEnabled(false);
That works as long as I don't create the modal, but as soon as that is spawned, the menu works again. I'm totally lost.
Thanks in advance
Edit as Patrice
Here is the constructor
Main_Screen::Main_Screen(QWidget* parent /*= NULL*/)
: QMainWindow(parent),
_newProj(new QAction(tr("New &ViPro"), this)),
_fileMenu(menuBar()->addMenu(tr("&File")))
{
//create slot for creating a new project
connect(_newProj.get(), SIGNAL(triggered()), this, SLOT(Create_ViPro()));
_fileMenu->addAction(_newProj.get());
//if i do this then setEnabled(false) works perfectly and i can't access the menu
Create_ViPro()
}
So the function is signaled by triggering the newproject action. If i call the function directly from within the constructor it disables it as you stated patrice, however, if I have the function called via the trigger, it doesn't disable it. Am I handling the signal / slot mechanism wrong? Thanks again.
Another example, if I make the function create_vipro() as below
void Main_Screen::Create_ViPro()
{
_fileMenu->setEnabled(false);
}
The file menu isn't disabled when I trigger the event, so it must have nothing to do with the modal itself, but instead how signals are handled.
Since child is a modal dialog main screen can't react to event. But you can disable the toolbar (or menubar) before creating the modal dialog and enable it as soon as you leave the exec function:
void Main_Screen::Create_ViPro()
{
_fileMenu->setEnabled(false);
std::auto_ptr<ViPro_Dialog> modal(new ViPro_Dialog(this));
modal->show();
modal->exec(); // Will stay here until you close the modal dialog
_fileMenu->setEnabled(true);
}
if ViPro_Dialog is really a modal dialog it will works.
Another thing, since ViPro_Dialog is modal you can declare it locally without using auto_ptr:
void Main_Screen::Create_ViPro()
{
_fileMenu->setEnabled(false);
ViPro_Dialog modal(this);
modal.show();
modal.exec(); // Will stay here until you close the modal dialog
_fileMenu->setEnabled(true);
}
EDIT:
I guess (I can't test at work) that you can't enable/disable menu when it is executing a QAction. Signal is calling slots sequentially so QAction is busy when you try to disable the menu.
Try this:
In Main Screen, create a slot with one boolean parameter that enable/disable menubar. Just call the setEnabled function
In ViPro_Dialog, emit a signal with a boolean parameter (false at startup, true at validation)
In Create_ViPro, once dialog created, connect the new signal with the slot, exec dialog and don't forget to disconnect slot from signal:
void Main_Screen::Create_ViPro()
{
ViPro_Dialog modal(this);
// Connect signal/slot
modal.show();
modal.exec(); // Will stay here until you close the modal dialog
// Disconnect signal/slot
}
This can achieve what you want
EDIT2
You are doing a mistake when using modal dialog. There's a conflict between show() and exec(). When you are displaying modal dialog you don't need to disable other windows: it's automatically done by modal status of the dialog. There are many modal depth: http://qt-project.org/doc/qt-4.8/qt.html#WindowModality-enum. So your code should be:
void Main_Screen::Create_ViPro()
{
ViPro_Dialog modal(this);
// modal.setWindowModality(Qt::WindowModal); // Uncomment this line if you want to only disable parent
modal.exec();
}
Read this for more detail: http://qt-project.org/doc/qt-4.8/QDialog.html#details.
Using exec() doesn't just create a modal dialog, it also suspends most of the regular event handling, and only handles events in the dialog that is in exec(). This may include some UI updates (like the transitions from enabled to disabled), but I'm not positive. A better way to handle this might be to explicitly set the dialog modal, but allow the regular event loop to continue, something like this:
void Main_Screen::Create_ViPro()
{
ViPro_Dialog* modal = new ViPro_Dialog(this);
modal->setModal(true);
modal->show();
}
That code will still not visually disable the toolbar or menubar, but they should be unresponsive. To disable them, you could try combining this with Patrice Bernassola's answer.