几个开发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()