tkinter绘制组件(41)——菜单按钮
- 引言
- 布局
- 函数结构
- 按钮部分
- 菜单显示
- 完整代码函数
- 效果
- 测试代码
- 最终效果
- github项目
- pip下载
- 结语
引言
TinUI5的新控件,菜单按钮,menubutton
。
这是一个与TinUI菜单(menubar)相关联的控件,可以当作按钮(button2)放置在窗口的任意位置。只需要单击就可以展开菜单。
在TinUI中,菜单按钮有两种展开方式,由参数side=x/y
决定,稍后会详细说明。当然,两种对齐方式在超出显示屏时,均会做出调整,以便全部可见。
我才知道,在WinUI3里,像菜单、选择器、浮窗这些脱离窗口的控件,叫“浮出控件(ContextFlyout)”。不过在tkinter里无法实现,原理决定了。
布局
函数结构
def add_menubutton(self,pos:tuple,text:str,side='y',fg='#1b1b1b',bg='#fbfbfb',line='#CCCCCC',linew=1,activefg='#5d5d5d',activebg='#f5f5f5',activeline='#e5e5e5',font=('微软雅黑',12),cont=(('command',print),'-'),tran='#01FF11'):#绘制按钮展开菜单
'''
此段略过,样式是从button2挪过来的,cont是menubar的
side - 展开方向。y在下方展开,x在右侧展开
'''
按钮部分
说实话,menubar就是一个“缝合怪”,TinUI样式部分就是button2。
但是情况有变,为了给menubar提供新的样式,button2也在事件绑定等方面做出了调整,不过在这里体现不大,可以直接照抄。
def in_button(event):
self.itemconfig(outline,outline=activeline,fill=activeline)
self.itemconfig(uid+'button',fill=activefg)
def out_button(event):
self.itemconfig(back,fill=bg,outline=bg)
self.itemconfig(outline,outline=line,fill=line)
self.itemconfig(uid+'button',fill=fg)
def on_click(event):
self.itemconfig(back,fill=activebg,outline=activebg)
self.itemconfig(uid+'button',fill=activefg)
self.after(500,lambda : out_button(None))
show(event)#从menu那偷过来的
...
def disable(fg='#9d9d9d',bg='#f5f5f5'):
self.itemconfig(uid+'button',state='disable',fill=fg)
self.itemconfig(back,state='disable',disabledfill=bg)
self.itemconfig(outline,state='disable')
def active():
self.itemconfig(uid+'button',state='normal')
self.itemconfig(back,state='normal')
self.itemconfig(outline,state='normal')
out_button(None)
button=self.create_text(pos,text=text,fill=fg,font=font,anchor='nw')
uid='menubutton'+str(button)
self.itemconfig(button,tags=(uid,uid+'button'))
x1,y1,x2,y2=self.bbox(uid)
if side=='y':
self.create_text((x2+5,(y1+y2)/2),text='\uE70D',fill=fg,font='{Segoe Fluent Icons} 12',anchor='w',tags=(uid,uid+'button'))
elif side=='x':
self.create_text((x2+5,(y1+y2)/2),text='\uE76C',fill=fg,font='{Segoe Fluent Icons} 12',anchor='w',tags=(uid,uid+'button'))
...
#创建菜单
menu=self.add_menubar(uid,'<Button-1>',font=font,fg=fg,bg=bg,line=line,activefg=activefg,activebg=activebg,cont=cont,tran=tran)[0]
self.tag_unbind(uid,'<Button-1>')
#重新绑定事件
...
引用自己很久以前写的代码,但现在应该写不出来的……
按钮主体代码不是重点。
在创建完uid
后,有对于side
的判断。这里是严格的判断,只接受小写字母x和y,默认为y。
然后是菜单部分(menu=...
),没错,这次不仅是抄,而且干脆直接调用。不过需要注意,因为菜单在创建的时候会自动绑定参数给的事件,因此我们需要对这个事件(<Button-1>
)解绑。
菜单显示
然后才是重点。注意到on_click
函数的最后一行,有show(event)
,就代表我们需要重写菜单的显示方式,为什么呢?因为,我们需要菜单像滚动选择框(picker)那样在按钮周边展开,要么下方,要么在右侧。
那么问题来了,TinUI的控件是函数式创建,如何重写菜单的显示方式呢?
很简单,直接在menubar的第一个返回值,menu窗口添加一个属性wind
,记录窗口数据。
反正我是TinUI开发者,内部代码想怎么改就怎么改,毕竟这个属性又不用提供该使用TinUI的人。
menu修改代码见最新TinUI库。
通过在picker里的积累,在元素周围展现浮出窗口并不难。只是需要浅浅地判断一下side
的方向就行了。
def unshow(event):#重写菜单
menu.withdraw()
menu.unbind('<FocusOut>')
def show(event):#显示的起始位置
#初始位置
maxx,maxy,winw,winh=menu.wind.data
sx,sy=event.x_root,event.y_root
#
maxx,maxy,winw,winh=menu.wind.data
bbox=self.bbox(uid)
scx,scy=event.x_root,event.y_root#屏幕坐标
if side=='y':
dx,dy=round(self.canvasx(event.x,)-bbox[0]),round(self.canvasy(event.y)-bbox[3])#画布坐标差值
elif side=='x':
dx,dy=round(self.canvasx(event.x,)-bbox[2]),round(self.canvasy(event.y)-bbox[1])#画布坐标差值
sx,sy=scx-dx,scy-dy
#...
完整代码函数
def add_menubutton(self,pos:tuple,text:str,side='y',fg='#1b1b1b',bg='#fbfbfb',line='#CCCCCC',linew=1,activefg='#5d5d5d',activebg='#f5f5f5',activeline='#e5e5e5',font=('微软雅黑',12),cont=(('command',print),'-'),tran='#01FF11'):#绘制按钮展开菜单
#Segoe Fluent Icons x右侧展开\uE76B \uE76C,y下方展开\uE70D \uE70E,默认y
def in_button(event):
self.itemconfig(outline,outline=activeline,fill=activeline)
self.itemconfig(uid+'button',fill=activefg)
def out_button(event):
self.itemconfig(back,fill=bg,outline=bg)
self.itemconfig(outline,outline=line,fill=line)
self.itemconfig(uid+'button',fill=fg)
def on_click(event):
self.itemconfig(back,fill=activebg,outline=activebg)
self.itemconfig(uid+'button',fill=activefg)
self.after(500,lambda : out_button(None))
show(event)#从menu那偷过来的
def unshow(event):#重写菜单
menu.withdraw()
menu.unbind('<FocusOut>')
def show(event):#显示的起始位置
#初始位置
maxx,maxy,winw,winh=menu.wind.data
sx,sy=event.x_root,event.y_root
#
maxx,maxy,winw,winh=menu.wind.data
bbox=self.bbox(uid)
scx,scy=event.x_root,event.y_root#屏幕坐标
if side=='y':
dx,dy=round(self.canvasx(event.x,)-bbox[0]),round(self.canvasy(event.y)-bbox[3])#画布坐标差值
elif side=='x':
dx,dy=round(self.canvasx(event.x,)-bbox[2]),round(self.canvasy(event.y)-bbox[1])#画布坐标差值
sx,sy=scx-dx,scy-dy
#
if sx+winw>maxx:
x=sx-winw
else:
x=sx
if sy+winh>maxy:
y=sy-winh
else:
y=sy
menu.geometry(f'{winw+15}x{winh+15}+{x}+{y}')
menu.attributes('-alpha',0)
menu.deiconify()
menu.focus_set()
for i in [0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1]:
menu.attributes('-alpha',i)
menu.update()
time.sleep(0.05)
menu.bind('<FocusOut>',unshow)
def disable(fg='#9d9d9d',bg='#f5f5f5'):
self.itemconfig(uid+'button',state='disable',fill=fg)
self.itemconfig(back,state='disable',disabledfill=bg)
self.itemconfig(outline,state='disable')
def active():
self.itemconfig(uid+'button',state='normal')
self.itemconfig(back,state='normal')
self.itemconfig(outline,state='normal')
out_button(None)
button=self.create_text(pos,text=text,fill=fg,font=font,anchor='nw')
uid='menubutton'+str(button)
self.itemconfig(button,tags=(uid,uid+'button'))
x1,y1,x2,y2=self.bbox(uid)
if side=='y':
self.create_text((x2+5,(y1+y2)/2),text='\uE70D',fill=fg,font='{Segoe Fluent Icons} 12',anchor='w',tags=(uid,uid+'button'))
elif side=='x':
self.create_text((x2+5,(y1+y2)/2),text='\uE76C',fill=fg,font='{Segoe Fluent Icons} 12',anchor='w',tags=(uid,uid+'button'))
x1,y1,x2,y2=self.bbox(uid+'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)
#创建菜单
menu=self.add_menubar(uid,'<Button-1>',font=font,fg=fg,bg=bg,line=line,activefg=activefg,activebg=activebg,cont=cont,tran=tran)[0]
self.tag_unbind(uid,'<Button-1>')
#重新绑定事件
self.tag_bind(uid+'button','<Button-1>',on_click)
self.tag_bind(uid+'button','<Enter>',in_button)
self.tag_bind(uid+'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.tag_bind(outline,'<Button-1>',on_click)
self.tag_bind(outline,'<Enter>',in_button)
self.tag_bind(outline,'<Leave>',out_button)
self.tkraise(uid+'button')
funcs=FuncList(2)
funcs.disable=disable
funcs.active=active
return uid+'button',back,outline,funcs,uid
效果
测试代码
b.add_menubutton((1500,50),'menubutton',cont=(('command',print),('menu',test1),'-',('TinUI文本移动',test)))
最终效果
github项目
TinUI的github项目地址
pip下载
pip install tinui
结语
后续,menubutton或许会和menubar混合联用。
🔆tkinter创新🔆