第3章〓Python图形用户界面 本章主要介绍图形界面。在介绍Python图形界面编程前,首先简单介绍一下Python的图形界面库。Python提供了多个图形开发界面的库,几个常用Python GUI库如下。 (1) Tkinter: Tkinter模块(Tk接口)是Python的标准Tk GUI工具包的接口,Tk和Tkinter可以在大多数的UNIX平台下使用,同样可以应用在Windows和macOS中。 (2) wxPython: wxPython是一款开源软件,是Python语言的一套优秀的GUI图形库,允许Python程序员很方便地创建完整的、功能健全的GUI。 (3) Jython: Jython程序可以和Java无缝集成。除了一些标准模块,Jython还使用Java的模块。Jython几乎拥有标准的Python中不依赖于C语言的全部模块。例如,Jython的用户界面将使用Swing、AWT或者SWT。Jython可以被动态或静态地编译成Java字节码。 其中,Tkinter是Python的标准GUI库。Python使用Tkinter可以快速地创建GUI应用程序。 由于Tkinter是内置到Python的安装包中,只要安装好Python之后就能导入Tkinter库,而且IDLE也是用Tkinter编写而成的,对于简单的图形界面,Tkinter能应付自如。导入Tkinter库的方法为: import tkinter 在开始GUI编程之前,需要先了解几个概念: 窗体控件、事件驱动、布局管理。 (1) 窗体控件: 包括窗体、标签、按钮、列表框、滚动条等。 (2) 事件驱动: 单击按钮及释放、鼠标移动、按回车键等。 (3) 布局管理: Tk有3种布局管理器,分别为Placer、Packer、Grid。 3.1布局管理 GUI编程就相当于搭积木,每个积木块应该放在哪里,每个积木块显示为多大,也就是对大小和位置都需要进行管理,而布局管理器正是负责管理各组件的大小和位置的。此外,当用户调整了窗口的大小后,布局管理器还会自动调整窗口中各组件的大小和位置。 3.1.1Pack布局管理器 如果使用Pack布局,那么当程序向容器中添加组件时,这些组件会依次向后排列,排列方向既可以是水平的,也可以是垂直的。 【例31】利用Pack布局管理器制作钢琴的布局。 """ Pack布局 """ from tkinter import * from tkinter import messagebox import random import matplotlib.pyplot as plt # plt用于显示图片 #matplotlib inline class Application(Frame): def __init__(self, mester=None): super().__init__(mester) self.mester = mester self.pack() self.creatWidget() def creatWidget(self): f1 = Frame(root) f1.pack() f2 = Frame(root) f2.pack() btnText = ('公平', '民主', '开放', '自由', '法治', '正义') for txt in btnText: Button(f1, text=txt).pack(side='left', padx='10') for i in range(1, 15): Label(f2, width=4, height=10, borderwidth=1, relief='solid', bg='black' if i % 2 == 0 else 'white').pack(side='left', padx='1') if __name__ == '__main__': root = Tk() root.geometry('400x200+200+200') app = Application(mester=root) root.mainloop() 运行程序,效果如图31所示。 图31钢琴布局 pack()方法通常可支持如下选项。  anchor: 当可用空间大于组件所需求的大小时,该选项决定组件被放置在容器的何处。该选项支持N(北,代表上)、E(东,代表右)、S(南,代表下)、W(西,代表左)、NW(西北,代表左上)、NE(东北,代表右上)、SW(西南,代表左下)、SE(东南,代表右下)、CENTER(中,默认值)这些值。  expand: 该bool值指定当父容器增大时是否拉伸组件。  fill: 设置组件是否沿水平或垂直方向填充。该选项支持NONE、X、Y、BOTH四个值,其中,NONE表示不填充,BOTH表示沿着两个方向填充。  ipadx: 指定组件在x方向(水平)上的内部留白(padding)。  ipady: 指定组件在y方向(垂直)上的内部留白(padding)。  padx: 指定组件在x方向(水平)上与其他组件的间距。  pady: 指定组件在y方向(垂直)上与其他组件的间距。  side: 设置组件的添加位置,可以设置为TOP、BOTTOM、LEFT或RIGHT这四个值的其中之一。 3.1.2Grid布局管理器 Grid布局管理器可以说是Tkinter这三个布局管理器中最灵活多变的。使用一个Grid就可以简单地实现用很多个框架和Pack搭建起来的效果。 Grid把组件空间分解成一个网格进行维护,即按照行、列的方式排列组件,组件位置由其所在的行号和列号决定; 行号相同而列号不同的几个组件会被依次上下排列,列号相同而行号不同的几个组件会被依次左右排列。 在Python程序中,调用grid()方法进行Grid布局。在调用grid()方法时可传入多个选项,该方法支持的ipadx、ipady、pady与pack()方法的这些选项相同。而grid()方法额外增加了如下选项。  column: 单元格的列号为从0开始的正整数。  columnspan: 跨列,跨越的列数,正整数。  row: 单元格的行号为从0开始的正整数。  rowspan: 跨行,跨越的行数,正整数。  sticky: 组件紧贴所在单元格的某一角,该选项支持N(北,代表上)、E(东,代表右)、S(南,代表下)、W(西,代表左)、NW(西北,代表左上)、NE(东北,代表右上)、SW(西南,代表左下)、SE(东南,代表右下)、CENTER(中,默认值)这些值。 【例32】利用Grid布局管理器设计一个计算器界面。 """Grid布局管理器""" # coding:utf-8 from tkinter import * from tkinter import messagebox import random class Application(Frame): """一个经典的GUI程序类写法""" def __init__(self, master=None): super().__init__(master) # super代表的是父类的定义,而不是父类的对象 self.master = master self.pack() self.createWidget() def createWidget(self): btnText = (('MC', 'M+', 'M-', 'MR'), ('C', '±','÷','×'), ('7','8','9','-'), ('4','5','6','+'), ('1','2','3','='),('0','·')) Entry(self).grid(row=0, column=0, columnspan=4) for rindex,r in enumerate(btnText): for cindex,c in enumerate(r): if c == '=': Button(self, text=c, width=2).grid(row=rindex + 1, column=cindex, rowspan=2, sticky=NSEW) elif c == '0': Button(self, text=c, width=2).grid(row=rindex + 1, column=cindex, columnspan=2, sticky=NSEW) elif c =='·': Button(self, text=c, width=2).grid(row=rindex + 1, column=cindex+1, sticky=NSEW) else: Button(self, text=c, width=2).grid(row=rindex+1, column=cindex, sticky=NSEW) if __name__ == "__main__": root = Tk() root.geometry("170x220+200+300") root.title('canvas') app = Application(master=root) root.mainloop() 运行程序,效果如图32所示。 图32计算器界面 3.1.3Place布局管理器 Place布局就是其他GUI编程中的“绝对布局”,这种布局方式要求程序显式指定每个组件的绝对位置或相对于其他组件的位置。 如果要使用Place布局,调用相应组件的place()方法即可。在使用该方法时同样支持一些详细的选项,关于这些选项的介绍如下。  x: 指定组件的X坐标。x为0,代表位于最左边。  y: 指定组件的Y坐标。y为0,代表位于最右边。  relx: 指定组件的X坐标,以父容器总宽度为单位1,该值应该为0.0~1.0,其中,0.0代表位于窗口最左边,1.0代表位于窗口最右边,0.5代表位于窗口中间。  rely: 指定组件的Y坐标,以父容器总宽度为单位1,该值应该为0.0~1.0,其中,0.0代表位于窗口最上边,1.0代表位于窗口最下边,0.5代表位于窗口中间。  width: 指定组件的宽度,以pixel为单位。  height: 指定组件的高度,以pixel为单位。  relwidth: 指定组件的宽度,以父容器总宽度为单位1,该值应该为0.0~1.0,其中,1.0代表整个窗口宽度,0.5代表窗口的一半宽度。  relheight: 指定组件的高度,以父容器总高度为单位1,该值应该为0.0~1.0,其中,1.0代表整个窗口高度,0.5代表窗口的一半高度。  bordermode: 该属性支持“inside”或“outside”属性值,用于指定当设置组件的宽度、高度时是否计算该组件的边框宽度。 【例33】Place布局管理实现。 """ Place布局管理器 """ from tkinter import * from tkinter import messagebox import random class Application(Frame): def __init__(self, mester=None): super().__init__(mester) self.mester = mester self.pack() self.creatWidget() def creatWidget(self): self.photo1 = PhotoImage(file='2.gif') self.puks01 = Label(root, image=self.photo1) self.puks01.place(x=20, y=50) self.photo2 = PhotoImage(file='11.gif') self.pukse02 = Label(root, image=self.photo2) self.pukse02.place(x=300, y=50) self.puks01.bind_class('Label', '', self.chupai) self.pukse02.bind_class('Label', '<1>', self.chupai) #为所有的Label增加事件处理 def chupai(self, event): print(event.widget.winfo_geometry()) print(event.widget.winfo_y()) if event.widget.winfo_y() == 50: event.widget.place(y=30) else: event.widget.place(y=50) if __name__ == '__main__': root = Tk() root.geometry('800x400+500+500') app = Application(mester=root) root.mainloop() 运行程序,效果如图33所示。 图33图片的上下管理 3.2Tkinter常用组件 Tkinter中,每个组件都是一个类,创建某个组件其实就是将这个类实例化。在实例化的过程中,可以通过构造函数给组件设置一些属性,同时还必须给该组件指定一个父容器,意即该组件放置何处。最后,还需要给组件设置一个几何管理器(布局管理器)。解决了放哪里的问题,还需要解决怎么放的问题,而布局管理器就是解决怎么放问题的,即设置子组件在父容器中的放置位置。 3.2.1Variable类 Tkinter支持将很多GUI组件与变量进行双向绑定,执行这种双向绑定后编程非常方便。  如果程序改变变量的值,GUI组件的显示内容或值会随之改变。  当GUI组件的内容发生改变时(如用户输入),变量的值也会随之改变。 为了让Tkinter组件与变量进行双向绑定,只要为这些组件指定variable(通常绑定组件的value)、textvariable(通常绑定组件显示的文本)等属性即可。但这种双向绑定有一个限制,就是Tkinter不允许将组件和普通变量进行绑定,只能和Tkinter包下Variable类的子类进行绑定。该类包含如下几个子类。  StringVar(): 用于包装str值的变量。  IntVar(): 用于包装整型值的变量。  DoubleVar(): 用于包装浮点值的变量。  BooleanVar(): 用于包装bool值的变量。 对于variable变量而言,如果要设置其保存的变量值,则使用它的set()方法; 如果要得到其保存的变量值,则使用它的get()方法。 【例34】将Entry组件与StringVar进行双向绑定。 程序既可以通过该StringVar改变Entry输入框显示的内容,也可以通过该StringVar获取Entry输入框中的内容。 from tkinter import * from tkinter import ttk #导入ttk class App: def __init__(self, master): self.master = master self.initWidgets() def initWidgets(self): self.st = StringVar() #创建Entry组件,将其textvariable绑定到self.st变量 ttk.Entry(self.master, textvariable=self.st, width=24, font=('StSong', 20, 'bold'), foreground='red').pack(fill=BOTH, expand=YES) #创建Frame作为容器 f = Frame(self.master) f.pack() #创建两个按钮,将其放入Frame中 ttk.Button(f, text='改变', command=self.change).pack(side=LEFT) ttk.Button(f, text='获取', command=self.get).pack(side=LEFT) def change(self): books = ('图形用户界面', 'Tkinter组件', 'Variable类') import random #改变self.st变量的值,与之绑定的Entry的内容随之改变 self.st.set(books[random.randint(0, 2)]) def get(self): from tkinter import messagebox #获取self.st变量的值,实际上就是获取与之绑定的Entry中的内容 #并使用消息框显示self.st变量的值 messagebox.showinfo(title='输入内容', message=self.st.get() ) root = Tk() root.title("variable测试") App(root) root.mainloop() 运行程序,界面如图34所示。单击界面中的“改变”按钮,将可以看到输入框中的内容会随之改变; 如果单击界面上的“获取”按钮,将会看到程序弹出一个消息框,显示了用户在Entry输入框中输入的内容。 图34Entry组件双向绑定 3.2.2compound选项 在Python中,可以为按钮或Label等组件同时指定文本(text)与图片(image)两个选项,其中,text用于指定该组件上的文本; image用于显示该组件上的图片,当同时指定这两个选项时,通常image会覆盖text。但在某些时候,我们希望该组件能同时显示文本和图片,此时就需要通过compound选项进行控制。 compound选项支持如下属性值。  None: 图片覆盖文字。  LEFT常量(值为'left'字符串): 图片在左,文本在右。  RIGHT常量(值为'right'字符串): 图片在右,文本在左。  TOP常量(值为'top'字符串): 图片在上,文本在下。  BOTTOM常量(值为'bottom'字符串): 图片在下,文本在上。  CENTER常量(值为'center'字符串): 文本在图片上方。 【例35】利用compound选项同时使用图片和文字。 from tkinter import * root = Tk() root.title("Label测试") img = PhotoImage(file="2.gif") stext = "Python简单、易学" #图片位于文字左侧 label1 = Label(root, image=img, text=stext, compound="left", bg="lightyellow") label1.pack() #图片位于文字右侧 label2 = Label(root, image=img, text=stext, compound="right", bg="lightcyan") label2.pack() #图片位于文字上方 label3 = Label(root, image=img, text=stext, compound="top", bg="lightgreen") label3.pack() #图片位于文字下方 label4 = Label(root, image=img, text=stext, compound="bottom", bg="lightgray") label4.pack() # 文字覆盖图片中央,可作背景图片 label5 = Label(root, image=img, text=stext, compound="center", bg="lightblue", fg="white", font=("微软雅黑", 24)) label5.pack() root.mainloop() 运行程序,效果如图35所示。 图35compound选项 3.2.3Entry与Text组件 Entry组件仅允许用于输入一行文本,如果用于输入的字符串长度比该组件可显示空间更长,那内容将被滚动。这意味着该字符串将不能被全部看到(可以用鼠标或键盘的方向键调整文本的可见范围)。如果希望接收多行文本的输入,可以使用Text组件。 不管是Entry还是Text组件,程序都提供了get()方法来获取文本框中的内容; 但如果程序要改变文本框中的内容,则需要调用二者的insert()方法来实现。 如果要删除Entry或Text组件中的部分内容,则可通过delete(self,first,last=None)方法实现,该方法指定删除从first到last之间的内容。 但两者之间支持的索引是不同的,由于Entry是单行文本框组件,因此它的索引很简单,例如,要指定第4~8个字符,将索引指定为(3,8)即可。但Text是多行文本框组件,因此它的索引需要同时指定行号和列号,例如,1.0代表第1行、第1列(行号从1开始,列号从0开始),如果要指定第2行第3个字符到第3行第7个字符,索引应指定为(2.2,3.6)。 提示: Entry支持双向绑定。 【例36】将Entry中用户输入的字符串在Text文本框中显示,其中触发不同按钮,用户输入的内容将插入在与之相应的不同位置。 import tkinter as tk window=tk.Tk() window.title('Entry与Text测试') window.geometry('200x200') e=tk.Entry(window,show='*') # Entry的第一个参数是父窗口,即这里的window # *表示输入的文本变为星号,在Entry中为不可见内容,如果为None则表示输入文本以原形式可见 e.pack() def insert_point(): var=e.get() t.insert('insert',var) def insert_end(): var=e.get() t.insert('end',var) #这里的end表示插入到结尾,可以换为1.2,则插入在第1行第2位后面 b1=tk.Button(window,text='插入点',width=15,height=2,command=insert_point) b1.pack() b2=tk.Button(window,text='插入端',width=15,height=2,command=insert_end) b2.pack() t=tk.Text(window,height=2) #这里设置文本框高,可以容纳两行 t.pack() window.mainloop() 运行程序,效果如图36所示。 图36Entry与Text测试效果 3.2.4Checkbutton组件 Checkbutton(多选按钮)组件用于实现确定是否选择的按钮。Checkbutton组件可以包含文本或图像,可以将一个Python的函数或方法与之相关联,当按钮被按下时,对应的函数或方法将被自动执行。 Checkbutton组件仅能显示单一字体的文本,但文本可以跨越多行。另外,还可以为其中的个别字符加上下画线(例如,用于表示键盘快捷键)。默认情况下,Tab键被用于在按钮间切换。 Checkbutton组件被用于作为二选一的按钮(通常为选择“开”或“关”的状态),当希望表达“多选多”选项的时候,可以将一系列Checkbutton组合起来使用。但是处理“多选一”的问题时,还是交给Radiobutton和Listbox组件来实现更合理。 【例37】创建Checkbutton复选框。 from tkinter import * tk = Tk() label = Label(tk, text="你最喜欢的计算机语言", bg="yellow", fg="blue", width=30) label.grid(row=0) var1 = IntVar() cbtn1 = Checkbutton(tk, text="Python", variable=var1) cbtn1.grid(row=1, sticky=W) var2 = IntVar() cbtn2 = Checkbutton(tk, text="MATLAB", variable=var2) cbtn2.grid(row=2, sticky=W) var3 = IntVar() cbtn3 = Checkbutton(tk, text="TensorFlow", variable=var3) cbtn3.grid(row=3, sticky=W) tk.mainloop() 运行程序,效果如图37所示。 图37复选按钮 3.2.5Radiobutton组件 Radiobutton组件跟Checkbutton组件的用法基本一致,唯一不同的是Radiobutton实现的是“单选”的效果。 要实现这种互斥的效果,同组内的所有Radiobutton只能共享一个Variable选项,并且需要设置不同的Value选项值。 【例38】利用Radiobutton组件创建单选按钮组。 import tkinter as tk root = tk.Tk() v=tk.IntVar() v.set(1) #如果set的括号里是1的话,一开始的默认选项在1;如果括号里是2的话,默认选项在2;如果超过了 #4,则没有默认选项 langs = [("One",1),("Two",2),("Three",3)] for lang,num in langs: tk.Radiobutton(root,text=lang,variable=v,value=num).pack(anchor=tk.W) tk.mainloop() 运行程序,效果如图38所示。 图38是一个单选按钮样式,如果将它的indicatoron选项设置为False,Radiobutton的样式就会变成普通按钮的样式了,如图39所示。 #在for循环中进行改动 for text in texts: tk.Radiobutton(root,text=text,variable=v,value=i,indicatoron=False).pack(fill=tk.X) i+=1 图38单选按钮 图39改成普通按钮形式 3.2.6Listbox和Combobox组件 Listbox代表一个列表框,用户可通过列表框来选择一个列表项。ttk模块下的Combobox则是Listbox的改进版,它既提供了单行文本框让用户直接输入(就像Entry一样),也提供了下拉列表框供用户选择(就像Listbox一样),因此它被称为复合框。 程序创建Listbox需要以下两步。 (1) 创建Listbox对象,并为之执行各种选项。Listbox除支持大部分通用选项之外,还支持selectmode选项,用于设置Listbox的选择模式。 (2) 调用Listbox的insert(self,index,*elements)方法来添加选项。从最后一个参数可以看出,该方法既可每次添加一个选项,也可传入多个参数,每次添加多个选项。index参数指定选项的插入位置,它支持END(结尾处)、ANCHOR(当前位置)和ACTIVE(选中处)等特殊索引。 Listbox的selectmode支持的选择模式有如下几种。 (1) 'browse': 单选模式,支持按住鼠标键拖动来改变选择。 (2) 'multiple': 多边模式。 (3) 'single': 单边模式,必须通过鼠标键单击来改变选择。 (4) 'extended': 扩展的多边模式,必须通过Ctrl或Shift键辅助实现多选。 【例39】利用Listbox和Combobox组件创建界面。 #用户界面 import os from tkinter import * from tkinter import ttk root = Tk() root.title("window") root.geometry('500x500') #创建标签 var1 = StringVar() l = Label(root, bg='green', fg='pink',font=('Arial', 12), width=10, textvariable=var1) l.pack() #列表框单击事件 def print_lb1(): value = lb1.get(lb1.curselection()) var1.set(value) #列表框单击按钮 b1 = Button(root,text='打印选择的事件lb',width=18,height=2,command=print_lb1) b1.pack() #创建Listbox var_lb1 = StringVar() var_lb1.set(('C30','C35','C40')) lb1 = Listbox(root,listvariable = var_lb1) lb1.pack() #组合框单击事件 def print_cb1(): value = cb1.get() var1.set(value) #组合框单击按钮 b2 = Button(root,text='打印选择的事件cb',width=18,height=2,command=print_cb1) b2.pack() #创建Combobox var_cb1 = StringVar() var_cb1.set('请选择混凝土标号') cb1 = ttk.Combobox(root,textvariable=var_cb1) cb1['values']=['C30','C35','C40'] cb1.pack() #事件循环 root.mainloop() 运行程序,效果如图310所示。 图310Listbox和Combobox组件界面 3.2.7Spinbox组件 Spinbox组件是一个带有两个小箭头的文本框,用户既可以通过两个小箭头上下调整该组件内的值,也可以直接在文本框内输入内容作为该组件的值。 Spinbox本质上也相当于持有一个列表框,这一点类似于Combobox,但Spinbox不会展开下拉列表供用户选择。Spinbox只能通过向上、向下箭头来选择不同的选项。 在使用Spinbox组件时,既可通过from(由于from是关键字,实际使用时写成from_)、to、increment选项来指定选项列表,也可通过values选项来指定多个列表项,该选项的值可以是list或tuple。 Spinbox同样可通过textvariable选项将它与指定变量绑定,这样程序即可通过该变量来获取或修改Spinbox组件的值。 Spinbox还可通过command选项指定事件处理函数或方法: 当用户单击Spinbox的向上、向下箭头时,程序就会触发command选项指定的事件处理函数或方法。 【例310】Spinbox组件创建界面。 ''' 打印Spinbox的当前内容 get:此方法取得当前显示的内容 ''' from tkinter import * root = Tk() def printSpin(): #使用get()方法得到当前的显示值 print(sb.get()) 图311Spinbox组件界面 sb = Spinbox(root, from_ = 0, to = 10, command = printSpin) sb.pack() root.mainloop() #每次单击Spinbox按钮时就会调用printSpin()函数,打印出Spinbox #的当前值 运行程序,效果如图311所示。 3.2.8Scale和LabeledScale组件 Scale组件代表一个滑动条,可以为该滑动条设置最小值和最大值,也可以设置滑动条每次调节的步长。Scale组件支持如下选项。  from: 设置该Scale的最小值。  to: 设置该Scale的最大值。  resolution: 设置该Scale滑动时的步长。  label: 为Scale组件设置标签内容。  length: 设置轨道的长度。  width: 设置轨道的宽度。  troughcolor: 设置轨道的背景色。  sliderlength: 设置轨道的长度。  sliderrelief: 设置滑块的立体样式。  showvalue: 设置是否显示当前值。  orient: 设置方向。该选项支持vertical和horizontal两个值。  digits: 设置有效数字至少要有几位。  variable: 用于与变量进行绑定。  command: 用于为该Scale组件绑定事件处理、函数或方法。 Scale组件同样支持variable进行变量绑定,也支持使用command选项绑定事件处理函数或方法,这样每当用户拖动滑动条上的滑块时,都会触发command绑定的事件处理方法,不过Scale的事件处理方法除外,它可以额外定义一个参数,用于获取Scale的当前值。 而对于LabeledScale组件,如果使用ttk.Scale组件,则更接近操作系统本地的效果,但允许定制的选项少。ttk.LabeledScale是平台化的滑动条,因此它允许设置的选项很少,只能设置from、to和compound等有限的几个选项,而且它总是生成一个水平滑动条(不能变成垂直的),其中,compound选项控制滑动条的数值标签是显示在滑动条的上方,还是滑动条的下方。 【例311】利用Scale和LabeledScale组件创建界面。 from tkinter import * root=Tk() root.resizable(0,0) def change(value): print(value, scale.get(), doubleVar.get()) '''获取Scale值 1.通过事件处理方法的参数来获取。 2.通过Scale组件提供的get()方法来获取。 3.通过Scale组件绑定的变量来获取。 ''' doubleVar = DoubleVar() scale = Scale(root, from_ = -100, #设置最小值 to = 100, #设置最大值 resolution = 5, #设置步长 label = 'Sacle:', #设置标签内容 length = 400, #设置轨道的长度 width = 30, #设置轨道的宽度 troughcolor='lightblue', #设置轨道的背景色 sliderlength=20, #设置滑块的长度 sliderrelief=SUNKEN, #设置滑块的立体样式 showvalue=YES, #设置显示当前值 orient = HORIZONTAL, #设置水平方向 digits=5,#设置五位有效数字 command=change, #绑定事件处理函数 variable=doubleVar #绑定变量 ) scale.pack() scale.set(20) f = Frame(root) f.pack(fill=X, expand=YES, padx=10) def valueshow(): scale['showvalue'] = showVar.get() Label(f, text='是否显示值:').pack(side=LEFT) showVar = IntVar() i = 0 for s in ('不显示', '显示'): Radiobutton(f, text=s, value=i,variable=showVar,command=valueshow).pack(side=LEFT) i += 1 showVar.set(1) f = Frame(root) f.pack(fill=X, expand=YES, padx=10) def scaleorient(): scale['orient'] = VERTICAL if orientVar.get() else HORIZONTAL Label(f, text='方向:').pack(side=LEFT) orientVar = IntVar() i = 0 for direction in ('水平', '垂直'): Radiobutton(f, text=direction, value=i,variable=orientVar,command=scaleorient).pack(side=LEFT) i += 1 orientVar.set(0) from tkinter import ttk f = Frame(root) f.pack(fill=X, expand=YES, padx=10) Label(f, text='LabeledScale:').pack(anchor=NW) labeledscale = ttk.LabeledScale(f, from_=-100, #设置最小值 to=100, #设置最大值 compound=BOTTOM #显示数值在滑动条下方 ) labeledscale.value = -20 labeledscale.pack(fill=X, expand=YES) mainloop() 运行程序,效果如图312所示。 图312Scale和LabeledScale组件界面图 3.2.9LabelFrame组件 LabelFrame组件是Frame组件的变体。默认情况下,LabelFrame会在其子组件的周围绘制一个边框以及一个标题。当想要将一些相关的组件分为一组的时候,可以使用LabelFrame组件,如一系列Radiobutton(单选按钮)组件。 【例312】创建两个LabelFrame组件,并为其添加内容。 import tkinter as tk win = tk.Tk() #定义第一个容器 frame_left = tk.LabelFrame(win, text="优点", labelanchor="n") frame_left.place(relx=0.2, rely=0.2, relwidth=0.3, relheight=0.6) label_1 = tk.Label(frame_left, text="简单") label_1.place(relx=0.2, rely=0.2) label_2 = tk.Label(frame_left, text="易学") label_2.place(relx=0.6, rely=0.2) label_3 = tk.Label(frame_left, text="易读") label_3.place(relx=0.2, rely=0.6) label_4 = tk.Label(frame_left, text="易维护") label_4.place(relx=0.6, rely=0.6) #定义第二个容器 frame_right = tk.LabelFrame(win, text="缺点", labelanchor="n") frame_right.place(relx=0.5, rely=0.2, relwidth=0.3, relheight=0.6) label_1 = tk.Label(frame_right, text="速度慢") label_1.place(relx=0.2, rely=0.2) label_2 = tk.Label(frame_right, text="强制缩进") label_2.place(relx=0.6, rely=0.2) label_3 = tk.Label(frame_right, text="单行语句") label_3.place(relx=0.2, rely=0.6) label_4 = tk.Label(frame_right, text="不加密") label_4.place(relx=0.6, rely=0.6) win.mainloop() 运行程序,效果如图313所示。 图313LabelFrame组件创建界面 3.2.10PanedWindow组件 当我们需要提供一个可供用户调整的多空间框架的时候,可以使用PanedWindow组件。PanedWindow组件会为每一个子组件生成一个独立的窗格,用户可以自由调整窗格的大小。 【例313】使用PanedWindow组件创建三个窗格。 import tkinter as tk window = tk.Tk() window.title('你好Tkinter') height= window.winfo_screenheight() width= window.winfo_screenwidth() window.geometry('400x300+%d+%d'%((width-400)/2,(height-300)/2)) #创建三个窗口,在窗口上可以拖动鼠标调整大小 m1 = tk.PanedWindow(bd=1,bg='blue') m1.pack(fill="both", expand=1) left = tk.Label(m1, text="左窗格") m1.add(left) m2 = tk.PanedWindow(orient="vertical",bd=1,bg='red') m1.add(m2) top = tk.Label(m2, text="顶部窗格") m2.add(top) bottom = tk.Label(m2, text="底部窗格") m2.add(bottom) window.mainloop() 运行程序,效果如图314所示。 图314PanedWindow组件创建界面 3.2.11OptionMenu组件 OptionMenu组件用于构建一个带菜单的按钮,该菜单可以在按钮的四个方向上展开,展开方向可通过direction选项控制。 使用OptionMenu比较简单,直接调用它的如下构造函数即可。 _init_(self,master,variable,value,*values,**kwargs) 其中,master参数的作用与所有的Tkinter组件一样,指定将该组件放入哪个容器中。其他参数的含义如下。  variable: 指定该按钮上的菜单与哪个变量绑定。  value: 指定默认选择菜单中的哪一项。  values: Tkinter将收集为此参数传入的多个值,为每个值创建一个菜单项。  kwargs: 用于为OptionMenu配置选项。除前面介绍的选项之外,还可通过direction选项控制菜单的展开方向。 【例314】利用OptionMenu创建菜单。 from tkinter import * from tkinter import messagebox class Application(Frame): def __init__(self,master=None): super().__init__(master) self.master = master self.pack() self.createwidget() def createwidget(self): option = ["青菜", "白菜", "菠菜", "黄瓜"] #所有的选项列表 variable = StringVar() variable.set(option[0]) #默认选项 op = OptionMenu(self, variable, *option) op["width"] = 10 #选项框的长度 op.pack(padx=10, pady=10) #选项框的位置 #设置按钮,按下后弹出窗口,提示选的蔬菜名称,即获取变量内容 Button(self, text="确定",command=lambda : self.print_fruit(variable)).pack(pady=20) def print_fruit(self,variable): messagebox.showinfo("蔬菜", "您选的蔬菜是:{}".format(variable.get())) if __name__ == '__main__': root = Tk() root.title("optionmenu测试") root.geometry("300x200") app = Application(root) root.mainloop() 运行程序,选择其中一个菜单并单击“确定”按钮,得到如图315所示的效果。 图315OptionMenu组件创建菜单按钮 3.3对话框 对话框也是图形界面编程中很常用的组件,通常用于向用户生成某种提示信息,或者请求用户输入某些简单的信息。 对话框看上去有点类似于顶级窗口,但对于对话框有如下两点需要注意。 (1) 对话框通常依赖其他窗口,因此程序在创建对话框时同样需要指定master属性(该对话框的属主窗口)。 (2) 对话框有非模式(nonmodal)和模式(modal)两种,当某个模式对话框被打开后,该模式对话框总是位于它依赖的窗口之上; 在模式对话框被关闭前,它依赖的窗口无法获得焦点。 3.3.1普通对话框 Tkinter在simpledialog和dialog模块下分别提供了SimpleDialog类和Dialog类,它们都可作为普通对话框使用,而且用法也差不多。 在使用simpledialog.SimpleDialog创建对话框时,可指定如下选项。  title: 指定该对话框的标题。  text: 指定该对话框的内容。  button: 指定该对话框下方的几个按钮。  default: 指定该对话框中默认第几个按钮得到焦点。  cancel: 指定当用户通过对话框右上角的×按钮关闭对话框时,该对话框的返回值。 如果使用dialog.Dialog创建对话框,除可使用master指定对话框的属主窗口之外,还可通过dict来指定如下选项。  title: 指定该对话框的标题。  text: 指定该对话框的内容。  strings: 指定该对话框下方的几个按钮。  default: 指定该对话框中默认第几个按钮得到焦点。  bitmap: 指定该对话框上的图标。 【例315】分别使用SimpleDialog和Dialog来创建对话框。 from tkinter import * from tkinter import simpledialog from tkinter import dialog root = Tk() root.title('普通对话框') root.geometry("250x250+30+30") msg = '先定义简单对话框的显示文本,再定义简单对话框函数,最后创建一个按钮引用此函数' def open_simpledialog(): d = simpledialog.SimpleDialog(root,title='Simpledialog',text=msg,buttons=["确定", "取消", "退出"],default=0,cancel=3) #获取用户单击对话框的哪个按钮或关闭对话框返回cancel指定的值 print(d.go()) Button(root,text='打开Simpledialog',command=open_simpledialog).pack(side='left',ipadx=10,padx=10) def open_dialog(): d = dialog.Dialog(root, {'title': 'Dialog', 'text': msg,'strings': ('确定','取消','退出'),'default':0, 'bitmap': 'question' }) #打印该对话框num属性的值,该返回值会获取用户单击了对话框的哪个按钮 print(d.num) Button(root,text='打开Dialog',command=open_dialog).pack(side='left',ipadx=10,padx=10) root.mainloop() 运行程序,单击界面上的按钮,得到如图316所示的效果。 图316普通对话框 在如图316所示的Dialog对话框中的左侧还显示了一个问号图标,这是Python内置的10个位图之一,可以直接使用。共有如下几个常量可用于设置位图: “error” “gray75” “gray50” “gray25” “gray12” “hourglass” “info” “questhead” “question” “warning”。 3.3.2非模式对话框 对话框分为模式对话框和非模式对话框。例如,常用的“登录”和“注册”对话框就是模式对话框。从主窗口打开非模式对话框后,不关闭对话框,也能操作主窗口。例如,文本编辑器中的“查找和替换”对话框就是非模式对话框,查找一般从光标处开始,如完成查找后,希望从开始再查找一次,必须在主窗口中将光标移到开始处。 Python Tkinter中定义了多个模式对话框类,包括messagebox类(通用消息对话框类)、filedialog类(文件对话框类)、colorchooser类(颜色选择对话框类)、simpledialog类(简单对话框类)和dialog类(对话框类)。但这些类只能满足比较简单的应用,像“登录”和“注册”对话框这样的对话框,需要输入用户名和密码,并要对输入的数据格式做检查,例如,注册时密码要求多少位、是否要求包括数字和字符、用户名是否唯一等。登录时要确定密码是否正确,若不正确则要求重新输入。对这些比较复杂的对话框,要使用Toplevel类生成自定义对话框。Toplevel类可以在主窗体外创建一个独立的窗口,它和用Tk()方法创建出来的主窗口一样有标题栏、边框等部件,它们有相似的方法和属性。在Toplevel窗口中,能像主窗口一样放入Button、Label和Entry等组件。Toplevel窗口和主窗口可以互相使用对方的变量和方法。一般用于创建自定义模式和非模式对话框。 【例316】用Toplevel类创建的对话框默认是非模式对话框。 import tkinter as tk def openDialog(): global f1,e1 #在Toplevel窗口和主窗口可以互相使用对方的变量和方法 f1 = tk.Toplevel(root) #用Toplevel类创建独立主窗口的新窗口 f1.grab_set() #将f1设置为模式对话框,f1不关闭无法操作主窗口 #f1.transient(root) #该函数使f1总是在父窗口前边,如父窗口最小化,f1被隐藏。模式对话 #框不用使用这条语句 e1=tk.Entry(f1) #可在e1中输入数据,单击“确定”按钮将数据显示在主窗口label1 e1.pack() b1 = tk.Button(f1,text='确定',command=showInput) b1.pack() def showInput(): #在此函数中,可检查数据格式是否正确 label1['text']=e1.get() #显示e1中输入的数据 f1.destroy() #关闭对话框 root = tk.Tk() root.geometry('200x200+50+50') tk.Button(root, text="打开模式对话框", command=openDialog).pack() label1=tk.Label(root,text='初始字符') label1.pack() root.mainloop() 运行程序,效果如图317所示。 图317非模式对话框 3.3.3输入对话框 Python的Tkinter模块中,有一个子模块simpledialog,这个子模块中包含三个函数: askinteger()、askfloat()、askstring()。它们通过GUI窗口的方式,让用户输入一个整数、浮点数或者字符串,并且自带输入合法性检测,使用非常方便。 1. askinteger()函数 通过对话框,让用户输入一个整数: import tkinter as tk from tkinter.simpledialog import askinteger,askfloat,askstring root = tk.Tk() #需要一个根窗口来显示下面的示例 print(askinteger('askinteger','请输入一个整数:')) 代码中的askinteger()函数的第1个参数是对话框的Title,第2个参数是输入条上面的一行信息,如图318所示。 在图318中输入整数,单击OK按钮,返回整数; 单击Cancel按钮,直接关闭此对话框,返回None。如果输入非法数据,会有如图319所示的对话框弹出。 图318输入整数对话框 图319自带输入合法性检查 askinteger()函数还支持设置初始值,设置可以接收的最大值和最小值,这极大地方便了应用的开发。 askinteger('askinteger','please give me an integer:', initialvalue=12345, minvalue=100, maxvalue=20000) 2. askfloat()函数 这是一个接收浮点数的对话框: print(askfloat('askfloat','请输入一个浮点数:')) 运行程序,在弹出的如图320所示的对话框中输入小数(浮点数),单击OK按钮,返回小数; 单击Cancel按钮,直接关闭此对话框,返回None。同样自带输入合法性检查。但是,askfloat()函数可以接收一个整数,返回的是此整数对应的float。 跟askinteger()函数一样,askfloat()函数也支持设置初始值、最大值和最小值。 3. askstring()函数 接收字符串的对话框,这个对话框可以接收的数据就很广了,输入整数或浮点数,都会被当作字符串返回。 print(askstring('askstring','请输入一个字符串:')) 运行程序,效果如图321所示。 图320浮点数对话框 图321字符串对话框 输入有效浮点数后,单击OK按钮即完成输入。 单击Cancel按钮和直接关闭对话框,都是返回None。 askstring()函数也支持设置初始值、最大值和最小值,不过要注意,askstring()函数接收的是字符串,内部比较大小,也是字符串之间比较大小。 3.3.4生成对话框 在filedialog模块下提供了各种用于生成文件对话框的工具函数。这些工具函数有些返回用户所选择文件的路径,有些直接返回用户所选择文件的输入/输出流。  askopenfilename(**options): 返回打开的文件名。  askopenfilenames(**options): 返回打开的多个文件名列表。  askopenfile(**options): 返回打开的文件对象。  askopenfiles(**options): 返回打开的文件对象的列表。  askdirectory(**options): 返回目录名。  asksaveasfile(**options): 返回保存的文件对象。  asksaveasfilename(**options): 返回保存的文件名。 上面用于生成打开文件的对话框的工具函数支持如下选项。  defaultextension指定默认扩展名。当用户没有输入扩展名时,系统会默认添加该选项指定的扩展名。  filetypes: 指定在该文件对话框中能查看的文件类型。该选项值是一个序列,可指定多个文件类型。可以通过“*”指定浏览所有文件。  initialdir: 指定初始打开的目录。  initialfile: 指定所选择的文件。  parent: 指定该对话框的属主窗口。  title: 指定对话框的标题。  multiple: 指定是否允许多选。 对于打开目录的对话框,还额外支持一个mustexist选项,该选项指定是否只允许打开已存在的目录。 【例317】测试文件对话框。 from tkinter import * from tkinter.filedialog import * root = Tk() root.geometry('400x300') def test01(): f1 = askopenfilename(title='上传文件', initialdir='c:file', filetype=[('文本文件', '.txt')]) file01['text'] = f1 Button(root, text='选择文件', command=test01).pack() file01 = Label(root, width=40, height=3, bg='pink') file01.pack() root.mainloop() 运行程序,效果如图322所示。 图322打开文件对话框 3.3.5颜色选择对话框 在colorchooser模块下提供了用于生成颜色选择对话框的askcolor()函数,可为该函数指定如下选项。  parent: 指定该对话框的属主窗口。  title: 指定该对话框的标题。  color: 指定该对话框初始选择的颜色。 【例318】演示颜色选择对话框的用法。 from tkinter import * import tkinter.colorchooser as cc main= Tk() def CallColor(): Color=cc.askcolor() print(Color) Button(main,text="选择颜色",command=CallColor).grid() mainloop() 运行程序,单击界面上的“选择颜色”按钮,将可以看到如图323所示的对话框。 图323颜色选择对话框 当用户选择指定颜色,并单击颜色选择对话框中的“确定”按钮后,askcolor()函数会返回用户所选择的颜色,因此可以在控制台看到用户所选择的颜色。 3.3.6消息框 在messagebox模块下提示了大量工具函数用来生成各种消息框,在默认情况下,开发者在调用messagebox的工具箱函数时只要设置提示区的字符串即可,图标区的图标、按钮区的按钮都有默认设置。如果有必要,则完全可通过如下两个选项来定制图标和按钮。  icon: 定制图标的选项。该选项支持“error”“info”“question”“waring”这几个选项值。  type: 定制按钮的选项。该选项支持“abortretryignore”(取消、重试、忽略)、“ok”(确定)、“okcancel”(确定、取消)、“retrycancel”(重试、取消)、“yesno”(是、否)、“yesnocancel”(是、否、取消)这些选项。 【例319】演示messagebox工具的用法。 import tkinter as tk import tkinter.messagebox window = tk.Tk() window.title('消息框') window.geometry('200x200') def hit_me(): tk.messagebox.showinfo(title='Hi',message='显示消息框信息') tk.messagebox.showerror(title='Hi',message='提示消息框错误') tk.messagebox.showwarning(title='Hi', message='提示消息框警告') print(tk.messagebox.askokcancel(title='Hi',message='是、取消')) print(tk.messagebox.askquestion(title='Hi', message='确定')) print(tk.messagebox.askyesno(title='Hi', message='是、否')) print(tk.messagebox.askretrycancel(title='Hi', message='重试、取消')) print(tk.messagebox.askyesnocancel(title='Hi', message='是、否、取消')) tk.Button(window,text='请单击',command=hit_me).pack() window.mainloop() 运行程序,得到相应的对话框界面,如图324所示。 图324各种消息框 3.4菜单 Tkinter为菜单提供了Menu类。该类既可代表菜单条,也可代表菜单,还可代表上下文菜单(右键菜单)。简单来说,Menu类就可以搞定所有菜单相关内容。  add_command(): 添加菜单项。  add_checkbutton(): 添加复选框菜单项。  add_radiobutton(): 添加单选按钮菜单。  add_separator(): 添加菜单分隔条。 上面的前三个方法都用于添加菜单项,因此都支持如下常用选项。  label: 指定菜单项的文本。  command: 指定为菜单项绑定的事件处理方法。  image: 指定菜单项的图标。  compound: 指定在菜单项中图标位于文字的哪个方位。 菜单有两种用法,分别为在窗口上方通过菜单条管理菜单、通过鼠标右键触发右键菜单(上下文菜单)。 3.4.1窗口菜单 Menu(菜单)组件通常被用于实现应用程序上的各种菜单,由于该组件是底层代码实现,所以不建议自行通过按钮和其他组件来实现菜单功能。 【例320】创建一个顶级菜单,需要先创建一个菜单实例,然后使用add()方法将命令和其他子菜单添加进去。 import tkinter as tk root = tk.Tk() def callback(): print("~被调用~") #创建一个顶级菜单 menubar = tk.Menu(root) menubar.add_command(label = "文件", command = callback) menubar.add_command(label = "退出", command = root.quit) #显示菜单 root.config(menu = menubar) root.mainloop() 图325顶级菜单 运行程序,效果如图325所示。 如果要创建一个下拉菜单(或者其他子菜单),方法与例320的大同小异,最主要的区别是它们最后需要添加到主菜单上(而不是窗口上),例如: import tkinter as tk root = tk.Tk() def callback(): print("~被调用~") #创建一个顶级菜单 menubar = tk.Menu(root) #创建一个下拉菜单"文件",然后将它添加到顶级菜单中 filemenu = tk.Menu(menubar, tearoff=False) filemenu.add_command(label="打开", command=callback) filemenu.add_command(label="保存", command=callback) filemenu.add_separator() filemenu.add_command(label="退出", command=root.quit) 图326下拉菜单 menubar.add_cascade(label="文件", menu=filemenu) #创建另一个下拉菜单"编辑",然后将它添加到顶级菜单中 editmenu = tk.Menu(menubar, tearoff=False) editmenu.add_command(label="剪切", command=callback) editmenu.add_command(label="复制", command=callback) editmenu.add_command(label="粘贴", command=callback) menubar.add_cascade(label="编辑", menu=editmenu) #显示菜单 root.config(menu=menubar) root.mainloop() 运行程序,效果如图326所示。 3.4.2右键菜单 实现右键菜单很简单,程序只要先创建菜单,然后为目标组件的右键单击事件绑定处理函数,当用户单击鼠标右键时,调用菜单的post()方法即可在指定位置弹出右键菜单。 【例321】创建弹出式菜单。 import tkinter def makeLabel(): global baseFrame tkinter.Label(baseFrame, text="Python简单、易用、易维护").pack() baseFrame = tkinter.Tk() menubar = tkinter.Menu(baseFrame) for x in ['平等', '自由', '民主']: menubar.add_separator() menubar.add_command(label=x) menubar.add_command(label="和谐", command=makeLabel) #事件处理函数一定要至少有一个参数,且第一个参数表示的是系统事件 def pop(event): #注意使用 event.x 和 event.x_root 的区别 menubar.post(event.x_root, event.y_root) baseFrame.bind("", pop) baseFrame.mainloop() 运行程序,效果如图327所示。 图327右键菜单 3.5在Canvas中绘图 Canvas为Tkinter提供了绘图功能,其提供的图形组件包括线形、圆形、图片甚至其他控件。Canvas控件为绘制图形图表、编辑图形、自定义控件提供了可能。 3.5.1Canvas的绘图 Canvas组件的用法与其他GUI组件一样简单,程序只要创建并添加Canvas组件,然后调用该组件的方法来绘制图形即可。 【例322】演示最简单的Canvas绘图。 from tkinter import * #创建窗口 root = Tk() #创建并添加Canvas cv = Canvas(root, background='white') cv.pack(fill=BOTH, expand=YES) cv.create_rectangle(30, 30, 200, 200, outline='yellow', #边框颜色 stipple = 'question', #填充的位图 fill="red", #填充颜色 width=5 #边框宽度 ) cv.create_oval(250, 32, 340, 210, outline='red', #边框颜色 fill='pink', #填充颜色 width=4 #边框宽度 ) root.mainloop() 运行程序,效果如图328所示。 图328简单的Canvas绘图 从上面的程序可看出,Canvas提供了create_rectangle()方法绘制矩形和create_oval()方法绘制椭圆(包括圆,圆是椭圆的特例)。实际上,Canvas还提供了如下方法来绘制各种图形。  create_arc: 绘制弧。  create_bitmap: 绘制位图。  create_image: 绘制图片。  create_line(): 绘制直线。  create_polygon: 绘制多边形。  create_text: 绘制文字。  create_window: 绘制组件。 Canvas的坐标系统是绘图的基础,其中,点(0,0)位于Canvas组件的左上角,X轴水平向右延伸,Y轴垂直向下延伸。 绘制上面这些图形时需要简单的几何基础。  在使用create_line()绘制直线时,需要指定两个点的坐标,分别作为直线的起点和终点。  在使用create_rectangle()绘制矩形时,需要指定两个点的坐标,分别作为矩形左上角点和右下角点的坐标。 图329内切椭圆  在使用create_oval()绘制椭圆时,需要指定两个点的坐标,分别作为左上角点和右下角点的坐标来确定一个矩形,而该方法则负责绘制该矩形的内切椭圆,如图329所示。 从图329可以看出,只要矩形确定下来,该矩形的内切椭圆就能确定下来,而create_oval()方法所需要的两个坐标正是用于指定该矩形的左上角点和右下角点的坐标。  在使用create_arc()绘制弧时,和create_oval()的用法相似,因为弧是椭圆的一部分,因此同样也是指定左上角和右下角两个点的坐标,默认总是绘制从3点(0)开始,逆时针旋转90°的那一段弧。程序可通过start改变起始角度,也可通过extent改变转过的角度。  在使用create_polygon()绘制多边形时,需要指定多个点的坐标来作为多边形的多个定点。  在使用create_bitmap()、create_image()、create_text()、create_window()等方法时,只要指定一个坐标点,用于指定目标元素的绘制位置即可。 在绘制这些图形时可指定如下选项。  fill: 指定填充颜色(不指定,默认不填充)。  outline: 指定边框颜色。  width: 指定边框宽度,边框宽度默认为1。  dash: 指定边框使用虚线。该属性值既可为单独的整数,用于指定虚线中线段的长度; 也可为形如(4,1,3)格式的元素,此时4指定虚线中线段的长度,1指定间隔长度,3指定虚线长度。  stipple: 使用位图平铺进行填充。该选项可与fill选项结合使用。  style: 指定绘制弧的样式(仅对create_arc()方法起作用)。支持PIESLICE(扇形)、CHORD(弓形)、ARC(仅绘制弧)选项值。  start: 指定绘制弧的起始角度(仅对create_arc()方法起作用)。  extent: 指定绘制弧的角度(仅对create_arc()方法起作用)。  arrow: 指定绘制直线时两端是否有箭头。支持NONE(两端无箭头)、FIRST(开始端有箭头)、LAST(结束端有箭头)、BOTH(两端都有箭头)选项值。  arrowshape: 指定箭头形状。该选项是一个形如“30 20 10”的字符串,字符串中的三个整数依次指定填充长度、箭头长度、箭头宽度。  joinstyle: 指定直接连接点的风格。仅对绘制直线和多边形有效。支持METTER、ROUND、BEVEL选项值。  anchor: 指定绘制文字、GUI组件的位置。仅对create_text()、create_window()方法有效。  justify: 指定文字的对齐方式(仅对create_text()方法有效)。支持CENTER、LEFT、RIGHT常量值。 【例323】通过不同的方法来绘制不同的图形,这些图形分别使用不同的边框、不同的填充效果。 from tkinter import * #创建窗口 root = Tk() root.title('绘制图形项') #创建并添加Canvas cv = Canvas(root, background='white', width=830, height=830) cv.pack(fill=BOTH, expand=YES) columnFont = ('黑体', 17) titleFont = ('黑体', 19, 'bold') #使用循环绘制文字 for i, st in enumerate(['默认', '指定边宽', '指定填充', '边框颜色', '位图填充']): cv.create_text((130 + i * 140, 20),text = st, font = columnFont, fill='gray', anchor = W, justify = LEFT) #绘制文字 cv.create_text(10, 60, text = '绘制矩形', font = titleFont, fill='magenta', anchor = W, justify = LEFT) #定义列表,每个元素的4个值分别指定边框宽度、填充色、边框颜色、位图填充 options = [(None, None, None, None), (4, None, None, None), (4, 'pink', None, None), (4, 'pink', 'green', None), (4, 'pink', 'green', 'error')] #采用循环绘制5个矩形 for i, op in enumerate(options): cv.create_rectangle(130 + i * 140, 50, 240 + i * 140, 120, width = op[0], #边框宽度 fill = op[1], #填充颜色 outline = op[2], #边框颜色 stipple = op[3]) #使用位图填充 #绘制文字 cv.create_text(10, 160, text = '绘制椭圆', font = titleFont, fill='magenta', anchor = W, justify = LEFT) #定义列表,每个元素的4个值分别指定边框宽度、填充色、边框颜色、位图填充 options = [(None, None, None, None), (4, None, None, None), (4, 'pink', None, None), (4, 'pink', 'green', None), (4, 'pink', 'green', 'error')] #采用循环绘制5个椭圆 for i, op in enumerate(options): cv.create_oval(130 + i * 140, 150, 240 + i * 140, 220, width = op[0], #边框宽度 fill = op[1], #填充颜色 outline = op[2], #边框颜色 stipple = op[3]) #使用位图填充 #绘制文字 cv.create_text(10, 260, text = '绘制多边形', font = titleFont, fill='magenta', anchor = W, justify = LEFT) #定义列表,每个元素的4个值分别指定边框宽度、填充色、边框颜色、位图填充 options = [(None, "", 'black', None), (4, "", 'black', None), (4, 'pink', 'black', None), (4, 'pink', 'green', None), (4, 'pink', 'green', 'error')] #采用循环绘制5个多边形 for i, op in enumerate(options): cv.create_polygon(130 + i * 140, 320, 185 + i * 140, 250, 240 + i * 140, 320, width = op[0], #边框宽度 fill = op[1], #填充颜色 outline = op[2], #边框颜色 stipple = op[3]) #使用位图填充 #绘制文字 cv.create_text(10, 360, text = '绘制扇形', font = titleFont, fill='magenta', anchor = W, justify = LEFT) #定义列表,每个元素的4个值分别指定边框宽度、填充色、边框颜色、位图填充 options = [(None, None, None, None), (4, None, None, None), (4, 'pink', None, None), (4, 'pink', 'green', None), (4, 'pink', 'green', 'error')] #采用循环绘制5个扇形 for i, op in enumerate(options): cv.create_arc(130 + i * 140, 350, 240 + i * 140, 420, width = op[0], #边框宽度 fill = op[1], #填充颜色 outline = op[2], #边框颜色 stipple = op[3]) #使用位图填充 #绘制文字 cv.create_text(10, 460, text = '绘制弓形', font = titleFont, fill='magenta', anchor = W, justify = LEFT) #定义列表,每个元素的4个值分别指定边框宽度、填充色、边框颜色、位图填充 options = [(None, None, None, None), (4, None, None, None), (4, 'pink', None, None), (4, 'pink', 'green', None), (4, 'pink', 'green', 'error')] #采用循环绘制5个弓形 for i, op in enumerate(options): cv.create_arc(130 + i * 140, 450, 240 + i * 140, 520, width = op[0], #边框宽度 fill = op[1], #填充颜色 outline = op[2], #边框颜色 stipple = op[3], #使用位图填充 start = 30, #指定起始角度 extent = 60, #指定逆时针转过角度 style = CHORD) # CHORD指定绘制弓 #绘制文字 cv.create_text(10, 560, text = '仅绘弧', font = titleFont, fill='magenta', anchor = W, justify = LEFT) #定义列表,每个元素的4个值分别指定边框宽度、填充色、边框颜色、位图填充 options = [(None, None, None, None), (4, None, None, None), (4, 'pink', None, None), (4, 'pink', 'green', None), (4, 'pink', 'green', 'error')] #采用循环绘制5条弧 for i, op in enumerate(options): cv.create_arc(130 + i * 140, 550, 240 + i * 140, 620, width = op[0], #边框宽度 fill = op[1], #填充颜色 outline = op[2], #边框颜色 stipple = op[3], #使用位图填充 start = 30, #指定起始角度 extent = 60, #指定逆时针转过角度 style = ARC) #ARC指定仅绘制弧 #绘制文字 cv.create_text(10, 660, text = '绘制直线', font = titleFont, fill='magenta', anchor = W, justify = LEFT) #定义列表,每个元素的5个值分别指定边框宽度、线条颜色、位图填充、箭头风格、箭头形状 options = [(None, None, None, None, None), (6, None, None, BOTH, (20, 40, 10)), (6, 'pink', None, FIRST, (40, 40, 10)), (6, 'pink', None, LAST, (60, 50, 10)), (8, 'pink', 'error', None, None)] #采用循环绘制5条直线 for i, op in enumerate(options): cv.create_line(130 + i * 140, 650, 240 + i * 140, 720, width = op[0], #边框宽度 fill = op[1], #填充颜色 stipple = op[2], #使用位图填充 arrow = op[3], #箭头风格 arrowshape = op[4]) #箭头形状 root.mainloop() 程序中演示了Canvas中不同的create_xxx()方法的功能和用法,它们可用于创建矩形、椭圆、多边形、扇形、弓形、弧、直线、位图、图片和组件等。在绘制不同的图形时可指定不同的选项,从而实现丰富的绘制效果。运行程序,效果如图330所示。 图330绘制图形 掌握了上面的绘制方法后,已经可以实现一些简单的游戏了。 【例324】利用Canvas制作五子棋的图形界面。 该五子棋还需要根据用户的鼠标动作来确定下棋坐标,因此程序会为游戏界面的(左键单击)、(鼠标移动)、(鼠标移出)事件绑定事件处理函数。 from tkinter import * import random BOARD_WIDTH = 536 BOARD_HEIGHT = 538 BOARD_SIZE = 15 #定义棋盘坐标的像素值和棋盘数组之间的偏移距 X_OFFSET = 21 Y_OFFSET = 23 #定义棋盘坐标的像素值和棋盘数组之间的比率 X_RATE = (BOARD_WIDTH - X_OFFSET * 2) / (BOARD_SIZE - 1) Y_RATE = (BOARD_HEIGHT - Y_OFFSET * 2) / (BOARD_SIZE - 1) BLACK_CHESS = "●" WHITE_CHESS = "○" board = [] #把每个元素赋为"╋",代表无棋 for i in range(BOARD_SIZE) : row = ["╋"] * BOARD_SIZE board.append(row) #创建窗口 root = Tk() #禁止改变窗口大小 root.resizable(width=False, height=False) #设置窗口标题 root.title('五子棋') #创建并添加Canvas cv = Canvas(root, background='white', width=BOARD_WIDTH, height=BOARD_HEIGHT) cv.pack() bm = PhotoImage("board.png") cv.create_image(BOARD_HEIGHT/2 + 1, BOARD_HEIGHT/2 + 1, image=bm) selectedbm = PhotoImage("selected.gif") #创建选中框图片,但该图片默认不在棋盘中 selected = cv.create_image(-100, -100, image=selectedbm) def move_handler(event): #计算用户当前的选中点,并保证该选中点在0~14中 selectedX = max(0, min(round((event.x - X_OFFSET) / X_RATE), 14)) selectedY = max(0, min(round((event.y - Y_OFFSET) / Y_RATE), 14)) #移动红色选择框 cv.coords(selected,(selectedX * X_RATE + X_OFFSET, selectedY * Y_RATE + Y_OFFSET)) black = PhotoImage("black.gif") white = PhotoImage("white.gif") def click_handler(event): #计算用户的下棋点,并保证该下棋点在0~14中 userX = max(0, min(round((event.x - X_OFFSET) / X_RATE), 14)) userY = max(0, min(round((event.y - Y_OFFSET) / Y_RATE), 14)) #当下棋点没有棋子时,用户才能下棋子 if board[userY][userX] == "╋": cv.create_image(userX * X_RATE + X_OFFSET, userY * Y_RATE+Y_OFFSET, image=black) board[userY][userX] = "●" while(True): comX = random.randint(0, BOARD_SIZE - 1) comY = random.randint(0, BOARD_SIZE - 1) #如果计算机要下棋的点没有棋子时,才能让计算机下棋 if board[comY][comX] == "╋": break cv.create_image(comX * X_RATE + X_OFFSET, comY * Y_RATE + _OFFSET, image=white) board[comY][comX] = "○" def leave_handler(event): #将红色选中框移出界面 cv.coords(selected, -100, -100) #为鼠标移动事件绑定事件处理函数 cv.bind('', move_handler) #为鼠标单击事件绑定事件处理函数 cv.bind('', click_handler) #为鼠标移出事件绑定事件处理函数 cv.bind('', leave_handler) root.mainloop() 运行以上程序后,弹出如图331所示的界面。当用户鼠标在棋盘上移动时,该选择框显示用户鼠标当前停留在哪个下棋点上。 图331五子棋 程序在绘制黑色棋子和白色棋子的同时,也改变了底层代表棋盘状态的board列表的数据,这样既可记录下棋状态,从而让程序在后面可以根据board[]列表来判断胜负,也可加入人工智能,根据board[]列表来决定计算机的下棋点。 3.5.2绘制动画 在Canvas中实现动画时要添加一个定时器,周期性地改变界面上图形面的颜色、大小、位置等选项,用户看上去就是所谓的“动画”。 【例325】以一个简单的桌面弹球游戏来介绍使用Canvas绘制动画。使用Tkinter Canvas控件生成一个小球,并在画布上反复随机滚动,并交替更换颜色。 from tkinter import * import time from random import randint, seed class Ball(): def __init__(self, canvas, x1, y1, x2, y2, max_x,max_y): self.x1 = x1 self.y1 = y1 self.x2 = x2 self.y2 = y2 self.max_x = max_x self.max_y = max_y self.center_x =0 self.center_y=0 self.canvas = canvas self.ball_color = 1 #(x1,y1)和(x2,y2)分别对应圆的左上角和右下角坐标,也就是定义了圆的大小 self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="blue") def move_ball(self): deltax = randint(0,10) deltay = randint(0,15) self.center_x += deltax self.center_y += deltay if self.center_x > self.max_x or self.center_y > self.max_y: print('ball disapear') canvas.delete(self.ball) if self.ball_color == 1: self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="red") self.ball_color = 2 else: self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="blue") self.ball_color=1 self.center_x =0 self.center_y=0 else: self.canvas.move(self.ball, deltax, deltay) self.canvas.after(200, self.move_ball)#定期自动更新 root = Tk() root.title("循环球") root.resizable(False,False) canvas = Canvas(root, width = 300, height = 300) canvas.pack() seed(time.time()) ball = Ball(canvas, 0, 0, 20, 20,300,300) #创建ball实体 ball.move_ball() #开始移动球 root.mainloop() 运行程序,效果如图332所示。 图332小球循环运动