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'] = 0
WorkCode.progressbar_func(self.num, self) # 需要将self作为参数传入,才可以实时更改UI实例中进度条的值
整体代码如下:
# -*- coding:utf-8 -*-
import tkinter as tk
import tkinter.ttk
import WorkCode
class 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 = 350
self.height = 150
ws = 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'] = 0
WorkCode.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'] = 0
def 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 = 0
if leng != 0:
progressNum = num/leng
self.progressbarOne['value'] = progressNum * 100 # 在这里设置进度条的值
self.root.update()
整体代码如下:
import time
def 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 = 0
if leng != 0:
progressNum = num/leng
self.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 threading
class MyThread(threading.Thread):
def __init__(self, num, handle, barNum):
super().__init__()
self.__flag = threading.Event() # 用于暂停线程的标识
self.__flag.set() # 设置为True
self.__running = threading.Event() # 用于停止线程的标识
self.__running.set() # 将running设置为True
self.num = num
self.handle = handle
self.barNum = barNum
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 pause(self):
self.__flag.clear() # 设置为False, 让线程阻塞
def resume(self):
self.__flag.set() # 设置为True, 让线程停止阻塞
def stop(self):
self.__flag.set() # 将线程从暂停状态恢复, 如何已经暂停的话
self.__running.clear() # 设置为False
class 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 = 350
self.height = 280
ws = 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.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'] = 0
self.progressbarTwo = tk.ttk.Progressbar(self.root, length=240)
# 进度值最大值
self.progressbarTwo['maximum'] = 100
# 进度值初始值
self.progressbarTwo['value'] = 0
def 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 time
def 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 = 0
if leng != 0:
progressNum = num/leng
if barNum == 1:
self.progressbarOne['value'] = progressNum * 100 # 在这里设置进度条的值
if barNum == 2:
self.progressbarTwo['value'] = progressNum * 100 # 在这里设置进度条的值
self.root.update() # 在这里更新进度条的值
2.2.3 实际效果
3. 代码存在的问题
在进度条没有完成进度之前,再次点击“开始”按钮,会出现显示不顺畅问题(如下动画所示),其实是两个进度重叠显示造成的。因为点击一次“开始”按钮,就会进行一次进度条的显示。代码没有能实现线程的暂停功能。