用tkinter制作Python GUI程序

Last Updated: 2023-04-17 10:11:47 Monday

-- TOC --

我用Python标准的tkinter模块写过一个超过5000行的在公司内部使用的生产系统的客户端,这个模块还算好用,做点简单的带界面的小工具还是很NICE的。(Windows安装Python后自带的IDLE工具,也是tkinter的杰作)

tkinter模块的基础是tcl/tk,它有两种编译方式,支持线程或不支持线程。tkinter从8.6版本开始,默认的编译方式就是支持线程,即线程安全。(Windows下的两个dll为:tcl86t.dll和tk86t.dll,t就是thread的意思)

用pyinstaller打包后,不会超过10M(无其它第3方模块的情况下)。

使用tkinter的好处是:简单好用不折腾!

messagebox

不需要root window,这些messagebox也可以显示。

showinfo

>>> from tkinter import messagebox as msgbox
>>> print(msgbox.showinfo('i\'m showinfo','Now you are at www.pynote.net! Welcome...:)'))
ok

第1个参数指定对话框左上角的title,第2个参数指定窗体中的内容,这个规则适合所有的messagebox。messagebox窗口的整体风格,会根据代码运行环境而出现变化。

showinfo.jpg

showinfo只有1个按钮,OK,点击OK,函数的返回也是ok字符串。直接关闭窗口,也是返回ok。

showwarning

>>> print(msgbox.showwarning('i\'m showwarning','This is a warning...'))
ok

showwarning.jpg

与showinfo不一样的地方是,默认的图标发生了变化。

showerror

>>> print(msgbox.showerror('i\'m showerror','Careful! There is an error...'))
ok

showerror.jpg

askquestion

二选一的问题窗口:

>>> print(msgbox.askquestion('i\'m askquestion','what if we go to Mars?'))
yes
>>> print(msgbox.askquestion('i\'m askquestion','what if we go to Mars?'))
no

askquestion.jpg

两个按钮,Yes返回yes,No返回no。直接关闭窗口,返回no。

askokcancel

确定或取消的窗口:

>>> print(msgbox.askokcancel('i\'m askokcancel','what if we go to Moon?'))
True
>>> print(msgbox.askokcancel('i\'m askokcancel','what if we go to Moon?'))
False

askokcancel.jpg

两个按钮,OK返回True,Cancel返回False。直接关闭窗口返回False。

askyesno

是,或否,适用这个窗口:

>>> print(msgbox.askyesno('i\'m askyesno','what if we dinner together?'))
True
>>> print(msgbox.askyesno('i\'m askyesno','what if we dinner together?'))
False

askyesno.jpg

两个按钮,Yes返回True,No返回False。与askquestion函数不一样的仅仅是返回值,这个细节可能会让某些代码的可读性更好。直接关闭窗口,返回False。

askyesnocancel

是,或否,还可以不回答,适合这个窗口:

>>> print(msgbox.askyesnocancel('i\'m askyesnocancel','would you marry me?'))
True
>>> print(msgbox.askyesnocancel('i\'m askyesnocancel','would you marry me?'))
False
>>> print(msgbox.askyesnocancel('i\'m askyesnocancel','would you marry me?'))
None

askyesnocancel.jpg

三个按钮,Yes返回True,No返回False,Cancel返回None。直接关闭窗口,返回None。

askretrycancel

询问要不要重来,适合这个消息窗口:

>>> print(msgbox.askretrycancel('i\'m askretrycancel','Again?'))
True
>>> print(msgbox.askretrycancel('i\'m askretrycancel','Again?'))
False

askretrycancel.jpg

两个按钮,Retry返回True,Cancel返回False。直接关闭窗口,返回False。

askinteger,askfloat,askstring

Python的tkinter模块中,有一个子模块simpledialog.py,这个子模块里有这样三个函数:askinteger,askfloat,askstring。他们通过GUI窗口的方式,让用户输入一个整数,浮点数,或者字符串,并且自带输入合法性检测!使用非常方便。

您不需要root window,也可以使用这几个输入窗口。

