几个开发GUI程序的注意点:
1.了解用户需求,获取他们的反馈信息。要多获取用户反馈,并不断更正设计。
2.GUI应该尽量简单,让功能很容易访问。
3.重要的功能突出显示。
4.隐藏高级功能,可能的话,在GUI创建专家模式,在此模式中才会显示高级功能。
5.默认显示中不要放太多的东西,减少干扰。
6.界面尽量符合操作系统风格,让用户能预测下一步的行为。
7.GUI应该具有视觉吸引性和清晰力。
pycharm技巧:有时候需要输入很长的代码,比如 if __name__ == '__main__': ,这时候手动输入不如直接 Command + J ,就可以直接插入常用代码了。
tkinter为内置GUI模块,例子:
from tkinter import Tk, Label, Button, LEFT, RIGHT if __name__ == '__main__': mainwin = Tk() # 实例化Tk类创建一个应用程序窗口 mainwin.geometry("140x40") # 将窗口大小指定为宽*高 lbl = Label(mainwin, text="Hello world", bg="yellow") # 标签,内容,背景色 lbl.pack(side=LEFT) # 靠左 exit_button = Button(mainwin, text="退出", command=mainwin.destroy) # 按钮,内容,功能 exit_button.pack(side=RIGHT) # 靠右 mainwin.mainloop() # 主程序的启动是通过调用mainloop()方法开始
如果程序较大,最好改用面对对象编程,上述代码改为:
class MyGame: def __init__(self, mainwin): lbl = Label(mainwin, text="Hello world", bg="yellow") exit_button = Button(mainwin, text="退出", command=self.exit_btn_callback) #按钮退出事件函数改为调用exit_btn_callback lbl.pack(side=LEFT) exit_button.pack(side=RIGHT) def exit_btn_callback(self): #定义按钮回调功能 mainwin.destroy() if __name__ == '__main__': mainwin = Tk() mainwin.geometry("140x40") game_app = MyGame(mainwin) #实例化一个包含主逻辑以及控件的类 mainwin.mainloop()
Tkinter中几种重要的类:Menu菜单类、Frame容器类(用作容纳其他空间的容器)、Canvas画板类、Label标签类、Button按钮类、RadioButton单选按钮类、CheckButton开关按钮类、ListBox选单类、
Entry文本输入控件。
在tkinter中,布局管理器通过几何管理器来实现的。几何管理器常用的有grid管理器、网格管理器等。
bind()方法:它将事件绑定到特定的控件实例。
# -*- coding: utf-8 -*- from tkinter import Tk, Label, Button, LEFT, RIGHT def exit_btn_callback(evt): print("在exit_btn_callback内,事件对象是:", evt) mainwin.destroy() if __name__ == '__main__': mainwin = Tk() mainwin.geometry("140x40") # 注意是x而不是* lbl = Label(mainwin, text='Hello World!', bg='yellow') lbl.pack(side=LEFT) exit_button = Button(mainwin, text='exit') exit_button.bind("<Button-1>", exit_btn_callback) # 将exit_btn_callback绑定Button-1即鼠标左键 exit_button.pack(side=RIGHT) mainwin.mainloop()
黄色代码Button-1表示在控件上按下鼠标左键。
bind_all方法表示将事件绑定到应用程序级别的所有控件。例如你想从运行游戏的任意时刻退出。可以绑定事件。
以下编写一个访问屋子的游戏,如果遇到敌人则失败,否则胜利:
# -*- coding: utf-8 -*- # @Time : 2020/10/29 11:39 # @Author : Zhenghui Lyu # @FileName: main.py # @Email : lzhfootmark@163.com # @Software: PyCharm # @Blog :https://www.cnblogs.com/ZhenghuiLyu import sys import random from tkinter import messagebox, Tk, Label, Radiobutton, PhotoImage, IntVar class Hutgame: def __init__(self, parent): self.village_image = PhotoImage(file="Jungle_small.gif") self.hut_image = PhotoImage(file="Hut_small.gif") # 图片tkinter中 self.hut_width = 40 self.hut_height = 56 self.container = parent # container---parent self.huts = [] self.result = "" self.occupy_huts() self.setup() def occupy_huts(self): # 占据五个屋子 occupants = ['敌人', '朋友', '无人'] # 占房子元素类型 while len(self.huts) < 5: computer_choice = random.choice(occupants) # 选择一个元素 self.huts.append(computer_choice) # 5个房子随机分配,注意追加,否则死循环 print("房子占据情况为:", self.huts) # 打印全部屋中内容 def radio_btn_pressed(self): self.enter_hut(self.var.get()) def create_widgets(self): # 程序创建 self.var = IntVar() # 整数变量,选定单选按钮时,这个值被分配到self.var,会根据选择的按钮分配不同值 self.background_label = Label(self.container, image=self.village_image) # 背景图片 txt = "选择一间屋子进入,如果屋子无人占领或被朋友占领,则你胜利。" self.info_label = Label(self.container, text=txt, bg="yellow") r_btn_config = {'variable': self.var, 'bg': "#A8884C", "activebackground": 'yellow', "image": self.hut_image, 'height': self.hut_height, 'width': self.hut_width, 'command': self.radio_btn_pressed} # 按钮通用设置,按下激活radio_btn_pressed功能 self.r1 = Radiobutton(self.container, r_btn_config, value=1) self.r2 = Radiobutton(self.container, r_btn_config, value=2) # 设置每个按钮标识,配置,值 self.r3 = Radiobutton(self.container, r_btn_config, value=3) self.r4 = Radiobutton(self.container, r_btn_config, value=4) self.r5 = Radiobutton(self.container, r_btn_config, value=5) def setup_layout(self): self.container.grid_rowconfigure(1, weight=1) # 权重影响行列相对其他行所占空间,默认0表示不会增长 self.container.grid_columnconfigure(0, weight=1) # 列0配置 self.container.grid_columnconfigure(4, weight=1) # 列4配置 self.background_label.place(x=0, y=0, relwidth=1, relheight=1) # place()为几何管理器,设置父控件高度宽度,1表示和主窗口相同 self.info_label.grid(row=0, column=0, columnspan=5, sticky="nsew") # nsew表示让空间贴上下左右四个边缘,占满整个单元格 self.r1.grid(row=1, column=0) self.r2.grid(row=1, column=4) self.r3.grid(row=2, column=3) self.r4.grid(row=3, column=0) self.r5.grid(row=4, column=4) # 各按钮位置(屋子位置) def setup(self): self.create_widgets() self.setup_layout() def enter_hut(self, hut_number): print("进入房子#", hut_number) hut_occupant = self.huts[hut_number - 1] print("房子占据类型是:", hut_occupant) if hut_occupant == '敌人': self.result = "看见敌人在屋子# %d\n\n" % hut_number self.result += "你输了:( 祝下次好运。" elif hut_occupant == '无人': self.result = "屋子#%d无人占据\n\n" % hut_number self.result += "恭喜,你赢了!" else: self.result = "看见朋友在屋子# %d\n\n" % hut_number self.result += "恭喜,你赢了!" self.announce_winner(self.result) def announce_winner(self, data): messagebox.showinfo("宣告结果", message=data) if __name__ == '__main__': mainwin = Tk() # 初始化GUI窗口 WIDTH = 494 HEIGHT = 307 mainwin.geometry("%sx%s" % (WIDTH, HEIGHT)) mainwin.resizable(0, 0) mainwin.title("兽人之袭") game_app = Hutgame(mainwin) mainwin.mainloop()
上述代码实现后,功能确实实现了,但代码显得很杂乱。最好能构建MVC(即模型M,视图V,控制器C)模式。
模型-----核心业务逻辑和数据,模型不管视图和控制器的事儿。
视图-----用户界面,显示给用户。
控制器-----在模型和视图之间,进行信息交互。
MVC的好处:有些用户界面不同但业务逻辑相同,就可以复用模型的代码。用户界面开发人员可以专注UI,逻辑实现人员也可以专注自己的事。
当这几个模块相对独立时,他们如何互相交互呢?一种方法是采用订阅方式。类似订报纸,你发布了新报纸,我和他如果订阅过此报纸,即可收到变化信息。借助pypubsub模块可引入订阅模式。
将之前的游戏代码优化为三个类:视图类、控制器类、模型类:
# -*- coding: utf-8 -*- # @Time : 2020/10/29 16:29 # @Author : Zhenghui Lyu # @FileName: mvc.py # @Email : lzhfootmark@163.com # @Software: PyCharm # @Blog :https://www.cnblogs.com/ZhenghuiLyu import sys import random from tkinter import messagebox, Tk, Label, Radiobutton, PhotoImage, IntVar from pubsub import pub class Model: def __init__(self): self.huts = [] self.result = "" self.occupy_huts() def occupy_huts(self): # 占据五个屋子 occupants = ['敌人', '朋友', '无人'] # 占房子元素类型 while len(self.huts) < 5: computer_choice = random.choice(occupants) # 选择一个元素 self.huts.append(computer_choice) # 5个房子随机分配,注意追加,否则死循环 print("房子占据情况为:", self.huts) # 打印全部屋中内容 def enter_hut(self, hut_number): print("进入房子#", hut_number) hut_occupant = self.huts[hut_number - 1] print("房子占据类型是:", hut_occupant) if hut_occupant == '敌人': self.result = "看见敌人在屋子# %d\n\n" % hut_number self.result += "你输了:( 祝下次好运。" elif hut_occupant == '无人': self.result = "屋子#%d无人占据\n\n" % hut_number self.result += "恭喜,你赢了!" else: self.result = "看见朋友在屋子# %d\n\n" % hut_number self.result += "恭喜,你赢了!" pub.sendMessage('result', data=self.result) #通知Controller实例 class View: def __init__(self, parent): self.village_image = PhotoImage(file="Jungle_small.gif") self.hut_image = PhotoImage(file="Hut_small.gif") self.container = parent self.hut_width = 40 self.hut_height = 56 self.radio_btn_pressed = None def setup(self): self.create_widgets() self.setup_layout() def create_widgets(self): # 程序创建 self.var = IntVar() # 整数变量,选定单选按钮时,这个值被分配到self.var,会根据选择的按钮分配不同值 self.background_label = Label(self.container, image=self.village_image) # 背景图片 txt = "选择一间屋子进入,如果屋子无人占领或被朋友占领,则你胜利。" self.info_label = Label(self.container, text=txt, bg="yellow") r_btn_config = {'variable': self.var, 'bg': "#A8884C", "activebackground": 'yellow', "image": self.hut_image, 'height': self.hut_height, 'width': self.hut_width, 'command': self.radio_btn_pressed} # 按钮通用设置,按下激活radio_btn_pressed功能 self.r1 = Radiobutton(self.container, r_btn_config, value=1) self.r2 = Radiobutton(self.container, r_btn_config, value=2) # 设置每个按钮标识,配置,值 self.r3 = Radiobutton(self.container, r_btn_config, value=3) self.r4 = Radiobutton(self.container, r_btn_config, value=4) self.r5 = Radiobutton(self.container, r_btn_config, value=5) def setup_layout(self): self.container.grid_rowconfigure(1, weight=1) # 权重影响行列相对其他行所占空间,默认0表示不会增长 self.container.grid_columnconfigure(0, weight=1) # 列0配置 self.container.grid_columnconfigure(4, weight=1) # 列4配置 self.background_label.place(x=0, y=0, relwidth=1, relheight=1) # place()为几何管理器,设置父控件高度宽度,1表示和主窗口相同 self.info_label.grid(row=0, column=0, columnspan=5, sticky="nsew") # nsew表示让空间贴上下左右四个边缘,占满整个单元格 self.r1.grid(row=1, column=0) self.r2.grid(row=1, column=4) self.r3.grid(row=2, column=3) self.r4.grid(row=3, column=0) self.r5.grid(row=4, column=4) # 各按钮位置(屋子位置) def announce_winner(self, data): messagebox.showinfo('result', message=data) def set_callbacks(self, callback_function): self.radio_btn_pressed = callback_function class Controller: def __init__(self, parent): self.parent = parent self.model = Model() self.view = View(parent) self.view.set_callbacks(self.radio_btn_pressed) self.view.setup() # 'Subscribe' to the topic 'WINNER ANNOUNCEMENT" pub.subscribe(self.model_change_handler, 'result')#接收Model变化 def radio_btn_pressed(self): self.model.enter_hut(self.view.var.get()) def model_change_handler(self, data): self.view.announce_winner(data) if __name__ == '__main__': mainwin = Tk() # 初始化GUI窗口 WIDTH = 494 HEIGHT = 307 mainwin.geometry("%sx%s" % (WIDTH, HEIGHT)) mainwin.resizable(0, 0) mainwin.title("兽人之袭") game_app = Controller(mainwin) # parent指定消息框的父组件,mainwin为game_app的父组件 mainwin.mainloop()
浙公网安备 33010602011771号