tkinter绘制组件(38)——状态开关按钮

引言

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个过渡颜色)而不是临时计算,一个列表是从关闭状态到激活状态的颜色变化,另一个就是倒序。因此,只需要写一个正序颜色列表的计算式就行了。

思路也十分清晰简单:

  1. 将始末颜色转为十进制方便计算。

  2. 分别计算RGB差值(末减初)。

  3. 循环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创新🔆

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值