I have a Toplevel window in a tkinter script, which contains two progress bars corresponding to a nested loop. I want to add a stop button in the Toplevel window to terminate the execution and close the Toplevel window but not the root.
from tkinter import Tk, Toplevel,Button,Label,TOP,BOTH,DoubleVar
from tkinter.ttk import Progressbar
import time
root = Tk()
root.configure(bg='lightgray')
root.wm_title("Main")
progress_window = Toplevel()
progress_window.attributes('-topmost', 'true')
progress_window.configure(bg='lightgrey')
progress_window.wm_title("Progress")
progress_window_label = Label(root, text="")
progress_window.geometry('600x300')
progress_window_label.pack()
M=4 # outer loop
N=5 # inner loop
progress1_var=DoubleVar()
progress1bar=Progressbar(master=progress_window,variable=progress1_var,length=M)
progress1bar.pack(side=TOP,ipady=5,fill=BOTH,expand=True)
progress1_var.set(0)
progress1bar.update()
progress1bar_label=Label(master=progress_window,text='Bar1',bg='lightgray')
progress1bar_label.pack(side=TOP,pady=5,fill=BOTH, expand=True)
progress2_var = DoubleVar()
progress2bar=Progressbar(master=progress_window,variable=progress2_var,length=N)
progress2bar.pack(side=TOP,ipady=5,fill=BOTH, expand=True)
progress2_var.set(0)
progress2bar.update()
progress2bar_label=Label(master=progress_window,text='Bar2',bg='lightgray')
progress2bar_label.pack(side=TOP,pady=5,fill=BOTH, expand=True)
def _stop():
return
stop_button=Button(master=progress_window, text="Cancel",command=_stop)
stop_button.pack(side=TOP,pady=5,fill=BOTH, expand=True)
for t in range(M):
progress1_var.set(t/M)
progress1bar_label.config(text='Bar 1: '+str(round((t+1)/M*100,3))+'%')
progress1bar.update()
progress2_var.set(0)
progress2bar.update()
for i in range(N):
progress2_var.set(i/N)
time.sleep(1.0) #Sleep to slow down execution and view progress window
progress2bar_label.config(text='Bar 2: '+str(round((i+1)/N*100,3))+'%')
progress2bar.update()
progress_window.destroy()
root.mainloop()
The stop button appears where it should but does not function and stop the execution. This has to be a very fundamental error, but I don't see how to correct it.
I've made a few modifications, primarily the addition of protocol for root and progress_window.
I've also created a flag called killed that is used to break the for loops. I've placed them in a function that is called by after.
This stops the TclError: Invalid command name .!toplevel.!label2
from tkinter import Tk, Toplevel, Button, Label, TOP, BOTH, DoubleVar
from tkinter.ttk import Progressbar
import time
killed = False
root = Tk()
root.configure(bg = 'lightgray')
root.wm_title("Main")
root.update()
progress_window = Toplevel()
def stop():
global killed
killed = True
progress_window.destroy()
root.destroy()
# Added protocol controls for exit
root.protocol( "WM_DELETE_WINDOW", stop )
progress_window.protocol( "WM_DELETE_WINDOW", stop )
progress_window.attributes('-topmost', 'true')
progress_window.configure(bg = 'lightgrey')
progress_window.wm_title("Progress")
progress_window_label = Label(root, text = "")
progress_window.geometry('600x300')
progress_window_label.pack()
M = 4 # outer loop
N = 5 # inner loop
progress1_var = DoubleVar()
progress1bar = Progressbar(master = progress_window, variable = progress1_var, length = M)
progress1bar.pack(side = TOP, ipady = 5, fill = BOTH, expand = True)
progress1_var.set(0)
progress1bar.update()
progress1bar_label = Label(master = progress_window, text = 'Bar1', bg = 'lightgray')
progress1bar_label.pack(side = TOP, pady = 5, fill = BOTH, expand = True)
progress2_var = DoubleVar()
progress2bar = Progressbar(master = progress_window, variable = progress2_var, length = N)
progress2bar.pack(side = TOP, ipady = 5, fill = BOTH, expand = True)
progress2_var.set(0)
progress2bar.update()
progress2bar_label = Label(master = progress_window, text = 'Bar2', bg = 'lightgray')
progress2bar_label.pack(side = TOP, pady = 5, fill = BOTH, expand = True)
def process():
for t in range(M):
if killed:
break
progress1_var.set(t/M)
progress1bar_label.config(text = "Bar 1: " + str( round( (t+1)/M*100, 3)) + "%")
progress1bar.update()
progress2_var.set(0)
progress2bar.update()
for i in range(N):
progress2_var.set(i/N)
time.sleep(1.0)
if killed:
break
progress2bar_label.config(text = "Bar 2: " + str( round( (i+1)/N*100, 3)) + "%")
progress2bar.update()
stop_button = Button(master = progress_window, text = "Cancel", command = stop)
stop_button.pack(side = TOP, pady = 5, fill = BOTH, expand = True)
root.after( 1000, process )
root.mainloop()
Related
I have a root window and I want to create a Toplevel window positioned in a specific place on the screen (centralized over the root window). So far I have been unable to accomplish this without seeing the Toplevel window quickly build itself in its default location and then hop over to the position I want it. I would prefer not to see this because it's weird and jarring.
Is there a way to build, then reposition a window in Tkinter without seeing it reposition itself?
Here's some example code with a sleep thrown in to simulate a complex window being rendered:
from tkinter import *
import time
def centralize_over_root(window_to_centralize):
root_geometry = root.winfo_geometry()
root_width_height = root_geometry.split('+')[0]
root_width = root_width_height.split('x')[0]
root_height = root_width_height.split('x')[1]
root_x_y = root_geometry.split(f'{root_height}+')[1]
root_x = root_x_y.split('+')[0]
root_y = root_x_y.split('+')[1]
window_to_centralize.update()
time.sleep(0.5)
window_width = window_to_centralize.winfo_width()
window_height = window_to_centralize.winfo_height()
window_x = int(root_x) + round((int(root_width) - int(window_width)) / 2.0)
window_y = int(root_y) + round((int(root_height) - int(window_height)) / 2.0)
result = f'+{window_x}+{window_y}'
return result
def new_window():
new_window = Toplevel(root)
Label(new_window, text='Something').pack(padx=20, pady=20)
new_window.geometry(centralize_over_root(new_window))
root = Tk()
Button(root, text='Make new window', command=new_window).pack(padx=50, pady=50)
root.mainloop()
You can hide the window using withdraw() and show it after reposition using deiconify():
def new_window():
new_window = Toplevel(root)
new_window.withdraw() # hide the window
Label(new_window, text='Something').pack(padx=20, pady=20)
new_window.geometry(centralize_over_root(new_window))
new_window.deiconify() # show the window
I think the following will do what you want. There are comments showing where I made changes. The problem was the time.sleep() was interfering with the mainloop() and your window_to_centralize.update() which makes it appear before you're finished configuring it.
from tkinter import *
import time
def centralize_over_root(window_to_centralize):
root_geometry = root.winfo_geometry()
root_width_height = root_geometry.split('+')[0]
root_width = root_width_height.split('x')[0]
root_height = root_width_height.split('x')[1]
root_x_y = root_geometry.split(f'{root_height}+')[1]
root_x = root_x_y.split('+')[0]
root_y = root_x_y.split('+')[1]
# window_to_centralize.update() # Don't do this - will happen automatically.
# time.sleep(0.5)
root.after(500) # This pauses without interfering with mainloop.
window_width = window_to_centralize.winfo_width()
window_height = window_to_centralize.winfo_height()
window_x = int(root_x) + round((int(root_width) - int(window_width)) / 2.0)
window_y = int(root_y) + round((int(root_height) - int(window_height)) / 2.0)
result = f'+{window_x}+{window_y}'
return result
def new_window():
new_window = Toplevel(root)
Label(new_window, text='Something').pack(padx=20, pady=20)
new_window.geometry(centralize_over_root(new_window))
root = Tk()
Button(root, text='Make new window', command=new_window).pack(padx=50, pady=50)
root.mainloop()
I'm building a flashcard app and want a label to pop up when the user gets the answer correct. I was wondering how to make it so that both labels are hidden until one of the buttons is pressed and then only one would show up(and preferably disappear when the next button is pressed). My code so far is below.
from tkinter import *
from PIL import ImageTk, Image
from random import randint
import random
root = Tk()
root.title('Chemistry Flashcards')
root.geometry("500x500")
def balancing():
hide_all_frames()
balancing_frame.pack(fill="both", expand=1)
global show_balancing
show_balancing = Label(balancing_frame)
show_balancing.pack(pady=15)
global balancing_list
balancing_list = ['balanced1', 'balanced2', 'balanced3', 'balanced4', 'balanced5', 'unbalanced1', 'unbalanced2', 'unbalanced3', 'unbalanced4', 'unbalanced5']
global balanced_list
balanced_list = balancing_list[:5]
global unbalanced_list
unbalanced_list = balancing_list[5:10]
global rando_image
rando_image = random.choice(balancing_list)
global balancing_image
balancing1 = "C:/Users/Kisitu/Desktop/project/balancing/" + rando_image + ".png"
balancing_image = ImageTk.PhotoImage(Image.open(balancing1))
show_balancing.config(image=balancing_image)
global balanced_button
balanced_button = Button(balancing_frame, text = 'balanced', command = balancing_answer).pack()
global unbalanced_button
unbalanced_button = Button(balancing_frame, text = 'unbalanced', command = balancing_answer).pack()
global balanced_label
balanced_label = Label(balancing_frame, text='It was balanced', font=("Helvetica",18), bg='#B3FDFF')
balanced_label.pack(pady=15)
global unbalanced_label
unbalanced_label = Label(balancing_frame, text='It was unbalanced', font=("Helvetica",18), bg='#B3FDFF')
unbalanced_label.pack(pady=15)
balancing()
def hide_all_frames():
for widget in balancing_frame.winfo_children():
widget.destroy()
balancing_frame.pack_forget()
balancing_frame = Frame(root, width=500, height=500, )
my_menu = Menu(root)
root.config(menu=my_menu, bg='#B7F7BB')
#menu options(elements and compound)
lesson_menu = Menu(my_menu)
my_menu.add_cascade(label="Lesson", menu=lesson_menu)
lesson_menu.add_command(label="balancing", command=balancing)
lesson_menu.add_separator()
lesson_menu.add_command(label="Exit", command=root.quit)
'''
end
'''
root.mainloop()
add functions to both buttons
def balancing_answer(): #this will make the label to show and hide
balanced_label.pack(pady=15)
unbalanced_label.pack_forget()
def unbalancing_answer(): #this will make the label to show and hide
balanced_label.pack_forget()
unbalanced_label.pack(pady=15)
global balanced_button
balanced_button = Button(balancing_frame, text = 'balanced', command = balancing_answer).pack()
global unbalanced_button
unbalanced_button = Button(balancing_frame, text = 'unbalanced', command = unbalancing_answer).pack() # change the command to unbalancing_answer
global balanced_label
balanced_label = Label(balancing_frame, text='It was balanced', font=("Helvetica",18), bg='#B3FDFF')
global unbalanced_label
unbalanced_label = Label(balancing_frame, text='It was unbalanced', font=("Helvetica",18), bg='#B3FDFF')
# balancing() # no need for this
If I get your question correctly; this will make the labels to pop up and disappear.
from tkinter import *
from PIL import ImageTk, Image
from random import randint
import random
root = Tk()
root.title('Chemistry Flashcards')
root.geometry("500x500")
def balancing_answer():
balancing_frame.pack()
balanced_label.pack(pady=15)
unbalanced_label.pack_forget()
show_balancing.config(image=balancing_image)
def unbalancing_answer():
balancing_frame.pack_forget()
balanced_label.pack_forget()
unbalanced_label.pack(pady=15)
show_balancing.config(image=balancing_image)
#two frames-unbalancing frame and balancing frame
balancing_frame = Frame(root)
unbalancing_frame=Frame(root)
#this is the show balancing frame
show_balancing = Label(balancing_frame)
show_balancing.pack()
#only one image is specified. there can be no random images.but the image will flash up at a click and disappear at another click
img = Image.open('p.jpg').convert("RGBA")
w, h = img.size
left = w/5
right = 3*w/5
upper = h/5
lower = 3*h/5
img2 = img.crop([ left, upper, right, lower]) #this part is used to crop the image. you can choose to ignore
balancing_image= ImageTk.PhotoImage(img2)
#two buttons to click
balanced_button = Button(unbalancing_frame, text = 'balanced', command = balancing_answer).pack()
unbalanced_button = Button(unbalancing_frame, text = 'unbalanced', command = unbalancing_answer).pack()
#the two labels balanced and unbalanced
balanced_label = Label(balancing_frame, text="it was balanced", font=("Helvetica",9), bg='#B3FDFF')
unbalanced_label = Label(unbalancing_frame, text='It was unbalanced', font=("Helvetica",9), bg='#B3FDFF')
#when the user click balancing.. a new frame appear
def balancing():
unbalancing_frame.pack()
#========the menu=========
my_menu = Menu(root)
root.config(menu=my_menu, bg='#B7F7BB')
#menu options(elements and compound)
lesson_menu = Menu(my_menu)
my_menu.add_cascade(label="Lesson", menu=lesson_menu)
lesson_menu.add_command(label="balancing", command=balancing)
lesson_menu.add_separator()
lesson_menu.add_command(label="Exit", command=root.quit)
root.mainloop()
I'm trying to have my Tk window perform a function when a button is pressed, and they automatically close itself. I assume I need some sort of destroy() function inside of the action function, but I don't know how to word it.
Here is what I am trying to do
import pandas as pd
from tkinter import *
import numpy as np
from functools import partial
fake data
test = pd.DataFrame(columns = ["id", 'sent', "O1", "O2", "O3", "O4"])
results = []
for i in range(5):
test.loc[i,:] = [i,"this is test "+ str(i), .2, .5, .1, .1]
levels = [["Baby"], ["Dinos"], ["bad"], ["Spoons"]]
###
This is the action I want it to take. It needs to record what was pressed, then delete the window afterwards. I think this is where my destroy() function needs to go, but I'm not sure how to word it.
def Add_results(option):
results.append(option)
My window maker
def Window_maker(sent, choices):
root = Tk()
topFrame = Frame(root)
topFrame.pack()
botFrame = Frame(root)
botFrame.pack()
label = Label(topFrame, text =sent)
label.pack()
indi= 0
button1 = Button(botFrame, text = choices[0], command = lambda: Add_results(option = choices[0]))
button1.pack()
button2 = Button(botFrame, text = choices[1], command = lambda: Add_results(option = choices[1]))
button2.pack()
root.mainloop()
return(results)
The implementation
for i in range(test.shape[0]):
index = get_params(test.iloc[i, 2:])
choices = [levels[x] for x in index.values]
pred = Window_maker(test.iloc[i,1], choices)
I found a fix.
I change Add_results to:
def Add_results(option):
results.append(option)
root.quit()
And it worked!
I am new to python and Tkinter and I need some help. I try to write a program which will show toplevel window with message on defined time. I introduce date, hour and text to program. Press "START" button and wait until toplevel window with message appear.
Program work when I do not use thread, but main window "freeze" until loop is done. Then new toplevel window appear with text.
What I would like to do is to get rid of "freezing" main window. My idea was to use thread for loop executing. But it does not work. When loop is finished in a thread it should call function which cause to Toplevel window appear. But it does not. Moreover program freeze.
I know that I should not use thread within tkinter mainloop but I can not figure out how in other way I can get rid of "freezing" main window.
thank you for all your answers.
Rafal
here is my program:
from Tkinter import *
import time
import calendar
import datetime
import thread
class Okienka(object):
def __init__(self, master):
self.rok = Label(master, text = "Podaj rok: ")
self.rok.grid(row = 0, sticky = E)
self.miesiac = Label(master, text = "Podaj miesiac w formacie XX: ")
self.miesiac.grid(row = 1, sticky = E)
self.dzien = Label(master, text = "Podaj dzien w formacie XX: ")
self.dzien.grid(row = 2, sticky = E)
self.godzina = Label(master, text = "Podaj godzine w formacie XX:XX: ")
self.godzina.grid(row = 3, sticky = E)
self.przyp = Label(master, text = "Tekst przypomnienia: ")
self.przyp.grid(columnspan = 2)
self.erok = Entry(master, width = 4)
self.erok.grid(row = 0 ,column = 1)
self.emiesiac = Entry(master, width = 2)
self.emiesiac.grid(row = 1 ,column = 1)
self.edzien = Entry(master, width = 2)
self.edzien.grid(row = 2 ,column = 1)
self.egodzina = Entry(master, width = 5)
self.egodzina.grid(row = 3 ,column = 1)
self.eprzyp = Text(master, width = 50, heigh = 10, font = ("Helvetica",10))
self.eprzyp.grid(columnspan = 2)
self.button1 = Button(master, text = "START", fg = "red", command = watek)
self.button1.grid(columnspan = 2)
def watek():
thread.start_new_thread(Czas,())
def Czas():
data = "{0}-{1}-{2} {3}".format(c.erok.get(), c.emiesiac.get(), c.edzien.get(), c.egodzina.get())
while True:
aktualny_czas = datetime.datetime.today()
czas_str = time.strftime(str(aktualny_czas))
czas_str = czas_str[:16]
print czas_str
if data == czas_str:
okienko()
break
def okienko():
komunikat = c.eprzyp.get("1.0","end-1c")
top = Toplevel()
top.title("Przypomnienie")
msg = Message(top, text = komunikat)
msg.pack()
root = Tk()
c = Okienka(root)
root.mainloop()
Destroying the root window in a Tkinter application actually means destroying (freezing) the whole application, not only the root window. The root window is the main
window for the application; it is the first to pop up and must be the last to
go. If I understand correctly, your application does not have an actual main
window: if a second window is opened from the initial window, closing the
initial window should not quit the application. Am I right?
If I am, the way to do that with Tkinter (or tcl/tk) is to create a fake root
window and hide it. This window will only be used to quit the application when
the last window is closed:
This is the code for the function I'm using to start the main part of the program, however I want some sort of loop or something which creates ten questions, but waits for an input from the Entry box before moving onto the next question.
Any ideas?
def StartGame():
root = Tk()
root.title("Maths Quiz - Trigonometry and Pythagoras' Theorem | Start The Game")
root.geometry("640x480")
root.configure(background = "gray92")
global AnswerEntry
TotScore = 0
Count = 0
AnswerReply = None
WorkingArea = Text(root, width = 70, height = 10, wrap = WORD).place(x = 38, y = 100)
n = GetRandomNumber()
Angle,Opposite,Adjacent,Hypotenuse = Triangle()
Question,RealAnswer = QuestionLibrary(Opposite,Adjacent,Hypotenuse,Angle,n)
AskQuestion = Label(root, text = Question, wraplength = 560).place(x = 48, y = 300)
PauseButton = ttk.Button(root, text = "Pause").place(x = 380, y = 10)
HelpButton = ttk.Button(root, text = "Help", command = helpbutton_click).place(x = 460, y = 10)
QuitButton = ttk.Button(root, text = "Quit", command = root.destroy).place(x = 540, y = 10)
AnswerEntry = Entry(root)
AnswerEntry.place(x = 252, y = 375)
SubmitButton = ttk.Button(root, text = "Submit", command = submit_answer).place(x = 276, y = 400)
TotScore,AnswerReply = IsAnswerCorrect(Answer,RealAnswer)
ScoreLabel = ttk.Label(root, text = TotScore).place(x = 38, y = 10)
AnswerReplyLabel = ttk.Label(root, text = AnswerReply).place(x = 295, y = 440)
root.mainloop()
I want the loop to start after the AnswerReply = None
You don't want a loop. The only really important loop inside a GUI should be the mainloop(), handling signal and executing callbacks.
Example:
try:
import Tkinter as Tk
except ImportError:
import tkinter as Tk
class QAGame(Tk.Tk):
def __init__(self, questions, answers, *args, **kwargs):
Tk.Tk.__init__(self, *args, **kwargs)
self.title("Questions and answers game")
self._setup_gui()
self._questions = questions[:]
self._answers = answers
self._show_next_question()
def _setup_gui(self):
self._label_value = Tk.StringVar()
self._label = Tk.Label(textvariable=self._label_value)
self._label.pack()
self._entry_value = Tk.StringVar()
self._entry = Tk.Entry(textvariable=self._entry_value)
self._entry.pack()
self._button = Tk.Button(text="Next", command=self._move_next)
self._button.pack()
def _show_next_question(self):
q = self._questions.pop(0)
self._label_value.set(str(q))
def _move_next(self):
self._read_answer()
if len(self._questions) > 0:
self._show_next_question()
self._entry_value.set("")
else:
self.quit()
self.destroy()
def _read_answer(self):
answer = self._entry_value.get()
self._answers.append(answer)
def _button_classification_callback(self, args, class_idx):
self._classification_callback(args, self._classes[class_idx])
self.classify_next_plot()
if __name__ == "__main__":
questions = ["How old are you?",
"What is your name?"]
answers = []
root = QAGame(questions, answers)
root.mainloop()
for q,a in zip(questions, answers):
print "%s\n>>> %s" % (q, a)
We only have a Label, an Entry and a Button (I did not care about layout!, just pack()).
Attached to the button is a command (aka callback). When the button is pressed, the answer is read and the new question is assigned to the label.
Usage of this class is understandable from the example in the `if name == "main" block. Please note: the answers-list is filled in place, the questions-list is kept unchanged.
I don't know Tk, but is there no any signals of input text changed? There should be for sure. Just check if this signal occured and then move onto new question, because it means that someone typed something in input box.