python的tkinter

tkinter

python的PEP8规范:PEP 8 – Style Guide for Python Code | peps.python.org

1.Tkinter
tkinter(Tk interface)是Python的标准GUI库,支持跨平台的GUI程序开发。tkinter适合小型的GUI程序编写,也特别适合初学者学习GUI编程。本书以tkinter为核心进行讲解。
2.wxPython
wxPython是比较流行的GU1库,适合大型应用程序开发,功能强于tkinter,整体设计框架类似于MFC(MicrosoftFoundation Classes微软基础类库)。
3.PyQT
Qt是一种开源的GUI库,适合大型GUI程序开发,PyQT是Qt工具包标准的Python实现。我们也可以使用Qt Desginer

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

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

    1. 通过类Tk的无参构造函数

      from tkinter inport *
      
      
      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)
      

    【示例】使用tkinter模块,创建GUI应用程序,并实现点击按钮的事件处理

from tkinter import *
from tkinter import messagebox


root = Tk()
btn01 = Button(root)
btn01["text"] = "点我送花"
btn01.pack()

def songhua(e):
    messagebox.showinfo("Message","送你一朵玫瑰花,请你爱上我!")
    print("送你99朵玫瑰花")
btn01.bind("<Button-1>", songhua)


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

tkinter 主窗口

主窗口位置和大小

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

from tkinter import *
from tkinter import messagebox


root = Tk()

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

GUI 编程整体描述

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

image-20230130225359525

根据上图所示,我们依次简介这些类的基本作用。

  • 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 组件同时具备这四个父类的属性和方法。

image-20230130232402478

【注】想观察的层次结构可以在类定义处的类名上单击右键,选择 Diagram --> Show Diagram

常用组件汇总列表

Tkinter类 名称 简介
Toplevel 顶层 容器类,可用于为其他组件提供单独的容器;Toplevel有点类似于窗口
Button 按钮 代表按钮组件
Canvas 画布 提供绘图功能,包括直线、矩形、椭圆、多边形、位图等
Checkbutton 复选框 可供用户勾选的复选框
Entry 单行输入框 用户可输入内容
Frame 容器 用于装载其它 GUI 组件
Label 标签 用于像是不可编辑的文本或图标
LabelFrame 容器 也是容器组件,类似于Frame,但它支持添加标题
Listbox 列表框 列出多个选项,供用户选择
Menu 菜单 菜单组件
Menubutton 菜单按钮 用来包含菜单的按钮(包括下拉式、层叠式等)
OptionMenu 菜单按钮 Menubutton 的子类,也代表菜单按钮,可通过按钮打开一个菜单
Message 消息框 类似于标签,但可以显示多行文本;后来当 Label 也能显示多行文本之后,该组件基本处于废弃状态
PanedWindow 分区窗口 该容器会被划分成多个区域,每添加一个组件占一个区域,用户可通过拖动分隔线来改变各区域的大小
Radiobutton 单选按钮 可供用户点边的单选钮
Scale 滑动条 拖动滑块可设定起始值和结束值,可显示当前位置的精确值
Spinbox 微雕选择器 用户可通过该组件的向上、向下箭头选择不同的值
Scrollbar 滚动条 用于为组件(文本域、画布、列表框、文本框)提供滚动功能
Text 多行文本框 显示多行文本

GUI 应用程序类的经典写法

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

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

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

Label标签

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

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

  1. width, height:

    1. 用于指定区域大小,如果显示是文本,则以单个英文字符大小为单位(一个汉字宽度占2个字符位置,高度和英文字符一样);如果显示图像,则以像素为单位。默认值是根据具体显示的内容动态调整。
  2. font

    指定字体和字体大小,如:font = (font_name,size)

  3. image:

    显示在Label上的图像,目前 tkinter 只支持 GIF 格式。

  4. fg 和 bg

    fg (foreground):前景色、bg(background): 背景色

  5. justify

    针对多行文字的对齐,可设置 justify 属性, 可选值 "left", “center” or “right”

