学会使用视图委托

1. 基本介绍

2. paint()和editorEvent()

3. createEditor()

4. updateEditorGeometry()

5. setEditorData()

6. setModelData()


尽管PyQt5的列表视图、树形视图和表格视图都已经提供了许多的接口让我们实现想要的功能,但是在数据编辑和显示的个性化控制方面显得有点薄弱。比方说,我们想让表格视图某一列上的单元格都默认将数值以进度条(QProgress)的方式来显示,而当用户双击单元格进行编辑时,进度条消失,出现一个数值调节框(QSpinBox)。用户编辑结束后,进度条再次显示,且进度值等于新数值。

    

显然,列表、树形和表格视图本身提供的接口很难让我们实现以上功能,而我们用视图委托的话却能够轻松实现。

1. 基本介绍

PyQt5给我们提供了QItemDelegate和 QStyledItemDelegate类,用于实现委托功能。这两者没多大区别,只不过后者能够基于当前界样式在界面上绘制出新的内容,并且能够使用QSS。所以笔者建议通过继承QStyledItemDelegate类来实现委托功能。

from PyQt5.QtWidgets import QStyledItemDelegateclass DelegateDemo(QStyledItemDelegate):def __init__(self):super(DelegateDemo, self).__init__()

继承后我们可以通过重新paint()、createEditor()、setEditorData()、updateEditorGeometry()和setModelData()函数来绘制控件以及控制数据的编辑和显示方式:

from PyQt5.QtWidgets import QStyledItemDelegateclass DelegateDemo(QStyledItemDelegate):def __init__(self):super(DelegateDemo, self).__init__()def paint(self, painter, option, index):passdef editorEvent(self, event, model, option, index):passdef createEditor(self, parent, option, index):passdef updateEditorGeometry(self, editor, option, index):passdef setEditorData(self, editor, index):passdef setModelData(self, editor, model, index):pass

委托功能编写完毕后,我们可以调用相应视图的setItemDelegate方法将委托中绘制的东西映射到视图上:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QTableViewclass Demo(QWidget):def __init__(self):super(Demo, self).__init__()self.table = QTableView(self)delegate_demo = DelegateDemo()self.table.setItemDelegate(delegate_demo)if __name__ == '__main__':app = QApplication(sys.argv)demo = Demo()demo.show()sys.exit(app.exec_())

下面我们用实例来讲解下委托中各个函数的作用。

笔者在这一章将只用表格QTableView来进行示范,当然知识点同样适用列表视图和树形视图,读者可以在学习完本章内容后再拿后面两种视图进行练习。

同样适用QListWidget、QTreeWidget和QTableWidget。

2. paint()和editorEvent()

通过paint函数我们可以对视图上特定的行、列或项进行渲染。

  • painter是我们的绘图工具,有了它就可以在界面上绘制出很多东西。关于绘图的一些知识点,请大家去看下《快速掌握PyQt5》 绘图与打印这一章节。
  • option参数包含了目标项的一些具体信息,比如我们要知道目标绘制区域(项)大小的话,就可以通过option.rect获取到。
  • index参数可以帮我们获取到指定的行、列或项,当然也可以通过该参数拿到相应项上的数据。

在表格视图中,一个单元格就是一个项(item)。

了解了该函数的作用后,我们来看下如何在一个单元格中个性化显示图片和其它样式:

