Python GUI编程:图形用户界面开发实战指南

GUI简介

我们前面实现的都是基于控制台的程序,程序和用户的交互通过控制台来完成

本章,我们来学习GUI图形用户界面编程,我们可以通过python提供的丰富的组件,快速的视线使用图形界面和用户交互

GUI变成类似于“搭积木”,将一个个组件(widget)放到窗口中。windows中的画图软件,就是一个典型的GUI程序:

上面的各种按钮、菜单、编辑区域等都是一个个组件,它们都防止到窗口中,并通过增加“对时间的处理”称为一个完整的程序。

常用的GUI库

  1. Tkinter
    tkinter是Python的标准GUI库,支持跨平台的GUI程序开发,主要一tkinter为核心进行讲解
  2. wxPython
    wxPyhton是比较流行的GUI库,适合大型应用程序开发,功能强于tkinter,整体设计框架类似于MFC
  3. PyQT
    Qt是一种开源的GUI库,适合大型GUI程序开发,PyQT是Qt工具包标准的Python实现。我们也可以使用Qt Desginer界面设计器快速开发GUI应用程序

tkinter模块

本章设计大量的API讲解。学习API最好的来源就是官方提供的文档:tkinter官方网站:

Graphical User Interfaces with Tk — Python 3.7.17 documentation或者:https://effbot.org/tkinternook/

基于tkinter模块创建GUI程序包含如下4个核心步骤:

1.创建应用程序主窗口对象
        (1)通过类Tk的无参数构造函数

from tkinter import *

root=Tk()

2.在主窗口中,添加各种可视化组件,比如:按钮(Button)、文本框(Label)等

btn01=Button(root)
btn01["text"]="点我就送花"

3.通过几何布局管理器,管理组件的大小和位置

btn01.pack()

4.事件处理

(1)通过绑定事件处理程序,响应用户操作所触发的时间(比如:单击、双击等)

def songhua(e):
    messagebox.showinfo("Message", "送你一朵玫瑰花,请你爱上我")
    print("送你99多玫瑰花")


btn01.bind("<Button-1>", songhua())

总代码

from tkinter import *
from tkinter import messagebox

root = Tk()

btn01 = Button(root)
btn01["text"] = "点我就送花"

btn01.pack()


def songhua(e):  # e就是事件对象
    messagebox.showinfo("Message", "送你一朵花")
    print("送你999多玫瑰花")


btn01.bind("<Button-1>", songhua)

root.mainloop()  # 调用组件的mainloop()方法,进入时间循环

主窗口大小和位置

通过geometry('w x h+-x+-y')进行设置。w为宽度,h为高度。+x表示距屏幕左边的距离;-x表示距屏幕右边的距离;+y表示距屏幕上边的距离;-y表示距屏幕下边的距离。

【测试】测试tkinter主窗口位置和大小的设置

# -*- coding: utf-8 -*-
from tkinter import *
from tkinter import messagebox

root = Tk()

root.title("这是我的第一个GUI程序")
root.geometry("500x300+100+200")

btn01 = Button(root)
btn01["text"] = "点我就送花"

btn01.pack()


def songhua(e):  # e就是事件对象
    messagebox.showinfo("Message", "送你一朵花")
    print("送你999多玫瑰花")


btn01.bind("<Button-1>", songhua)

root.mainloop()  # 调用组件的mainloop()方法,进入时间循环

GUI编程整体描述

图形用户界面是由一个个组件组成,就像小孩“搭积木”一样最终组成整个界面。有的组件还能再里面再放置其他组件,我们称为“容器”。Tkinter的GUI组件关系图如下:

GUI应用程序类的经典写法

本节程序也是GUI应用程序编写的恶一个主要结构,采用了面向对象的方式,更加合理的组织代码。

通过类Application组织整个GUI程序,类Application继承了Frame及通过继承拥有了父类的特性。通过构造函数__init__()初始化窗口中的对象,通过createWidfets()方法创建窗口中的对象。

Frame框架式一个tkinter组件,表示一个矩形的区域。Frame一般作为容器使用,可以放置其他组件,从而实现复杂的布局。

# -*- coding: utf-8 -*-
"""测试一个经典的GUI程序写法,使用面向对象的方式"""
from tkinter import *
from tkinter import messagebox


class Application(Frame):
    """
    一个经典的GUI程序的类的写法
    """

    def __init__(self, master=None):
        super().__init__(master)  # super()代表的是父类的定义,而不是父类对象
        self.master = master  # 传入root作为继承
        self.pack()  # 是用来将一个组件(如按钮、标签、框架等)添加到其父容器中的一种布局管理方法。

        self.createWiget()  # 调用创建组件方法

    def createWiget(self):
        """创建组件"""
        self.btn01 = Button(self.master)
        self.btn01["text"] = "点击送花"
        self.btn01.pack()
        self.btn01["command"] = self.songhua

        # 创建一个退出按钮
        self.btnQuit = Button(self.master, text="退出", command=root.destroy)
        self.btnQuit.pack()

    def songhua(self):
        messagebox.showinfo("送花", "送你99多玫瑰花")


