Walk through QTableView rows using tab key - c++

I have a QTableView and i wanted the user to be able to select a whole row rather than individual cell. So i changed the selection behavior as shown below.
table->setSelectionBehavior(QAbstractItemView::SelectRows)
But now when the tab key is clicked it still walks through individual cells rather than whole row. I want the user to be able to walk through each row rather individual cells.

You must inherit from QTableView class and override keyPressEvent(). For example:
#include <QTableView>
#include <QKeyEvent>
class CustomView : public QTableView
{
Q_OBJECT
// QWidget interface
protected:
void keyPressEvent(QKeyEvent *event) {
switch(event->key()) {
case Qt::Key_Tab: {
if(currentIndex().row() != model()->rowCount())
selectRow(currentIndex().row() + 1);
break;
}
default: QTableView::keyPressEvent(event);
}
}
public:
explicit CustomView(QWidget *parent = 0);
~CustomView(){}
signals:
public slots:
};

As an alternative to subclassing QTableView, you can install an event filter on it. For example, here I'm using the MainWindow of my program to filter events on a table view, which is one of the window's child widgets:
In mainwindow.h:
class MainWindow: public QMainWindow {
private:
bool eventFilter(QObject *watched, QEvent *event) override;
}
In mainwindow.cpp:
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if (watched == ui->tableView &&
event->type() == QEvent::KeyPress &&
static_cast<QKeyEvent*>(event)->key() == Qt::Key_Tab)
{
//Handle the tab press here
return true; //return true to skip further event handling
}
//If the event was not a tab press on the tableView, let any other handlers do their thing:
return false;
}
Then, in MainWindow::MainWindow() (or wherever) you install the event filter like this:
ui->tableView->installEventFilter(this);

Related

My Qt eventFilter() doesn't stop events as it should

