[英]How to Organize Threaded GUI Application (Python)
我在使用Tkinter(和ttk)和Python使用GUI将我的代码组织到一个可用的而不是超级错误的程序时遇到了麻烦。 基本上,它现在只是从网上下载图像,但是我什至无法使用简单的GUI来解决这个问题。 尽管所有内容都可以在控制台中运行,但使GUI成为噩梦,更不用说使其正常工作了。 现在我可以正常工作了,但是它经常崩溃,这很明显是我做错了,导致无法正确访问GUI中的变量错误(即使是控制台中的错误消息,我自己也放了一些函数来确保事情能够解决)正确运行)并不断崩溃。
基本上我有这样的事情。
发生且需要工作的主要事情是:用户输入字符串从输入文本发送到程序的强化部分(当前包含在线程中),强化部分进入GUI的进度栏,以及强化部分将文本消息发送给程序。没有GUI和密集部分崩溃的文本框/记录器。 同样,密集部分应在GUI完全加载后立即启动,并在准备就绪时将启动消息发送到文本框。
密集的部分可以处理其他事情,但不会干扰GUI,例如图像的实际下载和保存,浏览以及文件I / O,而且无论如何我都没有问题。
我还阅读了有关队列和线程以及教程的信息,但我似乎还是一无所获。 尤其是我如何使程序在GUI中不断移动进度条,同时还向GUI发送文本消息(例如,我如何从Queues进行处理,而不必进行非常慢且占用大量CPU的While和If循环以及多个在简单的示例中,只需要简单的while和queue.get()等待就可以了,因为它消耗很少的资源,这很好。 所以我的问题是,我需要为此实现哪种结构,并且如果可能的话,我能否得到一个或两个示例(我从示例中学到的知识比从阅读文档中学到的更好)? 非常感谢你。
from Tkinter import *
import ttk
import Threading
import #a bunch of other stuff
class myHardWorkerThread (threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.setDaemon(True)
self.myClass = ModifiedConsoleClass()
def run(self):
#thread needs to wait at least a little otherwise the thread begins too
#fast and causes even more errors, probably due to it sending text to
#the textbox upon startup among other things and just overall no
#organization
time.sleep(3)
self.myClass.BeginDoingStuff()
class ApplyMyGuiAndStartThread():
def __init__(self, root, thread):
root.geometry('500x500')
root.resizable(0,0)
#Put backgrounds or images or logos here
self.canvas = Canvas(root)
self.canvas.pack()
#My textbox that acts like a Log/Console output
self.txtLogger = Text(root,state="disabled",wrap="word")
self.txtLogger.place()
self.scrollbar = Scrollbar(root)
self.scrollbar.place()
#Progressbar
self.myVal = IntVar()
self.TProgressbar = ttk.Progressbar(root, orient=HORIZONTAL, variable = self.myVal, mode='determinate')
self.TProgressbar.place()
#Entrybox for user input
self.txbEntryText = StringVar()
self.txtbEntry = ttk.Entry (root, textvariable=self.txbEntryText)
self.txtbEntry.place()
self.txtbEntry.bind("<Return>", self.SendFromGUItoThread)
self.thread = thread
self.thread.start()
def SendFromGUItoThread(self,arg=None):
myentry = str(self.txtbEntry.get())
self.txtbEntry.delete(0, END)
self.thread.myClass.entryBoxValueCopy = myentry
def SendFromThreadToGUI(self,msg):
try:
self.txtLogger['state'] = 'normal'
self.txtLogger.insert('end', msg)
self.txtLogger['state'] = 'disabled'
except:
print "Could not be printed"
class ModifiedConsoleCode():
def __init__(self):
#constants here like
self.entryBoxValueCopy = None
def BeginDoingStuff():
#Thread does the bulk of work here, includes connecting to websites,
#collecting info, sending text messages to GUI, downloading images and
#stepping the progressbar by a calculated amount by file size
def OneOfManyFunctionsUsedInsideBeginDoingStuff():
#Breaks things down like looping time.sleep waits for user input in the entry box
#showing up in entryBoxValueCopy, and using the user input to surf websites and
#collect images
if __name__ == '__main__':
root = Tk()
root.title(titleOfTheProgram)
worker = myHardWorkerThread()
w = ApplyMyGuiAndStartThread(root,worker)
root.mainloop()
os._exit(0)
简短的答案是,您不能与工作线程中的小部件进行交互。 唯一的选择是让您的工作线程在线程安全队列中推送某些内容,并让主线程对其进行轮询。
您不需要任何while循环来轮询队列。 您已经有一个无限循环-事件循环(例如: mainloop
)-因此无需添加额外的循环。
从主线程轮询队列的方式如下所示:
def pollQueue(self):
<look at the queue, act on the results>
self.after(100, self.pollQueue)
这样做是安排每100毫秒轮询一次队列。 当然,您可以将轮询间隔设置为任何所需的值。
而不是使用线程,您应该使用tkinter方法“之后”在tkinter事件循环上设置事件。
例如,当使用画布元素时,我会使用
canvar.after(50, func=keepDoingSomething)
这与javascript函数setTimeout相似,并且是线程安全的,不会干扰tkinter gui线程。
我认为最好创建3个类而不是2个类并将它们分为
GUI和功能具有很强的自我描述性,该应用程序是两者之间的桥梁,因此不会妨碍其工作。
示例工作代码如下:
import tkinter as tk
from tkinter import ttk,messagebox
import threading
import time
#base GUI Class
class GUI:
def __init__(self, root, runCommand):
mf = ttk.Frame(root, padding="5 5 5 5")
mf.grid(column=0, row=0)
mf.columnconfigure(0, weight=1)
mf.rowconfigure(0, weight=1)
# Global Values
self.Fnm = tk.StringVar(root, "SearchFile.xlsx")
self.Ncol = tk.StringVar(root, "D")
self.Vcol = tk.StringVar(root, "C")
# Label
tk.Label(mf, text="File Name").grid(column=1, row=1, pady=6)
tk.Label(mf, text="Name Col").grid(column=1, row=3, pady=6)
tk.Label(mf, text="Value Col").grid(column=3, row=3, pady=6)
# components
self.fname = ttk.Entry(mf, width=18, textvariable=self.Fnm)
self.nmCol = ttk.Entry(mf, width=6, textvariable=self.Ncol)
self.valCol = ttk.Entry(mf, width=6, textvariable=self.Vcol)
self.but = ttk.Button(mf, text="Refresh", command=runCommand)
self.pgbar = ttk.Progressbar(mf, orient="horizontal", mode="determinate")
# Design
self.fname.grid(column=2, row=1, pady=3, columnspan=3)
self.nmCol.grid(column=2, row=3, pady=3)
self.valCol.grid(column=4, row=3, pady=3)
self.but.grid(column=2, row=2, columnspan=2)
self.pgbar.grid(column=1,row=4,columnspan=4)
def refresh(self):
pass
def get(self):
return [self.Fnm.get(), self.Ncol.get(), self.Vcol.get()]
#Base process Class
class Proc:
def __init__(self, dets,pgbar,but):
self.Fnm = dets[0]
self.Ncol = dets[1]
self.Vcol = dets[2]
self.pg=pgbar
self.butt=but
def refresh(self):
self.butt['state'] = 'disabled'
self.pg.start()
#ATTENTION:Enter Your process Code HERE
for _ in range(5):
time.sleep(2)
self.pg.stop()
#Any search/sort algorithm to be used
#You can use self.pg.step() to be more specific for how the progress bar proceeds
messagebox.showinfo("Process Done","Success")
self.butt['state'] = 'enabled'
#Base Application Class
class App:
def __init__(self, master):
self.master = master
self.gui = GUI(self.master, self.runit)
def runit(self):
self.search = Proc(self.gui.get(),self.gui.pgbar,self.gui.but)
self.thread1 = threading.Thread(target=self.search.refresh)
self.thread1.start()
def main():
app = tk.Tk()
gui = App(app)
app.title("Refresh Search File")
app.mainloop()
if __name__ == '__main__':
main()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.