Multiprocessing in Tkinter - python-3.x

I have this following code where I am calling the function from the button which takes input from the widgets. It's a function which takes about 4 minutes to run and for solving the 'not responding' problem of tkinter window, I want to get the func process running on the different core and terminate as it can be called again via the application running on mainloop with a different argument. I read the documentation of multiprocessing and Pool seemed to be the choice here but I don't know how to frame it here. Tried a few things with error.
class database(tk.Tk):
def __init__(self, *args, *kwargs):
tk.Tk.__init__(self, *args, **kwargs):
container= tk.Frame(self, width=1000, height=1000)
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 ( msPage): #many more pages here
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(msPage)
def show_frame(self,cont):
frame = self,frames[cont]
frame.tkraise()
def MarkS(msVar):
ms.func(msVar.get()) # func takes about 4 mins
class msPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
msVar = StringVar()
msCal = Calendar(self, selectmode='day'
textvariable=msVar).pack(fill="both", expand=True)
# button2 calls MarkS
button2 = ttk.Button(self,
text="Get Data",
command=lambda: MarkS(msVar)).pack(pady=30, padx=10)
app = database()
app.geometry("1066x568")
app.minsize(width=670, height=550)
app.mainloop()

This is a standalone example that might help you get started:
from multiprocessing import Process
class ms_var_class():
value = None
def __init__(self, value):
self.value = value
def get(self):
return self.value
# This is to simulate type(ms)
class ms_class():
process = None
# This is for simulating your long running function
def func(self, ms_var):
print(ms_var)
def MarkS(msVar):
ms.process = Process(target=ms.func, args=(msVar.get(),))
ms.process.start()
# Call ms.process.join() later
ms_var = ms_var_class("bogus")
ms = ms_class()
MarkS(ms_var)
if ms.process is not None:
ms.process.join()

Thank you Mike for commenting and helping me get towards the solution
Here's how I did it
Create a global queue
q= queue.Queue()
Defined the function MarkS within the class msPage
def MarkS(msVar):
q.put([7 , datetime.datetime.strptime(msVar.get(), '%x').strftime('%d-%b-%Y').upper(),0])
ThreadedTask(q).start()
7 is the unique number of the 9 functions that I had for each page (class)
Made another class of ThreadedTask as in the link below
Tkinter: How to use threads to preventing main event loop from "freezing"
class ThreadedTask(threading.Thread):
def __init__(self,queue):
threading.Thread.__init__(self)
q = queue
def run(self):
items = q.get()
if items[0]==7:
ms.func(items[1])
ThreadedTask(q).start() creates a constructor object and the run function automatically starts depending on the argument here which is of course the 1st element of q which is a list of 3 in my case. It is detected and the desired function is run in another thread which prevents the tkinter window from closing.
Hope this helps! :)

Related

Calling a Method of another class with a lambda expression in python

I am trying to create a little GUI with multiple pages. The First page has a button which raises the second page and changes the label text on the second page. However, I fail to call the method of the second page which is supposed to change the text. Can somebody tell me, why I get the following error when calling the method: SecondPage.changeLabel()?
TypeError: changeLabel() missing 1 required positional argument: 'self'
UPDATE:
import tkinter as tk
class ExampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack()
self.frames = {}
for F in (FirstPage, SecondPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(FirstPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def get_page(self, frame_class):
return self.frames[frame_class]
class FirstPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
page = controller.get_page(SecondPage)
self.buttonFP = tk.Button(self, text="Next Page",
command=lambda : [f for f in [page.changeLabel(),
controller.show_frame(SecondPage)]])
self.buttonFP.pack()
class SecondPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.label = tk.Label(self, text="Test")
self.label.pack()
def changeLabel(self):
"""change text of label"""
newLabel = "changed"
self.label.configure(text=newLabel)
if __name__ == "__main__":
app = ExampleApp()
app.mainloop()
You get the error because you are trying to call the method on the class rather than on the instance of the class.
Since your architecture has a controller, you can modify the controller to have a function that returns the instance of a page. You can then use the instance to call methods on that page.
Example:
class ExampleApp(tk.Tk):
...
def get_page(self, frame_class):
return self.frames[frame_class]
Later, you can use this method to get the instance of the page, and use the instance to call the method:
page = self.controller.get_page(SecondPage)
page.changeLabel()

Problems with disabling Menu in tkinter

I am having a problem Disabling Menu's in my Tkinter App. Only I want to show them only on certain pages, I have tried to disable them in the init function of my app, but that didn't work, I have tried to disable them in the show frames function of my app but that didn't work and I have tried to disable them through the start page class of my app, in that I have tried to use self.menubar , parent.menubar and controler.menubar; But nothing seems to work. I would just code them on each individual page but this is the only way I found to even show them on any page. Any help would be much appreciated.
class App(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)
menubar = tk.Menu(container)
file = tk.Menu(menubar, tearoff = 0)
file.add_command(label='Exit', command = quit)
menubar.add_cascade(label='File',menu=file)
tk.Tk.config(self, menu=menubar)
self.frames = {}
for F in (StartPage):
frame = F(container,self)
self.frames[F] = frame
frame.grid(row=0,column=0,sticky='nsew')
page = F
self.show_frame(StartPage)
def show_frame(self,cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
menubar.add_cascade(label='File',state=DISABLED)
label = ttk.Label(self, text='Start', font = LARGE_FONT).pack()
main = App()
main.mainloop()
This isn't a tkinter problem, it's how python works - to modify an object you need a reference to the object. This is true for tkinter widgets just as it is true for dictionaries, strings, or any other object in python.
In this case the object is part of the main application. You first need to save a reference to it:
class App(tk.Tk):
def __init__(self,*args,**kwargs):
...
self.menubar = tk.Menu(container)
...
In later code, you can now access this menu from the controller variable, which is a reference to the instance of App:
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
controller.menubar.entryconfigure('File',state="disabled")
However, that likely won't do what you want since that code runs when the program starts up. I'm guessing you want the code to run when the page is selected. To do that, follow the instructions in this answer to the question How would I make a method which is run every time a frame is shown in tkinter
1st lets clean up this to reflect the PEP8 standard more closely for readability reason.
Added in the imports as those should have been in your example.
We need to replace the parenthesis in (StartPage) with brackets like this [StartPage]. This is because a single value in parenthesis is treated like a single value without parenthesis so you are trying to iterate over an object and not a list.
You should be more specific on what you are configuring. Instead of calling tk.tk.config() do self.config(menu=self.menubar).
for your StartPage class you are not going to be able to do anything to the menubar for several reason. First you do not define the menubar as a class attribute in your main tk class. 2nd you are not properly calling your master and container so I have changed the argument names in the init to better reflect what we are working with so you can see what you need to call.
Lastly the crux if the issue for disabling the menu you need to use entryconfig()
See below cleaned up code:
import tkinter as tk
import tkinter.ttk as ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
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.menubar = tk.Menu(container)
self.config(menu=self.menubar)
file = tk.Menu(self.menubar, tearoff=0)
file.add_command(label='Exit', command=quit)
self.menubar.add_cascade(label='File', menu=file)
self.frames = {}
for F in [StartPage]:
frame = F(self, container)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky='nsew')
page = F
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, master, container):
tk.Frame.__init__(self, container)
master.menubar.entryconfig('File', state='disabled')
ttk.Label(self, text='Start').pack()
main = App()
main.mainloop()

Tkinter window does not update correctly when running

I'm trying to make a Tkinter window show updated data but it only pops up after 13 seconds with just the last value. I want it to pop up and change the values on screen. Mind you, the big goal of this code is to take data from a database (which updates every 3 seconds) and show the data onscreen, while running continuously, so if the answer could include some pointers on the "after" or "update" functions it would be greatly appreciated!
Here is what I have so far.
from tkinter import *
import time
class GUI(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
Tk.wm_title(self, "Main Window")
self.container = Frame(self)
self.container.pack(side=TOP, fill=BOTH, expand=TRUE)
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
self.frames = {}
self.frame = StartPage(self.container, self)
self.frames[StartPage] = self.frame
self.frame.grid(row=0, column=0, sticky=NSEW)
self.show_frame(StartPage)
def show_frame(self, controller):
frame = self.frames[controller]
frame.tkraise()
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.label = Label(self, text="Current ID:\n")
self.label.pack(padx=10, pady=10)
self.data_label = Label(self)
self.data_label.pack()
self.update_data()
def update_data(self):
var1 = StringVar()
for i in range(10):
var1.set(str(i))
self.data_label.config(text=str(i))
time.sleep(1)
main = GUI()
main.mainloop()
I can give you a partial answer. The reason you don't see any updates is that time.sleep() suspends the process and does not allow for tkinter to repaint the window.
You don't use the label textvariable correctly. Specify it in the label and the label will change as you change the textvariable.
You use both pack and grid at the same time which may cause problems.
I have not used after() in a class before so I don't know how to work it, but this example should give you some pointers. I'm keeping console printouts in the code so I can see what it does.
from tkinter import *
import time
class GUI(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
Tk.wm_title(self, "Main Window")
self.container = Frame(self)
self.container.pack(side=TOP, fill=BOTH, expand=TRUE)
self.frames = {}
self.frame = StartPage(self.container, self)
self.frames[StartPage] = self.frame
self.frame.pack(side=TOP, fill=BOTH, expand=TRUE)
self.show_frame(StartPage)
def show_frame(self, controller):
frame = self.frames[controller]
frame.tkraise()
frame.update_data()
print('Done')
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.parent = parent
self.label = Label(self, text="Current ID:\n")
self.label.pack(padx=10, pady=10)
self.var1 = StringVar()
self.data_label = Label(self, textvariable=self.var1)
self.data_label.pack()
self.update_idletasks() # Calls all pending idle tasks
def update_data(self):
if not self.var1.get(): self.var1.set('0')
iteration = int(self.var1.get())
print('iteration', iteration)
if iteration < 3:
iteration = iteration + 1
self.var1.set(str(iteration))
self.update_idletasks() # Calls all pending idle tasks
time.sleep(1)
self.update_data()
main = GUI()
main.mainloop()
You will have to research after() yourself as I can't help you there.

Python MySQLdb Object Oriented

I am trying to establish connection with MySQL database using Python
on RaspberryPi. I am new to object oriented python. In this code I am
taking a value as input and trying to insert it in the database. Due
to some issue, The insert query is not getting executed and the
Exception part is getting executed. Please help me in identifying my
mistake.
import tkinter as tk
import MySQLdb
class ImgComp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self,width=320, height=209)
container.pack(side="top")
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
container.grid_propagate(False)
try:
self.db = MySQLdb.connect("localhost","root","root","sample")
self.c= self.db.cursor()
except:
print ("I can't connect to MySql")
self.frames = {}
for F in (InputPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(InputPage)
def show_frame(self, cont):
frame = self.frames[cont]
if ((cont == PageOne) | (cont == InputPage) | (cont == OutputPage)):
self['menu'] = frame.menubar
else:
self['menu'] = ''
frame.tkraise()
def input_submit(self, input_value):
in_val = float(input_value)
self.sql = "INSERT INTO input_delay_master (input_delay) VALUES (%s)"
try:
c.execute(self.sql,(in_val))
self.db.commit()
except:
print("Except")
self.db.rollback()
class InputPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.menubar = tk.Menu()
filemenu = tk.Menu(self.menubar, tearoff=0)
filemenu.add_command(label="Output Reset Delay", command=lambda: controller.show_frame(OutputPage))
self.menubar.add_cascade(label="Settings", menu=filemenu)
label = tk.Label(self, text="Input Image Delay")
label.pack(pady=10,padx=10)
input_delay = tk.Entry(self)
input_delay.pack(pady=10)
submit1 = tk.Button(self, text = "Submit", command=lambda: controller.input_submit(input_delay.get()))
submit1.pack()
I get the following output
34.56 # This is the the value I entered in Entry field
Except
The problem is, the insert query present inside the input_submit button is not getting executed, rather Expection part is executed. Please help me getting the way out to successfully execute the insert query.
except expects tuple with arguments - even if you have only one argument - but (in_val) is not tuple, it is single element. To create tuple you need comma inside (in_val, )

How to check whether a thread has ended in Python

I am working on my first app, and have been following some tutorials on how to setup a working GUI with tkinter and python. My application starts by playing a splash screen animation, and once it completes, switches to the "UserSetup" frame. I am still busy trying to re-code the function that plays this video so that I can call upon it as a method (or whatever works). What I wanted to know is, if there is a way to check once the thread which plays this video has stopped? Any other animations played now run concurrently, and I don't want that. I basically want it to only continue with the next set of animations or whatever once the splash screen animation has stopped.
It is quite a bit of code....
# ======================
# imports
# ======================
import tkinter as tk
import imageio
import threading
from PIL import Image, ImageTk
LARGE_FONT = ("Segoe UI", 18)
# =======================
# INITIALISATION - MAIN
# =======================
class MyApp(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 (SplashScreen, UserSetup, TimerSetup):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(SplashScreen)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
# ======================
# PAGES
# ======================
""" To add new pages, just create a page class which inherits from tk.Frame,
add it to the dictionary, and add buttons for navigation """
class SplashScreen(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
video_name = "img//splash.avi" # This is your video file path
video = imageio.get_reader(video_name)
def stream(label):
for image in video.iter_data():
frame_image = ImageTk.PhotoImage(Image.fromarray(image))
label.config(image=frame_image)
label.image = frame_image
controller.show_frame(UserSetup)
def forget():
my_label.pack_forget()
my_label = tk.Label(self)
my_label.pack()
my_label.after(ms=15000, func=forget)
thread = threading.Thread(target=stream, args=(my_label,))
thread.daemon = 1
thread.setName("splash")
thread.start()
print(thread.getName())
class UserSetup(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
class TimerSetup(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# ======================
# INITIALISATION (MAIN)
# ======================
app = MyApp()
#app.overrideredirect(True)
app.mainloop()
I have used is_alive() in the past.. In the form of:
while thread_name.is_alive():
pass
This will wait for the thread to finish before continuing onto the next part of your script.
Hope this helps!
Luke

Resources