引言
所谓绘制组件,实际上就是虚拟组件,说白了就是依靠一个父组件绘制出来的图案,且具有组件的功能。在tkinter中,每一个组件都拥有自己的句柄,因为它们是实实在在的组件,而绘制组件则没有句柄。
绘制组件有很多优点:
- 占用内存小
- 样式比普通组件更丰富,这是其创建方式决定的
- 创建GUI窗口时更简洁、直观
- ……
之所以想要开发一个tkinter的绘制组件,并不是想代替tkinter的原生组件,因为tkinter的自身原因,某些组件功能是无法通过tkinter本身绘制出来的,如scrollbar。但是,开发虚拟组件,可以大大提高窗口运行速度,使界面布局更简单明了,同时也可以使用更加丰富的组件样式。这才是开发tkinter绘制组件的初衷。
因为目前该组件尚处于起步阶段,本篇只简单完成基础框架。
选择父组件
tkinter的画布(Canvas),当之无愧。Canvas是tkinter所有组件中综合功能最强大,也是最复杂的组件(没有之一)。
使用画布,可以很好得达到我们期望的目的。
这个绘制类组件,命名为“TinUI”。
我开发的另一个基于tkinter的富文本兼TinLayout布局渲染器——TinEngine
框架
先来创建一个组件类吧,很简单↓
class TinUI(Canvas):
"""适用于Tin的高级画布组件"""
def __init__(self,master,**kw):
self.frame = Frame(master)
self.vbar = Scrollbar(self.frame)
self.vbar.pack(side=RIGHT, fill=Y)
###
kw.update({'yscrollcommand': self.vbar.set})
Canvas.__init__(self, self.frame, **kw)
self.pack(fill=BOTH, expand=True)
self.vbar['command'] = self.yview
###
self.hbar=Scrollbar(self.frame,orient='horizontal',command=self.xview)
self.hbar.pack(side=BOTTOM,fill=X)
self.config(xscrollcommand=self.hbar.set)
# Copy geometry methods
canvas_meths = vars(Canvas).keys()
methods = vars(Pack).keys() | vars(Grid).keys() | vars(Place).keys()
methods = methods.difference(canvas_meths)
for m in methods:
if m[0] != '_' and m != 'config' and m != 'configure':
setattr(self, m, getattr(self.frame, m))
这并不是简单地创建一个画布。
可以看到,源码中还加入了滚动条。因为画布应该是能够滚动的,可以使窗口界面更加灵活。
但是,使用滚动条遇到一个重要问题,那就是滚动条与画布结合,必须指定画布的滚动范围。但是,在我们创建或更改虚拟组件时,画布的滚动范围是会改变的。那么,应该怎样然滚动条与画布内容匹配呢?
匹配滚动条
这里给出一个方法。
每隔一小会时间,就判断一次画布的可视范围,然后再绑定的滚动条即可。代码如下。
class TinUI(Canvas):
"""适用于Tin的高级画布组件"""
def __init__(self,master,**kw):
#未改变的代码
self.bind('<MouseWheel>',self.set_y_view)#绑定纵向滚动
self.init()#初始化一些自身变量
self.update__()#持续更新滚动条
def init(self):
self.title_size={0:20,1:18,2:16,3:14,4:12}
def set_y_view(self,event):
self.yview_scroll(int(-1*(event.delta/120)), "units")
def update__(self):#更新宽高
try:
#获取宽和高,绑定的滚动条
x2,y2=self.bbox('all')[2:]
self.config(scrollregion=(0,0,x2,y2))
except:
pass
finally:
#每隔一秒更新一次
self.after(1000,self.update__)
这样,就完成了即时更新画布的滚动范围。
简单创建
目前,TinUI处于起步阶段,先来几个简单的创建虚拟组件例子。
以下是创建标题和段落的例子,类似Label,但并不全是:
def add_title(self,pos:tuple,text:str,fg='black',font='微软雅黑',size=1,**kw):#绘制标题
return self.create_text(pos,text=text,fill=fg,font=(font,self.title_size[size]),anchor='nw',**kw)
def add_paragraph(self,pos:tuple,text:str,fg='black',font=('微软雅黑',12),side='left',width=500,**kw):#绘制段落
return self.create_text(pos,text=text,fill=fg,font=font,anchor='nw',justify=side,width=width,**kw)
起步阶段的TinUI
完整代码:
class TinUI(Canvas):
"""适用于Tin的高级画布组件"""
def __init__(self,master,**kw):
self.frame = Frame(master)
self.vbar = Scrollbar(self.frame)
self.vbar.pack(side=RIGHT, fill=Y)
###
kw.update({'yscrollcommand': self.vbar.set})
Canvas.__init__(self, self.frame, **kw)
self.pack(fill=BOTH, expand=True)
self.vbar['command'] = self.yview
###
self.hbar=Scrollbar(self.frame,orient='horizontal',command=self.xview)
self.hbar.pack(side=BOTTOM,fill=X)
self.config(xscrollcommand=self.hbar.set)
# Copy geometry methods
canvas_meths = vars(Canvas).keys()
methods = vars(Pack).keys() | vars(Grid).keys() | vars(Place).keys()
methods = methods.difference(canvas_meths)
for m in methods:
if m[0] != '_' and m != 'config' and m != 'configure':
setattr(self, m, getattr(self.frame, m))
self.bind('<MouseWheel>',self.set_y_view)
self.init()
self.update__()
def init(self):
self.title_size={0:20,1:18,2:16,3:14,4:12}
def set_y_view(self,event):
self.yview_scroll(int(-1*(event.delta/120)), "units")
def update__(self):#更新宽高
try:
x2,y2=self.bbox('all')[2:]
self.config(scrollregion=(0,0,x2,y2))
except:
pass
finally:
self.after(1000,self.update__)
def add_title(self,pos:tuple,text:str,fg='black',font='微软雅黑',size=1,**kw):#绘制标题
return self.create_text(pos,text=text,fill=fg,font=(font,self.title_size[size]),anchor='nw',**kw)
def add_paragraph(self,pos:tuple,text:str,fg='black',font=('微软雅黑',12),side='left',width=500,**kw):#绘制段落
return self.create_text(pos,text=text,fill=fg,font=font,anchor='nw',justify=side,width=width,**kw)
测试
测试代码
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')
b.add_title((40,670),'test TinUI scrolled',size=2)
b.add_paragraph((20,300),''' TinUI是基于tkinter画布开发的界面UI布局方案,作为tkinter拓展和TinEngine的拓展而存在。目前,TinUI尚处于开发阶段。如果想要使用完整的TinUI,敬请期待。''')
a,mainloop()
效果
github项目
结语
目前,TinUI正在开发的初步阶段。今年我要参加中考,估计要到暑假才能够认真地完善。
很好,已经2022-11-12了,现在回过头来看这篇文章,真的大有感慨。TinUI即将在年底左右推出-4.0-版本,届时,TinUI将正式常熟,包含tkinter所有原生控件,以及大量拓展控件。
这是来自2023-11的修改。TinUI-4.5修复了treeview多级元素展开时的样式错误问题,只是一个缝缝补补的小更新,大更新在2024年暑期之前不会到来。不过在此之前,突发奇想,计划由接下来的4.X版本过渡到5.0,然后就想设计一个新图标。这是当前的:
这是可能会在5.0出现的:
有兴趣的tkinter爱好者也可以联系我,说说你的想法:smart-space@qq.com。
🔆tkinter创新🔆