20--Python 图形界面与打包

@Author : Roger TX (425144880@qq.com)
@Link : https://github.com/paotong999

一、Python 图形界面简介

1.1 什么是 GUI

Python 图形界面开发也叫 Python GUI 开发,通俗地讲,就是开发出带界面的客户端程序。

GUI 是 Graphics User Interface(图形用户界面)的缩写。在 GUI 中,并不只是输入文本和返回文本,用户可以看到窗口、按钮、文本框等组件,还可以通过鼠标和键盘操作应用。

GUI 是程序交互的一种不同的方式,使用 GUI 开发的程序,和命令行程序一样,都具有输入数据、处理数据和输出数据这 3 个基本要素,只不过,使用 GUI 开发的程序,它们的输入和输出方式更丰富,更有趣。

Python GUI 常用库

  • Tkinter:Python官方内置的 GUI 库。
  • wxPython:跨平台的 GUI 工具集,可以让 GUI 程序在不同的平台上显示平台对应的风格。
  • PyQt:Python 编程语言和 Qt 库的成功融合,也能跨平台使用。
  • PyGTK:基于老版本的 GTK+2 的库提供绑定,借助于底层 GTK+2 所提供的各种可视化元素和组件,主要适用于 Linux/UNIX 系统。
  • Pywin32:可以像 VC 一样的形式使用 Python 开发 win32 应用。
  • Kivy:开源库,它能够让使用相同源代码创建的程序实现跨平台运行。主要关注创新型用户界面开发,例如多点触摸应用程序。
  • Flexx:一个纯 Python 工具包,可以用来创建图形化界面程序,还支持使用 Web 技术进行界面的渲染。

1.2 Python Tkinter GUI

Tkinter 有各种不同 GUI 组件如下图所示:


tkinter_gui.png

Tkinter 的 GUI 组件有两个根父类,它们都直接继承了 object 类:

  • Misc:所有组件的根父类。
  • Wm:主要提供了一些与窗口管理器通信的功能函数。
  • Tk:代表应用程序的主窗口,通常都需要直接或间接使用该窗口类。
  • BaseWidget:所有组件的基类,它还派生了一个子类 Widget。
  • Widget:代表一个通用的 GUI 组件,Tkinter 所有的 GUI 组件都是 Widget 的子类。
  • Pack、Place 和 Grid:布局管理器,它们负责管理所包含的组件的大小和位置。

Widget 的子类,各 GUI 组件的功能:

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 多行文本框 显示多行文本

1.3 第一个GUI程序

上面介绍了 GUI 组件,先不用太熟悉它们。下面来看一个简单的例子:

from tkinter import *

# 创建Tk对象,Tk代表窗口
root = Tk()
# 设置窗口标题
root.title('窗口标题')
# 创建Label对象,第一个参数指定该Label放入root
w = Label(root, text="Hello Tkinter!")
# 调用pack进行布局
w.pack()
# 启动主窗口的消息循环
root.mainloop()

这里创建了两个对象 Tk 和 Label。Tk 代表顶级窗口,Label 代表一个简单的文本标签,因此需要指定将该 Label 放在哪个容器内。程序在创建 Label 时第一个参数指定了 root,表明该 Label 要放入 root 窗口内。


二、Tkinter GUI编程

2.1 使用 Frame 创建 Tk 容器对象

Python 的 os 模块提供了一个 fork() 方法,该方法可以 fork 出来一个子进程。

from tkinter import *

