tkinter使用TinUI风格菜单

  • Post author:
  • Post category:其他




引言

据我所知,好像隔壁customtkinter目前(2022-5)还没有推出绘制型的菜单Menu控件。也就是说,TinUI是目前唯一一个能够提供绘制类菜单控件的UI框架。

看过

tkinter绘制组件(18)——菜单

的朋友一定知道,最新(2022-5)的TinUI的menu可以做到如下样式:

  • 圆角

  • 出现时的滚动动画

  • 自定义背景色、文本颜色、激活颜色

  • 插入分割线

  • 功能按钮

虽然目前还不能使用复选框、单选框、折叠菜单等功能,但是这些对于一般的情况已经够用了。

但是,TinUI提供的菜单是在TinUI框架中使用,我们能不能让普通的tkinter控件使用TinUI的菜单呢?当然是可以的。




提前声明

在正文开始前,我先借此机会说明一下TinUI和customtkinter关系。

customtkinter和TinUI应该是目前唯二提供高自定义绘制组件的tkinter拓展库,customtkinter的可自定义程度高于TinUI。



TinUI和customtkinter的区别

一文。




框架

class TinuiMenu:
    #将TinUI的菜单移植到普通tkinter控件可用
    #参数和功能按钮格式同TinUI

    def __init__(self,widget,font='微软雅黑 12',fg='#ecf3e8',bg='#2b2a33',activefg='#ecf3e8',activebg='#616161',tran='#01ff11'):
        self.widget=widget
        self.font=font
        self.fg=fg
        self.bg=bg
        self.activefg=activefg
        self.activebg=activebg
        self.tran=tran
        self.cont=[]
        self.visible=False#在未完成载入所有功能按钮之前不可用

虽然参数比较多,但是与TinUI的菜单组件的参数基本一致,其所代表的内容也是一模一样的,因此可以参考本文开头提供的链接。

唯一不同的是这一个

visible

参数。



显示的逻辑

根据TinUI中的逻辑,一个菜单的绘制,需要获取全部的、希望显示的菜单内容,然后根据这些内容来进行渲染。但是,使用者在创建菜单的时候必然没办法同时创建菜单内容,而这个时候菜单无论如何都不能够显示。因此,通过为菜单的显示函数添加对

self.visible

的判断,可以确定是否能够显示菜单。当然,在后续的文段中将讲到编写者何时可以改变这一属性,使得菜单可用。

有人可能会问,TinUI的菜单在创建的时候不就已经安排好了显示内容吗?确实,但那是TinUI风格的创建控件形式,这里讲得控件迁移使用的是tkinter中以类的形式创建控件。



添加功能菜单

这里需要注意,当这个菜单允许显示时,即代表其内容不可更改。所以我们需要判断是否能够像这个菜单内容添加功能信息。

    def load_command(self,name,command):
        if not self.visible:
            self.cont.append((name,command))

    def load_separate(self):
        if not self.visible:
            self.cont.append('-')

这里的“-”也是TinUI菜单中分割线的标志。



结束功能显示

这一段分为两个内容,一个是初始化菜单,一个是运行菜单可视。

对于编写者来说,特利迦(触发器)是

load_over

函数。

    def load_over(self,bind='<Button-3>'):
        self.bind=bind
        self.__load()
        self.visible=True

可以看到,我们依然保留的触发按钮的

bind

参数,即触发事件,一般是右键单击。

内置的初始化函数与TinUI基本一致,注意是基本,还是有地方要改的。毕竟前人栽树后人乘凉,虽然前人和后人都是我自己😂。

    def __load(self):
        font=self.font
        fg=self.fg
        bg=self.bg
        activefg=self.activefg
        activebg=self.activebg
        tran=self.tran
        widget=self.widget
        cont=self.cont
        def endy():
            #...
        def repaint():
            #...
        def readyshow():
            #...
        def show(event):
            if not self.visible:#如果不能显示,则不进行窗口展示
                return
            #...
        widget.bind(self.bind,show)
        #...

以上是简略,源码在下面。注意

show

函数中的注释部分。

这样就完成了TinUI菜单的“移植”。



完整代码

