确定以前有焦点的元素?
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_())
在 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_())