赞
踩
要用 python 做一个小工具,需要在子线程任务中更新界面,例如更新进度条,如果用 Pyqt5,Pyside2 等,可以通过在 QThread 里用pyqtSignal 来发射信号更新 UI,但是由于这俩框架做出来的程序打包成 .exe 后,包体积实在太大,这小工具界面又未复杂到非要使用 Pyqt5 的地步,以包体积换方便用户就不方便了,就选择使用 Tkinter 来实现好了。
最终效果:
问题一:TKinter 里原生控件太少,连进度条控件也没有找着,只能自己画一个了;
问题二:TKinter 里没有 pyqtSignal 这种东西可用,只能自己做子线程和 UI 线程通信来实现 UI 刷新;
第一个并不是很麻烦,可以用 canvas 自己画一个进度条;
对于第二个问题,我想到了 android 里的 handler 消息机制,android 上也是在子线程做耗时操作,完成后通知 UI 线程更新界面,android 原生 api 自带的 handler 就主要来干这个事情,另外 Rxjava 也可以干这个事情,所以 Rxpy 可能也行,但是在 python 里用 Rx,不会感觉有点怪怪的吗?还是模拟 android 的消息机制在 python 里搞个简易的实现好了。
这套消息机制有如下几个分工对象:
Handler:外层消息操作对象
Queue:消息队列
Message:消息
另外 android 还有 HandlerThread,Looper,这里通过 tKinter 的 after 实现 looper 的循环。
预期结果是在 tKinter 的 UI 线程创建一个 Handler 实例,并定义一个 handle_msg 方法,子线程可以通过这个 Handler 实例发送消息到 UI 线程的 handle_msg 里,统一在这里做更新界面操作。
from tkinter import Label, Canvas, StringVar, Frame class ProgressBar(Frame): def __init__(self, parent, width=300, height=30, border_width=2): super(ProgressBar, self).__init__(parent) self.width = width self.height = height self.border_width = border_width self.canvas = Canvas(self, width=self.width, height=self.height, bg="white") self.progress_text = StringVar() self.x1 = self.border_width + 1 self.y1 = self.border_width + 1 self.x2 = self.width - 1 self.y2 = self.height - 1 self.x11 = self.x1 + self.border_width / 2 self.y11 = self.y1 + self.border_width / 2 self.progress_width = self.width - self.x11 - self.border_width / 2 - 1 print(self.x1, self.y1, self.x2, self.y2) self.init() def init(self): self.canvas.grid(row=0, column=0) # 进度条背景框 self.canvas.create_rectangle(self.x1, self.y1, self.x2, self.y2, outline="green", width=self.border_width) # 进度条进度 self.fill_rec = self.canvas.create_rectangle(self.x11, self.y11, self.x11, self.y2 - self.border_width / 2, outline="", width=0, fill="green") Label(self, textvariable=self.progress_text, width=5).grid(row=0, column=1) def progress(self, current, total): self.canvas.coords(self.fill_rec, (self.x11, self.y11, self.x11 + (current / total) * self.progress_width, self.y2 - self.border_width / 2)) f = round(current / total * 100, 2) self.progress_text.set(str(f) + '%') self.update() if f == 100.00: self.progress_text.set("完成")
from queue import Queue class MainQueue(Queue): def __init__(self): super(MainQueue, self).__init__() class Message(object): def __init__(self, what, obj): super(Message, self).__init__() self.what = what self.obj = obj class Handler(object): def __init__(self, view, handle_msg): self.queue = MainQueue() self.view = view self.handle_msg = handle_msg self.loop() def loop(self): while not self.queue.empty(): content = self.queue.get() self.handle_msg(content) self.view.after(30, self.loop) def send_msg(self, msg): self.queue.put(msg) def send_obj(self, what, obj): self.queue.put(Message(what, obj))
import threading import time from tkinter import Button, Tk, TOP from util.HandlerMessage import Handler from util.ProgressBar import ProgressBar class GUI(object): def __init__(self, root): self.root = root self.init_view(root) # 消息处理 def handle_msg(self, msg): if msg.what == "key1": # 更新UI self.change_progress(msg.obj, 100) return def init_view(self, root): self.root.title("test") self.root.geometry("400x200+700+500") self.root.resizable = False self.progress_bar = ProgressBar(root) self.progress_bar.pack(anchor="center",pady=20) self.button_1 = Button(root, text="download", width=10, command=self.start_download) self.button_1.pack(anchor="center") # 创建handler self.handle = Handler(self.root, self.handle_msg) self.root.mainloop() def start_download(self): BackTask(self.handle).run() def change_progress(self, current, total): self.progress_bar.progress(current, total) class BackTask(object): def __init__(self, handler): self.handler = handler def _task(self): count = 0 while count < 100: count += 1 # 在子线程发送消息 self.handler.send_obj("key1", count) time.sleep(30 / 1000) def run(self): thread = threading.Thread(target=self._task) thread.setDaemon(True) thread.start() if __name__ == "__main__": root = Tk() GUI(root)
到此已完成,使用时如果需要在子线程更新 UI,就在 UI 线程创建一个 Handler 实例,并创建一个处理消息的方法就完了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。