自定义模型QAbstractTableModel
一般来说,通过上面介绍的一些标准模型(如QStringListModel、QFileSystemModel和QStandardItemModel等)结合 QListView、QTableView 和QTreeView 就够用了。
但有时需要更复杂的模型,这就需要用户自己定义。可以通过子类化QAbstractTableModel实现自定义模型,这需要注意以下几点:
- 必须实现函数rowCount()columnCount()和data()除非是子类化QAbstractTableModel否则函数index()和parent()也要重新实现。
- 这些功能用于只读模型,并且构成可编辑模型的基础。需要注意的是,在实现 data0函数时不需要支持 Qt.ItemDataRole 中的所有角色可以选择一些常用角色返回即可。
- 大多数模型至少为 QtDisplayRole 提供文本支持,有些模型会使用QtToolTipRole 和 QtWhatsThisRole 提供更多的信息。
- 其次,要在模型中启用编辑,还必须实现 setData()函数,并重新实现 flags()函数确保返回 ItemIsEditable。
- 还可以重新实现函数 headerData()和 setHeaderData()来控制模型标题的呈现方式。
- 在分别重新实现函数 setData()和 setHeaderData()时,必须显式发射dataChanged 信号和 headerDataChanged 信号。
- 最后,如果模型结构可以调整,则可以重新实现函数 insertRows()、removeRows0insertColumns()和 removeColumns()。
- 在实现这些函数时,重要的是要把模型结构上的变化通知连接模型的视图。
- 实现 insertRows()函数需要在新行插入数据结构之前先调用 beginnsertRows()函数,然后立即调用endInsertRows()函数。
- 实现 isertColumns()函数需要在新列插入数据结构之前先调用 beginInsertColumns()函数,然后立即调用endInsertColumns()函数。
- 实现removeRows()函数需要在从数据结构中删除行之前先调用 beginRemoveRows()函数,然后立即调用endRemoveRows()函数。
- 实现 removeColumns()函数需要在从数据结构中删除列之前先调用 beginRemoveColumns()函数,然后立即调用endRemoveColumns()函数。
- 在实现这些函数时,重要的是要把模型结构上的变化通知连接模型的视图。
如果数据比较大,则可以考虑创建增量填充的模型,重新实现函数 fetchMore()和canFetchMore()。如果通过 fetchMore()函数将行加到模型中,则必须调用函数beginInsertRows()和endInsertRows()。
一是创建数据结构 Student,主要包括科目、姓名、分数这3个属性
二是自定义模型,该模型使用 QAbstractTableModel作为基础模板,重新实现了最基础的函数 data()、headerData()、rowCount()和 columnCount(),支持编辑的函数 flags()和setData(),以及结构调整的函数 insertRows()和removeRows()。在 data()函数中可以根据列的名称和角色设置不同的颜色
三是将 QTableView 和自定义模型结合,这里仅仅实现了增/删行的功能,只提供了setData0函数用于编辑模型,并且只支持 EditRole 一个角色
# -*- coding: UTF-8 -*-
# File date: Hi_2023/3/26 23:55
# File_name: 01-QTableView 控件结合自定义模型.py
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import *
import sys
import random
import os
os.chdir(os.path.dirname(__file__))
SUBJECT,NAME,SCORE,DESCRIPTION = range(4)
class Student(object):
def __init__(self,subject,name,score=0,description=""):
self.subject = subject
self.name = name
self.score = score
self.description = description
def __hash__(self):
return super(Student,self).__hash__()
def __lt__(self,Other):
if self.name < Other.name:
return True
if self.subject < Other.subject:
return True
return id(self)< id(Other)
def __eq__(self,Other):
if self.name == Other.name:
return True
if self.subject == Other.subject:
return True
return id(self)== id(Other)
class StudentTableModel(QAbstractTableModel):
def __init__(self,filename=""):
super(StudentTableModel,self).__init__()
self.students = []
def initData(self):
for subject in ['语文','数学','外语','综合']:
for name in ['张三','李四','王五','赵六']:
score = random.random()* 40 + 60
if score >= 80:
_str = f'{name}的{subject}成绩是:优秀'
else:
_str = f'{name}的{subject}成绩是:良好'
student = Student(subject,name,score,_str)
self.students.append(student)
self.sortBySubject()
def sortByName(self):
self.students = sorted(self.students,key=lambda x:(x.name,x.subject))
self.endResetModel()
def sortBySubject(self):
self.students = sorted(self.students,key=lambda x:(x.subject,x.name))
self.endResetModel()
def flags(self,index):
if not index.isValid():
return Qt.ItemIsEnabled
return Qt.ItemFlags(QAbstractTableModel.flags(self,index)| Qt.ItemIsEditable)
def data(self,index,role=Qt.DisplayRole):
if not index.isValid()or not(0 <= index.row()< len(self.students)):
return None
student = self.students[index.row()]
column = index.column()
if role == Qt.DisplayRole:
if column == SUBJECT:
return student.subject
elif column == NAME:
return student.name
elif column == DESCRIPTION:
return student.description
elif column == SCORE:
return"{:.2f}".format(student.score)
elif role == Qt.TextAlignmentRole:
if column == SCORE:
return int(Qt.AlignRight | Qt.AlignVCenter)
return int(Qt.AlignLeft | Qt.AlignVCenter)
elif role == Qt.ForegroundRole and column == SCORE:
if student.score < 80:
return QColor(Qt.black)
elif student.score < 90:
return QColor(Qt.darkGreen)
elif student.score < 100:
return QColor(Qt.red)
elif role == Qt.BackgroundRole:
if student.subject in("数学","语文"):
return QColor(250,230,250)
elif student.subject in("外语",):
return QColor(250,250,230)
elif student.subject in("综合"):
return QColor(230,250,250)
else:
return QColor(210,230,230)
return None
def headerData(self,section,orientation,role=Qt.DisplayRole):
if role == Qt.TextAlignmentRole:
if orientation == Qt.Horizontal:
return int(Qt.AlignLeft | Qt.AlignVCenter)
return int(Qt.AlignRight | Qt.AlignVCenter)
if role != Qt.DisplayRole:
return None
if orientation == Qt.Horizontal:
if section == SUBJECT:
return"科目"
elif section == NAME:
return"姓名"
elif section == SCORE:
return"分数"
elif section == DESCRIPTION:
return"说明"
return int(section + 1)
def rowCount(self,index=QModelIndex()):
return len(self.students)
def columnCount(self,index=QModelIndex()):
return 4
def setData(self,index,value,role=Qt.EditRole):
if index.isValid()and 0 <= index.row()< len(self.students)and role == Qt.EditRole:
student = self.students[index.row()]
column = index.column()
if column == SUBJECT:
student.subject = value
elif column == NAME:
student.name = value
elif column == DESCRIPTION:
student.description = value
elif column == SCORE:
try:
student.score = int(value)
except:
print('输入错误,请输入数字')
self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"),index,index)
return True
return False
def insertRows(self,position,rows=1,index=QModelIndex()):
self.beginInsertRows(QModelIndex(),position,position + rows - 1)
for row in range(rows):
self.students.insert(position + row,Student("test","test",0,''))
self.endInsertRows()
return True
def removeRows(self,position,rows=1,index=QModelIndex()):
self.beginRemoveRows(QModelIndex(),position,position + rows - 1)
self.students =(self.students[:position] + self.students[position + rows:])
self.endRemoveRows()
return True
class QTableViewDemo(QMainWindow):
def __init__(self,parent=None):
super(QTableViewDemo,self).__init__(parent)
self.setWindowTitle("QTableModel案例")
self.resize(500,600)
self.tableView = QTableView()
self.model = StudentTableModel()
self.model.initData()
self.tableView.setModel(self.model)
self.selectModel = QItemSelectionModel()
self.tableView.setSelectionModel(self.selectModel)
self.tableView.horizontalHeader().setStretchLastSection(True)
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)
widget = QWidget()
self.setCentralWidget(widget)
widget.setLayout(layout)
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())