askinteger

通过对话框,让用户输入一个整数:

>>> from tkinter.simpledialog import askinteger,askfloat,askstring
>>> print(askinteger('askinteger','please give me an integer:'))
12345
>>> print(askinteger('askinteger','please give me an integer:'))
None

第1个参数是对话框的Title,第2个参数是输入条上面的一行信息。本文所介绍的三个ask函数,都是这样的参数风格。

askinteger.jpg

输入整数,点击OK,返回整数,点击Cancel,返回None,直接关闭此对话框,返回None。如果输入非法数据,会弹出一个illegal value的窗口,即这个输入窗口有合法性检查。askinteger函数还支持设置初始值,设置可以接收的最大值和最小值:

askinteger('askinteger','please give me an integer:', 
                initialvalue=12345, minvalue=100, maxvalue=20000)

askfloat

>>> print(askfloat('askfloat','please give me a float:'))
1.2345
>>> print(askfloat('askfloat','please give me a float:'))
None

跟askinteger一样,askfloat也支持设置初始值,最大值和最小值。

askstring

>>> print(askstring('askstring','please give me a string:'))
123
>>> print(askstring('askstring','please give me a string:'))
1.234
>>> print(askstring('askstring','please give me a string:'))
asdfasd
>>> askstring('askstring','please give me a string:')
'  6  '  # no trim
>>> askstring('askstring','please give me a string:')
None

askstring也支持设置初始值,最大值和最小值,不过要注意,askstring接收的是字符串,内部比较大小,也是字符串之间比较大小。

选择文件或文件夹

GUI程序与用户交互,一个重要的方面就是让用户选择文件或文件夹,比如选择要执行某个动作的文件或文件夹,或者要选择一个文件来保存某些内容的时候。Python标准的tkinter.filedialog模块,提供了这类对话框实现的简单接口。不需要root window,也可以打开选择窗口。

>>> from tkinter.filedialog import (askopenfilename, 
                                    askopenfilenames, 
                                    askdirectory, 
                                    asksaveasfilename)
>>> 
>>> askopenfilename()
'/home/xinlin/repos/sendslip/config.ini'

askopenfilename.jpg

askopenfilename有可能返回一个用户选择的文件路径,空tuple或空字符串(选择了文件,但最后没有点击open)。

这一组打开接口,有一组key/value参数可以使用:

askopenfilename(title='Please choose a file', 
                  initialdir='/', filetypes=[('Python source file','*.py')])

askopenfilenames,这个窗口可以让用户同时选择多个文件。

askdirectory,这个窗口可以让个用户选择一个文件夹。

asksaveasfilename,让用户选择一个文件来保存输出。

askcolor选择颜色

>>> import tkinter as tk
>>> tk.colorchooser.askcolor()
((0.0, 255.99609375, 64.25), '#00ff40')
>>> tk.colorchooser.askcolor()
((78.3046875, 168.65625, 177.69140625), '#4ea8b1')

可以设置系统选择颜色窗口的title和默认颜色:

>>> from tkinter import colorchooser
>>> colorchooser.askcolor(title='Choose Color', color='blue')

ubuntu_askcolor.jpg

Botton

注册带参数的command

需要使用lambda语法:

import tkinter as tk

def bn_cmd(str=None):
    if str is not None:
        lb.config(text=str)

root = tk.Tk()
root.geometry('200x100')
lb = tk.Label(root, text='12345')
lb.grid()
tstr = 'I\'m from Button command'
bn = tk.Button(root, text='clickme', command=lambda:bn_cmd(tstr))
bn.grid()
root.mainloop()

Entry

默认值

下面代码中,两种写法都OK。

import tkinter as tk

root = tk.Tk()
lb = tk.Label(root, text='网址:')
lb.grid(row=0,column=0)
# addr = tk.StringVar()
# addr.set('https://cs.pynote.net')
addr = tk.StringVar(value='https://cs.pynote.net')
en = tk.Entry(root, textvariable=addr)
en.grid(row=0,column=1)
root.mainloop()

