• 【Python、Qt】使用QItemDelegate实现单元格的富文本显示+复选框功能


    [2023-10-19]代码已更新,完善了单元格宽度不足时省略号的显示问题。


    [2023-10-18]代码已更新,追加单元格的文本对齐功能(使用成员函数QStandardItem.setTextAlignment设置单元格的Align。
    运行结果-1


    主打一个 折磨 坑多 陪伴。代码为Python,C++的就自己逐条语句慢慢改吧。



    Python代码:

    
    import sys
    from types import MethodType
    from PyQt5.QtCore import Qt,QPoint,QSize,QRect,QEvent
    from PyQt5.QtGui import QStandardItemModel, QStandardItem,QTextDocument,QTextCursor
    from PyQt5.QtWidgets import QTreeView,QApplication,QItemDelegate,QStyle
    
    class RichDelegate(QItemDelegate):#使用QItemDelegate进行单元格重绘:https://blog.csdn.net/Loc_Haoclass/article/details/106528047
    	__cboxSize=QSize(14,14)#复选框大小
    	__cboxAlignV=Qt.AlignVCenter#复选框位置-竖直(居中)
    	__cboxAlignH=Qt.AlignLeft#复选框位置-水平(左对齐)
    
    	__alignV=[Qt.AlignTop,Qt.AlignVCenter,Qt.AlignBottom]#【简化代码】
    	__alignH=[Qt.AlignLeft,Qt.AlignHCenter,Qt.AlignRight]
    	def __init__(self,parent=None,*,align=None,size=None):
    		super().__init__(parent)
    		if(align):
    			self.SetCheckboxAlign(align)
    		if(size):
    			self.SetCheckboxSize(size)
    	def SetCheckboxAlign(self,align):#设置复选框位置
    		self.__cboxAlignH,self.__cboxAlignV=self.__DepartAlign(align)
    	def SetCheckboxSize(self,size):#设置复选框大小
    		self.__cboxSize=size
    	def editorEvent(self,event,model,opt,index):#处理复选框点击逻辑:https://blog.csdn.net/xbnlkdbxl/article/details/51316424
    		if(event.type()==QEvent.MouseButtonRelease):#仅处理鼠标抬起事件
    			if(event.button()==Qt.LeftButton):#仅处理鼠标左键
    				item=index.model().itemFromIndex(index)
    				if(item.isCheckable()):#仅处理复选框存在的情况
    					rect_cbox=self.__GetRect(opt.rect,self.__cboxAlignH,self.__cboxAlignV,self.__cboxSize)
    					if(rect_cbox.contains(event.pos())):#仅复选框被点击时翻转状态
    						item.setCheckState(Qt.Unchecked if item.checkState()==Qt.Checked else Qt.Checked)
    						return True
    		return False
    	def drawCheck(self,ptr,opt,rect,state):#绘制复选框(这里直接默认绘制,有想法的可以改成其他绘制例如画个圈之类的
    		super().drawCheck(ptr,opt,rect,state)#默认绘制的复选框总是正方形
    
    	def paint(self,ptr,opt,index):
    		style=opt.widget.style() if opt.widget else QApplication.style()
    		style.drawControl(QStyle.CE_ItemViewItem, opt, ptr, opt.widget)#这条语句解决了行选中时背景色不变化的问题:https://blog.csdn.net/gongjianbo1992/article/details/108687172
    
    		doc=QTextDocument()
    		doc.setHtml(index.data())
    		txDot='…'#省略号
    		rect=QRect(opt.rect)
    		item=index.model().itemFromIndex(index)
    		if(item.isCheckable()):#绘制复选框:https://blog.csdn.net/xbnlkdbxl/article/details/51316424
    			rect_cbox=self.__GetRect(rect,self.__cboxAlignH,self.__cboxAlignV,self.__cboxSize)
    			if(rect.width()<self.__cboxSize.width()+1):#单元格过小,不予绘制
    				rect=rect_cbox
    				doc.setPlainText(txDot)
    				ptr.save()
    				ptr.translate(rect.topLeft())
    				ptr.setClipRect(rect.translated(-rect.topLeft()))
    				doc.drawContents(ptr)
    				ptr.restore()
    				return
    			doc=QTextDocument()
    			doc.setHtml(index.data())
    			self.drawCheck(ptr,opt,rect_cbox,item.checkState())
    			#计算剩余位置用于绘制文本内容
    			if(self.__cboxAlignH==Qt.AlignRight):#只调整水平位置(应该不会有人那么异端把复选框放在单元格正中间的吧,不会吧不会吧
    				rect.setRight(rect.right()-rect_cbox.width())
    			else:
    				rect.setLeft(rect.left()+rect_cbox.width())
    
    		w=rect.width()
    		s=set()
    		#使用hitTest判断字符能否完全显示:https://blog.csdn.net/eiilpux17/article/details/118461445
    		for h in range(1,int(doc.size().height()/10)):#逐行测试(该方法不严谨,小字体容易漏)
    			h*=10
    			testPoint=QPoint(w, h)
    			pos = doc.documentLayout().hitTest(testPoint, Qt.ExactHit)
    			s.add(pos)
    		s=sorted(s,reverse=True)
    
    		if(s[0]==-1):#只有一个结果,此时判断单元格是否过小
    			if(w<doc.size().width()-10):#单元格大小不足以绘制
    				return
    		elif(0 in s):#空间不足,但又能够画个省略号
    			doc.setHtml(txDot)
    		else:
    			for p in s:
    				if(p==-1):
    					continue
    				cursor=QTextCursor(doc)
    				cursor.setPosition(p-1)
    				cursor.movePosition(QTextCursor.EndOfLine,QTextCursor.KeepAnchor)
    				cursor.insertText(txDot,cursor.block().charFormat())
    
    		rect=self.__GetRect(rect,*self.__DepartAlign(item.textAlignment()),doc.size())
    		ptr.save()
    		ptr.translate(rect.topLeft())
    		ptr.setClipRect(rect.translated(-rect.topLeft()))
    		# doc.setDefaultTextOption(doc.defaultTextOption())
    		doc.drawContents(ptr)
    		ptr.restore()
    
    	def sizeHint(self,opt,index):#设置行高函数:https://blog.csdn.net/Lutx/article/details/6641353
    		tx=index.data()
    		doc=QTextDocument()
    		doc.setHtml(tx)
    		size=doc.size()
    		return QSize(size.width(),size.height())
    
    	def __GetRect(self,rect,alignH,alignV,size):#根据align返回确切位置
    		posV=[rect.top(),rect.bottom()]
    		posH=[rect.left(),rect.right()]
    		lenH,lenV=size.width(),size.height()
    		for nape in [[alignV,self.__alignV,posV,lenV],
    					[alignH,self.__alignH,posH,lenH]]:
    			align,alignLst,pos,width=nape
    			index=alignLst.index(align)
    			if(index==0):#靠左/靠上
    				pos[1]=pos[0]+width
    			elif(index==1):#居中
    				pos[0]=pos[0]+int((pos[1]-pos[0]-width)/2)
    				pos[1]=pos[0]+width
    			elif(index==2):#靠右/靠下
    				pos[0]=pos[1]-width
    		r=QRect(posH[0],posV[0],posH[1]-posH[0],posV[1]-posV[0])
    		if(r.width()>rect.width()):
    			r.setWidth(rect.width())
    		return r
    	def __DepartAlign(self,align):#将align拆解为水平和竖直并依次返回
    		alignV=self.__alignV.copy()
    		alignH=self.__alignH.copy()
    		alignV=list(filter(lambda a:int(align) & int(a)!=0,alignV))
    		alignH=list(filter(lambda a:int(align) & int(a)!=0,alignH))
    		alignV.append(Qt.AlignVCenter)
    		alignH.append(Qt.AlignLeft)
    		return alignH[0],alignV[0]
    
    if __name__ == '__main__':
    	app = QApplication(sys.argv)
    
    	tv=QTreeView()
    	tv.setModel(QStandardItemModel(tv))
    	model=tv.model()
    	model.appendRow([QStandardItem(d) for d in [' R3 ']])
    	model.appendRow([QStandardItem(d) for d in [' R5 ']])
    	model.appendRow([QStandardItem(d) for d in [' R7 ']])
    	model.appendRow([QStandardItem(d) for d in [' R50px ','B70px']])
    	model.appendRow([QStandardItem(d) for d in ['bbbppp
    SSSIIIUUU

    R20pt'
    ]]) model.item(1,0).setCheckable(True) model.item(3,1).setCheckable(True) model.item(4,0).setCheckable(True) model.item(3,0).setTextAlignment(Qt.AlignCenter)#文本对齐:居中 model.item(4,0).setTextAlignment(Qt.AlignBottom|Qt.AlignRight)#文本对齐:右下 rich_1=RichDelegate() rich_2=RichDelegate(align=Qt.AlignBottom|Qt.AlignRight)#复选框右对齐是什么邪道行为,太怪了(感觉除了左居中以外的对齐都是邪道 tv.setItemDelegateForRow(0,rich_1) tv.setItemDelegateForRow(1,rich_1) tv.setItemDelegateForRow(2,rich_1) tv.setItemDelegateForRow(3,rich_1) tv.setItemDelegateForRow(4,rich_2) tv.show() tv.resize(1200,700) sys.exit(app.exec())
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161

    运行结果:

    运行结果


    补充:

    1、我的代码仅完成富文本显示,像是往单元格里塞入按钮、下拉列表亦或是其他控件不在本篇讨论范围之内,有需要的可以参考[CSDN]QStyledItemDelegate单元格数据渲染与编辑[51CTO]QTableWidget使用setCellWidget设置控件居中显示或是自行搜索其他文章
    2、复选框的绘制样式甚至可以自定义,像是画成圆圈或是其他东西,又或是嫌黑色不好看改成紫色绿色啥的,只不过得自己实现就是了,重绘仅需QPainter倒少了挺多麻烦(只不过还是挺麻烦的所以没这需求就没必要自找麻烦


    参考资料:


    本文发布于CSDN,未经个人同意不得私自转载:https://blog.csdn.net/weixin_44733774/article/details/133838003

  • 相关阅读:
    Ground Truth
    Python的面向对象、继承、多态和对象的消失与重生
    在windows 10 里安装并设置了gvim 9.0
    【HTML】【休闲益智】真相?真香?只有一个!看看谁是大馋虫 or 贪吃鬼(找出真正吃了月饼的人
    配置NFS服务器
    lamp环境搭建(三台主机各司其职)
    Golang学习:基础知识篇(三)—— Map(集合)
    AXI 握手规则
    【javaWeb学习笔记】HTML,CSS,JS
    python数据可视化-matplotlib入门(4)-条形图和直方图
  • 原文地址:https://blog.csdn.net/weixin_44733774/article/details/133838003