Python GUI Cookbook —— 线程与网络

创建线程、队列和TCP/IP套接字。

当进程被创建时,进程会自动创建一个主线程来运行该应用程序,被称为单线程应用程序。

对于我们的 Python GUI,单线程应用程序将导致 GUI 在调用长运行时间的任务时(例如点击了具有几秒钟睡眠时间的按钮)变冻结。为了保持 GUI 的响应,我们需要使用多线程。 我们也可以通过创建多个 GUI 实例来构成(正如在任务管理器中所见的)多个进程。

在设计上,进程之间彼此隔离、不共享公共数据。为了在独立的两个进程之间进行通信,需要使用先进的进程间通信(Inter Process
Communication,IPC)技术。而另一方面,线程则共享公用数据、代码和文件,这使得同一进程中的线程间通信比使用 IPC 时更容易。

创建多线程

为了保持 GUI 响应,创建多线程时必要的。多线程运行在相同的计算机进程内存空间,不需要写复杂的 IPC 代码。

首先导入 threading 模块

1
2
3
4
5
6
7
8
9
from tkinter import ttk
from tkinter import scrolledtext
from tkinter import messagebox as msg
from tkinter import Spinbox
from tkinter import Menu
from time import sleep

from threading import Thread
[...]

在类中添加一个方法以创建线程

1
2
def method_in_a_thread(self):
print('Hi, How are you?')

调用该方法

1
2
3
4
5
6
app = App()

# Running methods in Threads
run_thread = Thread(target=app.method_in_a_thread)

app.win.mainloop()

现在运行代码 … … 好吧,什么都没发生。

开始一个线程

当我们在不使用线程时,调用一些有睡眠(sleep)的函数或方法的 GUI 会发生什么?

这里我们使用 sleep 来模拟实际应用中等待 web 服务器和数据库响应、大文件传输或复杂的计算等任务。

在按钮的调用方法中添加一个带有睡眠时间的循环,会导致 GUI 无响应。

1
2
3
4
5
6
7
def click_me(self):
self.action.configure(text='Hello '+self.name.get()+' '+
self.number_chosen.get())
# Non-threaded code with sleep freezes the GUI
for idx in range(10):
sleep(5)
self.scrol.insert(tk.INSERT, str(idx)+'\n')

不像常规的函数和方法,这里我们需要 start 一个方法才能开启它自己的线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[...]

def method_in_a_thread(self):
print('Hi, How are you?')

# Running methods in Threads
def create_thread(self):
self.run_thread = Thread(target=self.method_in_a_thread)
print(self.run_thread)
self.run_thread.start()


def click_me(self):
self.action.configure(text='Hello '+self.name.get()+' '+
self.number_chosen.get())
self.create_thread()
# # Non-threaded code with sleep freezes the GUI
# for idx in range(10):
# sleep(5)
# self.scrol.insert(tk.INSERT, str(idx)+'\n')

[...]

method_in_a_thread 中添加一些 sleep,来验证一下是不是真的解决了我们的问题:

1
2
3
4
5
6

def method_in_a_thread(self):
print('Hi, How are you?')
for idx in range(10):
sleep(5)
self.scrol.insert(tk.INSERT, str(idx)+'\n')

可以发现我们的 GUI 又能响应了。

停止线程

直觉上来说,如果有 start() 方法那么就会有相应的 stop() 方法,然而并没有。因此需要将线程作为后台任务运行(守护进程,daemon),当关闭主线程(GUI)时,所有的守护进程也将自动停止。

首先,通过对线程构造函数添加 args 参数,我们可以给线程的方法传入参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def method_in_a_thread(self, num_of_loops=10):
print('Hi, How are you?')
for idx in range(num_of_loops):
sleep(1)
self.scrol.insert(tk.INSERT, str(idx)+'\n')
sleep(1)
print('method_in_a_thread():', self.run_thread.isAlive())

# Running methods in Threads
def create_thread(self):
self.run_thread = Thread(target=self.method_in_a_thread, args=[8])
print(self.run_thread)
self.run_thread.start()
print('createThread():',self.run_thread.isAlive())

