3

我在使用 Tkinter(和 ttk)和 Python 的 GUI 将我的代码组织成一个可用且不是超级错误的程序时遇到了麻烦。基本上,它现在只是从网上下载图像,但即使是简单的 GUI,我也遇到了麻烦。虽然一切都在控制台中工作,但制作 GUI 是一场噩梦,更不用说让它工作了。现在我已经让它工作了,但它经常崩溃,很明显我做错了,导致 GUI 中的变量错误没有被正确访问(即使是控制台中的错误消息,我自己也放了函数来确保事情正确运行)和不断的崩溃。

基本上我有这样的东西。

发生和需要工作的主要事情是:用户输入的字符串从 entrytext 发送到程序的密集部分(当前包含在一个线程中),密集部分步进 GUI 的进度条,密集部分发送文本消息到没有 GUI 和密集部分崩溃的文本框/记录器。此外,密集部分应该在 GUI 完全加载后立即开始,并在准备好时将启动消息发送到文本框。

密集部分处理其他事情,但不干扰 GUI,例如实际下载和保存图像、浏览和文件 I/O,而且我没有任何问题,反正大部分情况下。

我还阅读了有关队列和线程以及教程的内容,但我似乎还是没有得到它。尤其是我将如何让程序在 GUI 中不断步进进度条,同时向 GUI 发送文本消息(例如,我如何从队列中接近而不必执行非常缓慢且 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)
4

3 回答 3

3

简短的回答是,您不能与工作线程中的小部件进行交互。您唯一的选择是让您的工作线程将某些内容推送到线程安全队列中,并让主线程对其进行轮询。

您不需要任何 while 循环来轮询队列。你已经有一个无限循环——事件循环(例如:mainloop)——所以不需要添加一个额外的循环。

从主线程轮询队列的方式如下所示:

def pollQueue(self):
    <look at the queue, act on the results>
    self.after(100, self.pollQueue)

这样做是安排每 100 毫秒轮询一次队列。当然,您可以将轮询间隔设置为您想要的任何值。

于 2013-05-31T14:16:53.120 回答
1

而不是使用线程,您应该使用 tkinter 方法“之后”在 tkinter 事件循环上设置事件。

例如,当使用画布元素时,我会使用

canvar.after(50, func=keepDoingSomething)

这与javascript函数 setTimeout 类似,它是线程安全的,不会干扰 tkinter gui 线程。

于 2013-05-31T11:49:40.857 回答
0

我认为创建 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()
于 2018-06-25T19:29:19.007 回答