set函数设置值,get函数获取值。

还有第3种方法:

import tkinter as tk

root = tk.Tk()
lb = tk.Label(root, text='网址:')
lb.grid(row=0,column=0)
addr = tk.StringVar()
en = tk.Entry(root, textvariable=addr)
en.insert(0, 'www.pynote.net')
en.grid(row=0,column=1)
root.mainloop()

insert函数的第1个参数是0,表示从最开始的位置插入,如果是INSERT,表示从光标所在位置插入,如果是END,表示在末尾插入。

自动检查输入

tk.Entry控件的作用,就是承接用户的输入。用户输入,一定要通过键盘。因此,实现输入自动检查功能,我们可以让tk.Entry控件去响应按键的KeyRelease事件,用户每输入一个字符,都会产生一个KeyRelease事件,这时就可以在响应事件的函数中检查用户的输入,如果有错,进行相应的操作,比如背景颜色的改变作为提示,或者在log窗口提示,或者直接去掉这个错误的输入等等。

下面的python示例代码,实现了一个tk.Entry控件对输入的自动检查,只允许输入10个字符,一旦发现输入超长,自动将超长的部分删除:

import tkinter as tk

def _number_check(event):
    data = num.get().strip()
    if len(data) <= 10: return
    num.set(data[:10])

root = tk.Tk()
num = tk.StringVar()
numEntry = tk.Entry(root, textvariable=num)
numEntry.bind('<KeyRelease>', _number_check)
numEntry.pack()
root.mainloop()

tkinter控件通过bind函数绑定事件,对应的操作函数,比如要有1个输入参数,一般命令为event,这个event对象内含有很多可以利用的信息,不过本文示例没有使用。

tk.Entry通过这种方式实现输入自动检查,有个细节,即如果用户连续按着键盘的某个按键不放,也是有效的,每一个输入都会产生一个KeyRelease事件。

输入密码

输入密码的一个需求,是不可见,输入的字符要用*号来代替。tk.Entry控件可以用来做这个事情。

import tkinter as tk


def mima():
    bt['text'] = en.get().strip()


root = tk.Tk()
lb = tk.Label(root, text='密码:')
lb.grid(row=0,column=0)
addr = tk.StringVar()
en = tk.Entry(root, textvariable=addr, show='*')
en.grid(row=0,column=1)
bt = tk.Button(root, command=mima, text='密码揭晓')
bt.grid(row=1,columnspan=2)
root.mainloop()

情景式(modal)的Toplevel窗口

Python的tkinter模块,提供的Toplevel窗口,默认是非情景式的,即modaless。这种modaless式的窗口存在一个问题,即此窗口打开后,用户还可以对原窗口(父窗口)进行操作。但有时需要modal式的Toplevel窗口。

>>> import tkinter as tk
>>> from tkinter import Toplevel
>>> root = tk.Tk()
>>> tp = Toplevel(root)
>>> tp.grab_set()  # switch to modal window
>>> root.mainloop()

Label

Label上文字对齐方式

默认是center,可修改为leftright

change_label['justify'] = 'left'

点击Label

tkinter库的Label控件没有command参数,不能像Button一样,响应鼠标左键的点击。不过,我们可以通过bind绑定事件的方式,让Label控件也能像Button一样,对鼠标的点击有反应。有的时候我们需要这样做,因为Label和Button的界面显示有差异。

bind方法是tkinter的一个很通用的方法,通过bind函数,将某个事件跟界面控件绑定,当事件发生时,调用绑定的函数。有了bind方法,Python的门面,tkinter库,其实是非常强大的。

让Label控件能够响应鼠标点击,我们的思路就是给Label控件绑定鼠标左键点击事件,测试代码如下:

import tkinter as tk

def changeColor(event):
    global gNum; gNum += 1
    if gNum % 2 == 0: clickLabel.config(bg='red')
    if gNum % 2 != 0: clickLabel.config(bg='blue')

