Everyone! I first of all apologize for my lack of coding knowledge, I am currently attempting to learn Python on my own for "fun." My only formal education comes from a high-school Java AP course taken years ago.
I am currently using Python version 3.6 on the Windows 10 operating system, utilizing the PyCharm IDE.
On run my Tkinter GUI based application automatically executes an exit function that I defined under a class. The desired effect is for the window to close only when the user clicks the "terminate" button within the GUI window.
My code so far is as follows:
import webbrowser
import tkinter as ttk
from PIL import ImageTk, Image
##Application main window setup.
window = ttk.Tk()
window.maxsize(width=200, height=200)
window.minsize(width=200,height=200)
window.config(bg=("black"))
window.title("Hello World")
##Set a 'class' for exit function of application.
class Exit():
##Defines the countdown timer and sets parameters that need to be satisfied before exit.
def timer(self):
countdown = 3
self.x = int
for self.x in reversed(range(0,countdown + 1)):
print(self.x)
##When 'x' reahces -1 the application exits.
if self.x > -1:
print("Bye!")
window.destroy()
##Otherwise a label displaying a text message appears.
else:
swell = ttk.Label(text=("'Hello World!'"),bg=("black"),fg=("white"),font=("Times New Roman",12,"bold"))
swell.place(x=50,y=50)
##Retrieve the defined 'timer' function from the 'Exit' class.
exit=Exit()
exit.timer()
##Button with attahced command to execute the exit of application via user input.
quitButton=ttk.Button(
window,text=("Terminate"),bg=("red"),fg=("white"),font=("bold"),width=20,height=1,anchor=ttk.S,command=lambda: exit)
quitButton.place(x=6,y=150)
window.mainloop()
Any form of help is appreciated, and I thank you in advance.
*As a side note I can successfully issue a command from a button, however the retrieved function is only one line. It seems I cannot handle multiple lines of code.
I think what is happening is that you are destroying the window in the timer class method. After your for loop, x will equal 0. Therefore it is more than -1, and the window class is destroyed. Quitbutton trys to use window but it has been destroyed.
In the output I assume you are seeing 'Bye'
I got the correct result with the following:
import tkinter as ttk
from time import sleep
##Application main window setup.
window = ttk.Tk()
window.maxsize(width=200, height=200)
window.minsize(width=200, height=200)
window.config(bg=("black"))
window.title("Hello World")
##Set a 'class' for exit function of application.
class Exit():
"""
Defines the countdown timer and sets parameters
that need to be satisfied before exit.
"""
def __init__(self):
self.countdown = 3
swell = ttk.Label(text=("Hello World!"), bg=("black"),
fg=("white"), font=("Times New Roman", 12, "bold"))
swell.place(x=50,y=50)
def quit(self):
for iteration in reversed(range(0, self.countdown + 1)):
print(iteration)
sleep(1)
print("Bye!")
window.destroy()
##Retrieve the defined 'timer' function from the 'Exit' class.
exit=Exit()
##Button with attahced command to execute the exit of application via user input.
quitButton=ttk.Button(
window,text=("Terminate"), bg=("red"), fg=("white"), font=("bold"),
width=20, height=1, anchor=ttk.S, command=lambda: exit.quit())
quitButton.place(x=6,y=150)
window.mainloop()
You can see here I also used the init method in the exit class. Its a special kind of method which will auto run when the class is initiated.
It didn't require much changing. All I did was move the destroy window function into its own class method and had the second window instance command be set to run this method.
Related
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
I have a tkinter application where user have many tasks to do. Most of the tasks call themselves recursively with tkinter after() method since every call I must check some pin states and other things, I figured that calling the function recursively every second will be fine. When the requirmenet is met ( some ping is toggled as required by the task), the task will complete and the function will go to mainloop "idle" state where I wait again for the user input.
Everything works fine except that I would like to implement one very important feature. Whenever user starts a task, I must have a emergency reset button, if pressed would restart the mainloop and immediately return to the "idle" state without the need to complete task. However, when the mainloop is running, I am not not sure how can I interrupt the task and stop it. I have made a simple "test" program to show exactly what I mean:
import tkinter as tk
from tkinter import Button,Entry,Canvas,Label,ttk
class Application(tk.Frame):
def __init__(self,master=None):
self.master = master
self.button_counter = 0
def Create_canvas(self,canvas_width,canvas_height):
global canvas#described as global because used outside class
canvas = tk.Canvas(master,bg='papaya whip',width=canvas_width,height=canvas_height)
def Application_Intro(self):
print("starting new app")
restart_program_button = tk.Button(canvas, text="Restart_program",font='Helvetica 12 bold', width=20, height=2,
command =self.Restart)
start_program_button = tk.Button(canvas, text="Start_program",font='Helvetica 12 bold', width=20, height=2,
command =self.Start_program)
increment_button = tk.Button(canvas, text="increment_button",font='Helvetica 12 bold', width=20, height=2,
command =self.increment_button)
canvas.create_text(960,20,text="MY PROGRAM",font='Helvetica 16 bold')
canvas.create_window(710,300,window = restart_program_button)
canvas.create_window(710,500,window = start_program_button)
canvas.create_window(710,100,window = increment_button)
canvas.pack()
master.mainloop()
def increment_button(self):
print("BUTTON INCREMENTED")
self.button_counter = self.button_counter +1 # increment button pressed everytime is pressed
def Start_program(self):
print("Program started")
if(self.button_counter > 1):
print("Task complete")
self.button_counter = 0
return
else:
master.after(1000,self.Start_program)
def Restart(self):# IF TASK STARTED SET RESTART =1, IF NOT restart devices and refresh app
print("HERE I WANT TO INTERRUPT START PROGRAM AND RETURN TO IDLE STATE")
print("REFRESH GUI ELEMENTS, DESTROY ANY WIDGETS IF CREATED")
print("RESET THE GLOBAL VARIABLE VALUES")
#master.mainloop()
#WHAT TO DO IN THIS FUNCTION TO GO BACK TO INITIAL MAINLOOP STATE??
return
master = tk.Tk()
app = Application(master=master)
app.Create_canvas(1920,1080)
app.Application_Intro()
The program above will create a simple tkinter program with 3 buttons.
Button at the top will increment button counter variable
Button in the middle is supposed to interrupt whatever task is running and return to "idle"state immediately
Button at the bottom will start a task. A task involves a person pressing Button at the top 2 times to increment button counter >1. After button incremented the counter 2 times, i return out of the recursive function which returns me back to mainloop "idle" state.
Now, as I mentioned, I must implement a technique where a user can return to "idle" state of the mainloop even if he didint press button to increment counter. It may be due to whatever emergency reason but I must be able to stop the current task and cancel it!
Can someone suggest me how should I do that since I could not find a neat way to do that.. It is also very important for me that this "reset" function will work for any possible task.
For example in reset function I could just set the button counter to any values > 1, and then the next call to task would trigger a reset, but this may not be the case for other tasks I will have that will not have any button counter involved.
Appreciate any help and suggestions
Even though there is a mainloop being called my tk window will not appear. The code used to work but as soon as I coded in the second function in the nums class there is no tk window. I would like for someone to point out the mistake instead of simply handing out the answer.
Can someone please help me fix this problem?
I use Python IDLE 3.8
Image: [1]: https://i.stack.imgur.com/o65WI.png
Code:
from tkinter import *
from random import randint
import time
#number assignments
class nums:
def __init__(self):
self.value=randint(1,100)
def assignnewnums(oldnum1,oldnum2,lbltxt,lbl,answer):
getans = answer.get()
if(getans==str((oldnum1.value+oldnum2.value))):
del(oldnum1)
del(oldnum2)
oldnum1=nums()
oldnum2=nums()
lbltxt="Correct!"
lbl.config(text=lbltxt)
time.sleep(5)
lbltxt="What is {} + {}".format(oldnum2.value,oldnum1.value)
lbl.config(text=lbltxt)
else:
lbltxt="Wrong! Try Again!"
lbl.config(text=lbltxt)
time.sleep(3)
lbltxt="What is {} + {}".format(oldnum2.value,oldnum1.value)
lbl.config(text=lbltxt)
a = nums()
b = nums()
#GUI startup
root = Tk()
#Label
title = Label(root, text="AddPrac", fg="dark blue")
title.pack()
#Question
questxt = "What is {} + {}".format(a.value,b.value)
ques = Label(root,text=questxt,fg="red")
ques.pack()
#UserAnswer
ans = Entry(root)
ans.pack()
#SubmitButton
enter = Button(root,text="Submit Answer!",fg="yellow",command=nums.assignnewnums(a,b,questxt,ques,ans))
enter.pack()
#GUI continued startup
root.mainloop()
I tried your code and the window does appear if you wait a few seconds.
This is due to the following offending code snippet:
command=nums.assignnewnums(a,b,questxt,ques,ans)
This doesn't do what you think it does. You were thinking of:
command=lambda: nums.assignnewnums(a, b, questxt, ques, ans)
The way your code is written now, it does not bind a callback to the button, but rather, calls- and executes the function (since you are invoking it explicitly), and attempts to bind the return value as a callback, which makes no sense. As a side effect of calling the function, the main thread sleeps (since assignnewnums uses time.sleep) for a bit before you reach root.mainloop.
Anytime you are binding a callback to a button, you want to provide a callable object - either just a function object, or if arguments are critical, a lambda or functools.partial.
Ok, so this is from a larger project that I am working on, so I apologize if it looks messy.
The issue is that when I click the 'Exit Program' Button on the GUI, the window remains active.
I know that the button is working as when I hit the 'x' on the top right corner the window; the program closes, so the run variable has been set back to 0, which stops the code from looping.
My question is how do I get the window to be closed automatically when the exit button is clicked, because the root.destroy() method isn't doing it.
#imports
from tkinter import *
import random, pickle, shelve
#global vars
run = 0
class Window(Frame):
#the class that manages the UI window
def __init__(self, master, screen_type = 0):
"""Initilize the frame"""
super(Window, self).__init__(master)
self.grid()
if screen_type == 1:
self.log_in_screen()
def log_in_screen(self):
#Program Exit Button
self.exit = Button(self, text = " Exit Program ", command = self.end)
self.exit.grid(row = 3, column = 0, columnspan = 2, sticky = W)
def end(self):
global run, root
run = 0
root.destroy()
#Main Loop
def main():
global run, root
run = 1
while run != 0:
root = Tk()
root.title("Budget Manager - 0.6.1")
root.geometry("400x120")
screen = Window(root, screen_type = run)
root.mainloop()
store = shelve.open("store.dat", "c")
main()
store.close()
My question is how do I get the window to be closed automatically when
the exit button is clicked, because the root.destroy() method isn't
doing it.
The answer is: call destroy() on the root window. You say it isn't working, but the code you posted seems to work, and what destroy() is documented to do is exactly what you describe you want to have happen: it will destroy the window. Your code creates new toplevel windows in a loop, so maybe it only appears to not work since the old window id destroyed and the new window is created in the blink of an eye.
It seems like what you're really asking is "how can I make clicking on the "x" do the same as clicking on the "Exit program" button?". If that is the case, the answer is very straight-forward, even with your unconventional code that creates root windows in a loop.
To get the "x" button on the window frame to call a function instead of destroying the window, use the wm_protocol method with the "WM_DELETE_WINDOW" constant and the function you want it to call.
For example:
while run != 0:
root = Tk()
...
screen = Window(root, screen_type = run)
root.wm_protocol("WM_DELETE_WINDOW", screen.end)
...
root.mainloop()
you could do something like the below. I've used it in my own projects etcand it works.
Mywin =tkinter.Tk()
def exit():
Mywin.quit()
# etc.
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.