8.4.1 将 GUI 应用程序封装成对象

GUI 编程的一个常用技术是将整个应用程序封装成一个类,在应用程序类中建立图形界 面并处理各种交互事件。具体来说,GUI 应用程序类应该首先创建一个主窗口,并在其中布 置所需的各种构件,然后再为各个构件编写事件处理程序(都是类的方法)。这种做法的好处 是:由于事件处理函数都定义为应用类的方法,而类的方法很自然地能访问类中的实例变量, 所以只要我们将界面中的各种构件也存储为实例变量,就能实现程序的处理代码与程序的图 形界面进行“无缝集成”。

在用 Tkinter 编程时,根据需要可以有多种方式来建立程序主窗口:

(1)在应用程序类中创建自己的根窗口,即程序自成体系。代码大致形如:

  1. class MyApp:
  2. def __init__ (self):
  3. root = Tk()
  4. b = Button(root,...)
  5. ...
  6. root.mainloop()
  7. app = MyApp()

(2)程序主窗口是程序类外部的某个窗口的子构件,该外部窗口在创建程序实例时作为 参数传递给构造器。例如:

  1. class MyApp:
  2. def __init__ (self,master):
  3. f = Frame(master,...)
  4. b = Button(f,...)
  5. ...
  6. root = Tk()
  7. app = MyApp(root) root.mainloop()

(3)将应用程序类定义为框架构件类的子类,即程序就是窗口,窗口就是程序。如:

  1. class MyApp(Frame):
  2. def __init__ (self):
  3. Frame. __init__ (self) # 先用父类的构造器进行初始化
  4. b = Button(self,...)
  5. ...
  6. app = MyApp() app.mainloop()

在应用程序类的设计中,如果一个构件具有“全局性”,即多个方法都要访问该元素,那 么就需用一个实例变量来存储(引用)这个构件,因为类的实例变量在所有类方法中都可访 问,而局部变量只在某一个方法中可见。

作为例子,我们定义一个应用程序类 MyApp,该程序的用户界面包括窗口、标签和按钮。 我们采用上述第一种方式,即程序创建自己的根窗口。根窗口和标签构件被存储为实例变量 self.root 和 self.t,以便 MyApp 类的所有方法都能引用它们;两个按钮则被存储为局部变量 b1 和 b2,这样在其他方法中是不能引用它们的。

【程序 8.11】myapp.py

  1. from Tkinter import *
  2. class MyApp:
  3. def __init__ (self):
  4. self.root = Tk()
  5. self.root.title("My App")
  6. self.t = Label(self.root,text="Spam")
  7. self.t.pack()
  8. b1 = Button(self.root,text="Play",command=self.changeText)
  9. b2 = Button(self.root,text="Quit",command=self.root.quit)
  10. b1.pack()
  11. b2.pack()
  12. self.root.mainloop()
  13. self.root.destroy()
  14. def changeText(self):
  15. if self.t["text"] == "Spam":
  16. self.t["text"] = "Egg"
  17. else:
  18. self.t["text"] = "Spam"
  19. app = MyApp()

程序 8.11 定义了类 MyApp,在其构造器 init__中首先创建根窗口 root,然后添加一个 标签和两个按钮。点击按钮 b1 时的回调函数是类 MyApp 中自定义的方法 changeText(功能 是改变标签的文本),点击 b2 时的回调函数是根窗口的内建方法 quit(退出事件循环)。标签 构件必须作为实例变量存储,因为 init 和 changeText 方法都要引用它;而根窗口和两个按 钮在本例中既可以作为实例变量存储,也可以作为局部变量存储。创建各构件并完成布局之 后进入事件循环,等待处理事件。

类只是一个定义,封装成类的应用程序如何执行呢?我们通常会为应用程序类定义一个 专门的启动方法 run,将来创建应用程序对象后通过调用对象的 run 方法来启动程序功能。本 例中 MyApp 程序对象 app 一经创建就自动进入程序主循环,这是因为我们将所有启动代码 包括 mainloop 都放在构造器 init之中的缘故。程序启动后,点击 Play 按钮可以看到标签 内容在“Spam”和“Egg”之间切换。点击 Quit 按钮将退出事件循环,从而执行 init的最 后一条语句 root.destroy 关闭根窗口①。

作为练习,读者可以用上述第二、三种方式来改写程序 8.11。