这时,如果我们提前结束 GUI,我们就会得到以下错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<Thread(Thread-1, initial)>
Hi, How are you?
createThread(): True
Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Python36\lib\threading.py", line 916, in _bootstrap_inner
self.run()
File "C:\Python36\lib\threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "multiple_threads.py", line 169, in method_in_a_thread
self.scrol.insert(tk.INSERT, str(idx)+'\n')
File "C:\Python36\lib\tkinter\__init__.py", line 3266, in insert
self.tk.call((self._w, 'insert', index, chars) + args)
RuntimeError: main thread is not in main loop

通过将线程转换为守护进程能解决这个问题。

1
2
3
4
5
6
7
# Running methods in Threads
def create_thread(self):
self.run_thread = Thread(target=self.method_in_a_thread, args=[8])
print(self.run_thread)
self.run_thread.setDaemon(True)
self.run_thread.start()
print('createThread():',self.run_thread.isAlive())

使用队列

当接收到要处理和显示的最终的结果时,使用多线程在队列中来完成分配的任务是非常有用的。数据以先入先出(FIFO)的方式工作。

queue 模块导入 Queue

1
2
3
4
5
6
7
8
9
10
11
12
import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
from tkinter import messagebox as msg
from tkinter import Spinbox
from tkinter import Menu
from time import sleep

from threading import Thread
from queue import Queue

[...]

创建队列实例

1
2
3
4
5
6
7
def use_queues(self):
gui_queue = Queue()
print(gui_queue)
for idx in range(10):
gui_queue.put('Message from a queue: '+str(idx))
while True:
print(gui_queue.get())

在按钮点击事件中调用该方法

1
2
3
4
5
def click_me(self):
self.action.configure(text='Hello '+self.name.get()+' '+
self.number_chosen.get())
self.create_thread()
self.use_queues()

运行代码,会导致以下结果:

随着代码的运行,我们的 GUI 被冻结了。为了解决该问题,我们需要在它自己的线程中调用该方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Running methods in Threads
def create_thread(self):
self.run_thread = Thread(target=self.method_in_a_thread, args=[8])
print(self.run_thread)
self.run_thread.setDaemon(True)
self.run_thread.start()
# print('createThread():',self.run_thread.isAlive())

# start queue in its own thread
write_thread = Thread(target=self.use_queues, daemon=True)
write_thread.start()


def use_queues(self):
gui_queue = Queue()
print(gui_queue)
for idx in range(10):
gui_queue.put('Message from a queue: '+str(idx))
while True:
print(gui_queue.get())


def click_me(self):
self.action.configure(text='Hello '+self.name.get()+' '+
self.number_chosen.get())
self.create_thread()

在不同的模块之间传递队列

  • 模块化设计使得代码能够重用且提高了代码的可读性
  • 将 GUI widget 从表达业务逻辑的功能中分离出来

导入另外的模块

1
import Queues as bq

Queues.py

1
2
3
4
5
def write_to_scrol(inst):
print('hi from Queue', inst)
for idx in range(10):
inst.gui_queue.put('Message from a queue: ' + str(idx))
inst.create_thread(6)

修改代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[...]

def __init__(self):
# Create instance
self.gui_queue = Queue()
self.win = tk.Tk()
# Add title
self.win.title("Python GUI")
self.create_widgets()

[...]

# Running methods in Threads
def create_thread(self, num=3):
self.run_thread = Thread(target=self.method_in_a_thread, args=[num])
print(self.run_thread)
self.run_thread.setDaemon(True)
self.run_thread.start()
# print('createThread():',self.run_thread.isAlive())

# start queue in its own thread
write_thread = Thread(target=self.use_queues, daemon=True)
write_thread.start()


def use_queues(self):
# gui_queue = Queue()
# print(gui_queue)
# for idx in range(10):
# gui_queue.put('Message from a queue: '+str(idx))
while True:
print(self.gui_queue.get())


def click_me(self):
print(self)
self.action.configure(text='Hello '+self.name.get()+' '+
self.number_chosen.get())
# self.create_thread()
bq.write_to_scrol(self)

[...]

使用对话框 widget 将文件复制到网络

  • 将文件从本地硬盘复制到网络位置
  • 使用内置对话框浏览硬盘
  • 让文本框只读、指定默认的进入位置

首先,在 Tab2 中添加两个按钮和文本框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Create Manage Files Frame
mngFilesFrame = ttk.LabelFrame(tab2, text=' Manage Files: ')
mngFilesFrame.grid(column=0, row=1, sticky='WE', padx=10, pady=5)