图片下载地址:https://www.easyicon.net/1229836-heart_icon.html

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QStandardItem, QStandardItemModel, QPixmap, QBrush, QPen
from PyQt5.QtWidgets import QApplication, QWidget, QTableView, QStyledItemDelegate, QHBoxLayoutclass Demo(QWidget):def __init__(self):super(Demo, self).__init__()self.resize(650, 240)self.model = QStandardItemModel(6, 6)   # 1for row in range(6):for column in range(6):item = QStandardItem(f'({row}, {column})')self.model.setItem(row, column, item)self.table = QTableView()self.table.setModel(self.model)self.table.horizontalHeader().setStretchLastSection(True)delegate_demo = DelegateDemo()          # 2self.table.setItemDelegate(delegate_demo)h_layout = QHBoxLayout()h_layout.addWidget(self.table)self.setLayout(h_layout)class DelegateDemo(QStyledItemDelegate):def __init__(self):super(DelegateDemo, self).__init__()def paint(self, painter, option, index):if index.row() == 0 and index.column() == 0:    # 3rect = option.rectpainter.drawPixmap(rect.x(), rect.y(), 20, 20, QPixmap('heart.png'))painter.drawPixmap(rect.x()+20, rect.y(), 20, 20, QPixmap('heart.png'))painter.drawPixmap(rect.x()+40, rect.y(), 20, 20, QPixmap('heart.png'))elif index.row() == 0 and index.column() == 1:  # 4rect = option.rectx = rect.x()y = rect.y()width = rect.width()height = rect.height()painter.drawPixmap(x+width/2-10, y+height/2-10, 20, 20, QPixmap('heart.png'))elif index.row() == 0 and index.column() == 2:  # 5rect = option.rectdata = index.data()painter.drawText(rect.x(), rect.y()+10, data)elif index.row() == 0 and index.column() == 3:  # 6brush = QBrush(Qt.SolidPattern)brush.setColor(Qt.red)rect = option.rectpainter.fillRect(rect.x(), rect.y(), rect.width()/2, rect.height(), brush)elif index.row() == 0 and index.column() == 4:  # 7pen = QPen(Qt.SolidLine)painter.setPen(pen)painter.drawRect(option.rect)elif index.column() == 5:                       # 8rect = option.rectdata = index.data()painter.drawPixmap(rect.x(), rect.y(), 20, 20, QPixmap('heart.png'))painter.drawText(rect.x()+50, rect.y()+15, data)else:                                           # 9option.displayAlignment = Qt.AlignCenterreturn super(DelegateDemo, self).paint(painter, option, index)if __name__ == '__main__':app = QApplication(sys.argv)demo = Demo()demo.show()sys.exit(app.exec_())

1. 定义一个QStandardItemModel模型,并在每个单元格上显示行列坐标。在没设置委托前,程序界面显示如下:

2. 实例化我们写好的委托,并将其设置到视图上。

3. 通过index参数获取行列值,从而有针对性地对不同单元格(项)进行渲染。如果单元格所在的行列值都为0,那么我们先通过option.rect获取到该单元格的区域范围(QRect类型),接着调用painter的drawPixmap方法在指定位置绘制图片。显示如下:

4. 绘制一张图片,并居中显示:

5. 通过index参数获取到目标单元格的数据,并调用drawText方法绘制在指定位置:

6. 实例化一个笔刷,并调用fillRect方法填充半个单元格:

7. 实例化一个画笔,并给单元格画上边框(绘制不完整):

8. 注意这里的判断稍有不同,笔者是想给这一列上的所有单元格都进行绘制。绘制操作也很简单,就是调用drawPixmap和drawText方法绘制图片和文本:

在绘制文本时,x坐标给的是rect.x()+50。这个50是一个固定值,这样就没法做到自适应,也就是说单元格变大时,文本位置还是不变的:

所以我们可以将其改成rect.x()+rect.width()/2,让文本位置根据单元格的宽度自动调整:

9. 除了以上特定的单元格,剩余单元格全部按照原先样式居中显示(返回父类的paint函数就表示将原先的样式也绘制出来):

通过上面的例子我们知道了可以用paint函数来个性化绘制一些图片、文本和边框之类的等等,那如果我们想要绘制一个控件呢?这时候就不得不提到QStyle这个类了。QStyle非常强大,封装了一个GUI界面的视觉样式和效果,通过它我们可以让界面呈现出不同的风格。不过笔者这里将只会讲解如何使用这个类的drawControl方法,其他的知识点还请大家自行浏览下文档。drawControl方法介绍如下:

  • element参数指待绘制控件的表现形式,比如我们要绘制一个进度条的话,就传入QStyle.CE_ProgressBar;如果要绘制复选框,那就传入QStyle.CE_CheckBox。element参数可填写的值汇总列表请见该链接。
  • option参数指待绘制控件的属性,我们通常会传入QStyleOption的子类。比如要绘制的是进度条,那么我们就实例化一个QStyleOptionProgressBar,再设置相关属性后传给option。如果是复选框,那我们这里就用一个QStyleOptionButton。子类汇总如下:

  • painter的话就传入paint函数的painter参数。
  • widget参数是可选的,在绘制时起辅助作用。没怎么用到,笔者这里就不赘述了。

