Update tkinter progress-bar from outside of main function/class - python

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()

Related

Does Tkinter hang on constantly changing the image?

I'm designing a GUI with Tkinter. It has many frames(pages) that by pressing a button in one frame, that frame is hided and next frame is displayed. Each of the button in each frames(pages) has variable images, so I need a function that changes the button image of each frames(pages) being displayed.
I have written the following code and it is working. (Because the number of my frames was high, here I put only the code containing two frames)
According to my design, when we reach the last frame, we automatically return to the first frame after a few seconds.
The problem is that when the system returns to the first frame, the images are no longer changed and the system is disrupted.
At first, I thought it was a hardware problem. So I upgraded my hardware to Raspberry Pi 4 with RAM 4. But then I noticed that even when the system crashes, only 25% of RAM and CPU involved. Therefore, it was not a problem. (I also prepared a fan to reduce its temperature & I bought the fastest microSD for fast data transfer from microSD)
where is the problem from?
The only thing I can think of is that the system hangs because I put the image change function in the main function. But I don't know how to separate the two?
import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
class Project(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.counter = 1
self.animation_direction = 1 # it will add `+1` to self.counter
self.sw = 1000
self.sh = 1800
container = tk.Frame(self)
container.configure(background="#000000")
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.container = container
self.frames = {}
for F in ( PageStart, PageOne):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(PageStart)
def show_frame(self, cont):
self.cont = cont
for frame in self.frames.values():
frame.grid_remove()
frame = self.frames[cont]
frame.configure(background="#000000")
frame.grid()
frame.winfo_toplevel().geometry('%dx%d+%d+%d' % (self.sw,self.sh,0,0))
#frame.counter()
self.change_image()
def twoside(self, inputaddress, startframe, stopframe):
self.input = inputaddress
self.startframe = startframe
self.stopframe = stopframe
self.counter += self.animation_direction
self.address = '%s%s.jpg' % (self.input, self.counter)
if self.counter == self.stopframe:
self.animation_direction = -self.animation_direction
if self.counter == self.startframe:
self.animation_direction = -self.animation_direction
def get_address(self):
return self.address
def change_image(self):
if self.cont == PageStart:
self.frames[self.cont].counter()
self.after(100, self.change_image)
class PageStart(tk.Frame): # PEP8: UpperCaseNames for classes
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.ButtonStyle = ttk.Style()
self.ButtonStyle.configure("Tabedstart.TButton", background="#000000", borderwidth=0)
self.ButtonStyle.map("Tabedstart.TButton", background=[('selected', "#000000")])
self.button = ttk.Button(self, style="Tabedstart.TButton", command=lambda: controller.show_frame(PageOne))
self.button.pack(pady=320)
self.counter()
def counter(self):
self.inputaddress = "/home/pi/Documents/Reference0/"
self.controller.twoside(self.inputaddress, 0, 138)
self.address = self.controller.get_address() # PEP8: lower_case_names for functions/methods and variables
self.photo = Image.open(self.address)
self.photo = ImageTk.PhotoImage(self.photo)
self.button.image = self.photo
self.button.config(image=self.photo)
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.ButtonStyle = ttk.Style()
self.ButtonStyle.configure("Tabedstart.TButton", background="#000000", borderwidth=0)
self.ButtonStyle.map("Tabedstart.TButton", background=[('selected', "#000000")])
self.button = ttk.Button(self, style="Tabedstart.TButton", command=lambda: controller.show_frame(PageStart))
self.button.pack(pady=320)
self.counter()
def counter(self):
self.inputaddress = "/home/pi/Documents/Reference1/"
self.controller.twoside(self.inputaddress, 0, 138)
self.address = self.controller.get_address()
self.photo = Image.open(self.address)
self.photo = ImageTk.PhotoImage(self.photo)
self.button.image = self.photo
self.button.config(image=self.photo)
if __name__ == "__main__":
app = Project()
app.mainloop()

Python Tkinter TypeError: __init__() takes from 1 to 3 positional arguments but 4 were given

I'm getting the error TypeError: __init__() takes from 1 to 3 positional arguments but 4 were given but i have no idea why??
I have one window which then calls a function to open a second window. This second window uses classes and throws up the error. Here are the lines of code that cause the error:
def openadmin():
class SCapp(tk.Toplevel(window)):
def __init__(self, *args, **kwargs):
tk.Toplevel.__init__(self, *args, **kwargs)
(Obviously that's not all the code, just the part which throws up the TypeError)
("window" is the first window which calls the function openadmin() to open the second window)
Any help would be appreciated because this is my coursework!
Edit:
IGNORE PREV CODE ^^
Here is a basic version of working code for the second window which uses frames (I applied some changes already):
import tkinter as tk
class SCapp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage,): #list of frames
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label1 = tk.Label(self,text='This is the start page of 2nd window')
label1.pack()
def openadmin():
if __name__ == "__main__":
app = SCapp()
app.title("SC App")
app.attributes("-topmost", True)
app.mainloop()
And here is the first window (in a separate file) which is supposed to open the previous window:
import tkinter as tk
from testcode import *
def onClick():
if False:
return #some code is here
else:
openadmin()
def WindowOne():
global entid, entpassword
window = tk.Tk()
window.wm_title("Window 1")
label1 = tk.Label(text='This is the first window')
label1.pack()
enter = tk.Button(window, text = 'Click', command = onClick)
enter.pack()
window.mainloop()
WindowOne()
When these are in the same file it works fine, but when they are in separate files (like i need them to be) nothing happens when the button is clicked.

Update tkinter/matplotlib python without mainloop()

Is there a way to manually update Tkinter plots from another file?
I have another file that needs to be continuously polling (while True), so I believe I can't have a mainloop in the plots class since it would block my main class. Is there any way I can call the method "plot_data" from this other file and have the graphs (a,b,c,d) update live? Currently the frame freezes and live updates aren't seen.
However it seems to update in real time when the program is being run in debug mode. Is there a reason why the behavior in execution is different between RUN/ DEBUG in Pycharm?
Below is a view of the Plots class:
class Plots(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "GUI")
# TODO Make sure 'zoomed' state works on all laptops
self.state('zoomed') # Make fullscreen by default
container = tk.Frame(self, bg='blue')
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
frame = GraphPage(container)
self.page = GraphPage
frame.configure(background='black')
self.frame = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame() # Display frame on window
def show_frame(self):
frame = self.frame
frame.tkraise()
def plot_data(self):
# TODO Add exception handling for opening the file
file = open("../storage/data.csv", "r") # Open data file for plotting
t_list = ...
a_list = ...
v_list = ...
a2_list = ...
a.clear()
a.plot(time_list, t_list)
a.set_ylabel('T')
b.clear()
b.plot(time_list, a_list)
b.set_ylabel('A')
c.clear()
c.plot(time_list, v_list)
c.set_ylabel('V')
d.clear()
d.plot(time_list, a2_list)
d.set_xlabel('Time(s)')
d.set_ylabel('A')
class GraphPage(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, bg='red', text="GUI", font=LARGE_FONT, width=400)
label.pack(pady=10, padx=10)
canvas = FigureCanvasTkAgg(f, self)
canvas.draw()
canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
The other file is essentially just:
while(true):
plots.plot_data()

Python Tkinter, modify Text from outside the class

I am trying to access the Text widget defined in class FirstPage from outside of the class.
I tried to solve this problem by creating a new instance of FirstPage, but could not find the right arguments to use. Also tried to use instance of GUI to gain the access, but unsuccessfully.
My problem is solved when I can use text.insert(0.0, t) from outside of the classes. It would help me modify the text displayed with Tkinter by functions that are not directly related with the GUI.
The origin of the code I am trying to use is found: Switch between two frames in tkinter
Also I removed lines that were not necessary for this question..
import Tkinter as tk
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.geometry(self, '580x410')
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
frame = FirstPage(container, self)
self.frames[FirstPage] = frame
frame.grid(row=0, column=0, sticky="nsew")
frame = self.frames[FirstPage]
frame.tkraise()
class FirstPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
text = tk.Text(self , height=25, width=80)
text.grid(column=0, row=0, sticky="nw")
app = GUI()
app.mainloop()
EDIT:
Here is the working code:
import Tkinter as tk
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.geometry(self, '580x410')
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
frame = FirstPage(container, self)
self.frames[FirstPage] = frame
frame.grid(row=0, column=0, sticky="nsew")
frame = self.frames[FirstPage]
frame.tkraise()
page_name = FirstPage.__name__
self.frames[page_name] = frame
def get_page(self, page_name):
return self.frames[page_name]
class FirstPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.text = tk.Text(self , height=25, width=80)
self.text.grid(column=0, row=0, sticky="nw")
app = GUI()
app.get_page("FirstPage").text.insert("1.0", "Hello, world")
app.mainloop()
There's nothing special you need to do. As with any python object, you simply need a reference to the object in order to manipulate it.
The concept in the code you started with is to have a "controller" that controls access to all of the pages, since that object is where the pages are created. You can add a function in the controller that gives you a reference to a page, and then you can use that to call a function on that page.
Here's the changes you need to make to the controller:
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
...
page_name = FirstPage.__name__
self.frames[page_name] = frame
...
def get_page(self, page_name):
return self.frames[page_name]
You also need to modify FirstPage to keep a reference to the widget so that you can access it later:
class FirstPage(tk.Frame):
def __init__(self, parent, controller):
...
self.text = tk.Text(...)
...
From within any other code you can now access the text widget via get_page (but your pages must save a reference to the controller for this to work).
class AnotherPage(tk.Frame):
def __init__(self, parent, controller):
...
self.controller = controller
...
def some_function(self):
...
first_page = self.controller.get_page("FirstPage")
text = first_page.text.get("1.0", "end-1c")
...
first_page.text.insert("end", "some new text\n")
Note that this technique works outside of any GUI pages. In your code, app is the controller, so you can do something like this:
app = GUI()
app.get_page("FirstPage").text.insert("1.0", "Hello, world")

How can I open a new window when the user clicks the button?

How would I create a new window when the user clicks a button (still needs creating)? I have took some code out to make this shorter. I need a button creating and when they hit that button, a new window opens. I haven't created the button because the button has to be linked to the new window. Please help
My imports...
class App:
def __init__(self, master):
self.master = master
# call start to initialize to create the UI elemets
self.start()
def start(self):
self.master.title("E-mail Extranalyser")
self.now = datetime.datetime.now()
tkinter.Label(
self.master, text=label01).grid(row=0, column=0, sticky=tkinter.W)
# CREATE A TEXTBOX
self.filelocation = tkinter.Entry(self.master)
self.filelocation["width"] = 60
self.filelocation.focus_set()
self.filelocation.grid(row=0, column=1)
# CREATE A BUTTON WITH "ASK TO OPEN A FILE"
# see: def browse_file(self)
self.open_file = tkinter.Button(
self.master, text="Browse...", command=self.browse_file)
# put it beside the filelocation textbox
self.open_file.grid(row=0, column=2)
# now for a button
self.submit = tkinter.Button(
self.master, text="Execute!", command=self.start_processing,
fg="red")
self.submit.grid(row=13, column=1, sticky=tkinter.W)
def start_processing(self):
#code here
def browse_file(self):
# put the result in self.filename
self.filename = filedialog.askopenfilename(title="Open a file...")
# this will set the text of the self.filelocation
self.filelocation.insert(0, self.filename)
root = tkinter.Tk()
app = App(root)
root.mainloop()
Use a Toplevel to open a new one. Modify your code as shown below.
self.NewWindow = tkinter.Button(self.master,
text="New Window",
command=self.CreateNewWindow)
def CreateNewWindow(self):
self.top = tkinter.Toplevel()
self.top.title("title")
Take a look at https://www.youtube.com/watch?v=jBUpjijYtCk. Working through this tutorial would probably help you but this specific video shows how to work with multiple pages.
Something like this:
from tkinter import *
class Sample(Tk):
def __init__(self,*args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = Frame(self)
container.pack(side="top", fill="both", expand = True)
self.frames = {}
for F in (MainPage, OtherPage):
frame=F(container, self)
self.frames[F]=frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(MainPage)
def show_frame(self, page):
frame = self.frames[page]
frame.tkraise()
class MainPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
Label(self, text="Start Page").pack()
Button(self, text="other page?", command=lambda:controller.show_frame(OtherPage)).pack()
class OtherPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
Label(self, text="Next Page").pack()
Button(self, text="back", command=lambda:controller.show_frame(MainPage)).pack()
app = Sample()
app.mainloop()

Categories

Resources