Destroy modeless wxDialog on close - c++

I want to destroy a modeless wxDialog when the window is closed.
What I tried is:
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
class MyDialog : public wxDialog {
public:
MyDialog() : wxDialog(nullptr, wxID_ANY, "Dialog", wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {
// Comment next 2 lines out for it to work as expected.
auto* sizer = CreateButtonSizer(wxOK);
this->SetSizerAndFit(sizer);
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &) { this->Destroy(); });
}
};
class MyApp : public wxApp {
public:
auto OnInit() -> bool override {
if (!wxApp::OnInit()) {
return false;
}
auto *dialog = new MyDialog();
dialog->Show(true);
return true;
}
};
wxIMPLEMENT_APP(MyApp);
But for some reason the application does not exit.
Edit, from the comments thanks to #catalin:
wxVersion: v3.1.5
Deleting everything in the ctor except the Bind exits the app as expected.
But just having something simple as a new wxButton(this, wxID_OK, "OK") in the ctor prevents the app from exiting.
If the ctor is empty the app also doesn't exit when closing the dialog.
Edit2:
Pressing the button or "ESC" does not exit the app. Pressing the "X" in top right (on Windows) closes it, but shouldn't the behaviour be the same according to the documentation:
EVT_CLOSE(func): The dialog is being closed by the user or programmatically (see wxWindow::Close). The user may generate this event clicking the close button (typically the 'X' on the top-right of the title bar) if it's present (see the wxCLOSE_BOX style) or by clicking a button with the wxID_CANCEL or wxID_OK ids.
Last Edit (I hope):
I somewhat solved it. Adding Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {this->Destroy();}); closes the app on button click, the Bind for wxEVT_CLOSE_WINDOW closes it on "X" click. But still the wxOK Button should generate a wxEVT_CLOSE_WINDOW not a wxEVT_BUTTON event according to doc.

Related

Qt: Show popup menu, close if I click anywhere outside of it

I want to show a menu in Qt with the following functionality: clicking anywhere outside of the menu will close it, but the click will be processed by the receiving widget. As far as I know, this rules out using setWindowFlag(Qt::Popup) because the click that closed the popup will be 'eaten' and not processed any further. I'd love to know if there is a way around this.
For the time being, I'm overriding focusOutEvent like so:
class Menu : public QWidget
{
Q_OBJECT
public:
Menu(QWidget* parent = nullptr)
: QWidget(parent){
setWindowFlags(Qt::FramelessWindowHint);
resize(200, 100);
}
public slots:
void showMenu(QPoint point)
{
move(point);
show();
setFocus(); // so that focusOutEvent will be trigerred
}
protected:
void focusOutEvent(QFocusEvent *event) override
{
hide();
}
};
And I'm using it like this:
class Window : public QMainWindow
{
Q_OBJECT
public:
Window(): m_menu()
{
auto* list = new QListWidget();
list->addItems(QStringList() << "item1" << "item2" << "item3");
connect(list, &QListWidget::itemClicked, [this](){
m_menu.showMenu(QCursor::pos());
});
// Layout stuff
auto central_widget = new QWidget();
auto layout = new QVBoxLayout(central_widget);
layout->addWidget(list);
layout->addItem(new QSpacerItem(20,200, QSizePolicy::Minimum, QSizePolicy::Expanding));
setCentralWidget(central_widget);
}
public:
~Window(){}
private:
Menu m_menu;
};
Single clicking on any of the items in the list will show the menu and clicking anywhere on the grey background will close the menu.
The issue I'm facing is that when the menu is shown, focus is lost from the main window (photo on the right, the titlebar buttons and selected item are greyed out). Focus is lost because of calling setFocus on the menu but if I don't use it then the QFocusOutEvents are not trigerred and the menu doesn't close.
The same happens if I use setAttribute(Qt::WA_ShowWithoutActivating) on the menu.
So here are my questions:
Is there a way to use Qt::Popup without the 'eaten' clicks issue?
If not, is there a way to show the menu without losing the focus from the main window but still trigger the focusOutEvents as necessary?
Using Qt 5.15.1 on MacOS 11.2.3. Thanks for reading through my long post!

When and how to properly destroy QMenu context menu?