现在我们通过一个示例来演示下如何使用这个方法。在下面这个例子中,笔者在单元格(项)中绘制了进度条和复选框按钮:

class DelegateDemo(QStyledItemDelegate):def __init__(self):super(DelegateDemo, self).__init__()def paint(self, painter, option, index):if index.row() == 0 and index.column() == 0:        # 1progress_style = QStyleOptionProgressBar()progress_style.rect = option.rectprogress_style.minimum = 0progress_style.maximum = 100progress_style.progress = 50QApplication.style().drawControl(QStyle.CE_ProgressBar, progress_style, painter)elif index.row() == 0 and index.column() == 1:      # 2check_style = QStyleOptionButton()check_style.rect = option.rectif index.data():check_style.state = QStyle.State_Enabled | QStyle.State_Onelse:check_style.state = QStyle.State_Enabled | QStyle.State_OffQApplication.style().drawControl(QStyle.CE_CheckBox, check_style, painter)else:return super(DelegateDemo, self).paint(painter, option, index)

将代码模块导入部分修改如下:

import sys
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import QApplication, QWidget, QTableView, QStyledItemDelegate, QHBoxLayout, QStyle, \QStyleOptionProgressBar, QStyleOptionButton

1. 实例化一个QStyleOptionProgressBar对象,设置进度条的相关属性。调用QApplication.style()方法获取到程序界面的QStyle对象,然后再调用drawControl绘制出一个进度条。

2. 实例化一个QStyleOptionButton对象,并设置复选框按钮的属性。如果单元格中有数值的话,那么复选框就处于选中状态,没有值的话就处于非选中状态(状态属性值请到该页面进行查阅)。最后再通过drawControl方法进行绘制。

运行截图如下:

大家运行程序后可能会发现复选框按钮不能通过鼠标点击来改变状态,因为paint只负责绘制,具体的事件响应逻辑我们还需要通过其他函数来实现。这里我们只需要重新实现下editorEvent方法就好:

  • model指当前视图的模型,通过该参数我们可以更新模型值。

现在给上面的程序添加如下代码:

def editorEvent(self, e, model, option, index):if index.row() == 0 and index.column() == 1:if e.type() == e.MouseButtonPress:if index.data():model.setData(index, '')else:model.setData(index, '(0, 1)')return Truereturn super(DelegateDemo, self).editorEvent(e, model, option, index)

如果当前操作的是目标单元格的话,判断下是不是鼠标按下事件,是的话再判断当前项有没有数据,接着调用setData方法修改目标单元格内容。修改之后paint函数会根据单元格的内容重新绘制。

3. createEditor()

通常我们双击单元格后,单元格内会出现一个输入框让我们修改数据。那假如我想在双击后出现一个数字调节框QSpinBox呢?那此时就需要用createEditor方法创建一个出来。

  • parent指定新建控件的父类,我们只需在控件实例化时传入即可。
  • 该函数最后要返回一个控件。

以下是示例代码:

import sys
from PyQt5.QtGui import QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import QApplication, QWidget, QTableView, QStyledItemDelegate, QHBoxLayout, QStyle, \QStyleOptionProgressBar, QSpinBoxclass Demo(QWidget):def __init__(self):super(Demo, self).__init__()self.resize(650, 240)self.model = QStandardItemModel(6, 6)           # 1self.model.setItem(0, 0, QStandardItem('10'))self.table = QTableView()self.table.setModel(self.model)self.table.horizontalHeader().setStretchLastSection(True)delegate_demo = DelegateDemo()self.table.setItemDelegate(delegate_demo)h_layout = QHBoxLayout()h_layout.addWidget(self.table)self.setLayout(h_layout)class DelegateDemo(QStyledItemDelegate):def __init__(self):super(DelegateDemo, self).__init__()def paint(self, painter, option, index):if index.row() == 0 and index.column() == 0:progress_style = QStyleOptionProgressBar()progress_style.rect = option.rectprogress_style.minimum = 0progress_style.maximum = 100progress_style.progress = int(index.data())     # 2QApplication.style().drawControl(QStyle.CE_ProgressBar, progress_style, painter)def createEditor(self, parent, option, index):          # 3print('createEditor')if index.row() == 0 and index.column() == 0:spin_box = QSpinBox(parent)spin_box.setRange(0, 100)return spin_boxelse:return super(DelegateDemo, self).createEditor(parent, option, index)if __name__ == '__main__':app = QApplication(sys.argv)demo = Demo()demo.show()sys.exit(app.exec_())

