ceztko
ceztko

Reputation: 15237

How to intercept Qt Quick qml events?

I would like to intercept Qt Quick events, such as key events, so that I can process them before they reach the current target such as the current focused item, optionally preventing the event to propagate in the default event chain. Can this be achieved by processing the event in QML code?

Upvotes: 1

Views: 2473

Answers (1)

ceztko
ceztko

Reputation: 15237

This can be achieved by installing a event filter in the top level window. The top level window can be found and accessed everywhere in the QML source by saving a reference to ApplicationWindow with a QML Singleton (this is not exactly trivial: follow this or other guides and save the reference in the Component.onCompleted event of ApplicationWindow). The event filter can be installed with a C++ QML registered plugin.

The c++ event filter plugin is like this:

#pragma once

#include <QQuickItem>

class QmlEventFilter : public QQuickItem
{
    Q_OBJECT
public:
    Q_PROPERTY(QObject * source READ getSource WRITE setSource)
    Q_PROPERTY(bool filterEnabled READ getFilterEnabled WRITE setFilterEnabled)

public:
    QmlEventFilter()
    {
        m_source = nullptr;
        m_filterEnabled = false;
    }

    ~QmlEventFilter()
    {
        if (m_source != nullptr)
            m_source->removeEventFilter(this);
    }

    void setSource(QObject *source)
    {
        source->installEventFilter(this);
        m_source = source;
    };

    QObject * getSource() { return m_source; }
    void setFilterEnabled(bool value) { m_filterEnabled = value; }
    bool getFilterEnabled() { return m_filterEnabled; }

private:

    void keyPressEvent(QKeyEvent *event) override
    {
        // This is actually called when the QML event handler hasn't accepted the event
        m_qmlAccepted = false;

        // Ensure the event won't be propagated further
        event->setAccepted(true);
    }

    void keyReleaseEvent(QKeyEvent *event) override
    {
        // This is actually called when the QML event handler hasn't accepted the event
        m_qmlAccepted = false;

        // Ensure the event won't be propagated further
        event->setAccepted(true);
    }

    bool eventFilter(QObject *obj, QEvent *event) override
    {
        if (!m_filterEnabled)
            return false;

        bool ret = false;
        switch (event->type())
        {
        case QEvent::KeyPress:
        case QEvent::KeyRelease:
            m_qmlAccepted = true;
            QCoreApplication::sendEvent(this, event);
            ret = m_qmlAccepted;
            break;
        }
        return ret;
    }

private:
    QObject *m_source;
    bool m_filterEnabled;
    bool m_qmlAccepted;
};

It has to be registered before the Qt Quick application like this:

    qmlRegisterType<QmlEventFilter>("MyPlugins", 1, 0, "EventFilter");

Then it can be used in a QML source like this:

import MyPlugins 1.0

[...]

EventFilter
{
    id: filter
    filterEnabled: true // It can also be enabled on demand in other events
    Keys.onPressed:
    {
        // Accepting the event won't propagate the event
        // with the default event chain
        event.accepted = true
        console.log("onPressed")
    }
}

Component.onCompleted:
{
    // Singleton.window is the top level QML ApplicationWindow
    filter.source = Singleton.window
}

Upvotes: 3

Related Questions