Python升级之路( Lv11 ) GUI图形界面编程

Python系列文章目录

第一章 Python 入门
第二章 Python基本概念
第三章 序列
第四章 控制语句
第五章 函数
第六章 面向对象基础
第七章 面向对象深入
第八章 异常机制
第九章 文件操作
第十章 模块
第十一章 GUI图形界面编程


前言

这一章学习的是Python GUI 编程, 首先通过第一部分来介绍基于tkinter 的GUI编程;
第二部分介绍 tkinter 常用的七个组件; 第三部分介绍 tkinter 三种布局管理器;
第四部分介绍 tkinter 鼠标和键盘事件, 以及组件对象和组件类的绑定方式;
第五部分介绍 tkinter 七种高级组件; 第六部分介绍 tkinter 两种菜单;
最后一部分进行实操, 利用 tkinter 开发简易笔记本软件和简易画图软件.


一、GUI图形界面编程 —— tkinter

常用的GUI库

  • Tkinter tkinter(Tk interface)
    是 Python 的标准 GUI 库,支持跨平台的 GUI 程序开发. tkinter 适合小型的 GUI 程序编写,也特别适合初学者学习 GUI 编程. 本章以 tkinter 为核心来进行学习

  • wxPython
    wxPython 是比较流行的 GUI 库,适合大型应用程序开发,功能强于 tkinter,整体设计 框架类似于 MFC(Microsoft Foundation Classes 微软基础类库)

  • PyQT
    Qt 是一种开源的 GUI 库,适合大型 GUI 程序开发,PyQT 是 Qt 工具包标准的 Python 实现. 我们也可以使用 Qt Desginer 界面设计器快速开发 GUI 应用程序

tkinter 模块

本章中,涉及大量的 API 讲解。学习 API 最好的来源就是官方提供的文档:
tkinter 官网 传送门
tkinter 中文文档 传送门
tkinter 菜鸟教程 传送门

核心步骤

  1. 创建应用程序主窗口对象(也称:根窗口)

    # import tkinter
    from tkinter import * 
    root = Tk()
    

    ps: 我们可以通过 from tkinter import * 来手动导入所有 tkinter 所有包,
    也可以先不导包, 然后在提示需要导包导包时 Alt+ 回车 后选择第一条提示自动导包即可

  2. 在主窗口中,添加各种可视化组件

    btn01 = Button(root)    
    btn01["text"] = "点击进行抽奖"
    
  3. 通过几何布局管理器,管理组件的大小和位置

    btn01.pack()  
    
  4. 事件处理(通过绑定事件处理程序,响应用户操作所触发的事件)

    def lottery(e):     # 4.1 通过绑定事件处理程序,响应用户操作所触发的事件(比如:单击、双击等)
        messagebox.showinfo("Message", "恭喜您中奖了, 奖励您500万")
        print("有用户中奖, 奖金500万")
    
    
    btn01.bind("<Button-1>", lottery)
    

第一个 GUI 程序

基于以上步骤, 来实现一个简单的抽奖应用

步骤:

  1. 通过类 Tk 的无参构造函数
  2. 在主窗口中,添加各种可视化组件
  3. 通过几何布局管理器,管理组件的大小和位置
  4. 通过绑定事件处理程序,响应用户操作所触发的事件
  5. 调用组件方法, 进入事件循环

实操代码

from tkinter import Tk, Button, messagebox

root = Tk()     # 1. 通过类 Tk 的无参构造函数

btn01 = Button(root)    # 2. 在主窗口中,添加各种可视化组件,比如:按钮(Button)、文本框(Label)等
btn01["text"] = "点击进行抽奖"
btn01.pack()        # 3. 通过几何布局管理器,管理组件的大小和位置


def lottery(e):     # 4.1 通过绑定事件处理程序,响应用户操作所触发的事件(比如:单击、双击等)
    messagebox.showinfo("Message", "恭喜您中奖了, 奖励您500万")
    print("有用户中奖, 奖金500万")


btn01.bind("<Button-1>", lottery)
root.mainloop()     # 4.2 调用组件方法, 进入事件循环

结果展示
在这里插入图片描述

tkinter 主窗口

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

from tkinter import * root = Tk()

root.title("测试主窗口的位置和大小")
root.geometry("500x400+100+200") # 宽度 500,高度 400;距屏幕左边 100,距屏幕上边 200
root.mainloop()

GUI 编程整体描述

tkinter 中 GUI 组件的继承关系图图下图所示
在这里插入图片描述
这些类的基本作用如下:

  • Misc 和 Wm:
    Tkinter 的 GUI 组件有两个根父类,它们都直接继承了 object 类:
    Misc:它是所有组件的根父类: Wm:它主要提供了一些与窗口管理器通信的功能函数

  • Tk
    Misc 和 Wm 派生出子类 Tk,它代表应用程序的主窗口. 一般应用程序都需要直接或间接 使用 Tk

  • Pack、Place、Grid
    Pack、Place、Grid 是布局管理器. 布局管理器管理组件的大小、位置通过布局管理器可以将容器中的组件实现合理的排布

  • BaseWidget
    BaseWidget 是所有组件的父类

  • Widget
    Widget 是所有组件类的父类. Widget 一共有四个父类:BaseWidget、Pack、Grid、Place.
    这意味着,所有 GUI 组件同时具备这四个父类的属性和方法
    在这里插入图片描述

常用组件汇总列表

在这里插入图片描述
在这里插入图片描述

