tkinter绘制组件(12)——表格
tkinter绘制组件(12)表格
tkinter绘制组件(12)——表格
引言
在GUI中,表格作为一种数据呈现的方式组件,会运用到数据反馈等功能中,让用户对程序处理的结果有一种较为直观的感受。因此,绘制表格也是TinUI走向成熟一个重要里程。
实际上,我对tkinter(tcl\tk)自身的表格(Treeview)很不满。首先,列宽虽然能够自定义,但不能够自动适配表头内容,默认情况下,表头宽度是一定的。其次,单元格的内容不能够换行,否则无法显示除了第一行以外的其它内容,而且如果第一行的文本超过了列宽,它居然就不显示了,还得用户自己调整表格宽度。以上的这些缺点,对任何人都不太友好,那么TinUI绘制表格的主要目标就是表格尺寸与内容自动契合。
再次剧透 ,TinUI绘制的表格(又)是目前最复杂的组件绘制。
构思
TinUI的表格究竟如何绘制,还是考虑了一段时间的。
外观
想想那个UWP并没有原生表格控件,其中的表格都是由DataGrid等单元格组件绘制出来的。因此,TinUI也将以单元格的应试绘制表格单元。TinUI的表格也不会支持宽度手动改变,目前也没想要明显区分表头和表格内容。
看起来TinUI绘制表格简化了很多工作,但是我们的最终目的——绘制一个自动契合内容的表格,是必须要达成的。
绘制方法
为了契合表格内容,绘制过程中需要用到大量的计算和平面图形想象。
绘制基本流程如下:
有了逻辑图,接下来就可以开工了。
布局
函数结构
def add_table(self,pos:tuple,outline='#E1E1E1',fg='black',bg='white',data=[['1','2','3'],['a','b','c']],minwidth=100,font=('微软雅黑',12)):#绘制表格
'''
pos::位置
outline::边框颜色
fg::文本颜色
bg::单元格颜色
data::表格数据。格式:((title,...,...),(content,...,...),...)
minwidth::最小列宽
font::字体
'''
绘制表头
根据逻辑图,绘制表头时,先绘制表头文本,再绘制单元格,同时,将列宽记录到宽度组。
为了方便后面获取特定列的列宽,宽度组的设计如下:
line_width={1:width1,2:width2,3:width3,...}
绘制代码如下:
title_num=len(data[0])#获取表头个数
end_x,end_y=pos#起始位置
height=0
line_width={}#获取每列的固定宽度
count=1#列数
for i in data[0]:#绘制表头
title=self.create_text((end_x,end_y),anchor='nw',text=i,fill=fg,font=font)
bbox=self.bbox(title)
#判断最小宽度
if bbox[2]-bbox[0]<=100:
width=100
else:
width=bbox[2]-bbox[0]
#设定该列列宽
line_width[count]=width
height=bbox[3]-bbox[1]
self.create_rectangle((end_x,end_y,end_x+width,end_y+height),outline=outline,fill=bg)
#下一个表头的起始位置
end_x=end_x+width+2
count+=1
self.tkraise(title)
通过注释和解读代码可知,绘制表头主要分为以下几个部分。
- 绘制表头文本和单元格
- 获取单元格宽度,并加入宽度字典
- 计算下一个表头的起始绘制位置
- 重新调整个画布对象的层次
其中的第二、三步就涉及到了平面图形想象和计算。
绘制表格内容
格局逻辑图,可以得出绘制表格内容的基本框架:
for line in data[1:]:
#绘制每一行
for a in line:
#绘制单元格
#获取最大高度,重绘该行
那么根据绘制表头经验,绘制同一行内容的代码如下:
count=1
a_dict={}#高度字典
end_x=pos[0]#其实横坐标
height=0
for a in line:
width=line_width[count]
cont=self.create_text((end_x,end_y),anchor='nw',text=a,fill=fg,width=width,font=font)
bbox=self.bbox(cont)
height=bbox[3]-bbox[1]#获取高度
back=self.create_rectangle((end_x,end_y,end_x+width,end_y+height),outline=outline,fill=bg)#单元格
self.tkraise(cont)
a_dict[count]=(back,height,(end_x,end_y,end_x+width))#(end_x,end_y,width)为重新绘制确定位置范围
end_x=end_x+width+2#该行下一个单元格的起始横坐标
count+=1
获取高度和重绘
在上面的一段代码中,出现了字典a_dict
。其结构如下:
a_dict={列数:(单元格画布ID,高度,(x1,y1,x2))}
通过循环其中的每一个高度,获取最大高度,这个很简单,直接给代码:
def get_max_height(widths:dict):
height=0
for i in widths.values():
height=i[1] if i[1]>height else height
#重新绘制
for back in widths.keys():
self.delete(widths[back][0])
x1,y1,x2=widths[back][2]
y2=y1+height
newback=self.create_rectangle((x1,y1,x2,y2),outline=outline,fill=bg)
self.lower(newback)
return height
end_y=pos[1]+height+2
for line in data[1:]:
#...
for a in line:
#...
height=get_max_height(a_dict)
end_y=end_y+height+2
经过以上的一波操作,一个能够自定义颜色、适配列宽、自动调整单元格高度以显示全部内容的表格组件已经被绘制出来了。
完整代码函数
def add_table(self,pos:tuple,outline='#E1E1E1',fg='black',bg='white',data=[['1','2','3'],['a','b','c']],minwidth=100,font=('微软雅黑',12)):#绘制表格
def get_max_height(widths:dict):
height=0
for i in widths.values():
height=i[1] if i[1]>height else height
#重新绘制
for back in widths.keys():
self.delete(widths[back][0])
x1,y1,x2=widths[back][2]
y2=y1+height
newback=self.create_rectangle((x1,y1,x2,y2),outline=outline,fill=bg)
self.lower(newback)
return height
title_num=len(data[0])#获取表头个数
end_x,end_y=pos#起始位置
height=0
line_width={}#获取每列的固定宽度
count=1
for i in data[0]:
title=self.create_text((end_x,end_y),anchor='nw',text=i,fill=fg,font=font)
bbox=self.bbox(title)
if bbox[2]-bbox[0]<=100:
width=100
else:
width=bbox[2]-bbox[0]
line_width[count]=width
height=bbox[3]-bbox[1]
self.create_rectangle((end_x,end_y,end_x+width,end_y+height),outline=outline,fill=bg)
end_x=end_x+width+2
count+=1
self.tkraise(title)
end_y=pos[1]+height+2
for line in data[1:]:
count=1
a_dict={}
end_x=pos[0]
height=0
for a in line:
width=line_width[count]
cont=self.create_text((end_x,end_y),anchor='nw',text=a,fill=fg,width=width,font=font)
bbox=self.bbox(cont)
height=bbox[3]-bbox[1]
back=self.create_rectangle((end_x,end_y,end_x+width,end_y+height),outline=outline,fill=bg)
self.tkraise(cont)
a_dict[count]=(back,height,(end_x,end_y,end_x+width))#(end_x,end_y,width)为重新绘制确定位置范围
end_x=end_x+width+2
count+=1
height=get_max_height(a_dict)
end_y=end_y+height+2
return None
效果
测试代码
def test(event):
a.title('TinUI Test')
b.add_paragraph((50,150),'这是TinUI按钮触达的事件函数回显,此外,窗口标题也被改变、首行标题缩进减小')
b.coords(m,100,5)
def test1(word):
print(word)
def test2(event):
ok1()
def test3(event):
ok2()
def test4(event):
from time import sleep
for i in range(1,101):
sleep(0.02)
progressgoto(i)
if __name__=='__main__':
a=Tk()
a.geometry('700x700+5+5')
b=TinUI(a,bg='white')
b.pack(fill='both',expand=True)
m=b.add_title((600,0),'TinUI is a test project for futher tin using')
m1=b.add_title((0,680),'test TinUI scrolled',size=2,angle=24)
b.add_paragraph((20,290),''' TinUI是基于tkinter画布开发的界面UI布局方案,作为tkinter拓展和TinEngine的拓展而存在。目前,TinUI尚处于开发阶段。如果想要使用完整的TinUI,敬请期待。''',
angle=-18)
b.add_paragraph((20,100),'下面的段落是测试画布的非平行字体显示效果,也是TinUI的简单介绍')
b.add_button((250,450),'测试按钮',activefg='white',activebg='red',command=test,anchor='center')
b.add_checkbutton((80,430),'允许TinUI测试',command=test1)
b.add_label((10,220),'这是由画布TinUI绘制的Label组件')
b.add_entry((250,300),350,30,'这里用来输入')
b.add_separate((20,200),600)
b.add_radiobutton((50,480),300,'sky is blue, water is blue, too. So, what is your heart',('red','blue','black'),command=test1)
b.add_link((400,500),'TinGroup知识库','http://tinhome.baklib-free.com/')
_,ok1=b.add_waitbar1((500,220),bg='lightgreen')
b.add_button((500,270),'停止等待动画',activefg='cyan',activebg='black',command=test2)
bu1=b.add_button((700,200),'停止点状滚动条',activefg='white',activebg='black',command=test3)[1]
bu2=b.add_button((700,250),'nothing button 2')[1]
bu3=b.add_button((700,300),'nothing button 3')[1]
b.add_labelframe((bu1,bu2,bu3),'box buttons')
_,_,ok2=b.add_waitbar2((600,400),fg='blue')
b.add_combobox((600,550),text='中考成绩预测',content=('730','740','750','760','770','780'))
b.add_button((600,480),text='测试进度条(无事件版本)',command=test4)
_,_,_,progressgoto=b.add_progressbar((600,510))
b.add_table((180,630),data=(('a','space fans over the world','c'),('you\ncan','2','3'),('I','II','have a dream, then try your best to get it!')))
b.add_paragraph((300,810),text='上面是一个表格')
a.mainloop()
最终效果
2021-12-12新样式
突出表头。
2022-1-2新样式
表头自动匹配换行。
2022-7-3新功能
初始化maxwidth
可指定单元格最大宽度,文本自动换行。
2024-1-26新样式
使用圆角边框
(原来上次更新是2022……
下面是标准明亮和暗黑样式:
补充说明
在TinUI-2.5.0-版本中,除了一些简单和窗口外组件,所有的TinUI组件的返回值的最后一项统一是固定的:uid
,也就是整体组件的画布对象名称。可以通过最后一个返回值来获取表格的尺寸。
github项目
pip下载
pip install tinui
结语
这次绘制的表格对于鼠标事件的响应可能有点欠缺,但是相比于外观,其完整显示内容的实用性已经完全达到了。
🔆tkinter创新🔆
更多推荐
所有评论(0)