This is sort of a chicken and egg problem. I'd like my widget window to be closed when the mouse clicks outside. As I understand it, there will be no mouse events for my widget for a click occurring outside of it. There is a SetFocus slot, but where is its counterpart or focus loss? "focusOutEvent" doesn't get called for my class.
My widget window is a child window of a widget always shown on my main window and it's a "Qt::ToolTip", so I assume some problems could arise from that fact. Any way around that?
My Goal: I have a custom toolbar widget where buttons on it may have “drop down” widgets. These drop down widgets have no standard windows frame. I don’t want them to “steal” caption focus from the main window and I want them to disappear as soon as the user clicks ANYWHERE on the screen outside of their region. I have having serious difficulties finding a strategy that’s not compromise on Qt to get this done.
Am I missing something? (bet I am).
I used:
setWindowFlags(Qt::FramelessWindowHint | Qt::Popup);
This seems to work well on OSX and Windows. My window appears correctly, does not steal the focus from my main window's caption, and the focus loss event is called correctly as soon as I click outside of it.
If your widget could have focus, and 'steal' the caption focus of some of your other widgets, it would have been easier. Something like this could work:
class ToolBarWidget : public QWidget
{
Q_OBJECT
public:
explicit ToolBarWidget(QWidget * parent = 0)
{
setFocusPolicy(Qt::ClickFocus);
}
protected:
void focusOutEvent(QFocusEvent * event)
{
close();
}
}
And when you create any of your widgets you'd do:
ToolBarWidget * pWidget = new ToolBarWidget(this);
pWidget->show();
pWidget->setFocus();
Done! Well, I guess not quiet. first, you don't want the ToolBarWidget to get any focus in the first place. And second, you want for the user to be able to click anywhere and the ToolBarWidget to be hidden.
So, you may keep track of every ToolBarWidget that you create. For example, in a 'QList ttWidgets' member variable. Then, whenever you create a new ToolBarWidget, you'd do this:
ToolBarWidget * pWidget = new ToolBarWidget(this);
pWidget->installEventFilter(this);
pWidget->show();
and in your main widget class, implement the eventFilter() function. Something like:
bool MainWidget::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::FocusOut ||
event->type() == QEvent::KeyPress ||
event->type() == QEvent::MouseButtonPress)
{
while (!ttWidgets.isEmpty()) {
ToolBarWidget * p = ttWidgets->takeFirst();
p->close();
p->deleteLater();
}
}
return MainWidget::eventFilter(obj, event);
}
And that will work. Because this way, even though your ToolTabWidgets aren't getting focus, some other widget in your main widget has focus. And once that changes (whether the user clicked out of your window, or on another control inside it, or in this case, a key or mouse button is pressed, the control will reach that eventFilter() function and close all your tab widgets.
BTW, in order to capture the MouseButtonPress, KeyPress etc. from the other widgets, you would either need to installEventFilter on them too, or just reimplement the QWidget::event(QEvent * event) function in your main widget, and look for those events there.
you can do this by using QDesktopWidget.h like this
void MainWindow::on_actionAbout_triggered()
{
AboutDialog aboutDialog;
//Set location of player in center of display
aboutDialog.move(QApplication::desktop()->screen()->rect().center() -aboutDialog.rect().center());
// Adding popup flags so that dialog closes when it losses focus
aboutDialog.setWindowFlags(Qt::Popup);
//finally opening dialog
aboutDialog.exec();
}
This is what worked for me in order to not steel the focus from the main application:
.h
bool eventFilter(QObject *obj, QEvent *event) override;
.cpp
bool Notification::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::MouseButtonPress)
deleteLater();
return QObject::eventFilter(obj, event);
}
...
// somewhere else (i.e. constructor, main window,...)
qApp->installEventFilter(this);
OP's own answer is great for Qt versions below 4.8, but as they mention in their answer, it does not work for versions above that. The Qt::Popup widget will not disappear when the mouse is clicked outside of the widget, and it will sink all of the input that would normally close it.
Upon further investigation, this is only an issue for non-dialog widgets. A QDialog using Qt::Popup will properly close when the user clicks outside of it, but any other QWidget, like a QFrame, will not. So in order to work around this behavior change in Qt 4.8, all that is necessary is to wrap the widget in a QDialog.
Related
I would like to create a QWidget that acts like a pop-up, and I am having trouble figuring out how to do it.
I want to create a sidebar/drawer that gets shown when the user clicks a button. The user will briefly interact with child widgets of the sidebar. The tricky part is that I want the sidebar to be hidden automatically when the user clicks elsewhere in the application. Worst case I can require the user to manually toggle the sidebar, but automatically hiding it is convenient.
I tried or investigated:
Setting the Qt::Popup window flag, but the sidebar needs to be a widget, not a separate window.
Listening for focus events, but I want to support keyboard navigation. The user should be able to Tab to other widgets while the sidebar remains visible. The sidebar should be hidden when the user actually interacts with the other widgets (such as hitting Enter).
Adding an event filter on the top-level widget, but it doesn't receive events that occur on its children (such as a button being clicked).
Adding an invisible "overlay" widget over the rest of the application, but I want the user to be able to directly click on other widgets. Capturing clicks in the invisible overlay would require the user to click twice (once to clear the overlay, then again to interact with other widgets).
Is this just not feasible?
One possibility might be to install an event filter on the QApplication instance itself. Note, though, that application wide event filters can have a noticeable performance impact so it should only be installed as/when necessary. A mock-up might look something like...
class sidebar: public QWidget {
using super = QWidget;
protected:
virtual bool eventFilter (QObject *obj, QEvent *event) override
{
/*
* Your stuff goes here...
*/
if (auto *widget = dynamic_cast<QWidget *>(obj)) {
if (isAncestorOf(widget)) {
/*
* obj refers to a QWidget of some description that is
* a child of this sidebar instance.
*/
} else {
/*
* widget _isn't_ a child of this sidebar so may need
* to take action (including hiding) depending on the
* specifics of widget and the event details.
*/
}
}
return super::eventFilter(obj, event);
}
virtual void showEvent (QShowEvent *event) override
{
super::showEvent(event);
qApp->installEventFilter(this);
}
virtual void hideEvent (QHideEvent *event) override
{
super::hideEvent(event);
qApp->removeEventFilter(this);
}
};
Will require a certain amount of experimentation obviously :-)
I want hide a QWidget when mouse clicks out of that widget just like it have a popup flag:
auto widget = new QWidget(this);
widget->setWindowFlag(Qt::Popup); // this widget will hide, when mouse click out of that widget.
For some reason, I can't set these flags and must implement some thing myself which behaves like this.
Or can I get a mouse event out of that widget?
Solution:
As I said, I can't use Qt flags and have to implement the similar behavior myself.My solution is installEventFilter to QApplication, in the override method eventFilter,I filter the QMouseEvent and send a signal.
Yes, you can get a mouse event out of the widget.
Make a custom widget and reimplement mousePressEvent, which will catch the last click (the ouside-the-widget click that hides the "popup"). But be careful to add the call to QWidget::mousePressEvent(event) in the end, otherwise the last click will be lost and your widget will remain onscreen.
CustomWidget::CutomWidget(QWidget *parent) : QWidget(parent)
{
setWindowFlags(Qt::Popup);
}
void CustomWidget::mousePressEvent(QMouseEvent *event)
{
if (!this->underMouse()) {
//if the click is not on the widget, i.e. if it's the click that hides it,
// you caught it, do what you want to do here.
}
QWidget::mousePressEvent(event);
}
Hope it helps.
my eventFilter doesn't work as expected. I either missunderstood its behaviour or I have a problem in my program.
In my program I have a QScrollArea with a QLabel inside to display an Image (just like in the Qt Image Viewer Example), but if the user scrolls his MouseWheel I don't want the ScrollArea to scroll, but the Image to scale. Therefore I implemented an eventFilter for my Label and my ScrollArea.
bool RBDP::eventFilter(QObject *target, QEvent *event)
{
if (target == scrollArea || target == iv) {
if (event->type() == QEvent::Wheel) {
QWheelEvent *w = static_cast<QWheelEvent *>(event);
iv->scaleImage(w->delta());
return true;
}
I know that the eventFilter actually gets triggered when you scroll. The problem is that the ScrollArea ALSO scrolls, i.e. if you use your MouseWheel, the Image scales and the ScrollArea scrolls!
As far as I know, the event shouldn't be propagated to its original target, if the eventFilter returns true.
I installed in my main class eventFilters for both, the scrollArea and iv (which is my QLabel).
scrollArea->installEventFilter(this);
iv->installEventFilter(this);
How do I prevent the ScrollArea from scrolling? I know this isn't much code, but the program is kind of complex already and I don't know, what else could be useful. So please let me know, if you need to see other code segments.
Thanks in advance
For the last two days, I've been searching for a way to pass mouse events to the widgets behind a widget used as a container/parent for it's children. I know there is a way to make a widget transparent for mouse events like this:
QWidget w;
w.setAttribute( Qt::WA_TransparentForMouseEvents );
But this also disables the delivery of mouse events to its children! I want the children of the front widget and the widgets behind the front widget to receive the mouse events.
Qt::WA_TransparentForMouseEvents: When enabled, this attribute disables the delivery of mouse events to the widget and its children.
Mouse events are delivered to other widgets as if the widget and its
children were not present in the widget hierarchy; mouse clicks and
other events effectively “pass through” them. This attribute is
disabled by default.
If you have any idea about how to make a widget transparent for mouse events but not it's children then please share!
At last I found a solution :)
QWidget::setMask ( const QRegion & region )
https://doc.qt.io/qt-5/qwidget.html#setMask-1
http://qt-project.org/doc/qt-4.8/qwidget.html#setMask
I found the solution here: http://www.qtcentre.org/archive/index.php/t-3033.html
QRegion reg(frameGeometry());
reg -= QRegion(geometry());
reg += childrenRegion();
setMask(reg);
Now children of the front widget and the widgets behind the front widget respond to the mouse events as required!
Remember, you would need to call these lines again whenever the front widget is re-sized to recalculate the geometry for the mask!
void someWidget::resizeEvent(QResizeEvent *e){
QWidget::resizeEvent(e);
QRegion reg(frameGeometry());
reg-=QRegion(geometry());
reg+=childrenRegion();
setMask(reg);
}
The solution of the OP is awesome and very elegant. Just for completeness, another option would be to ignore mouse events when they reach the container widget. It can be done either by sub-classing or through a eventFilter.
This solution can be helpful if the widget hides/shows many children widgets dynamically and computing the mask becomes difficult.
Note: In the case you want to track mouse-move events in background widgets, you'd have to setMouseTracking(true) to receive (and then ignore) the QEvent::MouseMove when no button is pressed.
Example by sub-classing
ContainerWidget::ContainerWidget(...) {
setMouseTracking(true);
}
void ContainerWidget::mouseMoveEvent(QMouseEvent* e) {
e->ignore();
}
void ContainerWidget::mousePressEvent(QMouseEvent* e) {
e->ignore();
}
Example using the event filter
// Assume the container widget is configured in the constructor
MainWindow::MainWindow(...) {
// ...
containerWidget->installEventFilter(this);
containerWidget->setMouseTracking(true);
// ...
}
bool MainWindow::eventFilter(QObject* o, QEvent* e) {
if (o == containerWidget &&
(e->type() == QEvent::MouseMove || e->type() == QEvent::MouseButtonPress)) {
e->ignore();
return false;
}
return QMainWindow::eventFilter(o, e);
}
In my program, I'd like to have mouseMoveEvent(QMouseEvent* event) called whenever the mouse moves (even when it's over another window).
Right now, in my mainwindow.cpp file, I have:
void MainWindow::mouseMoveEvent(QMouseEvent* event) {
qDebug() << QString::number(event->pos().x());
qDebug() << QString::number(event->pos().y());
}
But this seems to only be called when I click and drag the mouse while over the window of the program itself. I've tried calling
setMouseTracking(true);
in MainWindow's constructor, but this doesn't seem to do anything differently (mouseMoveEvent still is only called when I hold a mouse button down, regardless of where it is). What's the easiest way to track the mouse position globally?
You can use an event filter on the application.
Define and implement bool MainWindow::eventFilter(QObject*, QEvent*). For example
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseMove)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
statusBar()->showMessage(QString("Mouse move (%1,%2)").arg(mouseEvent->pos().x()).arg(mouseEvent->pos().y()));
}
return false;
}
Install the event filter when the MainWindows is constructed (or somewhere else). For example
MainWindow::MainWindow(...)
{
...
qApp->installEventFilter(this);
...
}
I had the same problem, further exacerbated by the fact that I was trying to call this->update() to repaint the window on a mouse move and nothing would happen.
You can avoid having to create the event filter by calling setMouseTracking(true) as #Kyberias noted. However, this must be done on the viewport, not your main window itself. (Same goes for update).
So in your constructor you can add a line this->viewport()->setMouseTracking(true) and then override mouseMoveEvent rather than creating this filter and installing it.