1. 创建好模型后,给第一个单元格设置一个初始值10(字符串)。

2. 在paint函数中让进度条的值等于单元格的数值,要将字符串转为整型值。

3. 在createEditor函数中,创建一个QSpinBox对象,设置调节范围为1-100(正好是进度条的最大最小值),最后返回该对象。

运行截图如下:

初始界面

双击单元格进行编辑

编辑结束后进度条值也会更新

4. updateEditorGeometry()

我们已经在createEditor函数中返回一个QSpinBox,所以当用户双击单元格时会出现一个数字调节框。那如果想改变这个调节框的位置和大小,就需要实现updateEditorGeometry函数了。

  • editor就是createEditor函数返回的控件。
  • 我们可以让这个控件调用setGeometry方法,从而改变它自己的位置和大小。
  • option参数能够给我们提供单元格(项)的大小。

新增代码如下:

def updateEditorGeometry(self, editor, option, index):print('updateEditorGeometry')if index.row() == 0 and index.column() == 0:rect = option.recteditor.setGeometry(rect.x(), rect.y(), rect.width(), rect.height() / 2)else:return super(DelegateDemo, self).updateEditorGeometry(editor, option, index)

笔者这里调整了QSpinBox的大小,让它的高度变为单元格高度的一半。

运行截图如下:

当然不要像笔者这样做,因为进度条都还可以看到。

5. setEditorData()

该函数的作用就是把模型中相应单元格(项)的数据显示到editor(QSpinBox)上,但大家会发现其实不用这个方法也完全没关系,因为在上面的代码中,当我们双击进行编辑时,QSpinBox就已经会显示最新编辑过的值了。

  • 通过index.data()获取到数据,然后调用editor控件相应的方法将该数值显示出来。

新增代码如下:

def setEditorData(self, editor, index):print('setEditorData')if index.row() == 0 and index.column() == 0:editor.setValue(int(index.data()))else:super(DelegateDemo, self).setEditorData(editor, index)

当然如果我们想在每次编辑时,QSpinBox都能够显示同一个值,那重新实现这个函数是比较有意义的:

editor.setValue(50)

6. setModelData()

该函数的作用是将编辑好的值更新到模型中,但其实在我们这个例子中就算不实现,也是会自动更新的,所以是否要重新实现setEditorData()和setModelData()函数(或者怎样实现)还是要根据项目需求来定。

  • 首先获取到editor控件上的值,然后通过model.setData()将其更新到模型中。

新增代码如下:

def setModelData(self, editor, model, index):print('setModelData')if index.row() == 0 and index.column() == 0:model.setData(index, editor.value())else:return super(DelegateDemo, self).setModelData(editor, model, index)

相信阅读过这一章后,大家对视图委托的用法也有了一定了解,希望大家能够好好运用起来,给自己程序中的视图添加更多个性化的东西。