class TinuiMenu:
    #将TinUI的菜单移植到普通tkinter控件可用
    #参数和功能按钮格式同TinUI

    def __init__(self,widget,font='微软雅黑 12',fg='#ecf3e8',bg='#2b2a33',activefg='#ecf3e8',activebg='#616161',tran='#01ff11'):
        self.widget=widget
        self.font=font
        self.fg=fg
        self.bg=bg
        self.activefg=activefg
        self.activebg=activebg
        self.tran=tran
        self.cont=[]
        self.visible=False#在未完成载入所有功能按钮之前不可用

    def load_command(self,name,command):
        if not self.visible:
            self.cont.append((name,command))

    def load_separate(self):
        if not self.visible:
            self.cont.append('-')

    def load_over(self,bind='<Button-3>'):
        self.bind=bind
        self.__load()
        self.visible=True

    def __load(self):
        font=self.font
        fg=self.fg
        bg=self.bg
        activefg=self.activefg
        activebg=self.activebg
        tran=self.tran
        widget=self.widget
        cont=self.cont
        def endy():
            pos=bar.bbox('all')
            return 0 if pos==None else pos[3]+10
        def repaint():
            maxwidth=max(widths)
            for back in backs:
                pos=bar.bbox(back)
                bar.coords(back,(5,pos[1],10+maxwidth,pos[3]))
            for sep in seps:
                pos=bar.bbox(sep)
                bar.delete(sep)
                bar.add_separate((5,pos[1]),maxwidth+5,fg=activebg)
        def readyshow():
            allpos=bar.bbox('all')
            winw=allpos[2]-allpos[0]+5
            winh=allpos[3]-allpos[1]+5
            maxx=widget.winfo_screenwidth()
            maxy=widget.winfo_screenheight()
            wind.data=(maxx,maxy,winw,winh)
        def show(event):
            if not self.visible:
                return
            maxx,maxy,winw,winh=wind.data
            sx,sy=event.x_root,event.y_root
            if sx+winw>maxx:
                x=sx-winw
            else:
                x=sx
            if sy+winh>maxy:
                y=sy-winh
            else:
                y=sy
            menu.geometry(f'{winw+20}x{winh+20}+{x}+{y}')
            bar.move('all',0,-height-7)
            menu.deiconify()
            menu.focus_set()
            for i in range(0,height+5,5):#滚动动画
                bar.move('all',0,5)
                sleep(0.01)
                bar.update()
            bar.move('all',0,5)
            bar.config(scrollregion=bar.bbox('all'))
            bar.yview_moveto(0)
            bar.update()
        widget.bind(self.bind,show)
        menu=Toplevel()
        menu.overrideredirect(True)
        menu.withdraw()
        bar=BasicTinUI(menu,bg=tran)
        bar.pack(fill='both',expand=True)
        wind=TinUINum()#记录数据
        backs=[]#按钮
        funcs=[]#按钮函数接口
        seps=[]#分割线
        widths=[]#寻找最宽位置
        for i in cont:#添加菜单内容
            if i=='-':
                sep=bar.add_separate((15,endy()),100,fg=activebg)
                seps.append(sep)
            else:
                button=bar.add_button((15,endy()),i[0],fg,bg,bg,3,activefg,activebg,activebg,font,command=lambda event,i=i:(i[1](event),menu.withdraw()))
                backs.append(button[1])
                funcs.append(button[2])
                pos=bar.bbox(button[1])
                width=pos[2]-pos[0]
                widths.append(width)
        repaint()
        readyshow()
        bbox=bar.bbox('all')
        height=bbox[3]-bbox[1]
        start=bbox[2]-bbox[0]
        gomap=((start,bbox[1]),(bbox[2],bbox[1]),(bbox[2],bbox[3]),(bbox[0],bbox[3]),(bbox[0],bbox[1]),(start,bbox[1]))
        mback=bar.create_polygon(gomap,fill=bg,outline=bg,width=15)
        bar.lower(mback)
        bar.move('all',15,0)
        menu.bind('<FocusOut>',lambda event:menu.withdraw())
        menu.attributes('-transparent',tran)



效果

在这里插入图片描述




结语

TinUI虽然基于tkinter,但是其使用方式还是有一定的不同的。虽然如此,TinUI本体照样能够与tkinter契合。

☀tkinter创新☀



版权声明:本文为tinga_kilin原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。