# Add Widgets to Manage Files Frame
lb = ttk.Button(mngFilesFrame, text='Browse to File...',
command=self.getFileMame)
lb.grid(column=0, row=0, sticky=tk.W)

cb = ttk.Button(mngFilesFrame, text='Copy File to: ',
command=self.copyFile)
cb.grid(column=0, row=1, sticky=tk.W)

file = tk.StringVar()
self.entryLen = scrol_w
self.fileEntry = ttk.Entry(mngFilesFrame, width=self.entryLen,
textvariable=file)
self.fileEntry.grid(column=1, row=0, sticky=tk.W)

logDir = tk.StringVar()
self.netwEntry = ttk.Entry(mngFilesFrame, width=self.entryLen,
textvariable=logDir)
self.netwEntry.grid(column=1, row=1, sticky=tk.W)

# Add some space around each label
for child in mngFilesFrame.winfo_children():
child.grid_configure(padx=6, pady=6)

下面使用 tkinter 内置的对话框

1
2
from tkinter import filedialog as fd
from os import path
1
2
3
4
def getFileMame(self):
print('hello from getFileMame')
fDir = path.dirname(__file__)
fname = fd.askopenfilename(parent=self.win, initialdir=fDir)

可以为文本框设置默认值

1
2
3
4
5
self.name_entered = ttk.Entry(mighty, width=24, 
textvariable=self.name)
self.name_entered.grid(column=0, row=1, sticky='W')
self.name_entered.delete(0, tk.END)
self.name_entered.insert(0, '< default name>')

获取模块完整路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Module level GLOBALS
fDir, _ = path.split(path.realpath(__file__))
netDir = fDir + '\\Backup'

class App():
def __init__(self):
# Create instance
self.gui_queue = Queue()
self.win = tk.Tk()
# Add title
self.win.title("Python GUI")
self.create_widgets()
self.defaultFileEntries()


def defaultFileEntries(self):
self.fileEntry.delete(0, tk.END)
self.fileEntry.insert(0, fDir)
if len(fDir) > self.entryLen:
self.fileEntry.config(width=len(fDir) + 3)
self.fileEntry.config(state='readonly')
self.netwEntry.delete(0, tk.END)
self.netwEntry.insert(0, netDir)
if len(netDir) > self.entryLen:
self.netwEntry.config(width=len(netDir) + 3)

Tab2 设置为默认显示页

1
2
# self.name_entered.focus()
tabControl.select(1)

本例可以用来在 backup 文件夹备份代码,如果不存在则使用 os.makedirs 创建

1
2
if not path.exists(netDir):
makedirs(netDir, exist_ok=True)

源代码点此查看

使用 TCP/IP 通过网络通信

创建 TCP_Server 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from socketserver import BaseRequestHandler, TCPServer

class RequestHandler(BaseRequestHandler):
# override base class handle method

def handle(self):
print('Server connected to: ', self.client_address)
while True:
rsp =self.request.recv(512)
if not rsp: break
self.request.send(b'Server received: ' + rsp)


def start_server():
server = TCPServer(('', 24000), RequestHandler)
server.server_forever()

修改 Queues 模块

1
2
3
4
5
6
7
8
9
10
11
12
# Using TCP/IP
from socket import socket, AF_INET, SOCK_STREAM

def write_to_scrol(inst):
print('hi from Queue', inst)
sock = socket(AF_INET, SOCK_STREAM)
sock.connect(('localhost', 24000))
for idx in range(10):
sock.send(b'Message from a queue: ' + bytes(str(idx).encode()))
recv = sock.recv(8192).decode()
inst.gui_queue.put(recv)
inst.create_thread(6)

App 类的初始化过程中,让 TCP server 在自己的线程中开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[...]

import Queues as bq
from TCP_Server import start_server

[...]

class App():
def __init__(self):
# Create instance
self.gui_queue = Queue()
self.win = tk.Tk()
# Add title
self.win.title("Python GUI")
self.create_widgets()
self.defaultFileEntries()
# Start TCP/IP server in its own thread
svrT = Thread(target=start_server, daemon=True)
svrT.start()
[...]
def use_queues(self):