I allow custom context menu to appear over a table. This is how the menu is generated, using a generic function that accepts target widget and coordinates:
#include <QMenu>
void MainWindow::makeContextMenu(const QPoint& pos, QWidget* target)
{
QMenu *menu = new QMenu(this);
menu->addAction(new QAction("Action 1", menu));
menu->addAction(new QAction("Action 2", menu));
menu->addAction(new QAction("Action 3", menu));
// Notify window about clicking
QObject::connect(menu, &QMenu::triggered, this, &MainWindow::menuClicked);
// If this is a scroll area, map coordinates to real app coordinates
if(QAbstractScrollArea* area = dynamic_cast<QAbstractScrollArea*>(target))
menu->popup(area->viewport()->mapToGlobal(pos));
else
menu->popup(pos);
}
The problem is that the QMenu* menu never gets destroyed and removed from memory. It persists as MainWindow's child even after it's hidden.
What should I do? Can I set the menu to delete itself? Or should I reuse the same instance of menu or maybe save it into same pointer?
It doesn't have to be so complicated. That's it already:
menu->setAttribute(Qt::WA_DeleteOnClose);
That way when the QMenu is closed, the class is deleted as soon as the event loop is entered again. And it doesn't matter if an action was triggered or the popup was just closed.
To prove my answer, you can test it yourself by checking when the menu is created and if the 'deleted' message is triggered with the same address:
qDebug() << "created" << (qintptr)menu;
connect(menu, &QMenu::destroyed,
this, [menu]() { qDebug() << "deleted" << (qintptr)menu; });
From your code, it seems like menu should be deleted after this event has taken place?
// Notify window about clicking
QObject::connect(menu, &QMenu::triggered, this, &MainWindow::menuClicked);
Can I set the menu to delete itself?
Yes, you can have the object delete itself like this:
// Notify window about clicking
QObject::connect(menu, &QMenu::triggered, this, &MainWindow::menuClicked);
QObject::connect(menu, &QMenu::triggered, menu, &QMenu::deleteLater);
If you are worried about the order of those slots being called, see this
Or should I reuse the same instance of menu or maybe save it into same
pointer?
Well, you can do something like
//Your constructor
MainWindow::MainWindow(....)
{
menu = nullptr;
....
}
//Make context Menu
void MainWindow::makeContextMenu(const QPoint& pos, QWidget* target)
{
if(menu)
delete menu;
menu = new QMenu(this);
....
}
As for the MainWindow::~MainWindow() destructor, it will take care of the menu's clean up. Since MainWindow (which is a QObject derived class) automatically deletes all children
Lastly, you can simply have the menu as a member of MainWindow, and whenever you need to have fresh actions for menu, you can use QMenu::clear to delete all existing actions.
//Your constructor
MainWindow::MainWindow(....)
{
menu = new QMenu(this);
....
}
void MainWindow::makeContextMenu(const QPoint& pos, QWidget* target)
{
menu->clear();
//QMenu *menu = new QMenu(this);
menu->addAction(new QAction("Action 1", menu));
menu->addAction(new QAction("Action 2", menu));
menu->addAction(new QAction("Action 3", menu));
// Notify window about clicking
QObject::connect(menu, &QMenu::triggered, this, &MainWindow::menuClicked);
// If this is a scroll area, map coordinates to real app coordinates
if(QAbstractScrollArea* area = dynamic_cast<QAbstractScrollArea*>(target))
menu->popup(area->viewport()->mapToGlobal(pos));
else
menu->popup(pos);
}
It is possible to delete QMenu when it's hidden. I designed event filter class for that purpose:
#ifndef DELETEONHIDEFILTER_H
#define DELETEONHIDEFILTER_H
#include <QObject>
#include <QEvent>
class DeleteOnHideFilter : public QObject
{
Q_OBJECT
public:
explicit DeleteOnHideFilter(QObject *parent = 0) : QObject(parent) {}
protected slots:
bool eventFilter(QObject *obj, QEvent *event) override {
if(event->type() == QEvent::Hide) {
obj->deleteLater();
}
return false;
}
};
#endif // DELETEONHIDEFILTER_H
It can be used for other objects as well.

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();
}
}

Closing the application by selecting 'Quit' menu item - wxWidgets 3.0

So far I have written some simple code for a wxWidgets application, like creating a menu, frame and a few buttons. To follow the process of exiting, I have this function that shows a message box :
int OnExit( )
{
wxMessageBox( "Closing the application", wxOK | wxICON_INFORMATION )
return 0;
}
Closing the application by clicking close ( X ) button shows the message box and then exits. But closing it by clicking the "Quit" menu item doesn't work for me. I have tried copying some code from an old example and from CodeBlocks basic sample code that comes with wxWidgets project, with no luck. Please show me a method of closing the application from the menu item.
Try searching the web for "wxwidgets close window menu":
wxWidgets Hello World Example
In your OnExit function you need to call the Close method as in the example.
// Build: g++ this.cpp -std=gnu++11 $(wx-config --cxxflags --libs core,base)
#include <wx/wx.h>
class CApp : public wxApp
{
public:
bool OnInit() {
// Create the main frame.
wxFrame * frame = new wxFrame(NULL, wxID_ANY, wxT("demo"));
// Add the menubar
wxMenu * menus[] = {new wxMenu, new wxMenu};
wxString labels[] = {wxT("&File"), wxT("&Help")};
frame->wxFrame::SetMenuBar(new wxMenuBar(2, menus, labels));
menus[0]->Append(wxID_EXIT);
// Bind an event handling method for menu item wxID_EXIT.
this->Bind(wxEVT_MENU, [frame](wxCommandEvent &)->void{
frame->Close();
/* 1. method wxWindow::Close
* 2. event type wxEVT_CLOSE_WINDOW
* 3. method wxTopLevelWindow::OnCloseWindow
* 4. method wxTopLevelWindow::Destroy (overriding wxWindow::Destroy)
* 5. op delete
*/
}, wxID_EXIT);
// Enter the message loop.
frame->Centre(wxBOTH);
frame->Show(true);
return true;
}
int OnExit() {
wxMessageBox("Closing the application", wxEmptyString, wxOK | wxICON_INFORMATION);
return this->wxApp::OnExit();
}
};
wxDECLARE_APP(CApp);
wxIMPLEMENT_APP(CApp);

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.