if __name__ == '__main__':
    """Application 类实例化时,将 master 参数设为 root,这意味着 Application 的所有组件(在 Frame 中创建)都将在 root 主窗口中显示。"""
    root = Tk()  # 创建一个Tk对象
    root.geometry("400x100+200+300")  # 设置Tk的窗口大小和范围
    root.title("一个经典的GUI程序类的测试")  # 设置Tk的的名字
    app = Application(master=root)  # 实例化一个Application对象
    root.mainloop()  # 它的作用是启动 Tkinter 事件循环,让你的应用程序开始运行并等待用户的操作
  • root

  • 代表了 Tkinter 主窗口的实例,是 Tk 类的对象。
  • 这是 GUI 应用程序的顶层窗口,对整个应用进行管理。
  • Frame

  • 是一个容器控件,用于在应用程序窗口中组织其他控件(比如按钮、标签等)。
  • 在这里,Application 类是 Frame 的子类,它通过继承 Frame 来创建一个新的组件。
  • 1. Button(self.master)

    如果你的 master 是一个 Tk 实例(也就是主窗口),那么使用 Button(self.master) 是合适的。这种情况下,按钮会被添加到主窗口中。

    2. Button(self)

    如果你在一个自定义类中,且希望按钮添加到该类的实例(比如 Frame 或者 Toplevel),则使用 Button(self) 是合适的。按钮将被添加到这个特定的实例中。

    简单组件

    Label标签

    Label(标签)主要用于显示文本信息,也可以显示图像。

    Label(标签)有一些常见的属性:

    1. width,height:
      用于指定区域大小,如果显示是文本,则以单个英文字符大小为单位(一个汉字占2个字符位置,高度和英文字符一样);如果显示是图像,则以像素为单位。默认值是根据具体显示的内容动态调用。
    2. font
      指定字体和字体大小
    3. image
      显示在Label上的图像,目前tkinter只支持gif格式
    4. fg和bg
      fg(foreground):前景色、bg(background):背景色
    5. justify
      针对多行文字的对齐,可设置justify属性,可选值"left"、"center" or "right
    # -*- coding: utf-8 -*-
    """测试一个经典的GUI程序写法,使用面向对象的方式"""
    from tkinter import *
    
    
    class Application(Frame):
        """
        测试labels标签
        """
    
        def __init__(self, master=None):
            super().__init__(master)
            self.master = master
            self.pack()
    
            self.createWidget()  # 创建的框架
    
        def createWidget(self):
            self.label01 = Label(self, text="老高", width=10, height=2, bg="black", fg="white")
            self.label01.pack()
    
            self.label02 = Label(self, text="老杨", width=10, height=2, bg="blue", fg="white", font=("黑体", 30))
            self.label02.pack()
    
            # 显示图像
            # global photo  # 把photo声明成全局变量,如果是局部变量,不然本方法执行后,图像对象销毁
            self.photo = PhotoImage(file="img/3.gif")
            self.label03 = Label(self, image=self.photo)
            self.label03.pack()
    
            self.label04 = Label(self, text="你好!\n我是一名初学者\n正在学习python编程",
                                 borderwidth=1, relief="solid", justify="right")
            self.label04.pack()
    
    
    if __name__ == '__main__':
        root = Tk()
        root.geometry("600x500+200+200")
        app = Application(master=root)
        root.mainloop()
    

    Option选项详解

    通过学习Label组件,我们发现可以通过Options设置组件的属性,从而控制组件的各种状态,比如:宽度、高度、颜色、位置等等。

    我们可以通过三种方式设置Options选项,这在各种GUI组件中用法都一直。

    1. 创建对象时,使用命名参数(也叫关键字参数)
      fred=Button(self,fg="red,bg="blue")
    2. 创建对象后,使用字典索引方式
      fred["fg"]="red"
      fred["bg"]="blue"

    3. 创建对象后,使用config()方法
      fred.config(fg="red",bg="blue")

    Button

    Button(按钮)用来执行用户的单击操作。Button可以包含文本,也可以包含图像。按钮被单击后会自动调用对应事件绑定的方法。

    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):
            """框架方法"""
            self.butn1 = Button(self.master, text="begin",
                                width=6, height=3, anchor=E, command=self.songhua)  # anchor的作用就是决定你的text文本唯一按钮的那个位置
            self.butn1.pack()
    
            # 插入图片
            self.photo = PhotoImage(file="img/1.gif")
            self.butn2 = Button(self.master, image=self.photo, command=self.login)
            self.butn2.pack()
            self.butn2.config(state=DISABLED)  # 加入state会让你不能点击,就让你取消点击状态
    
        def songhua(self):
            """点击开始送花"""
            messagebox.showinfo("恭喜你", "得到两朵玫瑰花")
    
        def login(self):
            messagebox.showinfo("系统", "登录成功!欢迎开始学习!")
    
    
    if __name__ == '__main__':
        root = Tk()
        root.title("button练习")
        root.geometry("400x300+200+100")
        app = Application(root)
        root.mainloop()

    Entry单行文本框

    Entry用来接收一行字符串的控件。如果用户输入的文字长度长于Entry控件的宽度时,文字会自动向后滚动。如果想输入多行文本,需要使用Text控件。

    # -*- coding: utf-8 -*-
    
    from tkinter import *
    from tkinter import messagebox
    
    
    class Application(Frame):
        def __init__(self, master):
            super().__init__(master)
            self.master = master
            self.pack()
            self.createWidgt()
    
        def createWidgt(self):
            """创建登录界面的组件"""
            self.label01 = Label(self, text="用户名")
            self.label01.pack()
    
            # StringVar变量绑定到指定的组件。
            # StringVar变量的值发生变化,组件内容也变化。
            # 组件内容发生变化,StringVar变量的值也发生变化。
            v1 = StringVar()
            self.entry01 = Entry(self, textvariable=v1)
            self.entry01.pack()
            v1.set("admin")
            print(v1.get())
            print(self.entry01.get())
    
            # 创建密码框
            self.label02 = Label(self, text="密码")
            self.label02.pack()
    
            v2 = StringVar()
            self.entry02 = Entry(self, textvariable=v2, show="******")
            self.entry02.pack()
    
            # 登录按钮
            self.btn01 = Button(self, text="登录", command=self.login)
            self.btn01.pack()
    
        def login(self):
            username = self.entry01.get()
            pwd = self.entry02.get()
            print("用户名:" + username)
            print("密 码 :" + pwd)
            messagebox.showinfo("老杨系统", "恭喜登录成功")
    
    
    if __name__ == '__main__':
        root = Tk()
        root.geometry("400x300+200+100")
        root.title("entry文本")
        app = Application(root)
        root.mainloop()
    

    Text多行文本框

    Text(多行文本框)的主要用于显示多行文本,还可以显示网页链接,图片,HTML页面,甚至CSS样式表,添加组件等。因此,也常被当做简单的文本处理器、文本编辑器或者网页浏览器来使用。比如IDLE就是Text组件构成的。

    # -*- coding: utf-8 -*-
    import webbrowser
    from tkinter import *
    from tkinter import messagebox
    
    
    class Application(Frame):
        def __init__(self, master):
            super().__init__(master)
            self.master = master
            self.pack()
            self.createWidget()
    
        def createWidget(self):
            """创建框架"""
            self.w1 = Text(self.master, width=40, height=12, bg="gray")  # 注意这里的self.master可以是root,但是不能是self
            # 宽度20个字母(10个汉字),高度一个行高
            self.w1.pack()
    
            self.w1.insert(1.0, "0123456789\nabcdefg")  # 这里的1.0的意思是第一行第0列
            self.w1.insert(2.3, "锄禾日当午,汗滴禾下土。谁知盘中餐,粒粒皆辛苦\n ")
    
            Button(self, text="重复插入文本", command=self.insertText).pack(side="left")
            Button(self, text="返回文本", command=self.returnText).pack(side="left")
            Button(self, text="添加图片", command=self.addImage).pack(side="left")
            Button(self, text="添加组件", command=self.addWidget).pack(side="left")
            Button(self, text="通过tag精准控制文本", command=self.testTag).pack(side="left")
    
        def insertText(self):
            # INSERT索引表示在光标出插入
            self.w1.insert(INSERT, "老杨")
            # END索引号表示在最后插入
            self.w1.insert(END, "你好,我是你大哥")
    
        def returnText(self):
            # indexes(索引)是用来纸箱Text组件中文本的位置,
            # Text的组件索引也是对应实际字符之间的位置
            # 核心:行号以1开始,列好以0开始
            print(self.w1.get(1.2, 1.6))
            self.w1.insert(1.8, "老杨")
            print("所有文本内容为:\n" + self.w1.get(1.0, END))
    
        def addImage(self):
            """在光标位置插入图片"""
            global photo
            photo = PhotoImage(file="img/3.gif")
            self.w1.image_create(END, image=photo)  # 在文章的末尾插入
            self.w1.image_create(INSERT, image=photo)  # 在鼠标的光标出插入图片
    
        def addWidget(self):
            """增加新的控件方法"""
            b1 = Button(self.w1, text="爱你老杨", command=self.songhua)
            # 在text创建组件的命令
            self.w1.window_create(INSERT, window=b1)
    
        def testTag(self):
            self.w1.delete(1.0, END)
            self.w1.insert(INSERT, "good good study ,day day up!\n老杨,老杨\n我爱你\n就像老鼠爱大米\n百度")
            self.w1.tag_add("good", 1.0, 1.9)  # 第一个参数为标记名称,第二个参数和第三个参数是索引的位置
            self.w1.tag_config("good", background="yellow", foreground="red")  # 第一个参数是需要添加配置的标记,第二和第三个参数的含义是添加前景色和背景色
    
            self.w1.tag_add("baidu", 5.0, 5.2)
            self.w1.tag_config("baidu", underline=True)
            self.w1.tag_bind("baidu", "<Button-1>", self.webshow)
    
        def webshow(self, event):
            webbrowser.open("http://www.baidu.com")
    
        def songhua(self):
            """送花"""
            messagebox.showinfo("老杨系统", "送你9999朵玫瑰花")
    
    
    if __name__ == '__main__':
        root = Tk()
        root.geometry("400x300+200+100")
        root.title("老杨系统")
        app = Application(root)
        root.mainloop()
    

    Radiobutton单选按钮

    Radiobutton控件用于选择同一组单选按钮中的一个

    Radiobutton可以显示文本,也可以显示图像

    # -*- coding: utf-8 -*-
    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):
            """创建组件"""
            self.v = StringVar()
            self.v.set("女")
    
            self.election1 = Radiobutton(self.master, text="男", value="男", variable=self.v)
            self.election2 = Radiobutton(self.master, text="女", value="女", variable=self.v)
    
            self.election1.pack(side="left")
            self.election2.pack(side="left")
            Button(self.master, text="确定", command=self.confirm).pack(side="left")
    
        def confirm(self):
            messagebox.showinfo("测试", "选择的性别:" + self.v.get())
    
    
    if __name__ == '__main__':
        root = Tk()
        root.title("老杨的系统")
        root.geometry("400x300+200+100")
        app = Application(root)
        root.mainloop()
    

    Checkbutton复选按钮

    Checkbutton控件用于选择多个按钮的情况。Checkbutton可以显示文本,也可以显示图像

    # -*- coding: utf-8 -*-
    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):
            """创建组件"""
            self.codeHobby = IntVar()
            self.videoHobby = IntVar()
    
            print(self.codeHobby.get())  # 默认值是0
            self.c1 = Checkbutton(self, text="敲代码", variable=self.codeHobby, onvalue=1, offvalue=0)
            self.c2 = Checkbutton(self, text="看视频", variable=self.videoHobby, onvalue=1, offvalue=0)
            self.c1.pack(side="left")
            self.c2.pack(side="left")
            Button(self, text="确定", command=self.confirm).pack(side="left")
    
        def confirm(self):
            if self.videoHobby.get() == 1:
                messagebox.showinfo("老杨的系统", "我也喜欢看视频")
            if self.codeHobby.get() == 1:
                messagebox.showinfo("老杨的系统", "我也喜欢敲代码")
    
    
    if __name__ == '__main__':
        root = Tk()
        root.title("老杨的系统")
        root.geometry("400x300+200+100")
        app = Application(root)
        root.mainloop()
    

    canvas画布

    canvas(画布)是一个矩形区域,可以放置图形、图像、组件等。本节我们简单介绍canvas的使用。

    # -*- coding: utf-8 -*-
    from tkinter import *
    from tkinter import messagebox
    import random
    
    
    class Application(Frame):
        def __init__(self, master=None):
            super().__init__(master)
            self.pack()
            self.createWidget()
    
        def createWidget(self):
            """创建组件"""
            self.canvas = Canvas(self, width=400, height=500, bg="green")
            self.canvas.pack()
            # 画一条直线
            line = self.canvas.create_line(10, 10, 30, 20, 40, 50)
            # 画一个矩形
            rect = self.canvas.create_rectangle(50, 50, 100, 100)
            # 画一个椭圆,坐标两双。为椭圆的边界矩形左上角和底部右下角
            oval = self.canvas.create_oval(50, 50, 100, 100)  # 画圆是在它所在的外切矩形的左边点
    
            global photo
            photo = PhotoImage(file="img/3.gif")
            self.canvas.create_image(200, 300, image=photo)
    
            Button(self, text="画10个矩阵", command=self.draw50Recg).pack(side="left")
    
        def draw50Recg(self):
            for i in range(0, 10):
                x1 = random.randrange(int(self.canvas["width"]) / 2)  # 生成一个从0到这个数中的其中一个数
                y1 = random.randrange(int(self.canvas["height"]) / 2)
                x2 = x1 + random.randrange(int(self.canvas["width"]) / 2)
                y2 = y1 + random.randrange(int(self.canvas["height"]) / 2)
                self.canvas.create_rectangle(x1, y1, x2, y2)
    
    
    if __name__ == '__main__':
        root = Tk()
        root.title("老杨的系统")
        root.geometry("400x600+200+100")
        app = Application(root)
        root.mainloop()
    

    布局管理器

    一个GUI应用程序必然有大量的组件,这些组件如何排布?z这时候,就需要使用tkinter提供的布局管理器帮租我们组织、管理在父组件中子组件的布局方式,,tkinter提供了三种管理器:pack、grid、place。

    grid布局管理器

    grid表格布局,采用表格结构组织组件。子组件的位置由行和列的单元格来确定,并且可以跨行和跨列,从而实现复杂的布局。

    grid()方法提供的选项
    选项 说明 取值范围
    column 单元格的列号 从0开始的正整数
    columnspan 跨列,跨越的列数 正整数
    row 单元格的行号 从0开始的正整数
    rowspan kuahang,跨越的行数 正整数
    ipadx,ipady 设置子组件之间的间隔,x方向或者y方向,默认单位为像素 非负浮点数,默认为0.0
    padx,pady 与之并列的组件之间的间隔,x方向或者y方向,默认单位是像素 非负浮点数,默认0.0
    sticky 组件紧贴所在单元格的某一角,对应于东南西北中以及4个角 “n”,“s”,“w”,“e”,“nw”,“sw”,“se”,“ne”,“center(默认)”
    # -*- coding: utf-8 -*-
    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):
            """创建组件"""
            # 用户名
            self.label01 = Label(self, text="用户名")
            self.label01.grid(row=0, column=0)
            v1 = StringVar()
            self.entry01 = Entry(self, textvariable=v1)
            self.entry01.grid(row=0, column=1)
            v1.set("12346578")
            print(self.entry01.get())
            self.label02 = Label(self, text="用户名为手机号")
            self.label02.grid(row=0, column=2)
    
            # 密码
            self.label03 = Label(self, text="密码")
            self.label03.grid(row=1, column=0)
            v2 = StringVar()
            self.entry02 = Entry(self, textvariable=v2, show="*")
            self.entry02.grid(row=1, column=1)
    
            # 登录
            Button(self, text="登录", command=lambda: self.login(self.entry01.get(), self.entry02.get())).grid(row=2,
                                                                                                             column=1,
                                                                                                             sticky="EW")
            Button(self, text="取消", command=self.quxiao).grid(row=2, column=2, sticky="EW")
    
        def login(self, admin, key):
            """
            用于登录
            :param admin:用户名
            :param key: 密码
            :return:
            """
            if admin == "17729686779" and key == "123456":
                messagebox.showinfo("老杨的系统", "密码正确")
            else:
                messagebox.showinfo("老杨的系统", "密码错误")
    
        def quxiao(self):
            """取消登录"""
            self.entry01.delete(0, END)
            self.entry01.insert(0, "123456")
            self.entry02.delete(0, END)
    
    
    if __name__ == '__main__':
        root = Tk()
        root.title("老杨的系统")
        root.geometry("400x300+200+100")
        app = Application(root)
        root.mainloop()
    

    【示例】通过grid布局-实现计算器软件界面

    根据实际建议计算器的按键分布,设计一个相仿的计算器界面,

    如上界面,实际可以设计成一个7行4列的表格布局,然后将相应的按钮放置进去即可

    # -*- coding: utf-8 -*-
    from tkinter import *
    from tkinter import messagebox
    
    
    class Application(Frame):  # 制作一个简易的计算器
        def __init__(self, master=None):
            super().__init__(master)
            self.pack()
            self.master = master
            self.createWidget()
    
        def createWidget(self):
            """通过grid布局实现计算器的界面"""
            # 按钮
            binText = (("MC", "M+", "M-", "MR"),
                       ("C", "±", "÷", "x"),
                       (7, 8, 9, "-"),
                       (4, 5, 6, "+"),
                       (1, 2, 3, "="),
                       (0, "."))
    
            # 输入框
            self.v1 = StringVar()
            self.entry01 = Entry(self, textvariable=self.v1)
            self.entry01.grid(row=0, column=0, columnspan=4, pady=10)  # pady=10 意味着在 entry01 的顶部和底部各添加 10 像素的空间。
            for rindex, rText in enumerate(binText):
                for cindex, cvalue in enumerate(rText):
                    if cvalue == "=":
                        Button(self, text=cvalue, width=2, command=self.mainCaculate()).grid(row=rindex + 1, column=cindex,
                                                                                             rowspan=2, sticky=NSEW)
                    elif cvalue == 0:
                        Button(self, text=cvalue, width=2).grid(row=rindex + 1, column=cindex, columnspan=2, sticky=NSEW)
                    elif cvalue == ".":
                        Button(self, text=cvalue, width=2).grid(row=rindex + 1, column=cindex + 1, sticky=NSEW)
                    else:
                        Button(self, text=cvalue, width=4).grid(row=rindex + 1, column=cindex, sticky=NSEW)
                        # sticky=NSEW,意味着按钮会填充整个单元格
    
        def mainCaculate(self):
            pass
    
    
    if __name__ == '__main__':
        root = Tk()
        root.title("简易计算器")
        root.geometry("250x250+200+100")
        app = Application(root)
        root.mainloop()
    

    pack布局管理器

    pack按照组件的创建顺序将子组件添加到父组件中,按照垂直或者水平的方向自然排布。如果不指定任何选项,默认在父组件中自顶向下垂直添加组件。
    pack是代码量最少,最简单的一种,可以用于快速生成界面

    pack()方法提供的功能
    名称 描述 取值范围
    expand 当值为“yes”时,side选项无效,组件显示在父配件中心位置;若fill选项为“both”,则填充父组件的剩余空间 “yes”,自然数,“no”,0(默认值“no”或0)
    fill 填充x(y)方向上的控件,当属性side=“top”或“bottom”时,填充x方向;当属性side=“left”或“right”时,填充“y”方向;当expand选项为“yes”时,填充父组件的剩余空间。 “x”,“y”,“both”,“none”(默认值为none)
    ipadx,ipady 设置子组件之间的间隔,x方向或者y方向,默认单位为像素 非负浮点数,默认0.0
    padx,pady 与之并列的组件之间的间隔,x方向或者y方向,默认单位是像素 非负浮点数,默认0.0
    side 定义停靠在父组件的哪一边上 “top”,“bottom”,“left”,“right”(默认为“top”)
    before 将本组件于所选组建对象之前pack,类似于先创建本组件再创建选定组件 已经pack后的组件对象
    after 将本组件于所组建对象之后pack,类似于先创建选定组件再本组件 已经pack后的组件对象
    in_ 将本组件作为所选组建对象的子组件,类似于指定本组件的master为选定组件 已经pack后的组件对象
    anchor 对齐方式,左对齐“w”,右对齐“e”,顶对齐“n”,底对齐“s” “n”,“s”,“w”,“e”,“nw”,“sw”,“se”,“ne”,“center”(默认)

    【示例】pack布局用法,制作钢琴按键布局

    # -*- coding: utf-8 -*-
    from tkinter import *
    
    root = Tk()
    root.geometry("700x220")
    
    # Frame是一个矩形区域,就是用来放置其他子组件
    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, 20):
        Label(f2, width=5, height=10, borderwidth=1, relief="solid", bg="black" if i % 2 == 0 else "white").pack(
            side="left", padx=2)
    
    root.mainloop()
    

    place布局管理器

    place布局管理器可以通过坐标精准控制组件的位置,使用于一些布局更加灵活的场景

    place()方法的选项
    选项 说明 取值范围
    x,y 组件左上角的绝对坐标(相对于窗口)

    非负整数

    x和y选项用于设置偏移(像素)如果同时设置relx(rely)和x(y),那么place将优先计算relx和rely,然后再实现x和y指定的偏移

    relx

    rely

    组件左上角的坐标(相对于父容器)

    relx是相对于父组件的位置,0是最左边,0.5是正中间,1是最右边

    rely是相对于父组件的位置,0是最上边,0.5是正中间,1.是最下边

    width

    height

    组件的宽度和高度 非负整数

    relwidth

    relheight

    组件的宽度和高度(相对于父容器) 与relx、rely取值类似,但是相对于父组件的尺寸
    anchor 对齐方式,左对齐“w”,右对齐“e”,顶对齐“n”,底对齐“s” “n”,“s”,“w”,“e”,“nw”,“sw”,“se”,“ne”,“center”(默认)
    # -*- coding: utf-8 -*-
    from tkinter import *
    from tkinter import messagebox
    
    root = Tk()
    root.geometry("500x300")
    root.title("布局管理place")
    root["bg"] = "white"
    f1 = Frame(root, width=200, height=200, bg="green")
    f1.place(x=30, y=20)
    Button(root, text="老杨").place(relx=0.5, x=100, y=20, relwidth=0.2, relheight=0.5)
    # 如果relx和x同时存在的话,就是先定位relx的位置,然后再定位x的位置,就是定位了relx的位置后,向两边移x
    Button(f1, text="老杨爱睡觉").place(relx=0.6, rely=0.7)
    Button(f1, text="老杨大哥").place(relx=0.5, rely=0.2)
    
    root.mainloop()
    

    【示例】place布局管理-扑克牌游戏demo

    !!!扑克图片需要自己在网上去找,然后做成gif图片的形式

    # -*- coding: utf-8 -*-
    from tkinter import *
    from tkinter import messagebox
    
    
    class Application(Frame):
        """通过place布局管理器实现扑克牌位置控制"""
    
        def __init__(self, master=None):
            super().__init__(master)
            self.master = master
            self.pack()
            self.yabs = 100
            self.createWidget()
    
    
        def createWidget(self):
            """创建控制组件"""
            # self.photo = PhotoImage(file="img/puke/1.gif")
            # self.puke1 = Label(self.master, image=self.photo)
            # self.puke1.place(x=10, y=50)
            self.photos = [PhotoImage(file="img/puke/" + str(i + 1) + ".gif") for i in range(6)]
            self.pukes = [Label(self.master, image=self.photos[i]) for i in range(6)]
            for i in range(6):
                self.pukes[i].place(x=10 + i * 120, y=self.yabs)
    
            # 为所有的Label增加事件处理
            for i in range(6):
                self.pukes[i].bind_class("Label", "<Button-1>", self.chupai)
    
        def chupai(self, event):
            print(event.widget.winfo_geometry())
            print(event.widget.winfo_y())
            if event.widget.winfo_y() == self.yabs:
                event.widget.place(y=self.yabs - 50)
            else:
                event.widget.place(y=self.yabs)
    
    
    if __name__ == '__main__':
        root = Tk()
        root.geometry("800x400+300+200")
        root.title("扑克牌玩具")
        app = Application(root)
        root.mainloop()
    

    lambda表达式详解

    lambda表达式定义的是一个匿名函数,只适合简单输入参数,简单计算返回结果,不适合功能复杂情况

    lambda定义的匿名函数也有输入、也有输出,只是没有名字。语法格式如下:

    lambda 参数值列表:表达式

    参数值列表即为输入。

    表达式计算的结构即为输出。

    写一个简单的案例:

    add3args = lambda x, y, z: x + y + z
    print(add3args(10, 20, 30))
    

    上面的lambda表达式相当于如下函数定义:

    def add3args(x, y, z):
        return x + y + z
    print(add3args(10, 20, 30))

    lambda表达式的参数值列表可以为如下内容:

    lambda格式 说明
    lambda x,y:x*y 函数输入是x和y,输出是它们的积x*y
    lambda:None 函数没有输入参数,输出是None
    lambda:aaa(3,4) 函数没有输入参数,输出是aaa(3,4)的结果
    lambda *args:sum(args) 输入是任意个数的参数,输出是它们的和
    lambda **kwargs:1 输入是任意键值对参数,输出是1
    使用lambda表达式实现传参

    【示例】使用lambda帮助command属性绑定时传参

    from tkinter import *
    
    root = Tk()
    root.geometry("270x50")
    
    
    def mouseTest1():
        print("command方式,简单情况:不涉及获取event对象,可以使用")
    
    
    def mouseTest2(a, b):
        print("a={0},b={1}".format(a, b))
    
    
    Button(root, text="测试command1", command=mouseTest1).pack(side="left")
    
    Button(root, text="测试command2", command=lambda: mouseTest2("laoyang", "xixi")).pack(side="left")
    
    root.mainloop()
    

    多种事件绑定方式汇总

    组件对象的绑定
    1. 通过command属性绑定(适合简单不需要获取event对象)
      Button(root,text=“登录”,command=login)
    2. 通过bind()方法绑定(适合需要获取event对象)
      c1=Canvas();c1.bind("<Button-1>",drawLine)
    组件类的绑定

    调用对象的bind_class函数,将该组件类所有的组件绑定事件:
    w.bind_class("Widget","event",eventhanler)

    # -*- coding: utf-8 -*-
    from tkinter import *
    
    root = Tk()
    root.geometry("270x30")
    
    
    def mouseTest1(event):
        print("bind()方式绑定,可以获取event对象")
        print(event.widget)
    
    
    def mouseTest2(a, b):
        print("a={},b={}".format(a, b))
        print("command方式绑定,不能直接获取event对象")
    
    
    def mouseTest3(event):
        print("右键单击事件,绑定所有按钮啦!!")
        print(event.widget)
    
    
    b1 = Button(root, text="测试bind()绑定")
    b1.pack(side="left")
    # bind方式绑定事件
    b1.bind("<Button-1>", mouseTest1)
    
    # command属性直接绑定事件
    b2 = Button(root, text="测试command2", command=lambda: mouseTest2("laoyang", "xixi"))
    b2.pack(side="left")
    
    # 给所有Button按钮都绑定右键单击事件<Button-2>
    b1.bind_class("Button", "<Button-3>", mouseTest3)  # Button-3是单击右键的意思
    
    root.mainloop()
    

    其他组件

    OptionMenu选择项

    OptionMenu(选择项)用来做多选一,选中的项在顶部显示。

    【示例】OptioMenu(选择项)的基本用法

    from tkinter import *
    from tkinter import messagebox
    
    
    class Application(Frame):
        def __init__(self, master=None):
            super().__init__(master)
            self.pack()
            self.createWidget()
    
        def createWidget(self):
            """创建组件"""
            Label(self, text="最爱的人是谁:").grid(row=0, column=0)
            v = StringVar(self)
            v.set("彭于晏")
            om = OptionMenu(self, v, "彭于晏", "刘亦菲", "yyx")
            om["width"] = 10
            om.grid(row=0, column=1)
            Btn1 = Button(self, text="确定", command=lambda: self.test1(v))
            Btn1.grid(row=1, column=1)
    
        def test1(self, v):
            print("最爱的人:", v.get())
    
    
    if __name__ == '__main__':
        root = Tk()
        root.geometry("300x100")
        root.title("最爱的明星")
        app = Application(root)
        root.mainloop()

    Scale滑块

    Scale(移动滑块)用于在指定的数值区间,通过滑块的移动来选择值

    【示例】使用Scale(移动滑块)控制字体大小变化

    # -*- coding: utf-8 -*-
    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()
            self.a
    
        def createWidget(self):
            """创建框架"""
            s1 = Scale(self, from_=10, to=50, length=200, tickinterval=5, orient=HORIZONTAL,
                       command=self.test1)  # 默认是竖直的,HORIZONTAL是水平的,这里调用test的时候,会默认将value的值传入函数中
            s1.pack()
            self.a = Label(self, text="yyx", width=10, height=1, bg="white", fg="red")
            self.a.pack()
    
        def test1(self, value):
            print("滑块的值:", value)
            newFont = ("宋体", value)
            self.a.config(font=newFont)
    
    
    if __name__ == '__main__':
        root = Tk()
        root.geometry("400x300")
        root.title("yyx的系统")
        app = Application(root)
        root.mainloop()
    

    颜色选择框

    颜色选择框可以帮组我们设置背景色、前景色、画笔颜色、字体颜色等等

    【示例】颜色选择框的用法

    # -*- coding: utf-8 -*-
    from tkinter import *
    from tkinter import messagebox
    from tkinter import colorchooser
    
    
    class Application(Frame):
        def __init__(self, master=None):
            super().__init__(master)
            self.pack()
            self.master = master
            self.createWidget()
    
        def createWidget(self):
            """创建组件"""
            Button(self, text="选择背景色", command=self.test1).pack()
    
        def test1(self):
            s1 = colorchooser.askcolor(color="white", title="选择背景色")
            print(s1)
            self.master.config(bg=s1[1])
    
    
    if __name__ == '__main__':
        root = Tk()
        root.geometry("400x300")
        root.title("yyx的系统")
        app = Application(root)
        root.mainloop()
    

    文件对话框

    文件对话框帮助我们实现可视化的操作目录、操作文件。最后,将文件、目录的信息传入到程序中。文件对话框包含如下一些常用函数:

    函数名 对话框 说明
    askopenfilename(**options) 文件对话框 返回打开的文件名
    askopenfilenames(**options) 返回打开的多个文件名列表
    askopenfile(**options) 返回打开文件对象
    askopenfiles(**options) 返回打开的文件对象的列表
    askdirectory(**options) 目录对话框 返回目录名
    asksavefile(**options) 保存对话框 返回保存的文件对象
    asksaveasfilename(**options) 返回保存的文件名

    命名参数options的常见值如下:

    参数名 说明
    defaultextension

    默认后缀:.xxx

    用户没有输入则自动添加

    filetypes=[(label1,pattern1),(label2,pattern2)] 文件显示过滤器
    initialldir 初始目录
    initialfile 初始文件
    parent 父窗口,默认根窗口
    title 窗口标题

    【示例】文件对话框基本用法

    # -*- coding: utf-8 -*-
    
    from tkinter import *
    from tkinter import messagebox
    from tkinter import filedialog
    
    
    class Application(Frame):
        def __init__(self, master=None):
            super().__init__(master)
            self.pack()
            self.master = master
            self.createWidget()
    
        def createWidget(self):
            """用于创建组件"""
            Button(self, text="选择编辑的视频文件", command=self.test1).pack()
            self.show = Label(self, width=40, height=3, bg="green")
            self.show.pack()
    
        def test1(self):
            f = filedialog.askopenfilename(title="上传文件", initialdir="d:", filetypes=[("视频文件", "mp4")])
            self.show["text"] = f
    
    
    if __name__ == '__main__':
        root = Tk()
        root.geometry("400x300")
        root.title("yyx的程序")
        app = Application(root)
        root.mainloop()
    

    简单输入对话框

    simpledialog(简单对话框)包含如下常用函数:

    函数名 说明
    askfloat(title,prompt,**kw) 输入并返回浮点数
    askinteger(title,prompt,**kw) 输入并返回整数
    askstring(title,prompt,**kw) 输入并返回字符串

    参数中,title表示窗口标题;prompt是提示信息;命令参数kw为各种选项:initialvalue(初始值)、minvalue(最小值)、maxvalue(最大值)

    【示例】简单对话框基本用法

    # -*- coding: utf-8 -*-
    from tkinter.simpledialog import *
    
    
    class Application(Frame):
        def __init__(self, master=None):
            super().__init__(master)
            self.pack()
            self.master = master
            self.createWidget()
    
        def createWidget(self):
            """用于创建组件"""
            Button(self, text="老杨你多大了?请输入", command=self.test1).pack()
            self.show = Label(self, width=40, height=3, bg="green")
            self.show.pack()
    
        def test1(self):
            a = askinteger(title="输入年龄", prompt="请输入年龄", initialvalue=18, minvalue=1, maxvalue=150)
            self.show["text"] = a
    
    
    if __name__ == '__main__':
        root = Tk()
        root.geometry("400x300")
        root.title("yyx的系统")
        app = Application(root)
        root.mainloop()
    

    通用消息框

    messagebox(通用消息框)用于和用户简单的交互,用户点击确定、取消。如下列出了messagebox的常见函数:

    函数名 说明
    askokcancel(title,message,**options) OK/Cancel对话框
    askquestion(title,message,**options) Yes/No问题对话框
    askretrycancel(title,message,**options) Retry/Cancel问题对话框
    showerror(title,message,**options) 错误消息对话框
    showinfo(title,message,**options) 消息框
    showwarning(title,message,**options) 警告消息框
    # -*- coding: utf-8 -*-
    from tkinter import *
    from tkinter.messagebox import *
    
    
    class Application(Frame):
        def __init__(self, master=None):
            super().__init__(master)
            self.pack()
            self.master = master
            self.createWidget()
    
        def createWidget(self):
            """创建组件"""
            # a1 = showinfo(title="yyx系统", message="yyx大牛")
            # a2 = askokcancel(title="yyx系统", message="你是不是gay")
            # a3 = askquestion(title="yyx系统", message="你是不是傻逼")
            # a4 = showerror(title="yyx系统", message="报错了")
            # a5 = showwarning(title="yyx系统", message="无法进行这样的操作")
    
    
    if __name__ == '__main__':
        root = Tk()
        root.geometry("400x300")
        root.title("yyx的系统")
        app = Application(root)
        root.mainloop()
    

    菜单

    GUI程序通常都有菜单,方便用户的交互。我们一般将菜单分为两种:

    主菜单

    主菜单通常位于GUI程序上方

    上下文菜单

    快捷菜单(上下文菜单)是通过鼠标右键单击组件而弹出的菜单,一般是和这个组件相关的操作,比如:剪切、复制、粘贴、属性等。

    # -*- coding: utf-8 -*-
    from tkinter import *
    from tkinter import messagebox
    
    
    class Application(Frame):
        def __init__(self, master=None):
            super().__init__(master)
            self.pack()
            self.master = master
            self.textpad = None
    
            self.createWidget()
    
        def createWidget(self):
            """创建组件"""
            # 创建主菜单栏
            menubar = Menu(self)
    
            # 创建子菜单
            menuFile = Menu(menubar)
            menuEdit = Menu(menubar)
            menuHelp = Menu(menubar)
    
            # 将子菜单加入到主菜单栏
            menubar.add_cascade(label="文件(F)", menu=menuFile)
            menubar.add_cascade(label="编辑(E)", menu=menuEdit)
            menubar.add_cascade(label="帮组(H)", menu=menuHelp)
    
            # 添加菜单项
            menuFile.add_command(label="新建", accelerator="ctrl+n", command=self.test)
            menuFile.add_command(label="打开", accelerator="ctrl+o", command=self.test)
            menuFile.add_command(label="保存", accelerator="ctrl+s", command=self.test)
            menuFile.add_separator()  # 添加分割线
            menuFile.add_command(label="退出", accelerator="ctrl+q", command=self.test)
    
            # 将主菜单栏加到根窗口
            self.master["menu"] = menubar
    
            # 文本编辑区
            self.textpad = Text(self, width=50, height=30)
            self.textpad.pack()
    
            # 创建上下菜单
            self.contentMenu = Menu(self.master)
            self.contentMenu.add_command(label="背景颜色", command=self.test)
    
            # 为右键绑定事件
            self.master.bind("<Button-3>", self.createContextMenu)
    
        def test(self):
            """用于测试"""
            pass
    
        def createContextMenu(self, event):
            """菜单在鼠标右键单击的坐标处显示"""
            self.contentMenu.post(event.x_root, event.y_root)
    
    
    if __name__ == '__main__':
        root = Tk()
        root.geometry("400x300")
        root.title("yyx的记事本系统")
        app = Application(root)
        root.mainloop()
    

    记事本项目

    【示例】创建一个记事本项目

    # -*- coding: utf-8 -*-
    from tkinter import *
    from tkinter import messagebox
    from tkinter.filedialog import *
    from tkinter.colorchooser import *
    
    
    class Application(Frame):
        def __init__(self, master=None):
            super().__init__(master)
            self.pack()
            self.master = master
    
            # 对象参数
            self.textpad = None  # 初始化文本编辑区对象
            self.filename = None
    
            self.createWidget()
    
        def createWidget(self):
            """用于创建模块"""
            # 创建主菜单栏
            menubar = Menu(root)  # 创建一个主菜单对象
    
            # 创建子菜单
            menuFile = Menu(menubar)  # 将menuFile对象放在Menu中
            menuEdit = Menu(menubar)
            menuHelp = Menu(menubar)
    
            # 将子菜单加入到主菜单栏中
            menubar.add_cascade(label="文件(F)", menu=menuFile)  # 文件放到menuFile中
            menubar.add_cascade(label="编辑(E)", menu=menuEdit)
            menubar.add_cascade(label="帮助(H)", menu=menuHelp)
    
            # 添加菜单项
            menuFile.add_command(label="新建", accelerator="ctrl+n", command=self.newFile)
            menuFile.add_command(label="打开", accelerator="ctrl+o", command=self.openFile)
            menuFile.add_command(label="保存", accelerator="ctrl+s", command=self.saveFile)
            menuFile.add_separator()  # 添加分割线
            menuFile.add_command(label="退出", accelerator="ctrl+q", command=self.test)
    
            # 将主菜单栏加到根窗口
            self.master["menu"] = menubar
    
            # 文本编辑区
            self.textpad = Text(root)
            self.textpad.pack()
    
            # 创建上下菜单
            self.contentMenu = Menu(root)
            self.contentMenu.add_command(label="背景颜色", command=self.openAskColor)
    
            # 为右键绑定事件
            self.master.bind("<Button-3>", self.createContextMenu)
    
            # 绑定窗口变化事件
            root.bind('<Configure>', self.on_resize)
    
        def test(self):
            """用于测试专用"""
            pass
    
        def createContextMenu(self, event):
            """菜单在鼠标右键单击的坐标处显示"""
            self.contentMenu.post(event.x_root, event.y_root)
    
        def openFile(self):
            if self.textpad.get("1.0", "end") != "\n":
                if messagebox.askquestion("yyx的记事本", "请问是否要保存当前文本数据") == "yes":
                    self.saveFile()
                else:
                    self.textpad.delete("1.0", "end")
            try:
                with askopenfile(title="打开文本文件") as f:
                    self.textpad.insert(INSERT, f.read())
                    self.filename = f.name
                    # print(self.filename)
            except:
                messagebox.showinfo("yyx的记事本", "未打开文件,请重试")
    
        def saveFile(self):
            if self.filename == None:
                # 说明未选择要存取的的文件
                messagebox.showinfo("yyx的记事本", "请选择你要存储的文件")
                try:
                    with askopenfile(title="打开你要存储的文本文件") as f:
                        self.filename = f.name
                except:
                    messagebox.showinfo("yyx的记事本", "未保存成功,请重试")
            else:
                # 如果当前文件板中存在数据,但是想存入新的文件
                if messagebox.askquestion("yyx的记事本", "请问要把当前的文本数据存入其他文件中吗") == "yes":
                    try:
                        with askopenfile(title="打开你要存储的文本文件") as f:
                            self.filename = f.name
                        self.filename = None
                    except:
                        messagebox.showinfo("yyx的记事本", "未保存成功,请重试")
                else:
                    with open(self.filename, "w") as f:
                        c = self.textpad.get(1.0, END)
                        f.write(c)
                    self.filename = None  # 再进行写入过后要将filename令为None值
    
        def newFile(self):
            """创建一个新的文件"""
            self.filename = asksaveasfilename(title="另存为", initialfile="未命名.txt", filetype=[("文本文档", "*.txt")],
                                              defaultextension=".txt")
            self.saveFile()
    
        def openAskColor(self):
            """打开背景颜色框"""
            s1 = askcolor(color="red", title="选择背景色")
            self.textpad.config(bg=s1[1])
    
        def on_resize(self, event):
            """textpad的窗口跟随root的窗口变化"""
            self.textpad.config(width=event.width, height=event.height)
    
    
    if __name__ == '__main__':
        root = Tk()
        root.geometry("400x300")
        root.title("yyx的记事本")
        app = Application(root)
        root.mainloop()
    

    python项目打包成exe可执行文件

    在pycharm的Terminal终端输入如下命令:

    pyinstaller -F xxxx.py

    【注】相关参数如下:

            –icon=图标路径(pyinstaller -F –icon=my.ico XXXX.py)
            -F 打包成一个exe文件
            -W 使用窗口,无控制台
            -c 使用控制台,无窗口
            -D 创建一个目录,里面包含exe以及其他一些依赖性文件

    在打包的过程中遇到一些问题:

    1.如果提示AttributeError: 'str' object has no attribute 'decode'. Did you mean: 'encode'?
    可以尝试将pyinstaller更新一下

    pip install --upgrade pyinstaller

    2.如果提示Please remove this package (located in D:\python3.10\lib\site-packages ) using "D:\python3.10\python.exe" -m pip uninstall pathlib then try again.
    就将这个pathlib库删除即可

    画图软件开发

    开发一款简单的画图软件,包含如下功能:

    1. 画笔
    2. 矩形/椭圆绘制
    3. 清屏
    4. 橡皮擦
    5. 直线/带箭头的执行
    6. 修改画笔颜色、背景颜色
    # -*- coding: utf-8 -*-
    import tkinter
    from tkinter import *
    from tkinter import messagebox
    
    
    class Application(Frame):
        def __init__(self, master=None, win_width=900, win_height=450):
            super().__init__(master)
            self.pack()
            self.master = master
            self.x = 0
            self.y = 0
            self.bgcolor = "#000000"
            self.fgcolor = "#ff0000"
            self.lastDraw = 0  # 表示最后绘制的图形
            self.startDrawFlag = False
    
            # 创建元素参数区域
            self.canvasPad = None
            self.win_width = win_width
            self.win_height = win_height
    
            self.createWidget()
    
        def createWidget(self):
            """创建控件"""
            # 创建绘图区
            self.canvasPad = Canvas(self.master, width=self.win_width, height=self.win_height * 0.9, bg=self.bgcolor)
            self.canvasPad.pack(fill=tkinter.BOTH, expand=True)
    
            # 创建功能按钮
            btn_start = Button(self.master, text="开始", name="start")
            btn_start.pack(side="left", padx="10")
            btn_pen = Button(self.master, text="画笔", name="pen")
            btn_pen.pack(side="left", padx="10")
            btn_rect = Button(self.master, text="矩形", name="rect")
            btn_rect.pack(side="left", padx="10")
            btn_clear = Button(self.master, text="清屏", name="clear")
            btn_clear.pack(side="left", padx="10")
            btn_eraser = Button(self.master, text="橡皮擦", name="eraser")
            btn_eraser.pack(side="left", padx="10")
            btn_line = Button(self.master, text="直线", name="line")
            btn_line.pack(side="left", padx="10")
            btn_lineArrow = Button(self.master, text="箭头直线", name="lineArrow")
            btn_lineArrow.pack(side="left", padx="10")
            btn_color = Button(self.master, text="颜色", name="color")
            btn_color.pack(side="left", padx="10")
    
            # 时间处理
            self.canvasPad.bind_class("Button", "<1>", self.eventManger)
            self.canvasPad.bind("<ButtonRelease-1>", self.stopDraw)
    
            """绑定快捷键区域"""
            # 绑定窗口变化
            self.master.bind("<Configure>", self.resize_canvas)
    
        def test(self):
            """用于测试专用"""
            pass
    
        def eventManger(self, event):
            """整个事件管理"""
            name = event.widget.winfo_name()
            print(name)
            if name == "line":
                self.canvasPad.bind("<B1-Motion>", self.myline)
            elif name == "lineArrow":
                self.canvasPad.bind("<B1-Motion>", self.mylineArrow)
            elif name == "rect":
                self.canvasPad.bind("<B1-Motion>", self.myRect)
            elif name == "pen":
                self.canvasPad.bind("<B1-Motion>", self.myPen)
            elif name == "eraser":
                self.canvasPad.bind("<B1-Motion>", self.myEraser)
    
        def startDraw(self, event):
            self.canvasPad.delete(self.lastDraw)
            if not self.startDrawFlag:
                self.startDrawFlag = True
                self.x = event.x
                self.y = event.y
    
        def stopDraw(self, evnet):
            self.startDrawFlag = False
            self.lastDraw = 0
    
        def myline(self, event):
            """创建直线"""
            self.startDraw(event)
            self.lastDraw = self.canvasPad.create_line(self.x, self.y, event.x, event.y, fill=self.fgcolor)
    
        def mylineArrow(self, event):
            """绘制箭头直线"""
            self.startDraw(event)
            self.lastDraw = self.canvasPad.create_line(self.x, self.y, event.x, event.y, arrow=LAST, fill=self.fgcolor)
    
        def myRect(self, event):
            self.startDraw(event)
            self.lastDraw = self.canvasPad.create_rectangle(self.x, self.y, event.x, event.y, outline=self.fgcolor)
    
        def myPen(self, event):
            self.startDraw(event)
            self.canvasPad.create_line(self.x, self.y, event.x, event.y, fill=self.fgcolor)
            self.x = event.x
            self.y = event.y
    
        def myEraser(self, event):
            self.startDraw(event)
            self.canvasPad.create_rectangle(event.x - 6, event.y - 6, event.x + 6, event.y + 6,
                                                            fill=self.bgcolor)
            self.x = event.x
            self.y = event.y
    
        def resize_canvas(self, event):
            """canvas的窗口跟随root的窗口变化"""
            self.canvasPad.config(width=event.width, height=event.height * 0.9)
    
    
    if __name__ == '__main__':
        win_width = 900
        win_height = 450
        root = Tk()
        root.geometry("600x500")
        root.title("yyx的画图软件")
        app = Application(root, win_width=win_width, win_height=win_height)
        root.mainloop()
    

    作者:才不是小emo的小杨

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python GUI编程:图形用户界面开发实战指南

    发表回复