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:
Related
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()
This question already has answers here:
Tkinter: AttributeError: NoneType object has no attribute <attribute name>
(4 answers)
Closed 2 years ago.
I have a problem with a GUI I created, that includes a label that indicates that the program is working in the background, the real script processes a huge amount of data so I want that indicator to tell the user it is indeed doing something. It worked fine so far like this, displaying "Idle" at script start, "running" while the function runs its course, and "Done" when it's done. (code is only the relevant part):
from tkinter import*
def make_pause_function(sleepytime):
import time
print("Falling asleep")
time.sleep(sleepytime)
print("Awakening")
class MyGUI:
def __init__(self):
self.__mainWindow = Tk()
self.header = StringVar()
self.header.set(3)
self.labelText = 'Idle'
# self.Button2 = Button(text = "Sleep for X seconds!", command = self.run_main_script).grid(row=1,column=0)
# self.Entry2 = Entry(self.__mainWindow, textvariable=self.header, width = 100).grid(row=2,column=0)
# self.Label3 = Label(self.__mainWindow, text = self.labelText).grid(row=3,column=0)
self.Button2 = Button(text = "Sleep for X seconds!", command = self.run_main_script)
self.Entry2 = Entry(self.__mainWindow, textvariable=self.header, width = 100)
self.Label3 = Label(self.__mainWindow, text = self.labelText)
self.Button2.pack()
self.Entry2.pack()
self.Label3.pack()
mainloop()
def run_main_script(self):
self.Label3["text"] = 'running'
self.__mainWindow.update_idletasks()
header=self.header.get()
make_pause_function(int(header))
self.Label3["text"] = 'done'
self.__mainWindow.update_idletasks()
myGUI = MyGUI()
But when the GUI grew because of many options, I switched from pack to grid geometry manager and then the label updating I had gotten working after a lot of trial and error got broken again. The follwing code doesn't work:
from tkinter import*
def make_pause_function(sleepytime):
import time
print("Falling asleep")
time.sleep(sleepytime)
print("Awakening")
class MyGUI:
def __init__(self):
self.__mainWindow = Tk()
self.header = StringVar()
self.header.set(3)
self.labelText = 'Idle'
self.Button2 = Button(text = "Sleep for X seconds!", command = self.run_main_script).grid(row=1,column=0)
self.Entry2 = Entry(self.__mainWindow, textvariable=self.header, width = 100).grid(row=2,column=0)
self.Label3 = Label(self.__mainWindow, text = self.labelText).grid(row=3,column=0)
# self.Button2 = Button(text = "Sleep for X seconds!", command = self.run_main_script)
# self.Entry2 = Entry(self.__mainWindow, textvariable=self.header, width = 100)
# self.Label3 = Label(self.__mainWindow, text = self.labelText)
# self.Button2.pack()
# self.Entry2.pack()
# self.Label3.pack()
mainloop()
def run_main_script(self):
self.Label3["text"] = 'running'
self.__mainWindow.update_idletasks()
header=self.header.get()
make_pause_function(int(header))
self.Label3["text"] = 'done'
self.__mainWindow.update_idletasks()
myGUI = MyGUI()
Seems that update_idletasks doesn't like grid. But I don't like pack for a GUI with lots of buttons and fields. Any way to do what I want with grid packing?
try this:
self.Label3 = Label(self.__mainWindow, text = self.labelText).grid(row=3,column=0)
from that, to this:
self.Label3 = Label(self.__mainWindow, text = self.labelText)
self.Label3.grid(row=3,column=0)
The follwing line causes the error:
self.Label3 = Label(self.__mainWindow, text = self.labelText).grid(row=3,column=0)
grid() is a function that doesn't return anything (None). Because of that, you're saving in variable self.Lable3 nothing. Then, when you run the line self.Label3["text"] = 'running' an error pops up because self.Lable3 is None. In order to solve it, seperate those lines- first save the Label in a variable and then use the grid() function on it:
self.Label3 = Label(self.__mainWindow, text = self.labelText)
self.Label3.grid(row=3,column=0)
By the way, I recommend using place() method instead of grid() because in my opinion it is easier to place objects with it. You can read about it Here
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 fairly new to python and am currently working on a school project, my aim is to create a search bar that can be used to search a data file, however I am struggling to get the search bar to work correctly. I am using the tkinter entry widget.
When I call .get(), the string in the entry widget is not printed. Here is my code...
from tkinter import *
def searchButton():
text = searched.get()
print (text)
def drawStatWindow():
global searched
statWindow = Tk()
statWindow.title("View Statistics")
statWindow.config(bg = "grey")
statWindow.geometry('800x900')
searched = StringVar()
searchBox = Entry(statWindow, textvariable = searched)
searchBox.place(x= 450, y=50, width = 200, height = 24)
enterButton = tkinter.Button(statWindow, text ="Enter", command =searchButton)
enterButton.config(height = 1, width = 4)
enterButton.place(x=652, y=50)
drawStatWindow()
When I type a string into the entry widget and press the enter button, nothing happens.
Like I say I am not very experienced and this is my first project, but after reading about the tkinter entry widgets I can't understand why this won't work.
I am using python V3.4.0
Thanks.
Your code lacks a call to mainloop(). You could try adding it to the end of the drawStatWindow() function:
statWindow.mainloop()
You might want to restructure your code into a class. This allows you to avoid using global variables and generally provides better organisation for your application:
from tkinter import *
class App:
def __init__(self, statWindow):
statWindow.title("View Statistics")
statWindow.config(bg = "grey")
statWindow.geometry('800x900')
self.searched = StringVar()
searchBox = Entry(statWindow, textvariable=self.searched)
searchBox.place(x= 450, y=50, width = 200, height = 24)
enterButton = Button(statWindow, text ="Enter", command=self.searchButton)
enterButton.config(height = 1, width = 4)
enterButton.place(x=652, y=50)
def searchButton(self):
text = self.searched.get()
print(text)
root = Tk()
app = App(root)
root.mainloop()
You have to add mainloop() because tkinter needs it to run.
If you run code in IDLE which use tkinter then IDLE runs own mainloop() and code can work but normally you have to add mainloop() at the end.
And you have to remove tkinter in tkinter.Button.
from tkinter import *
def searchButton():
text = searched.get()
print(text)
def drawStatWindow():
global searched
statWindow = Tk()
statWindow.title("View Statistics")
statWindow.config(bg="grey")
statWindow.geometry('800x900')
searched = StringVar()
searchBox = Entry(statWindow, textvariable=searched)
searchBox.place(x= 450, y=50, width=200, height=24)
# remove `tkinter` in `tkinter.Button`
enterButton = Button(statWindow, text="Enter", command=searchButton)
enterButton.config(height=1, width=4)
enterButton.place(x=652, y=50)
# add `mainloop()`
statWindow.mainloop()
drawStatWindow()
No need to use textvariable, you should use this:
searchBox = Entry(statWindow)
searchBox.focus_set()
searchBox.place(x= 450, y=50, width = 200, height = 24)
then you will be able to use searchBox.get(), that will be a string.
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.