《PyQt5高级编程实战》学会使用视图委托相关推荐

  1. 《PyQt5高级编程实战》自定义信号详解

    自定义信号详解 1. 创建自定义信号 2. 让自定义信号携带值 3. 自定义信号的重载版本 4. 窗口间通信 5. 线程间通信 PyQt5中各个控件自带的信号已经能够让我们完成许多需求,但是如果想要更 ...

  2. ASP.NET MVC5 高级编程 第3章 视图

    参考资料<ASP.NET MVC5 高级编程>第5版 第3章 视图 3.1 视图的作用 视图的职责是向用户提供界面. 不像基于文件的框架,ASP.NET Web Forms 和PHP ,视 ...

  3. 慕课网Flask高级编程实战-10.鱼书业务处理

    10.1 最近的礼物 我们的首页会显示最近的赠送书籍列表.这个列表有三个限制条件: 1.数量不超过30 2.按照时间倒序排列,最新的排在最前面 3.去重,同一本书籍的礼物不重复出现 1.首先编写复杂S ...

  4. c++面向对象高级编程 学习五 组合、委托与继承

    组合 composition 表示has a queue类中有一个deque容器,这种关系叫做 组合 queue中的六个函数都是调用c的函数完成的 template <class T> c ...

  5. 慕课网Flask高级编程实战-7.静态文件、模板、消息闪现与Jinja2

    7.1 静态文件访问原理 1.默认访问方法 Flask访问静态文件非常简单,只需要在项目根目录建立static文件夹.将静态资源文件放入static下即可.访问的时候访问http://ip:port/ ...

  6. 第13章代码《跟老男孩学习Linux运维:Shell编程实战》

    本书历史上已出版最实战的Shell高级编程实战书籍,没有之一,和市面书籍不同,本书是作者经过18年的运维工作及教学工作后,创新类企业级实战书籍,适合所有学习及从事Linux相关工作的读者. <跟 ...

  7. c++面向对象高级编程 总目录

    本文是对学习侯捷视频 c++面向对象高级编程系列博客的目录总索引. c++面向对象高级编程 学习一 不带指针的类: 访问私有成员变量的方式,内联inline,常量成员函数,构造函数,值传递,引用传递, ...

  8. linux 运维高级脚本生成器,Linux运维系列,Shell高级脚本自动化编程实战

    课程文件目录: Linux自动化运维系列 Shell高级脚本自动化编程实战 [6.1G] ┣━━01.Shell基础概述 [315.1M] ┃ ┣━━1-1 Shell脚本体系概述.mp4 [154. ...

  9. 《C#多线程编程实战(原书第2版)》——3.2 在线程池中调用委托

    本节书摘来自华章出版社<C#多线程编程实战(原书第2版)>一书中的第3章,第3.2节,作者(美)易格恩·阿格佛温(Eugene Agafonov),黄博文 黄辉兰 译,更多章节内容可以访问 ...

最新文章

  1. sqlmap 常用操作
  2. 前端学习(3059):vue+element今日头条管理-优化文章状态
  3. C++与QML信号交互(非Q_PROPERTY法)
  4. anywhere执行时端口被占用Address already in use:8080解决方法
  5. dbnetlib sqlserver不存在或拒绝访问_部署IIS+PHP+SQL server环境
  6. java包装经验_java中基本类型和包装类型实践经验
  7. 一文讲清楚【KL距离】、【torch.nn.functional.kl_div()】和【torch.nn.KLDivLoss()】的关系
  8. flash 围棋_中国卫视执白0.5目胜flash77
  9. Vue3 npm run serve 太慢的解决方法
  10. HTML渐变背景不重复,CSS背景渐变重复问题
  11. python区间中的数字统计
  12. 忍看朋辈离职去,怒向HR要加薪
  13. Linux操作系统下复现github上的项目(一):下载项目、配置环境
  14. linux 命令:grep、egrep、ngrep、kill、pkill、killall
  15. 区块链学习笔记一 BTC密码学原理
  16. 交换机ARP代理详解
  17. 计算机视觉与摄影测量的不同2
  18. 阿拉丁HASP SRM加密锁(加密狗)
  19. javax.net.ssl.SSLHandshakeException: Unacceptable certificate: CN=GeoTrust SSL C
  20. 程序员兼职怎样报价力求中标?——接私活的项目报价策略

热门文章

  1. 图像的直方图计算及绘制(红绿蓝三通道直方图)
  2. 洛谷 P3802 小魔女帕琪
  3. Go开发 之 基础语法(常量、枚举、注释、类型别名、指针)
  4. P3802 小魔女帕琪 期望
  5. 微信小程序踩坑记——ColorUI组件的使用
  6. 云速建站配置https证书
  7. 装修颜色搭配方案,打造与众不同的家居
  8. 微信小程序配置多环境
  9. 尝试使用sklearn自动进行多模型预测并计算权重
  10. repost ACM算法竞赛生涯