GUI 应用程序类的经典写法

通过类 Application 组织整个 GUI 程序,类 Application 继承了 Frame 及通过继承拥有 了父类的特性.
通过构造函数__init__()初始化窗口中的对象,通过 createWidgets()方法创建窗口中的对象.
Frame 框架是一个 tkinter 组件,表示一个矩形的区域. Frame 一般作为容器使用,可以放置其他组件,从而实现复杂的布局.

实操代码

"""测试一个经典的 GUI 程序的写法,使用面向对象的方式"""
import random
from tkinter import *
from tkinter import messagebox


class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master)  # super 代表的是父类的定义, 而不是父类对象
        self.master = master
        self.pack()
        self.createWidget()

    def createWidget(self):
        """创建组件"""
        self.btn01 = Button(self)
        self.btn01["text"] = "点击抽奖"
        self.btn01.pack()
        self.btn01["command"] = self.lottery

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

    def lottery(self):
        messagebox.showinfo("点击抽奖", "恭喜您中奖, 奖金{0}个粽子+{1}个咸鸭蛋".format(random.randint(1, 100), random.randint(1, 100)))


if __name__ == "__main__":
    root = Tk()
    root.geometry("400x100+810+330")
    root.title("GUI应用程序经典写法")
    app = Application(master=root)
    root.mainloop()
    

结果展示
在这里插入图片描述


二、简单组件

Label 标签

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

常用属性

  • width,height: 用于指定区域大小
    如果显示是文本,则以单个英文字符大小为单位(一个汉字宽度占 2 个字符位置,高度和英文字符一样);如果显示是图像,则以像素为单位。默认值是 根据具体显示的内容动态调整
  • font: 指定字体和字体大小. 如:font = (font_name,size)
  • image: 显示在 Label 上的图像,目前 tkinter 只支持 gif 格式
  • fg 和 bg: fg(foreground):前景色、bg(background):背景色
  • justify: 针对多行文字的对齐,可设置 justify 属性,可选值"left", “center” or “right”

实操代码

"""测试 Label 组件的基本用法,使用面向对象的方式"""
from tkinter import *


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="点击label", width=10, height=2, bg="black", fg="white")
        self.label01["text"] = "第一个Label"
        self.label01.config(fg="red", bg="green")
        self.label01.pack()

        self.label02 = Label(self, text="第二个Label", width=10, height=2, bg="blue", fg="white", font=("黑体", 30))
        self.label02.pack()

        # 显示图像
        global photo    # 把 photo 声明成全局变量. 如果是局部变量,本方法执行完毕后,图像对象销毁,窗口显示不出图像
        photo = PhotoImage(file="111.gif")
        self.label03 = Label(self, image=photo)
        self.label03.pack()  # 在父小部件中打包一个小部件

        self.label04 = Label(self, text="第一行\n 第二行\n 第三行", borderwidth=5, relief="groove", justify="right")
        self.label04.pack()


if __name__ == "__main__":
    root = Tk()
    root.geometry("600x600+810+330")
    root.title("测试Label")
    app = Application(master=root)
    root.mainloop()

注意:

  1. Label(标签)主要用于显示文本信息,也可以显示图像
  2. pack 按照组件的创建顺序将子组件添加到父组件中,按照垂直或者水平的方向自然排布
    如果不指定任何选项,默认在父组件中自顶向下垂直添加组件. pack 是代码量最少最简单的一种,可以用于快速生成界面.

Options 选项详解

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

可以通过以下三种方式设置 Options 选项,这在各种 GUI 组件中用法都一致:

  • 创建对象时,使用可变参数
    fred = Button(self, fg="red", bg="blue")

  • 创建对象后,使用字典索引方式

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

如何查看组件的 Options 选项?

  1. 可以通过打印 config()方法的返回值,查看 Options 选项 print(fred.config())
  2. 通过在 IDE 中,ctrl+鼠标左键 即可进入组件对象的构造方法,进入到方法内观察:
    在这里插入图片描述
    上面代码中有:“standard options 标准选项”和“widget-specific options 组件特定选项”. 我们将常见的选项汇总如下:
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

Button 按钮

Button(按钮)用来执行用户的单击操作. Button 可以包含文本,也可以包含图像
按钮被单击后会自动调用对应事件绑定的方法. 相关属性参数介绍见上面Options 选项详解部分图片

实操代码

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


class Application(Frame):
    def __init__(self, master):
        super().__init__(master)  # super()代表的是父类的定义, 而不是父类的对象
        self.master = master
        self.pack()
        self.createWidget()

    def createWidget(self):
        """创建组件"""
        self.btn01 = Button(root, text="登录", width=4, height=1, anchor=NE, command=self.login)
        self.btn01.pack()

        global photo
        photo = PhotoImage(file="111.gif")
        self.btn02 = Button(root, image=photo, command=self.login)
        self.btn02.pack()
        self.btn02.config(state="disabled")  # 设置按钮为禁用

    def login(self):
        messagebox.showinfo("智慧终端学习系统", "登录成功!欢迎开始学习!")


if __name__ == '__main__':
    root = Tk()
    root.title("测试Button")
    root.geometry("400x400+200+300")
    app = Application(master=root)
    root.mainloop()

运行结果

在这里插入图片描述

利用 lambda 表达式实现传参

lambda 表达式定义的是一个匿名函数,只适合简单输入参数,简单计算返回结果,不适合功能复杂情况
lambda 定义的匿名函数也有输入、也有输出,只是没有名字。语法格式如下: lambda 参数值列表:表达式

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

