实战PyQt5: 066-Model-View框架中的Delegate类

委托类(Delegate)简介

不同于MVC模式,模型-视图设计不包括用于管理与用户交互的完全独立的组件。通常,视图负责将模型数据呈现给用户,并负责处理用户输入。为了让输入的方式具有一定的灵活性,这种交互由委托来完成。这些部件在视图中提供输入功能,同时负责在某些视图中渲染单个项。在QAbstractItemDelegate类中定义了用于控制委托的标准接口。QQAbstractItemDelegate是所有委托类的抽象基类。

委托通过实现paint()和sizeHint()函数来传递其自身的内容。但是,基于简单窗口小部件的委托可以继承QStyledItemDelegate而不是QAbstractItemDelegate的子类,并利用这些函数的默认实现。

委托中的编辑器可以通过使用部件来管理编辑过程或直接处理事件来实现。

使用现有的委托类

Qt提供的标准视图使用QStyledItemDelegate实例提供编辑功能。委托接口的默认实现以常规是样式将项传递给每个标准视图QListView,QTableView和QTreeView。所有标准角色均由标准视图使用的默认委托处理。

视图使用的委托由itemDelegate()函数返回。setItemDelegate()函数允许一个标准视图安装自定义的委托,当为自定义的视图设置一个委托时必须要用到这个函数。

下面实现的委托使用QSpinBox提供编辑功能,主要是想用于显示整数的模型。尽管我们为此目的建立了一个基于整数的自定义模型,但是我们可以很容易地使用QStandardItemModel。我们构造一个表格视图以显示模型的内容,同时会使用自定义的委托来进行编辑。

实战PyQt5: 066-MV框架中的Delegate类

图片来源:doc.qt.io

我们使用QStyledItemDelegate类来作为这个委托类的基类,这样就可以使用已建立好的显示函数。

注意,构建委托时编辑器部件是没有建立的。只有需要时我们才构建一个编辑器部件。

提供编辑器

当表格视图需要提供编辑器时,就向委托请求提供一个适合当前被修改项的编辑器部件。createEditor()函数提供了委托用于建立一个合适部件需要的所有东西:

def createEditor(self, parent, option, index):
        editor = QSpinBox(parent)
        editor.setFrame(False)
        editor.setMinimum(0)
        editor.setMaximum(100)
        return editor

我们在编辑器上安装了委托的默认事件过滤器,以确保它提供用户期望的标准编辑快捷方式。可以将其他快捷方式添加到编辑器,以允许更复杂的行为。

通过调用稍后定义的函数,视图确保能正确地设定编辑器的数据和几何尺寸大小。根据视图提供的模型索引,我们可以建立不同的编辑器。比如,我们有一列数据是整数,一列数据是字符,那根据当前被编辑的列,我们可以返回一个SpinBox 或 QLineEdit。
委托必须提供一个函数以便将模型里的数据复制到编辑器里。在这个例子中,我们读取储存在EditRole里的数据,并相应的把这个值设定在编辑器spinbox中。

def setEditorData(self, editor, index):
        value = index.model().data(index, Qt.EditRole)
        editor.setValue(int(value))

提交数据给模型

当用户在spinbox中完成对值的编辑后,视图通过调用setModelData()函数要求委托将已编辑的值存储到模型中。

def setModelData(self, editor, model, index):
        editor.interpretText()
        value = editor.value()
        model.setData(index, value, Qt.EditRole)

由于视图管理委托的编辑器,因此我们只需要使用提供的编辑器的内容来更新模型。在这种情况下,我们确保视频spinbox里的值最新的,并使用指定的index将spinbox里的值更新到模型中。

在委托完成编辑后,标准QStyledItemDelegate类通过发出closeEdito()信号通知视图。该视图确保关闭并销毁编辑器部件。在这个例子中,我们仅提供简单的编辑工具,因此我们不需要发出此信号。

对数据的所有操作都是通过QAbstractItemModel提供的接口执行的。这使得委托最大程度地独立于它所操控的数据类型,但是必须做出一些假设才能使用某些类型的编辑器部件。在这个例子中,我们假定模​型始终包含整数值,但是我们仍然可以将这个委托与其他类型的模型一起使用,因为QVariant为没考虑到的数据提供了合理的默认值。

