前言
我们每次创建Tkinter中的控件对象时,总需要为其指定一个master对象,它也称容器对象,表示控件需要放在哪个容器对象的内部,此举也防止了创建的控件对象被GC回收!
如果你仔细观察,会发现控件对象的第一个默认值参数master也可以什么都不传,此时的控件同样会默认放置在表示当然窗口的Tk对象中,这是为什么呢?让我们从代码中一探究竟!
创建Button对象时,没有指定master
import tkinter
root_window = tkinter.Tk()
first_btn = tkinter.Button(text="第一个按钮")
first_btn.pack()
second_btn = tkinter.Button(text="第二个按钮")
second_btn.pack()
root_window.mainloop()
输出结果:
两个Button可以在根窗口中正常显示
创建Button对象,指定放置在根窗口
…………省略…………
first_btn = tkinter.Button(root_window, text="第一个按钮")
…………省略…………
second_btn = tkinter.Button(root_window, text="第二个按钮")
…………省略…………
为每个Button对象传入表示窗口的Tk对象,这才是正常创建Button的代码,但是为什么没有传递Tk对象的控件,也会默认放在根窗口上呢?我们继续试验……
先创建Button对象,后创建根窗口Tk对象会怎么样?
import tkinter
first_btn = tkinter.Button(text="第一个按钮")
first_btn.pack()
root_window = tkinter.Tk()
root_window.mainloop()
这次我先创建的Button对象,然后才创建的Tk对象(表示 Root Window),那么tkinter会不会崩溃呢?
输出结果:
哇塞,竟然创建了2个窗口?这是为什么?我们必须深入到源码中,一探究竟……
Button对象的创建
def __init__(self, master=None, cnf={}, **kw):
Widget.__init__(self, master, 'button', cnf, kw)
首先是Button对象的__init__()方法被自动调用,在__init__()方法的内部它又调用了父类Widget的__init__()方法,我们马上看下Widget的__init__()方法做了什么?
def __init__(self, master, widgetName, cnf={}, kw={}, extra=()):
…………省略代码…………
BaseWidget._setup(self, master, cnf)
…………省略代码…………
省略了一部分无关代码,我们发现又调用了Widget的父类BaseWidget的_setup()方法,我们再进去看看这个_setup()方法做了什么?
def _setup(self, master, cnf):
if _support_default_root:
global _default_root
if not master:
if not _default_root:
_default_root = Tk()
master = _default_root
self.master = master
…………省略很多代码…………
我把关键的代码贴出来了
1、首先检查是否支持默认root_window
全局变量_support_default_root的默认值是1,说明支持默认的root_window
2、检查是否指定了容器对象
如果没有指定master,会执行这段代码
if not _default_root:
_default_root = Tk()
master = _default_root
注意,此时它首先判断全局变量_default_root是否已经创建Tk对象,如果没有创建Tk对象的话,它会执行Tk()创建一个新的根窗口Tk对象,这个全局变量_default_root在哪创建的?我们去Tk的代码中一探究竟
3、将_defulat_root赋值给局部变量master
4、局部变量master再将值赋值给实例变量master(这个实例变量,就是平时我们创建Button时指定的容器对象)
创建Tk对象(根窗口)
def __init__(self, screenName=None, baseName=None, className='Tk',
useTk=1, sync=0, use=None):
…………省略很多代码…………
if useTk:
self._loadtk()
…………省略很多代码…………
useTk这个默认值参数的默认值为1,所以接下来会调用一个当前实例方法_loadtk(),我们进去看看这个方法的实现
def _loadtk(self):
…………省略很多源码…………
if _support_default_root and not _default_root:
_default_root = self
self.protocol("WM_DELETE_WINDOW", self.destroy)
全局变量_support_default_root值为1,且_default_root还是指向一个None,注意接下来执行的一段代码
_default_root = self
果然我们的创建的Tk对象,会赋值给全局变量_default_root!原来_default_root指向的值是在我们创建Tk对象的时赋值的……
总结
1、如果我们先创建Tk()对象,此时没有指定容器对象的控件,比如Button(),Button会自动放置在全局变量_default_root指向的Tk对象里面,这个Tk对象是我们创建的。
结论:适用于所有的控件,创建控件对象时没有指定master,控件会默认放置在根窗口里面
2、如果我们先创建Button()对象,后创建一个Tk()对象,此时由于_default_root指向的是None,在Button的代码中会默认创建1个新的Tk()对象作为根窗口,此时我们会看到两个窗口同时显示,1个是Button中新创建的Tk对象,另一个是我们自己的创建的TK()对象(该条适用于所有的控件)