Option 选项详解

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

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

创建对象时,使用命名参数(也叫关键字参数)

fred = Button(self, fg="red", bg="blue")

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

fred["fg"] = "red"
fred["bg"] = "blue"

创建对象后,使用 config() 方法

fred.config(fg="red", bg="blue")

Button

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

"""使用面向对象的方式,测试一个经典的 GUI 程序的写法,使用面向对象的方式"""

from tkinter import *
from tkinter import messagebox


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

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


    def createWidget(self):
        """创建组件"""
        self.btn01 = Button(root, text="登录", command=self.login)
        self.btn01.pack()


        # 显示图像
        global photo # 把photo声明成全局变量。如果是局部变量,本方法执行完毕后,图像对象销毁,窗口显示不出图像。
        photo = PhotoImage(file="picture/OGC2.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.geometry("800x1000+200+300")
    root.title("button测试")
    app = Application(master=root)
    root.mainloop()

Entry 单行文本框

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

"""使用面向对象的方式,测试一个经典的 GUI 程序的写法,使用面向对象的方式"""

from tkinter import *
from tkinter import messagebox


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


    def __init__(self, master=None):
        super().__init__(master)    # super() 代表的是父类的定义, 而不是父类对象
        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.label02 = Label(self, text="密码")
        self.label02.pack()

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

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

    def login(self):
        username = self.entry01.get()
        pwd = self.entry02.get()

        print("去数据库比对用户名和密码!")
        print("用户名:"+username)
        print("密码:"+pwd)

        if username == "gaoqi" and pwd == "123456":
            messagebox.showinfo("尚学堂学习系统", "登录成功!欢迎开始学习!")
        else:
            messagebox.showinfo("尚学堂学习系统","登录失败!用户名或密码错误!")

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

Text多行文本框

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

"""使用面向对象的方式,测试一个经典的 GUI 程序的写法,使用面向对象的方式"""

from tkinter import *
import webbrowser


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


    def __init__(self, master=None):
        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):
        # INSERT索引表示在光标处插入
        self.w1.insert(INSERT,'Gaoqi')
        # EWD索引号表示在最后插入
        self.w1.insert(END, '【sxt】')
        self.w1.insert(1.8, "gaoqi")

    def returnText(self):
        # Indexes(索引)是用来指向Text组件中文本的位置,Text的组件索引也是对应实际字符之间的位置。
        # 核心:行号以1开始 列号以0开始
        print(self.w1.get(1.2, 1.6))
        print("所有文本内容:\n"+self.w1.get(1.0,END))

    def addImage(self):
        # global photo
        self.photo = PhotoImage(file="picture/OGC4.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 testTag(self):
        self.w1.delete(1.0, END)
        self.w1.insert(INSERT, "good good study,day day up!\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", 4.0, 4.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")

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

Radiobutton单选按钮

Radiobutton控件用于选择同一组单选按钮中的一个。
Radiobutton可以显示文本,也可以显示图像。
【示例】Radiobutton基础用法

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


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


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


    def createWidget(self):
        self.v = StringVar();
        self.v.set("F")

        self.r1 = Radiobutton(self, text="男性", value="M", variable=self.v)
        self.r2 = Radiobutton(self, text="女性", value="F", variable=self.v)
        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.v.get())

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

Checkbutton复选按钮

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

"""测试Checkbutton组件的基本用法,使用面向对象的方式"""
"""使用面向对象的方式,测试一个经典的 GUI 程序的写法,使用面向对象的方式"""

from tkinter import *
from tkinter import messagebox


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


    def __init__(self, master=None):
        super().__init__(master)    # super() 代表的是父类的定义, 而不是父类对象
        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("测试","看视频,都是正常人有的爱好I你喜欢看什么类型?")
        if self.codeHobby.get() == 1:
            messagebox.showinfo("测试","抓获野生程序猿一只,赶紧送给他尚学堂的视频充饥")

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

canvas画布

​ canvas(画布)是一个矩形区域,可以放置图形、图像、组件等。本节我们简单介绍canvas的使用,更加详细和深入的内容将在后面的“图形绘制”章节讲解.

"""使用面向对象的方式,测试一个经典的 GUI 程序的写法,使用面向对象的方式"""

from tkinter import *
from tkinter import messagebox
import random


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


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


    def createWidget(self):
        self.canvas = Canvas(self, width=300, height=200, 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="picture/OGC2.gif")
        # self.canvas.create_image(150, 170, 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)
            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")
    root.title("Radiobutton测试")
    app = Application(master=root)
    root.mainloop()

布局管理器

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

grid布局管理器

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

grid()方法提供的选项

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

【示例】通过gid布局-实现计算器软件界面.
根据实际简易计算器的按键分布,设计一个相仿的计算器界面,相应的功能暂不需要实现。

image-20230208224237679

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

"""使用面向对象的方式,测试一个经典的 GUI 程序的写法,使用面向对象的方式"""

from tkinter import *
from tkinter import messagebox
import random


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


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


    def createWidget(self):
        """通过grid布局实现计算器的界面"""
        btnText = (("MC","M+","M-","MR"),("C","±","/","x"),(7,8,9,"-"),(4,5,6,"+"),(1,2,3,"="),(0,"."))

        Entry(self).grid(row=0, column=0, columnspan=4,pady=10)
        for rindex, r in enumerate(btnText):
            for cindex, c in enumerate(r):
                if c == "=":
                    Button(self, text=c,width=2).grid(row=rindex+1, column=cindex,rowspan=2, sticky=NSEW)
                elif c == 0:
                    Button(self, text=c, width=2).grid(row=rindex + 1, column=cindex, columnspan=2, sticky=NSEW)
                elif c == ".":
                    Button(self, text=c, width=2).grid(row=rindex + 1, column=cindex+1, sticky=NSEW)
                else:
                    Button(self, text=c, width=2).grid(row=rindex+1, column=cindex, sticky=NSEW)


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

pack布局管理器

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

pack()方法提供的选项

名称 描述 取值范国
expand 当值为"yes“时,side选项无效.组件显示在父配件中心位置,若和yes,若fill选项为“both”,则填充父组件的剩余空间 "yes"。自然数,"no",0(默认值"no"或0)
fill 填充x(y)方向上的空间.当属性side="top"或"bottom"时.填充×方向;当属性side="left"或"right"时,填充"y"方向;当expand选项为"yes"时,填充父组件的剩余空间。 "x","y","both","none"(默认值为none)
ipadx,padxy 设置子组件之例的同隔.×方向或者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布局所有的属性,但是不需要挨个熟悉,了解基本的即可。pack适用于简单的垂直平排布,如果需腰复杂的布局可以使用grid或place.
【示例】pack布局用法,制作钢琴按键布局

image-20230208233421828

# coding=utf-8
# 测试pack布局管理
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,13):
    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布局管理器可以通过坐标精确控制组件的位置,适用于一些布局更加灵活的场景。