while True:
q_item = self.gui_queue.get()
self.scrol.insert(tk.INSERT, q_item+'\n')
print(q_item)
[...]

使用 urlopen 从网站读取数据

创建新的 URL 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from urllib.request import urlopen

link = 'http://python.org/'

def get_html():
try:
http_rsp = urlopen(link)
print(http_rsp)
html = http_rsp.read()
print(html)
html_decoded = html.decode()
print(html_decoded)
except Exception as ex:
print('*** Failed to get Html! ***\n\n' + str(ex))
else:
return html_decoded

if __name__ == '__main__':
get_html()

导入 URL 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[...]

import Queues as bq
from TCP_Server import start_server
import URL as url

[...]

def click_me(self):
print(self)
self.action.configure(text='Hello '+self.name.get()+' '+
self.number_chosen.get())
# self.create_thread()
bq.write_to_scrol(self)
sleep(2)
html_data = url.get_html()
print(html_data)
self.scrol.insert(tk.INSERT, html_data)

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
#!/usr/bin/env Python3

import tkinter as tk
from tkinter import ttk
from tkinter import scrolledtext
from tkinter import messagebox as msg
from tkinter import Spinbox
from tkinter import Menu
from time import sleep

from threading import Thread
from queue import Queue

import Queues as bq
from TCP_Server import start_server
import URL as url

from tkinter import filedialog as fd
from os import path, makedirs


# Module level GLOBALS
fDir, _ = path.split(path.realpath(__file__))
netDir = fDir + '\\Backup'
if not path.exists(netDir):
makedirs(netDir, exist_ok=True)

class App():
def __init__(self):
# Create instance
self.gui_queue = Queue()
self.win = tk.Tk()
# Add title
self.win.title("Python GUI")
self.create_widgets()
self.defaultFileEntries()
# Start TCP/IP server in its own thread
svrT = Thread(target=start_server, daemon=True)
svrT.start()


def defaultFileEntries(self):
self.fileEntry.delete(0, tk.END)
self.fileEntry.insert(0, fDir)
if len(fDir) > self.entryLen:
self.fileEntry.config(width=len(fDir) + 3)
self.fileEntry.config(state='readonly')
self.netwEntry.delete(0, tk.END)
self.netwEntry.insert(0, netDir)
if len(netDir) > self.entryLen:
self.netwEntry.config(width=len(netDir) + 3)

def create_widgets(self):
tabControl = ttk.Notebook(self.win)
tab1 = ttk.Frame(tabControl)
tabControl.add(tab1, text='Tab 1')
tab2 = ttk.Frame(tabControl)
tabControl.add(tab2, text='Tab 2')

tabControl.pack(expand=1, fill='both')

# LabelFrame using tab1 as the parent
mighty = ttk.LabelFrame(tab1, text=' Mighty Python ')
mighty.grid(column=0, row=0, padx=8, pady=4)

# Modify adding a Label using mighty as the parent instead of win
a_label = ttk.Label(mighty, text='Enter a name:')
a_label.grid(column=0, row=0, sticky='W')

# Adding a Textbox Entry widget
self.name = tk.StringVar()
self.name_entered = ttk.Entry(mighty, width=24,
textvariable=self.name)
self.name_entered.grid(column=0, row=1, sticky='W')
self.name_entered.delete(0, tk.END)
self.name_entered.insert(0, '< default name>')

# Adding a Button
self.action = ttk.Button(mighty, text='Click Me!', command=self.click_me)
self.action.grid(column=2, row=1)

ttk.Label(mighty, text="Choose a number:").grid(column=1, row=0)
number = tk.StringVar()
self.number_chosen = ttk.Combobox(mighty, width=14,
textvariable=number, state='readonly')
self.number_chosen['values'] = (1, 2, 4, 8, 16)
self.number_chosen.grid(column=1, row=1)
self.number_chosen.current(0)

# Adding a Spinbox widget
self.spin = Spinbox(mighty, values=(1, 2, 4, 8, 16), width=5, bd=9,
command=self._spin)
self.spin.grid(column=0, row=2, sticky='W')

# Using a scrolled Text control
scrol_w = 40
scrol_h = 10
self.scrol = scrolledtext.ScrolledText(mighty, width=scrol_w,
height=scrol_h, wrap=tk.WORD)
self.scrol.grid(column=0, row=3, sticky='WE', columnspan=3)

