【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

相关推荐
databook9 小时前
Manim实现闪光轨迹特效
后端·python·动效
Juchecar11 小时前
解惑:NumPy 中 ndarray.ndim 到底是什么?
python
用户83562907805111 小时前
Python 删除 Excel 工作表中的空白行列
后端·python
Json_11 小时前
使用python-fastApi框架开发一个学校宿舍管理系统-前后端分离项目
后端·python·fastapi
数据智能老司机18 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机19 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机19 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机19 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i19 小时前
drf初步梳理
python·django
每日AI新事件19 小时前
python的异步函数
python