Search code examples
c++qtqmlqtquick2qtquickcontrols2

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?


Solution

  • 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
    }