Propagate custom QEvent to parent widget in Qt/PyQt - qt

Fist, apologies for the length of the question.
I am trying to propagate custom Qt event from child widgets to a top parent widget in order to trigger some action based on event type instead of linking signals.
Qt docs suggests that every event posted with postEvent() that have accept() and ignore() methods can be propagated (meaning each QEvent subclass).
I have tried to override customEvents method instead of events but to no avail.
Python
I've tried this in Python using PyQt4 (Qt version is 4.6).
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Foo(QWidget):
def doEvent(self):
QApplication.postEvent(self, QEvent(12345))
def event(self, event):
event.ignore()
return False
class Bar(QWidget):
def __init__(self, *args, **kwargs):
super(Bar, self).__init__(*args, **kwargs)
self.foo = Foo(self)
layout = QHBoxLayout()
layout.addWidget(self.foo)
self.setLayout(layout)
def event(self, event):
if event.type() == 12345:
self.someEventHandler()
return True
def someEventHandler(self):
print 'Handler in {0}'.format(self.__class__.__name__)
if __name__=='__main__':
app = QApplication([''])
bar = Bar()
bar.show()
bar.foo.doEvent()
app.exec_()
In this example Bar.someEventHandler() would only trigger if event was posted with self.parent() as the first argument like so:
def doEvent(self):
QApplication.postEvent(self.parent(), QEvent(12345))
Which is understandable since the event is passed directly to receiving object.
C++
Similar example in C++:
foobar.h
#ifndef FOOBAR_H
#define FOOBAR_H
#include <QtGui>
class Foo : public QWidget
{
Q_OBJECT
public:
Foo(QWidget *parent = 0);
void doEvent();
bool event(QEvent *);
};
class Bar : public QWidget
{
Q_OBJECT
public:
Bar(QWidget *parent = 0);
Foo *foo;
bool event(QEvent *);
};
#endif // FOOBAR_H
foobar.cpp
#include "foobar.h"
Foo::Foo(QWidget *parent)
: QWidget(parent) {}
void Foo::doEvent() {
QEvent *event = new QEvent(QEvent::User);
QApplication::postEvent(this, event);
}
bool Foo::event(QEvent *event)
{
event->ignore();
return QWidget::event(event);
}
Bar::Bar(QWidget *parent)
: QWidget(parent)
{
foo = new Foo(this);
}
bool Bar::event(QEvent *event)
{
if (event->type() == QEvent::User) {
qDebug() << "Handler triggered";
return true;
}
return QWidget::event(event);
}
main.cpp
#include <QtGui>
#include "foobar.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Bar bar(0);
bar.show();
bar.foo->doEvent();
return app.exec();
}
Same as in python this only works if event is passed directly to an object.
void Foo::doEvent() {
QEvent *event = new QEvent(QEvent::User);
QApplication::postEvent(this->parentWidget(), event);
}
Perhaps I missed the point, is it possible that only Key and Mouse events are propagated upwards?

I've spent some time browsing the Qt source in order to answer your question, and what I finally came up with is this: propagation of events to parent widgets is performed by QApplication::notify, basically a long switch statement with all sorts of events. For example, this is how it's done for QEvent::WhatsThisClicked:
...
case QEvent::WhatsThisClicked:
{
QWidget *w = static_cast<QWidget *>(receiver);
while (w) {
res = d->notify_helper(w, e);
if ((res && e->isAccepted()) || w->isWindow())
break;
w = w->parentWidget();
}
}
...
Now, the salient point: this is not performed for user defined events (and many other explicitly handled standard Qt events, as well), since the default clause is:
default:
res = d->notify_helper(receiver, e);
break;
And notify_helper doesn't propagate events. So, my answer is: apparently, user defined events are not propagated to parent widgets, you will have to do it yourself (or better: override QApplication::notify (it's a virtual public member) and add event propagation for your event(s)).
I hope that helps.