在这里插入图片描述

实操代码

from tkinter import Tk, Button

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")
"""
lambda 定义的匿名函数也有输入、也有输出,只是没有名字。语法格式如下: lambda 参数值列表:表达式 参数值列表即为输入。 表达式计算的结构即为输出。
"""
Button(root, text="测试 command2", command=lambda: mouseTest2("实参1传入", "实参2传入")).pack(side="left")
root.mainloop()

结果展示

在这里插入图片描述

Entry 单行文本框

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

Entry构造函数如下图, 相关属性参数介绍见上面Options 选项详解部分图片
在这里插入图片描述
实操代码

"""
Entry 用来接收一行字符串的控件。如果用户输入的文字长度长于 Entry 控件的宽度时,
文字会自动向后滚动。如果想输入多行文本, 需要使用 Text 控件。
"""
# 【示例】Entry 单行文本框实现简单登录界面
from tkinter import *
from tkinter import messagebox


class Appliaction(Frame):
    def __init__(self, master):
        super().__init__(master)
        self.master = master
        self.pack()
        self.createWidget()

    def createWidget(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.btn02 = Label(self, text="密码")
        self.pack()

        v2 = StringVar()
        self.entry02 = Entry(self, textvariable=v2, show="*")
        self.entry02.pack()

        Button(self, text="登录", command=self.login).pack()

    def login(self):
        title = "智慧终端学习系统"
        username = self.entry01.get()
        pwd = self.entry02.get()

        print("去数据库对比密码")
        print("用户名" + username)
        print("密码" + pwd)
        if username == "TimePause" and pwd == "123456":
            messagebox.showinfo(title, "登陆成功! 欢迎开始学习!")
        else:
            messagebox.showinfo(title, "登录失败, 用户名密码错误")


if __name__ == "__main__":
    root = Tk()
    root.title("智慧终端学习系统")
    root.geometry("400x130+200+300")
    app = Appliaction(master=root)
    root.mainloop()

结果展示
在这里插入图片描述

Text 多行文本框

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

Text 构造函数如下图, 相关属性参数介绍见上面Options 选项详解部分图片

在这里插入图片描述
实操代码

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


class Application(Frame):
    def __init__(self, master=None):   # 这里相当于java中定义了一个带参构造. master指代的是一个形参, 为后面创建类的对象时使用
        super().__init__(master)  # super()代表的是父类的定义,而不是父类对象
        self.master = master
        self.pack()
        self.createWidget()

    def createWidget(self):
        self.w1 = Text(root, width=40, height=12, bg="gray")  # 宽度 20 个字母(10 个汉字),高度一个行高
        self.w1.pack()
        self.w1.insert(1.0, "0123456789\nabcdefg")
        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):
        self.w1.insert(INSERT, ' 测试插入 ')    # INSERT 索引表示在光标处插入
        self.w1.insert(END, '测试尾插')         # END 索引号表示在最后插入
        self.w1.insert(1.8, "测试指定位置插入")     # Indexes(索引)是用来指向 Text 组件中文本的位置,Text 的组件索引也是对应实际字符之间的位置

    def returnText(self):
        print(self.w1.get(1.2, 1.6))
        print("所有文本内容:\n" + self.w1.get(1.0, END))  # 核心:行号以 1 开始 列号以 0 开始

    def addImage(self):
        global photo
        self.photo = PhotoImage(file="111.gif")
        self.w1.image_create(END, image=self.photo)

    def addWidget(self):
        b1 = Button(self.w1, text='创建一个新组件')  # 在 text 创建组件的命令
        self.w1.window_create(INSERT, window=b1)

    def webshow(self):
        webbrowser.open("http://www.baidu.com")

    def testTag(self):
        self.w1.delete(1.0, END)
        self.w1.insert(INSERT, "good good study,day day up!\n 测试 baidu\n Tag标签\n 精准控制文本")
        self.w1.tag_add("good", 1.0, 1.9)
        self.w1.tag_config("good", background="yellow", foreground="red")
        self.w1.tag_add("baidu", 4.1, 4.3)
        self.w1.tag_config("baidu", underline=True)
        self.w1.tag_bind("baidu", "<Button-1>", self.webshow)


if __name__ == '__main__':
    root = Tk()
    root.title("测试多行文本text")
    root.geometry("450x300+200+300")
    app = Application(master=root)
    root.mainloop()

结果展示

在这里插入图片描述

Radiobutton 单选按钮

Radiobutton 控件用于选择同一组单选按钮中的一个, 可以显示文本,也可以 显示图像

实操代码

"""测试 Radiobutton 组件的基本用法,使用面向对象的方式"""
from tkinter import Frame, Radiobutton, Button, messagebox, Tk, StringVar


class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.pack()
        self.createWidget()

    def createWidget(self):     # 定义组件
        self.v1 = StringVar()
        self.v1.set("F")

        self.r1 = Radiobutton(self, text="男性", value="M", variable=self.v1)     # 定义单选框
        self.r2 = Radiobutton(self, text="女性", value="F", variable=self.v1)
        self.r1.pack(side="left")
        self.r2.pack(side="left")

        Button(self, text="确定", command=self.confirm).pack(side="left")

    def confirm(self):
        messagebox.showinfo("测试", "选择性别" + self.v1.get())   # 定义弹窗信息


if __name__ == '__main__':
    root = Tk()
    root.geometry("400x50+200+300")
    app = Application(root)
    root.mainloop()

