模型视图机制
优势:相较于基于widget
的模型,model/view
的在最大好处在于共享data
,一份data,可以被多个view
使用,实现信息同步更新;而如果想利用widget
做到这点,就必需手动去设置复杂的信号槽关系。
劣势:相关的概念较多,上手比较困难。
自定义listmodel
- 继承
QAbstractListModel
- 结构:
rowCount()
- 显示:
data()
- 编辑:
flags()、setData()
- 增减数据:
insertRows()、removeRows()
# 自定义自己的listmodel
import typing
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QListView, QComboBox
class MyListModel(QAbstractListModel):
def __init__(self, names=[], parent=None):
super(MyListModel, self).__init__(parent)
self.__name = names
self.__icon = QIcon('0.png')
def rowCount(self, parent) -> int:
return len(self.__name)
def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
row = index.row()
if role == Qt.DisplayRole:
return self.__name[row]
if role == Qt.EditRole:
return self.__name[row]
if role == Qt.DecorationRole:
return self.__icon
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...) -> typing.Any:
return "未定义"
def setData(self, index: QModelIndex, value: typing.Any, role: int = ...) -> bool:
row = index.row()
if role == Qt.EditRole:
self.__name[row] = value
self.dataChanged.emit(index, index) # 注意这里需要发出一个通知
return True
return False
def insertRows(self, row: int, count: int, parent=QModelIndex()) -> bool:
self.beginInsertRows(QModelIndex(), row, row + count - 1)
for i in range(count):
self.__name.insert(row, '默认')
self.endInsertRows()
def removeRows(self, row: int, count: int, parent=QModelIndex()) -> bool:
self.beginRemoveRows(QModelIndex(), row, row + count - 1)
for i in range(count):
self.__name.pop(row)
self.endRemoveRows()
if __name__ == '__main__':
app = QApplication([])
model = MyListModel(['小红', '小小', '星星', '大哥', '憨憨'])
w = QListView()
w.setModel(model)
w.show()
box = QComboBox()
box.setModel(model)
box.show()
model.insertRows(0, 2)
model.removeRows(0, 2)
app.exec_()
自定义tableModel
- 继承
QAbstractTableModel
- 结构:
rowCount()、colCount()
- 显示:
data()、headerData()
- 编辑:
flags()、setData()
- 增减数据:
insertRows()、removeRows()、insertColumns()、removeColums()
# myTableModel
import typing
from PyQt5.QtCore import Qt, QAbstractTableModel, QModelIndex
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QListView, QComboBox, QTableView
class MyTableModel(QAbstractTableModel):
def __init__(self, data=[[]], parent=None):
super(MyTableModel, self).__init__(parent)
self.__data = data
self.__headers = ['名字', '年龄']
def rowCount(self, parent) -> int:
return len(self.__data)
def columnCount(self, parent: QModelIndex = ...) -> int:
return len(self.__data[0])
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
row, col = index.row(), index.column()
if role == Qt.DisplayRole:
return self.__data[row][col]
if role == Qt.EditRole:
return self.__data[row][col]
def headerData(self, section: int, orientation: Qt.Orientation, role: int = ...):
if orientation == Qt.Horizontal:
if role == Qt.DisplayRole:
if section < len(self.__headers):
return self.__headers[section]
else:
return 'undefined'
def setData(self, index: QModelIndex, value: typing.Any, role: int = ...) -> bool:
row, col = index.row(), index.column()
if role == Qt.EditRole:
self.__data[row][col] = value
self.dataChanged.emit(index, index) # 注意这里需要发出一个通知
return True
return False
def insertRows(self, row: int, count: int, parent=QModelIndex()) -> bool:
self.beginInsertRows(QModelIndex(), row, row + count - 1)
for i in range(count):
self.__data.insert(row, ['' for i in range(self.columnCount())])
self.endInsertRows()
return True
def removeRows(self, row: int, count: int, parent=QModelIndex()) -> bool:
self.beginRemoveRows(QModelIndex(), row, row + count - 1)
for i in range(count):
self.__data.pop(row)
self.endRemoveRows()
return True
if __name__ == '__main__':
app = QApplication([])
model = MyTableModel([['小红', 19]])
tableView = QTableView()
tableView.setModel(model)
tableView.show()
model.insertRows(0, 2)
model.removeRows(0, 2)
app.exec_()
自定义treeModel
- 自定义树结构,维护好:
parent、children、data
- 结构:
rowCount()、columnCount()、index()
- 数据显示:
headerData()、data()
- 编辑:
flags()、setData()
- 增删:
insertRow()、removeRow()
- 父节点:
parent()
使用已有模型
QFileSystemModel
使用
# 利用已有的模型做使用展示
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import os
class Demo(QWidget):
def __init__(self):
super(Demo, self).__init__()
self.__setttingWindow()
self.__buildUI()
# ================== 功能函数 ==================
def __setttingWindow(self):
self.resize(640, 480)
def __buildUI(self):
vbox = QVBoxLayout(self)
splitter = QSplitter()
vbox.addWidget(splitter)
# 模型
model = QFileSystemModel()
model.setRootPath('') # 文档上说设置监视范围,没弄清楚是什么意思;如果不设置不会显示;
# 树形视图
treeView = QTreeView(splitter)
treeView.setModel(model)
treeView.setRootIndex(model.index('c:')) # 设置可见范围
# 列表型视图
listView = QListView(splitter)
listView.setModel(model)
listView.setRootIndex(model.index(os.getcwd())) # 设置可见范围, 只有直接子节点会显示
if __name__ == '__main__':
app = QApplication([])
w = Demo()
w.show()
app.exec_()
代理模型
model <----> proxy <----> view
通过在模型上在加一个代理模型,可以实现很多其他的功能:排序、过滤
from PyQt5.QtCore import Qt, QSortFilterProxyModel
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QApplication, QListView, QWidget, QLineEdit, QTableView, QVBoxLayout
import random
class Demo(QWidget):
def __init__(self):
super(Demo, self).__init__()
self.__buildUI()
def __buildUI(self):
vbox = QVBoxLayout(self)
# 输入re框
self.editRE = QLineEdit()
vbox.addWidget(self.editRE)
# 模型
model = QStandardItemModel()
for i in range(10):
model.insertRow(0, QStandardItem(f'{random.randint(0, 20)}'))
# 代理
proxyModel = QSortFilterProxyModel()
proxyModel.setSourceModel(model)
self.editRE.textChanged.connect(proxyModel.setFilterRegExp)
proxyModel.setDynamicSortFilter(True)
# 视图
self.view = QListView()
self.view.setModel(proxyModel)
# self.view.setSortingEnabled(True)
vbox.addWidget(self.view)
if __name__ == '__main__':
app = QApplication([])
w = Demo()
w.show()
app.exec_()
使用QStandarItemModel
搭配各类ViewQListView、QWidgetView、QTreeView
# 简单使用
from PyQt5.QtCore import QModelIndex, Qt
from PyQt5.QtWidgets import QApplication, QListView, QTableView, QTreeView
from PyQt5.QtGui import QStandardItemModel
if __name__ == '__main__':
app = QApplication([])
# list只需要一个列
listModel = QStandardItemModel(5, 1)
animals = ['小猫', '小狗', '小猪', '兔子', '猫头鹰']
for i in range(listModel.rowCount()):
index = listModel.index(i, 0)
listModel.setData(index, animals[i])
listView = QListView()
listView.setModel(listModel)
# listView.show()
# table多了一步设计表头
tableModel = QStandardItemModel(5, 2)
tableModel.setHorizontalHeaderLabels(['名称', '年龄'])
ages = [3, 2, 1, 3, 4]
for i in range(tableModel.rowCount()):
index = tableModel.index(i, 0)
tableModel.setData(index, animals[i])
index = tableModel.index(i, 1)
tableModel.setData(index, ages[i])
tableView = QTableView()
tableView.setModel(tableModel)
# tableView.show()
# tree
treeModel = QStandardItemModel(2, 2)
treeModel.setHorizontalHeaderLabels(['名称', '年龄'])
type = ['动物', '植物']
for i in range(2):
index = treeModel.index(i, 0)
treeModel.setData(index, type[i])
animalParentIndex = treeModel.index(0, 0)
treeModel.insertRows(0, 5, animalParentIndex)
treeModel.insertColumns(0, 2, animalParentIndex)
for i in range(5):
index = treeModel.index(i, 0, animalParentIndex)
treeModel.setData(index, animals[i])
index = treeModel.index(i, 1, animalParentIndex)
treeModel.setData(index, ages[i])
treeView = QTreeView()
treeView.setModel(treeModel)
treeView.show()
app.exec_()
代表
就是一个获取输入、显示数据的组件,本质上就是一个Qwidget,只不过其显示的内容和model里面的值绑定上了。我们不单单可以使用默认的组件,也可以自己实现组件,并将其作为delegate。
- 继承
QStyleItemDelegate
- 创建:
createEditor()
- 设置编辑器:
setEditorData()
- 设置模型:
setModelData()
- 设置位置:
updateEditorGeometry()
from PyQt5.QtWidgets import QApplication, QTableView, QStyledItemDelegate, QWidget, QLineEdit, QSpinBox
from PyQt5.QtGui import QStandardItemModel, QStandardItem, QIcon
from PyQt5.QtCore import Qt, QModelIndex, QAbstractItemModel
class MyDelegate(QStyledItemDelegate):
def createEditor(self, parent: QWidget, option: 'QStyleOptionViewItem', index: QModelIndex) -> QWidget:
if index.column() == 0:
return QLineEdit(parent)
else:
return QSpinBox(parent)
def setEditorData(self, editor: QWidget, index: QModelIndex) -> None:
if index.column() == 0:
editor.setText(index.data(Qt.EditRole))
else:
editor.setValue(index.data(Qt.EditRole))
def setModelData(self, editor: QWidget, model: QAbstractItemModel, index: QModelIndex) -> None:
if index.column() == 0:
model.setData(index, editor.text(), Qt.EditRole)
else:
model.setData(index, editor.value(), Qt.EditRole)
def updateEditorGeometry(self, editor: QWidget, option: 'QStyleOptionViewItem', index: QModelIndex) -> None:
editor.setGeometry(option.rect)
if __name__ == '__main__':
app = QApplication([])
model = QStandardItemModel(2, 2)
# 初始化
icon = QIcon('0.png')
for i in range(2):
index = model.index(i, 0)
model.setData(index, f'小{i}')
index = model.index(i, 1)
model.setData(index, 10)
model.setHorizontalHeaderLabels(['姓名', '年龄'])
view = QTableView()
view.setModel(model)
view.setItemDelegate(MyDelegate())
view.show()
app.exec_()
# 自己实现的简单滑动编辑:)
from PyQt5.QtWidgets import QApplication, QWidget, QListView, QStyledItemDelegate
from PyQt5.QtGui import QPainter, QPaintEvent, QMouseEvent, QStandardItemModel
from PyQt5.QtCore import Qt, QSize, pyqtSignal, QModelIndex, QAbstractItemModel
class PowerLine(QWidget):
editingFinished = pyqtSignal()
def __init__(self, parent=None, curPower=20, maxPower=100):
super(PowerLine, self).__init__(parent)
self.curPower = curPower
self.maxPower = maxPower
self.isSetting = False
self.__setting()
# ================== 辅助函数 ==================
def __setting(self):
self.resize(408, 100)
self.setMouseTracking(True)
self.setAutoFillBackground(True) # 当在老画面上绘制时,自动清空
def __getPower(self, pos):
miniScale = self.width() / 108
power = (pos.x() - 2 * miniScale) / miniScale
return int(min(100, max(0, power)))
# ================== 事件 ==================
def paintEvent(self, event: QPaintEvent) -> None:
painter = QPainter(self)
# 视图、窗口
painter.setViewport(self.rect())
w, h = self.width(), self.height()
painter.setWindow(0, 0, 108, 66)
# 外边框
pen = painter.pen()
pen.setWidth(2)
painter.setPen(pen)
painter.drawRect(2, 2, 104, 60)
# 内部
pen = painter.pen()
pen.setColor(Qt.green)
pen.setWidth(0)
painter.setPen(pen)
brush = painter.brush()
brush.setColor(Qt.green)
brush.setStyle(Qt.SolidPattern)
painter.setBrush(brush)
if self.curPower > 0:
painter.drawRect(4, 5, self.curPower, 54)
def mousePressEvent(self, event: QMouseEvent) -> None:
self.isSetting = True
self.curPower = self.__getPower(event.pos())
self.update()
def mouseMoveEvent(self, event: QMouseEvent) -> None:
if not self.isSetting:
return
power = self.__getPower(event.pos())
if self.curPower != int(power):
self.curPower = int(power)
self.update()
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
self.isSetting = False
self.editingFinished.emit()
# ================== 功能函数 ==================
def sizeHint(self) -> QSize:
return QSize(104, 50)
class MyDelegate(QStyledItemDelegate):
def createEditor(self, parent: QWidget, option: 'QStyleOptionViewItem', index: QModelIndex) -> QWidget:
powerline = PowerLine(parent)
powerline.editingFinished.connect(self.commitAndCloseEditor)
return powerline
def setEditorData(self, editor: QWidget, index: QModelIndex) -> None:
val = index.data()
editor.curPower = val
def setModelData(self, editor: QWidget, model: QAbstractItemModel, index: QModelIndex) -> None:
val = editor.curPower
model.setData(index, val, Qt.DisplayRole)
def commitAndCloseEditor(self):
editor = self.sender()
self.commitData.emit(editor)
self.closeEditor.emit(editor)
if __name__ == '__main__':
app = QApplication([])
model = QStandardItemModel(2, 1)
index = model.index(0, 0, QModelIndex())
model.setData(index, 10, Qt.DisplayRole)
index = model.index(1, 0, QModelIndex())
model.setData(index, 20, Qt.DisplayRole)
listView = QListView()
listView.setModel(model)
listView.setItemDelegate(MyDelegate())
listView.show()
app.exec_()