Re-implementation of QApplication.notify in Python which would propagate custom events.
In Qt notfy_helper makes sure the event filters are called (both QApplications and receivers) but I skipped that as I don't need them and notfy_helper is private member.
from PyQt4.QtCore import QEvent
from PyQt4.QtGui import QApplication
class MyApp(QApplication):
def notify(self, receiver, event):
if event.type() > QEvent.User:
w = receiver
while(w):
# Note that this calls `event` method directly thus bypassing
# calling qApplications and receivers event filters
res = w.event(event);
if res and event.isAccepted():
return res
w = w.parent()
return super(MyApp, self).notify(receiver, event)
And instead of using instance of QApplication we use instance of our subclass.
import sys
if __name__=='__main__':
app = MyApp(sys.argv)

As an alternative to rebus's answer, this snippet implements manual propagation of an event:
def _manual_propagate(target, evt):
app = QtGui.QApplication.instance()
while target:
app.sendEvent(target, evt)
if not evt.isAccepted():
if hasattr(target, 'parent'):
target = target.parent()
else:
target = None
return evt.isAccepted()
Note that this uses sendEvent, and thus must be called on the thread that the target object lives on. That limitation can be worked around with more indirection.
Note that you'll need to call _manual_propagate /instead of/ sendEvent yourself. This is a "less automatic" version of rebus's technique.
In addition, because this version uses sendEvent, event filters on objects are properly called.
The relevant reason custom events are not propagated, at least in Qt 4.8, is this:
//qobject.cpp
bool QObject::event(QEvent *e)
{
switch (e->type()) {
//several cases skipped
default:
if (e->type() >= QEvent::User) {
customEvent(e);
break;
}
return false;
}
return true;
}
//more skips
/*!
This event handler can be reimplemented in a subclass to receive
custom events. Custom events are user-defined events with a type
value at least as large as the QEvent::User item of the
QEvent::Type enum, and is typically a QEvent subclass. The event
is passed in the \a event parameter.
\sa event(), QEvent
*/
void QObject::customEvent(QEvent * /* event */)
{
}
That is, QObject::event calls another method when a custom event is received, and then breaks out of it's switch statement and executes return true;'. This return true signals the caller (generally QCoreApplication::notify that the event was handled.

Related

emit signal with delay

the goal that I want to achieve is to emit a signal but delay it some time before it is actually emitted.
My idea for this was something like this:
emitLater(QObject *obj, QEvent *event, int msDelay)
{
QTimer...
connect(timout to obj and event...
start timer with msDelay...
//maybe I might need a public timer of some sort but the idea is clear
}
So this approach failed with the QEvent parameter because Signals could not be converted to QEvents.
I tried calling with std::bind in a couple of variations but I couldn't get it to work:
//the call
std::function<void(void)> signal = std::bind(&CustomPushButton_Header::clicked, &uiWindow->btnHeader_left); //just a random button to connect a valid signal
emitLater(signal, 2000);
//the function
emitLater(std::function<void(void)>, int msDelay)
{
qDebug() << "hi";
}
I would appreciate a solution that works for all Objects and all Signals... maybe only the part which does call the function. I think the delay will turn out easy.
Or you have a different and even easier approach. That would be nice.
some sources I looked in:
https://oopscenities.net/2012/02/24/c11-stdfunction-and-stdbind/
https://stackoverflow.com/a/47104204
There is a function QTimer::singleShot() that can trigger a signal/slot after a certain amount of time. I implemented it like this:
void MyWindow::emitLater(const QObject *obj, const char *signalOrSlot, int msDelay)
{
//use QTimer::singleShot() to trigger the signal/slot after a certain amount of time
QTimer::singleShot(msDelay, obj, signalOrSlot);
}
and use it like this:
void MyWindow::on_pushButton_5_clicked()
{
//trigger the "doing fancy stuff" function after 5 seconds (= 5000 ms)
emitLater(this, SLOT(setText()), 5000);
}
void MyWindow::setText()
{
//do some fancy stuff
ui->label_3->setText("Hello World!");
}
An alternative would be in the answer from ΦXocę 웃 Пepeúpa ツ and uses a lambda expression as call function for the QTimer::timout() signal:
void MyWindow::on_pushButton_5_clicked()
{
//initialize timer
QTimer* t= new QTimer(this);
t->setSingleShot(true);
//do some fancy stuff after timeout
connect(t, &QTimer::timeout, [this]()
{
ui->label_3->setText("Hello World!");
});
//start the timer with delay of 5 seconds (= 5000 ms)
t->start(5000);
}
you can use a timer for that, in the exmple above, you react to the click slot in a button, there define a QTimer, connect what is called when it times out(in this case is a lambda) and then you start the timer :)
void MyWindow::on_pushButton_5_clicked()
{
QTimer* t= new QTimer(this);
connect(t, &QTimer::timeout, [this](){ui->label_3->setText("done!");});
t->start(5000); //after 5 seconds
}
Here is my implementation in Python using PySide2.
Firstly I created a class:
class DelayedSignal:
def __init__(self, timeout, func):
self.timeout = timeout
self.func = func
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.setInterval(1000 * timeout)
self.timer.timeout.connect(func)
#Slot()
def __call__(self):
self.timer.stop()
self.timer.start()
Here is the rest of the code therefore all together is a minimum working example:
from PySide2.QtCore import QTimer, Slot
from PySide2.QtWidgets import QPushButton, QWidget, QHBoxLayout
class AppWindow(QWidget):
def __init__(self):
super().__init__()
self.btn = QPushButton("PRESS", self)
self.setLayout(QHBoxLayout())
self.layout().addWidget(self.btn)
self.delayed_signal = DelayedSignal(timeout=2, func=self.target)
self.btn.clicked.connect(self.delayed_signal)
#Slot()
def target(self):
print("END")
if __name__ == '__main__':
import sys
from PySide2.QtWidgets import QApplication
app = QApplication(sys.argv)
window = AppWindow()
window.show()
sys.exit(app.exec_())

Global shortcut in QML

I am trying to make an application in QML (Qt 5.5) that is always running and shows up when the user presses alt+space.
I have tried using the Action class in QML, but it only works when the window has the focus, not when the window is not visible.
I've also tried QShortcut (which is not ideal, since my application is not based on QWidget) but I had no results.
Also using eventFilter on QApplication doesn't seem to work when the window is not visible.
Is there a way of doing it?
I've never implemented an app in QML but I think what you are looking for is the Global Shortcut Module (http://libqxt.bitbucket.org/doc/tip/qxtglobalshortcut.html).
It is a "A global shortcut triggers even if the application is not active."
If a system wide global shortcut is what you need, I don't recall Qt having anything to offer out of the box, much less QML.
You will have to resort to the platform specific APIs to get this thing done. Like for example on windows that would be the BOOL WINAPI RegisterHotKey() function.
I have resorted to use XGrab and create a subclass of QThread (because of the separate event loop) to integrate it with the Qt signals.
shortcutactivator.h
#ifndef SHORTCUTACTIVATOR_H
#define SHORTCUTACTIVATOR_H
#include <QThread>
class ShortcutActivator : public QThread
{
Q_OBJECT
public:
void run();
signals:
void activated();
public slots:
void end();
private:
bool terminate = false;
};
#endif // SHORTCUTACTIVATOR_H
shortcutactivator.cpp
#include "shortcutactivator.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
void ShortcutActivator::end() {
this->terminate = true;
}
void ShortcutActivator::run() {
Display* dpy = XOpenDisplay(0);
Window root = DefaultRootWindow(dpy);
XEvent ev;
unsigned int modifiers = Mod1Mask; // AnyModifier; // ControlMask | ShiftMask | AnyModifier;
int keycode = XKeysymToKeycode(dpy,XK_space);
Window grab_window = root;
Bool owner_events = False;
int pointer_mode = GrabModeAsync;
int keyboard_mode = GrabModeAsync;
XGrabKey(dpy, keycode, modifiers, grab_window, owner_events, pointer_mode, keyboard_mode);
XGrabKey(dpy, keycode, modifiers | Mod2Mask , grab_window, owner_events, pointer_mode, keyboard_mode);
XGrabKey(dpy, keycode, modifiers | LockMask, grab_window, owner_events, pointer_mode, keyboard_mode);
XGrabKey(dpy, keycode, modifiers | LockMask | Mod2Mask, grab_window, owner_events, pointer_mode, keyboard_mode);
XSelectInput(dpy, root, KeyPressMask );
while(true)
{
XNextEvent(dpy, &ev);
switch(ev.type)
{
case KeyPress:
printf("Key pressed\n");
emit this->activated();
default:
break;
}
if(this->terminate)
break;
}
XCloseDisplay(dpy);
XUngrabKey(dpy,keycode,modifiers,grab_window);
XUngrabKey(dpy,keycode,modifiers | Mod2Mask,grab_window);
XUngrabKey(dpy,keycode,modifiers| LockMask,grab_window);
XUngrabKey(dpy,keycode,modifiers | LockMask | Mod2Mask,grab_window);
}

QLabel click event using Qt?

I'm new in Qt and have a question.
I have QLabel and QLineEdit objects, and when QLabel text is clicked on, I want to set this text in QLineEdit.
Also I have read that QLabel has not clicked signal.
Can you explain how can I do this and write code for me ?!
Either style another type of QWidget such as a specific QPushButton to look like a QLabel and use its clicked() signal or inherit QLabel yourself and emit your own clicked() signal.
See this example:
https://wiki.qt.io/Clickable_QLabel
If you choose the latter option you can pass the text in the signal. Then connect the necessary signals/slots up between the QLabel and the QLineEdit like so:
QObject::connect(&label, SIGNAL(clicked(const QString& text)),
&lineEdit, SLOT(setText(const QString& text)));
A simple way to accomplish that, without a need for any subclassing, is a signal source that monitors the events on some object and emits relevant signals:
// main.cpp - this is a single-file example
#include <QtWidgets>
class MouseButtonSignaler : public QObject {
Q_OBJECT
bool eventFilter(QObject * obj, QEvent * ev) Q_DECL_OVERRIDE {
if ((ev->type() == QEvent::MouseButtonPress
|| ev->type() == QEvent::MouseButtonRelease
|| ev->type() == QEvent::MouseButtonDblClick)
&& obj->isWidgetType())
emit mouseButtonEvent(static_cast<QWidget*>(obj),
static_cast<QMouseEvent*>(ev));
return false;
}
public:
Q_SIGNAL void mouseButtonEvent(QWidget *, QMouseEvent *);
MouseButtonSignaler(QObject * parent = 0) : QObject(parent) {}
void installOn(QWidget * widget) {
widget->installEventFilter(this);
}
};
The emit keyword is an empty macro, Qt defines it as follows:
#define emit
It is for use by humans as a documentation aid prefix only, the compiler and moc ignore it. As a documentation aid, it means: the following method call is a signal emission. The signals are simply methods whose implementation is generated for you by moc - that's why we have to #include "main.moc" below to include all the implementations that moc has generated for the object class(es) in this file. There's otherwise nothing special or magical to a signal. In this example, you could look in the build folder for a file called main.moc and see the implementation (definition) of void MouseButtonSignaler::mouseButtonEvent( .. ).
You can then install such a signaler on any number of widgets, such as a QLabel:
int main(int argc, char ** argv) {
QApplication app(argc, argv);
MouseButtonSignaler signaler;
QWidget w;
QVBoxLayout layout(&w);
QLabel label("text");
QLineEdit edit;
layout.addWidget(&label);
layout.addWidget(&edit);
signaler.installOn(&label);
QObject::connect(&signaler, &MouseButtonSignaler::mouseButtonEvent,
[&label, &edit](QWidget*, QMouseEvent * event) {
if (event->type() == QEvent::MouseButtonPress)
edit.setText(label.text());
});
w.show();
return app.exec();
}
#include "main.moc"
You need to create one Custom Label class, which will inherit QLabel. Then you can use MouseButtonRelease event to check clicking of Label and emit your custom signal and catch in one SLOT.
Your .h file will be as below:
class YourLabelClass : public QLabel{
signals:
void myLabelClicked(); // Signal to emit
public slots:
void slotLabelClicked(); // Slot which will consume signal
protected:
bool event(QEvent *myEvent); // This method will give all kind of events on Label Widget
};
In your .cpp file, your constructor will connect signal & slot as below :
YourLabelClass :: YourLabelClass(QWidget* parent) : QLabel(parent) {
connect(this, SIGNAL(myLabelClicked()), this, SLOT(slotLabelClicked()));
}
Remaining event method and SLOT method will be implemented as below:
bool YourLabelClass :: event(QEvent *myEvent)
{
switch(myEvent->type())
{
case(QEvent :: MouseButtonRelease): // Identify Mouse press Event
{
qDebug() << "Got Mouse Event";
emit myLabelClicked();
break;
}
}
return QWidget::event(myEvent);
}
void YourLabelClass :: slotLabelClicked() // Implementation of Slot which will consume signal
{
qDebug() << "Clicked Label";
}
For Changing a Text on QLineEdit, you need to create a Custom Class and share object pointer with custom QLabel Class. Please check test code at this link
In the above example the header needs Q_OBJECT:
class YourLabelClass : public QLabel{
Q_OBJECT
signals:

QMainWindow - wait until 'show' function is done

Is there a signal that tells when 'show' function finishes?
I have a problem in my code: If I write:
QMainWinObj.show();
QMainWinObj.someGuiFunc();
the code doesn't work. But, if I write:
QMainWinObj.show();
sleep(3000);
QMainWinObj.someGuiFunc();
It does.
So I think the problem is that 'show' dosn't finish its jub before I call 'someGuiFunc'. That's why I want to have some kind of a sign that 'show' is finished..
This may be a bit dated but since nobody else answered it except the one:
Since there is no "Show" signal I suggest overriding the show event like this:
In your mainwindow.cpp file:
void MainWindow::show()
{
QMainWindow::show();
QApplication::processEvents();
emit windowShown();
}
In your mainwindow.h file, somewhere in MainWindow's declaration:
...
class MainWindow: public QMainWindow
{
...
signals:
void windowShown();
...
}
...
Then, when you go to the designer, right click on the main window (very top of the object tree), and select "Change signals/slots". In the "Signals" frame, click the "+" button, and you will need to add "windowShown()" and then press enter, and then the OK button (note that the elipses "..." denote other code that is already in your header).
That's it -- you can now use the signals/slots editor to link slots up to the 'windowShown' signal whenever you want. Now if you want something more like Microsoft's "Loaded" event which I think is used in .NET you will need to create some instance variable and flag it so that every time the window is shown, it isnt emitted, for example:
void MainWindow::show()
{
QMainWindow::show();
QApplication::processEvents();
emit windowShown();
if (firstTimeShown == true)
{
emit windowLoaded();
firstTimeShown = false;
}
}
Also, don't forget to initialize the variable to 'true' in your constructor:
MainWindow::MainWindow(QObject* parent)
...
{
firstTimeShown = true; // put this somewhere before ui->setupUi()
}
If you decide to put it in the initializer list however, make sure it is in proper order. The compiler will complain if the variables are not instantiated in a top-to-bottom fashion as declared in the class' header.
Now, make sure when you define firstTimeShown in your header, that you make it private. And lets not forget the added signals:
class MainWindow : public QMainWindow
{
...
signals:
void windowLoaded();
void windowShown();
private:
bool firstTimeShown;
...
That's about it. With the flexibility of signals and slots, its pretty easy to mimic any event that you might find from windows forms or from MFC. It just takes a little effort on the programmer's part. Once you get the hang of it however it it'll be like second nature.
note: there probably are optimizations or better and more precise ways of making the "Loaded" and "Shown" signals perform but I have left things like this out for simplicity's sake. And to come back to the question at hand, calling QApplication::processEvents() is most likely what you want to do instead of waiting a fixed amount of time because who knows how long it will take if the user is running 100 other things on top of it, etc, etc. Hope that helped, the extra explanation was included hoping that it might give you a better way to do the things that you want to do instead of waiting for something to be done, 'knowing' it is done is a much better alternative.
There is no such signal, but having QMainWindow subclassed you can override showEvent event.
void MainWindow::showEvent(QShowEvent *){
//your code
}
More info here: http://qt-project.org/doc/qt-4.8/qwidget.html#showEvent
Be aware it's called every time your window is about to be displayed.
Problem can decide without subclassing, just installing event filter like this:
class CWidgetIsPainting_EF : public QObject
{
bool m_bIsPainted = false;
public:
CWidgetIsPainting_EF( QObject * parent = 0 ) : QObject (parent) { }
inline bool IsPainted() const { return m_bIsPainted; }
inline void setIsPainted( bool bIsPainted ) { m_bIsPainted = bIsPainted; }
protected:
bool eventFilter( QObject * obj, QEvent *event )
{
if (event->type() == QEvent::Paint)
{
m_bIsPainted = true;
return true;
};
return QObject::eventFilter(obj, event);
}
};
...
...
CWidgetIsPainting_EF * pPaintingEF = new CWidgetIsPainting_EF( m_pWidget );
m_pWidget->installEventFilter( pPaintingEF );
...
...
while ( !pPaintingEF->IsPainted() )
QApplication::processEvents();
Override bool event(QEvent *event) and catch the Paint event. Works for me at least on Windows.
// MainWindow.h
class MainWindow : public QMainWindow
{
...
bool event(QEvent *event) override;
void functionAfterShown();
...
bool functionAfterShownCalled = false;
...
}
// MainWindow.cpp
bool MainWindow::event(QEvent *event)
{
const bool ret_val = QMainWindow::event(event);
if(!functionAfterShownCalled && event->type() == QEvent::Paint)
{
functionAfterShown();
functionAfterShownCalled = true;
}
return ret_val;
}

How to change a parent widget's background when a child widget has focus?

I would like to highlight a QFrame, if one of it's child widgets has focus (so the users know where to look for the cursor ;-)
using something along
ui->frame->setFocusPolicy(Qt::StrongFocus);
ui->frame->setStyleSheet("QFrame:focus {background-color: #FFFFCC;}");
highlights the QFrame when I click on it, but it loses its focus once one of its child widgets is selected.
Possible approaches:
I could connect() QApplication::focusChanged(old,now) and check each new object if it is a child of my QFrame, but this gets messy.
I could also subclass each child widget and reimplement focusInEvent()/focusOutEvent() and react on that, but with a lot of different widgets, this is also a lot of work.
Is there a more elegant solution?
Well, you can extend QFrame to make it listen on focus change of its children widgets.
Or you can also install an event filter on children widgets to catch QFocusEvent.
Here is an example:
MyFrame.h
#ifndef MYFRAME_H
#define MYFRAME_H
#include <QFrame>
class MyFrame : public QFrame
{
Q_OBJECT
public:
explicit MyFrame(QWidget* parent = 0, Qt::WindowFlags f = 0);
void hookChildrenWidgetsFocus();
protected:
bool eventFilter(QObject *object, QEvent *event);
private:
QString m_originalStyleSheet;
};
#endif // MYFRAME_H
MyFrame.cpp
#include <QEvent>
#include "MyFrame.h"
MyFrame::MyFrame(QWidget *parent, Qt::WindowFlags f)
: QFrame(parent, f)
{
m_originalStyleSheet = styleSheet();
}
void MyFrame::hookChildrenWidgetsFocus()
{
foreach (QObject *child, children()) {
if (child->isWidgetType()) {
child->installEventFilter(this);
}
}
}
bool MyFrame::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::FocusIn) {
setStyleSheet("background-color: #FFFFCC;");
} else if (event->type() == QEvent::FocusOut) {
setStyleSheet(m_originalStyleSheet);
}
return QObject::eventFilter(object, event);
}
MainWindow.cpp
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLineEdit>
#include "MyFrame.h"
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
setWindowTitle(tr("Test"));
MyFrame *frame1 = new MyFrame(this);
frame1->setLayout(new QVBoxLayout());
frame1->layout()->addWidget(new QLineEdit());
frame1->layout()->addWidget(new QLineEdit());
frame1->layout()->addWidget(new QLineEdit());
frame1->hookChildrenWidgetsFocus();
MyFrame *frame2 = new MyFrame(this);
frame2->setLayout(new QVBoxLayout());
frame2->layout()->addWidget(new QLineEdit());
frame2->layout()->addWidget(new QLineEdit());
frame2->layout()->addWidget(new QLineEdit());
frame2->hookChildrenWidgetsFocus();
QHBoxLayout *centralLayout = new QHBoxLayout();
centralLayout->addWidget(frame1);
centralLayout->addWidget(frame2);
QWidget *centralWidget = new QWidget();
centralWidget->setLayout(centralLayout);
setCentralWidget(centralWidget);
}
I believe the both answers you were given are wrong. They work for simple cases but are extremely fragile and clumsy. I believe that the best solution is what you actually suggested in your question. I would go for connecting to QApplication::focusChanged(from, to). You simply connect your main frame object to this signal and in the slot you check if the to object (the one which received focus) is a child of your frame object.
Frame::Frame(...)
{
// ...
connect(qApp, &QApplication::focusChanged, this, &Frame::onFocusChanged);
// ...
}
// a private method of your Frame object
void Frame::onFocusChanged(QWidget *from, QWidget *to)
{
auto w = to;
while (w != nullptr && w != this)
w = w->parentWidget();
if (w == this) // a child (or self) is focused
setStylesheet(highlightedStylesheet);
else // something else is focused
setStylesheet(normalStylesheet);
}
The advantage is obvious. This code is short and clean. You connect only one signal-slot, you do not need to catch and handle events. It responds well to any layout changes done after the object is created. And if you want to optimize away unnecessary redrawing, you should cache the information whether any child is focused and change the stylesheet only and only if this cached value gets changed. Then the solution would be prefect.
First, create a simple subclass of QFrame which reimplements the eventFilter(QObject*, QEvent*) virtual function:
class MyFrame : public QFrame {
Q_OBJECT
public:
MyFrame(QWidget *parent = 0, Qt::WindowFlags f = 0);
~MyFrame();
virtual bool eventFilter(QObject *watched, QEvent *event);
};
Use MyFrame instead of QFrame to contain your widgets. Then, somewhere in your code where you create the widgets contained in MyFrame, install an event filter on those widgets:
// ...
m_myFrame = new MyFrame(parentWidget);
QVBoxLayout *layout = new QVBoxLayout(myFrame);
m_button = new QPushButton("Widget 1", myFrame);
layout->addWidget(m_button);
m_button->installEventFilter(myFrame);
//...
At that point, MyFrame::eventFilter() will be called before any event is delivered to the widget, letting you act on it before the widget is aware of it. Within MyFrame::eventFilter(), return true if you want to filter the event out (i.e. you don't want the widget to process the event), or return false otherwise.
bool MyFrame::eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_button) { // An event occured on m_button
switch (event -> type()) {
case QEvent::FocusIn:
// Change the stylesheet of the frame
break;
case QEvent::FocusOut:
// Change the stylesheet back
break;
default:
break;
}
}
return false; // We always want the event to propagate, so always return false
}

Resources