Creating "Stop" button using tkinter & classes - python

I am working on a simulation code, and I had separated by classes the simulation and the interface. I was looking for a stop button to stop my long simulation code, but the interface I have created "Is not responding" until:
the whole simulation stops, or
I get an error, or
I stop it using Spyder "stop command"*
The structure is the following:
class interface(tk.Tk):
def __init__(self):
super().__init__()
self.startInterface()
def startInterface():
self.title('Simulation') # Window's title
self.minsize(450, 180) # Window's size
.
.
.
frm_Mediumdown = tk.Frame(self, bd=7, relief='flat')
frm_Mediumdown.grid(row=3, column=0, padx=0, pady=0)
BTN_stop = tk.Button(frm_Mediumdown, text='Stop', command = self.stop)
BTN_stop.grid(row=1, column=2, sticky=tk.W, padx=4)
BTN_simulate = tk.Button(frm_Mediumdown, text='Simulate',
command = self.Simulate)
BTN_simulate.grid(row=1, column=0, sticky=tk.W, padx=4)
def Simulate(self):
# here this function call another class which start the long simulation code
Simulation.starts(a, b, etc)
def stop(self):
# here it should appear the code option to stop the simulation
class Simulation():
# Long code which do the simulation
.
.
.
if __name__ == '__main__':
print('Start')
app = interface()
app.mainloop()
I have tried to put a global option in the stop and Simulate functions def inside the interface class, but it doesn't work, it has the same problem when I launch the code.
And I've also tried the threading option and the daemon thread, but I didn't get any response.

try doing this instead:
def simulate(self):
simulation = Simulation()
self.after(1000, simulation.starts, a, b, etc)
however does the starts() method run in loop? also try (not suggested) adding root.update() somewhere in the starts() method (root, is whatever is Your root (like interface))

Related

Exit Python Tkinter app cleanly while also using "wait_variable" function on a button in an "after" loop

I have a problem similar to this post: Exit program within a tkinter class
My variation on the problem involves the wait_variable being used on a button to control "stepping forward" in an app, but also allowing the app to close cleanly.
See my code below:
# To see output unbuffered:
# python -u delete_win_test.py
import tkinter as tk
from tkinter import *
class GUI(Tk):
def __init__(self):
super().__init__()
# Close the app when the window's X is pressed
self.protocol("WM_DELETE_WINDOW", self.closing)
# When this var is set to 1, the move function can continue
self.var = tk.IntVar()
# Close the app if the button is pressed
button = tk.Button(self, text="Exit",
command=self.destroy)
button.place(relx=.5, rely=.5, anchor="c")
# Step forward
self.step_button = tk.Button(self, text="Step",
command=lambda: self.var.set(1))
self.step_button.place(relx=.5, rely=.75, anchor="c")
def move(self):
print("doing stuff") # simulates stuff being done
self.step_button.wait_variable(self.var)
self.after(0, self.move)
def closing(self):
self.destroy()
app = GUI()
app.move()
app.mainloop()
The window shows correctly
"Stepping forward" works because "doing stuff" prints to terminal on button click
Exiting the window by both pressing X or using the "exit" button both work
The problem: the Python app never exits from the terminal and requires a closing of the terminal.
How can I make the Python program exit cleanly so the user does not need to close and re-open a new terminal window?
Related references for animation, etc:
Animation using self.after: moving circle using tkinter
Button wait: Making Tkinter wait untill button is pressed
The original "exit" code: Exit program within a tkinter class
UPDATE (the solution):
(Credit to both response answers below)
# Close the app if the button is pressed
button = tk.Button(self, text="Exit",
- command=self.destroy)
+ command=self.closing)
button.place(relx=.5, rely=.5, anchor="c")
# Step forward
...
def closing(self):
self.destroy()
+ self.var.set("")
+ exit(0)
This allows the native window's "X" to close the window and the Tk button to close the window while still closing the Python app cleanly in the terminal.
Your closing function needs to set the variable to cause the app to stop waiting.
def closing(self):
self.destroy()
self.var.set("")
in the closing function, you need to call exit to exit the program.
def closing(self):
self.destroy() #closes tkinkter window
exit(0) #exits program

