现在,很多界面库,比如wpf、winui都在使用xml(xaml)作为一种界面载入的重要实现方式。使用XML作为界面编写语言,有很多优点:
在tkinter的诸多拓展中,也有一些使用xml作为布局依据的,但我忘了其名称。但是,这些tkinter的xml载入功能都是从逻辑方面封装了tkinter的原生布局模式:pack
, grid
, place
,所以,这些很难借鉴到TinUI中。当然这很好,只不过相比较而言tkinter使用Python代码编写布局就够了,无论是手动设计界面还是使用可视化设计器,生成的Python布局代码完全可以直接使用。
接着上面所说,以往的TinUI,因为基于tkinter画布,所以只能够通过编写者输入坐标参数来确定组件绘制的原点。这种布局方式相当于tkinter原生中的place
,可是place
布局可以使用百分制(相对)坐标作为参数,可是因为画布范围的不确定性,只能够使用确定的坐标。
在TinUI中使用确定坐标布局,就会带来一些麻烦:
别看只有这两条,看看TinUI源码中的示例:
b=TinUI(a,bg='white')
b.pack(fill='both',expand=True)
m=b.add_title((600,0),'TinUI is a test project for futher tin using')
m1=b.add_title((0,680),'test TinUI scrolled',size=2,angle=24)
b.add_paragraph((20,290),''' TinUI是基于tkinter画布开发的界面UI布局方案,作为tkinter拓展和TinEngine的拓展而存在。目前,TinUI已可应用于项目。''',
angle=-18)
b.add_paragraph((20,100),'下面的段落是测试画布的非平行字体显示效果,也是TinUI的简单介绍')
b.add_button((250,450),'测试按钮',activefg='white',activebg='red',command=test,anchor='center')
b.add_checkbutton((80,430),'允许TinUI测试',command=test1)
b.add_label((10,220),'这是由画布TinUI绘制的Label组件')
b.add_entry((250,330),350,'这里用来输入')
b.add_separate((20,200),600)
b.add_radiobutton((50,480),300,'sky is blue, water is blue, too. So, what is your heart',('red','blue','black'),command=test1)
b.add_link((400,500),'TinGroup知识库','http://tinhome.baklib-free.com/')
_,ok1,_=b.add_waitbar1((500,220),bg='#CCCCCC')
b.add_button((500,270),'停止等待动画',activefg='cyan',activebg='black',command=test2)
bu1=b.add_button((700,200),'停止点状滚动条',activefg='white',activebg='black',command=test3)[1]
bu2=b.add_button((700,250),'nothing button 2')[1]
bu3=b.add_button((700,300),'nothing button 3')[1]
b.add_labelframe((bu1,bu2,bu3),'box buttons')
_,_,ok2,_=b.add_waitbar2((600,400))
b.add_combobox((600,550),text='你有多大可能去珠穆朗玛峰',content=('20%','40%','60%','80%','100%','1000%'))
b.add_button((600,480),text='测试进度条(无事件版本)',command=test4)
_,_,_,progressgoto,_,_=b.add_progressbar((600,510))
b.add_table((180,630),data=(('a','space fans over the\nworld','c'),('you\ncan','2','3'),('I','II','have a dream, then try your best to get it!')))
b.add_paragraph((300,850),text='上面是一个表格')
b.add_onoff((600,100))
b.add_spinbox((680,100))
b.add_scalebar((680,50),command=test5)
scale_text,_=b.add_label((890,50),text='当前选值:2')
b.add_info((680,140),info_text='this is info widget in TinUI')
mtb=b.add_paragraph((0,720),'测试菜单(右键单击)')
b.add_menubar(mtb,cont=(('command',print),('menu',test1),'-',('TinUI文本移动',test)))
一旦改起来,十分之难受。
作为TinUI的主要开发者,我自己在写示例的时候(也就是\test
中的文件),都需要从以前写好的布局代码中copy过来,再进行删改。可想而知,我自己是由多么难受。
综上,TinUI迫切需要能够使用XML进行布局的方式。
这必然是可以的,在TinUI-2.6.0-版本中,我加入了TinUIXml
类,允许使用xml进行布局。但是写到这一千多字了,关于TinUIXml
的开发之后再说,本文只讲述使用xml编写TinUI界面的具体方式。
我不知道其它库实现的原理如何,TinUI的原理很好解释,这不是重点,放到以后再说。
使用类:
x=TinUIXml(ui:Union[BasicTinUI,TinUI])
'''
ui::TinUI类 或 BasicTinUI类
'''
编写这篇文章时可用的函数:
TinUIXml.loadxml(xml:str)#载入xml字符串,渲染界面
TinUIXml.clean()#清空界面
看上去很简单是吧,但是还大可讲解,不妨请接着往下看。
相比于使用TinUI自带的绝对坐标布局,xml给人带来的感觉是更加简便、易维护,并且还可以使一个程序的界面设计和逻辑代码进行一个分离。即使是像我这种又要写Python后端又要完成UI显示的学生,使用TinUIXml
来进行设计也让我“神清气爽”。
很多人感觉使用xml脱离了一种语言的特性,但是有些感受确实是要切身体会。
不用多么复杂,就用下图,很简单的窗口:
首先我们看到了这是一个全按钮的界面,其中三个按钮在第一行,显示“two2”的按钮在第二行。
请先允许我这么分析,使用xml编写TinUI界面时不能如此分析。两个“two”按钮实际上是一个整体布局,但是绝对布局可以忽略这些。
以下代码中,u
为TinUI。
第一次尝试
随便按照脑中所想,输入绝对坐标:
u.add_button((5,5),text='one')
u.add_button((85,5),text='two1')
u.add_button((85,40),text='two2')
u.add_button((170,5),text='three')
额,无论上下间隔,都隔得有点开。
第二次尝试
缩小间距:
u.add_button((5,5),text='one')
u.add_button((45,5),text='two1')
u.add_button((45,30),text='two2')
u.add_button((90,5),text='three')
好像,有点挤……
第三次尝试
综合考虑,应该是这样:
u.add_button((5,5),text='one')
u.add_button((55,5),text='two1')
u.add_button((55,40),text='two2')
u.add_button((110,5),text='three')
终于,有模有样了。
但是,使用TinUIXml布局的我此刻不屑地呲了一下。
首先分析一下:这个界面实际上只有一行,只不过第二块纵行有两个按钮。
关于我为什么这么分析,还要看下一个小节。
编写xml:
<tinui>
<line>
<button text='one'>button>
<line>
<button text='two1'>button>
line>
<line>
<button text='two2'>button>
line>
<button text='three'>button>
line>
tinui>
问:使用绝对布局就进行了三次尝试,有什么大不了的吗?
答:确实,这样的一个简单界面试几次就可以了,但不说特别复杂,就如下这个界面。
答:你试试?
问:……
答:我只需要一个ui.xml
:
<tinui>
<line x='512'>
<label text='输入一个成语开始游戏:' font='宋体 48' outline='#f0f0f0' anchor='n'>tlabellabel>
line>
<line x='10'>
<entry width='940' fg='blue' activebg='powderblue' font='微软雅黑 64'>entryentry>
line>
<line x='512'>
<button text='确认成语' font='宋体 32' fg='cyan' command='self.funcs["findword"]' anchor='n'>button>
line>
<line x='512'>
<button text='失败退出' font='宋体 32' fg='red' command='self.funcs["closewindow"]' anchor='n'>button>
line>
<line x='70'>
<button text='关于编写' font='宋体 18' command='self.funcs["gybx"]'>button>
<button text='版本说明' font='宋体 18' command='self.funcs["bbsm"]'>button>
<button text='词库信息' font='宋体 18' command='self.funcs["ckxx"]'>button>
<button text='词库输入' font='宋体 18' command='self.funcs["cksr"]'>cy-inputbutton>
<button text='成语查询' font='宋体 18' command='self.funcs["check_web"]'>cy-webbutton>
<button text='检测帮助' font='宋体 18' command='self.funcs["find_chengyu"]'>cy-helpbutton>
<button text='添加词库' font='宋体 18' command='self.funcs["find_newciku"]'>button>
<button text='开源项目' font='宋体 18' command='self.funcs["find_kaiyuan"]'>button>
line>
tinui>
答:so easy~
问:Oh, I see!
看了那么多使用感受,是不是已经迫不及待使用xml来给TinUI编写界面呢?使用起来确实是一个字:爽,但是无规矩不成方圆。
接下来讲讲使用xml给TinUI写界面的规矩。
因为TinUI中一些组件绘制的特殊性,以下组件不支持使用xml布局:
具体不可使用xml的组件见TinUI源码中TinUIXml.noload
的值。
self.funcs={}
用于存放xml中涉及到的函数,通常使用在command
参数中。
xml中可以使用写法:
command='self.funcs["func_name"]'
表示。在Python中用:
x.funcs["func_name"]=your_function
定义。注意xml中使用self,Python中使用变量名。
这个性质可以使界面设计不受逻辑代码更改影响。
self.datas={}
用于存放xml中涉及到的数据等结构类型,比如宽度、尺寸、列表内容集合、选值区域或选值内容等。
在xml和Python中的使用同【funcs】。
self.tags={}
存放内部组件tag集合,用于有目标文本的xml-TinUI组件元素的回调。
如xml中
,
Python中可以使用
button1=x.tags['bu']
获取原组件返回值。
使用TinUIXml中的xml布局,要满足如下规定:
根元素必须是
行元素必须是
根元素的直接子集不能有除了行元素的其它元素
行元素可以嵌套。这个内容稍后会详细讲解
所有xml使用的函数,需要表述为self.funcs[...]
,注意是self
若需要,如【5】中类似地使用self.datas[...]
若需要使用整数定义宽度参数等,也如同width='200'
使用
字体使用如font="微软雅黑 12"
的写法
看下面的图示和解析:
使用
作为直接根元素,否则TinUIXml是不会进行解析的。
直接子元素是直属于
下的第一节点,只能够由若干个
元素组成,表示最终渲染中TinUI界面的每一行。
使用
作为行元素,表示该区域内容的一行内容。
注意,是该区域,不是整个界面。
完整写法:
。表示该行的起始位置,默认为自动接至上一个渲染组件的位置。比如上几张图中成语接龙的输入框等组件的使用。
一般只需要写
即可。
行元素的含义请看下一个小节。
TinUIXml允许使用xml绘制的所有组件。
使用
表示组件元素,其中,widget_name
为TinUI所有add_xxx
函数中的xxx
,表示组件名称。
组件元素的含义请看下一个小节。
为了给逻辑代码提供组件控制接口,TinUIXml设计了self.tags
作为存放这些值的专用字典,这些字典可被Python直接调用。
组件元素的文本内容表示了该组件被创造时所有的返回值对应的键值,该键值也就包含了各组件的所有返回值。
比如中,键值“two1”包括了组成按钮的所有画布对象、可用函数列表以及组件uid。在Python中,使用
bt1=x.tags['two1']
即可获取所有返回值。
在一个行元素中嵌入了一个或若干个新
元素,这些行元素代表着父行元素下一个纵行的一行渲染范围。
行元素是可以继续嵌套的。
嵌套行元素的含义请看下一个小节。
以上是在单个行元素中的分析,在根目录下任意行元素都如此。
为了实现真正的前后端分离,可以参考以下内容来设计程序编写区域或模块。
我们以成语接龙界面作为例子。
在使用xml之前,需要定义一系列的funcs、datas,也就是从逻辑代码中获取界面展示所需要的所有信息。
这一步,我称之为接口输入。
xui.funcs['findword']=findword
xui.funcs['closewindow']=closewindow
xui.funcs['gybx']=gybx
xui.funcs['bbsm']=bbsm
xui.funcs['ckxx']=ckxx
xui.funcs['cksr']=cksr
xui.funcs['check_web']=check_web
xui.funcs['find_chengyu']=find_chengyu
xui.funcs['find_newciku']=find_newciku
xui.funcs['find_kaiyuan']=find_kaiyuan
可能有人认为,如果写一个复杂界面,那么这一部分的代码实在是太多了。
确实如此,但是,如果使用一个统一的导入方法,则会遇到以下问题:
最有直接影响的就是第三点,按照上面的代码,只需要更改源码文件。
但是尽管如此,我还是提供了方法。在2022-8-23更新中,我给TinUIXml添加了environment
方法。
xui.environment(globals())
一键导入。
不过还是那句话,这样写复杂了可能会出事。可以但不是非常建议使用。
而且,就算是复杂界面,使用TinUIXml编辑器(下面第五个项目)也能自动生成IDO部分代码。
在完成所有前期准备时,开始载入界面。
我称之为载入UI。
xui.loadxml(xml)
x1,y1,x2,y2=mainu.bbox('all')
mainu['width']=x2-x1+50
mainu['height']=y2-y1
此刻,我们已经完成界面渲染了,需要为逻辑代码转出变量。
我称之为输出接口。
MyEntry=xui.tags['entry'][0]
vlabel=xui.tags['tlabel'][0]
cy_input=xui.tags['cy-input'][2]
cy_web=xui.tags['cy-web'][2]
cy_help=xui.tags['cy-help'][2]
至此,我们已经熟悉了TinUIXml的使用规则,可以自行设计界面了!!!
但是,如果你还想深入了解TinUIXml中的各种概念,已经摸清行元素和纵块直接到底有何种关系,请耐住性子接着看。
首次提到了“纵块”概念,请听我慢慢道来。
首先引入下面这张示意图:
我们将用这张图来介绍TinUIXml的相关概念。
这两个基础概念贯穿了TinUIXml的xml写法。
行元素是TinUIXml中的具体元素,其代表了在该区域的某一行内容。
行元素横向方向对同级和父子级渲染坐标有影响。
比如,第一行
,就是在整个TinUI界面下的第一行;第二行
亦如此。第二级第一行,是属于第一行范围内的第一行;第二级第二行亦如此。
纵块是TinUIXml中的抽象元素,其代表了一行中的一个元素整体。
纵块横向方向对父子级渲染坐标无影响。
比如第一行
中,button和entry分别是第一、第二纵块,而其中的第一个和第二个子行元素
为第三纵块。
TinUIXml为纵向布局,所以不考虑纵向坐标
很多人看到右侧的渲染图不解,为什么右上角的四个组件统称为第三纵块,而不是分为第三和第四纵块呢?这就和上面提到纵块的定义有关了。
纵块在TinUIXml中是抽象元素,其横向方向只会影响到同行的组件元素,与下方另一行的组件坐标没有任何关系。也就是说,一个
或若干个
相连,只代表一个纵块,因为在这些行里面,每一行的子纵块与另一行的纵块没有半毛钱关系。
接着,再根据字行元素的顺序,确定该纵块属于第几行。这一纵块的横向坐标终点为该纵块中所有子行元素最大的横向坐标。
但是,当我们想让两个相连的行元素为不同的纵块时该怎么办呢?没关系,使用TinUI自带的back组件,此时忽略uids
参数即可,如下写法:
...
<line>
...
line>
<back>back>
<line>
...
line>
...
TinUIXml忽略的起始坐标有两种:
这里主要说明第二种:在子行元素中,起始坐标可能会被忽略。
编写者可以通过使用多次loadxml(xml)
来完成组件绘制。
在TinUIXml类中,全局根坐标使用属性self.xendx
等定义,因此无法在一个TinUIXml类中使用多线程在TinUI载入组件,这会到这布局混乱。
但是,TinUI是可以使用多线程的。TinUIXml的初始化与TinUI无关,因此编写者可以为一个TinUI绑定多个TinUIXml,在每一个线程中为TinUI绘制所需组件。
在TinUI所有组件中,部分组件在xml中的写法有着特殊的规定。
在TinUI中,back组件属于一种辅助组件,其有以下两种用途。
在back的参数中,有必选项pos:tuple
和定义项uids:tuple=()
。虽然pos
是必选项,但是当参数中存在uids
时,TinUI会忽略pos
参数,转而使用已经被规定的画布对象元组。这里特别强调在TinUIXml下的uids参数。
虽然在Python代码中,需要使用x.tags['name']
来导出组件返回值,而且在xml文件中需要使用类似语法使用接口输入后的函数和特殊类型数据,但是TinUIXml为back组件做了特殊化处理。也就是说,在xml文件中,uids
参数里不需要使用导出语法。
比如:
...
<button>b1button>
<button>b2button>
<back uids='("b1","b2")'>back>
...
其中uids
的参数仍然为元组形式,但是只需要写明包含组件返回值在self.tags
中的键名就可以了。总结有以下注意要点:
需要写在包含组件的后面self.tags
中的键名uids
参数为元组形式的字符串,并且名称需要用引号包裹使用同
,其中的widgets
参数使用逻辑与
的uids
一样。
在增加对该组件xml解析渲染的更新中,为该组件添加了pos
参数,但是这个是无用标识参数,只是为了能够让TinUIXml渲染格式更加统一。
感谢观看到此,开始使用xml为TinUI布局吧。
以下是一些实战小项目:
TinUI的github项目地址
pip install tinui
tkinter创新