124-Model/View-自定义委托代理控件

自定义委托代理控件

代理在视图与模型之间交互操作时提供临时编辑组件的功能。模型向视图提供数据是单向的,一般仅用于显示。当需要在视图上编辑数据时,代理功能会为编辑数据提供一个编辑器,这个编辑器获取模型的数据、接受用户编辑的数据后又提交给模型。

例如在QTableView组件上双击一个单元格编辑数据时,在单元格里就会出现一个QLineEdit组件,这个编辑框就是代理提供的临时编辑器。代理的主要任务就是为视图组件提供代理编辑器。

对于一些特殊的数据编辑需求,例如只允许输入整型数,使用一个QSpinBox作为代理组件更合适;

从列表中选择一个数据,使用一个QComboBox作为代理组件更好。这时就需要从QStyledItemDelegate继承创建自定义代理类。

Qt_Delegate 代理类的继承关系

Qt_Delegate 代理类的继承关系

代理委托作用

Delcgate(代理或委托)的作用如下

  • 绘制视图中来自模型的数据,委托会参考项目的角色和数据进行绘制,不同的伯e色和数据有不同的展示效果。
  • 在视图与模型之间交互操作时提供临时编辑组件的功能,该编辑器会位于视图的0顶层。

当默认委托(QStyledItemDelegate)提供的这两方面作用无法满足需求时就可以考虑自定义委托。

例如,如果要呈现更复杂的可视化,或者使用 QComBox 来编辑整数,默认的QLineEdit 无法满足需求,那么需要使用自定义委托。

本章提供两种委托方式:

  • 一种是结合自定义模型的自定义委托,另一种是适用子通用模型的泛型委托。

  • 这两种委托都需要重新实QStyledItemDelegate 的一些方法对于

    • 第1种委托,唯一必须重写的函数是 paint()。如果要支持可编辑,则必须重写函数 createEditor()、setEditorDate()和 setModelData()。

      如果在编辑过程中要使用 QLineEdit或QTextEdit,通常也会重写 commitAndCloseEditor()函数。可以根据需要重写 sizeHint()函数。

    • 对于第2种委托,只需要重写函数 createEditor()、setEditorDate()和 setModelData()即可,其他函数可以根据情况重写。这种委托相对简单,适用于多个模型,代码可以重复使用,是比较推荐的方式。

  • createEditor()创建用于编辑模型数据的widget组件,如一个QSpinBox或一个QComboBox组件
  • setEditorData()从模型获得数据,供widget组件进行编辑
  • setModelData()将widget上的数据更新到数据模型
  • updateEditorGeometry()用于给widget组件设置合适的大小

QStyledItemDelegate自定义代理

QStyledItemDelegate类为模型中的数据项提供了显示和编辑功能。

from PySide6.QtWidgets import QStyledItemDelegate

QStyledItemDelegate(parent: Union[PySide6.QtCore.QObject, NoneType] = None)-> None

当在Qt项目视图(例如QTableView)中显示来自模型的数据时,单个项目由代理绘制。此外,当编辑项目时,它会提供一个编辑器小部件,在进行编辑时,该小部件会放置在项目视图的顶部。QStyledItemDelegate是所有Qt项目视图的默认委托,并在创建时安装在它们上。

QStyledItemDelegate类是模型/视图类之一,也是Qt模型/视图框架的一部分。代理允许项目的显示和编辑独立于模型和视图进行开发。

模型中项目的数据被分配了一个ItemDataRole;每个项目可以为每个角色存储一个QVariant。QStyledItemDelegate实现了用户期望的最常见数据类型的显示和编辑,包括布尔值、整数和字符串。

根据他们在模型中所扮演的角色,数据将以不同的方式绘制。下表描述了代理可以为每个角色处理的角色和数据类型。通常,只要确保模型为每个角色返回适当的数据,以确定项目在视图中的外观就足够了。

RoleAccepted Types
BackgroundRoleQBrush
CheckStateRoleCheckState
DecorationRoleQIcon , QPixmap , QImage and QColor
DisplayRoleQString 和具有字符串表示形式的类型
EditRole有关详细信息,请参阅QItemEditorFactory
FontRoleQFont
SizeHintRoleQSize
TextAlignmentRoleAlignment
ForegroundRoleQBrush