for child in mighty.winfo_children():
child.grid_configure(padx=4, pady=2)


self.mighty2 = ttk.LabelFrame(tab2, text=' The Snake ')
self.mighty2.grid(column=0, row=0, sticky='WE', padx=8, pady=4)

chVarDis = tk.IntVar()
check1 = tk.Checkbutton(self.mighty2, text='Disabled',
variable=chVarDis, state='disabled')
check1.select()
check1.grid(column=0, row=0, sticky=tk.W)

self.chVarUn = tk.IntVar()
self.check2 = tk.Checkbutton(self.mighty2, text='UnChecked',
variable=self.chVarUn)
self.check2.deselect()
self.check2.grid(column=1, row=0, sticky=tk.W)

self.chVarEn = tk.IntVar()
self.check3 = tk.Checkbutton(self.mighty2, text='Enabled',
variable=self.chVarEn)
self.check3.deselect()
self.check3.grid(column=2, row=0, sticky=tk.W)

# trace the state of the two checkbutton
self.chVarUn.trace('w', lambda unused0, unused1, unused2 :
self.checkCallback())

self.chVarEn.trace('w', lambda unused0, unused1, unused2 :
self.checkCallback())

colors = ['Blue', 'Gold', 'Red']
self.radVar = tk.IntVar()
# Selecting a non-existing index value for radVar
self.radVar.set(99)

# Creating all three Radiobutton widgets within one loop
for col in range(3):
curRad = tk.Radiobutton(self.mighty2, text=colors[col],
variable=self.radVar, value=col, command=self.radCall)
curRad.grid(column=col, row=1, sticky=tk.W)

# Add a Progressbar to Tab2
self.progress_bar = ttk.Progressbar(tab2, orient='horizontal',
length=256, mode='determinate')
self.progress_bar.grid(column=0, row=3, sticky='WE', pady=2)

# Create a container to hold buttons
buttons_frame = ttk.LabelFrame(self.mighty2, text=' ProgressBar ')
buttons_frame.grid(column=0, row=2, sticky='W', columnspan=2)

# Add Buttons for Progressbar commands
ttk.Button(buttons_frame, text=' Run ProgressBar ',
command=self.run_progressbar).grid(column=0, row=0, sticky=tk.W)

ttk.Button(buttons_frame, text=' Start ProgressBar ',
command=self.start_progressbar).grid(column=0, row=1, sticky=tk.W)

ttk.Button(buttons_frame, text=' Stop immediately ',
command=self.stop_progressbar).grid(column=1, row=0, sticky=tk.W)

ttk.Button(buttons_frame, text=' Stop after second ',
command=self.progressbar_stop_after).grid(column=1,
row=1, sticky=tk.W)

for child in buttons_frame.winfo_children():
child.grid_configure(padx=2, pady=2)
for child in self.mighty2.winfo_children():
child.grid_configure(padx=8, pady=2)

# Create Manage Files Frame
mngFilesFrame = ttk.LabelFrame(tab2, text=' Manage Files: ')
mngFilesFrame.grid(column=0, row=1, sticky='WE', padx=10, pady=5)

# Add Widgets to Manage Files Frame
lb = ttk.Button(mngFilesFrame, text='Browse to File...',
command=self.getFileMame)
lb.grid(column=0, row=0, sticky=tk.W)

cb = ttk.Button(mngFilesFrame, text='Copy File to: ',
command=self.copyFile)
cb.grid(column=0, row=1, sticky=tk.W)

file = tk.StringVar()
self.entryLen = scrol_w
self.fileEntry = ttk.Entry(mngFilesFrame, width=self.entryLen,
textvariable=file)
self.fileEntry.grid(column=1, row=0, sticky=tk.W)

logDir = tk.StringVar()
self.netwEntry = ttk.Entry(mngFilesFrame, width=self.entryLen,
textvariable=logDir)
self.netwEntry.grid(column=1, row=1, sticky=tk.W)

# Add some space around each label
for child in mngFilesFrame.winfo_children():
child.grid_configure(padx=6, pady=6)

# Creating a Menu Bar
menu_bar = Menu(self.win)
self.win.config(menu=menu_bar)

