הלינקייה: מגזין חודשי למפתחים

רוצה לשמוע על כל האירועים, המדריכים, הקורסים והמאמרים שנכתבו החודש ?
הלינקייה הינו מגזין חופשי בעברית שמשאיר אותך בעניינים.
בלי ספאם. בלי שטויות. פעם בחודש אצלך בתיבה.

In Depth Event Filters

Qt signals and slots are awesome. They let you write your functionality in one class as a slot, connect the signal to that slot and Qt does all other magic. You can also add more functionality from other classes and connect those slots as well.

Signals have one limitations, though. They are emitted as a response to lower-level events, and add an extra level of logic into those events by the emitting widget. Problem is, we sometimes need more signals than what the widget provides.

Consider the case of a mouse click on QLabel. It'is usually a meaningless event caused by mistake, therefore it emits no signal — but for your application it might be important to catch that event and bind functionality to it.

One option is to use C++ inheritance and create a new class called ClickableLabel that will work "just like a label" but will also emit a signal when it's clicked. This works, but can lead to complex class hierarchies.

In this article I will cover event filters, which provide a more simple approach, and provide some extra functionality. Filters are not always the best idea, but when they are it's worth to have them in your tool box.

1. What is an Event Filter

Normally, Qt provides two mechanisms to handle things. The first is signals and slots. This is what most developers start with. Every widget has a set of signals it emits when some semantic event happens. It makes sense for a button to be clicked on, therefore we have a signal for that. It doesn't make sense for someone to drop an image onto a button, so we don't have a signal for that.

The second mechanism is event handling. Events happen at a lower level than signals, and they are usually the source (or reason) for a signal. A mouse click event inside the button's area translates into a clicked signal (if the button is enabled). Signals build another layer of functionality on top of the basic events.

Unlike signals, which can be connected to slots to add functionality — if you want to connect functionality to an event you need to override a protected method. For example, if you want a widget that can handle mouse click events, you will need to override the protected method:

void QWidget::mousePressEvent ( QMouseEvent * event ) [virtual protected]

Which means only classes derived from QWidget can have the code to handle an event.

Every QObject also has one special protected method related to events:

bool QObject::eventFilter ( QObject * watched, QEvent * event )[virtual]

In Qt, every object can function as an event filter for other objects. An event filter listens on a target QObject for events, and when a new event is sent to the target, the filter gets a chance to handle it before the original target. Qt will simply call the filter's eventFilter method for each event.

2. How you can use event filter to handle clicks on a QLabel

If you want to handle clicks on a QLabel (which does not respond to mouse press events), all you need is create an event filter that will convert the mouse press event to a signal. Here's the sample code:


bool MainWindow::eventFilter( QObject * watched, QEvent * event )
{
    QLabel *label = qobject_cast<QLabel *>(watched);
    if ( label && event->type() == QEvent::MouseButtonPress )
    {
        ui->label->setText("Ouch");
    }

    return QObject::eventFilter(watched, event);
}

After adding this code in MainWindow, you still need to install MainWindow as an event handler for the label. This is performed in the constructor using the call installEventFilter. Here's the code for MainWindow constructor:



MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->label->installEventFilter( this );
}

Full source code for this example is available on github:

https://github.com/ynonp/qt-event-filters-example

3. Using event filters to alter reality

The QTextEdit is a really nice widget for editing text. By using filters, we can even make it better. Let's assume we want to use QTextEdit to represent code. I'm not a big fan of tabs inside code, but I like the fact that the tab key creates indentation (4 spaces). Most text editors can automatically translate a single keypress on the Tab key to 4 spaces.

If you were to write such a text editor in Qt, event filters may be just the thing you need. Just install a filter that captures a Tab key press before it reaches your QTextEdit, drop the event, and create a new event with 4 spaces key press.

Here's the relevant filter code:


QTextEdit *edit = qobject_cast<QTextEdit *>(watched);
if ( edit && event->type() == QEvent::KeyPress )
{
QKeyEvent *k = ( QKeyEvent * ) event;
if ( k->key() == Qt::Key_Tab )
{
QEvent *evtSpace = new QKeyEvent(QEvent::KeyPress, Qt::Key_Space, Qt::NoModifier, "    ");
qApp->postEvent(edit, evtSpace );
return true;
}
}

Full source code for this example is on the same github repository.

Notice how easy it is to send a new event to a widget using QApplication. The postEvent method takes ownership of the event object, and will send it to the target widget when execution context returns to the main loop. We use return true to drop the original event so no double handling will occur.