root = tk.Tk()
gNum = 0
clickLabel = tk.Label(text='click me', font=('microsoft yahei',32,'bold'))
clickLabel.bind('<Button-1>', changeColor)
clickLabel.pack()
root.mainloop()

clickLabel绑定了Button-1,这就是鼠标左键点击事件,绑定的函数是changColor,修改自己的背景颜色,每一次点击,在红色和蓝色之间切换。以上代码的运行效果如下:

label_click.gif

cursor鼠标样式

import tkinter as tk

root = tk.Tk()
lb = tk.Label(text='cursor: ', font=('',32,''), cursor='circle')
lb.pack()
root.mainloop()

上面的代码,在一个Label控件上指定鼠标样式为circle,效果就是一个白色的圈。

还有哪些样式可用?

arrow, man, mouse, boat, pencil, pirate, clock, sailboat, coffee_mug, cross, dot, shuttle, spider, star, watch, xterm.....

如何pack

每一个tkinter组件,都有pack方法(也都有gridplack方法)。pack,就是打包的意思,将组件打包进界面。pack方法,就是按照代码的先后顺序,将组件一个个的从上到下(默认)摆放在GUI界面中。

from tkinter import *

root = Tk()
for i in range(5):
    Label(root, text=str(i), relief=GROOVE).pack(fill=BOTH, expand=True)
root.mainloop()

fill=BOTH表示按X和Y两个方向随控件延伸,expand=True表示控件随父组件的尺寸扩展而扩展。

tkinter_pack.jpg

我们可以通过side属性值,来改变默认的从上到下排雷的方法,side的可选值有LEFT,RIGHT,BOTTOM,TOP(默认)。我们试一下side=RIGHT的效果,代码和效果如下:

from tkinter import *

root = Tk()
for i in range(5):
    Label(root, text=str(i), relief=GROOVE).pack(side=RIGHT, fill=BOTH, expand=True)
root.mainloop()

tkinter_pack_side.jpg

在来一个比较复杂的:

from tkinter import *

root = Tk()
for i in range(5):
    Label(root, text=str(i), relief=GROOVE).pack(fill=BOTH, expand=True)
for i in range(5):
    Label(root, text=str(i), relief=GROOVE).pack(side=RIGHT, fill=BOTH, expand=True)
for i in range(5):
    Label(root, text=str(i), relief=GROOVE).pack(side=BOTTOM, fill=BOTH, expand=True)
for i in range(5):
    Label(root, text=str(i), relief=GROOVE).pack(side=LEFT,fill=BOTH, expand=True)
root.mainloop()

tkinter_pack_all.jpg

字体(font)的使用

基本上tkinter中的控件,在创建的时候,都可以使用font字体参数,来控制控件上显示的字体,大小和样式。tkinter是Python的标准GUI库,支持的成熟度是最好的,编写GUI小工具,应该优先选择tkinter。本文介绍tkinter模块中字体使用的相关技巧。

font tuple

>>> root = tk.Tk()
>>> tk.Label(root, text='hello font', font=('microsoft yahei', 16, 'bold')).pack
>
>>> tk.Label(root, text='hello font', font=('microsoft yahei', 16, 'bold')).pack()
>>> tk.Label(root, text='hello font', font=('Times', 20, 'bold italic')).pack()
>>> tk.Label(root, text='hello font', font=('Times', 24, 'bold italic underline')).pack()
>>> tk.Label(root, text='hello font', font=('Times', 24, 'bold italic underline overstrike')).pack()
>>> tk.Label(root, text='default font').pack()

tkinter_font_tuple.jpg

tkinter.font对象

import tkinter as tk
import tkinter.font as tkFont

root = tk.Tk()  # must be here
f1 = tkFont.Font(family='microsoft yahei', size=16, weight='bold')
f2 = tkFont.Font(family='times', size=20, slant='italic')
f3 = tkFont.Font(family='Helvetica', size=24, underline=1, overstrike=1)
tk.Label(root, text='tkFont', font=f1).pack()
tk.Label(root, text='tkFont', font=f2).pack()
tk.Label(root, text='tkFont', font=f3).pack()
tk.Label(root, text='default font').pack()
root.mainloop()

tkFont_class.jpg

tkFont对象有一个方法measure,可以获取字符串参数的像素宽度:

>>> import tkinter as tk
>>> import tkinter.font as tkfont
>>> root = tk.Tk()
>>> f1 = tkfont.Font(family='times', size=24, weight='bold')
>>> f1.measure('a')
16
>>> f1.measure('ab')
34
>>> f1.measure('abc')
48

窗口属性

Title

root.title('I am the title...')

透明度

def _focusIn(self, event):
    self.win.attributes('-alpha', 1.0)

def _focusOut(self, evnet):
    self.win.attributes('-alpha', 0.6)

置顶

def topWin():
    tp = Toplevel(root)
    tp.attributes('-topmost', True)
    # tp.attributes('-topmost', False)

多个置顶窗口之间可以相互遮挡!

全屏

import tkinter as tk
from tkinter import Toplevel

root = tk.Tk()
root.attributes('-fullscreen', True)
root.mainloop()

使用False,就可以取消全屏。

overrideredirect(only windows)

wm_overrideredirect(boolean=None) method of tkinter.Tk instance instruct the window manager to ignore this widget if BOOLEAN is given with 1. Return the current value if None is given.

所谓ignore,就是正常窗口上面有一条控制栏,ignore之后,就没有了。

>>> import tkinter as tk
>>> root = tk.Tk()
>>> tk.Label(root, text='test', font=('',64)).pack()
>>> root.overrideredirect(1)

overrideredirect.jpg

toolwindow(only windows)

>>> import tkinter as tk
>>> root = tk.Tk()
>>> root.attributes('-toolwindow', True)
''
>>> root.attributes('-toolwindow', False)
''

toolwindow.jpg

窗口宽高

>>> root = tk.Tk()
>>> root.winfo_width()
200
>>> root.winfo_height()
200

最大化

Windows

>>> import tkinter as tk
>>> root = tk.Tk()
>>> root.state('zoomed')
>>> root.state('normal')

Linux

>>> import tkinter as tk
>>> root = tk.Tk() 
>>> root.attributes('-zoomed', True)
''
>>> root.attributes('-zoomed', False)
''

最小化

>>> import tkinter as tk
>>> root = tk.Tk()
>>> root.state('icon')
>>> root.iconify()  # alternative choice

窗口state函数可以设置窗口的状态,还可以查询窗口的状态,此时state函数不带任何参数。

显示器分辨率

import tkinter as tk

root = tk.Tk()
print(root.winfo_screenwidth())
print(root.winfo_screenheight())
root.destroy()

窗口消失和恢复

def twd():
    root.withdraw()
    time.sleep(3)
    root.deiconify()

窗口消失不是最小化,消失后在系统中找不到这个窗口。

禁止调整窗口大小

>>> import tkinter as tk
>>> root = tk.Tk()
>>> root.resizable(0,0)

默认窗口大小可以被拉伸。

设置窗口固定尺寸

>>> import tkinter as tk
>>> root = tk.Tk()
>>> root.resizable(0,0)
''
>>> root.geometry('600x600')
''

移动窗口在屏幕上的位置

>>> root.geometry('+0+0')
>>> root.geometry('+300+400')

当geometry函数的参数是上面这种两个加号风格的时候,就是调整窗口在屏幕上的位置,第1个加号是距离屏幕左边的宽,第2个加号是距离屏幕顶部的高。注意加号后面可以跟负数,这是一种隐藏窗口的方式:

>>> root.geometry('+-3000+-4000')

两个加号后面跟非常大的负数,这样的实际效果是,将窗口移动到屏幕外面,彻底看不见了。这时,只有任务栏还有显示此程序。

同时设置宽高和移动位置

>>> root.geometry('300x250+500+240')

geometry(None)

如果geometry函数的参数是None,就是获取此时窗口的宽高以及在屏幕上的位置:

>>> root.geometry(None)
'300x250+336+55'
>>> root.geometry(None)
'300x250+1192+284'

