当前位置:   article > 正文

tkinter绘制组件(10)——组合框/下拉框_tk 下拉框

tk 下拉框

引言

组合框?你一个绘制类型的TinUI框架,也想通过整合其它的个体组件来创造一个复合组件?

好吧,TinUI是不会尝试绘制可以gird或pack布局的组件的(因为tkinter画布的限制)。本篇文章想要讲的组件,是combobox

combobox是“组合框”英译名称,而在Windows操作系统中,“combobox”有下拉框、下拉列表的含义,通常作为想给出既定选项,又不希望像单选框一样占用的一定的位置而与用户交互的组件。这次,TinUI将实现该组件。

(剧透) TinUI实现combobox挺费劲的,是TinUI目前最复杂的个体组件。


构思

因为下拉框使用画布绘制比较复杂,先来想一下该如何实现下拉框。

样式与功能(初次构想)

基本与系统下拉框对齐(除不能够直接输入外),要基本实现以下部分:

  1. 初始化时有一个文本显示框和下拉提示框
  2. 文本显示框中应该有初始文字
  3. 下拉提示框响应鼠标单击动作,其文本型提示应该与下拉框的“闭合”、“展开”状态相符合
  4. 下拉提示框响应鼠标进入与离开动作
  5. 展开下拉框后,应出现只能够单选若干选项
  6. 各选项响应鼠标进入与离开动作
  7. 各选项响应鼠标单击动作,文本显示框显示选中文本,并将选中文本作为参数回调指定函数
  8. ……

额- - -,好像要求有点高,但是远不止这些。还有:各选项维持选定状态、下拉框响应焦点状态、文本显示框的动态更新……

先不管了,一个组件的绘制要求不能这么严格,应该从基础开始,我们就先实现这些功能……

但,还是好复杂啊…(⊙_⊙;)…(引出下文)

样式与功能(二次构想)

我认为,因为TinUI已经有了一定的规模,我们应该尝试运用以往的经验和技巧来简化下拉框的绘制。这样,才能够让之前的只是运用到我们现在要解决的问题。

combobox的整体结构我们是改不了的,但是下拉提示框可以使用add_button替代,并且实例化其中的按钮文本和按钮背景。

下拉框的响应动作还是得自己写。

展开状态的选项列表可用借鉴add_radiobutton的相关代码。

以下,就是绘制下拉框的新(简)版方法:

  1. 文本显示框和按钮
  2. 单选框

完事。现在开始绘制下拉框。


布局

函数结构

def add_combobox(self,pos:tuple,width:int=200,text='',content:tuple=(),fg='black',bg='white',activefg='#757F87',activebg='#CCE4F7',font=('微软雅黑',12),command=None):#绘制组合框
    '''
    pos::位置
    width::文本显示框宽度
    text::初始文本
    content::各个选项组成的元组
    fg::文本、边框颜色
    bg::背景色
    activefg::响应鼠标的文本、边框颜色
    activebg::响应鼠标的背景色
    font::字体
    command::回调函数。必须有一个参数:被选中的选项文本
    '''
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

闭合状态下拉框

下拉框在常规状态下,由文本显示框和下拉提示框组成。其中,下拉提示框我们会使用TinUI的按钮替代。

main=self.create_text(pos,text=text,font=font,fill=fg,anchor='nw')
bbox=self.bbox(main)
x1,y1,x2,y2=bbox[0]-3,bbox[1]-3,bbox[0]+width+3,bbox[3]+3
back=self.create_rectangle((x1,y1,x2,y2),fill=bg,outline=fg)
self.tkraise(main)
#下拉提示框
button_text,button_back=self.add_button((x2+3,y1+3),'∨',fg,bg,activefg,activebg,font=font,command=open_box)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其中,函数open_box对应了展开和闭合选项框的操作,后面再讲。

选项框

下面的代码借鉴了TinUI单选框的绘制代码。

def button_in(_tag):
    self.itemconfig(_tag,fill=activebg,outline=activefg)
def button_out(_tag):
    self.itemconfig(_tag,fill=bg,outline=fg)
start_x=bbox[0]#起始x位置
    height=bbox[3]+3#变量y位置
    for i in content:
        choice=self.create_text((start_x+2,height+2),text=i,fill=fg,font=(font[0],10),anchor='nw',width=width-4)
        pos=self.bbox(choice)
        h=pos[3]-pos[1]+4
        cho_back=self.create_rectangle((start_x,height,start_x+width,height+h),outline=fg,fill=bg)
        self.tkraise(choice)
        height+=h+2
        self.tag_bind(choice,'<Enter>',lambda event,back=cho_back:button_in(back))
        self.tag_bind(choice,'<Leave>',lambda event,back=cho_back:button_out(back))
        self.tag_bind(cho_back,'<Enter>',lambda event,back=cho_back:button_in(back))
        self.tag_bind(cho_back,'<Leave>',lambda event,back=cho_back:button_out(back))
        self.tag_bind(choice,'<Button>',lambda event,_text=i,back=cho_back:choose_this(back,_text))
        self.tag_bind(cho_back,'<Button>',lambda event,_text=i,back=cho_back:choose_this(back,_text))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这段代码取自于TinUI的radiobutton绘制代码,有删改。

这段代码的含义在TinUI绘制单选框中提到,这里不再阐述。

函数choose_this代表响应鼠标的选中动作。届时,文本显示框将显示选中的文本。