结果展示
在这里插入图片描述

Checkbutton 复选按钮

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

实操代码

"""测试 Checkbutton 组件的基本用法,使用面向对象的方式"""
from tkinter import Frame, IntVar, Checkbutton, Button, messagebox, Tk


class Application(Frame):
    # 创建构造方法
    def __init__(self, master=None):
        super().__init__(master)
        self.pack()
        self.createWidget()

    # 创建组件, 创建复选框并设置属性.
    def createWidget(self):
        self.codeHobby = IntVar()
        self.videoHobby = IntVar()

        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")

        # 创建确定按钮并创建对象的提示框
        self.b = Button(self, text="确定", command=self.showInfo).pack(side="left")  # 这里command传入的是方法的对象

    def showInfo(self):
        if self.codeHobby.get() == 1 and self.videoHobby.get() == 1:
            messagebox.showinfo("提示框", "既爱看视频又爱学习, 来我这里学编程吧")
        elif self.codeHobby.get() == 1:       # IntVar需要调用get方法才能获得
            messagebox.showinfo("提示框", "你这个人爱学习编程啊, 学python吧")
        elif self.videoHobby.get() == 1:
            messagebox.showinfo("提示框", "你这个人比较爱看视频啊, 少看点")
        else:
            messagebox.showinfo("提示框", "啥都不爱啊, 随便选一个作为你的爱好吧")


# 启动方法以及相关组件信息
if __name__ == "__main__":
    root = Tk()
    root.geometry("400x50+200+300")
    app = Application(root)
    root.mainloop()

结果展示
在这里插入图片描述

canvas 画布

canvas(画布)是一个矩形区域,可以放置图形、图像、组件等

实操代码

import random
from tkinter import Frame, Canvas, PhotoImage, Button, Tk


class Application(Frame):
    # 1. 初始化
    def __init__(self, master=None):
        super().__init__(master)
        self.pack()
        self.createWidget()

    def createWidget(self):
        # 2. 组件相关
        self.canvas = Canvas(self, width=300, height=200, bg="green")
        self.canvas.pack()
        # 画一条线
        line = self.canvas.create_line(10, 10, 30, 20, 40, 50)
        # 画矩形
        reat = self.canvas.create_rectangle(50, 50, 100, 100)
        # 画椭圆, 前后一对坐标为椭圆的边界矩形左上角和底部右下角
        oval = self.canvas.create_oval(50, 50, 100, 100)

        global photo
        photo = PhotoImage(file="111.gif")
        self.canvas.create_image(250, 250, image=photo)

        Button(self, text="来画10个矩形", command=self.draw10rect).pack(side="left")

    def draw10rect(self):
        for i in range(0, 10):
            x1 = random.randrange(int(self.canvas["width"]) / 2)
            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.geometry("400x300+200+300")
    app = Application(root)
    root.mainloop()

结果展示
在这里插入图片描述


三、布局管理

grid布局管理器

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

grid()方法提供的属性参数选项如下

选项 说明 取值范围
column 单元格的列号 从 0 开始的正整数
columnspan 跨列,跨越的列数 正整数
row 单元格的行号 从 0 开始的正整数
rowspan 跨行,跨越的行数 正整数
ipadx ipady 设置子组件之间的间隔,x 方向或者 y 方向, 默认单位为像素 非负浮点数,默认 0.0
padx pady 与之并列的组件之间的间隔,x 方向或者 y 方 向,默认单位是像素 非负浮点数,默认 0.0
sticky 组件紧贴所在单元格的某一角,对应于东南西 北中以及 4 个角 “n”, “s”, “w”, “e”, “nw”, “sw”, “se”, “ne”, “center”(默认)

实操代码

from tkinter import *


class Application(Frame):
    def __init__(self, master=None):
        super(Application, self).__init__(master)
        self.pack()
        self.createWidget()

    def createWidget(self):
        self.label1 = Label(self, text="用户名")
        self.label1.grid(row=0, column=0)
        self.entry1 = Entry(self)
        self.entry1.grid(row=0, column=1)
        Label(self, text="用户名为手机号").grid(row=0, column=2)
        Label(self, text="密码").grid(row=1, column=0)
        Entry(self, show="*").grid(row=1, column=1)

        Button(self, text="登录").grid(row=2, column=1, sticky=SW)
        Button(self, text="取消").grid(row=2, column=2, sticky=S)


if __name__ == '__main__':
    root = Tk()
    root.geometry("400x90+200+300")
    app = Application(master=root)
    root.mainloop()

结果展示
在这里插入图片描述

pack 布局管理器

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

pack()方法提供的方法参数选项

如上列出了 pack 布局所有的属性,但是不需要挨个熟悉,了解基本的即可
pack 适用于简单的垂直或水平排布,如果需要复杂的布局可以使用 grid 或 place

选项 说明 取值范围
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”(默认)

在这里插入图片描述

实操代码

from tkinter import *

root = Tk()
root.geometry("700x220")

# Frame是个矩形区域, 用来存放其他组件
f1 = Frame(root)
f1.pack()
f2 = Frame(root)
f2.pack()

# 声明一个元组
btnText = ("流行风", "中国风", "日本风", "重金属", "轻音乐")

# 遍历元组, 为Button命令
for txt in btnText:
    Button(f1, text=txt).pack(side="left", padx="10")

# 遍历一个range序列
for i in range(1, 20):
    Button(f2, width=5, height=10, bg="black" if i % 2 == 0 else "white").pack(side="left")

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”(默认)