place0方法的选项

选项 说明 取值范围
x,y 组件左上角的绝对坐标(相对于窗口) 非负整数
×和y选项用于设置偏移(像素),如果同时设置relx(rely)和x(y),那么place将优先计算relx和rely,然后再实现×和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 *

root = Tk();root.geometry("500x300")
root.title("布局管理olace");root["bg"]="white"
f1 = Frame(root, width=200, height=200, bg="green")
f1.place(x=30, y=30)

Button(root, text="尚学堂").place(relx=0.2, x=100, y=20, relwidth=0.2, relheight=0.5)
Button(f1, text="百战程序员").place(relx=0.6, rely=0.7)
Button(f1, text="高淇老师").place(relx=0.5, rely=0.2)
root.mainloop()

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

image-20230209000410665

"""扑克牌游观的界面设计"""
from tkinter import *
class Application(Frame):
    def __init__(self, master=None):
        super().__init__(master) #super()代表的是父类的定义,而不是父类对象
        self.master = master
        self.pack()
        self.createWidget()

    def createWidget(self):
        """通过place布局管理器实现扑克牌位置控制"""

        # self.photo = PhotoImage(file="picture/puke/puke1.gif")
        # self.puke1 = Label(self.master,image=self.photo)
        # self.puke1.place(x=10,y=50)

        self.photos = [PhotoImage(file="picture/puke/puke"+str(i+1)+".gif") for i in range(5)]
        self.pukes = [Label(self.master, image=self.photos [i]) for i in range(5)]
        for i in range(5):
            self.pukes[i].place(x=10+i*40,y=50)
        #为所有的Label增加事件处理
        self.pukes[0].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()==50:
            event.widget.place(y=30)
        else:
            event.widget.place(y=50)

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

