Create widget modal or detect mousePressEvent outside QDialog
-
Hi, In my app, I want to display a window modal at a moment. But if the user press the mouse outside the widget, the widget will be closed.
I tried to detect in a QDialog a solution to detect mouse outside the widget but I can't
And I tried to create a new widget as a modal widget too but it's same, I can't...
Anyone?My class :
class ModalPopup : public QWidget { Q_OBJECT public: ModalPopup(QPoint cursor); ~ModalPopup(void); QString getAct() { return act; } private: void onActClicked(void); QVBoxLayout *layout; QPushButton *ActNew; QPushButton *ActAdd; QPushButton *ActSub; QString act; protected: void mousePressEvent(QMouseEvent *event) override; };
ModalPopup::ModalPopup(QPoint cursor) { grabMouse(); setWindowFlags(Qt::Window | Qt::FramelessWindowHint); ActNew = new QPushButton(tr("new")); connect(ActNew, &QPushButton::clicked, this, &ModalPopup::onActClicked); ActAdd = new QPushButton(tr("add")); connect(ActAdd, &QPushButton::clicked, this, &ModalPopup::onActClicked); ActSub = new QPushButton(tr("sub")); connect(ActSub, &QPushButton::clicked, this, &ModalPopup::onActClicked); layout = new QVBoxLayout(this); layout->addWidget(ActNew); layout->addWidget(ActAdd); layout->addWidget(ActSub); move(cursor); show(); } ModalPopup::~ModalPopup(void) { delete ActNew; delete ActAdd; delete ActSub; delete layout; } void ModalPopup::onActClicked(void) { act = qobject_cast<QPushButton *>(sender())->text(); close(); } void ModalPopup::mousePressEvent(QMouseEvent *event) { QRect pos = this->geometry(); QPoint cursor = event->globalPos(); if (cursor.x() < pos.x() || cursor.x() > pos.x() + pos.width()) close(); else if (cursor.y() < pos.y() || cursor.y() > pos.y() + pos.height()) close(); }
Thanks in advance
-
Hi,
The goal of a modal dialog is to block all inputs outside of the dialog. Can you explain your use case ?
-
What sound ?
-
Then why make it a modal dialog at all ?
-
it doesn't work
is not a statement that allows to go further.Show what you are doing, how you are using your dialog etc.
-
@bozo6919
As @SGaist says, if you really care about getting rid of the native-OS-windows behaviour of beeping when clicking outside a modal dialog (why?), then you should use a modeless dialog instead and implement your desired behaviour in its totality. Though it's a lot easier to just accept the natural window manager behaviour.... -
My class modalpopup :
class ModalPopup : public QWidget { Q_OBJECT public: ModalPopup(QPoint cursor); ~ModalPopup(void); QString getAct() { return act; } private: void onActClicked(void); QVBoxLayout *layout; QPushButton *ActNew; QPushButton *ActAdd; QPushButton *ActSub; QString act; protected: void mousePressEvent(QMouseEvent *event) override; };
It's implementation :
#include "ModalPopup.hpp" ModalPopup::ModalPopup(QPoint cursor) { setWindowFlags(Qt::Window | Qt::FramelessWindowHint); ActNew = new QPushButton(tr("new")); connect(ActNew, &QPushButton::clicked, this, &ModalPopup::onActClicked); ActAdd = new QPushButton(tr("add")); connect(ActAdd, &QPushButton::clicked, this, &ModalPopup::onActClicked); ActSub = new QPushButton(tr("sub")); connect(ActSub, &QPushButton::clicked, this, &ModalPopup::onActClicked); layout = new QVBoxLayout(this); layout->addWidget(ActNew); layout->addWidget(ActAdd); layout->addWidget(ActSub); move(cursor); show(); } ModalPopup::~ModalPopup(void) { delete ActNew; delete ActAdd; delete ActSub; delete layout; } void ModalPopup::onActClicked(void) { act = qobject_cast<QPushButton *>(sender())->text(); close(); } void ModalPopup::mousePressEvent(QMouseEvent *event) { if (event->buttons() != Qt::LeftButton) return; QRect pos = this->geometry(); QPoint cursor = event->globalPos(); if (cursor.x() < pos.x() || cursor.x() > pos.x() + pos.width()) close(); else if (cursor.y() < pos.y() || cursor.y() > pos.y() + pos.height()) close(); }
His call :
void FlowView::dropEvent(QDropEvent *event) { ModalPopup modal(event->pos()); foreach(const QUrl &url, event->mimeData()->urls()) { if (modal.getAct() == "new") _scene->loadThisPath(url.toLocalFile()); else if (modal.getAct() == "add") _scene->add(url.toLocalFile()); } }
But the widget is never display
-
@bozo6919
Your modal is modeless now. The constructor ends with ashow()
. That does not block, and it will not actually show till the next time the event loop is hit. Your code (dropEvent
) constructs it, then immediately tests some action, and then the local variable modal goes out of scope and the function exits, destroying the dialog (step through in debugger or put in someqDebug()
s to see the flow). Hence you probably see nothing.If you are going to use a modeless dialog, it's your job to allow the event loop to be hit and the modeless to stay in existence & open until such time as the user does something where you want it closed.
-
-
@bozo6919
If you cannot rewrite your logic such that the actions are executed from within the dialog on button press, instead of returning to calling code and having it do it, then I think (untested) you can simulate a modal dialog via something like:- Create a
QEventLoop
at the very start ofdropEvent
. - Pass the
QEventLoop *
to theModalPopup
constructor. - Have
dropEvent
goloop.exec()
after theModalPopup
constructor. - Have the actions in
ModalPopup
goloop.quit
.
Or, you could put the
QEventLoop
into theModalPopup
constructor right at the end after theshow()
(or in another function) if that seems easier, keeping all the code inModalPopup
. - Create a
-
I have try again :
#include "ModalPopup.hpp" ModalPopup::ModalPopup(QPoint cursor) : loop(this) { setWindowFlags(Qt::Window | Qt::FramelessWindowHint); ActNew = new QPushButton(tr("new")); connect(ActNew, &QPushButton::clicked, this, &ModalPopup::onActClicked); ActAdd = new QPushButton(tr("add")); connect(ActAdd, &QPushButton::clicked, this, &ModalPopup::onActClicked); ActSub = new QPushButton(tr("sub")); connect(ActSub, &QPushButton::clicked, this, &ModalPopup::onActClicked); layout = new QVBoxLayout(this); layout->addWidget(ActNew); layout->addWidget(ActAdd); layout->addWidget(ActSub); move(cursor); show(); loop.exec(QEventLoop::AllEvents); } ModalPopup::~ModalPopup(void) { delete ActNew; delete ActAdd; delete ActSub; delete layout; } void ModalPopup::onActClicked(void) { act = qobject_cast<QPushButton *>(sender())->text(); loop.quit(); close(); } void ModalPopup::mousePressEvent(QMouseEvent *event) { if (event->buttons() != Qt::LeftButton) return; QRect pos = this->geometry(); QPoint cursor = event->globalPos(); if (cursor.x() < pos.x() || cursor.x() > pos.x() + pos.width()) { loop.quit(); close(); } else if (cursor.y() < pos.y() || cursor.y() > pos.y() + pos.height()) { loop.quit(); close(); } }
My widget is displayed, but I never enter in my function mousePressEvent so, my window is never closed :/
-
@bozo6919
And are you saying yourmousePressEvent()
function would be entered if you removed theloop.exec()
? How do you know the function is not even entered (please put some debug in)?I don't really get the concept here. I believe your
ModalPopup::mousePressEvent()
is intending to recognise that a mouse click's coordinates are outside of the "dialog" and then close itself, right? But why would a mouse press event outside of a window be sent to that window? I would have thought you'd need this test in the app/main window's event filter or widget or something, not in an override of the window you want to close? Unless I'm not getting it....(P.S. There's a window style like "Popup" [
Qt::Popup
] or similar, I believe, which does auto-dismiss on click outside without beep, which I think is what you're wanting. I don't know if it stays there while you interact with its buttons. But if that would do what you want it would be a lot simpler, you sure that won't do you...)EDIT: The longer I think about this, I wonder if the last paragraph above is your problem? Go back to how you started. If I were doing this I would expect to create a modeless dialog (you said you know how to do that). But the code to recognise the "click outside" (elsewhere in your app, e.g. on the main window) cannot be written in the dialog (I assume it won't be sent the event). I would expect that code to be in your other window or main event loop, and there you would recognise a click on it and tell the modeless dialog to close if it's open. No?
I see stuff like https://stackoverflow.com/a/16513021/489865, where the OP does
2.I installed an event filter with my Qt::tool window and I began receiving events that assisted me in understanding when other parts of my application were clicked, or if the application itself lost focus to another application. This was what I needed, functionality wise. I could also get an event when users click the non-client areas of the application's main window, such as the windows caption so that I can close it when dragging begins etc.
All sounds hideous...
-
Thanks a lot, I have some result, my window displays and closed when I click outside. But I'm searching for how close the QEventLoop when I switch windows whit Alt+Tab
In my constructor, I added :setWindowFlags(Qt::Popup | Qt::FramelessWindowHint); setAttribute(Qt::WA_NoSystemBackground, true); setAttribute(Qt::WA_TranslucentBackground, true); setWindowFlag(Qt::WindowType::NoDropShadowWindowHint, true); setFocusPolicy(Qt::ClickFocus);
And in my class, a function reimplemented :
void ModalPopup::leaveEvent(QEvent *event) { if (event->type() == QEvent::ApplicationDeactivate) loop.quit(); }
But with no effect :/