四、事件管理

一个 GUI 应用整个生命周期都处在一个消息循环 (event loop) 中。它等待事件的发生,并作出相应的处理.
Tkinter 提供了用以处理相关事件的机制. 处理函数可被绑定给各个控件的各种事件.
widget.bind(event, handler) 如果相关事件发生, handler 函数会被触发, 事件对象 event 会传递给 handler 函数.

鼠标和键盘事件

代码 说明
<Button-1> <ButtonPress-1> 鼠标左键按下。 2 表示右键,3 表示中键;
<ButtonRelease-1> 鼠标左键释放
<B1-Motion> 按住鼠标左键移动
<Double-Button-1> 双击左键
<Enter> 鼠标指针进入某一组件区域
<Leave> 鼠标指针离开某一组件区域
<MouseWheel> 滚动滚轮;
<KeyPress-a> 按下 a 键,a 可用其他键替代
<KeyRelease-a> 释放 a 键。
<KeyPress-A> 按下 A 键(大写的 A)
<Alt-KeyPress-a> 同时按下 alt 和 a;alt 可用 ctrl 和 shift 替代
<Double-KeyPress-a> 快速按两下 a
<Control-V> CTRL 和 V 键被同时按下,V 可以换成其它键位

event 对象常用属性

名称 说明
char 按键字符,仅对键盘事件有效
keycode 按键编码,仅对键盘事件有效
keysym 按键名称,仅对键盘事件有效 比如按下空格键: 键的 char: 键的 keycode, 32 键的 keysym: space ; 再比如按下 a 键: 键的 char:a 键的 keycode:65 键的 keysym:a
num 鼠标按键,仅对鼠标事件有效
type 所触发的事件类型
widget 引起事件的组件
width,height 组件改变后的大小,仅 Configure 有效
x,y 鼠标当前位置,相对于父容器
x_root,y_root 鼠标当前位置,相对于整个屏幕

实操代码

from tkinter import *

root = Tk();
root.geometry("530x300")
c1 = Canvas(root, width=200, height=200, bg="green")
c1.pack()


def mouseTest(event):
    """鼠标事件测试"""
    print("鼠标左键单击位置(相对于父容器):{0},{1}".format(event.x, event.y))
    print("鼠标左键单击位置(相对于屏幕): {0},{1}".format(event.x_root, event.y_root))
    print("事件绑定的组件:{0}".format(event.widget))


def testDrag(event):
    c1.create_oval(event.x, event.y, event.x + 1, event.y + 1)


def keyboardTest(event):
    """
    char 按键字符,仅对键盘事件有效
    keycode 按键编码,仅对键盘事件有效
    keysym 按键名称,仅对键盘事件有效 比如按下空格键: 键的 char: 键的 keycode:32 键的 keysym:space
    num 鼠标按键,仅对鼠标事件有效
    type 所触发的事件类型
    widget 引起事件的组件
    width,height 组件改变后的大小,仅 Configure 有效
    x,y 鼠标当前位置,相对于父容器
    x_root,y_root 鼠标当前位置,相对于整个屏幕
    """
    print("键的 keycode:{0},键的 char:{1},键的 keysym:{2}".format(event.keycode, event.char, event.keysym))


def press_a_test(event):
    print("press a")


def release_a_test(event):
    print("release a")


c1.bind("<Button-1>", mouseTest)    # 鼠标左键按下
c1.bind("<B1-Motion>", testDrag)    # 按住鼠标左键移动
root.bind("<KeyPress>", keyboardTest)    # 键盘按压事件
root.bind("<KeyPress-a>", press_a_test)     # 按下 a 键,a 可用其他键替代
root.bind("<KeyRelease-a>", release_a_test)     # # 释放 a 键
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) 比如:btn01.bind_class(“Button”,”<Button-1>”,func)

实操代码

from tkinter import Tk, Button

root = Tk()
root.geometry("270x30")


def mouseTest1(event):
    print("bind()方式绑定,可以获取 event 对象")
    print(event.widget)


def mouseTest2(a, b):
    print("a={0},b={1}".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("实参1传入", "实参2传入"))
b2.pack(side="left")

# 给所有 Button 按钮都绑定右键单击事件<Button-2>
b1.bind_class("Button", "<Button-2>", mouseTest3)
root.mainloop()


五、高级组件

OptionMenu 选择项

OptionMenu(选择项, 类似前端多选框)用来做多选一,选中的项在顶部显示。显示效果如下:

实操代码

from tkinter import Tk, StringVar, OptionMenu, Button

root = Tk()
root.geometry("200x100")
v = StringVar(root)
v.set("首选项")
"""下拉列表选项框"""
om = OptionMenu(root, v, "第二选项", "第三选项", "第四选项")
om["width"] = 10
om.pack()

def test1():
    print("您的选择是:", v.get())
    v.set("新设置的首选项")      # 直接修改了 optionmenu 中选中的值


Button(root, text="确定", command=test1).pack()
root.mainloop()

结果展示
在这里插入图片描述

Scale 移动滑块

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

实操代码

"""
scale 滑块的使用测试
【示例】使用 Scale(移动滑块)控制字体大小变化
"""
from tkinter import *

root = Tk()
root.geometry("400x150")


def test1(value):
    print("滑块的值:", value)
    newFont = ("宋体", value)
    a.config(font=newFont)      # 这里将滑块的值, 附入标签中


