Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

using QKeyEvents



  • Hi all -

    I'm experimenting with handling key events in an app, and (as usual) I'm having beginner's difficulties. I've read a couple pages on this, and there seems to be a few different ways to do it. I chose one that made the most sense to me.

    I created a KeyPress class:

    class KeyPress : public QWidget
    {
        Q_OBJECT
    public:
        explicit KeyPress(QWidget *parent = nullptr);
    protected:
        void keyPressEvent (QKeyEvent *ev);
        void keyReleaseEvent(QKeyEvent *ev);
    
    signals:
        void keyEvent(int event, int key);
    };
    

    And in my main widget, I do this in the c'tor:

        // create and connect a KeyPress object.
        m_keyPress = new KeyPress(this);
        connect(m_keyPress, &KeyPress::keyEvent, this, &Widget::handleKeyEvent);
        m_keyPress->installEventFilter(this);
    

    And I overrode the event filter:

    bool eventFilter(QObject *obj, QEvent *ev) override;
    ...
    bool Widget::eventFilter(QObject *obj, QEvent *ev)
    {
        bool rc = false;
    
        if (obj == m_keyPress)
        {
            if (ev->type() == QEvent::KeyPress)
            {
                rc = true;
                qDebug() << ev;
            }
        }
        return rc;
    }
    

    I realize this isn't functional yet, but I thought I'd at least catch a key press. I'm not, though. Can someone tell me what I'm missing?

    Thanks...



  • Why do you need the KeyEvent Class?

    You can do following:

    Subclass the widget where you want the keyevents to be catched.
    Then add a definition for

    void YourWidget::keyPressEvent(QKeyEvent *event)
    

    that looks something like this:

    void YourWidget::keyPressEvent(QKeyEvent *event)
    {
        if(event->key() == Qt::Key_A)
        {
            doAKeyPressStuff();
        }
        else
        {
            doAnyOtherKeyPressStuff();
        }
    }
    


  • @gde23 I guess I don't understand the reason for the subclass. I want to trap a key press anywhere on my main widget. I don't want sub-class my main widget, do I?


  • Lifetime Qt Champion

    Hi,

    It's the other way around, you need to install KeyPress in your QMainWindow.



  • @SGaist not using QMainWindow; using QWidget.

    class Widget : public QWidget
    {
        Q_OBJECT
    ...
    

    Same thing, though?

    And by "install KeyPress" I assume you mean more than create an object of that class, since I'm already doing that.

        m_keyPress = new KeyPress(this);
        connect(m_keyPress, &KeyPress::keyEvent, this, &Widget::handleKeyEvent);
        m_keyPress->installEventFilter(this);
    

    I'm missing a step here, aren't I?


  • Lifetime Qt Champion

    You're not missing a step, you are doing one in the wrong direction. The object on the left of installEventFilter is the one that is going to be monitored so just call

    installEventFilter(m_keyPress);
    

    See the installEventFilter documentation.



  • OK...made that change; still not seeing what I expect.

        // create and connect a KeyPress object.
        m_keyPress = new KeyPress(this);
        connect(m_keyPress, &KeyPress::keyEvent, this, &Widget::handleKeyEvent);
        installEventFilter(m_keyPress);
    

    Here's my event routine:

    void KeyPress::keyPressEvent(QKeyEvent *ev)
    {
        int key;
    
        key = ev->key();
        if (key == Qt::Key_Alt)
        {
            emit keyEvent(QEvent::KeyPress, key);
        }
    }
    

    I'm never hitting a breakpoint in there.


  • Lifetime Qt Champion

    Because an event filter re-implements the eventFilter method and does all there. It does not replace a class original methods.



  • @SGaist said in using QKeyEvents:

    Because an event filter re-implements the eventFilter method and does all there. It does not replace a class original methods.

    So, which object needs the eventFilter() method? I've tried adding to both the Widget and the KeyPress -- both compile and run, but neither trigger a breakpoint.


  • Lifetime Qt Champion

    Did you check the example I linked to ?



  • @SGaist you're referring to the KeyPressEater class in the QObject::installEventFilter() documentation, right?

    Its behavior isn't the same as my KeyPress class, but I can worry about that later. I still don't see why I'm not hitting a breakpoint.

    bool KeyPress::eventFilter(QObject *obj, QEvent *ev)
    {
        bool rc = false;
    
        if (ev->type() == QEvent::KeyPress)
        {
            if (obj == this)
            {
                rc = true;
                qDebug() << ev;
            }
        }
        return rc;
    }
    

    My breakpoint is on my inner "if" and it's never reached.


  • Lifetime Qt Champion

    Because as is, your filter will never be the target of a KeyPress event.
    The value of obj will be one of the object you installed your filter on.



  • I understand your 2nd statement, but not the first. I install the filter on my Widget object. Is a QWidget incapable of accepting KeyPress events?



  • Well, I got it partly working -- the problem was that I was deriving my KeyPress class from QWidget instead of QObject.

    Now, though, my filter only seems to be trapping "meta keys." Control, alt, etc. all work, but regular keys like A, B, C don't. It appears that these keys generate a ShortcutOverride event. Does this make sense to you?

    Thanks...



  • My filter seems to be doing what I want (when two keys are held down, I make a button visible).

        if (C_pressed && D_pressed)
        {
            ui->pushButtonNics->setVisible(true);
        }
        else
        {
            ui->pushButtonNics->setVisible(false);
        }
    

    But when I click on that button, my clicked slot isn't reached. I thought maybe holding down the keys was blocking the button push, but my other buttons work fine. My button is enabled when visible (I checked in the debugger). Any ideas why it's not generating the signal?

    Thanks...


  • Lifetime Qt Champion

    You are forgetting to call the base class implementation.



  • @SGaist I hadn't forgotten; I just hadn't gotten to that yet. But shouldn't I hit this slot anyway?

    void Widget::on_pushButtonNics_clicked()
    {
        qDebug() << "Nics pressed.";
    }
    

  • Lifetime Qt Champion

    The last implementation I saw does not call it at all which means that all events are filtered out.

    To check, comment out the filter installation and see if it does work correctly.



  • @SGaist let's make sure we're talking about the same thing. Here's where I create the filter:

        // create and connect a KeyPress object.
        m_keyPress = new KeyPress(this);
        connect(m_keyPress, &KeyPress::keyEvent, this, &Widget::handleKeyEvent);
        installEventFilter(m_keyPress);
    
        // hide the button for the NICs window.
    //    ui->pushButtonNics->setVisible(false);
    

    You'll notice that the last line is commented out. And, it works. So it would seem that it has something to do with the filter...?


  • Lifetime Qt Champion

    Can you provide a minimal compilable example that shows the behaviour ?
    Si au can check on my side what is going on.



  • @SGaist

    keypress.h

    #ifndef KEYPRESS_H
    #define KEYPRESS_H
    
    #include <QWidget>
    #include <QtGui>
    
    class KeyPress : public QObject
    {
        Q_OBJECT
    public:
        explicit KeyPress(QWidget *parent = nullptr);
    protected:
        bool eventFilter(QObject *obj, QEvent *ev) override;
    
    signals:
        void keyEvent(QEvent event, int key);
    };
    
    #endif // KEYPRESS_H
    

    keypress.cpp

    #include "keypress.h"
    
    KeyPress::KeyPress(QWidget *parent) : QObject (parent)
    {
    }
    
    bool KeyPress::eventFilter(QObject *obj, QEvent *ev)
    {
        Q_UNUSED(obj)
    
        bool rc = false;
        int key;
        QKeyEvent *qke;
    
        if (ev->type() == QEvent::ShortcutOverride || ev->type() == QEvent::KeyRelease)
        {
            qke = static_cast<QKeyEvent *>(ev);
            key = qke->key();
    
            // don't bother signaling when the key isn't meaningful to us.
            if (key == 'c' || key == 'C' || key == 'd' || key == 'D')
            {
                emit keyEvent(*ev, key);
            }
            rc = true;
        }
        else
        {
            rc = QObject::eventFilter(obj, ev);
        }
        return rc;
    }
    

    Let me know if there's something more you want to look at...thanks.


  • Lifetime Qt Champion

    A minimal widget that you install that filter on would be nice :-)



  • widget.h

    #ifndef WIDGET_H
    #define WIDGET_H
    
    #include <QWidget>
    #include "keypress.h"
    
    namespace Ui {
        class Widget;
    }
    
    class Widget : public QWidget
    {
        Q_OBJECT
    
    private:
        Ui::Widget *ui;
        KeyPress *m_keyPress = nullptr;
        bool C_pressed = false;
        bool D_pressed = false;
    
    public:
        explicit Widget(QWidget *parent = nullptr);
        ~Widget();
    private slots:
        void on_pushButtonQuit_clicked();
        void on_pushButtonNics_clicked();
        void handleKeyEvent(QEvent event, int key);
    signals:
        void quitButtonPushed(int rc);
    };
    #endif // WIDGET_H
    
    

    widget.cpp

    #include <iostream>
    #include <sstream>
    #include <stdio.h>
    
    #include <QDebug>
    
    #include "editdialog.h"
    #include "nics.h"
    #include "widget.h"
    #include "ui_widget.h"
    
    using namespace std;
    
    Widget::Widget(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::Widget)
    {
        ui->setupUi(this);
    
        // create and connect a KeyPress object.
        m_keyPress = new KeyPress(this);
        connect(m_keyPress, &KeyPress::keyEvent, this, &Widget::handleKeyEvent);
        installEventFilter(m_keyPress);
    
        // hide the button for the NICs window.
        ui->pushButtonNics->setVisible(false);
    }
    
    Widget::~Widget()
    {
        delete ui;
    }
    
    
    void Widget::on_pushButtonQuit_clicked()
    {
        emit quitButtonPushed(0);
    }
    
    void Widget::handleKeyEvent(QEvent event, int key)
    {
        if (event.type() == QEvent::ShortcutOverride)
        {
            if (key == 'c' || key == 'C')
            {
                C_pressed = true;
            }
            else if ((key == 'd' || key == 'D'))
            {
                D_pressed = true;
            }
    
        }
        else if (event.type() == QEvent::KeyRelease)
        {
            if (key == 'c' || key == 'C')
            {
                C_pressed = false;
            }
            else if ((key == 'd' || key == 'D'))
            {
                D_pressed = false;
            }
        }
    
        if (C_pressed && D_pressed)
        {
            ui->pushButtonNics->setVisible(true);
        }
        else
        {
            ui->pushButtonNics->setVisible(false);
        }
    }
    
    
    
    void Widget::on_pushButtonNics_clicked()
    {
        Nics nics(this);
        nics.exec();
    }
    

    You'll want to connect the Widget signal to something. Sorry...this was as compact as I dared make it.

    Thanks for looking at this.


  • Lifetime Qt Champion

    Since I don't have the .ui file I modified the code a bit.

    I created a QPushButton and did an explicit connection and it worked was expected.



  • @SGaist well, doesn't that tear the rag right off the bush.

    What exactly did you mean by "explicit" connection?


  • Lifetime Qt Champion

    By explicit I meant writing the connect statement. Currently you are using the connectSlotsByName feature that is called as part of setupUi.



  • OK...I added the explicit connection in widget.cpp:

        connect(ui->pushButtonNics, &QPushButton::clicked, this, &Widget::on_pushButtonNics_clicked);
    

    Here's my widget.ui:

    <?xml version="1.0" encoding="UTF-8"?>
    <ui version="4.0">
     <class>Widget</class>
     <widget class="QWidget" name="Widget">
      <property name="geometry">
       <rect>
        <x>0</x>
        <y>0</y>
        <width>320</width>
        <height>160</height>
       </rect>
      </property>
      <property name="sizePolicy">
       <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
      <property name="minimumSize">
       <size>
        <width>320</width>
        <height>160</height>
       </size>
      </property>
      <property name="windowTitle">
       <string>Discovery Utility</string>
      </property>
      <widget class="QPushButton" name="pushButtonNics">
       <property name="geometry">
        <rect>
         <x>130</x>
         <y>50</y>
         <width>75</width>
         <height>23</height>
        </rect>
       </property>
       <property name="text">
        <string>NICs</string>
       </property>
      </widget>
     </widget>
     <layoutdefault spacing="6" margin="11"/>
     <resources/>
     <connections/>
    </ui>
    

    And, just for good measure, here's main.cpp:

    #include <QApplication>
    
    #include <stdio.h>
    
    #include "widget.h"
    
    using namespace std;
    
    int main(int argc, char *argv[])
    {
        int rc;
        QApplication a(argc, argv);
        Widget *widget;
    
        widget = new Widget();
        widget->show();
        rc = a.exec();
        return rc;
    }
    

    This is my entire project, except for the pro file itself:

    QT       += core gui network serialport xml
    
    greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
    
    TARGET = discovery_utility_test
    DEFINES += QT_DEPRECATED_WARNINGS
    SOURCES += \
    	keypress.cpp \
    	main.cpp \
    	nics.cpp \
    	widget.cpp
    
    HEADERS += \
    	keypress.h \
    	nics.h \
    	widget.h \
    
    FORMS += \
    	nics.ui \
    	widget.ui
    

    Could it possibly be due to the way that I'm building it? Or, maybe a Windows issue (you're testing on Linux, right)?

    Thanks...



  • This problem has to do with setting the button's visibility. If I disable this line in my widget c'tor:

    //    ui->pushButtonNics->setVisible(false);
    

    My slot receives the signal, and opens the Nics dialog. After the dialog closes, I set the button to not visible, and the problem recurs.

    This problem exists on Windows 10, but not on MacOS. Could it be a bug?


  • Lifetime Qt Champion

    One silly test, did you try to make the button invisible after calling connect. AFAIK, it should have no relation but it's worth a shot.



  • @SGaist yes, that's how I had it:

    Widget::Widget(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::Widget)
    {
        ui->setupUi(this);
    
        // create and connect a KeyPress object.
        m_keyPress = new KeyPress(this);
        connect(m_keyPress, &KeyPress::keyEvent, this, &Widget::handleKeyEvent);
        connect(ui->pushButtonNics, &QPushButton::clicked, this, &Widget::on_pushButtonNics_clicked);
        installEventFilter(m_keyPress);
    
        // hide the button for the NICs window.
    //    ui->pushButtonNics->setVisible(false);
    }
    


  • Update: I submitted a bug report on this; no movement on it yet.

    One of my users noticed that the application is now rather CPU-intensive. I verified that it was the key filter by disabling this code in my main widget:

        m_keyPress = new KeyPress(this);
        connect(m_keyPress, &KeyPress::keyEvent, this, &Widget::handleKeyEvent);
        installEventFilter(m_keyPress);
    

    Here's the filter:

    bool KeyPress::eventFilter(QObject *obj, QEvent *ev)
    {
        Q_UNUSED(obj)
    
        bool rc = false;
        int key;
        QKeyEvent *qke;
        QEvent::Type type = ev->type();
        if (type == QEvent::ShortcutOverride || type == QEvent::KeyRelease)
        {
            qke = static_cast<QKeyEvent *>(ev);
            key = qke->key();
    
            // don't bother signaling when the key isn't meaningful to us.
            if (key == 'c' || key == 'C' || key == 'd' || key == 'D')
            {
                emit keyEvent(*ev, key);
            }
            rc = true;
        }
        else
        {
            rc = QObject::eventFilter(obj, ev);
        }
        return rc;
    }
    

    I don't see anything really inefficient in my code, but with this enabled, the app uses ~15% of my CPU (on an i3) at idle. Is this to be expected, or am I doing something wrong?

    Thanks...


  • Lifetime Qt Champion

    What type of widgets are you checking ?
    You can add an additional check for the obj parameter class to avoid further checks.



  • @SGaist
    If @mzimmers says:

    but with this enabled, the app uses ~15% of my CPU (on an i3) at idle

    assuming "idle" means not pressing any key, why is this KeyPress::eventFilter() being hit at all, let alone loads of times to use that CPU?



  • @SGaist well, maybe that's my problem -- I'm checking the entire QWidget. (The idea was to reveal hidden buttons when certain keys were pressed.) Given that, I'm not sure there's any meaningful obj parameter checking I can do.



  • @JonB I suspect I'm doing something wrong with my use of the filter, but I'm not sure what to change. I think my filterEvent() is being called for all events, not just for key events.



  • @mzimmers said in using QKeyEvents:

    I think my filterEvent() is being called for all events, not just for key events.

    I suspect that is more like it ;-) I would worry about that before anything else :)

    Use a debugger or qDebug()s to at least see when your filter is being hit, when the user isn't doing anything?



  • @JonB

        qDebug() << QTime::currentTime().toString();
    

    Got about 6000 hits in ~2 seconds of run time. That explains the CPU usage.

    I have no idea what could possibly be generating that many events, though.



  • @mzimmers
    Start by debugging out ev->type()!



  • @JonB the great majority were:

    QEvent::UpdateRequest
    QEvent::Paint

    UPDATE: I managed to eliminate the flood of events, but...that didn't have an effect on my CPU usage. The problem is definitely in the KeyEvent class -- when I comment it out, CPU usage is nil. Anyone have any ideas?



  • Update on this, documented here

    The behaviour is different because clicked is emitted on button down on macOS, but on button up on the other two[Windows and Linux]. While the keyboard keys are down, the QPushButton stays down. That's a UX difference caused by how the OS itself behaves.

    So, my idea for using the keys goes out the window...I'll think of something else.

    The performance issue is a mystery, but I'm going to open a new thread on that. Thanks to everyone for looking.


Log in to reply