Python Tkinter commands priority

I have faced a strange problem in my program.
from tkinter import *
import time
class Window:
def __init__(self):
self.root = Tk()
self.root.title('Test')
self.root.geometry('400x500')
self.root.resizable(FALSE, FALSE)
self.root.configure(bg ='#1A181B')
def draw_widgets(self):
Button(self.root, text='Start', font='Verdana 17',command = self.start_bot).grid(row=1, column=1)
def run(self):
self.draw_widgets()
self.root.mainloop()
def start_bot(self):
Button(self.root, text='Start', font='Verdana 17', command=self.start_bot).grid(row=2, column=1)
time.sleep(4)
print('a')
win = Window()
win.run()
win.draw_widgets()
As you can see after pressing a button, I want to create another button, then wait for 4 seconds, then print 'a', but it is doing another thing: 1) Waiting for 4 seconds 2) Printing 'a' 3) Creating button.
Please, how I can fix this, I really need your help.
When you use time.sleep() the application suspends processing until the time period is done. This includes updating the GUI changes. To allow the changes to take effect before sleep is started you have to tell the application to do that with update_idletasks(). See example:
def start_bot(self):
Button(self.root, text='Start', font='Verdana 17',
command=self.start_bot).grid(row=2, column=1)
self.root.update_idletasks() # Update GUI changes
time.sleep(4)
print('a')
Have a look at the after() function, which does not suspend processing but schedules something for a later time. This may often be a good function to use instead of sleep.

Python program will only run from IDLE

This is my first run at python and i am by no means a code writer. I have a project that I needed to write a GUI to command the arduino and after much googling and piecing together code I made this program which works perfectly when ran from IDLE. When I launch it from windows (double click it) or from linux (via command line python3 filler.py) it opens and closes like there is an error. I can launch other python programs by same methods with no problems. Where as its not an issue to launch it out of IDLE to make the hardware operate I just cant give up as I would like to get more knowledgeable with python for future projects. Any help would be greatly appreciated.
import tkinter as tk
import serial as s
import time as t
from tkinter import *
class Action:
def __init__(self):
def on():
ser.write(b'7')
def exit():
ser.close() # close serial port
quit()
self.window = Tk()
self.window.title("Moore Speciality Brewing")
self.window.geometry('640x480')
self.lbl = Label(self.window, text="Can Filler",fg='black',font=(None, 15))
self.lbl.place(relx=0.24, rely=0.10, height=50, width=350)
bo = Button(self.window, text="Fill Can", width=10 ,bg='red' ,command=on)
bo.place(relx=0.34, rely=0.30, height=40, width=200)
ext = Button(self.window, text="Exit", width=10, bg='white', command=exit)
ext.place(relx=0.34, rely=0.50, height=40, width=200)
class Prompt(tk.Tk):
def __init__(self):
global comm
comm = None
tk.Tk.__init__(self)
self.geometry('640x480')
self.label = tk.Label(self, text="Comm Port",fg='black',font=(None, 15))
self.entry = tk.Entry(self)
self.button = tk.Button(self, text="Get", command=self.on_button)
self.entry.place(relx=.5, rely=.5,anchor="center" )
self.label.place(relx=.5, rely=.44,anchor="center")
self.button.place(relx=.5, rely=.56,anchor="center")
def on_button(self):
comm = self.entry.get()
global ser
ser = s.Serial(comm, 9600, timeout=0) # check your com port
t.sleep(2)
Action()
self.destroy()
Prompt()
You need to call the mainloop.
Remove the call to Prompt() (last line) and substitute it with something like this (at the bottom of the script):
if __name__ == "__main__":
prm = Prompt()
prm.mainloop()
Here more on tkinter mainloop
you have already imported time in your code.
just use t.sleep(60) at the end of your code to make the cli wait for you to see if there is an error and let you debug.
at the end Prompt() is not correct. use something like this:
myPrompt = Prompt()
myPrompt.mainloop()
this part actualy calls the tkinter gui.

