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

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


Python代码:

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#水平位置(左对齐)
	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):#设置复选框位置
		alignV=[Qt.AlignTop,Qt.AlignVCenter,Qt.AlignBottom]
		alignH=[Qt.AlignLeft,Qt.AlignHCenter,Qt.AlignRight]
		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)
		self.__cboxAlignV=alignV[0]
		self.__cboxAlignH=alignH[0]
	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_Checkbox(opt.rect)
					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

		rect=QRect(opt.rect)
		item=index.model().itemFromIndex(index)
		if(item.isCheckable()):#绘制复选框:https://blog.csdn.net/xbnlkdbxl/article/details/51316424
			rect_cbox=self.__GetRect_Checkbox(rect)
			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())

		tx=index.data()
		doc=QTextDocument()
		doc.setHtml(tx)
		txDot='...'#替换为省略号
		testPoint=QPoint(rect.width(), rect.height() / 2)#获取能完整显示的字符个数:https://blog.csdn.net/eiilpux17/article/details/118461445
		pos = doc.documentLayout().hitTest(testPoint, Qt.ExactHit)
		if(pos!=-1 and pos !=len(doc.toPlainText())):#不能完全显示的情况下进行字符替换
			docDot=QTextDocument()
			docDot.setHtml(txDot)
			docDot.setDocumentMargin(0)#发现调用该语句后doc.size取值恢复正常:https://cloud.tencent.com/developer/ask/sof/105271901
			testPoint=QPoint(rect.width()-docDot.size().width(), rect.height() / 2)
			pos = doc.documentLayout().hitTest(testPoint, Qt.ExactHit)
			if(pos==-1):
				pos=0
			cursor=QTextCursor(doc)
			cursor.setPosition(pos)
			# cursor.insertText(txDot)
			cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor)
			cursor.insertText(txDot,cursor.block().charFormat())
		LT=rect.bottomLeft()
		LT.setY(LT.y()-doc.size().height())
		# LT=rect.topLeft()
		ptr.save()
		ptr.translate(LT)
		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_Checkbox(self,rect):#返回复选框确切位置
		alignV=[Qt.AlignTop,Qt.AlignVCenter,Qt.AlignBottom]
		alignH=[Qt.AlignLeft,Qt.AlignHCenter,Qt.AlignRight]
		posV=[rect.top(),rect.bottom()]
		posH=[rect.left(),rect.right()]
		for nape in [[self.__cboxAlignV,alignV,posV,self.__cboxSize.height()],
					[self.__cboxAlignH,alignH,posH,self.__cboxSize.width()]]:
			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
		return QRect(posH[0],posV[0],posH[1]-posH[0],posV[1]-posV[0])

if __name__ == '__main__':
	app = QApplication(sys.argv)

	tv=QTreeView()
	tv.setModel(QStandardItemModel(tv))
	model=tv.model()
	model.appendRow([QStandardItem(d) for d in ['<font color="red" size=3> R3 </font>']])
	model.appendRow([QStandardItem(d) for d in ['<font color="red" size=5> R5 </font>']])
	model.appendRow([QStandardItem(d) for d in ['<font color="red" size=7> R7 </font>']])
	model.appendRow([QStandardItem(d) for d in ['<font color="red" style="font-size:50px"> R50px </font>','<font style="background:#0000FF;font-size:70px">B70px</font>']])
	model.appendRow([QStandardItem(d) for d in ['<font size=5><sub>bbb</sub><sup>ppp</sup><br><s>SSS</s><i>III</i><u>UUU</u></font><br><font color="red" style="background:#00FFFF;font-size:20pt">R20pt</font>']])

	model.item(1,0).setCheckable(True)
	model.item(3,1).setCheckable(True)
	model.item(4,0).setCheckable(True)
	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()
	sys.exit(app.exec())

运行结果:


补充:

1、我的代码仅完成富文本显示,像是往单元格里塞入按钮、下拉列表亦或是其他控件不在本篇讨论范围之内,有需要的可以参考[CSDN]QStyledItemDelegate单元格数据渲染与编辑[51CTO]QTableWidget使用setCellWidget设置控件居中显示或是自行搜索其他文章

2、复选框的绘制样式甚至可以自定义,像是画成圆圈或是其他东西,又或是嫌黑色不好看改成紫色绿色啥的,只不过得自己实现就是了,重绘仅需QPainter倒少了挺多麻烦(只不过还是挺麻烦的所以没这需求就没必要自找麻烦


参考资料:


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

相关推荐
艾派森3 分钟前
大数据分析案例-基于随机森林算法的智能手机价格预测模型
人工智能·python·随机森林·机器学习·数据挖掘
小码的头发丝、29 分钟前
Django中ListView 和 DetailView类的区别
数据库·python·django
spygg1 小时前
Qt低版本多网卡组播bug
qt·组播·多网卡组播·qt5.7.0
Chef_Chen1 小时前
从0开始机器学习--Day17--神经网络反向传播作业
python·神经网络·机器学习
码农客栈1 小时前
qt QWebSocketServer详解
qt
千澜空2 小时前
celery在django项目中实现并发任务和定时任务
python·django·celery·定时任务·异步任务
斯凯利.瑞恩2 小时前
Python决策树、随机森林、朴素贝叶斯、KNN(K-最近邻居)分类分析银行拉新活动挖掘潜在贷款客户附数据代码
python·决策树·随机森林
yannan201903132 小时前
【算法】(Python)动态规划
python·算法·动态规划
蒙娜丽宁2 小时前
《Python OpenCV从菜鸟到高手》——零基础进阶,开启图像处理与计算机视觉的大门!
python·opencv·计算机视觉
光芒再现dev2 小时前
已解决,部署GPTSoVITS报错‘AsyncRequest‘ object has no attribute ‘_json_response_data‘
运维·python·gpt·语言模型·自然语言处理