3

When using the Tkinter .after method, the code continues passed without waiting for the callback to complete.

import tkinter as tk
import tkinter.ttk as ttk
import time
from datetime import datetime

global i
i = 0
global j
j = 0

def SomeFunction():
    global i
    for num in range(10):
        i+=1
        x = barVar.get()
        barVar.set(x+5)
        histrun_mainWindow.update()
        time.sleep(2)

def SecondFunction():
    global j
    for num in range(10):
        j+=1
        x = barVar.get()
        barVar.set(x+5)
        histrun_mainWindow.update()
        time.sleep(2)

def Load(run_date):
    histrun_mainWindow.after(50, SomeFunction)
    histrun_mainWindow.after(50, SecondFunction)
    global i, j 
    print 'Number is :', i + j

histrun_mainWindow = tk.Tk()
run_date = datetime.today().date()
barVar = tk.DoubleVar()
barVar.set(0)
bar = ttk.Progressbar(histrun_mainWindow, length=200, style='black.Horizontal.TProgressbar', variable=barVar, mode='determinate')
bar.grid(row=1, column=0)
button= tk.Button(histrun_mainWindow, text='Run for this date ' + str(run_date), command=lambda:Load(run_date))
button.grid(row=0, column=0)
histrun_mainWindow.mainloop()

This example shows what is happening. The .after() calls the Load() function but doesn't wait for Load() to complete, it goes straight on to the next line.

I want i to print as 10 but because the .after() doesn't wait for Load() to finish it's additions, i prints as 0

The progress bar continues to update so I know that Load was called as it continues in the background after i is printed

J K
  • 33
  • 5
  • 1
    Why do you need to delay the call to `SomeFunction()` with `after()`- if you call it directly, it will behave as you wish. – Reblochon Masque Apr 26 '19 at 14:17
  • In my real code the function `Load()` contains multiple `after()` methods to gather data. Each depends on the previous function having completed so that they can use data it has called/calculated. I have added more detail to my example to clarify. – J K Apr 26 '19 at 14:22
  • 1
    If I call the functions directly without `after()`, the progress bar doesn't update - the window freezes until all functions have completed. Maybe there is a better way to get this functionality? – J K Apr 26 '19 at 14:35
  • @JK: *"a better way to get this functionality"*: Read [use threads to preventing main event loop from “freezing”](https://stackoverflow.com/a/16747734/7414759). – stovfl Apr 26 '19 at 15:05
  • @stovfl thank you for that link. I'm having difficulty understanding it though, I'm only used to very basic multithreading and I've never worked with classes before. Do I need to create a separate class for `SomeFunction()` and `SecondFunction()` and how do I call a class when calling a function? – J K Apr 26 '19 at 15:33

1 Answers1

1

Question: the progress bar doesn't update - the window freezes until all functions have completed

Use a Thread to prevent main loop from freezing.
Your functions - SomeFunction, SecondFunction - may also in global namespace.
Then you have to pass self.pbar as paramter, e.g. SomeFunction(pbar): ... f(self.pbar).


Note:
You see a RuntimeError: main thread is not in main loop
if you .destroy() the App() window while the Thread is running!

import tkinter as tk
import threading

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        btn = tk.Button(self, text='Run', 
                              command=lambda :threading.Thread(target=self.Load).start())
        btn.grid(row=0, column=0)
        self.pbar = ttk.Progressbar(self, maximum=2 *(5 * 5), mode='determinate')
        self.pbar.grid(row=1, column=0)

    def SomeFunction(self):
        for num in range(5):
            print('SomeFunction({})'.format(num))
            self.pbar['value'] += 5
            time.sleep(1)
        return num

    def SecondFunction(self):
        for num in range(5):
            print('SecondFunction({})'.format(num))
            self.pbar['value'] += 5
            time.sleep(1)
        return num

    def Load(self):
        number = 0
        for f in [self.SomeFunction, self.SecondFunction]:
            number += f()
        print('Number is :{}'.format(number))

if __name__ == "__main__":
    App().mainloop()

Output:

SomeFunction(0)
SomeFunction(1)
SomeFunction(2)
SomeFunction(3)
SomeFunction(4)   
SecondFunction(0)
SecondFunction(1)
SecondFunction(2)
SecondFunction(3)
SecondFunction(4)
Number is :8

Tested with Python: 3.5 *)Could not test with Python 2.7

stovfl
  • 14,998
  • 7
  • 24
  • 51
  • That's perfect thanks very much for your help! I was able to use this and passing the `self.pbar` parameter opens up more uses when threading – J K May 02 '19 at 11:54