简体   繁体   中英

Cannot detect multiple keypresses in eventFilter in Qt 4

I am trying to understand the event filtering mechanism in Qt. To that effect, I have subclassed QLineEdit and tried to capture any key presses before the actual QLineEdit does. The code I have written (mostly pasted from the Qt documentation) partially works: if I press any one key, the QLineEdit correctly says "Ate key press LETTER".

If I press down the Option key (I am on a Mac) and press, say, "S", I correctly get "Ate key press ∫"; however, if I press again "S" while still holding down the Option key, the QLineEdit reads "Ate key press ∫∫", which I cannot explain. It looks like this second (and subsequent) press of the key "S" is not a QKeyEvent or QShortcutEvent and is delivered directly to the actual widget but then, what kind of event is it?

To further complicate things, if, while holding down the Option key, I press a key other than "S", the results vary depending on which key is it. For instance, for the key sequence Option+{S,D,F,G,H}, the QLineEdit reads "Ate key press ∫∂ƒ™". However, if I go on to press "J", then the QLineEdit reads only "Ate key press ¶".

Can anyone replicate this behaviour and, better yet, explain it? Thank you all in advance.

main.cpp :

#include <QApplication>
#include "customlineedit.h"

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    CustomLineEdit w;
    w.show();
    return a.exec();
}

customlineedit.h

#ifndef CUSTOMLINEEDIT_H
#define CUSTOMLINEEDIT_H

#include <QLineEdit>

class CustomLineEdit : public QLineEdit
{
    Q_OBJECT
public:
    explicit CustomLineEdit(QWidget *parent = 0);
    virtual bool eventFilter(QObject *watched, QEvent *event);
};

#endif // CUSTOMLINEEDIT_H

customlineedit.cpp

#include "customlineedit.h"
#include <QKeyEvent>

CustomLineEdit::CustomLineEdit(QWidget *parent) :
    QLineEdit(parent)
{
    this->installEventFilter (this);
}

bool CustomLineEdit::eventFilter (QObject *watched, QEvent *event)
{
    if (event->type () == QEvent::KeyPress
        || event->type () == QEvent::ShortcutOverride) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
        this->setText("Ate key press " + keyEvent->text());
        return true;
    } else if (event->type () == QEvent::Shortcut) {
        this->setText ("Shortcut event");
        return true;
    } else {
        return false;
    }
}

There's a lot of unusual stuff going on there.

First, event filters are meant to allow a QObject to receive and optionally react to events intended for another QObject. What you are doing, particularly this line:

 this->installEventFilter (this);

is basically redirecting events of your widget to itself. This isn't congruent with how the feature is designed to work and doesn't really reflect how you will use event filtering in real projects. When this is combined with your very strong event eating, you are essentially breaking the widget. For example, modifier key presses are delivered as a QKeyEvent, so by eating them in the eventfilter function you are likely throwing off shortcut handling.

A couple reasons why you might not be getting QShortcut events. I don't see any QShortcut objects being attached. I wouldn't expect to get any QShortcutEvent events until you do. Also, I believe they are meant to work with QMenus, not as a generic way to get modifier+key sequences in all widgets. You will typically use QKeyEvent::modifiers() to do that.

Also, it's not clear what you're trying to do by filtering shortcutoverride events and casting them as keyevents.

According to the docs, keyEvent->text() will be empty for modifier keys, so that may explain the characters... On windows it shows as blank.

Event filters are VERY handy, though, and are great for keeping things loosely coupled and highly reusuable. Here's an example that might be a better starting point for you as you learn:

main.cpp

#include <QtGui/QApplication>

#include "customlineedit.h"

#include <QWidget>
#include <QLineEdit>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QWidget w;
    QLineEdit *le1 = new QLineEdit( &w );
    le1->move( 4, 4 );
    le1->setMinimumWidth( 200 );
    CustomLineEdit *le2 = new CustomLineEdit( &w );
    le2->move( 4, 35 );
    le2->setMinimumWidth( 200 );

    le1->installEventFilter (le2);

    w.setMinimumWidth( 260);
    w.show();

    return a.exec();
}

customlineedit.h

#ifndef CUSTOMLINEEDIT_H
#define CUSTOMLINEEDIT_H

#include <QLineEdit>

class CustomLineEdit : public QLineEdit
{
    Q_OBJECT
public:
    explicit CustomLineEdit(QWidget *parent = 0);
    virtual bool eventFilter(QObject *watched, QEvent *event);
};

#endif // CUSTOMLINEEDIT_H

customlineedit.cpp

#include "customlineedit.h"

#include<QShortcutEvent>

CustomLineEdit::CustomLineEdit(QWidget *parent) :
    QLineEdit(parent)
{
}


bool CustomLineEdit::eventFilter (QObject *watched, QEvent *event)
{
    if (event->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
        this->setText("QKeyEvent: " + QString::number(keyEvent->modifiers() ) +" / "+ QString::number(keyEvent->key()) );
        return false;
    } else if (event->type () == QEvent::Shortcut) { //you shouldn't see these here...
        QShortcutEvent *shrtcutEvent = static_cast<QShortcutEvent*>(event);
        this->setText ("Shortcut event: " + shrtcutEvent->key().toString() );
        return false;
    } else {
        return false;
    }
}

Edit: So I see from the comments that you're trying to add Emacs-style custom shortcuts. That's a great idea! Here's how I'd approach it without using event filtering:

#include <QtGui/QApplication>

#include <QLineEdit>
#include <QKeyEvent>
#include <QDebug>


class LineEditEmacs : public QLineEdit
{
    void keyPressEvent( QKeyEvent* event )
    {
        //qDebug()<< QString::number( event->key(), 16 ).toUpper();
        switch ( event->key() )
        {
        case Qt::Key_Up:
            { undo(); return; }
        case Qt::Key_Down:
            { redo(); return; }
        case Qt::Key_Minus: //shift+underscore
            if( (event->modifiers() & Qt::ShiftModifier) == Qt::ShiftModifier ) { undo(); return; }

        case Qt::Key_U:
            if( (event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier ) {
                SetSelectionToCase( true );
                return;
            }
        case Qt::Key_L:
            if( (event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier ) {
                SetSelectionToCase( false );
                return;
            }

        default: 
            break;
        }
        QLineEdit::keyPressEvent( event );
    }

    void SetSelectionToCase( bool Upper )
    {
        if( !hasSelectedText() )
            return;

        QString s = selectedText();
        int iPreviousCursorPos = cursorPosition();
        del();
        QString su = Upper? s.toUpper() : s.toLower();
        insert( su );
        int iNewCursorPos = cursorPosition();

        //restore selection
        if( iPreviousCursorPos < iNewCursorPos )
            cursorBackward( true, su.length() );
        else if( iPreviousCursorPos == iNewCursorPos )
            setSelection( iPreviousCursorPos-su.length(), su.length() );
    }



};


int main( int argc, char *argv[] )
{
    QApplication a( argc, argv );
    LineEditEmacs w;
    w.show();
    return a.exec();
}

You can swallow keys and key+modifier combinations this way as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM