Python 图形界面框架TkInter(第二篇:Window创建过程分析)

(备注:本文基于Python3.7)

前言

    本想写tkinter的入门教程,但已经有非常多的技术大佬对Tkinter的API有着非常全面的介绍,我临时决定改变策略,决定分析Tkinter框架的源码,让我们从创建一个最简单的TkInter应用开始,只需如下的3行代码!

import tkinter

root = tkinter.Tk()

root.mainloop()

1、导入tkinter模块

2、创建Tk对象

3、调用Tk对象的mainloop()方法

    让我们运行起来……下图为Windows系统下的效果,标题栏有个羽毛的图标、还有一个英文标题:tk

    这个窗口是如何创建出来的呢?Tkinter框架执行了哪些代码呢?让我们从源码中一探究竟! 

tkinter框架目录结构

     先了解一下tkinter框架的所有代码,tkinter本身为一个包模块,tkinter包体中的代码定义在__init__.py模块文件中,当执行import tkinter的时候,__init__.py中没有缩进的代码会立刻执行(python标准)

    分析tkinter包的__init__.py中没有缩进的代码前,再先介绍一下tkinter图形界面框架中的所有模块(如上方截图)

    算上__init__.py文件,一共14个模块文件 + 1个模块包test(这个模块包用于单元测试,值得学习),接下来介绍下14个模块文件以及test模块包

1、__main__.py

用于测试tkinter

2、colorchooser.py

实现了颜色选择对话框

3、commondialog.py

定义所有对话框的基类

4、constants.py

定义了很多有用的常量

5、dialog.py

定义了一个对话框类

6、dnd.py

一个测试模块

7、filedialog.py

定义了与文件系统相关的对话框

8、font.py

定义了关于字体的相关类

9、messagebox.py

定义了很多对话框

10、scrolledtest.py

一个全新的控件ScrolledText,自带滑动条

11、simpledialog.py

定义了一些简单的对话框

12、tix.py

实现的新控件,不过已经在python3.6后废弃,可以不用关心它的代码了

13、ttk.py

定义了一些全新样式的控件,非常好用

14、__init__.py 

tkinter包模块的代码体文件,tkinter框架的主要代码都在里面,各种控件,比如Label、Button都在里面

15、test包

用于执行单元测试的模块包

    接下来分析__init__.py包模块文件,当我们使用import tkinter的时候,即会创建tkinter模块对象,此时__init__.py文件中的顶层代码(没有缩进)全部会执行…………我会按照__init__.py模块中的执行顺序做一个介绍

__init__.py源码分析

    __init__.py模块,大约4000+行,按照书写的从上到下的执行顺序,描述这些顶层代码都是干什么的,首先是总览

导入模块:enum、sys、_tkinter、tkinter.constants、re

全局变量:14个

函数:多少个来着,再多了 

类:控件类都在里面、还有其他类

一、注释内容

"""Wrapper functions for Tcl/Tk.

Tkinter provides classes which allow the display, positioning and
control of widgets. Toplevel widgets are Tk and Toplevel. Other
widgets are Frame, Label, Entry, Text, Canvas, Button, Radiobutton,
Checkbutton, Scale, Listbox, Scrollbar, OptionMenu, Spinbox
LabelFrame and PanedWindow.

Properties of the widgets are specified with keyword arguments.
Keyword arguments have the same name as the corresponding resource
under Tk.

Widgets are positioned with one of the geometry managers Place, Pack
or Grid. These managers can be called with methods place, pack, grid
available in every Widget.

Actions are bound to events by resources (e.g. keyword argument
command) or with the method bind.

Example (Hello, World):
import tkinter
from tkinter.constants import *
tk = tkinter.Tk()
frame = tkinter.Frame(tk, relief=RIDGE, borderwidth=2)
frame.pack(fill=BOTH,expand=1)
label = tkinter.Label(frame, text="Hello, World")
label.pack(fill=X, expand=1)
button = tkinter.Button(frame,text="Exit",command=tk.destroy)
button.pack(side=BOTTOM)
tk.mainloop()
"""

    作者不仅阐述了tkinter框架的底层依赖为Tcl/Tk程序,也写了一个Tkinter的Hello World,所以好好看看源码中作者的注释,受益匪浅!!