事件处理

​ 一个GUI应用整个生命周期都处在一个消息循环(eventloop)中。它等待事件的发生,并作出相应的处理。
​ Tkinter提供了用以处理相关事件的机制.处理函数可被绑定给各个控件的各种事件。

widget.bind(event,handler)

如果相关事件发生,handler函数会被触发,事件对象event会传递给handler函数.

鼠标和键盘事件

代码 说明
<Button-1> <ButtonPress-1> <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可用ctl和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 鼠标当前位置,相对于整个屏幕
# coding=utf-8
# 测试键盘和鼠标事件
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):
    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)
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

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

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

我们在平时使用时,注意lambda只是一个匿名函数(没有名字的函数),功能不强,不要过度使用;

使用lambda表达式实现传参

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

# coding=utf-8
# 测试command属性绑定事件,测试lambda表达式帮助传参
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("gaoqi","xixi")).pack(side="left")
root.mainloop()

多种事件绑定方式汇总

  • 组件对象的绑定

    1.通过command属性绑定(适合简单不需获取event对象)

​ Button(root,text=”登录”,command=login)

​ 2.通过bind0方法绑定(适合需要获取event对象)

​ c1 = Canvas();c1.bind("",drawLine)

  • 组件类的绑定

​ 调用对象的bind_class函数,将该组件类所有的组件绑定事件:

​ w.bind_class("Widget","event",eventhanler)

​ 比如:btn01.bind_class("Button",””,func)

# 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={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("gaoqi","xixi"))
b2.pack(side="left")

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

OptionMenu选择项

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

image-20230210001354974

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

"""optionmenu的使用测试"""

from tkinter import *

root = Tk(); root.geometry("200x100")

v = StringVar(root)
v.set("百战程序员")
om = OptionMenu(root,v,"尚学堂","百战程序员","卓越班[保底18万]")

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

颜色选择框

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

image-20230210004211081

【示例】颜色选择框基本用法

from tkinter.colorchooser import *
from tkinter import *
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(root, text="选择背景色", command=test1).pack()
root.mainloop()

文件对话框

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

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

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

参数名 说明
defaultextension 默认后缀:.xxx 用户没有输入则自动添加
filetypes=[(label1,pattern1),(label2,pattern2)] 文件显示过滤器
initaldir 初始目录
initalfile 初始文件
parent 父窗口,默认根窗口
title 窗口标题
"""文件对活框获取文件"""
from tkinter import *
from tkinter.filedialog import *


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

def testl():
    f=askopenfilename(title="上传文件",initialdir="f:",filetypes=[("视频文件",".mp4"),("文件",".txt")])
    #print (f)
    show["text"]=f


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

root.mainloop()
# -*- coding: UTF-8 -*-
from tkinter import *
from tkinter.filedialog import  *


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


