Background
I've made a custom widget with QLineEdit and several QPushButtons to use it with custom item delegate:
class LineEditor : public QWidget
{
public:
explicit LineEditor(QWidget *parent = 0) : QWidget(parent) {
setLayout(new QHBoxLayout);
layout()->setContentsMargins(0, 0, 0, 0);
layout()->setSpacing(0);
QLineEdit *edit = new QLineEdit(this);
layout()->addWidget(edit);
layout()->addWidget(new QPushButton(this));
layout()->addWidget(new QPushButton(this));
setFocusProxy(edit);
}
};
class PropertyDelegate : public QItemDelegate
{
public:
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
return new LineEditor(parent);
}
bool eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
qDebug() << "KeyPress";
}
if (event->type() == QEvent::ShortcutOverride) {
qDebug() << "ShortcutOverride";
}
return QItemDelegate::eventFilter(object, event);
}
};
I'm going to bind them with QListView and QStandardItemModel like this:
QStandardItemModel *model = new QStandardItemModel;
model->appendRow(new QStandardItem("1"));
model->appendRow(new QStandardItem("2"));
model->appendRow(new QStandardItem("3"));
QListView w;
w.setItemDelegate(new PropertyDelegate);
w.setModel(model);
w.show();
Question
Why in the PropertyDelegate::eventFilter when Tab key is pressed there is only QEvent::ShortcutOverride event, but pressing of any other key emits both QEvent::ShortcutOverride and QEvent::KeyPress events?
UPD: I want to implement the moving between lines by pressing Tab and Backtab like with standard widgets.
Well, finally I've done some research about that.
Explanation
When a view calls createEditor function of delegate it also installs the delegate event filter to editor.
QWidget *QAbstractItemViewPrivate::editor(const QModelIndex &index,
const QStyleOptionViewItem &options)
{
Q_Q(QAbstractItemView);
QWidget *w = editorForIndex(index).widget.data();
if (!w) {
QAbstractItemDelegate *delegate = delegateForIndex(index);
if (!delegate)
return 0;
w = delegate->createEditor(viewport, options, index);
if (w) {
w->installEventFilter(delegate);
......
}
However the delegate can catch only events of editor widget, but not events of its children. When the Tab key is pressed the QWidget::event function is called, it uses it to change focus to another widget:
bool QWidget::event(QEvent *event)
{
......
switch (event->type()) {
......
case QEvent::KeyPress: {
QKeyEvent *k = (QKeyEvent *)event;
bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier?
if (k->key() == Qt::Key_Backtab
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
else if (k->key() == Qt::Key_Tab)
res = focusNextPrevChild(true);
if (res)
break;
}
......
}
......
}
Accordingly in my case the focus is set to next QPushButton after QLineEdit and event isn't propagated to the parent (LineEditor).
Solving
The right way to solve the problem is like QSpinBox does it. Because it is also has QLineEdit. In constructor of widget it sets focus proxy for line edit:
edit->setFocusProxy(this);
So all of focus events will reach the main widget. Also the focusPolicy property must be set because it's NoFocus by default:
setFocusPolicy(Qt::WheelFocus);
All we need to do in this moment it is to propagate necessary events to QLineEdit from main widget like this:
bool LineEditor::event(QEvent *e)
{
switch(e->type())
{
case QEvent::ShortcutOverride:
if(m_lineEdit->event(e))
return true;
break;
case QEvent::InputMethod:
return m_lineEdit->event(e);
default:
break;
}
return QWidget::event(e);
}
void LineEditor::keyPressEvent(QKeyEvent *e)
{
m_lineEdit->event(e);
}
void LineEditor::mousePressEvent(QMouseEvent *e)
{
if(e->button() != Qt::LeftButton)
return;
e->ignore();
}
void LineEditor::mouseReleaseEvent(QMouseEvent *e)
{
e->accept();
}
void LineEditor::focusInEvent(QFocusEvent *e)
{
m_lineEdit->event(e);
QWidget::focusInEvent(e);
}
void LineEditor::focusOutEvent(QFocusEvent *e)
{
m_lineEdit->event(e);
QWidget::focusOutEvent(e);
}
This should be enough.
Tricky
As it's said above the delegate can't catch events of editor's children. So to make editor's behavior like "native" I have to duplicate events from children to editor.
LineEditor installs event filter to QLineEdit in constructor:
edit->installEventFilter(this);
Implementation of filter looks like this:
bool LineEditor::eventFilter(QObject *object, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab)
{
QApplication::postEvent(this, new QKeyEvent(keyEvent->type(), keyEvent->key(), keyEvent->modifiers()));
// Filter this event because the editor will be closed anyway
return true;
}
}
else if(event->type() == QEvent::FocusOut)
{
QFocusEvent* focusEvent = static_cast<QFocusEvent *>(event);
QApplication::postEvent(this, new QFocusEvent(focusEvent->type(), focusEvent->reason()));
// Don't filter because focus can be changed internally in editor
return false;
}
return QWidget::eventFilter(object, event);
}
It's also possible to use qApp->notify(this, event) for QKeyEvent instead of QApplication::postEvent because we filter this events anyway. But it's not possible for QFocusEvent because notify will redirect the event and it will not reach a child.
Note that the standard (QItemDelegate or QStyledItemDelegate) delegate will care about situation when focus is changed internally by itself:
if (event->type() == QEvent::FocusOut || (event->type() == QEvent::Hide && editor->isWindow())) {
//the Hide event will take care of he editors that are in fact complete dialogs
if (!editor->isActiveWindow() || (QApplication::focusWidget() != editor)) {
QWidget *w = QApplication::focusWidget();
while (w) { // don't worry about focus changes internally in the editor
if (w == editor)
return false;
w = w->parentWidget();
}
......
Related
I have a QDockWidget in my Mainwindow with a QTableWidget and two QPushbuttons.
Of course, I can click the buttons with my mouse, but I want also to "click" them with left- and right-arrow-key.
It nearly works perfect. But before they are clicked via key, it seems like the focus jumps to the right/left of the QTableWidget (the items in it, it goes through all columns).
Is it possible that I have the KeyPressEvents only for the buttons in the QDockWidget?
You can use an event filter like this:
class Filter : public QObject
{
public:
bool eventFilter(QObject * o, QEvent * e)
{
if(e->type() == QEvent::KeyPress)
{
QKeyEvent * event = static_cast<QKeyEvent *>(e);
if((event->key() == Qt::Key_Left) || (event->key() == Qt::Key_Right))
{
//do what you want ...
return true;
}
}
return QObject::eventFilter(o, e);
}
};
keep an instance of the filter class in your main window class:
private:
Filter filter;
then install it in your widgets, e.g. in your main window class constructor:
//...
installEventFilter(&filter); //in the main window itself
ui->dockWidget->installEventFilter(&filter);
ui->tableWidget->installEventFilter(&filter);
ui->pushButton->installEventFilter(&filter);
//etc ...
You may want to check for modifiers (e.g. Ctrl key), to preserve the standard behaviour of the arrow keys:
//...
if(event->modifiers() == Qt::CTRL) //Ctrl key is also pressed
{
if((event->key() == Qt::Key_Left) || (event->key() == Qt::Key_Right))
{
//...
I don't know how to emit a signal when the user changes of row with the keyboard by pressing up or down arrow key in QTableWidget. And after, I will have to use this signal to make some changes to my video.
How can I implement that?
Subclass the class QTableWidget by adding two signals :
class myTableWidget: public QTableWidget
{
public:
myTableWidget() {}
~myTableWidget() {}
private:
void keyPressEvent(QKeyEvent* event)
{
if(event->key() == Qt::Key_Up)
emit keyUpPressed();
else if(event->key() == Qt::Key_Down)
emit keyDownPressed();
else
QWidget::keyPressEvent(event);
}
signals :
keyUpPressed();
keyDownPressed();
};
In your class where you use the video, add two slots (and let's say your class is named yourClass) :
public slots :
void onKeyUpPressed();
void onKeyDownPressed();
Now you can use the signals and slots connection in your main class like this :
myTableWidget* table = new myTableWidget();
connect(table, SIGNAL(keyUpPressed()), this, SLOT(onKeyUpPressed()));
connect(table, SIGNAL(keyDownPressed()), this, SLOT(onKeyDownPressed()));
Then, in your slots, you can process your video.
void yourClass::onKeyUpPressed()
{
// do something here
}
void yourClass::onKeyDownPressed()
{
// do something else here
}
An alternative is to install an event filter.
EDIT : from your comment, your class inherits QStyledItemDelegate so you can just override the function eventFilter like this :
bool myTableWidget::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Up)
emit keyUpPressed();
else if(event->key() == Qt::Key_Down)
emit keyDownPressed();
}
// standard event processing
return QStyledItemDelegate::eventFilter(obj, event);
}
Then, it is the same for the slots in yourClass.
You probably want to connect to to signal currentCellChanged this will deal with both mouse clicks and keyboard movement and give you the indices of the newly selected cell.
To catch a signal from key event you can inherit from QTableWidget and override method keyPressEvent(QKeyEvent *event), for example:
.H file:
class CustomTableWidget : public QTableWidget
{
Q_OBJECT
protected:
void keyPressEvent(QKeyEvent *event);
public:
explicit CustomTableWidget(QWidget *parent = 0);
~CustomTableWidget();
signals:
void upEvent(const QModelIndex &index);
void downEvent(const QModelIndex &index);
.CPP file:
void CustomTableWidget::keyPressEvent(QKeyEvent *event)
{
switch(event->key()) {
case Qt::Key_Up: emit upEvent(currentIndex());
break;
case Qt::Key_Down: emit downEvent(currentIndex());
break;
default: QTableWidget::keyPressEvent(event);
}
}
This is a bit of a beginners question but I don't find the solution.
I'm using an own object that inherits from QLineEdit and reiceves numbers as input (which works smoothly now).
Now I want to receive an event, when the user presses the Escape-button. This does not happen with the textChanged()-event. According to the documentation there is no special escape-event. So how else can this be done?
Thanks!
I had this same problem. I am solving it by implementing keyPressEvent in my QMainWindow.
void MainWindow::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Escape) {
QLineEdit *focus = qobject_cast<QLineEdit *>(focusWidget());
if (lineEditKeyEventSet.contains(focus)) {
focus->clear();
}
}
}
And setting up QSet<QLineEdit *> lineEditKeyEventSet to contain the QLineEdits that need this behavior.
void MainWindow::setupLineEditKeyEventList()
{
lineEditKeyEventSet.insert(ui->lineEdit_1);
lineEditKeyEventSet.insert(ui->lineEdit_2);
lineEditKeyEventSet.insert(ui->lineEdit_3);
}
You can either implement keyPressEvent :
void LineEdit::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape)
{
...
}
QLineEdit::keyPressEvent(event);
}
Or implement eventFilter :
bool LineEdit::eventFilter(QObject *obj, QEvent * event)
{
if((LineEdit *)obj == this && event->type()==QEvent::KeyPress && ((QKeyEvent*)event)->key() == Qt::Key_Escape )
{
...
}
return false;
}
When using the eventFilter approach, install the event filter in the constructor :
this->installEventFilter(this);
I have a simple application with just one QPlainTextEdit, basically the same as Qt's example here:
http://qt-project.org/doc/qt-5.1/qtwidgets/mainwindows-application.html
When I press Ctrl+Z, it calls undo. When I press Ctrl+A, it selects all text. This is ok.
But when I press Ctrl+E or Ctrl+R, which are not defined in the menu, the characters "e" and "r" will appear in the QSimpleTextEdit.
How do I prevent this? How to "filter" keypresses which have been defined as menu shortcuts and keep them working, and "prevent" those keypresses not defined as menu shortcuts from appearing in the edit?
There are 2 options:
1) Create a subclass and reimplement keyPressEvent()
2) Create an eventFilter and use installEventFilter() (see http://qt-project.org/doc/qt-5.0/qtcore/qobject.html#installEventFilter)
You can use the following code:
CSimpleEdit.h
#include <QPlainTextEdit>
class CSimpleEdit: public QPlainTextEdit
{ Q_OBJECT
public:
explicit CSimpleEdit(QWidget* parent = 0);
~ CSimpleEdit();
protected:
bool eventFilter(QObject* obj, QEvent* event);
};
CSimpleEdit.cpp
CSimpleEdit::CSimpleEdit(QWidget* parent)
: QPlainTextEdit(parent)
{ installEventFilter(this); }
CSimpleEdit::~CSimpleEdit()
{ removeEventFilter(this); }
bool CSimpleEdit::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->modifiers() != Qt::NoModifier &&
!keyEvent->modifiers().testFlag(Qt::ShiftModifier))
{ bool bMatch = false;
for (int i = QKeySequence::HelpContents; i < QKeySequence::Deselect; i++)
{ bMatch = keyEvent->matches((QKeySequence::StandardKey) i);
if (bMatch) break;
}
/*You can also set bMatch to true by checking you application
*actions list -> QWidget::actions(). */
if (!bMatch) return true;
}
}
else if (event->type() == QEvent::KeyRelease)
{ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->modifiers() != Qt::NoModifier &&
!keyEvent->modifiers().testFlag(Qt::ShiftModifier))
{ bool bMatch = false;
for (int i = QKeySequence::HelpContents; i < QKeySequence::Deselect; i++)
{ bMatch = keyEvent->matches((QKeySequence::StandardKey) i);
if (bMatch) break;
}
if (!bMatch) return true;
}
}
return QObject::eventFilter(obj, event);
}
I have a little problem, I need to set my event filter to QComboBox popup.
I need to catch events when left and right keys are pressed.
How can I do this?
Thank you!
You need to set the eventFilter on QComboBox's view() (http://qt-project.org/doc/qt-4.8/qcombobox.html#view).
You may need to add following code somewhere in your code.
void MyComboBox::keyPressEvent (QKeyEvent *event)
{
if (event->button() == Qt::Key_Left)
{
// handle left key press
}
if (event->button() == Qt::Key_Right)
{
// handle right key press
}
}
Hope this helps!
The question is quite old, but I provide my answer since it can help someone else.
After popup all events will be sent to the list view used for the QComboBox popup. You can get the things done using key handler class watching on events for list view.
KeyPressHandler.h:
class KeyPressHandler : public QObject
{
Q_OBJECT
public:
explicit KeyPressHandler(QObject *parent = nullptr);
virtual ~KeyPressHandler() override;
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
KeyPressHandler.cpp:
#include <QCoreApplication>
KeyPressHandler::KeyPressHandler(QObject *parent) : QObject(parent)
{
}
KeyPressHandler::~KeyPressHandler()
{
}
bool KeyPressHandler::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
switch(keyEvent->key())
{
case Qt::Key_Left:
// Send press event for the Key_Up which understood by list view
QCoreApplication::postEvent(obj, new QKeyEvent(QEvent::KeyPress,
Qt::Key_Up,
Qt::NoModifier));
return true;
case Qt::Key_Right:
QCoreApplication::postEvent(obj, new QKeyEvent(QEvent::KeyPress,
Qt::Key_Down,
Qt::NoModifier));
return true;
default:
break;
}
}
// standard event processing
return QObject::eventFilter(obj, event);
}
In ComboBox you will need to install event filter when popup is shown.
It can be done in different ways, for example by overriding QComboBox::showPopup() function.
MyComboBox.h:
#include <memory>
#include <QComboBox>
class MyComboBox : public QComboBox
{
Q_OBJECT
public:
explicit MyComboBox(QWidget *parent = 0);
protected:
void showPopup() override;
void hidePopup() override;
private:
std::unique_ptr<KeyPressHandler> m_key_press_handler;
};
MyComboBox.cpp:
...
void MyComboBox::showPopup()
{
if(!m_key_press_handler)
{
m_key_press_handler.reset(new KeyPressHandler());
QAbstractItemView *v = view();
v->installEventFilter(m_key_press_handler.get());
}
QComboBox::showPopup();
}
void MyComboBox::hidePopup()
{
m_key_press_handler.reset(nullptr);
QComboBox::hidePopup();
}