Show dialog before main window - c++

I have a windowed application, which crashes after showing information dialog only before QMainWindow is activated.
Information dialog is shown only if passed data is invalid, however it might be a user interaction (file select / drag) or passed as argument, which causes problems. When / how should I show such error dialog than?
Note: When dialog is only shown (with show() method rather than exec()) it doesn't crash, but dialog gets discarded right away even with setModal( true ).
Any ideas? Thanks,
EDIT:
Some code:
int WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, LPSTR lpCmdLine, int nShowCmd)
{
QApplication app(__argc, __argv);
MBViewer viewer;
viewer.show();
return app.exec();
}
MBViewer::MBViewer()
{
setAcceptDrops(true);
m_ui.setupUi(this);
m_viewer = new Viewer_Widget();
m_ui.preview_layout->addWidget(m_viewer);
parse_parameters();
connect_controls();
connect_actions();
}
void MBViewer::connect_controls()
{
(...)
connect( m_viewer, SIGNAL( view_initialized()), this, SLOT( open_file() ));
(...)
}
void MBViewer::open_file()
{
// somefile is set in parse_parameters or by user interaction
if (!somefile.is_valid()) {
m_viewer->reset();
// This will crash application after user clicked OK button
QMessageBox::information( this, "Error", "Error text", QMessageBox::Ok );
return;
}
(...)
}

Try a message box without pointer to your main window like in this example:
QMessageBox msgBox;
msgBox.setText(text.str().c_str());
msgBox.setIcon(QMessageBox::Question);
QPushButton *speed = msgBox.addButton("Speed optimization", QMessageBox::AcceptRole);
QPushButton *memory = msgBox.addButton("Memory optimization", QMessageBox::AcceptRole);
QPushButton *close = msgBox.addButton("Close", QMessageBox::RejectRole);
msgBox.setDefaultButton(speed);
msgBox.exec();
if (msgBox.clickedButton() == memory)
return true;
if (msgBox.clickedButton() == close)
exit(4);
It works even before creating any window (but after QApplication initialization).

When you call app.exec( ),it starts the main message handler loop which is required to be running before you start displaying dialogs. QMessageBox is a modal dialog when used with exec, so will prevent the app.exec function being called. Therefore, it's likely that messages are being sent before the message handler has been initialised and so a crash is observed.
When show() is used, execution of app.exec is allow to process, which is why the crash doesn't happen.
If you want a modal MessageBox at startup, you'll need to launch it after the message handler has been created / initialised. Not the cleanest way, but you could try launching it on a timer to delay the call to exec.

Related

Qt Dialog X Button Override Reject Not Working As Expected

I have been trying to hide a stand alone dialog application when the user hits the typical close button (The one with the X in the corner usually next to the minimize button) I cam across this post:
Qt: How do I handle the event of the user pressing the 'X' (close) button?
which I thought would have my solution, but I get strange behavior when I implement it.
void MyDialog::reject()
{
this->hide()
}
When I hit the X Button the whole application closes (the process disappears) which is not what I want. Since my gui spawns with a command line, I setup a test system where I can tell my dialog to hide via a text command where I call the same 'this->hide()' instruction, and everything works fine. The dialog hides and then shows back up when I tell it to show.
Any ideas why the reject method is closing my app completely even when I don't explicitly tell it to?
Override the virtual function "virtual void closeEvent(QCloseEvent * e)" in your dialog class. The code comment will explain in detail.
Dialog::Dialog(QWidget *parent) :QDialog(parent), ui(new Ui::Dialog){
ui->setupUi(this);
}
Dialog::~Dialog(){
delete ui;
}
//SLOT
void Dialog::fnShow(){
//Show the dialog
this->show();
}
void Dialog::closeEvent(QCloseEvent *e){
QMessageBox::StandardButton resBtn = QMessageBox::question( this, "APP_NAME",
tr("Are you sure?\n"),
QMessageBox::Cancel | QMessageBox::No | QMessageBox::Yes,
QMessageBox::Yes);
if (resBtn != QMessageBox::Yes){
//Hiding the dialog when the close button clicked
this->hide();
//event ignored
e->ignore();
//Testing. To show the dialog again after 2 seconds
QTimer *qtimer = new QTimer(this);
qtimer->singleShot(2000,this,SLOT(fnShow()));
qtimer->deleteLater();
}
//below code is for understanding
//as by default it is e->accept();
else{
//close forever
e->accept();
}
}

Close modeless dialog when another window closes

I have a modeless dialog which I'm creating as below,
CPlotDlg * newd = new CPlotDlg ();
newd->Create(IDD_PLOT,this->GetParentOwner());
newd->SetParent(this->GetParentFrame()->GetParent());
newd->ShowWindow(SW_SHOW);
I want to close this dialog when a different window closes (not the parent). How can I achieve this?
Thanks.
Just, save CPlotDlg* to other window which will be used for closing CPlotDlg window.
If the closer window is SomeWhereDlg,
class SomeWhereDlg
{
public:
...
...
CPlotDlg* m_plotDlg;
};
void SomeWhereDlg::SetPlotDlg(CPlotDlg* plotDlg)
{
ASSERT(plotDlg);
if(plotDlg == nullptr) { return;}
m_plotDlg = plotDlg;
}
And then, when create CPlotDlg window, save the pointer.
CPlotDlg* newd = new CPlotDlg ();
//Save newd(CPlotDlg*) to somewhere
//i.e) specific window which will close this newd window
//SomeWhereDlg->SetPlotDlg(newd);
newd->Create(IDD_DIALOG1,this->GetParentOwner());
newd->SetParent(this);
newd->ShowWindow(SW_SHOW);
if a closing event occur, just call Close() or delete, etc via m_plotDlg.
To close the modeless dialog save the pointer as CodeDreamer shows, and call m_plotDlg->DestroyWindow()

How do I detect if a modeless CDialog has been closed?

I have followed this question to make a non-modal/modeless dialog:
How to display a non-modal CDialog?
I'm using MFC/C++ in VS2008. I'm more fluent with C# and .net than with MFC and C++.
I have a menu item in my form that launches the dialog. There can only be one instance of the dialog opened. The dialog displays fine. I can close it by clicking the X in the corner and it closes when I close the main form. The problem I am having is the dialog cannot be opened again after I click the X to close the dialog. I know it is because the pointer is never set back to NULL.
I have this in my form's header file:
CChildDialog *m_pDialog;
I have this part in my form's constructor:
m_pDialog = NULL;
When clicking on a menu item I have this code in the menu item's method (I modified it from the other SO answer because I only want one instance of the dialog opened):
if(m_pDialog == NULL)
{
// Invoking the Dialog
m_pDialog = new CChildDialog();
BOOL ret = m_pDialog->Create(IDD_CHILDDIALOG, this);
if (!ret) //Create failed.
{
AfxMessageBox(_T("Error creating Dialog"));
}
m_pDialog->ShowWindow(SW_SHOW);
}
Now I know I need to execute this part and set the pointer to NULL, but I don't know where to put this:
// Delete the dialog once done
delete m_pDialog;
m_pDialog = NULL;
Do I need to keep monitoring if the dialog has been disposed? Is there an event triggered to the parent form when the dialog is closed?
If you want to recycle the contents of the window after closing it with X, you have to handle the WM_CLOSE message in your dialog:
void CChildDialog::OnClose()
{
ShowWindow(SW_HIDE);
}
Then in the code that opens the window:
if(m_pDialog == NULL)
{
// Invoking the Dialog
m_pDialog = new CChildDialog();
BOOL ret = m_pDialog->Create(IDD_CHILDDIALOG, this);
if (!ret) //Create failed.
{
AfxMessageBox(_T("Error creating Dialog"));
}
}
m_pDialog->ShowWindow(SW_SHOW); //moved outside the if(m_pDialog == NULL)
Hope it can help
If you want to delete the modeless dialog, then just do so.
If you want to delete the dialog's object when the user closed the modeless dialog you might take a look at WM_PARENTNOTIFY. If a child window is destroyed and the child windows has not the extended window style WS_EX_NOPARENTNOTIFY set, then windows sends a WM_PARENTNOTIFY with wParam=WM_DESTROY to the parent window. You should implement a handler for that message in the parent window and check if it's the modeless dialog that is being destroyed.
I had the question drafted up and was ready to post it, but then I had an idea and ended up solving my own problem. So for anyone else who has an issue with detecting the closing of a modeless dialog, this is what I did:
void Form1::MenuItemMethod()
{
if(m_pDialog == NULL)
{
// Invoking the Dialog
m_pDialog = new CChildDialog();
BOOL ret = m_pDialog->Create(IDD_CHILDDIALOG, this);
if (!ret) //Create failed.
{
AfxMessageBox(_T("Error creating Dialog"));
}
m_pDialog->ShowWindow(SW_SHOW);
}
else
{
// cannot check if visible at the beginning of method because
// pointer could be NULL and will throw an exception
if(m_pDialog->IsWindowVisible())
{
return;
}
m_pDialog->DestroyWindow();
m_pDialog = NULL;
MenuItemMethod();
}
}
I just ended up checking if the modeless dialog is visible after clicking on the form's menu item again. If it is visible, don't do anything. If not, destroy the existing non-visible dialog, set the pointer to NULL, and recursively call the method again. Since the pointer is now NULL, it should recreate the dialog normally and then return to normal operation.
you have to delete the memory in PostNcDestroy like this
void CChildDialog ::PostNcDestroy()
{
CDialog::PostNcDestroy();
GetParent()->PostMessage(WM_WIN_CLOSED,0,0);
delete this;
}
and send a user defined message to the parent window that your window is closed. In the parent window add a message handler for WM_WIN_CLOSED like
LRESULT CMainDialog::OnMyMethod(WPARAM wParam, LPARAM lParam)
{
m_pDialog = NULL;
return 0;
}

QDialog not closing right away when pressing the X, how to make it NOT on top?