# 定义继承Frame的Application类
class Application(Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.pack()
        # 调用initWidgets()方法初始化界面
        self.initWidgets()

    def initWidgets(self):
        # 创建Label对象,第一个参数指定该Label放入root
        w = Label(self)
        # 创建一个位图
        bm = PhotoImage(file = 'serial.png')
        # 必须用一个不会被释放的变量引用该图片,否则该图片会被回收
        w.x = bm
        # 设置显示的图片是bm
        w['image'] = bm
        w.pack()
        # 创建Button对象,第一个参数指定该Button放入root
        okButton = Button(self, text="确定")
        okButton['background'] = 'yellow'
        okButton.configure(background='yellow')
        okButton.pack()

# 创建Application对象
app = Application()
# Frame有个默认的master属性,该属性值是Tk对象(窗口)
print(type(app.master))
# 通过master属性来设置窗口标题
app.master.title('窗口标题')
# 启动主窗口的消息循环
app.mainloop()

程序创建了 Frame 的子类 Application,并在该类的构造方法中调用了 initWidges() 方法(这个方法可以是任意方法名),程序在 initWidgets() 方法中创建了两个组件,即 Label 和 Button。

w.x = bm

这行代码负责为 w 的 x 属性赋值。这行代码有什么作用呢?因为程序在 initWidgets() 方法中创建了 PhotoImage 对象,这是一个图片对象。当该方法结束时,如果该对象没有被其他变量引用,这个图片可能会被系统回收,此处由于 w(Label 对象)需要使用该图片,因此程序就让 w 的 x 属性引用该 PhotoImage 对象,阻止系统回收 PhotoImage 的图片。

2.2 GUI 组件的常见选项以及各自的含义

Tkinter GUI 组件除了 backgroundtext 这些属性外,还有一些常用属性如下:

选项名(别名) 含义 单位 典型值
activebackground 指定组件处于激活状态时的背景色 color 'gray25'或'#ff4400'
activeforeground 指定组件处于激活状态时的前景色 color 'gray25'或'#ff4400'
anchor 指定组件内的信息(比如文本或图片)在组件中如何显示。必须为下面的值之一:N、NE、E、SE、S、SW、W、NW或CENTER。比如NW(NorthWest)指定将信息显示在组件的左上角 CENTER
background(bg) 指定组件正常显示时的背景色 color 'gray25'或'#ff4400'
bitmap 指定在组件上显示该选项指定的位图,该选项值可以是Tk_GetBitmap接收的任何形式的位图。位图的显示方式受anchor、justify选项的影响。如果同时指定了bitmap和text,那么bitmap 覆盖文本;如果同时指定了bitmap 和image,那么image 覆盖bitmap
borderwidth 指定组件正常显示时的3D边框的宽度,该值可以是Tk_GetPixels接收的任何格式 pixel 2
cursor 指定光标在组件上的样式。该值可以是Tk_GetCursors 接受的任何格式 cursor gumby
disabledforeground 指定组件处于禁用状态时的前景色 color 'gray25'或'#ff4400'
font 指定组件上显示的文本字体 font 'Helvetica'或('Verdana', 8)
foreground(fg) 指定组件正常显示时的前景色 color 'gray'或'#ff4400'
highlightbackground 指定组件在高亮状态下的背景色 color 'gray'或'#ff4400'
highlightcolor 指定组件在高亮状态下的前景色 color 'gray'或'#ff4400'
highlightthickness 指定组件在高亮状态下的周围方形区域的宽度,该值可以是Tk_GetPixels接收的任何格式 pixel 2
height 指定组件的高度,以font选项指定的字体的字符高度为单位,至少为1 integer 14
image 指定组件中显示的图像,如果设置了image 选项,它将会覆盖text、bitmap选项 image
justify 指定组件内部内容的对齐方式,该选项支持LEFT(左对齐)、CENTER(居中对齐)或RIGHT(右对齐)这三个值 constant RIGHT
padx 指定组件内部在水平方向上两边的空白,该值可以是Tk_GctPixels接收的任何格式 pixel 12
pady 指定组件内部在垂直方向上两地的空白,该值可以是Tk_GctPixels接收的任何格式 pixel 12
relief 指定组件的3D 效果,该选项支持的值包括RAISED、SUNKEN、FLAT、RIDGE、SOLID、GROOVE。该值指出组件内部相对于外部的外观样式,比如RAISED表示组件内部相对于外部凸起 constant GROOVE / RAISED
selectbackground 指定组件在选中状态下的背景色 color 'gray'或'#ff4400'
selectborderwidth 指定组件在选中状态下的3D边框的宽度,该值可以是Tk_GetPixels接收的任何格式 pixel 2
selectforeground 指定组在选中状态下的前景色 color 'gray'或'#ff4400'
state 指定组件的当前状态。该选项支持NORMAL(正常)、DISABLE(禁用)这两个值 constant NORMAL
takefocus 指定组件在键盘遍历(Tab 或 Shift+Tab)时是否接收焦点,将该选项设为 1 表示接收焦点;设为 0 表示不接收焦点 boolean 1或YES
text 指定组件上显示的文本,文本显示格式由组件本身、anchor 及 justify 选项决定 str '确定'
textvariable 指定一个变量名,GUI 组件负责显示该变量值转换得到的字符串,文本显示格式由组件本身、anchor 及 justify 选项决定 variable bnText
underline 指定为组件文本的第几个字符添加下画线,该选项就相当于为组件绑定了快捷键 integer 2
width 指定组件的宽度,以font 选项指定的字体的字符高度为单位,至少为 1 integer 14
wraplength 对于能支持字符换行的组件,该选项指定每行显示的最大字符数,超过该数量的字符将会转到下行显示 integer 20
xscrollcommand 通常用于将组件的水平滚动改变(包括内容滚动或宽度发生改变)与水平滚动条的set方法关联,从而让组件的水平滚动改变传递到水平滚动条 function scroll.set
yscrollcommand 通常用于将组件的垂直滚动改变(包括内容滚动或高度发生改变)与垂直滚动条的set 方法关联,从而让组件的垂直滚动改变传递到垂直滚动条 function scroll.set

三、Python 打包

3.1 常用的 Python 打包工具

本章将介绍两个常用的 Python 打包工具,分别是「zipapp」和「PyInstaller」。

  • zipapp 模块主要用于将 Python 应用打包成一个 .pyz 文件。同时,无论开发 Python 应用时有多少源文件和依赖包,使用 zipapp 都可以将它们打包成一个 .pyz 文件,不足之处是该文件依然需要 Python 环境来执行。
  • PyInstaller 工具则更强大,它可以直接将 Python 程序打包成可执行程序,前该工具跨平台,使用非常方便。使用 PyInstaller 打包出来的程序,完全可以被分发到对应平台的的目标机器上直接运行,无须在目标机器上安装 Python 解释器环境。

3.2 Python zipapp直接打包

zipapp 是一个可以直接运行的模块,该模块用于将单个 Python 文件或整个目录下的所有文件打包成可执行的档案包。

zipapp 模块的命令行语法如下:

python -m zipapp source [options]

该命令的 options 支持如下选项:

  • -o <output>--output=<output>:应选项指定输出档案包的文件名。如果不指定该选项,所生成的档案包的文件名默认为 source 参数值,并加上 .pyz 后缀。
  • -p <interpreter>--python=<interpreter>:改选项用于指定 Python 解释器。
  • -m <mainfn>--main=<mainfn>:该选项用于指定 Python 程序的入口函数。该选项应该为 pkg.mod:fn 形式,其中 pkg.mod 是一个档案包中的包或模块,fn 是指定模块中的函数。如果不指定该选项,则默认从模块中的 __main__.py 文件开始执行。
  • -c,--compress:从 Python 3.7 开始支持该选项。该选项用于指定是否对档案包进行压缩来减小文件的大小,默认是不压缩。
  • --info:该选项用于在诊断时显示档案包中的解释器。
  • -h--help:该选项用于显示 zipapp 模块的帮助信息。

举例说明:

python -m zipapp app -o first.pyz -m "app:main"

上面命令指定将当前目录下的 app 子目录下的所有 Python 源文件打包成一个档案包,并通过 -o 选项指定所生成的档案包的文件名为 first.pyz;-m 选项指定使用 app.py 模块中的 main 函数作为程序入口。

3.3 Python zipapp打包独立应用(包含第三方包)

在 app 所在目录下创建一个 dbapp 子目录,该子目录将会作为本应用的目录。接下来在 dbapp 目录下新建一个 __main__.py 文件作为程序入口,这样程序在打包档案包时就不需要指定程序入口了。

下面是 __main__.py 文件的代码:

from exec_select import *
# 执行query_db()函数
query_db()

exec_select.py 包含代码如下:

# 导入访问MySQL的模块
import mysql.connector
def query_db():
    ......

按照如下步骤将 dbapp 子目录下的应用打包成独立应用:

  1. 通过命令行工具在dbapp当前所在目录执行如下命令:

    python -m pip install -r requirements.txt --target dbapp

    上面命令实际上就是使用 pip 模块来安装模块:

    • python -m pip install 表示要安装模块。
    • --target 选项指定将模块安装到指定目录下,此处指定将依赖模块安装到 dbapp 子目录下。
    • -r 选项指定要安装哪些模块,此处使用 requirements.txt 文件列出要安装的模块和包。-r 选项支持两个值:直接指定要安装的模块或包;使用清单文件指定要安装的模块和包。
  2. 如果 pip 在 dbapp 子目录下生成了 .dist-info 目录,则建议删除该目录。

  3. 使用 zipapp 模块执行打包操作。由于本例的 dbapp 子目录下包含了 __main__.py 文件,该文件将会作为程序入口,因此打包时不需要指定 -m 选项。使用如下命令来打包:

    python -m zipapp dbapp

3.4 Python PyInstaller安装和使用

Python 默认并不包含 PyInstaller 模块,因此需要自行安装 PyInstaller 模块。

pip install pyinstaller

不管这个 Python 应用是单文件的应用,还是多文件的应用,只要在使用 pyinstaller 命令时编译作为程序入口的 Python 程序即可。

PyInstaller 工具生成可执行程序的语法如下:

pyinstaller 选项 Python 源文件

选项 说明
-F,-onefile 产生单个的可执行文件
-D,--onedir 产生一个目录(包含多个文件)作为可执行程序
-h,--help 查看该模块的帮助信息
-a,--ascii 不包含 Unicode 字符集支持
-d,--debug 产生 debug 版本的可执行文件
-w,--windowed,--noconsolc 指定程序运行时不显示命令行窗口(仅对 Windows 有效)
-c,--nowindowed,--console 指定使用命令行窗口运行程序(仅对 Windows 有效)
-o DIR,--out=DIR 指定 spec 文件的生成目录。如果没有指定,则默认使用当前目录来生成 spec 文件
-p DIR,--path=DIR 设置 Python 导入模块的路径(和设置 PYTHONPATH 环境变量的作用相似)。也可使用路径分隔符(Windows 使用分号,Linux 使用冒号)来分隔多个路径
-n NAME,--name=NAME 指定项目(产生的 spec)名字。如果省略该选项,那么第一个脚本的主文件名将作为 spec 的名字

下面看示例

exec_select.py 文件包含的代码如下:

# 导入访问MySQL的模块
import mysql.connector

def query_db():
    # ①、连接数据库
    conn = conn = mysql.connector.connect(user='root', password='123456',
        host='localhost', port='3306',
        database='python', use_unicode=True)
    # ②、获取游标
    c = conn.cursor()
    # ③、调用执行select语句查询数据
    c.execute('select * from user_tb where user_id > %s', (2,))
    # 通过游标的description属性获取列信息
    description = c.description
    # 使用fetchall获取游标中的所有结果集
    rows = c.fetchall()
    # ④、关闭游标
    c.close()
    # ⑤、关闭连接
    conn.close()
    return description, rows

mian.py 文件包含的代码如下:

from exec_select import *
from tkinter import *

def main():
    description, rows = query_db()
    # 创建窗口
    win = Tk()
    win.title('数据库查询')
    # 通过description获取列信息
    for i, col in enumerate(description):
        lb = Button(win, text=col[0], padx=50, pady=6)
        lb.grid(row=0, column=i)
    # 直接使用for循环查询得到的结果集
    for i, row in enumerate(rows):
        for j in range(len(row)):
            en = Label(win, text=row[j])
            en.grid(row=i+1, column=j)
    win.mainloop()
if __name__ == '__main__':
    main()

执行打包命令:

Pyinstaller -F -w main.py

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,719评论 0 10
  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    小迈克阅读 2,957评论 1 3
  • 在从图书馆回宿舍的林荫道上,被一阵季风扫过的梧桐叶沙沙作响。迎风前行的我闭上了双眼,聆听这久违的沉吟。 ...
    风吹过的帆阅读 230评论 0 1
  • 班级情况: 校区:科学创想乐高机器人和平校区 时间:周二17:00——19:00 学员:宋美萱 杜昊洋 任教老师:...
    bong撒卡啦卡阅读 206评论 0 0
  • 原文 问上达工夫。 先生曰:“后儒教人,才涉精微,便谓‘上达’未当学,且说‘下学’。是分‘下学’‘上达’为二也。夫...
    无住居士阅读 3,986评论 0 15