s1 = Scale(root, from_=10, to=50, length=200, tickinterval=5, orient=HORIZONTAL, command=test1)
s1.pack()

a = Label(root, text="测试滑块", width=10, height=1, bg="black", fg="white")
a.pack()


root.mainloop()

结果展示
在这里插入图片描述

颜色选择框

实操代码

from tkinter import Tk, StringVar, OptionMenu, Button
from tkinter.colorchooser import askcolor

print("======================================")
root = Tk()
root.geometry("400x150")


def test1():
    s1 = askcolor(color="red", title="选择背景色")
    print(s1)  # s1 的值是:((0.0, 0.0, 255.99609375), '#0000ff')
    root.config(bg=s1[1])       # 这里, 将父组件的颜色设置成button按钮点击后颜色选择框的值,


Button(root, text="选择背景色", command=test1).pack()
root.mainloop()

结果展示
在这里插入图片描述

文件对话框

文件对话框帮助我们实现可视化的操作目录、操作文件. 将文件、目录的信息传入到程序中

文件对话框包含如下一些常用函数
在这里插入图片描述
命名参数 options 的常见值如下:

在这里插入图片描述
实操代码——文件对话框基本用法

"""文件对话框获取文件"""
from tkinter import *
from tkinter.filedialog import *

root = Tk()
root.geometry("400x100")


def test1():
    f = askopenfilename(title="上传文件", initialdir="f:/file", filetypes=[("视频文件", ".mp4")])     # askopenfilename(**options)  返回打开的文件名
    # print(f)
    show["text"] = f        # 这里label将显示选择的视频文件名称


Button(root, text="选择编辑的视频文件", command=test1).pack()
show = Label(root, width=40, height=3, bg="green")
show.pack()
root.mainloop()

结果展示
在这里插入图片描述

简单输入对话框

simpledialog(简单对话框)包含如下常用函数:
在这里插入图片描述
实操代码

"""简单对话框"""
from tkinter.simpledialog import *

root = Tk()
root.geometry("400x100")


def test1():
    a = askinteger(title="输入年龄", prompt="请输入年龄 ", initialvalue=18, minvalue=1,
                   maxvalue=150)  # askstring、askfloat 框使用方式一样
    show["text"] = a


Button(root, text="你有多大了? 请点击后输入", command=test1).pack()
show = Label(root, width=40, height=3, bg="green")
show.pack()

root.mainloop()


结果展示
在这里插入图片描述

通用消息框

messagebox(通用消息框)用于和用户简单的交互,用户点击确定、取消

如下列出了 messagebox 的常见函数:

函数 说明
askokcancel(title,message,**opt ions) OK/Cancel 对话框
askquestion(title,message,**opt ions) Yes/No 问题对话框
askretrycancel(title,message,** options) Retry/Cancel问题对话框
showerror(title,message,**optio ns) 错误消息对话框
showinfo(title,message,**option s) 消息框
showwarning(title,message,**opt ions) 警告消息框

实操代码

from tkinter import *
from tkinter.messagebox import *

root = Tk()
root.geometry("400x100")

a1 = showinfo(title="测试通用消息框", message="点击出现通用消息框")
print(a1)

root.mainloop()

结果展示
在这里插入图片描述

ttk 子模块控件

前面学的组件是 tkinter 模块下的组件,整体界面风格较老旧。为了弥补这点不足, 推出了 ttk 组件
ttk 组件更加美观、功能更加强大, 新增了 LabeledScale(带标签的 Scale)、Notebook(多文档窗口)、Progressbar(进度条)、Treeview(树)等组件. 使用 ttk 组件与使用普通的 Tkinter 组件并没有多大的区别,只要导入 ttk 模块即可

注意事项:

  • ttk 子模块的官方文档: https://docs.python.org/3.7/library/tkinter.ttk.html
  • 如果项目确实需要用到复杂的界面,推荐大家使用 wxpython 或者 pyQt.

六、菜单

GUI 程序通常都有菜单,方便用户的交互

我们一般将菜单分为两种:

  1. 主菜单: 主菜单通常位于 GUI 程序上方。例如:
    在这里插入图片描述
  2. 快捷菜单(上下文菜单): 通过鼠标右键单击某个组件对象而弹出的菜单,一般是与该组件相关的操作

主菜单

主菜单一般包含:文件、编辑、帮助等,位于 GUI 窗口的上面

创建主菜单一般有如下 4 步:

  1. 创建主菜单栏对象
    menubar = tk.Menu(root)

  2. 创建菜单,并添加到主菜单栏对象
    file_menu = tk.Menu(menubar) menubar.add_cascade(label=”文件”,menu=file_menu)

  3. 添加菜单项到 2 步中的菜单

    file_menu.add_command(label=”打开”) 
    file_menu.add_command(label=”保存”,accelerator=^p” command=mySaveFile) 
    file_menu.add_separator() 
    file_menu.add_command(label=”退出”) 
    
  4. 将主菜单栏添加到根窗口
    root[“menu”]=menubar

实操代码

"""开发记事本软件的菜单 """
from tkinter import Frame, Menu, Text, Tk
from tkinter.filedialog import *
from tkinter.colorchooser import *


class Application(Frame):
    def __init__(self, master=None):
        super(Application, self).__init__(master)
        self.master = master
        self.textpad = None
        self.pack()
        self.createWidget()

    def createWidget(self):
        """创建主菜单栏"""
        menubar = Menu(root)
        # 创建子菜单
        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)
        # 将主菜单栏加到根窗
        root["menu"] = menubar
        # 文本编辑区
        self.textpad = Text(root, width=50, height=30)
        self.textpad.pack()

    def test(self):
        pass


