Stop focus becoming ensnared in QTableView

0

MRE:

from PyQt5.QtCore import QRect, Qt, QAbstractTableModel
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView, QWidget, QVBoxLayout, QLineEdit, QListWidget
import sys, types

class MyTableModel( QAbstractTableModel ):
    def __init__( self ):
        super(MyTableModel, self).__init__()
        data = [
              [4, 9, 2],
              [1, 0, 0],
              [3, 5, 0],
        ]
        self._data = data

    def data(self, index, role):
        if role == Qt.DisplayRole:
            return self._data[index.row()][index.column()]

    def rowCount(self, index):
        return len(self._data)

    def columnCount(self, index):
        return len(self._data[0])

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.resize(600, 700 )
        self.centralwidget = QWidget(MainWindow)
        self.verticalLayoutWidget = QWidget(self.centralwidget)
        self.verticalLayoutWidget.setGeometry( QRect(20, 20, 500, 500))
        self.verticalLayout = QVBoxLayout(self.verticalLayoutWidget)
        self.comps = []
        
        self.title_line_edit = QLineEdit(self.verticalLayoutWidget)
        self.comps.append( self.title_line_edit )
        self.verticalLayout.addWidget(self.title_line_edit)

        self.tags_list = QListWidget(self.verticalLayoutWidget)
        self.comps.append( self.tags_list )
        self.verticalLayout.addWidget(self.tags_list)
        
        # UNCOMMENT THESE LINES:
#         self.table_view = QTableView(self.verticalLayoutWidget)
#         self.comps.append( self.table_view )
#         self.table_view.setGeometry(QRect(20, 20, 200, 200))
#         self.verticalLayout.addWidget(self.table_view)
#         self.table_view.setModel( MyTableModel() )

        self.title_line_edit2 = QLineEdit(self.verticalLayoutWidget)
        self.comps.append( self.title_line_edit2 )
        self.verticalLayout.addWidget(self.title_line_edit2)
        
        MainWindow.setCentralWidget(self.centralwidget)

class MyWindow(QMainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        def focus_in_event( self, event ):
            self.original_stylesheet = self.styleSheet()
            self.setStyleSheet( 'border:1px solid rgb(0, 255, 0);' )
        def focus_out_event( self, event ):
            self.setStyleSheet( self.original_stylesheet )
        for comp in self.ui.comps:
            comp.focusInEvent = types.MethodType( focus_in_event, comp )
            comp.focusOutEvent = types.MethodType( focus_out_event, comp )

app = QApplication(sys.argv)
application = MyWindow()
application.show()
sys.exit(app.exec())

As is, the focus tabs nicely through the components.

But when you uncomment to add the QTableView, tabbing into this component means you can't tab out again. I tried Shift-Tab (which tabs in the reverse cycling direction, but still within the table cells) and I tried Ctrl-Tab/Ctrl-Shift-Tab (which seems to move the current cell).

I'm not interested in using Tab at all to navigate through these cells (the arrow navigation keys do the same thing) and just want this "ensnaring" not to happen, so I can Tab/Shift-Tab through all components in circular fashion as before.

I tried a couple of things, adding these lines to the end of MyWindow.__init__:

    # seemingly no effect:
    def focus_next_prev_child( self, next ):
        return False
    self.ui.table_view.focusNextPrevChild = types.MethodType( focus_next_prev_child, comp )
    # just omits the table view from the focus cycle:
    self.ui.table_view.setFocusPolicy( Qt.NoFocus )

I also tried intercepting the keystrokes reaching the table view (again, add these lines to MyWindow.__init__:

    def keypress_event( self, key_event ):
        modifs = key_event.modifiers()
        key = key_event.key()
        def unmodified_not_keypad():
            print( f'unmodified, key: {hex( key )} text: {key_event.text()}' )
            # strangely, if you press Shift-Tab, this gets to here (i.e. unmodified)
            if key == Qt.Key_Tab or key == Qt.Key_Backtab:
                # what might I do here? 
                # how might I find the next component in the cycle (forwards or backwards)?
                return True
        switcher = {
            0x00000000: unmodified_not_keypad,
        }
        def print_invalid():
            print( f'invalid, hex( modifs): {hex(modifs)}' )
        # if any called function returns True this means skip the default action...
        if switcher.get( int( modifs ), print_invalid )():
            return
        # execute default action:
        QTableView.keyPressEvent( self, key_event )
    self.ui.table_view.keyPressEvent = types.MethodType( keypress_event, self.ui.table_view )

This does indeed intercept Tab and Shift-Tab, and prevent the default action, but I don't know how to find the right components in the focus cycle. Also it seems like a sledgehammer to crack a nut: presumably there's a more elegant way to get the behaviour I want...

python
pyqt5
focus
qtableview
asked on Stack Overflow Nov 1, 2020 by mike rodent • edited Nov 1, 2020 by mike rodent

1 Answer

1

If you don't want to tab through the cells at all, add self.table_view.setTabKeyNavigation(False)

https://doc.qt.io/qt-5/qabstractitemview.html#tabKeyNavigation-prop

answered on Stack Overflow Nov 1, 2020 by LittleFoxyFox

User contributions licensed under CC BY-SA 3.0