Something is fundamentally wrong with my eventFilter, as it lets every single event through, while I want to stop everything. I've read lots of documentation on QEvent, eventFilter() and so on, but obviously I'm missing something big. Essentially, I'm trying to create my own modal-functionality for my popup-window class based on QDialog. I want to implement my own since the built-in setModal(true) includes a lot of features, e.g. playing QApplication::Beep(), that I want to exclude. Basically, I want to discard all events going to the QWidget (window) that created my popup. What I have so far is,
// popupdialog.h
#ifndef POPUPDIALOG_H
#define POPUPDIALOG_H
#include <QDialog>
#include <QString>
namespace Ui {class PopupDialog;}
class PopupDialog : public QDialog
{
Q_OBJECT
public:
explicit PopupDialog(QWidget *window=0, QString messageText="");
~PopupDialog();
private:
Ui::PopupDialog *ui;
QString messageText;
QWidget window; // the window that caused/created the popup
void mouseReleaseEvent(QMouseEvent*); // popup closes when clicked on
bool eventFilter(QObject *, QEvent*);
};
...
// popupdialog.cpp
#include "popupdialog.h"
#include "ui_popupdialog.h"
PopupDialog::PopupDialog(QWidget *window, QString messageText) :
QDialog(NULL), // parentless
ui(new Ui::PopupDialog),
messageText(messageText),
window(window)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true); // Prevents memory leak
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
ui->message_text_display->setText(messageText);
window->installEventFilter(this);
//this->installEventFilter(window); // tried this also, just to be sure ..
}
PopupDialog::~PopupDialog()
{
window->removeEventFilter(this);
delete ui;
}
// popup closes when clicked on
void PopupDialog::mouseReleaseEvent(QMouseEvent *e)
{
close();
}
Here's the problem, the filter doesn't work. Note that if I write a std::cout
inside the if(...), I see that it does trigger whenever events are sent to window, it just doesn't stop them.
bool PopupDialog::eventFilter(QObject *obj, QEvent *e)
{
if( obj == window )
return true; //should discard the signal (?)
else
return false; // I tried setting this to 'true' also without success
}
When the user interacts with the main program, a PopupDialog can be created like this:
PopupDialog *popup_msg = new PopupDialog(ptr_to_source_window, "some text message");
popup_msg->show();
// I understand that naming the source 'window' might be a little confusing.
// I apologise for that. The source can in fact be any 'QWidget'.
Everything else works as expected. Only the event filter fails. I want the filter to remove events sent to the window that created the popup; like mouse clicking and key pressing, until the popup is closed. I'm expecting to be extremely embarrassed when someone points out a trivial fix in my code.
You have to ignore all events that arrive in the widget tree of the window. Therefore, you need to install the eventFilter application-wide and check, if the object you are filtering on is a descendant of window. In other words: Replace
window->installEventFilter(this);
by
QCoreApplication::instance()->installEventFilter(this);
and implement the event filter function this way:
bool PopupDialog::eventFilter(QObject *obj, QEvent *e)
{
if ( !dynamic_cast<QInputEvent*>( event ) )
return false;
while ( obj != NULL )
{
if( obj == window )
return true;
obj = obj->parent();
}
return false;
}
I tried it, tested it and it worked for me.
Note: Using event filters in Qt is a bit messy in my experience, since it is not quite transparent what is happening. Expect bugs to pop up from time to time. You may consider disabling the main window instead, if you and your clients don't have a problem with the grayed-out main window as a consequence.
After the massive amount of responses, feedback, suggestions and time ivested in extensive research I've finally found what I believe to be the optimal, and safest solution. I wish to express my sincere gratidtude to everyone for their aid to what Kuba Ober describes as "(...) not as simple of a problem as you think".
We want to filter out all certain events from a widget, including its children. This is difficult, because events may be caught in the childrens default eventfilters and responded to, before they are caught and filtered by the the parent's custom filter for which the programmer implements. The following code solves this problem by installing the filter on all children upon their creation. This example assumes the use of Qt Creator UI-forms and is based on the following blog post: How to install eventfilters for all children.
// The widget class (based on QMainWindow, but could be anything) for
// which you want to install the event filter on, includings its children
class WidgetClassToBeFiltered : public QMainWindow
{
Q_OBJECT
public:
explicit WidgetClassToBeFiltered(QWidget *parent = 0);
~WidgetClassToBeFiltered();
private:
bool eventFilter(QObject*, QEvent*);
Ui::WidgetClassToBeFiltered *ui;
};
...
WidgetClassToBeFiltered::WidgetClassToBeFiltered(QWidget *parent) :
QMainWindow(parent), // Base Class constructor
ui(new Ui::WidgetClassToBeFiltered)
{
installEventFilter(this); // install filter BEFORE setupUI.
ui->setupUi(this);
}
...
bool WidgetClassToBeFiltered::eventFilter(QObject *obj, QEvent* e)
{
if( e->type() == QEvent::ChildAdded ) // install eventfilter on children
{
QChildEvent *ce = static_cast<QChildEvent*>(e);
ce->child()->installEventFilter(this);
}
else if( e->type() == QEvent::ChildRemoved ) // remove eventfilter from children
{
QChildEvent *ce = static_cast<QChildEvent*>(e);
ce->child()->removeEventFilter(this);
}
else if( (e->type() == QEvent::MouseButtonRelease) ) // e.g. filter out Mouse Buttons Relases
{
// do whatever ..
return true; // filter these events out
}
return QWidget::eventFilter( obj, e ); // apply default filter
}
Note that this works, because the eventfilter installs itself on added children! Hence, it should also work without the use of UI-forms.
Refer this code to filter out specific event:-
class MainWindow : public QMainWindow
{
public:
MainWindow();
protected:
bool eventFilter(QObject *obj, QEvent *ev);
private:
QTextEdit *textEdit;
};
MainWindow::MainWindow()
{
textEdit = new QTextEdit;
setCentralWidget(textEdit);
textEdit->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == textEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
qDebug() << "Ate key press" << keyEvent->key();
return true;
} else {
return false;
}
} else {
// pass the event on to the parent class
return QMainWindow::eventFilter(obj, event);
}
}
If you want to set more specific event filter on multiple widgets you can refer following code:
class KeyPressEater : public QObject
{
Q_OBJECT
protected:
bool eventFilter(QObject *obj, QEvent *event);
};
bool KeyPressEater::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug("Ate key press %d", keyEvent->key());
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
KeyPressEater *keyPressEater = new KeyPressEater(this);
QPushButton *pushButton = new QPushButton(this);
pushButton->installEventFilter(keyPressEater);

Emit a signal when the user changes of row with the keyboard (Key_Up and Key_Down) in QTableWidget

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);
}
}

Qt C++ how to use keyPressEvent for a QWidget within a class