二、导入模块

import enum
import sys

import _tkinter # If this fails your Python may not be configured for Tk
TclError = _tkinter.TclError
from tkinter.constants import *
import re

    import enum 导入enum模块  【说明:python规定每个模块也是对象,会隐式的使用同名的全局变量enum指向一个模块对象】

    import sys 导入sys模块,sys模块表示Python虚拟机相关的操作

    import _tkinter 导入_tkinter模块,表示依赖的内部模块

    创建全局变量TclError,它指向的是_tkinter模块对象的一个属性TclError,TclError是一个class,从面向对象的角度看,全局变量TclError指向的是一个class对象(TclError类对象)

    from tkinter.constants imort * 从tkinter包下的constansts模块下导入所有可导出的属性,这个位于tkinter包中的constants模块,定义了好多全局变量,这里主要就是为了导入这些常量,比如我们可以直接使用tkinter.TOP,后续的文章会专门介绍constants的设计(如果模块没有覆盖__all__内置属性,则默认导出所有属性,如何覆盖了__all__内置属性,则*仅会导出__all__序列中的元素)

    import re 导入re模块,表示正则表达式模块

三、创建8个全局变量

wantobjects = 1

TkVersion = float(_tkinter.TK_VERSION)
TclVersion = float(_tkinter.TCL_VERSION)

READABLE = _tkinter.READABLE
WRITABLE = _tkinter.WRITABLE
EXCEPTION = _tkinter.EXCEPTION


_magic_re = re.compile(r'([\\{}])')
_space_re = re.compile(r'([\s])', re.ASCII)

wantobjects 没看出来有啥用

TkVersion 此全局变量保存着Tk的版本号

TclVersion 此全局变量保存着Tcl的版本号

READABLE、WRITABLE、EXCEPTION 这三个全局变量应该是调试用的吧

         

四、连续创建3个函数

_join 返回值为一个字符串,主要是拼接字符串
_stringify 返回值也是一个字符串,里面利用正则,替换字符串了
_flatten 返回值是一个元组,看样子是合并序列用的(以后专门学习一下)

五、执行一行语句

try: _flatten = _tkinter._flatten
except AttributeError: pass

    尝试访问_tkinter模块的一个属性_flatten,然后赋值给新建的全局变量_flatten,不过对于可能出现的AttributeError,直接捕获,然后什么也不做……,只是pass

六、创建一个函数

_cnfmerge 用于合并属性的?

七、再次尝试定义一个全局变量

try: _cnfmerge = _tkinter._cnfmerge
except AttributeError: pass

八、再创建一个函数(_开头的函数表示内部函数)

_splitdict 看名称是分离字典用的

九、创建两个关于事件的类

EventType

class EventType(str, enum.Enum):
     #省略很多代码#
​

    EventType继承了两个类,一个str、一个Enum,Python支持多继承,看来此处的EventType对字符串和枚举,进行了扩展,里面定了很多的类变量、还重写了特殊方法__str__,这个类值得学习


Event

class Event:
 #省略很多代码#

     Event类比较单纯,就重写了一个__repr__方法

十、再次创建两个内部使用的全局变量

_support_default_root = 1
_default_root = None

十一、创建3个函数

NoDefaultRoot
_tkerror
_exit

十二、创建1个内部使用的全局变量

_varnum = 0

十三、创建关于双向绑定的数据类,共计5个

Variable
class Variable:
    #省略很多代码#
    Variable是所有双向绑定数据类的父类,它写了很多通用的方法

StringVar
class StringVar(Variable):
    #省略很多代码#
    StringVar是用于持有一个字符串对象的双向绑定类