Scrollbar

Scrollbar控件一般都是与Text或Listbox等需要上下滚动显示的控件一并使用,使得这些显示控件能够上下翻滚以便方便的显示和供人查看所有信息。那么,这就需要将Scrollbar与其它widget进行捆绑使用,本文介绍此内容。其实,从Scrollbar需要自己手动编写代码来进行与其它widget捆绑就能看出,tkinter这个python官方提供的GUI库,是比较底层一点的,所以它更加地灵活。

tk.Scrollbar与tk.Listbox捆绑

>>> root = tk.Tk()
>>> listbox = tk.Listbox(root)
>>> listbox.pack(side=tk.LEFT)
>>> scrollbar = tk.Scrollbar(root, command=listbox.yview)
>>> listbox.config(yscrollcommand=scrollbar.set)
>>> scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
>>> for i in range(300):
...     listbox.insert(tk.END, str(i))
...

Listbox一般都是放在左边,Scrollbar放在右边并在Y轴进行填充,Scrollbar的command绑定Listbox的yview方法,Listbox的yscrollcommand绑定Scrollbar的set方法。这样就成功了,导入300行数据,看到的效果如下,默认鼠标上下滚动,以及使用Up和Down按键,还有PageUp和PageDown都有效果。

scrollbar_listbox.jpg

tk.Scrollbar与tk.Text绑定

>>> root = tk.Tk()
>>> text = tk.Text(root)
>>> text.pack(side=tk.LEFT)
>>> scrollbar = tk.Scrollbar(root, command=text.yview)
>>> text.config(yscrollcommand=scrollbar.set)
>>> scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
>>> text.config(width=24)
>>> for i in range(300):
...     text.insert(tk.END, str(i))

换成了Text控件,但在于Scrollbar配合使用的时候,代码风格是完全一样的。这段代码缩小了Text的宽度,默认窗口太大了,不方便截图。后面插入一堆数据,忘记了换行,就这样吧。以上代码运行效果如下:

scrollbar_text.jpg

获取组件属性

>>> import tkinter as tk
>>> root = tk.Tk()
>>> root.cget('bg')
'#d9d9d9'

如果cget遇到tkinter组件不存在的属性,会抛出TclError异常。

不使用cget也可以获取组件的各种属性:(可设置)

>>> bt['font']
'TkDefaultFont'
>>> bt['bg']
'SystemButtonFace'
>>> bt['text']
'cget test'
>>> bt['font'] = ('yahei', 24, 'bold')
>>> bt['font']
'yahei 24 bold'

访问和设置tkinter组件的属性,就像是在操作一个dict对象。

响应窗口关闭事件

>>> import tkinter as tk
>>> root = tk.Tk()
>>> root.protocol('WM_DELETE_WINDOW', root.destroy) # default
''
>>> root.protocol('WM_DELETE_WINDOW', root.iconify) # 最小化窗口
''
>>> root.protocol('WM_DELETE_WINDOW', customized_function)  # 自定义

使用protocol函数,绑定关闭事件到一个指定的函数入口。您也可以自己写一个函数,先执行一段自己的代码,最后调用destroy函数。

after定时器

tkinter窗口,比如root窗口,以及Toplevel窗口,都有一个after方法。此方法执行后,将会在规定的时间间隔之后,执行一个特定的您指定的函数。如果在您指定的这个定时执行的函数中,再次调用after方法,就可以起到一个定时器的效果。其实,python中简单的定时器基本都是这个思路。

下面是一个after方法的测试程序:

import time
import tkinter as tk

def __writeText():
    text.insert(tk.END, str(time.time())+'\n')
    root.after(1000, __writeText)  # again forever

root = tk.Tk()
text = tk.Text(root)
text.pack()
root.after(1000, __writeText)
root.mainloop()

定时销毁主窗口

import tkinter as tk

root = tk.Tk()
root.after(3000, root.destroy) 
root.mainloop() 

避免多个mainloop

