引言
据我所知,好像隔壁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创新