确定以前有焦点的元素?

Determine the element which previously had focus?

在 Java 中,FocusEvent 有一个方法 getOppositeComponent(),它是焦点来自或去向的地方。

在 PyQt5 中有没有什么方法可以在重写时找到以前有焦点的东西focusInEvent

如注释中所述,我希望能够在 table 视图获得焦点时自动启动编辑会话,通过在单元格中按 Ctrl-E 结束编辑会话,将焦点留在table 查看但未触发另一个编辑会话。

MRE(使用笨重的添加属性机制):

import sys
from PyQt5 import QtWidgets, QtCore, QtGui

class TableViewDelegate( QtWidgets.QStyledItemDelegate ):
    def createEditor(self, parent, option, index):
        # QPlainTextEdit for multi-line text cells... 
        editor = QtWidgets.QPlainTextEdit( parent )
        table_view = parent.parent()
        def end_edit():
            print( f'end_edit...' )
            # clunky mechanism 
            table_view.do_not_start_edit = True
            self.closeEditor.emit( editor )
            
        self.end_edit_shortcut = QtWidgets.QShortcut( 'Ctrl+E', editor, context = QtCore.Qt.ShortcutContext.WidgetShortcut )
        self.end_edit_shortcut.activated.connect( end_edit )
        return editor

class TableViewModel( QtCore.QAbstractTableModel ):
    def __init__( self ):
        super().__init__()
        self._data = [
            [ 1, 2, ],
            [ 3, 4, ],
        ]
    
    def rowCount( self, *args ):
        return len( self._data )
    
    def columnCount(self, *args ):
        return 2
    
    def data(self, index, role):
        if role == QtCore.Qt.DisplayRole:
            # print( f'data... {self._data[index.row()][index.column()]}')
            return self._data[index.row()][index.column()]
        
    def flags( self, index ):
        result = super().flags( index )
        return QtCore.Qt.ItemFlag.ItemIsEditable | result
    
class TableView( QtWidgets.QTableView ):
    def __init__(self ):
        super().__init__()
        self.setTabKeyNavigation( False )
        self.setItemDelegate( TableViewDelegate() ) 
    
    def focusInEvent( self, event ):
        print( f'table view focus-in event')
        super().focusInEvent( event )
        
        if hasattr( self, 'do_not_start_edit' ):
            print( f'start of edit vetoed...')
            del self.do_not_start_edit
            return
        
        n_rows = self.model().rowCount()
        if n_rows == 0:
            return
        # go to last row, col 1
        cell1_index = self.model().index( n_rows - 1, 1 )
        self.edit( cell1_index )

class MainWindow( QtWidgets.QMainWindow ):
    def __init__( self ):
        super().__init__()
        self.setWindowTitle( 'Editor focus MRE' )
        layout = QtWidgets.QVBoxLayout()
        self.table_view = TableView()
        table_label = QtWidgets.QLabel( '&Table' )
        layout.addWidget( table_label )
        table_label.setBuddy( self.table_view )
        layout.addWidget( self.table_view )
        self.table_view.setModel( TableViewModel() )
        edit_box = QtWidgets.QLineEdit()
        layout.addWidget( edit_box )
        centralwidget = QtWidgets.QWidget( self )
        centralwidget.setLayout( layout )
        self.setCentralWidget( centralwidget )
        self.setGeometry( QtCore.QRect(400, 400, 400, 400) )
        
        edit_box.setFocus()
                
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())    

首先按 Alt-T:这会将焦点移动到 QTableView 并开始编辑右下角的单元格。输入一些文本,然后按 Ctrl-E。这将停止编辑会话,并且由于笨拙的属性 do_not_start_edit,新的编辑会话被(根据需要)否决。结束编辑会话的另一种方法是单击 QLineEdit,例如。

我不确定这种相当笨拙的“添加属性”机制是否适用于所有情况。事实上,它似乎比我最初想象的要好一些,因此这个 MRE... 对我来说它似乎不太优雅。

由于您只想在事件中特定焦点后才开始编辑单元格,因此您必须检查事件的 reason()

您的方法存在一个问题,即您正在使用标签的伙伴功能来设置焦点,如果您想在激活快捷方式时开始编辑,这可能是个问题。如果 table 已经有焦点,它无法接收 focusInEvent。

为了实现这一点,您可以使用所需的快捷方式创建一个 QAction,将其添加到小部件并在 table 中创建一个专用函数以开始编辑。然后你可以使用html标签“模拟”标签上的助记效果。