def choose_this(back,word):
    self.itemconfig(main,text=word)
    if command!=None:
        command(word)
  • 1
  • 2
  • 3
  • 4

整合选项框

在之后的操作中,我们需要对所有选项框进行统一操作,因此我们将为它们建立一个一致的tag名称。为了避免tag名称重复,我们将使用文本显示框和下拉提示框的组件id进行tag命名。

start_x=bbox[0]#起始x位置
height=bbox[3]+3#变量y位置
box_tagname='combobox>'+str(main)+'>'+str(back)#绑定独立的tag名称
for i in content:
    #...
    self.addtag_withtag(box_tagname,choice)
    self.addtag_withtag(box_tagname,cho_back)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

展开与闭合

这个动作均有open_box函数进行操作。我们可以通过按钮的文本,来判断当前下拉框的状态。

首先,那些选项框本身是闭合的。

#...
for i in content:
    #...
self.itemconfig(box_tagname,state='hidden')
  • 1
  • 2
  • 3
  • 4

那么,函数的操作如下:

def open_box(event):
    if self.itemcget(button_text,'text')=='∨':
        self.itemconfig(button_text,text='∧',fill=activefg)
        self.itemconfig(box_tagname,state='normal')
    else:
        self.itemconfig(button_text,text='∨',fill=activefg)
        self.itemconfig(box_tagname,state='hidden')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

OK,可以去吃饭了。

完整代码函数

def add_combobox(self,pos:tuple,width:int=200,text='',content:tuple=(),fg='black',bg='white',activefg='#757F87',activebg='#CCE4F7',font=('微软雅黑',12),command=None):#绘制组合框
    def button_in(_tag):
        self.itemconfig(_tag,fill=activebg,outline=activefg)
    def button_out(_tag):
        self.itemconfig(_tag,fill=bg,outline=fg)
    def open_box(event):
        if self.itemcget(button_text,'text')=='∨':
            self.itemconfig(button_text,text='∧',fill=activefg)
            self.itemconfig(box_tagname,state='normal')
        else:
            self.itemconfig(button_text,text='∨',fill=activefg)
            self.itemconfig(box_tagname,state='hidden')
    def choose_this(back,word):
        self.itemconfig(main,text=word)
        if command!=None:
            command(word)
    main=self.create_text(pos,text=text,font=font,fill=fg,anchor='nw')
    bbox=self.bbox(main)
    x1,y1,x2,y2=bbox[0]-3,bbox[1]-3,bbox[0]+width+3,bbox[3]+3
    back=self.create_rectangle((x1,y1,x2,y2),fill=bg,outline=fg)
    self.tkraise(main)
    button_text,button_back=self.add_button((x2+3,y1+3),'∨',fg,bg,activefg,activebg,font=font,command=open_box)
    start_x=bbox[0]#起始x位置
    height=bbox[3]+3#变量y位置
    box_tagname='combobox>'+str(main)+'>'+str(back)#绑定独立的tag名称
    for i in content:
        choice=self.create_text((start_x+2,height+2),text=i,fill=fg,font=(font[0],10),anchor='nw',width=width-4)
        pos=self.bbox(choice)
        h=pos[3]-pos[1]+4
        cho_back=self.create_rectangle((start_x,height,start_x+width,height+h),outline=fg,fill=bg)
        self.tkraise(choice)
        height+=h+2
        self.tag_bind(choice,'<Enter>',lambda event,back=cho_back:button_in(back))
        self.tag_bind(choice,'<Leave>',lambda event,back=cho_back:button_out(back))
        self.tag_bind(cho_back,'<Enter>',lambda event,back=cho_back:button_in(back))
        self.tag_bind(cho_back,'<Leave>',lambda event,back=cho_back:button_out(back))
        self.tag_bind(choice,'<Button>',lambda event,_text=i,back=cho_back:choose_this(back,_text))
        self.tag_bind(cho_back,'<Button>',lambda event,_text=i,back=cho_back:choose_this(back,_text))
        self.addtag_withtag(box_tagname,choice)
        self.addtag_withtag(box_tagname,cho_back)
    self.itemconfig(box_tagname,state='hidden')
    return main,back,box_tagname
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

效果

测试代码

def test(event):
    a.title('TinUI Test')
    b.add_paragraph((50,150),'这是TinUI按钮触达的事件函数回显,此外,窗口标题也被改变、首行标题缩进减小')
    b.coords(m,100,5)
def test1(word):
    print(word)
def test2(event):
    ok1()
def test3(event):
    ok2()

if __name__=='__main__':
    a=Tk()
    a.geometry('700x700+5+5')

    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尚处于开发阶段。如果想要使用完整的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,300),350,30,'这里用来输入')
    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='lightgreen')
    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),fg='blue')
    b.add_combobox((600,550),text='你有多大可能去珠穆朗玛峰',content=('20%','40%','60%','80%','100%','1000%'))

    a.mainloop()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

最终效果

在这里插入图片描述


2021-7-29新样式

在这里插入图片描述


github项目

TinUI的github项目地址

pip下载

pip install tinui
  • 1

结语

确实,TinUI的下拉框相比于系统下拉框还有细节上的不足,但是TinUI的下拉框提供了较多的自定义选项,而且不会影响使用效果。

中考考完,居然就直接写了这一篇8000+字的说明文……

欢迎到github上完善TinUI。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/83544
推荐阅读
相关标签