if __name__ == '__main__':
    root = Tk()
    root.geometry("450x300+200+300")
    root.title("简易记事本")
    app = Application(master=root)
    root.mainloop()

上下文菜单

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

创建快捷菜单步骤如下

  1. 创建菜单

     menubar = tk.Menu(root) 
     menubar.add_command(label=”字体”) 
    
  2. 绑定鼠标右键单击事件

    def test(event): 
    	menubar.post(event.x_root,event.y_root) #在鼠标右键单击坐标处显示菜单 
    root.bind(<Button-3>,test)
    

实操代码

"""
为记事本程序增加上下文菜单
"""
from tkinter import *
from tkinter.colorchooser import *
from tkinter.filedialog import *

root = Tk()
root.geometry("400x400")


def openAskColor():
    s1 = askcolor(color="red", title="选择背景色")
    root.config(bg=s1[1])


# 创建快捷菜单
menubar2 = Menu(root)
menubar2.add_command(label="颜色", command=openAskColor)      # 通过command来添加函数, 为该实现该菜单功能
menuedit = Menu(menubar2, tearoff=0)
menuedit.add_command(label="剪切")
menuedit.add_command(label="复制")
menuedit.add_command(label="粘贴")
menubar2.add_cascade(label="编辑", menu=menuedit)


def test(event):
    # 菜单在鼠标右键单击的坐标处显示
    menubar2.post(event.x_root, event.y_root)


# 编辑区
w1 = Text(root, width=50, height=30)
w1.pack()
w1.bind("<Button-3>", test)
root.mainloop()


七、项目实操

记事本软件开发

结合所学 GUI 知识,开发一款模仿 windows 记事本的软件。包含了基本的功能:

  1. 新建文本文件
  2. 保存文件
  3. 修改文件内容
  4. 退出
  5. 各种快捷键处理 6. 修改文本区域背景色

实操代码

"""
结合所学 GUI 知识,开发一款模仿 windows 记事本的软件。包含了基本的功能:
1. 新建文本文件
2. 保存文件
3. 修改文件内容
4. 退出
5. 各种快捷键处理
6. 修改文本区域背景色
"""
from tkinter import Frame, Tk, Menu, Text, INSERT, END
from tkinter.filedialog import *
from tkinter.colorchooser import *


class Application(Frame):
    def __init__(self, master=None):
        super(Application, self).__init__(master)
        self.master = master
        self.textPad = None  # 文本框对象
        self.filename = None  # 打开文件的名字
        self.contextMenu = None  # 上下文菜单对象
        self.pack()
        self.createWidget()

    def createWidget(self):
        """在组件中创建主菜单"""
        menubar = Menu(root)
        """创建子菜单"""
        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.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.exit)
        # 将主菜单栏加到根窗口
        root["menu"] = menubar
        # 添加快捷键事件处理
        root.bind("<Control-n>", lambda event: self.newfile())
        root.bind("<Control-o>", lambda event: self.openfile())
        root.bind("<Control-s>", lambda event: self.savefile())
        root.bind("<Control-q>", lambda event: self.exit())
        # 文本编辑区
        self.textpad = Text(root, width=50, height=30)
        self.textpad.pack()
        # 创建上下菜单
        self.contextMenu = Menu(root)
        self.contextMenu.add_command(label="背景颜色", command=self.openAskColor)
        # 为右键绑定事件
        root.bind("<Button-3>",self.createContextMenu)

    def newfile(self):
        self.textpad.delete('1.0','end')  # 把 Text 控件中的内容清空
        self.filename = asksaveasfilename(title='另存为', initialfile='未命名.txt', filetypes=[("文本文档", "*.txt")], defaultextension='.txt')
        print(self.filename)
        self.savefile()

    def openfile(self):
        self.textpad.delete('1.0','end')  # 先把 Text 控件中的内容清空
        with askopenfile(title="打开文件") as f:
            self.textpad.insert(INSERT, f.read())
            self.filename = f.name
            print(f.name)

    def savefile(self):
        with open(self.filename, "w") as f:
            c = self.textpad.get(1.0, END)
            f.write(c)

    def exit(self):
        root.quit()

    def openAskColor(self):
        s1 = askcolor(color="red", title="选择背景色")
        self.textpad.config(bg=s1[1])

    def createContextMenu(self, event):  # 菜单在鼠标右键单击的坐标处显示
        self.contextMenu.post(event.x_root, event.y_root)


if __name__ == '__main__':
    root = Tk()
    root.geometry("450x300+200+300")
    root.title("最初的记事本")
    app = Application(root)
    root.mainloop()

将 python 程序打包成 exe 文件

  1. 安装 pyinstaller 模块 在 pycharm 中操作:file-->setting-->Project:xxx -->Project interpretor,再点击+ (加号)
    在这里插入图片描述
    在新弹窗中输入要下载的第三方模块, 然后点击该模块, 最后点击左下角安装即可
    在这里插入图片描述

  2. 在 pycharm 的 Terminal 终端输入如下命令: pyinstaller -F xxxx.py
    需要进入到当前模块所在目录下

    注意: pyinstaller相关参数如下: 
    --icon=图标路径(pyinstaller 
    -F --icon=my.ico XXXX.py) 
    -F 打包成一个 exe 文件 
    -w 使用窗口,无控制台 
    -c 使用控制台,无窗口 
    -D 创建一个目录,里面包含 exe 以及其他一些依赖性文件
    
  3. 在项目的 dist 目录下可以看到生成了 exe 文件,直接在 windows 系统中使用即可
    在这里插入图片描述