更新编辑器的空间外形

编辑者的空间外形由委托负责管理。当创建编辑器时,项的大小或在视图中的位置时发生改变时,必须设置编辑器的空间外形。在视图中,视图选项对象提供了所有必要的几何信息,可以使用它来修改编辑器的空间外形。

def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)

在这个例子中,我们只使用了视图选项提供的项矩形几何尺寸信息。传递多个要素项的委托不会直接使用项矩形。它将根据相对于项中的其他元素对编辑器定位。

编辑提示

编辑后,委托应向其他组件提供有关编辑过程结果的提示,并提供有助于后续任何编辑操作的提示。这是通过发出带有适当提示的closeEditor()信号来实现的。这个过程由默认的QStyledItemDelegate事件过滤器来处理,该事件过滤器是在构造spinbox时创建的。

spinbox的行为可以进行调整,以使其更易用。在QStyledItemDelegate提供的默认事件过滤器中,如果用户按回车键来确认在spinbox中的选择,那么委托就把值提交给模型并关闭spinbox。通过在spinbox上安装自定义的事件过滤器,可以改变这种行为,并提供适合我们需要的编辑提示。例如,我们可以发出带有EditNextItem提示的closeEditor()信号,来自动开始视图中的下一个项的编辑。

另一种不需要使用事件过滤器的方法是提供我们自己的编辑器部件,为方便起见,可能将QSpinBox子类化。这种替代方法能够更好地控制编辑器部件的行为,而无需编写其他代码。如果需要自定义标准Qt编辑器窗口部件的行为,通常的做法是在委托中安装事件过滤器,这样做更容易。

委托不一定发出这些提示,但相对于那些发出提示以支持常用编辑操作的提示相比,未提供这些提示的委托跟应用的整合度会降低。

完整代码

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt,QVariant
from PyQt5.QtGui import QStandardItemModel,QStandardItem
from PyQt5.QtWidgets import (QApplication, QMainWindow, QStyledItemDelegate,
                             QSpinBox, QTableView)
 
class SpinBoxDelegate(QStyledItemDelegate):
    def __init(self, parent=None):
        super(SpinBoxDelegate, self).__init__(parent)
        
    def createEditor(self, parent, option, index):
        editor = QSpinBox(parent)
        editor.setFrame(False)
        editor.setMinimum(0)
        editor.setMaximum(100)
        return editor
    
    def setEditorData(self, editor, index):
        value = index.model().data(index, Qt.EditRole)
        editor.setValue(int(value))
        
    def setModelData(self, editor, model, index):
        editor.interpretText()
        value = editor.value()
        model.setData(index, value, Qt.EditRole)
        
    def updateEditorGeometry(self, editor, option, index):
        editor.setGeometry(option.rect)
               
 
class DemoDelegate(QMainWindow):
    def __init__(self, parent=None):
        super(DemoDelegate, self).__init__(parent)   
        
         # 设置窗口标题
        self.setWindowTitle('实战PyQt5: Model-View框架演示')      
        # 设置窗口大小
        self.resize(320, 200)
      
        self.initUi()
        
    def initUi(self):
        model = QStandardItemModel(4, 2, self)
        for row in range(4):
            for col in range(2):
                item = QStandardItem('%s'%((row+1) * (col + 1)))
                model.setItem(row, col, item)
        
        table = QTableView(self)
        table.setModel(model)
        
        delegate = SpinBoxDelegate(table)
        table.setItemDelegate(delegate)
        
        self.setCentralWidget(table)
        
    
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = DemoDelegate()
    window.show()
    sys.exit(app.exec())

运行结果如下图:

实战PyQt5: 066-MV框架中的Delegate类

使用委托来定制单元格数据的编辑器

本文知识点

  • 什么是委托,委托的基本用法;
  • 使用QStyledItemDelegate为基类,实现一个委托,提供自定义编辑器;
  • 编辑器数据提交给模型;
  • 调整编辑器的空间外形。

前一篇: 实战PyQt5: 065-Model-View框架中的View类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值