I am trying to create a 2 mins countdown app with multiple windows. I am putting all windows(frames) into the main container, and then use tkraise() to raise the frame whenever the navigation button to specific window is clicked(eg:'startPage' frame will be raised if 'back to startPage' button is clicked).
The code below works well only when the object of class PracticePage was first created.
However, the timer is still running at the back when I navigate from the countdown timer frame(class PracticePage) to another page. In other words, the timer will not count down from 2 mins whenever I navigate from another page back to the countdown timer page. I want it to countdown from 2 mins whenever the timer frame is raised.
I am a beginner in programming. I apologized if my question and code is confusing. Can someone help? Thank you in advance.
Below is my code:
import tkinter as tk
from tkinter import *
from tkinter import ttk
import time
class App(tk.Tk): #we want this class to inherit from tk.Tk
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self,"PreSys")
container = tk.Frame(self, height = 1000, width =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 (SignInPage, StartPage, PracticePage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(SignInPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise() #to raise one of the frames up to the front
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.startButton = ttk.Button(self, text="スタート", command = lambda: controller.show_frame(PracticePage))
self.startButton.pack()
class PracticePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.timeLeft = tk.Label(self,text= "")
self.backButton = ttk.Button(self, text="やり直す", command = lambda: controller.show_frame(StartPage))
self.homeButton = ttk.Button(self, text="サインアウト", command = lambda: controller.show_frame(SignInPage))
self.timeLeft.pack()
self.backButton.pack()
self.homeButton.pack()
#rc.start_record(SignInPage.entry_name.get())
self.remaining = 0
self.countdown(121)
def countdown(self, remaining = None):
if remaining is not None:
self.remaining = remaining
if self.remaining <= 0:
self.timeLeft.configure(text="お疲れ様です!")
else:
mins, secs = divmod(self.remaining,60)
mins = round(mins)
secs = round(secs)
self.timeLeft.configure(text=str(mins) +"分"+ str(secs) +"秒")
self.remaining = self.remaining - 1
self.after(1000, self.countdown)
apps = App()
apps.mainloop()
Before we get to the solution, when you supply your code make sure it works. You removed the SignInPage because it wasn't necessary, but you didn't follow up and remove the other references to it (i.e.- self.show_frame(SignInPage) in App.__init__ and self.homebutton in PracticePage.
As for your issue with resetting the timer, there are many ways to go about it, but they all come down to the fact that all you are doing is raising frames: you don't include any code that influences countdown based on what frame you are currently switching to.
Since this is a relatively simple application, I'll suggest the simplest solution: when you raise PracticePage reset its remaining variable. To keep it as easy and consistent as possible, we'll just modify show_frame
## You should store variables that are meaningful (you can place this underneath the imports)
PRACTICETIME = 121
## App():
def show_frame(self, cont):
frame = self.frames[cont]
if cont == PracticePage:
frame.remaining = PRACTICETIME
frame.tkraise() #to raise one of the frames up to the front
In a larger application I would recommend either using widget.after_cancel() to stop the countdown completely or simply to destroy and recreate your Pages if reasonable to do so. With this method you may notice a 1-second lag where the PracticePage is still displaying the previous remaining time: if this really bothers you, then I would recommend extracting the code that formats PracticePage.timeLeft into its own function and then calling that function from App.show_frame right after you set remaining.
Related
I am trying to update progressbar in tkinter.
First of i have two documents.
document 1: conatins GUI
document 2: Contains functions, that are called when pressing the button widgets attached to the GUI.
When i perform a calculation within document 2 i want to update the progress bar which is in document 1.
An exaple of the code is as follows. (just to mention, this is my first script in python, so please correct me if im wrong in any of the coding)
#Document 1
from Document2 import *
import tkinter as tk
from tkinter import *
from tkinter import messagebox
from tkinter import ttk
from tkinter import font as tkfont # python 3
from PIL import ImageTk, Image
from tkinter.ttk import Progressbar
class TEST(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.resizable(self, width=False, height=False)
tk.Tk.title(self, "APP NAME")
tk.Tk.iconbitmap(self, filepath_NAME_icon)
tk.Tk.geometry(self, "465x262")
#self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
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 (ProjectingPage):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("ProjectingPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class ProjectingPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent, bg=bg_color)
self.controller = controller
Button_get_TK = Button(self, text="Indsæt terrænkote",
font=("Calibri", btn_font_size, "normal"),
width=std_button_width,
bg=bg_color_button,
fg=fg_color_button,
borderwidth=size_border_width,
activebackground=act_bg,
command=Get_TK)
Button_get_TK.place(x=12, y=166)
Button_get_TK.update()
self.progress = Progressbar(self, orient = HORIZONTAL,
length = 100, mode = 'determinate')
self.progress.pack()
def UpdateProgressBar(self, data):
self.progress.start()
self.progress.update_idletasks()
and
#Document 2
import Document1 as KTP
from Document1 import *
def Get_TK():
for i in range(0,100):
test_progressbar = KTP.ProjectingPage
test_progressbar.UpdateProgressBar(i)
I have trimmed down my code to just the relevant part. I need to know the principals of getting my progressbar to update by calling a function from a separate document.
I really hope someone can help me. thanks in advance :-)
I wouldn't uses elements from Document1 as hardcoded in Document2 but as argument in function.
#Document 2
# without imports from Document1
def Get_TK(item):
test_progressbar = item.ProjectingPage
for i in range(100):
test_progressbar.UpdateProgressBar(i)
And then I would use lambda to assing function with values
command=lambda:Get_TK(self.UpdateProgressBar):
I would said that sending as argument is consistent with the principle of The Zen of Python
Explicit is better than implicit.
But if I would have to use Get_TK in ProjectingPage to access element in the same class ProjectingPage then I wouldn't put it in separated file but directly in ProjectingPage
class ProjectingPage(tk.Frame):
def Get_TK(self):
for i in range(100):
self.UpdateProgressBar(i)
I'm a teacher and every week I grade students' participation score. Since I teach computers, I thought I'd create a program to handle that logic for me. I plan on using TkInter to create a start screen with the 4 periods of the day, and depending on the period, it would pull up that class. But I'm trying to use the same class for all 4 periods, since the code is exactly the same.
Here's my code:
class part(tk.Tk):
#Creates a class for the GUI
def __init__(self, *args, **kwargs):
#Initialization function of partGUI
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.iconbitmap(self, default="") #default icon in an .ico file
tk.Tk.wm_title(self, "Lucey's Participation Program") #title
window = tk.Frame(self)
window.pack(side="top", fill="both", expand=True)
window.grid_rowconfigure(0, weight=1)
window.grid_columnconfigure(0, weight=1)
self.frames= {}
for F in (StartPage, ClassPart, SettingsPage):
frame = F(window, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, window):
#Allows Program to switch windows/pages/frames
frame = self.frames[window]
frame.tkraise()
class StartPage(tk.Frame):
# Home Screen for Program
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
title = tk.Label(self, text="Participation Scores", font=LARGE_FONT)
title.pack(pady=10, padx=10)
btnPeriod1 = tk.Button(self, text="1st Period", fg="red",
command=lambda: controller.show_frame(ClassPart(controller, 1)))
btnPeriod1.pack()
class ClassPart(tk.Frame):
# Screen showing students, participation buttons & their scores/Hogwarts houses
def __init__(self, parent, controller, period):
tk.Frame.__init__(self, parent)
But this throws an error:
Traceback (most recent call last):
File "/home/klucey/Documents/partvers2.py", line 307, in <module>
window = part()
File "/home/klucey/Documents/partvers2.py", line 40, in __init__
frame = F(window, self)
TypeError: __init__() missing 1 required positional argument: 'period'
Any help to a beginner/intermediate would be greatly appreciated!
This boilerplate code that you (and everybody else on SO, it seems) are using to handle a multi-page Tkinter app is simply not suitable for handing multiple pages of the same class. You'd have to put multiple occurrences of ClassPart in the page list, and somehow arrange for them to be given a different period parameter when constructed - but this breaks the .show_frame() method, as you no longer have a unique identifier to select the page to be shown.
Here's what I would suggest instead, given that you have a fixed set of pages (this wouldn't work so well with dynamically-generated pages):
Get rid of the period parameter in your class (so that its constructor is compatible with the other pages).
Create a subclass of it for each period:
class ClassPart1(ClassPart):
period = 1
class ClassPart2(ClassPart):
period = 2
... and so on. Refer to self.period in the base class to access this value.
Change the initial page creation loop to for F in (StartPage, ClassPart1, ClassPart2, ClassPart3, ClassPart4, SettingsPage):. Use ClassPart1, etc. as the identifier to pass to .show_frame().
I have written an GUI with tkinter. In the background I have to perfom some intensive calculations. From time to time I want to write some information from the calculation thread to the GUI window.
First I thought it was a good idea to add the computation code into the "mainloop". But this doesn't work, because the mainloop is resposible for keeping the GUI reactive. It seams to be no good idea to mainpulate it.
Below I have created a dummy app that scatches my new idea. The Sample app has a container. Inside that container, there is a TitleBar. The class TitleBar is defined below. It contains only one label.
Next I define a simple thread. It simulates a timeconsuming computation that wants to write some information to the GUI.
import tkinter as tk
import threading
import time
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
# initialize the main window
tk.Tk.__init__(self, *args, **kwargs)
# add a container which will take all the widgets
container = tk.Frame(self, bg="green")
container.pack(side="top", fill="both", expand=True)
# Add a titlebar object (defined below)
titleBar = TitleBar(container, controller=self)
titleBar.grid(row=0, column=0, columnspan=2, sticky=tk.N+tk.W+tk.E)
class TitleBar(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
# the title bar contains only one label element
titleLabel = tk.Label(self, text="This is the initial text")
titleLabel.pack(side=tk.LEFT)
# Define a thread that runs in the background to perform intensive calculations
class MyTestThread(threading.Thread):
def run(self):
for i in range(10):
time.sleep(1)
a = i+100 # intensive calculation
# from time to time: inform use about status
print(a) # printing to console works fine
app.titleBar.titleLabel['text'] = "test 1" # --- FAILS ---
if __name__ == "__main__":
app = SampleApp()
app.titleBar.titleLabel['text'] = "test 2" # --- FAILS ---
t = MyTestThread()
t.start()
app.mainloop()
The problem is that I cannot access the label to write information to it. The writing fails, both, from within the thread and from the app itself. In both cases it fails with the following error:
AttributeError: '_tkinter.tkapp' object has no attribute 'titleBar'
How can I access and change the properties of the label-object?
Thank you
Bernd
With the help of eyllanesc and Mark_Bassem I was able to solve the problem. It seems that the problem was indeed very simple.
Just in case people will wisit this post in the future, I leave the correct code here:
import tkinter as tk
import threading
import time
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
# initialize the main window
tk.Tk.__init__(self, *args, **kwargs)
# add a container which will take all the widgets
container = tk.Frame(self, bg="green")
container.pack(side="top", fill="both", expand=True)
# Add a titlebar object (defined below)
self.titleBar = TitleBar(container, controller=self)
self.titleBar.grid(row=0, column=0, columnspan=2, sticky=tk.N+tk.W+tk.E)
class TitleBar(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
# the title bar contains only one label element
self.titleLabel = tk.Label(self, text="This is the initial text")
self.titleLabel.pack(side=tk.LEFT)
# Define a thread that runs in the background to perform intensive calculations
class MyTestThread(threading.Thread):
def run(self):
for i in range(10):
time.sleep(1)
a = i+100 # intensive calculation
# from time to time: inform use about status
print(a) # printing to console works fine
app.titleBar.titleLabel['text'] = "status: " + str(a)
if __name__ == "__main__":
app = SampleApp()
#app.titleBar.titleLabel['text'] = "test 2"
t = MyTestThread()
t.start()
app.mainloop()
You forgot to put self before the titleBar attribute
Immediately during the first ~1 sec after I've landed on a new page in my GUI, I'm seeing numerous visual glitches before the window arrives at the proper layout (before/after screenshots below).
UPDATE:
The below code will give the desired error. I feel like the initial frame is being merged with the next frame upon calling the show_frame function, and posit that the initial frame must actually be manually hidden from view before preloading (hiding/loading) the next frame. Any help on this is greatly appreciated - and thank you to all who have taken a look so far.
import tkinter as Tk
from tkinter import font as tkfont
from tkinter import filedialog, ttk #browse directories; widget support
class Manifold(Tk.Tk):
def __init__(self, *args, **kwargs):
Tk.Tk.__init__(self, *args, **kwargs)
#custom font options:
self.title1_font = tkfont.Font(family='Helvetica',size=13) #normal type
#customized ttk GUI theme:
GUItheme = ttk.Style()
GUItheme.theme_use('alt')
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,geometry,title,options,wait in zip((StartPage,PageOne),
("532x279","528x270"),
("","Experimental Data"),
((False,False),(True,False)),
(100,100)):
page_name = F.__name__
frame = F(container, self)
self.frames[page_name] = (frame,geometry,title,options,wait) #puts all pages in stacked order
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name): #show a frame for the given page name
frame, geometry, title, options, wait = self.frames[page_name]
self.geometry(geometry) #changes between window sizes
self.title(title) #changes titles of windows
self.resizable(*options) #changes ability of user to resize each window
self.withdraw()
frame.tkraise() #raises window to top
self.after(wait,self.deiconify) #preload page before viewing
self.update_idletasks()
class StartPage(Tk.Frame):
def __init__(self, parent, controller):
Tk.Frame.__init__(self, parent)
self.controller = controller
self.configure(background='black') #set window background color
#page one button:
button1 = ttk.Button(self, text="Page 1",
command=lambda: controller.show_frame("PageOne"))
button1.grid(row=0,column=0,sticky='W')
class PageOne(Tk.Frame):
def __init__(self, parent, controller):
Tk.Frame.__init__(self, parent)
self.controller = controller
self.configure(background='gray15') #set window background color
self.grid_columnconfigure(0,weight=1000) #for resizing window horizontally
#average volume filename:
self.label1 = Tk.Label(self, text="Average Volume Filename:",fg="gray90",bg="gray25",font=controller.title1_font)
self.label1.grid(row=0,column=0,ipadx=10,ipady=0,sticky='W')
self.label1.config(height=1)
self.entry1 = Tk.Entry(self,textvariable=Tk.StringVar(),highlightbackground="black",width=50)
self.entry1.grid(row=1,column=0,sticky="WE")
self.entry1.insert(0," .mrc, .spi")
self.entry1.configure(state="disabled") #prevent typing
self.browse1 = ttk.Button(self,text="Browse",
command=self.browse_button1)
self.browse1.grid(row=1,column=1,sticky='W')
#gathers volume input:
def browse_button1(self):
self.entry1.configure(state="normal") #unlocks typing for program
self.label1.config(fg="gray90",bg='gray25') #standard colors, or reset on additional wrong input
content_initial = self.entry1.get()
if __name__ == "__main__":
app = Manifold()
app.mainloop()
Page One:
Transition:
Page Two:
Okay, I've figured it out. Using my above code (in the Update section of my initial question), I made two changes. The first is right under the main Class structure:
class Manifold(Tk.Tk):
def __init__(self, *args, **kwargs):
Tk.Tk.__init__(self, *args, **kwargs)
#preload windows (avoids watching widgets load in real time):
self.withdraw() #hide window
self.after(0,self.deiconify) #unhide window asap
The second is in the show_frame loop, with the preceding wait tuple (within the chunk starting with for F, geometry,etc. set to (10,10,10,10):
def show_frame(self, page_name): #show a frame for the given page name
frame, geometry, title, scaling, wait = self.frames[page_name]
self.quit() #seems to be important in exiting out of previous window entirely
self.geometry(geometry) #changes between window sizes
self.title(title) #changes titles of windows
self.resizable(*scaling) #changes ability of user to resize each window
self.withdraw()
frame.tkraise() #raises window to top
self.after(wait,self.deiconify) #preload page before viewing
self.update_idletasks()
If anyone else ends up running into something like this, I hope this helps!
Remove all calls to update until your are ready for your UI to be visible, and don't use after(0, ...) because the zero means that code will run before tkinter has had a chance to process any other events, including requests to redraw portions of the screen.
Hide your Toplevel or Tk instance(s) that is/are glitchy until after a certain amount of time has passed:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except:
import Tkinter as tk
def hide(*toplevel_or_tk_instance_s_):
for each in toplevel_or_tk_instance_s_:
each.withdraw()
def show(*toplevel_or_tk_instance_s_):
for each in toplevel_or_tk_instance_s_:
each.iconify()
each.deiconify()
if __name__ == '__main__':
root = tk.Tk()
my_toplevel1 = tk.Toplevel(root, bg='red')
my_toplevel2 = tk.Toplevel(root, bg='green')
hide(root, my_toplevel1, my_toplevel2)
root.after(1000, show, root)
root.after(2000, show, my_toplevel1)
root.after(3000, show, my_toplevel2)
root.mainloop()
This is likely a simple problem related to how variables stored in a dictionary are given values within a function.
I'm trying to get the value of a Boolean variable I'm storing in a dictionary (self.controller.Page1_data["Step1Complete"]) that I set to "True" in the code below.
#!/usr/local/bin/env python3
import tkinter as tk # python3
from tkinter import BooleanVar
TITLE_FONT = ("Helvetica", 18, "bold")
class SampleApp(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.Page1_data={"Step1Complete": BooleanVar()}
self.frames = {}
for F in (StartPage, PageFifteen):
page_name = F.__name__
frame = F(container, self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
controller.title("Part B Data Collection")
controller.geometry("600x500")
label = tk.Label(self, text="Welcome to the Part B Test!", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Complete Step 1", command=self.MakeStep1Complete)
button1.place(relx=0.385, rely=0.65)
def MakeStep1Complete(self):
Step1Complete=True
self.controller.Page1_data["Step1Complete"]=Step1Complete
self.controller.show_frame("PageFifteen")
class PageFifteen(tk.Frame):
def StatusCheck(self):
Step1Complete=self.controller.Page1_data["Step1Complete"]
print("True or false: at Step 15, Step 1 completed -a ")
print(Step1Complete)
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Check the data", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
self.StatusCheck()
tk.Label(self, text="Click on each of the buttons below to review the data you have inputted").place(relx=0.15, rely=0.12)
Step1Complete=self.controller.Page1_data["Step1Complete"].get()
print("True or false: at Step 15, Step 1 completed - b")
print(Step1Complete)
button17=tk.Button(self, text="Check if we did everything",
command=self.StatusCheck)
button17.place(relx=0.7, rely=0.75)
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
When I execute the above (now edited per Florent's suggestions) code, I get the following results:
True or false: at Step 15, Step 1 completed -a
False
True or false: at Step 15, Step 1 completed - b
False
True or false: at Step 15, Step 1 completed -a
True
The first result is from the first execution of the StatusCheck function, where I simply call the function. The second result is from when I am explicitly executing the command (Step1Complete=self.controller.Page1_data["Step1Complete"].get()) that ought to (but doesn't) get the correct Boolean data. The third result, and the only result that gets the correct Boolean data, is generated when the user clicks the "Check if we did everything", which calls the StatusCheck function from the tk.Button command config option.
Why might this happen? Why would the StatusCheck function not work the first time I execute it but then work the second time I execute it. Ideally, I'd like the code to immediately find that the "Step1Complete" variable has been set to "True" without having the user click a button.
I have reason to believe that the problem has something to do with which functions write values to variables, since I'm able to make the program work correctly when I set "Step1Complete" to "True" within the __init__ function in the StartPage class.
Being a Python N00b, I know I must be missing something pretty obvious, but I really appreciate any help you could provide.
The short version of the answer is this: When you are using tkinter variables you need to use set and get functions to access the value of the variables. Also in an unrelated issue, in a class you don't need to define a function before calling it and it is better to have __init__ on top of your class (cf class PageFifteen).
Now, specifically for your code and why you get those prints:
currently, you call 2 prints when creating the PageFifteen Frame:
in the first on, you print a tk.BooleanVar, which gives you its handle and not its value (var.get() to print the value)
in the second one, you correctly use get() and print the current value of a your var (which is the default of a BooleanVar: False)
Then you show StartPage, click on button and you overwrite your Booleanvar with the bool True, that's why your last StatusCheck works without a get.
In your code you need to replace:
def StatusCheck(self):
Step1Complete=self.controller.Page1_data["Step1Complete"]
print("True or false: at Step 15, Step 1 completed -a ")
print(Step1Complete)by
by:
def StatusCheck(self):
Step1Complete=self.controller.Page1_data["Step1Complete"].get()
print("True or false: at Step 15, Step 1 completed -a ")
print(Step1Complete)
and
def MakeStep1Complete(self):
Step1Complete=True
self.controller.Page1_data["Step1Complete"]=Step1Complete
self.controller.show_frame("PageFifteen")
by:
def MakeStep1Complete(self):
Step1Complete=True
self.controller.Page1_data["Step1Complete"].set(Step1Complete)
self.controller.show_frame("PageFifteen")