【用tkinter在一个程序中创建多个根root窗口不是一个好的设计】Tkinter只是一个嵌入式Tcl解释器的python包装器,可以导入Tk库。创建根窗口时,将创建Tcl解释器的实例。每个Tcl解释器都是一个独立的沙箱。一个沙箱中的对象无法与另一个沙箱中的对象进行交互。最常见的表现是StringVar在一个解释器中创建的,在另一个解释器中不可见。小部件也是如此 - 您无法在一个解释器中创建小部件,该解释器在另一个解释器中具有父小部件。最佳解决方案99.9%的时间是创建一个Tk用于程序生命周期的实例。很简单,这就是tkinter和底层Tcl / Tk解释器的设计使用方式。 从技术角度来看,没有理由不能同时拥有两个实例Tk。反对它的建议是因为很少有实际需要有两个或更多不同的Tcl解释器,并且它会导致初学者难以掌握的问题。

避免死锁

问题出现在点击GUI上的按钮,访问log窗口时,获取不到log窗口的mutex,因为此时这个mutex在线程手里,线程获得mutex后,还没有release,执行序列就被python切换到主GUI上,而主GUI上的执行是要一口气执行完的!点击GUI上的按钮,按钮背后的代码要一口气执行完,否则GUI不会响应其它事件

于是,就是死锁了。

GUI事件代码如果要访问log窗口,用线程,都用线程,就没事,就怕部分是线程,部分不是线程,就会死锁。或者,选择好线程启动的时机来规避可能出现的死锁!

不要阻塞tkinter的mainloop

自定义icon

icon_img = \
b'AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAMMOAADDDg'\
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJRXvUhYJ98ESBfTwEAL2/xEC/P8RA/P/EQTx'\
b'+RQG9OwfDvKiOyrlHgAAAAAAAAAAAAAAAAAAAAAzZswFLRDjnSUI9f8jBfX/JQbw/yoL5d'\
b'krFuWfKhvqiikg7IcmGumZJhHvszEV4b9CIdk9AAAAAAAAAAB/f/8CPBLUsjkL7f81Ctj/'\
b'Ogve/z8Z2r0mGeqtEgj30BAH8/gPBf//CwT29BAH98knFe6cTzDUc39VvwwAAAAATxnGek'\
b'0Q3P9HDsb/TQ/V/1Qgx5dLQuI2IxPuTywY6zQuG+dCJRDofCEH6O4gBP3/HQX4/ysV57Z1'\
b'desNazrEGlkUt+xcEr7/XRLA/18budX/zJkFAAAAAAAAAAAAAAAAAAAAAAAAAABQKNYmNQ'\
b'3c5jIJ4P80CfD/PRrYknQmrZNtFqz/bRaq/3UZtf98L65cAAAAAAAAAAAAAAAAAAAAAAAA'\
b'AAAAAAAAAAAAAE0axl9ID9f/RAzO/0MPyfSIKqL1fxqb/4Eanf+CH531o1utGQAAAAAAAA'\
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAB/P78QVxS571gRv/9WErz/liuR+ZIdjP+VHpD/kiOM'\
b'6MyZzAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGoaq9ZsFbT/aBWt/6czhK'\
b'CmIn//piGA/6YmgfevV54dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKqq/wN7G53f'\
b'fxmk/3sZnv+/SH8ctiVu7b0mc//EKXj/tzt7ZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'\
b'AAAACSMJcvkB+S/I4dkf+OHZD/AAAAAMcxZHffLWn/0Spk/8wwZeS8XoQbAAAAAAAAAAAA'\
b'AAAAAAAAAAAAAAAAAAAAnSeCoqwiiv+gIIH/oCGB+wAAAAD//38C2TVZne4wVv/wMFn/3D'\
b'JX3t09XFvUVW4exmN/EuxCWyrwZWUjsTR8UroneP+zJHL/wyd6/7UveLgAAAAAxlSNCchO'\
b'eVTlPlaC9TVFzvkzRf//Nkn/7jRE/vM4SN/tQFCu00FqjccqaPDKKWb/1Spo/8kuaubIVH'\
b'shAAAAAAAAAADLNmJB1Tdes+g7VpHrQlKC8UJLg/FDToXmP1SF2zdbqtkrV/rkLlj/7jBb'\
b'/90zXNTTUGkpAAAAAAAAAAAAAAAAAAAAANw5VizqNU+67DJL7egxSebpMUjo5zBH/vcxSf'\
b'/wMUb/7jRK6uk8VYzXiXUNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZKTVb5PUTY'\
b'/zQ7//wzOf/5Njz4+kVLqfFoYicAAAAAAAAAAAAAAAAAAAAA8AMAAMABAACAAAAAgAAAAA'\
b'PgAAAH8AAAB/AAAAf4AAAH8AAAB/AAAIPwAACAAAAAgAAAAMABAADgAwAA+A8AAA=='

