Solved Intercept QScrollBar Key presses
-
Hi.
I want to disable horizontal scrolling of my QTreeView when pressing left/right keys. This scrolling of course only happens when the horizontal scrollbar is shown (the policy is set to show it as needed).I tried to subclass QScrollBar and reimplement the keyPressEvent() but this never seems to be called. I also tried to use an EventFilter in the ScrollBar subclass, but no keyboard events are reported there.
How do I go about this? Thanks!
-
I finally solved it as follows (I want to use the left/right key presses to switch focus between 3
QTreeView
lists without the keys to reach the horizontal scrollbars of the views)- Install eventFilter on the views (I previously only had them on the view's viewport).
- In the event filter, intercept the key press conditions you want to filter, and forward the event to MainWindow.
if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast<QKeyEvent *>(event); const int key = keyEvent->key(); QTreeView* tree = qobject_cast<QTreeView*>(obj); if (((key == Qt::Key_Right || key == Qt::Key_Left) && (tree == ui->fileTree || tree == ui->scriptTree || tree == m_dirList)) || (key == Qt::Key_Space || key == Qt::Key_Escape)) { MainWindow::event(event); //Forward to the keyPressEvent() handler of MainWindow return true; //Must return true to eat the event }
- In
MainWindow::keyPressEvent()
do your stuff. Here, I want to switch focus between my views in a non-cyclic way, and use the Spacebar to start playing the file currently selected in the fileTree view, while Esc stops the playback. I did this as follows:
int key = ev->key(); if (key == Qt::Key_Left) { QTreeView* tree = qobject_cast<QTreeView*>(QApplication::focusWidget()); if (tree == ui->scriptTree) { if (m_fileModel) ui->fileTree->setFocus(); else if (m_dirModel) m_dirList->setFocus(); } else if (tree == ui->fileTree) { if (m_dirModel) m_dirList->setFocus(); } } else if (key == Qt::Key_Right) { QTreeView* tree = qobject_cast<QTreeView*>(QApplication::focusWidget()); if (tree == ui->fileTree) { if (m_scriptModel) ui->scriptTree->setFocus(); } else if (tree == m_dirList) { if (m_fileModel) ui->fileTree->setFocus(); else if (m_scriptModel) ui->scriptTree->setFocus(); } } else if (key == Qt::Key_Space && ui->actionPlay_Spacebar->isChecked()) { //Stop play file } else if (key == Qt::Key_Escape) { if (m_audioEngine->mode() == QAudio::AudioOutput && m_audioEngine->state() != QAudio::StoppedState) { //Stop playback } }
By forwarding the Space and Esc events, I can initiate play/stop of the selected file no matter if the view has focus or not (most of my Ui widgets have click-focus only).
As it turned out, the solution to my problem was very simple indeed...
-
Hi
I think it happens on the viewport so install eventfilter on that on and see.
(ui->treeView->viewport())
You should be able to eat the keys. -
@mrjj , thanks, as always for your help.
I tried that before. Installing an eventFilter on the viewport of a QTreeView does not seem to pass any key presses. I use the filter on the viewport to intercept mouse clicks though, which works.
When I install the eventFilter directly on the QTreeView, the key presses are reported, but they seem unusable for my purpose, since I can't determine which left/right key press will result in a focus change, and which in a scroll. For all key presses, the object reported by the eventFilter is the treeview, never its scrollbar.
I should mention that I am already intercepting left/right key presses on my treeviews to change the focus between these 3 views. I am doing this in
MainWindow::keyPressEvent(...)
. This works fine, except for the horizontal scrolling when the treeview has a scrollbar. And as I wrote earlier, installing the eventFilter on theQTreeView::horizontalScrollBar
does not report key events.When the scrollbar is at one extremity, the next keypress will change the focus to the previous/next focusable widget. However, when e.g. at the left extremity, a press on the right arrow key will scroll the bar one notch. Only when the bar has reached the right extremity will the focus change. This is the behavior I seek to disable: I want to filter out those scrolls and but keep the focus change.
-
@Diracsbracket
Hi
Very good description.It seems like horizontalScrollBar is concerned only with keys if
setFocusPolicy() is used to set to something else than Qt::NoFocus ( which is default)
So most likely its the view that transform the keys into scroll.
(also suggested by your event filter never saw a key when on the scrollbar )I been roaming around in
https://code.woboq.org/qt5/qtbase/src/widgets/itemviews/qtreeview.cpp.html
to see if i can spot where it uses a key but so far i didnt see anything interesting.
(except views being c++ friends to QAbstractSlider)
in
line 1981 void QTreeView::keyPressEvent(QKeyEvent *event)
nothing seems to use scrollbars and it then calls
https://code.woboq.org/qt5/qtbase/src/widgets/itemviews/qabstractitemview.cpp.html#_ZN17QAbstractItemView13keyPressEventEP9QKeyEventWhere it does
case Qt::Key_Left:
2351 newCurrent = moveCursor(MoveLeft, event->modifiers());And i followed that call chain around but found nothing that suggest scrolling the view.
It might be hidden in the _p private classes.
Lets see if others have some trick or ideas as i do not see a way.
-
@mrjj Awesome! Thanks for your efforts. I hope I too can soon get up to level with my Qt skills an go and delve into the source code to find the reasons for one or the other behavior.
If any solution presents itself, I will let you know. Thanks again.
-
I finally solved it as follows (I want to use the left/right key presses to switch focus between 3
QTreeView
lists without the keys to reach the horizontal scrollbars of the views)- Install eventFilter on the views (I previously only had them on the view's viewport).
- In the event filter, intercept the key press conditions you want to filter, and forward the event to MainWindow.
if (event->type() == QEvent::KeyPress) { QKeyEvent* keyEvent = static_cast<QKeyEvent *>(event); const int key = keyEvent->key(); QTreeView* tree = qobject_cast<QTreeView*>(obj); if (((key == Qt::Key_Right || key == Qt::Key_Left) && (tree == ui->fileTree || tree == ui->scriptTree || tree == m_dirList)) || (key == Qt::Key_Space || key == Qt::Key_Escape)) { MainWindow::event(event); //Forward to the keyPressEvent() handler of MainWindow return true; //Must return true to eat the event }
- In
MainWindow::keyPressEvent()
do your stuff. Here, I want to switch focus between my views in a non-cyclic way, and use the Spacebar to start playing the file currently selected in the fileTree view, while Esc stops the playback. I did this as follows:
int key = ev->key(); if (key == Qt::Key_Left) { QTreeView* tree = qobject_cast<QTreeView*>(QApplication::focusWidget()); if (tree == ui->scriptTree) { if (m_fileModel) ui->fileTree->setFocus(); else if (m_dirModel) m_dirList->setFocus(); } else if (tree == ui->fileTree) { if (m_dirModel) m_dirList->setFocus(); } } else if (key == Qt::Key_Right) { QTreeView* tree = qobject_cast<QTreeView*>(QApplication::focusWidget()); if (tree == ui->fileTree) { if (m_scriptModel) ui->scriptTree->setFocus(); } else if (tree == m_dirList) { if (m_fileModel) ui->fileTree->setFocus(); else if (m_scriptModel) ui->scriptTree->setFocus(); } } else if (key == Qt::Key_Space && ui->actionPlay_Spacebar->isChecked()) { //Stop play file } else if (key == Qt::Key_Escape) { if (m_audioEngine->mode() == QAudio::AudioOutput && m_audioEngine->state() != QAudio::StoppedState) { //Stop playback } }
By forwarding the Space and Esc events, I can initiate play/stop of the selected file no matter if the view has focus or not (most of my Ui widgets have click-focus only).
As it turned out, the solution to my problem was very simple indeed...