最近在用 python 写工具给小组的其他成员使用,因为需要可以在 GUI 上输入不同的变量值做不同的判断处理所以最后写了一个 gui 。现在问题是,最后我希望写一个进度条,可以在主线程的 for-loop 中,每处理完一个任务后发送一个事件,给在另外一个线程的事件管理器,事件管理器可以立刻更新响应 GUI 的进度条的进度。 下面是我写的代码:
from queue import Queue, Empty
from threading import *
from tkinter import *
import time
from tkinter import ttk
EVENT_TYPE_1 = "Count"
MAX_NUMBER = 10
CUR_NUMBER = 0
class event_manager:
def __init__(self):
self._eventQueue = Queue()
self._thread = Thread(target=self.Run)
self._handlers = {}
self._active = False
def Start(self):
self._active = True
self._thread.start()
def Run(self):
while self._active is True:
try:
event = self._eventQueue.get(block=True, timeout=1)
self.Process(event)
except Empty:
pass
def Process(self, event):
if event.type in self._handlers:
for handler in self._handlers[event.type]:
handler()
else:
pass
def Stop(self):
self._active = False
self._thread.join()
def addEventListenter(self, type_, handler):
try:
handlerList = self._handlers[type_]
except KeyError:
handlerList = []
self._handlers[type_] = handlerList
if handler not in handlerList:
handlerList.append(handler)
def removeEventListenter(self, type_, handler):
try:
handlerList = self._handlers[type_]
if handler in handlerList:
handlerList.remove(handler)
if not handlerList:
del self._handlers[type_]
except KeyError:
pass
def sendEvent(self, event):
self._eventQueue.put(event)
class Event:
def __init__(self, event_event_name, cur_done_task, type_=None):
self.type = type_
self._event_name = event_event_name
self._curDoneTask = cur_done_task
class EventSource:
def __init__(self, event_name, event_mgr, max_number, type):
self._event_name = event_name
self._event_manager = event_mgr
self._type = type
self._max_number = max_number
def count(self):
global CUR_NUMBER
for i in range(self._max_number):
CUR_NUMBER = i + 1
print("************ main thread start:now start process {} - count : {}".format(self._event_name, CUR_NUMBER))
event = Event("test", CUR_NUMBER, type_=self._type)
self._event_manager.sendEvent(event)
time.sleep(1)
class GUIListener(Tk):
def __init__(self):
super(GUIListener, self).__init__()
self.title("Progress GUI")
self.geometry("1200x805+600+100")
self.config(bg="#535353")
self.resizable(True, True)
self.progressBar = ttk.Progressbar(master=self, orient=HORIZONTAL, maximum=MAX_NUMBER, length=300)
self.progressBar.pack()
self.button = ttk.Button(self, text="Run", command=lambda: self.button_function(MAX_NUMBER))
self.button.pack()
def update_progress_value(self):
print("************Sub thread start: detect progress bar value is now...{}".format(self.progressBar['value']))
self.progressBar['value'] = CUR_NUMBER
self.progressBar.update_idletasks()
print("************Sub thread start: update progress bar value to...{}".format(CUR_NUMBER))
def button_function(self,max_number):
es = EventSource("eventSource", eventMgr, max_number, EVENT_TYPE_1)
es.count()
if __name__ == '__main__':
gui = GUIListener()
eventMgr = event_manager()
eventMgr.addEventListenter(EVENT_TYPE_1, gui.update_progress_value)
eventMgr.Start()
gui.mainloop()
现在的问题是,返回的结果很奇怪,是这样的:
************ main thread start:now start process eventSource - count : 1
************ main thread start:now start process eventSource - count : 2
************ main thread start:now start process eventSource - count : 3
************ main thread start:now start process eventSource - count : 4
************ main thread start:now start process eventSource - count : 5
************ main thread start:now start process eventSource - count : 6
************ main thread start:now start process eventSource - count : 7
************ main thread start:now start process eventSource - count : 8
************ main thread start:now start process eventSource - count : 9
************ main thread start:now start process eventSource - count : 10
************Sub thread start: detect progress bar value is now...0.0
************Sub thread start: update progress bar value to...10
************Sub thread start: detect progress bar value is now...10
************Sub thread start: update progress bar value to...10
************Sub thread start: detect progress bar value is now...10
************Sub thread start: update progress bar value to...10
************Sub thread start: detect progress bar value is now...10
************Sub thread start: update progress bar value to...10
************Sub thread start: detect progress bar value is now...10
************Sub thread start: update progress bar value to...10
************Sub thread start: detect progress bar value is now...10
************Sub thread start: update progress bar value to...10
************Sub thread start: detect progress bar value is now...10
************Sub thread start: update progress bar value to...10
************Sub thread start: detect progress bar value is now...10
************Sub thread start: update progress bar value to...10
************Sub thread start: detect progress bar value is now...10
************Sub thread start: update progress bar value to...10
************Sub thread start: detect progress bar value is now...10
************Sub thread start: update progress bar value to...10
请大佬指点一下,因为我希望的结果是类似下面这样立刻响应插入的顺序:
************ main thread start:now start process eventSource - count : 1
************Sub thread start: detect progress bar value is now...0.0
************Sub thread start: update progress bar value to...1
************ main thread start:now start process eventSource - count : 2
************Sub thread start: detect progress bar value is now...1.0
************Sub thread start: update progress bar value to...2
************ main thread start:now start process eventSource - count : 3
************Sub thread start: detect progress bar value is now...2.0
************Sub thread start: update progress bar value to...3
...
etc
真的这个问题困扰了我蛮久了,希望大佬可以不吝赐教,指点问题发生在哪里,如何修改可以达到预期目的,感激不尽!
1
ec0 2021-02-12 21:41:57 +08:00 5
|
2
seventhbible OP @ec0 我的天,大佬非常非常感谢您的指导!终于解决了这个困扰我很久的问题,谢谢谢谢谢谢!另外可以请大佬解答一下 daemon 这个是啥意思么?
|
3
ec0 2021-02-12 22:12:53 +08:00 1
默认情况下 daemon=False
这样当你的窗口关闭时,UI 线程关闭了,但是 event_manager 里面的线程还在运行,程序没有关闭 你可以试试:运行程序,关闭窗口,但是在任务管理器里面可以看到 python 还在运行,只是没有显示窗口 daemon=True 时,主线程关闭,daemon 线程也会关闭 |
4
seventhbible OP @ec0 原来如此,再次感谢大佬指教!
|
5
seventhbible OP @ec0 大佬可以追问一个问题嘛!就是现在的做法是把具体的订阅者的业务逻辑放到另外一个子线程去跑了,主线程只管 GUI 。那么如何做到我在 GUI 上点击一个按钮之后,在子线程开始处理具体的业务逻辑(譬如 count )且没有处理结束之前,锁住整个 GUI 的其他按钮模块不允许进行其他业务线程的处理?
|
6
ec0 2021-02-13 23:55:09 +08:00
button["state"] = "disabled" # 按钮不可点击
button["state"] = "normal" # 按钮可以点击 比如,你可以在之前的 Thread(target=es.count).start() 前面加上 self.button["state"] = "disabled" 在 class EventSource 的 count 函数 for 语句的后面加上 for i in range(self._max_number): ... gui.button["state"] = "normal" |
7
seventhbible OP @ec0 如果我的 GUI 上有 N 个按钮的话,就是每个按钮都要这样设定么
|
8
seventhbible OP 其实不只是按钮,我的 gui 上还有其他的类似 combobox,text input entry 框等等可以互动的组件,如果想要一起锁住,请问有比较通用的方法么?
|
9
ec0 2021-02-14 01:04:55 +08:00
是啊,很麻烦,不过你可以给 button 取有序的名字,比如 button0,button1,然后
for i in range(n): (缩进)getattr(gui, "button"+str(i))["state"] = "disabled" 批量 disable 按钮 |
10
ec0 2021-02-14 02:06:38 +08:00
我找到了这段代码,可以批量 disable TButton,更通用一些,改造一下 if 语句可以 disable 其他类型的控件,不过不是所有控件都有 state 属性的
for child in gui.winfo_children(): (缩进)if child.winfo_class() == 'TButton': (缩进)(缩进)child['state'] = 'disabled' |
12
seventhbible OP @ec0 非常感谢大佬这么有耐心帮助我!我马上就去试试!
|