550 likes | 879 Views
计算机编程导论 - Python 语言. 第 8 讲 图形用户界面. 讲课教师:常姗 2014. 5. 20. 程序的用户界面. 程序中与用户进行交互的部分 用户通过 UI 向程序输入数据或者请求程序执行特定任务 程序通过 UI 向用户显示各种信息 UI 应该是 用户友好的 两类 UI 命令行界面 图形界面( GUI). GUI 构件 / 控件 / 组件. 窗口 Window 是容器:可以 容纳其他构件 的构件 常用操作:移动,改变大小等 程序有一个根窗口 标签 Label 按钮 Button 菜单 Menu 框架 Frame :分隔窗口空间
E N D
计算机编程导论 -Python语言 第8讲 图形用户界面 讲课教师:常姗 2014. 5. 20
程序的用户界面 • 程序中与用户进行交互的部分 • 用户通过UI向程序输入数据或者请求程序执行特定任务 • 程序通过UI向用户显示各种信息 • UI应该是用户友好的 • 两类UI • 命令行界面 • 图形界面(GUI)
GUI构件/控件/组件 • 窗口Window • 是容器:可以容纳其他构件的构件 • 常用操作:移动,改变大小等 • 程序有一个根窗口 • 标签Label • 按钮Button • 菜单Menu • 框架Frame:分隔窗口空间 • 也是容器 基本界面元素
构件之间的关系 • 父子关系 • 在一个构件内创建另一个构件 • 界面上全体构件形成一个层次结构 • 顶层是根窗口 • 设计图形界面,需要指明构件间的父子关系 • 父构件内安排子构件:使用布局管理器
事件驱动 • GUI程序的执行流程取决于与用户的实时交互 • 事件驱动编程 • 普通程序的执行:启动——做事——终止 • 事件驱动程序的执行:启动——事件循环 • 等待事件发生,然后处理 • 程序的终止也是由特定事件(如关闭窗口事件)引起的
基本概念 • 事件:例如点击鼠标,按下键盘等. • 事件处理程序:对事件进行处理的代码. • 事件驱动编程:一种编程范型 • 程序有一个事件循环,循环的做两件事: • 事件监测 • OS功能,由工具包提供支持 • 事件处理 • 应用程序员只需编写自己的事件处理程序
GUI编程 • 需要GUI工具包 • 将低层细节对程序员隐藏 • 跨平台 • Python标准库:Tkinter • GUI编程 • 设计界面外观 • 为每个构件定义事件处理代码 • 建立启动初始化和总控部分
Tkinter的常用构件类 • Button • Canvas • Checkbutton • Entry • Frame • Label • Listbox • Menu • Message • Radiobutton • Text • Toplevel
最简单的Tkinter程序 • 导入Tkinter • 建立根窗口 • 进入事件循环 • 例:eg8_1.py from Tkinter import * root = Tk() root.mainloop()
在窗口中添加构件 • 两步: • 创建 • 布局 • 例:eg8_2.py from Tkinter import * root = Tk() aLabel = Label(root,text="Hello World") aLabel.pack() root.mainloop()
在窗口中添加构件 • 构件对象有很多属性(实例变量): • 创建时设置属性的值 • 属性一般都有缺省值 • 用“关键字参数"方式设置较方便:属性=属性值 • 创建时不设置,将来再设置或修改 • 利用构件类的config方法: 对象.config(属性=属性值) • 对象视为字典: 对象[属性]=属性值
根窗口 • 根窗口的标题设置 root.title("My GUI") • 缺省值为"Tk" • 根窗口的尺寸设置 root.geometry("400x400") • 缺省值为200x200
构件间的父子关系 • GUI中的所有构件按父子关系构成树状层次结构 • 每个构件都有master和children属性 • Tkinter自动维护这两个属性的值 • 编程时可利用这两个属性,例如: aLabel.master.title("My GUI")
界面设计过程 • 根据需要创建多个构件,然后在窗口中进行布局. • 例:eg8_3.py from Tkinter import * root = Tk() aLabel = Label(root,text="Hello World") aLabel.pack() aButton = Button(root,text="Click Me") aButton.pack() root.mainloop()
常用构件:标签 • 类:Label lb = Label(窗口,选项设置) • text:标签文本内容 • width:标签宽度 • fg:前景色(文本颜色) • 例如 >>> aLabel = Label(root,text="Hello World") >>> aLabel.pack() >>> Label(root,text='red color',fg='red').pack() • 注意写法:对象创建与方法调用合二为一
常用构件:按钮 • 类:Button btn = Button(窗口,选项设置) • text:按钮上的文本 • command:点击按钮时要执行的命令 • 需要提供一个函数或方法名f • 注意不是函数调用f() • height,width • fg,bg • relief: “flat/groove/raised/ridge/solid/sunken” • state: “active/disabled/normal”
例:按钮 • 下例中按钮对应的函数是root对象的方法quit >>> b=Button(root,text="Quit",command=root.quit) >>> b.pack() >>> root.mainloop() # 点击按钮退出主循环,回到>>> • 下例中按钮对应的函数是自定义函数 >>> def hiButton(): ... print 'hi there' ... >>> Button(root,text='print',command=hiButton).pack()
常用构件:勾选钮 • 类:Checkbutton cb = Checkbutton(窗口,选项设置) • text >>> Checkbutton(root,text="Math").pack() >>> Checkbutton(root,text="Python").pack() >>> Checkbutton(root,text="English").pack() >>> v= IntVar() >>> Checkbutton(root,text="Check",variable=v).pack() • variable: IntVar类型的控制变量 • 程序中可以通过v.get()和v.set()来查询或设置勾选钮的状态
常用构件:单选钮 • 类:Radiobutton rb = Radiobutton(窗口,选项设置) • value:每个单选钮对应一个值,用于控制变量 • variable: IntVar类型的控制变量 • 一组单选钮共用一个控制变量 >>> v = IntVar() >>> v.set(1) >>> Radiobutton(root,text="One",variable=v,value=1).pack() >>> Radiobutton(root,text="Two",variable=v,value=2).pack() >>> Radiobutton(root,text="Three",variable=v,value=3).pack()
常用构件:文本编辑区 • 类Entry:单行文本编辑 e = Entry(窗口,选项设置) • textvariable: StringVar类型的控制变量 • 例如 >>> v = StringVar() >>> e = Entry(root,textvariable = v) >>> e.pack() >>> print v.get() hello >>> v.set('new text') 20
常用构件:框架 • 类Frame:构件容器,用于窗口分隔 f = Frame(窗口,选项设置) • width,height,bd,relief • 框架中的子构件独立地进行布局 • 例如 >>> f = Frame(root,bd=4,relief="groove") >>> f.pack() >>> Checkbutton(f,text="Math").pack() >>> Checkbutton(f,text="Python").pack() >>> Checkbutton(f,text="English").pack()
常用构件:菜单 • 类Menu,创建菜单构件: • 创建一个菜单构件实例 • 与某个窗口(根窗口或者顶层窗口)进行关联 • 为该菜单添加菜单项。 • 简单命令:add_command • 级联式菜单:add_cascade • 勾选钮:add_checkbutton • 一组单选钮:add_radiobutton • 例如 >>> m = Menu(root) >>> root.config(menu = m) >>> m.add_command(label="File") >>> m.add_command(label="Help")
菜单编程例:eg8_4.py def callback(): print "hello from menu" root = Tk() m = Menu(root) root.config(menu = m) filemenu = Menu(m) m.add_cascade(label="File", menu=filemenu) filemenu.add_command(label="New", command=callback) filemenu.add_command(label="Open...", command=callback) filemenu.add_separator() filemenu.add_command(label="Exit", command=callback) helpmenu = Menu(m) m.add_cascade(label="Help", menu=helpmenu) helpmenu.add_command(label="About...", command=callback) 以m为父构件创建菜单构件filemenu 将filemenu设为m的菜单项File的子菜单 添加分割线
常用构件:顶层窗口 • 类Toplevel • 顶层窗口是根窗口的子构件,但有一定的独立性:可以移动,改变大小. • 根窗口只有一个,顶层窗口可有多个. • 例如 >>> root = Tk() >>> Label(root,text="hello").pack() >>> top = Toplevel() >>> Label(top,text="world").pack() >>> top.title('hello toplevel') >>> top.geometry('400x300') 无需指明其父构件 24
布局 • 布局:界面元素在界面中的位置安排. • 布局管理器:程序员不需要了解底层显示系统的细节, 在较高层次上考虑界面布局 • 构件都是先创建,再布局: w = Constructor(parent,...) w.GeometryManager(...) • Tkinter提供三种布局管理器:Pack,Grid和Place
Pack布局管理器 • Pack以紧凑方式将构件在窗口中“打包” • 设想窗口由弹性材料制成,放入构件时先把窗口空间撑大到足够容纳该构件,然后将构件紧贴内部的某条边(缺省是顶边)放入. • 缺省情形下放入同一个窗口的所有构件是沿垂直方向自顶向下一个紧贴一个进行布置的 • 可以通过pack方法的side选项设置成沿水平方向打包. • pack_forget() • 将用pack()布局的构件从界面拿掉,变为不可见。
例:Pack布局管理器 >>> Label(root,text="Input a number:").pack() >>> Entry(root).pack() >>> Button(root,text="OK").pack() >>> Label(root,text="Input a number:").pack(side="left") >>> Entry(root).pack(side="left") >>> Button(root,text="OK").pack(side="left") 贴左放置
Grid布局管理器 • Grid将窗口或框架视为由行和列构成的二维表格,并将构件放入行列交叉处的单元格中 • 根据构件大小自动调整行列尺寸:列宽由该列中最宽构件决定,行高由该行最高的构件决定. • 构件都有grid()方法 • 选项row和column指定行列编号 • 从0开始编号 • row的缺省值为当前下一空行,column的缺省值总为0 • 选项sticky:指定构件在单元格内的对齐方式(方位) • 方位值用"+"组合:延伸构件尺寸以填满单元格 • Sticky=E+W • 选项rowspan和columnspan:占据多个单元格
例:Grid布局管理器 >>> Label(root,text="ID Number:").grid() # 0,0 >>> Label(root,text="Name:").grid() # 1,0 >>> Entry(root).grid(row=0,column=1) >>> Entry(root).grid(row=1,column=1)
例:Grid布局管理器 >>> Label(root,text="ID Number:").grid(sticky=E) >>> Label(root,text="Name:").grid(sticky=E) >>> Entry(root).grid(row=0,column=1) >>> Entry(root).grid(row=1,column=1) >>> Checkbutton(root,text="Registered User").grid( ... columnspan=2,sticky=W) >>> Label(root,text="X").grid(row=0,column=2, ... columnspan=2,rowspan=2,sticky=W+E+N+S) >>> Button(root,text="Zoom In").grid(row=2,column=2) >>> Button(root,text="Zoom Out").grid(row=2,column=3)
例:Grid布局管理器 >>> f1 = Frame(root,width=100,height=100,bd=4,relief="groove") >>> f1.grid(row=1,column=1,rowspan=2,sticky=N+S+W+E) >>> Checkbutton(f1,text="PC").grid(row=1,sticky=W) >>> Checkbutton(f1,text="Laptop").grid(row=2,sticky=W) >>> f2 = Frame(root,width=100,height=50,bd=4,relief="groove") >>> f2.grid(row=1,column=2,columnspan=2,sticky=N) >>> b1 = Button(root,text="OK",width=6) >>> b1.grid(row=2,column=2,sticky=E+W,padx=2) >>> b2 = Button(root,text="Cancel",width=6) >>> b2.grid(row=2,column=3,sticky=E+W,padx=2)
Tkinter事件驱动编程 • 事件:针对应用程序所发生的事情,并且需要应用程序对它做出响应或进行处理。 • Tkinter事件可以用特定形式的字符串来描述,称为事件模式,一般形式为: "<修饰符-类型符-细节符>" • 类型符:指定事件类型,如Button和Key • 修饰符(可选):描述双击,组合键等 • 细节符(可选):指定具体鼠标或键盘按键 • 简化形式,例如"<Double-Button-1>"
常用鼠标事件 • <ButtonPress-1>或<Button-1>或<1>:单击左键 • 类似有<Button-2>和<Button-3> • <B1-Motion>:按下左键并移动 • 类似有<B2-Motion>和<B3-Motion> • <Double-Button-1>:双击左键 • <Enter>和<Leave>:鼠标指针进入/离开构件区域
常用键盘事件 • <Key-a>或a:按下字母a • 可打印字符都类似,但空格是<space>,小于号是<less> • 注意:1和<1>不同! • <Return>:按下回车键 • 非可打印字符都类似,如:<Tab>, <Shift_L>, <Control_R>, <Up>, <Down>, <F1>等 • <Key>:按下任意键 • <Shift-Up>:同时按下Shift和↑键 • 类似的有Alt组合、Ctrl组合 鼠标事件
事件对象 • 每个事件都创建一个事件对象(Event类的实例),并将该对象传递给事件处理函数 • 事件对象具有若干描述事件的属性: • x和y:鼠标点击位置(相对于构件左上角) • x_root和y_root:鼠标点击位置(相对于屏幕左上角) • num:点击的鼠标键号(1、2、3) • char:按下的字符(ASCII);如果按下特殊键则为空串 • keysym:按下的字符(ASCII);如果按下特殊键则为该键的名称(是个字符串)
事件处理 • GUI应用程序在建立图形界面等初始化工作后进入事件循环,等待事件发生并触发绑定的事件处理程序 • mainloop()方法:进入事件循环 • 绑定:建立事件与事件处理程序间的对应
构件绑定事件 • 语法: <构件实例>.bind(<事件描述符>,<事件处理程序>) • 语义:若针对<构件实例>发生了与<事件描述符>相匹配的事件,则调用<事件处理程序> • 调用事件处理程序时,系统传递Event对象作为实参. • <事件处理程序>一般是由用户自定义的函数. • 这种函数在应用程序中定义但不由应用程序调用,而是由系统调用,一般称为回调(callback)函数 • GUI应用程序可封装为类,这时事件处理程序常定义为应用程序类的方法. (详后)
例:框架绑定鼠标事件 • eg8_6.py from Tkinter import * def callback(event): print "clicked at",event.x,event.y root = Tk() f = Frame(root,width=100,height=100) f.bind("<Button-1>",callback) f.pack() root.mainloop()
例:按钮绑定键盘事件 • 按键时,界面中哪个构件响应该键盘事件? • 占有界面焦点的构件,例eg8_7.py from Tkinter import * def printInfo(event): print "pressed", event.char root = Tk() b = Button(root,text = 'Press any key') b.bind('<Key>',printInfo) b.focus_set() b.pack() root.mainloop() 设置焦点位置
例:绑定多个事件 • 一个构件可以响应多种事件 • 例,同时响应鼠标和键盘,eg8_8.py: from Tkinter import * def callback1(event): print "pressed", event.char def callback2(event): f.focus_set() print "clicked at", event.x, event.y root = Tk() f = Frame(root,width=100,height=100) f.bind("<Key>",callback1) f.bind("<Button-1>",callback2) f.pack() root.mainloop()
应用程序作为对象 • GUI编程经常将整个应用程序封装成类 • 在类中建立图形界面,定义事件处理方法. • 好处 • 事件处理函数是类的方法,自然可以访问类中所有实例变量 • 只要将构件存储为实例变量,就能实现处理代码与图形界面的“无缝集成”. • Tkinter编程时,根据需要可有多种方式建立程序主窗口 • 独立创建根窗口 • 依赖外部跟窗口 • ……
例:程序类独立创建根窗口 class MyApp: def __init__(self): root = Tk() b = Button(root,...) ... root.mainloop() def f(): ... app = MyApp() 如果一个构件具有“全局性”,即多个方法都要访问该元素,那么就需用一个实例变量来存储(引用)这个构件
例:程序类依赖外部根窗口 class MyApp: def __init__(self,master): f = Frame(master,...) b = Button(f,...) ... root = Tk() app = MyApp(root) root.mainloop()
例:myapp.py (eg8_11) class MyApp: def __init__(self): self.root = Tk() self.t = Label(self.root,text="Spam") self.t.pack() Button(self.root,text="Play",command=self.changeText).pack() Button(self.root,text="Quit",command=self.root.quit).pack() self.root.mainloop() self.root.destroy() def changeText(self): if self.t["text"] == "Spam": self.t["text"] = "Egg" else: self.t["text"] = "Spam" app = MyApp() 关闭根窗口
模型-视图(MV)设计方法 • 复杂程序常可分解成核心逻辑和用户界面两部分 • 模型:为应用问题建模,管理应用问题的数据和行为,对来自UI的数据请求或数据更新指令进行响应 • 视图:负责显示模型的当前数据状态,响应用户的交互动作 • 优点:模型和视图相互独立,可以分开设计和测试 controller view model MVC
编程案例:汇率换算器 • 设计图形界面的汇率换算器,实现外币和人民币间换算。 • 程序规格 • 汇率换算器程序用于在外币与人民币之间进行换算。 • 输入:外币币种,换算方向,金额 • 输出:等值的目标币种金额
编程案例:汇率换算器 • 将应用程序模型部分封装成一个类CCApp, • 该类的实例需要记录当前的汇率信息 • 需要实现构造器方法__init__ • 程序启动方法run。 • 将界面也封装成对象xInterface。
编程案例:汇率换算器 • 实现模型 class CCApp: def __init__(self, inter): self.xRate = {'USD':6.306, 'Euro':8.2735, 'Yen':0.0775, 'Pound':10.0486} self.interface = inter def run(self): while True: gFlag,to,fc,amount = self.interface.getInfo() if gFlag: break elif to == 'RMB': result = amount * self.xRate[fc] else: result = amount / self.xRate[fc] self.interface.showInfo(result) 代表程序的界面(尚未设计) gFlag:退出标志 to:换算方向 fc:换算的外币 amount:换算的金额
编程案例:汇率换算器 • 设计界面 • 选择要换算的外币种类,每次处理一种外币,用单选钮实现; • 输入和显示外币及等价人民币的金额,可用两个录入框实现; • 双向换算和退出用三个命令按钮实现。
编程案例:汇率换算器 • gui.py from Tkinter import * class GUInterface: def __init__(self): self.root = Tk() self.root.title("Currency Converter") self.qFlag = False # Quit flag self.fc = StringVar() # foreign currency selected self.fc.set('USD') self.to = 'RMB' # convert to? self.amt = 0 # amount to be converted self.aRMB = StringVar() # amount of RMB self.aRMB.set('0.00') self.aFC = StringVar() # amount of foreign currency self.aFC.set('0.00')