引言
TinUI的又一个规模型控件,而且标签栏视图在应用中很常见:便笺、浏览器、设置界面、分类工作栏等等。所以,一旦TinUI加入了标签栏视图,TinUI的实用性将更上一层楼。
当然,TinUI早就可以进行实用开发了。
为什么说它是规模型控件呢?因为它真的很复杂。
目前,TinUI实现控件代码量最大的是滚动条(scrollbar),但是,(剧透)notebook标签栏视图的代码量跟滚动条差不多。不同的是,滚动条只是在一个小小的地方进行诸多复杂的操作,而notebook这是一个“复杂”的广控制范围控件。因此,notebook控件也是我目前唯一一个打草稿后才编写的TinUI控件。
草稿就不放了,还是往下看看吧。
布局
函数结构
def add_notebook(self,pos:tuple,width:int=400,height:int=400,color='#f9f9fc',fg='#1a1a1a',bg='#f3f3f3',activefg='#595959',activebg='#ededed',onfg='#191919',onbg='#eaeaea'):
'''
pos::位置
width::页面宽度
height::页面高度。注意,都是页面,不是控件
color::大背景颜色
fg::文本、滚动条颜色
bg::标签颜色
activefg::鼠标进入时提示色
activebg::鼠标进入时提示色
onfg::鼠标点击时提示色
onbg::鼠标点击时提示色
'''
创建主体元素
这一部分和其它控件的操作是一样的,毕竟我们的重点在逻辑部分的渲染和操作。这里直接给代码:
tbu=BasicTinUI(self,bg=color)
tbuid=self.create_window((pos[0]+2,pos[1]+2),window=tbu,width=width,height=30,anchor='nw')
uid='notebook'+str(tbuid)
self.addtag_withtag(uid,tbuid)
scro=self.add_scrollbar((pos[0]+5,pos[1]+32),tbu,height=width-5,direction='x',bg=bg,color=fg,oncolor=onfg)
self.addtag_withtag(uid,scro[-1])
barheight=self.bbox(scro[-1])[3]
backpos=(pos[0]+5,pos[1]+3,pos[0]+width+2,pos[1]+3,pos[0]+width+2,barheight+height-3,pos[0]+5,barheight+height-3,pos[0]+5,pos[1]+5)
back=self.create_polygon(backpos,outline=color,fill=color,width=10,tags=uid)
self.tkraise(tbuid)
self.tkraise(scro[-1])
viewpos=(pos[0]+2,barheight+2)
添加关键变量
这就涉及到逻辑操作了,首先我们需要明白有以下变量是很关键的:
-
nowpage 当前显示的页面标识符 -
vdict 页面元素字典 -
tbdict 标签元素字典 -
flaglist 标识符列表
这里说到了标识符,解释一下。就是这个标识符对应了相应的所有标签和页面元素。一般由编写者自行决定,也可以由TinUI自动决定。稍后会谈到。
nowpage=''
vdict=dict()
tbdict=dict()
flaglist=list()
font='微软雅黑 12'
样式操作
老生常谈:
def __onenter(flag):
if flag==nowpage:
return
t,c,b=tbdict[flag]
tbu.itemconfig(t,fill=activefg)
tbu.itemconfig(c,fill=activefg)
tbu.itemconfig(b,fill=activebg,outline=activebg)
def __onleave(flag):
if flag==nowpage:
return
t,c,b=tbdict[flag]
tbu.itemconfig(t,fill=fg)
tbu.itemconfig(c,fill=fg)
tbu.itemconfig(b,fill=bg,outline=bg)
def __onclick(flag):
if flag==nowpage:
return
t,c,b=tbdict[flag]
tbu.itemconfig(t,fill=onfg)
tbu.itemconfig(c,fill=onfg)
tbu.itemconfig(b,fill=onbg,outline=onbg)
添加页面
这里指的是单纯地往notebook添加一个页面,不是显示。
先来在标签栏里创建一个标签。
def addpage(title:str,flag=None,scrollbar=False):
if tbu.bbox('all')==None:
endx=3
else:
endx=tbu.bbox('all')[2]+3
titleu=tbu.create_text((endx,2),text=title,fill=fg,font=font,anchor='nw')
cbx=tbu.bbox(titleu)[2]+10
cb=tbu.create_text((cbx,2),text='×',font=font,fill=fg,anchor='nw')
tbbbox=tbu.bbox(titleu)
bux=(endx+2,tbbbox[1],cbx+15,tbbbox[1],cbx+15,tbbbox[3],endx+2,tbbbox[3],endx+2,tbbbox[1])
bu=tbu.create_polygon(bux,fill=bg,outline=bg,width=5)
tbu.lower(bu)
上面提到的标识符,就是这里的flag 参数,这个参数对于后续编写者对这些页面的控制是至关重要的,因此需要做到唯一。如果编写者已经给定了,那就是机器不违背人的意志,随TA,但是,如果需要TinUI自己决定呢?(ta比较懒)
if flag==None:
flag='flag'+str(titleu)
很简单。
接下来随便创建页面:
if scrollbar:
page=TinUI(self,True,bg=self['background'])
elif scrollbar==False:
page=BasicTinUI(self,bg=self['background'])
uiid=self.create_window(viewpos,window=page,width=width,height=height,anchor='nw',state='hidden')
uixml=TinUIXml(page)
bbox=tbu.bbox('all')
tbu.config(scrollregion=bbox)
vdict[flag]=(page,uixml,uiid)
tbdict[flag]=(titleu,cb,bu)
flaglist.append(flag)
tbu.tag_bind(titleu,'<Enter>',lambda event:__onenter(flag))
tbu.tag_bind(cb,'<Enter>',lambda event:__onenter(flag))
tbu.tag_bind(bu,'<Enter>',lambda event:__onenter(flag))
tbu.tag_bind(titleu,'<Leave>',lambda event:__onleave(flag))
tbu.tag_bind(cb,'<Leave>',lambda event:__onleave(flag))
tbu.tag_bind(bu,'<Leave>',lambda event:__onleave(flag))
tbu.tag_bind(titleu,'<Button-1>',lambda event:showpage(flag))
tbu.tag_bind(bu,'<Button-1>',lambda event:showpage(flag))
tbu.tag_bind(cb,'<Button-1>',lambda event:deletepage(flag))
显示页面
这分为两个步骤:恢复当前显示页面,显示指定页面。
值得注意的是__onclick 的逻辑。我们不会对nowpage 的页面进行操作,所以我们需要一个中间来替代当前显示页面的标识符,作为一个过渡。
def showpage(flag):
nonlocal nowpage
mp=nowpage
if nowpage!='':
self.itemconfig(vdict[nowpage][2],state='hidden')
nowpage=flag
__onleave(mp)
nowpage=mp
先释放当前显示页面,再重新恢复原来的显示页面标识符,等页面转化完成后,再彻底更改当前显示页面标识符。
上面所说的过程有点绕,但是事实确实是这样。以后会简化逻辑,但是现在就这么用吧。
(懒,等待issue)
self.itemconfig(vdict[flag][2],state='normal')
__onclick(flag)
nowpage=flag
删除页面
这个也很重要,因为页面可以由编写者关闭,也可以有使用者关闭。
以后会添加是否可删除该页面的创建选项。
UI上删除标签和页面:
def deletepage(flag):
nonlocal nowpage
wbbox=tbu.bbox(tbdict[flag][2])
w=wbbox[2]-wbbox[0]
w+=1
for i in tbdict[flag]:
tbu.delete(i)
self.delete(vdict[flag][2])
vdict[flag][0].destroy()
等标签被删除了,我们就应该将后面的标签往前移一点来补位:
index=flaglist.index(flag)
if index+1==len(tbdict):
pass
else:
for i in flaglist[index+1:]:
for iid in tbdict[i]:
tbu.move(iid,-w,0)
页面删除后,我们不可能干巴巴地啥也不显示,除非那是一个唯一界面。
页面删除后,会自动显示下一个页面,如果没有,则显示前面一个页面。
不得不说,字典中使用列表检索有点小复杂,所有后来加了flaglist 变量。
if flag==nowpage:
if len(tbdict)==1:
pass
nowpage=''
elif index+1<len(tbdict):
showpage(flaglist[index+1])
nowpage=flaglist[index+1]
elif index+1==len(tbdict):
showpage(flaglist[index-1])
nowpage=flaglist[index-1]
逻辑上删除页面和标签:
del tbdict[flag]
del vdict[flag]
del flaglist[index]
bbox=tbu.bbox('all')
tbu.config(scrollregion=bbox)
一些其它函数
这些都是基本的获取类函数,凭借页面标识符获取。
def getuis(flag):
return vdict[flag]
def gettitles(flag):
return tbdict[flag]
def getvdict():
return vdict
def gettbdict():
return tbdict
创建函数结构体
这是为了方便编写者使用。为什么不像之前创建一个函数列表呢?因为notebook的主体功能不在创建时产生,而是运行时产生。
notebook=TinUINum
notebook.addpage=addpage
notebook.showpage=showpage
notebook.deletepage=deletepage
notebook.getuis=getuis
notebook.gettitles=gettitles
notebook.getvdict=getvdict
notebook.gettbdict=gettbdict
完整代码函数
def add_notebook(self,pos:tuple,width:int=400,height:int=400,color='#f9f9fc',fg='#1a1a1a',bg='#f3f3f3',activefg='#595959',activebg='#ededed',onfg='#191919',onbg='#eaeaea'):
def __onenter(flag):
if flag==nowpage:
return
t,c,b=tbdict[flag]
tbu.itemconfig(t,fill=activefg)
tbu.itemconfig(c,fill=activefg)
tbu.itemconfig(b,fill=activebg,outline=activebg)
def __onleave(flag):
if flag==nowpage:
return
t,c,b=tbdict[flag]
tbu.itemconfig(t,fill=fg)
tbu.itemconfig(c,fill=fg)
tbu.itemconfig(b,fill=bg,outline=bg)
def __onclick(flag):
if flag==nowpage:
return
t,c,b=tbdict[flag]
tbu.itemconfig(t,fill=onfg)
tbu.itemconfig(c,fill=onfg)
tbu.itemconfig(b,fill=onbg,outline=onbg)
def addpage(title:str,flag=None,scrollbar=False):
if tbu.bbox('all')==None:
endx=3
else:
endx=tbu.bbox('all')[2]+3
titleu=tbu.create_text((endx,2),text=title,fill=fg,font=font,anchor='nw')
cbx=tbu.bbox(titleu)[2]+10
cb=tbu.create_text((cbx,2),text='×',font=font,fill=fg,anchor='nw')
tbbbox=tbu.bbox(titleu)
bux=(endx+2,tbbbox[1],cbx+15,tbbbox[1],cbx+15,tbbbox[3],endx+2,tbbbox[3],endx+2,tbbbox[1])
bu=tbu.create_polygon(bux,fill=bg,outline=bg,width=5)
tbu.lower(bu)
if flag==None:
flag='flag'+str(titleu)
if scrollbar:
page=TinUI(self,True,bg=self['background'])
elif scrollbar==False:
page=BasicTinUI(self,bg=self['background'])
uiid=self.create_window(viewpos,window=page,width=width,height=height,anchor='nw',state='hidden')
uixml=TinUIXml(page)
bbox=tbu.bbox('all')
tbu.config(scrollregion=bbox)
vdict[flag]=(page,uixml,uiid)
tbdict[flag]=(titleu,cb,bu)
flaglist.append(flag)
tbu.tag_bind(titleu,'<Enter>',lambda event:__onenter(flag))
tbu.tag_bind(cb,'<Enter>',lambda event:__onenter(flag))
tbu.tag_bind(bu,'<Enter>',lambda event:__onenter(flag))
tbu.tag_bind(titleu,'<Leave>',lambda event:__onleave(flag))
tbu.tag_bind(cb,'<Leave>',lambda event:__onleave(flag))
tbu.tag_bind(bu,'<Leave>',lambda event:__onleave(flag))
tbu.tag_bind(titleu,'<Button-1>',lambda event:showpage(flag))
tbu.tag_bind(bu,'<Button-1>',lambda event:showpage(flag))
tbu.tag_bind(cb,'<Button-1>',lambda event:deletepage(flag))
def showpage(flag):
nonlocal nowpage
mp=nowpage
if nowpage!='':
self.itemconfig(vdict[nowpage][2],state='hidden')
nowpage=flag
__onleave(mp)
nowpage=mp
self.itemconfig(vdict[flag][2],state='normal')
__onclick(flag)
nowpage=flag
def deletepage(flag):
nonlocal nowpage
wbbox=tbu.bbox(tbdict[flag][2])
w=wbbox[2]-wbbox[0]
w+=1
for i in tbdict[flag]:
tbu.delete(i)
self.delete(vdict[flag][2])
vdict[flag][0].destroy()
index=flaglist.index(flag)
if index+1==len(tbdict):
pass
else:
for i in flaglist[index+1:]:
for iid in tbdict[i]:
tbu.move(iid,-w,0)
if flag==nowpage:
if len(tbdict)==1:
pass
nowpage=''
elif index+1<len(tbdict):
showpage(flaglist[index+1])
nowpage=flaglist[index+1]
elif index+1==len(tbdict):
showpage(flaglist[index-1])
nowpage=flaglist[index-1]
del tbdict[flag]
del vdict[flag]
del flaglist[index]
bbox=tbu.bbox('all')
tbu.config(scrollregion=bbox)
def getuis(flag):
return vdict[flag]
def gettitles(flag):
return tbdict[flag]
def getvdict():
return vdict
def gettbdict():
return tbdict
tbu=BasicTinUI(self,bg=color)
tbuid=self.create_window((pos[0]+2,pos[1]+2),window=tbu,width=width,height=30,anchor='nw')
uid='notebook'+str(tbuid)
self.addtag_withtag(uid,tbuid)
scro=self.add_scrollbar((pos[0]+5,pos[1]+32),tbu,height=width-5,direction='x',bg=bg,color=fg,oncolor=onfg)
self.addtag_withtag(uid,scro[-1])
barheight=self.bbox(scro[-1])[3]
backpos=(pos[0]+5,pos[1]+3,pos[0]+width+2,pos[1]+3,pos[0]+width+2,barheight+height-3,pos[0]+5,barheight+height-3,pos[0]+5,pos[1]+5)
back=self.create_polygon(backpos,outline=color,fill=color,width=10,tags=uid)
self.tkraise(tbuid)
self.tkraise(scro[-1])
viewpos=(pos[0]+2,barheight+2)
nowpage=''
vdict=dict()
tbdict=dict()
flaglist=list()
font='微软雅黑 12'
notebook=TinUINum
notebook.addpage=addpage
notebook.showpage=showpage
notebook.deletepage=deletepage
notebook.getuis=getuis
notebook.gettitles=gettitles
notebook.getvdict=getvdict
notebook.gettbdict=gettbdict
return tbu,scro,back,notebook,uid
效果
测试代码
def test7():
ntvdict=ntb.getvdict()
num=1
for i in ntvdict:
uxml=ntvdict[i][1]
xml=f'''
<tinui><line><button text='这是第{num}个BasicTinUI组件' command='print'></button></line>
<line><label text='TinUI的标签栏视图'></label><label text='每个都是单独页面'></label></line>
</tinui>'''
uxml.loadxml(xml)
num+=1
if __name__=='__main__':
a=Tk()
a.geometry('700x700+5+5')
b=TinUI(a,bg='white')
b.pack(fill='both',expand=True)
ntb=b.add_notebook((800,900))[-2]
for i in range(1,11):
ntb.addpage('test'+str(i),'t'+str(i))
test7()
a.mainloop()
最终效果
细看,标签栏的标签是圆角哦。
github项目
TinUI的github项目地址
pip下载
pip install tinui
结语
TinUI完成了所有基本组件的加入,现在可以替代tkinter原生窗口了,同时搭配TinUIXml使用更加方便!
🔆tkinter创新🔆
|