编辑器是使用QItemEditorFactory创建的;QItemEditorFactory提供的默认静态实例安装在所有项目委托上。您可以使用setItemEditorFactory()设置自定义工厂,也可以使用setDefaultFactory(()设置新的默认工厂。被编辑的是存储在具有EditRole的项目模型中的数据。有关项目编辑器工厂的更高级介绍,请参阅QItemEditorFactory类。"颜色编辑器工厂"示例显示了如何使用工厂创建自定义编辑器。

子类

如果代理不支持绘制所需的数据类型,或者您想自定义项的绘制,则需要对QStyledItemDelegate进行子类化,并重新实现paint()和sizeHint()。paint()函数是为每个项目单独调用的,使用sizeHint()可以为每个项目指定提示。

当重新实现paint()时,通常会处理想要绘制的数据类型,并将超类实现用于其他类型。

复选框指示器的绘制由当前样式执行。该样式还指定了为不同的数据角色绘制数据的大小和边界矩形。项目本身的边界矩形也是由样式计算的。因此,当绘制已经支持的数据类型时,最好询问这些边界矩形的样式。QStyle类描述对此进行了更详细的描述。

如果您希望更改由复选框指示器的样式或绘制计算的任何边界矩形,可以对QStyle进行子类化。但是,请注意,重新实现sizeHint()也会影响项的大小。

自定义委托可以在不使用编辑器项工厂的情况下提供编辑器。在这种情况下,必须重新实现以下虚拟功能:

  • createEditor()返回用于更改模型中数据的小部件,并且可以重新实现以自定义编辑行为。
  • setEditorData()为小部件提供了要操作的数据。
  • updateEditorGeometry()确保编辑器相对于项目视图正确显示。
  • setModelData()将更新后的数据返回给模型。

Star Delegate示例通过重新实现这些方法来创建编辑器。

QStyledItemDelegate 与 QItemDelegate

自Qt 4.4以来,有两个委托类:QItemDelegate和QStyledItemDelegate。但是,默认的委托是QStyledItemDelegate。这两个类是绘制和为视图中的项目提供编辑器的独立替代方案。它们之间的区别在于QStyledItemDelegate使用当前样式来绘制其项目。因此,我们建议在实现自定义委托或使用Qt样式表时使用QStyledItemDelegate作为基类。除非自定义代理需要使用绘图样式,否则任何一个类所需的代码都应该相等。

如果希望自定义项目视图的绘制,则应实现自定义样式。有关详细信息,请参阅QStyle类文档。

方法描述
QStyledItemDelegate([parent=None])构造具有给定父级的项委托。
displayText(value, locale:PySide6.QtCore.QLocale)->str此函数返回委托将用于在区域设置中显示模型的DisplayRole的字符串。value是模型提供的DisplayRole的值。
默认实现使用toString将值转换为QString。
对于空模型索引,即模型返回无效QVariant的索引,不调用此函数。
initStyleOption(option:PySide6.QtWidgets.QStyleOptionViewItem, index:PySide6.QtCore.QModelIndex)使用索引索引的值初始化选项。当子类需要QStyleOptionViewItem,但又不想自己填写所有信息时,此方法对它们很有用。
itemEditorFactory()->PySide6.QtWidgets.QItemEditorFactory返回项委托使用的编辑器工厂。如果没有设置编辑器工厂,则函数将返回null。
setItemEditorFactory(factory:PySide6.QtWidgets.QItemEditorFactory)将项代理要使用的编辑器工厂设置为指定的工厂。如果未设置编辑器工厂,则项代理将使用默认的编辑器工厂。

QItemDelegate代理

QItemDelegate类为模型中的数据项提供了显示和编辑功能。

from PySide6.QtWidgets import QItemDelegate

QItemDelegate(parent: Union[PySide6.QtCore.QObject, NoneType] = None)-> None
  • QItemDelegate.clipping: bool

    如果代理应该剪裁绘制事件,则此属性保持不变。
    此属性将绘制片段设置为项目的大小。默认值为on。这在图像大于项目大小等情况下很有用。

QItemDelegate代理官方描述

QItemDelegate可用于为基于QAbstractItemView子类的项目视图提供自定义显示功能和编辑器小部件。为此目的使用委托可以独立于模型和视图自定义和开发显示和编辑机制。

QItemDelegate类是模型/视图类之一,也是Qt模型/视图框架的一部分。请注意,QStyledItemDelegate已经接管了绘制Qt的项目视图的工作。我们建议在创建新委托时使用QStyledItemDelegate。

在标准视图中显示自定义模型中的项目时,通常只需确保模型为决定项目在视图中外观的每个角色返回适当的数据就足够了。Qt的标准视图使用的默认委托使用此角色信息以用户期望的大多数常见形式显示项目。但是,有时有必要对项的外观进行比默认委托所能提供的更多的控制。

此类提供用于在视图中绘制项目数据和编辑项目模型中的数据的函数的默认实现。提供了在QAbstractItemDelegate中定义的paint()和sizeHint()虚拟函数的默认实现,以确保委托实现视图所期望的正确基本行为。您可以在子类中重新实现这些函数,以自定义项的外观。

在项目视图中编辑数据时,QItemDelegate提供了一个编辑器小部件,这是一个在进行编辑时放置在视图顶部的小部件。编辑器是使用QItemEditorFactory创建的;QItemEditorFactory提供的默认静态实例安装在所有项目委托上。您可以使用setItemEditorFactory()设置自定义工厂,也可以使用setDefaultFactory(()设置新的默认工厂。被编辑的是存储在具有EditRole的项目模型中的数据。

此处仅重新实现基于小部件的委托的标准编辑功能:

  • createEditor()返回用于更改模型中数据的小部件,并且可以重新实现以自定义编辑行为。
  • setEditorData()为小部件提供了要操作的数据。
  • updateEditorGeometry()确保编辑器相对于项目视图正确显示。
  • setModelData()将更新后的数据返回给模型。

closeEditor()信号表示用户已经完成了对数据的编辑,并且编辑器小部件可以被销毁。

标准角色和数据类型

Qt提供的标准视图使用的默认委托将每个标准角色(由ItemDataRole定义)与某些数据类型相关联。返回这些类型数据的模型可能会影响代理的外观,如下表所述。

RoleAccepted Types
BackgroundRoleQBrush
CheckStateRoleCheckState
DecorationRoleQIcon , QPixmap and QColor
DisplayRoleQString 和具有字符串表示形式的类型
EditRole有关详细信息,请参阅QItemEditorFactory
FontRoleQFont
SizeHintRoleQSize
TextAlignmentRoleAlignment
ForegroundRoleQBrush

如果默认委托不允许您出于显示目的或编辑数据所需的自定义级别,则可以将QItemDelegate子类化以实现所需的行为。

子类

当对QItemDelegate进行子类化以创建使用自定义渲染器显示项目的委托时,重要的是确保该委托能够针对所有所需状态适当地渲染项目;例如,选中、禁用、选中。paint()函数的文档中包含一些提示,说明如何实现这一点。
您可以使用QItemEditorFactory提供自定义编辑器。"颜色编辑器工厂示例"显示了如何使自定义编辑器可用于具有默认项目编辑器工厂的代理。这样,就不需要对QItemDelegate进行子类化。另一种选择是重新实现createEditor()、setEditorData()、setModelData()和updateEditorGeometry()。此过程在"旋转框代理示例"中进行了描述。

QStyledItemDelegate vs. QItemDelegate

自Qt 4.4以来,有两个委托类:QItemDelegate和QStyledItemDelegate。但是,默认的委托是QStyledItemDelegate。这两个类是绘制和为视图中的项目提供编辑器的独立替代方案。它们之间的区别在于QStyledItemDelegate使用当前样式来绘制其项目。因此,我们建议在实现自定义委托或使用Qt样式表时使用QStyledItemDelegate作为基类。除非自定义代理需要使用绘图样式,否则任何一个类所需的代码都应该相等。

方法描述
decoration(option: PySide6.QtWidgets.QStyleOptionViewItem, variant)->QPixmap
doCheck(option, bounding:QRect, variant)->QRect
drawBackground(painter:QPainter, option:QStyleOptionViewItem, index:.QModelIndex)使用给定的painter和style选项渲染给定索引的项目背景。
hasClipping()->bool属性剪辑的Getter。
itemEditorFactory()->PySide6.QtWidgets.QItemEditorFactory返回项委托使用的编辑器工厂。如果没有设置编辑器工厂,则函数将返回null。
rect(option:PySide6.QtWidgets.QStyleOptionViewItem, index:PySide6.QtCore.QModelIndex, role:int)->QRect
setClipping(clip:bool)属性剪辑的设置程序。
setItemEditorFactory(factory:PySide6.QtWidgets.QItemEditorFactory)将项代理要使用的编辑器工厂设置为指定的工厂。如果未设置编辑器工厂,则项代理将使用默认的编辑器工厂。
setOptions(index:PySide6.QtCore.QModelIndex, option:PySide6.QtWidgets.QStyleOptionViewItem)->PySide6.QtWidgets.QStyleOptionViewItem
textRectangle(painter:Painter, rect:QRect, font:QFont, text:str)->QRect
drawCheck(painter:QPainter, option:QStyleOptionViewItem, rect:QRect, state:PySide6.QtCore.Qt.CheckState)使用给定的painter和style选项,使用给定的状态,在rect指定的矩形内渲染检查指示器。
drawDecoration(painter:QPainter, option:QStyleOptionViewItem, rect:QRect, pixmap:QPixmap)使用给定的painter和style选项在rect指定的矩形内渲染装饰像素图。
drawDisplay(painter:QPainter, option:QStyleOptionViewItem, rect:QRect, pixmap:QPixmap)使用给定的painter和style选项在rect指定的矩形内渲染项目视图文本。
drawFocus(painter:QPainter, option:QStyleOptionViewItem, rect:QRect)使用给定的painter和style选项,渲染rect指定的矩形内的区域,表明该区域具有焦点。
[static]selectedPixmap(pixmap:QPixmap, palette:QPalette, enabled:bool)->QPixmap
  • PySide6.QtCore.Qt.CheckState

    此枚举描述可检查项、控件和小部件的状态。

    ConstantDescription
    Qt.Unchecked该项目未选中。
    Qt.PartiallyChecked项目已部分检查。如果检查了层次模型中的项目的部分子项(而不是全部子项),则可以对其进行部分检查。
    Qt.Checked项目已选中。
方法介绍

在视图控件中双击某个数据项,可以修改数据项当前显示的值,即可以输入新的值。

  • 输人新值时,并不是直接在视图控件上输入(视图控件只具有显示数据的功能),而是在视图控件的单元格位置出现一个新的可以输入数据的控件,
  • 例如 QLieEdit。QLineEdit 读取数据项的值作为初始值,供用户修改,修改完成后通过数据项的索引把数据保存到数据模型中,并通知视图控件显示新的数据,像这种为视图控件提供编辑功能的控件称为代理控件或委托控件。

系统为每种数据类型定义了默认的代理控件,用户也可以自定义代理控件。

  • 例如某个数据项存储性别值该数据项只有"男"和"女"两个选择可以用QComboBox作为代理控件双击该数据项,弹出QComboBox 控件,从QComboBox 的列表中选择"男"或"女";

  • 再如对于存储成绩的数据项,用QDoubleSpinBox作为代理控件,设置其可以输人1位小数定义代理控件需要用QStyledItemDelegate类或 QItemDelegate类创建子类,这两个类都继承自QAbstractItemDelegate类。

  • 这两个类的主要区别是前者可以使用当前的样式表来设置代理控件的样式因此建议使用前者来定义代理控件。

  • 在QStyledItemDelegate或QItemDelegate 的子类中定义代理控件的类型位置,以及如何读取和返回数据。

  • 视图控件都有从 QAbstractItemView 继承而来的:

    • setItemDelegate(delegate: QAbstractltemDelegate)方法为所有的数据项设置代理控件
    • setItemDelegateForColumn(column; int,delegate: QAbstractItemDelegate)方法为列数据项设置代理控件
    • setItemDelegateForRow(row;int,delegate:QAbstractItemDelegate)方法为行数据项设置代理控件
  • 创建代理控件可以用项编辑器工厂QItemEditorFactory定义默认的代理控件,也可以自定义代理控件的类型本书讲解自定义代理控件。自定义代理控件需要重写 QStyledItemDelegate 类或 QItemDelegate 类的下面4个函数:

    • createEditor(parent: QWidget,option: QStyleOptionViewItem,index: QModelIndex)->QWidget函数用于创建代理控件的实例对象并返回该实例对象
    • setEditorData(editor:QWidget,index:QModelIndex)-> None 函数,用于读取视图控件的数据项的值到代理控件中。
    • setModelData(editor: QWidget,model: QAbstractItemModel,index:QModelIndex)->None函数,用于将编辑后的代理控件的值返回到数据模型中
    • updateEditorGeometry(editor: QWidget,option: QStyleOptionViewItem,index:QModelIndex)->None 函数,用于设置代理控件显示的位置。
    • createEditor)函数中的参数 parent 指代理控件所在的窗口,通常取视图控件所在的窗体;
    • 其他3个函数的editor 指 createEditor()返回的代理控件,用于传递数据;
    • QModelIndex是数据项的索引,系统会给实参传递索引;
    • QStyleOptionViewItem 传递的一些属性用于确定代理控件的位置和外观,其属性如表所示。其中枚举值QStyleOptionViewltem.Position 可取:
      • QStyleOptionViewItem.Left
      • QStyleOptionViewItem.Right
      • QStyleOptionViewltem.op
      • QStyleOptionViewItem.Bottom;
    • 枚举值 QStyleOptionViewItem.ViewItemFeatures 可取:
      • QStyleOptionViewltem None
      • QStyleOptionViewItem.WrapText
      • QStyleOptionViewltem.Alternate
      • QStyleOptionViewItem.HasCheckIndicator
      • OStyleOptionViewltem.HasDisplay
      • QStyleOptionViewItem.HasDecoration;
    • 枚举值 QStyleOptionViewItem.ViewItemPosition 可取:
      • QStyleOptionViewItem.Beginning
      • QStyleOptionViewItem.Middle
      • QStyleOptionViewItem.End
      • QStyleOptionViewItem.OnlyOne(行中只有一个项.两端对齐)。
QStyleOptionViewltem的属性属性值的类型说明
backgroundBrushQBrush项的背景画刷
checkStateQt.CheckState项的勾选状态
decorationAlignmentQt.Alignment项的图标对齐位置
decorationPositionQStyleOptionViewItem.Position项的图标位置
decorationSizeQSize项的图标尺寸
displayAlignmentQt.Alignment项的文字对齐位置
featuresQStyleOptionViewItem.ViewItemFeatures项所具有的特征
fontQFont项的字体
iconQIcon项图标
indexQModelIndex项的模型索引
showDecorationSelectedbool项是否显示图标
textQString项显示的文本
textElideModeQt.TextElideMode省略号的模式
viewItemPositionQStyleOptionViewltem.ViewItemPosition项在行中的位置
directionQt.LayoutDirection布局方向
paletteQPalette调色板
TectQRect项的矩形区域
styleObjectQObject项的窗口类型
versionint版本

QTableView 控件结合自定义委托实例

image-20230327001025895

一是创建自定义委托类 StudentTableDelegate,这个类只适用于自定义模型 StudentTableModel,不适合用于其他模型。在这个类中,重写了常用的函数 paint()、createEditor()、setEditorDate()和 setModelData(),以及函数 commitAndCloseEditor()和sizeHint()。

二是创建日期和整数两列的自定义委托,这两种委托也可以用于其他模型,方便重复。这也是第 2 种委托,即泛型委托,是比较推荐的一种方式。笔者追求的效果比较简单,每种委托只重写了 createEditor(、setEditorData0和 setModelData()这 3个函数

三是程序启动主体,本案例创建了 tableView 和 tableView2 两个表格,前者对应第1种委托,后者对应第2种委托,方便对照。在init model2 中对 tableView2的不同列设置了不同的数据,从而与自定义委托进行匹配

tableView,"科目"和"姓名"使用 OLineEdit 委托,"分数"使用QSpinBox 委托,"说明"使用 QTextEdit 委托,用来显示 HTML。中间的几个按钮的行为也都基于 tableView。
对于下面的 tableView2,前两列使用默认委托(QLineEdit),第3 列使用 QSpinBox委托,第4列使用 QDateEdit 委托。QSpinBox 委托和QDateEdit 委托也可以结合其他模型使用。

# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/27 0:07
# File_name: 01-QTableView 控件结合自定义委托实例.py


from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import *
import re
import sys
import datetime

from qt_QTableModel import StudentTableModel
import os

os.chdir(os.path.dirname(__file__))

SUBJECT,NAME,SCORE,DESCRIPTION = range(4)


class StudentTableDelegate(QStyledItemDelegate):

    def __init__(self,parent=None):
        super(StudentTableDelegate,self).__init__(parent)

    def paint(self,painter,option,index):
        if index.column()== DESCRIPTION:
            text = index.model().data(index)
            if text[-2:] == '优秀':
                text = f'{text[:-2]}<font color=red><b>优秀</b></font>'
                index.model().setData(index,value=text)
            elif text[-2:] == '良好':
                text = f'{text[:-2]}<font color=green><b>良好</b></font>'
                index.model().setData(index,value=text)
            palette = QApplication.palette()
            document = QTextDocument()
            document.setDefaultFont(option.font)
            if option.state & QStyle.State_Selected:
                document.setHtml("<font color={}>{}</font>".format(
                    palette.highlightedText().color().name(),text))
            else:
                document.setHtml(text)
            color =(palette.highlight().color()
                     if option.state & QStyle.State_Selected
                     else QColor(index.model().data(index,Qt.BackgroundRole)))
            painter.save()
            painter.fillRect(option.rect,color)
            painter.translate(option.rect.x(),option.rect.y())
            document.drawContents(painter)
            painter.restore()
        else:
            QStyledItemDelegate.paint(self,painter,option,index)

    def sizeHint(self,option,index):
        fm = option.fontMetrics
        if index.column()== SCORE:
            return QSize(fm.averageCharWidth(),fm.height())
        if index.column()== DESCRIPTION:
            text = index.model().data(index)
            document = QTextDocument()
            document.setDefaultFont(option.font)
            document.setHtml(text)
            return QSize(document.idealWidth()+ 5,fm.height())
        return QStyledItemDelegate.sizeHint(self,option,index)

    def createEditor(self,parent,option,index):
        if index.column()== SCORE:
            spinbox = QSpinBox(parent)
            spinbox.setRange(0,100)
            spinbox.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
            return spinbox
        elif index.column()in(NAME,SUBJECT):
            editor = QLineEdit(parent)
            # self.connect(editor,SIGNAL("returnPressed()"),self.commitAndCloseEditor)
            return editor
        elif index.column()== DESCRIPTION:
            editor = QTextEdit()
            # self.connect(editor,SIGNAL("returnPressed()"),self.commitAndCloseEditor)
            return editor
        else:
            return QStyledItemDelegate.createEditor(self,parent,option,index)

    def commitAndCloseEditor(self):
        editor = self.sender()
        if isinstance(editor,(QTextEdit,QLineEdit)):
            self.emit(SIGNAL("commitData(QWidget*)"),editor)
            self.emit(SIGNAL("closeEditor(QWidget*)"),editor)

    def setEditorData(self,editor,index):
        text = index.model().data(index,Qt.DisplayRole)
        if index.column()== SCORE:
            try:
                value = int(float(text)+ 0.5)
            except:
                value = 0
            editor.setValue(value)
        elif index.column()in(NAME,SUBJECT):
            editor.setText(text)
        elif index.column()== DESCRIPTION:
            editor.setHtml(text)
        else:
            QStyledItemDelegate.setEditorData(self,editor,index)

    def setModelData(self,editor,model,index):
        if index.column()== SCORE:
            model.setData(index,editor.value())
        elif index.column()in(NAME,SUBJECT):
            model.setData(index,editor.text())
        elif index.column()== DESCRIPTION:
            model.setData(index,editor.toHtml())
        else:
            QStyledItemDelegate.setModelData(self,editor,model,index)


class DateColumnDelegate(QStyledItemDelegate):
    def __init__(self,minimum=QDate(),maximum=QDate.currentDate(),format="yyyy-MM-dd",parent=None):
        super(DateColumnDelegate,self).__init__(parent)
        self.minimum = minimum
        self.maximum = maximum
        self.format = format

    def createEditor(self,parent,option,index):
        dateedit = QDateEdit(parent)
        dateedit.setDateRange(self.minimum,self.maximum)
        dateedit.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        dateedit.setDisplayFormat(self.format)
        dateedit.setCalendarPopup(True)
        return dateedit

    def setEditorData(self,editor,index):
        value = index.model().data(index,Qt.DisplayRole)
        try:
            date = datetime.datetime.strptime(value,'%Y-%m-%d').date()
            editor.setDate(QDate(date.year,date.month,date.day))
        except:
            print(value,index)
            editor.setDate(QDate())

    def setModelData(self,editor,model,index):
        model.setData(index,editor.date().toString('yyyy-MM-dd'))


class IntegerColumnDelegate(QStyledItemDelegate):

    def __init__(self,minimum=0,maximum=100,parent=None):
        super(IntegerColumnDelegate,self).__init__(parent)
        self.minimum = minimum
        self.maximum = maximum

    def createEditor(self,parent,option,index):
        spinbox = QSpinBox(parent)
        spinbox.setRange(self.minimum,self.maximum)
        spinbox.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        return spinbox

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

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


class QTableViewDemo(QMainWindow):

    def __init__(self,parent=None):
        super(QTableViewDemo,self).__init__(parent)
        self.setWindowTitle("QTableDelegate案例")
        self.resize(550,600)

        # 方式1:基于自定义模型的自定义委托
        self.tableView = QTableView()
        self.model = StudentTableModel()
        self.delegate = StudentTableDelegate()
        self.model.initData()

        self.tableView.setModel(self.model)
        self.selectModel = QItemSelectionModel()
        self.tableView.setSelectionModel(self.selectModel)
        self.tableView.setItemDelegate(self.delegate)
        self.tableView.horizontalHeader().setStretchLastSection(True)

        # 方式2:通用模型的通用委托
        self.tableView2 = QTableView()
        self.model2 = QStandardItemModel(5,4)
        self.init_model2()
        self.tableView2.setModel(self.model2)
        self.delegate2 = IntegerColumnDelegate()
        self.tableView2.setItemDelegateForColumn(2,self.delegate2)
        self.tableView2.setItemDelegateForColumn(3,DateColumnDelegate())

        self.buttonAddRow = QPushButton('增加行')
        self.buttonInsertRow = QPushButton('插入行')
        self.buttonDeleteRow = QPushButton('删除行')
        self.buttonAddRow.clicked.connect(self.onAdd)
        self.buttonInsertRow.clicked.connect(self.onInsert)
        self.buttonDeleteRow.clicked.connect(self.onDelete)

        self.model.setData(self.model.index(3,1),'Python',role=Qt.EditRole)

        layout = QVBoxLayout(self)
        layout.addWidget(self.tableView)
        layoutH = QHBoxLayout()
        layoutH.addWidget(self.buttonAddRow)
        layoutH.addWidget(self.buttonInsertRow)
        layoutH.addWidget(self.buttonDeleteRow)
        layout.addLayout(layoutH)
        layout.addWidget(self.tableView2)

        widget = QWidget()
        self.setCentralWidget(widget)
        widget.setLayout(layout)

    def init_model2(self):
        for row in range(self.model2.rowCount()):
            for column in range(self.model2.columnCount()):
                if column == 2:
                    value = column + row
                elif column == 3:
                    date = datetime.datetime.strptime('2022-01-01','%Y-%m-%d')+ datetime.timedelta(days=column * row)
                    value = datetime.datetime.strftime(date,'%Y-%m-%d')
                else:
                    value ="row %s,col %s"%(row,column)
                item = QStandardItem(str(value))
                self.model2.setItem(row,column,item)

    def onAdd(self):
        rowCount = self.model.rowCount()
        self.model.insertRow(rowCount)

    def onInsert(self):
        index = self.tableView.currentIndex()
        row = index.row()
        self.model.insertRow(row)

    def onDelete(self):
        index = self.tableView.currentIndex()
        row = index.row()
        self.model.removeRow(row)


if __name__ =="__main__":
    app = QApplication(sys.argv)
    demo = QTableViewDemo()
    demo.show()
    sys.exit(app.exec())

QConcatenateTablesProxyModel代理链接

QConcateateTablesProxyModel类代理多个源模型,将它们的行连接起来。

QConcatenateTablesProxyModel采用多个源模型并连接它们的行。

换句话说,代理将具有第一个源模型的所有行,然后是第二个源模型,依此类推。

如果源模型的列数不相同,则代理的列数将仅与列数最小的源模型的数相同。其他源模型中的其他列将被简单地忽略。

可以在运行时添加和删除源模型,并相应地调整列数。

此代理不继承自QAbstractProxyModel,因为它使用多个源模型,而不是单个源模型。

仅支持平面模型(列表和表),不支持树模型。

from PySide6.QtCore import QConcatenateTablesProxyModel

QConcatenateTQConcatenateTablesProxyModel(parent: Union[PySide6.QtCore.QObject, NoneType] = None)-> NoneablesProxyModel

构造具有给定父级的连接行代理模型。

方法描述
addSourceModel(self, sourceModel: PySide6.QtCore.QAbstractItemModel)-> None在以前添加的所有源模型下方添加源模型sourceModel。
sourceModel的所有权不受此影响。
同一源模型不能添加多次。
mapFromSource(self, sourceIndex: Union[PySide6.QtCore.QModelIndex, PySide6.QtCore.QPersistentModelIndex])-> PySide6.QtCore.QModelIndex返回给定sourceIndex的代理索引,该索引可以来自任何源模型。
mapToSource(self, proxyIndex: Union[PySide6.QtCore.QModelIndex, PySide6.QtCore.QPersistentModelIndex])-> PySide6.QtCore.QModelIndex返回给定proxyIndex的源索引。
removeSourceModel(self, sourceModel: PySide6.QtCore.QAbstractItemModel)-> None删除以前添加到此代理的源模型sourceModel。
sourceModel的所有权不受此影响。
sourceModels(self)-> List[PySide6.QtCore.QAbstractItemModel]返回已添加为此代理模型的源模型的模型列表。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在 PyQt5 中,Model-View 架构是一种用于处理数据和界面分离的设计模式。它将应用程序的数据(Model)和用户界面(View)分离开来,通过中间层的数据模型(Model)来进行数据交互和管理。 Model-View 架构的核心思想是让数据模型(Model)负责管理和维护数据,而用户界面(View)则负责展示数据和与用户进行交互。通过将数据和界面分离,可以实现数据的独立处理和界面的灵活展示。 下面是 Model-View 架构的主要组成部分: 1. Model(模型): - Model 是数据的核心,负责管理和维护数据的状态和结构。 - Model 提供了一组接口和方法,用于对数据的操作,例如增加、删除、修改等。 - Model 可以是自定义的数据结构,也可以是 Qt 提供的内置模型类,例如 QStandardItemModel、QAbstractTableModel 等。 2. View(视图): - View 是用户界面,负责展示数据和与用户进行交互。 - View 可以是窗口、部件、表格等控件,用于展示和编辑数据。 - View 通过读取 Model 中的数据来显示,并将用户的操作反馈给 Model 进行处理。 3. Delegate(委托): - Delegate 是可选的组件,用于自定义 View 中的渲染和编辑行为。 - Delegate 可以定制数据的展示方式,例如使用自定义的渲染器来显示特定类型的数据。 - Delegate 还可以处理用户的编辑操作,并将编辑后的数据传递给 Model 进行更新。 4. Controller(控制器): - 在 Model-View 架构中,Controller 并不是必需的组件。 - Controller 负责协调 ModelView 之间的交互,处理一些额外的逻辑。 - 例如,Controller 可以处理用户的操作或事件,并根据需要更新 ModelViewModel-View 架构的优势在于它将数据和界面分离,使得数据的处理和界面的呈现可以独立进行。这样可以提高代码的可维护性和可扩展性。同时,Model-View 架构也提供了一些内置的模型类和视图类,使得开发者可以更方便地使用和管理数据。 在 PyQt5 中,可以使用内置的模型类(如 QStandardItemModel、QAbstractTableModel)和视图类(如 QListViewQTableView)来实现 Model-View 架构。通过将数据存储在模型中,并将模型与视图进行绑定,可以实现数据的展示、编辑和更新。 总结:Model-View 架构是 PyQt5 中一种用于处理数据和界面分离的设计模式。通过将数据和界面分离,Model-View 架构提供了一种灵活和可扩展的方式来处理和展示数据。在 PyQt5 中,可以使用内置的模型类和视图类来实现 Model-View 架构。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

士别三日,当挖目相待

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值