画图软件开发

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

  1. 画笔
  2. 矩形/椭圆绘制
  3. 清屏
  4. 橡皮擦
  5. 直线/带箭头的直线
  6. 修改画笔颜色、背景颜色

实操代码

"""开发绘图软件的菜单"""
from tkinter import Frame, Canvas, Tk, Button, LAST
from tkinter.colorchooser import *

# 窗口的宽度和高度
win_width = 900
win_height = 450


class Application(Frame):
    def __init__(self, master=None, bgcolor="#000000"):
        super(Application, self).__init__(master)
        self.master = master
        self.bgcolor = bgcolor
        self.x = 0
        self.y = 0
        self.fgcolor = "#ff0000"
        self.lastDraw = 0
        self.startDrawFlag = False
        self.pack()
        self.createWidget()

    def createWidget(self):
        # 创建绘图区
        self.drawpad = Canvas(root, width=win_width, height=win_height * 0.9, bg=self.bgcolor)
        self.drawpad.pack()

        # 创建按钮
        btn_start = Button(root, text="开始", name="start")
        btn_start.pack(side="left", padx="10")
        btn_pen = Button(root, text="画笔", name="pen")
        btn_pen.pack(side="left", padx="10")
        btn_rect = Button(root, text="矩形", name="rect")
        btn_rect.pack(side="left", padx="10")
        btn_clear = Button(root, text="清屏", name="clear")
        btn_clear.pack(side="left", padx="10")
        btn_erasor = Button(root, text="橡皮擦", name="erasor")
        btn_erasor.pack(side="left", padx="10")
        btn_line = Button(root, text="直线", name="line")
        btn_line.pack(side="left", padx="10")
        btn_lineArrow = Button(root, text="箭头直线", name="lineArrow")
        btn_lineArrow.pack(side="left", padx="10")
        btn_color = Button(root, text="颜色", name="color")
        btn_color.pack(side="left", padx="10")
        # 事件处理
        btn_pen.bind_class("Button", "<1>", self.eventManager)
        self.drawpad.bind("<ButtonRelease-1>", self.stopDraw)

        # 增加颜色切换的快捷键
        root.bind("<KeyPress-r>", self.kuaijiejian)
        root.bind("<KeyPress-g>", self.kuaijiejian)
        root.bind("<KeyPress-y>", self.kuaijiejian)

    def eventManager(self, event):
        name = event.widget.winfo_name()
        # print(name)
        if name == "line":
            self.drawpad.bind("<B1-Motion>", self.myline)
        elif name == "lineArrow":
            self.drawpad.bind("<B1-Motion>", self.mylineArrow)
        elif name == "rect":
            self.drawpad.bind("<B1-Motion>", self.myRect)
        elif name == "pen":
            self.drawpad.bind("<B1-Motion>", self.myPen)
        elif name == "erasor":
            self.drawpad.bind("<B1-Motion>", self.myErasor)
        elif name == "clear":
            self.drawpad.delete("all")
        elif name == "color":
            c = askcolor(color=self.fgcolor, title="选择画笔颜色")
            # [(255,0,0),"#ff0000"]
            self.fgcolor = c[1]

    def stopDraw(self, event):
        self.startDrawFlag = False
        self.lastDraw = 0

    def startDraw(self, event):
        self.drawpad.delete(self.lastDraw)

        if not self.startDrawFlag:
            self.startDrawFlag = True
            self.x = event.x
            self.y = event.y

    def myline(self, event):
        self.startDraw(event)
        self.lastDraw = self.drawpad.create_line(self.x, self.y, event.x, event.y, fill=self.fgcolor)

    def mylineArrow(self, event):
        self.startDraw(event)
        self.lastDraw = self.drawpad.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.drawpad.create_rectangle(self.x, self.y, event.x, event.y, outline=self.fgcolor)

    def myPen(self, event):
        self.startDraw(event)
        self.drawpad.create_line(self.x, self.y, event.x, event.y, fill=self.fgcolor)
        self.x = event.x
        self.y = event.y

    def myErasor(self, event):
        self.startDraw(event)
        self.drawpad.create_rectangle(event.x - 4, event.y - 4, event.x + 4, event.y + 4, fill=self.bgcolor)
        self.x = event.x
        self.y = event.y

    def kuaijiejian(self, event):
        if event.char == "r":
            self.fgcolor = "#ff0000"
        elif event.char == "g":
            self.fgcolor = "#00ff00"
        elif event.char == "y":
            self.fgcolor = "#ffff00"


if __name__ == "__main__":
    root = Tk()
    root.geometry(str(win_width) + "x" + str(win_height) + "+200+300")
    root.title("你的画图软件")
    app = Application(root)
    root.mainloop()

将 python 程序打包成 exe 文件

打包步骤同上面笔记本打包步骤一致, 但需注意如果使用 pyinstaller -F paint_release.py 进行打包,
打包后的程序在运行后会启动一个命令行界面. 因此我们可以在打包时添加 -w, 即 pyinstaller -F -w paint_release.py
通过这种命令打包后, 运行程序时便不会同时打包命令行/控制台


猜你喜欢

转载自blog.csdn.net/qq_43371556/article/details/125332732