gRoot = tk.Tk()
gRoot.title('title')
# get top left icon
try:
    _tmpfile = '_icon_tmp.ico'
    with open(_tmpfile,'wb') as f:
        f.write(base64.b64decode(icon_img))
    gRoot.iconbitmap(_tmpfile)
    os.remove(_tmpfile)
except:
    pass

多root窗口

建议每个root窗口都在一个独立的进程内!

import tkinter as tk
import threading


def create(lbStr):
    root = tk.Tk()
    lb = tk.Label(root, text=lbStr)
    lb.pack()
    root.mainloop()


tt1 = threading.Thread(target=create, args=('1111111',),daemon=True)
tt1.start()

tt2 = threading.Thread(target=create, args=('2222222',),daemon=True)
tt2.start()

tt3 = threading.Thread(target=create, args=('3333333',),daemon=True)
tt3.start()

tt1.join()
tt2.join()
tt3.join()

不使用daemon参数,可以这样:

import tkinter as tk
import threading


def create(lbStr):
    root = tk.Tk()
    lb = tk.Label(root, text=lbStr)
    lb.pack()
    root.mainloop()


for i in range(3):
    tt = threading.Thread(target=create, args=(str(i)*5,))
    tt.start()

我不确定这样做是否有意义?

Max Screen FPS

这是一个小程序,用来测试抓屏,运行起来后,基本上可以让屏幕的变化达到物理屏幕的刷新率上限。

#!/usr/bin/env python3
import time
import random
import threading
import multiprocessing as mp
import tkinter as tk


width = 512
height = 64
top = 0
left = 0
interval = 0
num_win = 4


class win():

    def __init__(self, i):
        self.i = i
        self.width = width
        self.height = height
        self.interval = interval
        self.root = tk.Tk()
        self.root.title('[%d] Area Size %d' % (i,width*height))
        self.root.resizable(0,0)
        self.root.geometry(str(width)+'x'+str(height)
                           +'+'+str(left)+'+'+str(top+(height+32)*i))
        self.change_label = tk.Label()
        self.change_label['justify'] = 'left'
        self.change_label.pack(fill=tk.BOTH, expand=True)
        th = threading.Thread(target=self.change_content,
                              args=(), daemon=True)
        th.start()
        self.root.mainloop()

    def change_content(self):
        row = int(self.height/16)
        col = int(self.width/6)
        fps = 0
        tic = time.time()
        while True:
            time.sleep(self.interval)
            t = ''.join([''.join([chr(random.randint(33,126))
                             for i in range(col)])+'\n'
                                 for j in range(row)])
            self.change_label['text'] = t
            c = '#' + hex(random.randint(0,0xFFFFFF))[2:].rjust(6,'0')
            self.change_label['bg'] = c
            fps += 1
            if(time.time() - tic >= 1):
                print('win[%d] soft FPS: %d' % (self.i,fps)) 
                fps = 0
                tic = time.time()


if __name__ == '__main__':
    print('Press Ctrl-C here to stop them all in once...')
    for i in range(num_win):
        ph = mp.Process(target=win, args=(i,))
        ph.start()

本文链接:https://cs.pynote.net/sf/python/202212251/

-- EOF --

-- MORE --