class TableView(QtWidgets.QTableView):
    def __init__(self):
        super().__init__()
        self.setTabKeyNavigation(False)
    
    def startEdit(self):
        n_rows = self.model().rowCount()
        if n_rows:
            cell1_index = self.model().index(n_rows - 1, 1)
            self.edit(cell1_index)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        # ...
        table_label = QtWidgets.QLabel('<u>T</u>able')
        layout.addWidget(table_label)
        # this is not necessary anymore...
        # table_label.setBuddy(self.table_view)

        self.startEditAction = QtWidgets.QAction(self)
        self.startEditAction.setShortcut('alt+t')
        self.startEditAction.triggered.connect(self.table_view.startEditLastCell)
        self.addAction(self.startEditAction)

我认为,答案是使用 QApplication.focusChanged( old, new ), as alluded to by Musicamante in a comment, and combine this with overriding QStyledItemDelegate.destroyEditor( editor, index ),这样每次调用 createEditor 时都会重新使用编辑器组件,而不是破坏编辑器组件(第一次调用的惰性实例化)。然后很容易检测焦点何时从编辑器组件传递到 QTableView.

这种“可重复使用的编辑器组件”技术在 Java Swing(可能还有 JavaFX)中广为人知。它似乎在 PyQt5 中工作正常:修改 MRE:

import sys
from PyQt5 import QtWidgets, QtCore, QtGui

class TableViewDelegate( QtWidgets.QStyledItemDelegate ):
    def __init__( self, *args, **kvargs ):
        super().__init__( *args, **kvargs )
        self.editor = None
    
    def createEditor(self, parent, option, index):
        if not self.editor:  
            self.editor = QtWidgets.QPlainTextEdit( parent )
            def end_edit():
                self.closeEditor.emit( self.editor )
            self.end_edit_shortcut = QtWidgets.QShortcut( 'Ctrl+E', 
                self.editor, context = QtCore.Qt.ShortcutContext.WidgetShortcut )
            self.end_edit_shortcut.activated.connect( end_edit )
        return self.editor

    def setEditorData(self, editor, index ):
        existing_text = index.model().data( index, QtCore.Qt.DisplayRole )
        editor.document().setPlainText( str( existing_text ) )            
    
    def destroyEditor( self, editor, index ):
        index.model().setData( index, editor.document().toPlainText() )
        editor.clear()

class TableViewModel( QtCore.QAbstractTableModel ):
    def __init__( self ):
        super().__init__()
        self._data = [[ 1, 2, ], [ 3, 4, ],]
    def rowCount( self, *args ):
        return len( self._data )
    def columnCount(self, *args ):
        return 2
    def data(self, index, role):
        if role == QtCore.Qt.DisplayRole:
            return self._data[index.row()][index.column()]
    def setData(self, index, value, role = QtCore.Qt.EditRole ):
        if role == QtCore.Qt.EditRole:
            self._data[ index.row() ][ index.column() ] = value
    def flags( self, index ):
        result = super().flags( index )
        return QtCore.Qt.ItemFlag.ItemIsEditable | result
    
class TableView( QtWidgets.QTableView ):
    def __init__(self ):
        super().__init__()
        self.setTabKeyNavigation( False )
        self.setItemDelegate( TableViewDelegate() ) 
        
    def focus_changed( self, old, new ):
        print( f'table view focus change old {old} new {new}')
        if new == self:
            editor_component = self.itemDelegate().editor
            if old == None or old != editor_component:
                n_rows = self.model().rowCount()
                if n_rows == 0:
                    return
                # go to last row, col 1
                cell1_index = self.model().index( n_rows - 1, 1 )
                self.edit( cell1_index )
            else:
                print( 'edit command VETOED' )

class MainWindow( QtWidgets.QMainWindow ):
    def __init__( self ):
        super().__init__()
        self.setWindowTitle( 'Editor focus MRE' )
        layout = QtWidgets.QVBoxLayout()
        self.table_view = TableView()
        table_label = QtWidgets.QLabel( '&Table' )
        layout.addWidget( table_label )
        table_label.setBuddy( self.table_view )
        layout.addWidget( self.table_view )
        self.table_view.setModel( TableViewModel() )
        edit_box = QtWidgets.QLineEdit()
        layout.addWidget( edit_box )
        centralwidget = QtWidgets.QWidget( self )
        centralwidget.setLayout( layout )
        self.setCentralWidget( centralwidget )
        self.setGeometry( QtCore.QRect(400, 400, 400, 400) )
        
        edit_box.setFocus()
                
app = QtWidgets.QApplication([])

main_window = MainWindow()
app.focusChanged.connect( main_window.table_view.focus_changed )

main_window.show()
sys.exit(app.exec_())