代码之家  ›  专栏  ›  技术社区  ›  jfsturtz

QTableView-防止离开单元格和关闭委托编辑器

  •  0
  • jfsturtz  · 技术社区  · 6 年前

    我有一个QAbstractTableModel+QTableView,并分配了一个委托来创建一个QLabel小部件用作编辑器。

    什么都不要做

    我原以为这会很容易,但我一直想不出怎么做。

    closeEditor 信号。代码如下所示。它有点长(以便成为独立的可执行文件),但显示的大部分内容只是标准的模型/视图/委托内容。有趣的部分在下面。我已经定义了一个槽( on_closeEditor() ),并将其连接到 信号(参见 ### ... ### 评论)。

    当按下Enter键时,代理将捕捉并发出 关闭编辑器 打开关闭编辑器() 插槽被调用。所以这种联系似乎是正确的。

    (还有一件事,即使我的代码 能够 当代理编辑器关闭时获得控制,我不清楚如何阻止它发生。但一次只做一件事……)

    有没有直接的方法?我觉得我一定错过了什么。。。

    谢谢!

    样本代码

    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_())
    

    [编辑]

    我的下一个想法是可以为委托安装一个事件过滤器,并过滤掉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 回复  |  直到 6 年前
        1
  •  1
  •   jfsturtz    6 年前

    如果有人关注这一点,并好奇它是如何运作的:

    如果单元格中的数据未验证:

    • 重写代理的 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_())