I open QDialog window from QMainWindow. Now when I press the QDialog window
its not always closing in the first press - I need to press few times (3-4) to close it .
I have closeEvent slot that has simple event->accept(); inside it.
This is how I call the QDialog from the main window:
void MyManager::DialogContainerOpen(type t)
{
if(pMyDialogContainer == NULL)
{
pMyDialogContainer = new MyDialogContainer();
}
int returnVal = QDialog::Rejected;
if(!m_bContainer)
{
m_bContainer = true;
int returnVal = pMyDialogContainer->exec();
if(returnVal != QDialog::Accepted ) {
m_bContainer = false;
}
}
}
This is the first problem.
The second problem is how do i set the QDialog windows NOT to be allays on top? (I don’t want it to block the parent window.
UPDATE
well i found out that the function from the MainWindow that showing the contexMenu
and inside it has the connect single/slot is keeps to invoke so i just used the disconnect
i dont know if its the best sulotion but its working.
now i juat have the final problem .
here is the code i hope its ok
void MainWindowContainer::ShowContextMenu(const QPoint& pos) // this is a slot
{
QModelIndex modelIndx;
QPoint globalPos = ui.treeView_mainwindow->mapToGlobal(pos);
bool b1 = connect(OpenAction, SIGNAL(triggered()),m_SignalMapper, SLOT(map()) );
m_SignalMapper->setMapping(OpenAction,voidID);
bool b2 = connect(m_SignalMapper, SIGNAL(mapped(QString)), this, SLOT(OpenWin(QString)));
QAction* selectedItem = ContextMenu.exec(globalPos);
}
void MainWindowContainer::OpenWin(QString gid)
{
//disconnect(sender0, SIGNAL(overflow()),receiver1, SLOT(handleMathError()));
disconnect(m_SignalMapper, SIGNAL(mapped(QString)),this, SLOT(OpenWin(QString)));
disconnect(OpenAction,SIGNAL(triggered()),m_SignalMapper, SLOT(map()));
....
....
}
For your second question, the term you are looking for is modal vs modeless dialogs. The QDialog documentation tells exactly how you create non-modal dialogs:
Modeless dialogs are displayed using show(), which returns control to the caller immediately.
i.e. don't use exec() as that will make a modal dialog (which blocks the parent).
You should not connect the same signal/slot more than once unless you want the action run multiple times. All you need to do is to connect the QAction's signal to the slot once. This is usually done in the constructor (or a dedicated function called from the constructor) where you create the action.

How do I make Qt dialog get info while locking main window from user?

I have a Qt main window that will pop up a dialog box that has an OK and Cancel button. This dialog has a simple spinner that asks a user for a number that should be returned to the main window when OK or Cancel is pressed (for cancel, it will just send back -1).
I thought about using code in a signal in mainWindow.cpp like so:
void mainWindow::slot_openNumberDlg(){
// Create a new dialog
numberDlg dlg( this );
// Show it and wait for Ok or Cancel
if( dlg.exec() == QDialog::Accepted ){
return;
}
}
But how would I return the value of the spinner in the dialog back to main window if it is destroyed when the button is pressed?
I also thought about an alternative solution where I would initialize the dialog in mainWindow's constructor, but it isn't shown until a signal activated a slot in mainWindow.cpp like so:
// Create as pointer in main.h:
numberDlg *dlg;
// Initialize pointer and setup listener in constructor (main.cpp):
mainWindow::mainWindow(){
dlg = new numberDlg();
QObject::connect( dlg, SIGNAL( sig_retVal(int) ), ui->widget, SLOT( slot_showNum(int) ) );
}
// Make a function to handle show/close:
void mainWindow::slot_numberOpenClose(bool trigger){
if(trigger){
dlg->show();
} else {
dlg->close();
}
}
The above code would be able to send the number from the dialog's spinner to mainWindow using a signal/slot, but it won't lock out mainWindow. I do not want the user to continue using mainWindow until the dialog has been accepted or rejected.
Any ideas on how I can lock the main window and send a number back to main on dialog close?
What you should do is create a function in your dialog class that returns the value you want, and use it like so from your main window:
void mainWindow::slot_openNumberDlg(){
// Create a new dialog
numberDlg dlg( this );
// Show it and wait for Ok or Cancel
if( dlg.exec() == QDialog::Accepted ){
m_theValue = dlg.myValue(); // <--
return;
}
} // dlg goes out of scope
dlg won't be destroyed until it goes out of scope where I placed the comment, so your method will work fine.
use dlg.setWindowModality(Qt::WindowModal); :
void mainWindow::slot_openNumberDlg(){
numberDlg dlg( this );
dlg.setWindowModality(Qt::WindowModal);//blocks input to its parent window
if( dlg.exec() == QDialog::Accepted )
{
dlg.setResult(true);//mask
return;
}
else if(dlg.exec() == QDialog::Rejected)
{
dlg.setResult(false);//mask
return;
}
}
if(dlg.getResult() == true)//value usable only when accepted
{
m_theValue = dlg.myValue();
}
If you want more accurate result, override the closeEvent() method of QWidget to handle the exit button of your dialog. (or you could set default value as false)
void numberDlg::closeEvent(QCloseEvent *event)
{
setResult(false);
close();
}