def test1():
    # with open(askopenfile(title="上传文件", initialdir="d:", filetypes=[("文本文件",".txt")]), encoding='utf-8') as f:
    with askopenfile(title="上传文件", initialdir="d:", filetypes=[("文本文件",".txt")]) as f:
        show["text"]=f.read()

Button(root, text="选择读取的文本文件",command=test1).pack()
show = Label(root,width=40,height=3,bg="green")
show.pack()
root.mainloop()

简单输入对话框

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

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

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

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

"""简单对活框"""""
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,**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) 警告消息框

【示例】

from tkinter import *
from tkinter.messagebox import *
root = Tk();root.geometry("400x100")
a1=showinfo(title="尚学堂", message="Python400集从零开始,深入底层,\
深入算法,打好基础。还手写神经网络")
print (a1)
root.mainloop()

tk子模块控件

​ 我们再前面学的组件是tkinter模块下的组件,整体风格较老较丑。为了弥补这点不足,推出了ttk组件。ttk组件更加美观、功能更加强大。新增了LabeledScale(带标签的Scale)、Notebook(多文档窗口)、Progressbar(进度条)、Treeview(数)等组件。

​ 使用ttk组件与使用普通的Tkinter组件并没有多大的区别,只要导入ttk模块即可。tk子模块的官方文档:

https://docs.python.org/3.7/library/tkinter.ttk.html

【注】此处我们不展开细讲ttk。如果你的项目确实需要用到复杂的界面,推荐大家使用wxpython或者pyQt.

菜单和记事本小项目

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

1.主菜单

GU1程序通常都有菜单,方便用户的交互。我们一般将菜单
分为两种:
1.主菜单
主菜单通常位于GU1程序上方。例如:

上下文菜单

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

1.创建菜单

​ menubar = tk.Menu(root)
​ menubar.add_command(label="字体")

2.绑定鼠标右键单击事件
def test(event):
menubar..post(event.x_root,event.y_root)#在鼠标右键单击坐标处显示菜单
root.bind("",test)
【示例】为记事本程序增加上下文菜单

#coding=utf-8
"""开发记事本软件的菜单"""

from tkinter.filedialog import *
from tkinter.colorchooser import *
from tkinter import *


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

    def createWidget(self):
        # 创建主菜单栏
        menubar = Menu(root)

        # 创建子菜单:虚线的意思是菜单是否可以独立出来为一个窗口显示   tearoff
        # menuFile = Menu(menubar, tearoff=1)
        menuFile = Menu(menubar, tearoff=0)
        menuEdit = Menu(menubar,tearoff=1)
        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=62, height=25)
        self.textpad.pack()

        # 创建上下菜单
        self.contextMenu = Menu(root, tearoff=0)
        self.contextMenu.add_command(label="背景颜色", command=self.openAskColor)

        # 为右键绑定事件
        root.bind("<Button-3>",self.createContextMenu)


    def newfile(self):
        self.textpad.delete("1.0","end")
        self.filename = asksaveasfilename(title="另存为", initialfile="未命名.txt", filetypes=[("文本文档", "*.txt")], defaultextension=".txt")
        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


    def savefile(self):
        try:
            with open(self.filename, "w") as f:
                context = self.textpad.get(1.0, END)
                f.write(context)
        except TypeError:
            self.filename = asksaveasfilename(title="另存为", initialfile="未命名.txt",
                                              filetypes=[("文本文档", "*.txt")], defaultextension=".txt")
            with open(self.filename, "w") as f:
                context = self.textpad.get(1.0, END)
                f.write(context)

    def exit(self):
        root.quit()


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

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

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

将 python 程序打包成exe文件

我们可以使用pyinstaller模块实现将python项目打包成exe文件。操作步聚如下:
1.安装pyinstaller模块
在pycharm中操作:file->setting-->Project:xxx->Project interpretor,再点击+即可,

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

posted @ 2023-02-05 23:49  房东家的猫  阅读(252)  评论(0编辑  收藏  举报