tkinter绘制组件(38)——状态开关按钮
- 引言
- 布局
- 函数结构
- 按钮主体
- 渐变色处理
- 颜色处理基础
- 渐变色列表
- 形成列表
- 样式绑定
- 完整函数代码
- 效果
- 测试代码
- 最终效果
- github项目
- pip下载
- 结语
引言
TinUI里的状态开关按钮(togglebutton)和开关(onoff)一样,都是用来显示并操作两种对立状态的交互控件。在很多UI库中,这两个控件均由按钮派生出来。开关类控件提供了即时状态改变响应的接口,开关控件多用于设置界面,状态开关按钮则多用于应用运行中的状态更改。
布局
函数结构
def add_togglebutton(self,pos:tuple,text:str,fg='#1b1b1b',bg='#fbfbfb',line='#CCCCCC',linew=1,activefg='#f3f4fd',activebg='#3041d8',activeline='#5360de',font=('微软雅黑',12),command=None,anchor='nw'):#绘制状态开关按钮
'''
- pos::位置
- text::文本
- fg::文本颜色
- bg::背景色
- line::边框颜色
- linew::边框宽度
- activefg::开启状态文本颜色
- activebg::开启状态背景色
- activeline::开启状态边框颜色
- font::字体
- command::响应函数,接受一个参数:`True`开启,`False`关闭
- anchor::对齐方向
'''
按钮主体
参考WinUI3中的状态开关按钮,按钮的主体使用TinUI中的圆角按钮(button2),在此基础上进行功能修改:
#...
button=self.create_text(pos,text=text,fill=fg,font=font,anchor=anchor)
uid='togglebutton'+str(button)
self.itemconfig(button,tags=uid)
x1,y1,x2,y2=self.bbox(button)
linew-=1
outline_t=(x1-linew,y1-linew,x2+linew,y1-linew,x2+linew,y2+linew,x1-linew,y2+linew)
outline=self.create_polygon(outline_t,width=9,tags=uid,fill=line,outline=line)
back_t=(x1,y1,x2,y1,x2,y2,x1,y2)
back=self.create_polygon(back_t,width=7,tags=uid,fill=bg,outline=bg)
self.tag_bind(button,'<Button-1>',on_click)
#self.tag_bind(button,'<Enter>',in_button)
#self.tag_bind(button,'<Leave>',out_button)
self.tag_bind(back,'<Button-1>',on_click)
#self.tag_bind(back,'<Enter>',in_button)
#self.tag_bind(back,'<Leave>',out_button)
self.tkraise(button)
funcs=FuncList(3)
funcs.change_command=change_command
funcs.disable=disable
funcs.active=active
#...
整个绘制过程基本copy了button2的代码,但是除了一些小修改以外,就是去除了对于鼠标进出的响应过程(被注释掉的代码)。为了避免添加过多参数,同时方便编写者管理按钮样式,将激活时的颜色作为状态开启的提示色。
渐变色处理
这是togglebutton创建的一个重点,也是唯一值得讲一讲的。
TinUI中使用渐变色作为togglebutton的过渡动画。因为这一段代码是我突发兴致决定的,所以肯定由比较繁琐的地方,往后或许会修改,也希望指正。
颜色处理基础
渐变色过程由十进制方式计算,而tkinter(Tcl/Tk)的颜色模式采用的是十六进制字符串,很有想法,做个微操:
def __rgb2num(rgb):#tkinter颜色类型转十进制rgb
return int(rgb[1:3],16),int(rgb[3:5],16),int(rgb[5:],16)
def __num2rgb(hexs:tuple):#十进制rgb转tkinter颜色类型
co='#'
for i in hexs:
co+=str(hex(i))[2:]
return co
渐变色列表
为了不让动画过于耗时,使用两个列表存储渐变颜色(25个过渡颜色)而不是临时计算,一个列表是从关闭状态到激活状态的颜色变化,另一个就是倒序。因此,只需要写一个正序颜色列表的计算式就行了。
思路也十分清晰简单:
-
将始末颜色转为十进制方便计算。
-
分别计算RGB差值(末减初)。
-
循环25遍(算上初始值是26遍),每次计算颜色变化量
t
。
具体代码如下:
def get_color_change(st,ed):
colors_list=[]
#起始和终止颜色,十进制rgb
a1,a2,a3=__rgb2num(st)
b1,b2,b3=__rgb2num(ed)
#两颜色差值
r,g,b=(b1-a1),(b2-a2),(b3-a3)
for i in range(26):
t=i/25
rgb=(int(a1 + r * t), int(a2 + g * t), int(a3 + b * t))
colors_list.append(__num2rgb(rgb))
return colors_list
其中的r,g,b
是颜色差值,t=i/25
就是在计算颜色变化量,也就是渐变到哪一步的指示。通过[r|g|b] * t
就可以得到这一步颜色的具体变化值。
之后可能把这个渐变色列表生成方法作为TinUI的通用函数,具体看其它控件使用需要。
形成列表
这一步就简单了。
#...
#处理渐变色
colors.append(get_color_change(fg,activefg))#文本颜色
colors.append(get_color_change(bg,activebg))#背景颜色
#re_colors 反向颜色列表
re_colors=[colors[0][::-1],colors[1][::-1]]
nowcolors=colors
样式绑定
这里要注意,我们之前只是创建了渐变颜色列表而已,动画部分还没完成。
因为控件的不同状态对应的渐变顺序不一样,因此每当按钮状态改变时要重新指定动画颜色列表,这个操作放在on_click
中执行。
def on_click(event):
nonlocal state
if state==False:
state=True
self.itemconfig(outline,fill=activeline,outline=activeline)
change_color(0,2)
else:
state=False
self.itemconfig(outline,fill=line,outline=line)
change_color(0,1)
if command!=None:
command(state)
里面提到的change_color
就是变化颜色函数,通过tkinter计时器进行函数循环,间隔时间是0.005秒,完成后改变指向的颜色列表。
def change_color(t,change:int):#变化颜色
#change:: 1=>colors, 2=>re_colors
nonlocal nowcolors
if t<=25:
self.itemconfig(back,fill=nowcolors[1][t],outline=nowcolors[1][t])
self.itemconfig(button,fill=nowcolors[0][t])
self.after(5,lambda : change_color(t+1,change))
else:
if change==1:
nowcolors=colors
elif change==2:
nowcolors=re_colors
完整函数代码
def add_togglebutton(self,pos:tuple,text:str,fg='#1b1b1b',bg='#fbfbfb',line='#CCCCCC',linew=1,activefg='#f3f4fd',activebg='#3041d8',activeline='#5360de',font=('微软雅黑',12),command=None,anchor='nw'):#绘制状态开关按钮
def in_button(event):
pass
#状态开关按钮当前不再对鼠标进入和离开进行响应
def out_button(event):
pass
def on_click(event):
nonlocal state
if state==False:
state=True
self.itemconfig(outline,fill=activeline,outline=activeline)
change_color(0,2)
else:
state=False
self.itemconfig(outline,fill=line,outline=line)
change_color(0,1)
if command!=None:
command(state)
def change_color(t,change:int):#变化颜色
#change:: 1=>colors, 2=>re_colors
nonlocal nowcolors
if t<=25:
self.itemconfig(back,fill=nowcolors[1][t],outline=nowcolors[1][t])
self.itemconfig(button,fill=nowcolors[0][t])
self.after(5,lambda : change_color(t+1,change))
else:
if change==1:
nowcolors=colors
elif change==2:
nowcolors=re_colors
def change_command(new_func):
nonlocal command
command=new_func
def disable(fg='#9d9d9d',bg='#f5f5f5'):
if state==False:#区分当前状态进行禁用配色
self.itemconfig(button,state='disable',fill=fg)
self.itemconfig(back,state='disable',disabledfill=bg)
else:
self.itemconfig(button,state='disable',fill=bg)
self.itemconfig(back,state='disable',disabledfill=fg)
def active():
self.itemconfig(button,state='normal')
self.itemconfig(back,state='normal')
out_button(None)
def __rgb2num(rgb):#tkinter颜色类型转十进制rgb
return int(rgb[1:3],16),int(rgb[3:5],16),int(rgb[5:],16)
def __num2rgb(hexs:tuple):#十进制rgb转tkinter颜色类型
co='#'
for i in hexs:
co+=str(hex(i))[2:]
return co
def get_color_change(st,ed):
colors_list=[]
#起始和终止颜色,十进制rgb
a1,a2,a3=__rgb2num(st)
b1,b2,b3=__rgb2num(ed)
#两颜色差值
r,g,b=(b1-a1),(b2-a2),(b3-a3)
for i in range(26):
t=i/25
rgb=(int(a1 + r * t), int(a2 + g * t), int(a3 + b * t))
colors_list.append(__num2rgb(rgb))
return colors_list
state=False#off:False on:True
colors=[]#渐变色颜色列表,25个,off->on,[[文本颜色,...],[背景色,...]]
button=self.create_text(pos,text=text,fill=fg,font=font,anchor=anchor)
uid='togglebutton'+str(button)
self.itemconfig(button,tags=uid)
x1,y1,x2,y2=self.bbox(button)
linew-=1
outline_t=(x1-linew,y1-linew,x2+linew,y1-linew,x2+linew,y2+linew,x1-linew,y2+linew)
outline=self.create_polygon(outline_t,width=9,tags=uid,fill=line,outline=line)
back_t=(x1,y1,x2,y1,x2,y2,x1,y2)
back=self.create_polygon(back_t,width=7,tags=uid,fill=bg,outline=bg)
self.tag_bind(button,'<Button-1>',on_click)
#self.tag_bind(button,'<Enter>',in_button)
#self.tag_bind(button,'<Leave>',out_button)
self.tag_bind(back,'<Button-1>',on_click)
#self.tag_bind(back,'<Enter>',in_button)
#self.tag_bind(back,'<Leave>',out_button)
self.tkraise(button)
funcs=FuncList(3)
funcs.change_command=change_command
funcs.disable=disable
funcs.active=active
#处理渐变色
colors.append(get_color_change(fg,activefg))#文本颜色
colors.append(get_color_change(bg,activebg))#背景颜色
#re_colors 反向颜色列表
re_colors=[colors[0][::-1],colors[1][::-1]]
nowcolors=colors
return button,back,outline,funcs,uid
效果
测试代码
def test13(state):
if state:
b.itemconfig(tgbutton,text='状态开关按钮:开启')
else:
b.itemconfig(tgbutton,text='状态开关按钮:关闭')
if __name__=='__main__':
tgbutton=b.add_togglebutton((1200,230),text='状态开关按钮:关闭',command=test13)[0]
最终效果
github项目
TinUI的github项目地址
pip下载
pip install tinui
结语
TinUI4可能会时不时加一些莫名其妙的控件,而基础的应用功能基本不会改变,但还是会偶尔更新的。
🔆tkinter创新🔆