I'm trying to use a keyPressEvent, but it is only working when the window has focus and not any of the QWidgets.
Here is my code:
In customdialog.h:
class CustomDialog : public QDialog, public Ui::CustomDialog
{
Q_OBJECT
private:
Ui::CustomDialog *ui;
QString lastKey;
public:
CustomDialog(QWidget * parent = 0);
protected:
void keyPressEvent(QKeyEvent *e);
};
In customdialog.cpp:
void CustomDialog::keyPressEvent(QKeyEvent *e)
{
lastKey = e->text();
qDebug() << lastKey;
}
How can I make all widgets within this class use the same keyPressEvent?
You can solve your problem by installing event filters to every child of CustomDialog:
void CustomDialog::childEvent(QChildEvent *event)
{
if (event->added()) {
event->child()->installEventFilter(this);
}
}
bool CustomDialog::eventFilter(QObject *, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
keyPressEvent(static_cast<QKeyEvent*>(event));
return false;
}
But since every ignored keyPress event is sent to the parent widget, you can get keyPressEvent called multiple times for the same event.
I ended up deciding not to use keyPressEvent in this case for my purposes. I just needed to get the last key pressed in a QTextBrowser. Here is what I ended up doing:
connect(ui->textBrowser, SIGNAL(textChanged()), this, SLOT(handleTextBrowser()));
void CustomDialog::handleTextBrowser()
{
QTextCursor cursor(ui->textBrowser->textCursor());
QString key = ui->textBrowser->toPlainText().mid(cursor.position() - 1, 1);
qDebug() << key;
}

QComboBox EventFilter to popup

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();
}

QSpinBox inside a QScrollArea: How to prevent Spin Box from stealing focus when scrolling?