IntVar

        IntVar用于持有一个整型

DoubleVar

        DoubleVar用于持有一个double

BooleanVar

        BooleanVar用于持有一个boolean值

十四、创建用于将主线程进入循环事件的函数,这个函数会一直循环下去

mainloop

十五、再次创建两个全局变量,指向两个类,一个指向int,一个指向float

getint = int

getdouble = float

十六、转化true和false的函数

getboolean

十七、创建控件基类

Misc 控件的基类之一,它的代码量非常大

十八、创建用于可调用对象的包装类

CallWrapper

十九、创建用于控件所在Window的类2个

XView
YView

二十、创建关于Window的类

Wm 窗口的基类

二十一、创建我们经常使用的,当作窗口的类

Tk
class Tk(Misc, Wm):
    #省略很多代码#

    Tk类继承了Misc和Wm

二十二、创建一个TCL函数

Tcl

二十三、创建布局管理器类3个

Pack:表示线性布局
Place:表示位置布局
Grid:表示网格(表格)布局

二十四、创建控件相关类

BaseWidget
Widget
Toplevel
Button
Canvas
Checkbutton
Entry
Frame
Label
Listbox
Menu
Menubutton
Message
Radiobutton
Scale
Scrollbar
Text
_setit
OptionMenu

二十五、创建关于图片的类

Image
PhotoImage
BitmapImage

二十六、又是控件类

Spinbox
LabelFrame
PanedWindow

二十七、创建用于测试当前模块的函数

_test

二十八、执行语句,__init__.py作为脚本执行时,会执行测试

if __name__ == '__main__':
    _test()

    以上是整个tkinter包中__init__.py代码执行过程,我们常见的一些功能全部在这里看到了,接下来继续我们的主流程,3行代码创建了一个窗口,剩下的两行做了什么?

import tkinter

root = tkinter.Tk()

root.mainloop()

Tk类继承结构

class Tk(Misc, Wm):
      
     #省略很多代码#

Tk类定义在tkinter包中的__init__.py模块中,它继承于两个父类,Misc和Wm,每个Tk对象表示顶级窗口,看你下它的继承树,会很清晰

Misc类继承结构

class Misc:
     #省略很多代码#

Misc类位于tkinter包中的__init__.py模块中,父类则是Object,官方给的注释为:

Methods defined on both toplevel and interior widgets

Wm类继承结构

class Wm:
    #省略很多源码#

Wm类也位于tkinter包中的__init__.py模块中,父类为Object,官方的注释为:

Provides functions for the communication with the window manager.

Tk、Misc、Wm类加载时初始化

当tkinter包导入时,这3个类被创建,同时它们中的类属性将会执行,我罗列一下(注意:不列出函数的定义)

Tk类有个类变量_w

    _w = '.'

Misc类的类变量好多呀

    _last_child_ids = None
    _tclCommands = None
    waitvar = wait_variable # XXX b/w compat
    focus = focus_set # XXX b/w compat?
    lift = tkraise
    _nametowidget = nametowidget
    register = _register
    _subst_format = ('%#', '%b', '%f', '%h', '%k',
             '%s', '%t', '%w', '%x', '%y',
             '%A', '%E', '%K', '%N', '%W', '%T', '%X', '%Y', '%D')
    _subst_format_str = " ".join(_subst_format)
    config = configure
    _noarg_ = ['_noarg_']
    propagate = pack_propagate
    slaves = pack_slaves
    anchor = grid_anchor
    bbox = grid_bbox
    columnconfigure = grid_columnconfigure
    rowconfigure = grid_rowconfigure
    size = grid_size

