0
点赞
收藏
分享

微信扫一扫

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

mm_tang 2022-01-17 阅读 109
python

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

前言

    本想写tkinter的入门教程,后来看到很多优秀的技术爱好者已经对Tkinter有着非常全面的介绍,我临时决定改变策略,决定分析Tkinter框架的源码,创建一个最简单的TkInter应用,只需要下面的3行代码!

import tkinter

root = tkinter.Tk()

root.mainloop()

    Windows系统下的效果,标题栏有个羽毛的图标、还有一个英文标题:tk,这个Window是如何创建出来的?tkinter框架执行了哪些代码? 

tkinter框架目录结构

     tkinter是一个包模块,tkinter包体中的代码定义在__init__.py模块文件中,当执行import tkinter的时候,__init__.py中没有缩进的代码将会立刻执行……分析tkinter包的__init__.py中那些没有缩进的代码前,先介绍一下tkinter图形界面框架中的所有模块

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

1、__main__.py

这个模块用于测试

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的主要代码

15、test包

    接下来分析一下最重要的__init__.py包模块文件,它的顶层代码会在import tkinter时候,即创建tkinter模块对象时,得到执行……

__init__.py源码分析

    __init__.py模块中,大约4000+行,这里我就不罗列了,按照顺序写一下在包体中的顶层代码都执行了哪些……

导入模块(全局变量):

全局变量:

函数:

类:

一、注释内容

"""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框架的底层依赖,也写了一个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 导入枚举模块

    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的设计

    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
DoubleVar
BooleanVar

十四、创建用于将主线程进入循环事件的函数

mainloop

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

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类中

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

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

 

总结

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

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

举报

相关推荐

0 条评论