Tkinter GUI stuck till end of the task when pressing a button

When I press the "start program" button, it starts a 5 second task and blocks the GUI.
As i understand, I need to use Threading so each button will work independently from the GUI.
I've been stuck for almost a month already, can someone show me how can execute def start_Button(self): function using threading?
from tkinter import *
import time
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.var = IntVar()
self.master.title("GUI")
self.pack(fill=BOTH, expand=1)
quitButton = Button(self, text="Exit", command=self.client_exit)
startButton = Button(self, text="Start Program", command=self.start_Button)
quitButton.grid(row=0,column=0)
startButton.grid(row=0, column=2)
def client_exit(self):
exit()
def start_Button(self):
print('Program is starting')
for i in range (5):
print(i)
time.sleep(1)
root = Tk()
root.geometry("200x50")
app = Window(root)
root.title("My Program")
root.mainloop()
There are a lot of important questions to ask before you jump into threading for the first time, but by and large the most important question is "how do I want to communicate between my threads?" In your minimal example you don't require any communication at all, however in your real code start_Button may be doing some Work and returning data back to the GUI. If that's the case, you have more work to do. Please clarify that as a comment if that's the case.
For the minimal example, it's actually quite easy.
class Window(tkinter.Frame):
# the rest of your GUI class as written, but change...
def start_Button(self):
def f():
# this is the actual function to run
print('Program is starting')
for i in range (5):
print(i)
time.sleep(1)
t = threading.Thread(target=f)
t.start()

python running task in the background while allowing tkinter to be active

When my program executes the python GUI freezes. Here is my main code. Can I get some help in doing threading? So the execution happens in the background and I can still be able to use the "x" button in the GUI if I want to end the execution? Currently I just ask the user to close the cmd to end the program.
if __name__ == "__main__":
root = Tk()
root.title('Log')
root.geometry("400x220")
font1=('times', 15)
font2=('times', 10)
#Label inside root
Label(root, relief=GROOVE, font=font2, text="level").pack()
variable = StringVar(root)
variable.set("INFO") # default value
w = OptionMenu(root, variable, "CRITICAL", "DEBUG")
w.pack()
Button(root, font=font1, background= "yellow", text='START',command=main).pack()
Label(root, text="To end just close the CMD window").pack()
root.mainloop()
UPDATE: Turns out the Button callback was autorunning launch because the function object wasn't being set as the callback, the called function itself was. The fix is to replace the callback lambda: spawnthread(fcn) so that a function object is set as the callback instead. The answer has been updated to reflect this. Sorry for missing that.
The GUI mainloop will freeze when you try to run some other function, and has no way to restart itself (because it's frozen.)
Let's say the command you'd like to run alongside the GUI mainloop is myfunction.
Imports:
import time
import threading
import Queue
You need to set up a ThreadedClient class:
class ThreadedClient(threading.Thread):
def __init__(self, queue, fcn):
threading.Thread.__init__(self)
self.queue = queue
self.fcn = fcn
def run(self)
time.sleep(1)
self.queue.put(self.fcn())
def spawnthread(fcn):
thread = ThreadedClient(queue, fcn)
thread.start()
periodiccall(thread)
def periodiccall(thread):
if(thread.is_alive()):
root.After(100, lambda: periodiccall(thread))
You then want the widget calling the function to instead call a spawnthread function:
queue = Queue.Queue()
Button(root, text='START',command=lambda: spawnthread(myfunction)).pack() #<---- HERE
N.B. I'm adapting this from a multithreaded tkinter GUI I have; I have all my frames wrapped up in classes so this might have some bugs since I've had to tweak it a bit.

Categories

Resources