1.前情提要
一直想用python的 tkinter模块实现类似QT中信号和槽的功能,但是在网上没有找到合适的方法(也可能是自己没有找到)。尝试着将界面代码中的 self 作为参数传入功能代码中,最终实现了。本人使用python3.7.4, 32位版本。
2. 1单个进度条
为了展示tkinter进度条的实现,写了两个python文件,一个是UICode.py,该文件是UI界面代码。一个是WorkCode.py,该文件是功能代码。
2.1.1 UICode.py代码
代码中起主要作用的代码:
def run(self):self.progressbarOne['value'] = 0WorkCode.progressbar_func(self.num, self) # 需要将self作为参数传入,才可以实时更改UI实例中进度条的值
整体代码如下:
# -*- coding:utf-8 -*-
import tkinter as tk
import tkinter.ttk
import WorkCodeclass GuiSample(object):def __init__(self):self.root = tk.Tk()# 设置GUI界面属性self.root.title('tkinter 进度条测试') # 设置GUI标题self.root.wm_attributes("-alpha", 1.0) # 设置GUI透明度(0.0~1.0)self.root.wm_attributes("-topmost", True) # 设置GUI置顶# self.root.wm_attributes("-toolwindow", True) # 设置为工具窗口(没有放大和缩小按钮)# self.root.overrideredirect(-1) # 去除GUI边框(GUI标题、放大缩小和关闭按钮都会消失)# self.bind_window_move_events() # 如果去除GUI边框了,就要绑定窗口移动事件,否则GUI无法移动self.width = 350self.height = 150ws = self.root.winfo_screenwidth()hs = self.root.winfo_screenheight()x = (ws / 2) - (self.width / 2)y = (hs / 2) - (self.height / 2)self.root.geometry('%dx%d+%d+%d' % (self.width, self.height, x, y))# 设置类中的全局变量self.num = 30# 设置所有窗口部件self.build_label()self.build_button()self.build_progressbarOne()# 执行所有窗口部件self.label1.place(x=115, y=30, anchor=tk.W)self.progressbarOne.place(x=55, y=60, anchor=tk.W)self.test_button.place(x=155, y=100, anchor=tk.W)def run(self):self.progressbarOne['value'] = 0WorkCode.progressbar_func(self.num, self) # 需要将self作为参数传入,才可以实时更改UI实例中进度条的值def build_progressbarOne(self):self.progressbarOne = tk.ttk.Progressbar(self.root, length=240)# 进度值最大值self.progressbarOne['maximum'] = 100# 进度值初始值self.progressbarOne['value'] = 0def build_label(self):self.label1 = tk.Label(self.root, text="这是一个测试进度条")def build_button(self):self.test_button = tk.Button(self.root, text='开 始', command=self.run)if __name__ == '__main__':progressbarSample = GuiSample()progressbarSample.root.mainloop()
2.1.2 WorkCode.py代码
起到实时传递任务进度的代码:
def get_progress(num, leng, self): # 该函数起到实时更新进度条的作用,可以看做是一个信号函数progressNum = 0if leng != 0:progressNum = num/lengself.progressbarOne['value'] = progressNum * 100 # 在这里设置进度条的值self.root.update()
整体代码如下:
import timedef progressbar_func(num, self): # 该函数是实际的功能函数,在该函数中调用get_progress(),实时显示任务进度for i in range(0, num):time.sleep(0.1)get_progress(i, num-1, self)def get_progress(num, leng, self): # 该函数起到实时更新进度条的作用,可以看做是一个信号函数progressNum = 0if leng != 0:progressNum = num/lengself.progressbarOne['value'] = progressNum * 100 # 在这里设置进度条的值self.root.update() # 在这里更新进度条的值
2.1.3. 实际效果
2.2 两个进度条
要实现两个进度条相互不受影响,需要采用线程,每一个进度条是一个线程。还是两个python文件,一个是UICode.py,该文件是UI界面代码。一个是WorkCode.py,该文件是功能代码。
2.2.1UICode.py代码
“self”参数传递过程:使用GuiSample类run函数开启线程,将“self”传入线程,在线程中调用功能代码时,“self”参数通过线程中的run函数传入功能代码。
GuiSample类run函数:
def run(self, barNum):t = MyThread(self.num, self, barNum)t.start()
线程中的run函数:
def run(self):while self.__running.isSet():self.__flag.wait() # 为True时立即返回, 为False时阻塞直到内部的标识位为True后返回WorkCode.progressbar_func(self.num, self.handle, self.barNum)self.stop()
def build_button(self):代码不能按照如下形式来写,因为“command=self.run(1)”会在没有点击按钮时直接调用调用run函数,需要通过lambda表达式调用run函数。
def build_button(self):self.test_button = tk.Button(self.root, text='开 始', command=self.run(1))self.test_button2 = tk.Button(self.root, text='开 始', command=self.run(2))
整体代码:
# -*- coding:utf-8 -*-
import tkinter as tk
import tkinter.ttk
import WorkCode
import threadingclass MyThread(threading.Thread):def __init__(self, num, handle, barNum):super().__init__()self.__flag = threading.Event() # 用于暂停线程的标识self.__flag.set() # 设置为Trueself.__running = threading.Event() # 用于停止线程的标识self.__running.set() # 将running设置为Trueself.num = numself.handle = handleself.barNum = barNumdef run(self):while self.__running.isSet():self.__flag.wait() # 为True时立即返回, 为False时阻塞直到内部的标识位为True后返回WorkCode.progressbar_func(self.num, self.handle, self.barNum)self.stop()def pause(self):self.__flag.clear() # 设置为False, 让线程阻塞def resume(self):self.__flag.set() # 设置为True, 让线程停止阻塞def stop(self):self.__flag.set() # 将线程从暂停状态恢复, 如何已经暂停的话self.__running.clear() # 设置为Falseclass GuiSample(object):def __init__(self):super().__init__()self.root = tk.Tk()# 设置GUI界面属性self.root.title('tkinter 进度条测试') # 设置GUI标题self.root.wm_attributes("-alpha", 1.0) # 设置GUI透明度(0.0~1.0)self.root.wm_attributes("-topmost", True) # 设置GUI置顶# self.root.wm_attributes("-toolwindow", True) # 设置为工具窗口(没有放大和缩小按钮)# self.root.overrideredirect(-1) # 去除GUI边框(GUI标题、放大缩小和关闭按钮都会消失)# self.bind_window_move_events() # 如果去除GUI边框了,就要绑定窗口移动事件,否则GUI无法移动self.width = 350self.height = 280ws = self.root.winfo_screenwidth()hs = self.root.winfo_screenheight()x = (ws / 2) - (self.width / 2)y = (hs / 2) - (self.height / 2)self.root.geometry('%dx%d+%d+%d' % (self.width, self.height, x, y))# 设置类中的全局变量self.num = 30self.barNum = 0# 设置所有窗口部件self.build_label()self.build_button()self.build_progressbarOne()# 执行所有窗口部件self.label1.place(x=115, y=30, anchor=tk.W)self.progressbarOne.place(x=55, y=60, anchor=tk.W)self.test_button.place(x=155, y=100, anchor=tk.W)self.label2.place(x=115, y=160, anchor=tk.W)self.progressbarTwo.place(x=55, y=190, anchor=tk.W)self.test_button2.place(x=155, y=230, anchor=tk.W)def run(self, barNum):t = MyThread(self.num, self, barNum)t.start()def build_progressbarOne(self):self.progressbarOne = tk.ttk.Progressbar(self.root, length=240)# 进度值最大值self.progressbarOne['maximum'] = 100# 进度值初始值self.progressbarOne['value'] = 0self.progressbarTwo = tk.ttk.Progressbar(self.root, length=240)# 进度值最大值self.progressbarTwo['maximum'] = 100# 进度值初始值self.progressbarTwo['value'] = 0def build_label(self):self.label1 = tk.Label(self.root, text="这是第一个测试进度条")self.label2 = tk.Label(self.root, text="这是第二个测试进度条")def build_button(self):self.test_button = tk.Button(self.root, text='开 始', command=lambda :self.run(1))self.test_button2 = tk.Button(self.root, text='开 始', command=lambda :self.run(2))if __name__ == '__main__':progressbarSample = GuiSample()progressbarSample.root.mainloop()
2.2.2 WorkCode.py代码
import timedef progressbar_func(num, handle,barNum): # 该函数是实际的功能函数,在该函数中调用get_progress(),实时显示任务进度for i in range(0, num):time.sleep(0.1)get_progress(i, num-1, self, barNum)def get_progress(num, leng, self, barNum): # 该函数起到实时更新进度条的作用,可以看做是一个信号函数progressNum = 0if leng != 0:progressNum = num/lengif barNum == 1:self.progressbarOne['value'] = progressNum * 100 # 在这里设置进度条的值if barNum == 2:self.progressbarTwo['value'] = progressNum * 100 # 在这里设置进度条的值self.root.update() # 在这里更新进度条的值
2.2.3 实际效果
3. 代码存在的问题
在进度条没有完成进度之前,再次点击“开始”按钮,会出现显示不顺畅问题(如下动画所示),其实是两个进度重叠显示造成的。因为点击一次“开始”按钮,就会进行一次进度条的显示。代码没有能实现线程的暂停功能。
4. 参考资料
Python之Tkinter使用详解
Python之tkinter 进度条 Progressbar