0

The following code should update the GUI while at the same time running a calculation. IN this case self.after() is not a solution as the calculation will be ongoing for a long time (and also there has to be time.sleep(n) in the calculation, but this should not stop the GUI from working (i.e. user can still adjust sliders). So it has to be done via multiple threads. The following code is what I would like to suggest, but it doesn't work. Any suggestions what I got wrong?

import Tkinter as tk
import ttk
import time
import threading

class GUI(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.pack()
        self.i=0
        self.var1 = tk.StringVar()
        self.var2 = tk.StringVar()
        self.var3 = tk.StringVar()
        self.var4 = tk.StringVar()
        self.var5 = tk.StringVar()
        self.var6 = tk.StringVar()
        self.var7 = tk.StringVar()
        tk.Label(self, textvariable=self.var1,justify='left').pack()
        tk.Label(self, textvariable=self.var2).pack()
        tk.Label(self, textvariable=self.var3).pack()
        tk.Label(self, textvariable=self.var4).pack()
        tk.Label(self, textvariable=self.var5).pack()
        tk.Label(self, textvariable=self.var6).pack()
        tk.Label(self, textvariable=self.var7).pack()


        p1 = tk.Scale(master, from_=1, to=20, orient=tk.HORIZONTAL).pack()
        p2 = tk.Scale(master, from_=1, to=20, orient=tk.HORIZONTAL).pack()
        p3 = tk.Scale(master, from_=1, to=20, orient=tk.HORIZONTAL).pack()
        p4 = tk.Scale(master, from_=1, to=20, orient=tk.HORIZONTAL).pack()
        p5 = tk.Scale(master, from_=1, to=20, orient=tk.HORIZONTAL).pack()

        self.progress = ttk.Progressbar(self, orient="horizontal",length=100, mode="determinate")
        self.progress.pack()

    def calculation(self):
        while True:
            # complex calculations that last 30 minutes in total
            self.i += 1
            time.sleep(1)
            max=1000
            self.var1.set("Value1: "+str(self.i))
            self.var2.set("Value2: "+str(self.i+100))
            self.var3.set("Value3: "+str(self.i+100))
            self.var4.set("Value4: "+str(self.i+100))
            self.var5.set("Value5: "+str(self.i+100))
            self.var6.set("Value6: "+str(self.i+100))
            self.var7.set("Value7: "+str(self.i+100))
            self.progress["value"] = int(round(self.i*100/max))
            #self.update()  # no need, will be automatic as mainloop() runs
            #self.after(1, self.calculation) not necessary anymore as everything is in a main loop


app=GUI()

t1 = threading.Thread(target=app.calculation, args=[])
t2 = threading.Thread(target=app.mainloop, args=[])
t1.start()
t2.start()
t1.join()
t2.join()
print ("thread finished...exiting")
Nickpick
  • 6,163
  • 16
  • 65
  • 116
  • Multiple threads cannot interact with Tkinter objects. Your claim of needing the call `time.sleep()` in the calculation is utter nonsense. A good way to safely multi-thread in a Tkinter application is to use `Queue` object(s) to communicate (pass data) from auxiliary threads to the main one. Typically the `self.after()` is used to schedule periodic retrieval of the data the other thread(s) are generating. – martineau Aug 23 '15 at 16:51
  • No, it's not utter nonsense that I need the sleep function. The above example is a simplification of my real problem, and in my real problem there are several sleep functions that are necessary (a poker bot that plays by itself and needs to make sure the program has a human touch) – Nickpick Aug 23 '15 at 16:57
  • If you really need to put a time delay in the "calculation", the docs say you can use `self.after()` in place of `time.sleep()` but only pass it a single time value argument. – martineau Aug 23 '15 at 17:11
  • That wouldn't work as the calculation itself takes a long time and during that time the GUI would be unresponsive. The only solution is as below with a second thread. – Nickpick Aug 23 '15 at 17:16
  • Call `self.after()` with a single argument as described would not make the GUI unresponsive. I suggest you try it. – martineau Aug 23 '15 at 17:19
  • I have tried it and the sliders could not be moved whenever in the calculation function time.sleep was called – Nickpick Aug 23 '15 at 17:37

1 Answers1

1

First of all you need to learn DRY rule.

You have to run app.mainloop() directly. Something like

t1 = threading.Thread(target=app.calculation, args=[])
t1.start()
app.mainloop()
  • Thanks, that works. Any suggestion how I can then end the program when the GUI is closed? I.e. terminate the other thread? – Nickpick Aug 23 '15 at 16:59
  • Hard solution is make thread non endless. Easy solution is to kill both gui and thread with sys.exit – Speaking Code Aug 23 '15 at 17:02