I have a control with several QSpinBox objects inside a QScrollArea. All works fine when scrolling in the scroll area unless the mouse happens to be over one of the QSpinBoxes. Then the QSpinBox steals focus and the wheel events manipulate the spin box value rather than scrolling the scroll area.
I don't want to completely disable using the mouse wheel to manipulate the QSpinBox, but I only want it to happen if the user explicitly clicks or tabs into the QSpinBox. Is there a way to prevent the QSpinBox from stealing the focus from the QScrollArea?
As said in a comment to an answer below, setting Qt::StrongFocus does prevent the focus rect from appearing on the control, however it still steals the mouse wheel and adjusts the value in the spin box and stops the QScrollArea from scrolling. Same with Qt::ClickFocus.
In order to solve this, we need to care about the two following things:
The spin box mustn't gain focus by using the mouse wheel. This can be done by setting the focus policy to Qt::StrongFocus.
The spin box must only accept wheel events if it already has focus. This can be done by reimplementing QWidget::wheelEvent within a QSpinBox subclass.
Complete code for a MySpinBox class which implements this:
class MySpinBox : public QSpinBox {
Q_OBJECT
public:
MySpinBox(QWidget *parent = 0) : QSpinBox(parent) {
setFocusPolicy(Qt::StrongFocus);
}
protected:
virtual void wheelEvent(QWheelEvent *event) {
if (!hasFocus()) {
event->ignore();
} else {
QSpinBox::wheelEvent(event);
}
}
};
That's it. Note that if you don't want to create a new QSpinBox subclass, then you can also use event filters instead to solve this.
Try removing Qt::WheelFocus from the spinbox' QWidget::focusPolicy:
spin->setFocusPolicy( Qt::StrongFocus );
In addition, you need to prevent the wheel event from reaching the spinboxes. You can do that with an event filter:
explicit Widget( QWidget * parent=0 )
: QWidget( parent )
{
// setup ...
Q_FOREACH( QSpinBox * sp, findChildren<QSpinBox*>() ) {
sp->installEventFilter( this );
sp->setFocusPolicy( Qt::StrongFocus );
}
}
/* reimp */ bool eventFilter( QObject * o, QEvent * e ) {
if ( e->type() == QEvent::Wheel &&
qobject_cast<QAbstractSpinBox*>( o ) )
{
e->ignore();
return true;
}
return QWidget::eventFilter( o, e );
}
edit from Grant Limberg for completeness as this got me 90% of the way there:
In addition to what mmutz said above, I needed to do a few other things. I had to create a subclass of QSpinBox and implement focusInEvent(QFocusEvent*) and focusOutEvent(QFocusEvent*). Basically, on a focusInEvent, I change the Focus Policy to Qt::WheelFocus and on the focusOutEvent I change it back to Qt::StrongFocus.
void MySpinBox::focusInEvent(QFocusEvent*)
{
setFocusPolicy(Qt::WheelFocus);
}
void MySpinBox::focusOutEvent(QFocusEvent*)
{
setFocusPolicy(Qt::StrongFocus);
}
Additionally, the eventFilter method implementation in the event filter class changes its behavior based on the current focus policy of the spinbox subclass:
bool eventFilter(QObject *o, QEvent *e)
{
if(e->type() == QEvent::Wheel &&
qobject_cast<QAbstractSpinBox*>(o))
{
if(qobject_cast<QAbstractSpinBox*>(o)->focusPolicy() == Qt::WheelFocus)
{
e->accept();
return false;
}
else
{
e->ignore();
return true;
}
}
return QWidget::eventFilter(o, e);
}
My attempt at a solution. Easy to use, no subclassing required.
First, I created a new helper class:
#include <QObject>
class MouseWheelWidgetAdjustmentGuard : public QObject
{
public:
explicit MouseWheelWidgetAdjustmentGuard(QObject *parent);
protected:
bool eventFilter(QObject* o, QEvent* e) override;
};
#include <QEvent>
#include <QWidget>
MouseWheelWidgetAdjustmentGuard::MouseWheelWidgetAdjustmentGuard(QObject *parent) : QObject(parent)
{
}
bool MouseWheelWidgetAdjustmentGuard::eventFilter(QObject *o, QEvent *e)
{
const QWidget* widget = static_cast<QWidget*>(o);
if (e->type() == QEvent::Wheel && widget && !widget->hasFocus())
{
e->ignore();
return true;
}
return QObject::eventFilter(o, e);
}
Then I set the focus policy of the problematic widget to StrongFocus, either at runtime or in Qt Designer.
And then I install my event filter:
ui.comboBox->installEventFilter(new MouseWheelWidgetAdjustmentGuard(ui.comboBox));
Done. The MouseWheelWidgetAdjustmentGuard will be deleted automatically when the parent object - the combobox - is destroyed.
Just to expand you can do this with the eventFilter instead to remove the need to derive a new QMySpinBox type class:
bool eventFilter(QObject *obj, QEvent *event)
{
QAbstractSpinBox* spinBox = qobject_cast<QAbstractSpinBox*>(obj);
if(spinBox)
{
if(event->type() == QEvent::Wheel)
{
if(spinBox->focusPolicy() == Qt::WheelFocus)
{
event->accept();
return false;
}
else
{
event->ignore();
return true;
}
}
else if(event->type() == QEvent::FocusIn)
{
spinBox->setFocusPolicy(Qt::WheelFocus);
}
else if(event->type() == QEvent::FocusOut)
{
spinBox->setFocusPolicy(Qt::StrongFocus);
}
}
return QObject::eventFilter(obj, event);
}
With help from this post we cooked a solution for Python/PySide. If someone stumbles across this. Like we did :]
class HumbleSpinBox(QtWidgets.QDoubleSpinBox):
def __init__(self, *args):
super(HumbleSpinBox, self).__init__(*args)
self.setFocusPolicy(QtCore.Qt.StrongFocus)
def focusInEvent(self, event):
self.setFocusPolicy(QtCore.Qt.WheelFocus)
super(HumbleSpinBox, self).focusInEvent(event)
def focusOutEvent(self, event):
self.setFocusPolicy(QtCore.Qt.StrongFocus)
super(HumbleSpinBox, self).focusOutEvent(event)
def wheelEvent(self, event):
if self.hasFocus():
return super(HumbleSpinBox, self).wheelEvent(event)
else:
event.ignore()
This is my Python PyQt5 port of Violet Giraffe answer:
def preventAnnoyingSpinboxScrollBehaviour(self, control: QAbstractSpinBox) -> None:
control.setFocusPolicy(Qt.StrongFocus)
control.installEventFilter(self.MouseWheelWidgetAdjustmentGuard(control))
class MouseWheelWidgetAdjustmentGuard(QObject):
def __init__(self, parent: QObject):
super().__init__(parent)
def eventFilter(self, o: QObject, e: QEvent) -> bool:
widget: QWidget = o
if e.type() == QEvent.Wheel and not widget.hasFocus():
e.ignore()
return True
return super().eventFilter(o, e)
Just to help anyone's in need, it lacks a small detail:
call focusInEvent and focusOutEvent from QSpinBox :
void MySpinBox::focusInEvent(QFocusEvent* pEvent)
{
setFocusPolicy(Qt::WheelFocus);
QSpinBox::focusInEvent(pEvent);
}
void MySpinBox::focusOutEvent(QFocusEvent*)
{
setFocusPolicy(Qt::StrongFocus);
QSpinBox::focusOutEvent(pEvent);
}