我有一个QAbstractTableModel + QTableView,并分配了一个Delegate,它创建一个QLabel小部件以用作编辑器。
我只是想这样做:当“委托”编辑器处于活动状态时,在某些情况下(当单元格中的数据无效时),禁止离开单元格并停留在编辑会话中。换句话说,如果情况需要,并且用户尝试离开单元格(通过任何方式,例如Tab,箭头键,鼠标单击等),什么都不做。只是保持原状,好像什么都没发生。
我认为这很容易,但是我仍然无法弄清楚该怎么做。
我的第一个想法是我可以捕获代表的closeEditor
信号。该代码如下所示。它有点长(以便成为独立的可执行文件),但显示的大部分内容只是标准的模型/视图/委托内容。有趣的部分在底部。我已经定义了一个插槽(on_closeEditor()
),并将其连接到closeEditor
信号(请参见### ... ###
注释)。
当按下Enter键时,代表将捕获并显式发出closeEditor
信号。发生这种情况时,将调用on_closeEditor()
插槽。因此,连接似乎建立正确。
但是,当单元通过其他方式(例如,Tab键或鼠标单击)离开时,尽管代理编辑器确实似乎已关闭,但插槽从未被调用。
(还有一个问题,即使我的代码可以在Delegate编辑器关闭时获得控制权,我也不清楚如何阻止它发生。时间...)
有没有简单的方法可以做到这一点?我觉得我一定很想念东西...
谢谢!
示例代码
from PyQt5 import QtCore, QtWidgets, QtGui
import sys
# ------------------------------------------------------------------------------
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data = [[]], headers = None, parent = None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.__data = data
def rowCount(self, parent):
return len(self.__data)
def columnCount(self, parent):
return len(self.__data[0])
def data(self, index, role):
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
value = self.__data[row][column]
return value
if role == QtCore.Qt.BackgroundRole:
return QtGui.QBrush(QtGui.QColor(230, 240, 250))
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
if value is None:
value = ''
self.__data[row][column] = value
return True
return False
def flags(self, index):
return QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsEditable
# ------------------------------------------------------------------------------
class TableView(QtWidgets.QTableView):
def __init__(self, parent=None):
super().__init__(parent)
self.blocked = False
def keyPressEvent(self, event):
key = event.key()
mod = int(event.modifiers())
row = self.currentIndex().row()
if key == QtCore.Qt.Key_Q and mod == QtCore.Qt.CTRL:
self.close()
exit()
super().keyPressEvent(event)
# ------------------------------------------------------------------------------
class Delegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
self.editor = QtWidgets.QLabel(parent)
return self.editor
def setEditorData(self, label, index):
print('setEditorData()')
model = index.model()
v = model.data(index, QtCore.Qt.EditRole)
model.setData(index, None, QtCore.Qt.EditRole)
def setModelData(self, label, model, index):
print('setModelData()')
value = label.text()
row = index.row()
col = index.column()
model.setData(index, value, QtCore.Qt.EditRole)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def eventFilter(self, target, event):
if event.type() == QtCore.QEvent.KeyPress:
key = event.key()
mod = int(event.modifiers())
if (
key >= QtCore.Qt.Key_Space and key <= QtCore.Qt.Key_AsciiTilde and
(mod == QtCore.Qt.NoModifier or mod == QtCore.Qt.SHIFT)
):
text = self.editor.text()
self.editor.setText(text + event.text())
return True
# Enter (or ctrl-Enter) explicitly emits commitData, closeEditor
elif (
key == QtCore.Qt.Key_Return and
(mod == QtCore.Qt.NoModifier or mod == QtCore.Qt.CTRL)
):
self.commitData.emit(target)
self.closeEditor.emit(target)
return True
return False
### closeEditor slot ###
def on_closeEditor(self, editor, hint):
print('closeEditor()')
# ------------------------------------------------------------------------------
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setStyle('fusion')
tableView = TableView()
tableView.resize(550, 160)
delegate = Delegate()
tableView.setItemDelegate(delegate)
### connect closeEditor signal to slot ###
delegate.closeEditor.connect(delegate.on_closeEditor)
tableView.show()
rowCount = 3
columnCount = 4
data = [
['foo', 'goo', 'zoo', 'moo'],
['bar', 'zar', 'jar', 'gar'],
['qux', 'lux', 'mux', 'sux']
]
model = TableModel(data)
tableView.setModel(model)
sys.exit(app.exec_())
[edit]
我的下一个想法是,我可以为Delegate安装一个事件过滤器,并过滤掉FocusAboutToChange和/或FocusOut事件。实际上,我真的以为这将是完美的解决方案。
但是没有用。 :-(
print()
语句表明已正确检测到事件。我以为如果eventFilter()
返回了True
,事件将被停止。但事实并非如此。光标仍然会离开已编辑的单元格。
代码
from PyQt5 import QtCore, QtWidgets, QtGui
import sys
# ------------------------------------------------------------------------------
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data = [[]], headers = None, parent = None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.__data = data
def rowCount(self, parent):
return len(self.__data)
def columnCount(self, parent):
return len(self.__data[0])
def data(self, index, role):
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
value = self.__data[row][column]
return value
if role == QtCore.Qt.BackgroundRole:
return QtGui.QBrush(QtGui.QColor(230, 240, 250))
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
if value is None:
value = ''
self.__data[row][column] = value
return True
return False
def flags(self, index):
return QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsEditable
# ------------------------------------------------------------------------------
class TableView(QtWidgets.QTableView):
def __init__(self, parent=None):
super().__init__(parent)
self.blocked = False
def keyPressEvent(self, event):
key = event.key()
mod = int(event.modifiers())
if key == QtCore.Qt.Key_Q and mod == QtCore.Qt.CTRL:
self.close()
exit()
super().keyPressEvent(event)
# ------------------------------------------------------------------------------
class Delegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
self.editor = QtWidgets.QLabel(parent)
return self.editor
def setEditorData(self, label, index):
model = index.model()
v = model.data(index, QtCore.Qt.EditRole)
model.setData(index, None, QtCore.Qt.EditRole)
def setModelData(self, label, model, index):
value = label.text()
row = index.row()
col = index.column()
model.setData(index, value, QtCore.Qt.EditRole)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def eventFilter(self, target, event):
if event.type() == QtCore.QEvent.KeyPress:
key = event.key()
mod = int(event.modifiers())
# ASCII input
if (
key >= QtCore.Qt.Key_Space and key <= QtCore.Qt.Key_AsciiTilde and
(mod == QtCore.Qt.NoModifier or mod == QtCore.Qt.SHIFT)
):
text = self.editor.text()
self.editor.setText(text + event.text())
return True
### Ostensibly filter out FocusAboutToChange and FocusOut events ###
if event.type() == QtCore.QEvent.FocusAboutToChange:
print('FocusAboutToChange')
return True
if event.type() == QtCore.QEvent.FocusOut:
print('FocusOut')
return True
return False
# ------------------------------------------------------------------------------
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setStyle('fusion')
rowCount = 3
columnCount = 4
data = [
['foo', 'goo', 'zoo', 'moo'],
['bar', 'zar', 'jar', 'gar'],
['qux', 'lux', 'mux', 'sux']
]
tableView = TableView()
tableView.resize(550, 160)
delegate = Delegate()
tableView.setItemDelegate(delegate)
delegate.installEventFilter(delegate)
tableView.show()
model = TableModel(data)
tableView.setModel(model)
sys.exit(app.exec_())
答案 0 :(得分:0)
万一有人关注并好奇它是如何工作的:
在Riverbank Computing PyQt邮件列表的一些人的帮助下,我得出了以下解决方案。在涉及以下内容:
如果单元格中的数据无效:
重写委托的setModelData()
方法,并禁止将单元格数据发布到模型中。使用label.setFocus()
保持焦点在编辑器小部件上,使用view.setCurrentIndex()
保持视图的当前索引不变。
覆盖视图的closeEditor()
插槽以防止编辑器关闭。
解决方案如下所示。
from PyQt5 import QtCore, QtWidgets, QtGui
import sys, re
# ------------------------------------------------------------------------------
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data = [[]], headers = None, parent = None):
QtCore.QAbstractTableModel.__init__(self, parent)
self.__data = data
def rowCount(self, parent):
return len(self.__data)
def columnCount(self, parent):
return len(self.__data[0])
def data(self, index, role):
row = index.row()
column = index.column()
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
value = self.__data[row][column]
return value
if role == QtCore.Qt.BackgroundRole:
return QtGui.QBrush(QtGui.QColor(230, 240, 250))
def setData(self, index, value, role = QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
row = index.row()
column = index.column()
if value is None:
value = ''
self.__data[row][column] = value
return True
return False
def flags(self, index):
return QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsEditable
# ------------------------------------------------------------------------------
class TableView(QtWidgets.QTableView):
def __init__(self, parent=None):
super().__init__(parent)
def keyPressEvent(self, event):
key = event.key()
mod = int(event.modifiers())
if key == QtCore.Qt.Key_Q and mod == QtCore.Qt.CTRL:
self.close()
exit()
super().keyPressEvent(event)
def closeEditor(self, editor, hint):
### --- If data validates, close editor; otherwise, don't --- ###
if editor.validate():
print(f'>> Closing editor')
super().closeEditor(editor, hint)
else:
print(f'>> Not closing editor')
# ------------------------------------------------------------------------------
class Delegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self.editor = None
def createEditor(self, parent, option, index):
self.view = parent.parent()
self.editor = CellEditor(parent)
return self.editor
def setEditorData(self, label, index):
model = index.model()
v = model.data(index, QtCore.Qt.EditRole)
def setModelData(self, label, model, index):
value = label.text()
row = index.row()
col = index.column()
### --- If data validates, post it to the model; otherwise, don't --- ###
if label.validate():
model.setData(index, value, QtCore.Qt.EditRole)
print(f'>> [setModelData({value})] accepted')
else:
label.setFocus()
self.view.setCurrentIndex(index)
print(f'>> [setModelData({value})] rejected')
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def eventFilter(self, target, event):
if event.type() == QtCore.QEvent.KeyPress:
key = event.key()
mod = int(event.modifiers())
# ASCII input -- add to cell value
if (
key >= QtCore.Qt.Key_Space and key <= QtCore.Qt.Key_AsciiTilde and
(mod == QtCore.Qt.NoModifier or mod == QtCore.Qt.SHIFT)
):
text = self.editor.text()
self.editor.setText(text + event.text())
return True
# [ctrl-H], Backspace -- delete a character
elif (
(key == QtCore.Qt.Key_H and mod == QtCore.Qt.CTRL) or
(key == QtCore.Qt.Key_Backspace and mod == QtCore.Qt.NoModifier)
):
self.editor.setText(self.editor.text()[:-1])
return True
return False
# ------------------------------------------------------------------------------
class CellEditor(QtWidgets.QLabel):
def __init__(self, parent=None):
super().__init__(parent)
self.setStyleSheet('font-style: italic; font-weight: bold; color: blue')
self.setAutoFillBackground(True)
### --- Sample validation function --- ###
def validate(self):
return re.fullmatch('\d+', self.text())
# ------------------------------------------------------------------------------
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setStyle('fusion')
rowCount = 3
columnCount = 4
data = [
['foo', 'goo', 'zoo', 'moo'],
['bar', 'zar', 'jar', 'gar'],
['qux', 'lux', 'mux', 'sux']
]
view = TableView()
view.resize(550, 160)
model = TableModel(data)
view.setModel(model)
view.show()
delegate = Delegate()
view.setItemDelegate(delegate)
sys.exit(app.exec_())