# Add menu items
file_menu = Menu(menu_bar, tearoff=0)
file_menu.add_command(label='New')
file_menu.add_separator()
file_menu.add_command(label='Exit', command=self._quit)
menu_bar.add_cascade(label='File', menu=file_menu)

# Add another Menu to Menu Bar and an item
help_menu = Menu(menu_bar, tearoff=0)
help_menu.add_command(label='About', command=self._msgBox)
menu_bar.add_cascade(label='Help', menu=help_menu)

# Change the main windows icon
# img = tk.PhotoImage(file='卷纸.png')
# self.win.tk.call('wm', 'iconphoto', self.win._w, img)
self.win.iconbitmap('卷纸.ico')

# self.name_entered.focus()
tabControl.select(1)


def method_in_a_thread(self, num_of_loops=10):
print('Hi, How are you?')
for idx in range(num_of_loops):
sleep(1)
self.scrol.insert(tk.INSERT, str(idx)+'\n')
sleep(1)
print('method_in_a_thread():', self.run_thread.isAlive())

# Running methods in Threads
def create_thread(self, num=3):
self.run_thread = Thread(target=self.method_in_a_thread, args=[num])
print(self.run_thread)
self.run_thread.setDaemon(True)
self.run_thread.start()
# print('createThread():',self.run_thread.isAlive())

# start queue in its own thread
write_thread = Thread(target=self.use_queues, daemon=True)
write_thread.start()


def use_queues(self):

while True:
q_item = self.gui_queue.get()
self.scrol.insert(tk.INSERT, q_item+'\n')
print(q_item)


def click_me(self):
print(self)
self.action.configure(text='Hello '+self.name.get()+' '+
self.number_chosen.get())
# self.create_thread()
bq.write_to_scrol(self)
sleep(2)
html_data = url.get_html()
print(html_data)
self.scrol.insert(tk.INSERT, html_data)


def _spin(self):
value = self.spin.get()
# print(value)
self.scrol.insert(tk.INSERT, value+'\n')


def checkCallback(self, *ignoredArgs):
if self.chVarUn.get():
self.check3.configure(state='disabled')
else:
self.check3.configure(state='normal')
if self.chVarEn.get():
self.check2.configure(state='disabled')
else:
self.check2.configure(state='normal')


def radCall(self):
radSel = self.radVar.get()
if radSel == 0:
self.mighty2.configure(text='Blue')
elif radSel == 1:
self.mighty2.configure(text='Gold')
elif radSel == 2:
self.mighty2.configure(text='Red')


# update progressbar in callback loop
def run_progressbar(self):
self.progress_bar['maximum'] = 100
for i in range(101):
sleep(0.05)
self.progress_bar['value'] = i
self.progress_bar.update()
self.progress_bar['value'] = 0


def start_progressbar(self):
self.progress_bar.start()


def stop_progressbar(self):
self.progress_bar.stop()


def progressbar_stop_after(self, wait_ms=1000):
self.win.after(wait_ms, self.progress_bar.stop)


def _quit(self):
self.win.quit()
self.win.destroy()
exit()

def _msgBox(self):
msg.showinfo('Python Message Info Box', 'A Python GUI created by '
'tkinter \nThe year is 2018')

def getFileMame(self):
# print('hello from getFileMame')
# fDir = path.dirname(__file__)
# print(fDir)
fName = fd.askopenfilename(parent=self.win, initialdir=fDir)
self.fileEntry.config(state='enabled')
self.fileEntry.delete(0, tk.END)
self.fileEntry.insert(0, fName)

def copyFile(self):
import shutil
src = self.fileEntry.get()
file = src.split('/')[-1]
dst = self.netwEntry.get() + '\\' + file
try:
shutil.copy(src, dst)
msg.showinfo('Copy File to Network', 'Succes: File copied.')
except FileNotFoundError as err:
msg.showerror('Copy File to Network', '*** Failed to copy '
'file! ***\n\n'+str(err))
except EXception as ex:
msg.showerror('Copy File to Network', '*** Failed to copy '
'file! ***\n\n'+str(err))


app = App()

# Running methods in Threads
# run_thread = Thread(target=app.method_in_a_thread)
app.win.mainloop()

参考文献

  • Python GUI Programming Cookbook - Second Edition by Burkhard A. Meier
GreatX wechat
订阅公众号,获取更多信息。