1

I am new to Python and not very experienced with classes, however working on the creation of a tkinter GUI for data processing right now.

As many time consuming processes are happening in the background not visible for the user, I would like to insert a progress-bar that shows the current progress between 0 and 100 as Progress and the processing step Action in the main window

Right now, I have problems to access the bar parameters (value and label/name) outside of the class when the code is doing the data processing in a different function.

Below is a working example of the GUI in Python 3.7

import time
import tkinter as tk
from tkinter import ttk

def ProcessingScript():
    ### UpdateProgressbar(50, 'Halfway there') ###
    time.sleep(2)
    print('Processing takes place here')
    ### UpdateProgressbar(75, 'Finishing up') ###
    time.sleep(2)


class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        container = tk.Frame(self, width=500, height=500)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)
        self.geometry("500x500")
        self.frames = {}
        frame = ProcessingPage(container, self)
        self.frames[ProcessingPage] = frame
        frame.grid(row=0, column=0, sticky="nsew")
        self.show_frame(ProcessingPage)
    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()

class ProcessingPage(tk.Frame):
    def __init__(self, parent, controller, ):
        tk.Frame.__init__(self, parent)
        self.controller = controller

        def PlotData():
                UpdateProgressbar(10, 'Generating...')
                # Execute Main Plotting Function here
                ProcessingScript()
                UpdateProgressbar(100, 'Finished Plot')

        def UpdateProgressbar(Progress, Action):
            progressLabel = tk.Label(self, text=Action).place(x=20, y=440)
            progressBar['value'] = Progress

        progressBar = ttk.Progressbar(self, orient="horizontal", length=200,mode="determinate")
        progressBar.place(x=20, y=470)
        progressBar['value'] = 0
        progressLabel = tk.Label(self, text='Idle...').place(x=20, y=440)

        PlotButton = tk.Button(self, text='Plot Data',command= PlotData)
        PlotButton.place(x=20, y=320)

if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()

In this example, the ProcessingScript function would be in a different file and as an ideal outcome I would like to be able to call the UpdateProgressbar function from anywhere in my other scripts to update the bar.

Note: I am aware that a function inside the __init__ function is not best practice, however I was not able to get it running in any other way as I found no way to connect the results of the UpdateProgressbar function with the progressBar created.

Any help to achieve this and exclude UpdateProgressbar from __init__ is much appreciated.


EDIT:

Below is a working version based on the input from the comments. It might now be very pretty but is currently doing what I expect it do to. Please let me know if you see some possibilities for improvement.

app.update() has to be called after each change in the progress bar to show the error and old labels are deleted with self.progressLabel.destroy(). timeit.sleep() is simply a way of showing the changes and will not be part of the final code.

import time
import tkinter as tk
from tkinter import ttk

def ProcessingScript(self, callback):
    ProcessingPage.UpdateProgressbar(self, 50, 'Halfway there')
    time.sleep(2)
    print('Processing takes place here')
    ProcessingPage.UpdateProgressbar(self, 75, 'Finishing up')
    time.sleep(2)


class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        container = tk.Frame(self, width=500, height=500)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)
        self.geometry("500x500")
        self.frames = {}
        frame = ProcessingPage(container, self)
        self.frames[ProcessingPage] = frame
        frame.grid(row=0, column=0, sticky="nsew")
        self.show_frame(ProcessingPage)
    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()

class ProcessingPage(tk.Frame):
    def __init__(self, parent, controller, ):
        tk.Frame.__init__(self, parent)
        self.controller = controller



        progressBar = ttk.Progressbar(self, orient="horizontal", length=200,mode="determinate")
        progressBar.place(x=20, y=470)
        progressBar['value'] = 0
        self.progressLabel = tk.Label(self, text='Idle...')
        self.progressLabel.place(x=20, y=440)

        PlotButton = tk.Button(self, text='Plot Data',command= self.PlotData)
        PlotButton.place(x=20, y=320)

    def PlotData(self):
        self.UpdateProgressbar(10, 'Generating...')
        app.update()
        time.sleep(2)
        # Execute Main Plotting Function here
        ProcessingScript(self, self.UpdateProgressbar)
        self.UpdateProgressbar(100, 'Finished Plot')
        app.update()

    def UpdateProgressbar(self, Progress, Action):
        self.progressLabel.destroy()
        self.progressLabel = tk.Label(self, text=Action)
        self.progressLabel.place(x=20, y=440)
        progressBar = ttk.Progressbar(self, orient="horizontal", length=200,mode="determinate")
        progressBar.place(x=20, y=470)
        progressBar['value'] = Progress
        app.update()

if __name__ == "__main__":
    app = SampleApp()
    app.mainloop()
Korsii
  • 11
  • 2
  • Does this answer your question? [tkinter gui with progress bar](https://stackoverflow.com/questions/33768577/tkinter-gui-with-progress-bar) – lh1395 Nov 05 '19 at 00:08
  • Also, it's generally not a good idea to use `time.sleep()` in tkinter applications. Use the universal [`after()`](https://web.archive.org/web/20190222214221id_/http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/universal.html) method instead (without a callback function). Also note that the delay is given milliseconds, not seconds. – martineau Nov 05 '19 at 00:11
  • @LH1395 thanks for the suggestion but unfortunately not. In this question, the progress bar is not updated from somewhere completely outside of the class, which is causing the problems in my example. – Korsii Nov 05 '19 at 16:13
  • @Korsii: Change to `ProcessingScript(UpdateProgressbar)` and to `def ProcessingScript(callback)`. Your `def PlotData()` and `def UpdateProgressbar(...` can be a class method as well. Are you aware that your approach blocks the `tkinter.mainloop()` and therefore the `Progressbar` didn't get updated. – stovfl Nov 05 '19 at 17:15
  • @stovfl Thanks! I included `app.update()` at the relevant steps and changed the `PlotData()` and `UpdateProgressbar()` to class methods. After playing around with your input, I think I have a working version as visible in the main question. – Korsii Nov 05 '19 at 22:54
  • @Korsii: First you have to understand [Event-driven programming](https://stackoverflow.com/a/9343402/7414759). You are stacking widgets inside `def UpdateProgressbar(` at every call. ***"I included `app.update()`"***: Don't use `app.update()` use `app.update_idletasks()` instead but even better understand **Event-driven programming**. – stovfl Nov 06 '19 at 07:46

0 Answers0