Wm的类变量也不少,全部指向的是方法对象,相当于方法的别称,怪不得呢

    aspect = wm_aspect
    attributes=wm_attributes
    client = wm_client
    colormapwindows = wm_colormapwindows
    command = wm_command
    deiconify = wm_deiconify
    focusmodel = wm_focusmodel
    forget = wm_forget
    frame = wm_frame
    geometry = wm_geometry
    grid = wm_grid
    group = wm_group
    iconbitmap = wm_iconbitmap
    iconify = wm_iconify
    iconmask = wm_iconmask
    iconname = wm_iconname
    iconphoto = wm_iconphoto
    iconposition = wm_iconposition
    iconwindow = wm_iconwindow
    manage = wm_manage
    maxsize = wm_maxsize
    minsize = wm_minsize
    overrideredirect = wm_overrideredirect
    positionfrom = wm_positionfrom
    protocol = wm_protocol
    resizable = wm_resizable
    sizefrom = wm_sizefrom
    state = wm_state
    title = wm_title
    transient = wm_transient
    withdraw = wm_withdraw

创建Tk对象被回调的__init__()方法分析

    def __init__(self, screenName=None, baseName=None, className='Tk',
                 useTk=1, sync=0, use=None):
        self.master = None
        self.children = {}
        self._tkloaded = 0

        self.tk = None
        if baseName is None:
            import os
            baseName = os.path.basename(sys.argv[0])
            baseName, ext = os.path.splitext(baseName)
            if ext not in ('.py', '.pyc'):
                baseName = baseName + ext
        interactive = 0
        self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
        if useTk:
            self._loadtk()
        if not sys.flags.ignore_environment:
            # Issue #16248: Honor the -E flag to avoid code injection.
            self.readprofile(baseName, className)

 Tk类定义在tkinter包下的__init__.py模块中, 当我们执行tkinter.Tk()创建好Tk对象后,它的特殊方法__init__()会被自动调用,分析一下它的方法体

1、Tk对象持有的实例变量master初始化为None

2、创建一个空的字典对象,由实例变量children持有

3、创建一个_tkloaded实例变量,初始化为0

4、持有的tk,初始化为None

5、检查默认值参数baseName,当baseName为None时,先导入os模块,取出当前模块文件的全路径名称(sys.argv[0]),再使用os.path.basename取出文件名,接着检查将文件名进一步分离为文件名与扩展名,检查扩展名是否是py或者pyc,如果不是,basename直接使用basename与ext拼接起来

6、通过_tkinter的create()函数,创建tk对象,此为c语言实现,具体代码看不到

7、如果使用了tk,则会调用一个_loadtk()方法,局部变量useTk默认值为1

8、一个环境检查,忽略环境则会执行readprofile()方法

Tk的mainloop()方法分析

    如果你仔细找,发现Tk中并没有定义mainloop()方法,Tk类下只有下面3个方法,且只有一个loadtk()允许你调用,那么mainloop()在哪里?根据继承树的查找属性的逻辑,从下到上,从左到右的顺序,找啊找!

     终于在父类Misc类中找到了mainloop()方法

    def mainloop(self, n=0):
        """Call the mainloop of Tk."""
        self.tk.mainloop(n)

方法体分析

1、直接调用了持有的self.tk的mainloop()方法,那么self.tk指向的是哪个对象呢?

2、在Misc类中找了一遍没有找到,原来它是在子类Tk中初始化的,根据从下到上的继承树查找逻辑,我们找到了self.tk的初始化代码(此处为模版方法模式),位于Tk类中

self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)

 而_tkinter而是一个内部模块,具体实现我还没有找到,create()方法也是未知实现,应该快要到tkinter的底层tcl了,c语言实现的部分

def create(*args, **kwargs): # real signature unknown
    """
    wantTk
        if false, then Tk_Init() doesn't get called
      sync
        if true, then pass -sync to wish
      use
        if not None, then pass -use to wish
    """
    pass

而位于_tkinter.py的mainloop()方法,也看不到了

def mainloop(self, *args, **kwargs): # real signature unknown
    pass

总结

1、学习tkinter框架的设计,主要是为了学习Python,学习大佬们怎么更好的写Python代码

2、总之本篇文章虽然没有写的很完整,自己还是收获满满的……

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值