Tkinter - Set Shared Variable and Access in Different Class - python

I have created my own python script following the suggestion of this post, Switch between two frames in tkinter.
I have also followed this post, How to access variables from different classes in tkinter python 3.
However, I want to set or change the value of the shared value in one frame and then access it in another, when I switch between the frames.
class MainApplication(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.app = {
"foo": None
}
self.frames = {}
for F in (PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("PageOne")
def show_frame(self, page_name, **kwargs):
# self.app["foo"] = kwargs.get("bar")
frame = self.frames[page_name]
frame.tkraise()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
button = tk.Button(self, text="Login", command=lambda: self.func())
button.pack()
def func(self):
# self.controller.show_frame("PageTwo", bar="something")
self.controller.show_frame("PageTwo")
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
print(self.controller.app["foo"])
I tried adding arguments to the show_frame() function that allows for switching between frames but the result in PageTwo is still None. I have commented out the lines, to show what I attempted.

It's important to understand that PageTwo.__init__ runs at the very start of the program, before the user has a chance to do anything. If you want to access app after switching, you're going to have to add code that runs after switching.
The following answer, linked to from where you copied your code, shows you one way to do that:
How would I make a method which is run every time a frame is shown in tkinter

Related

What OOP techniques are used in this code? Is it aggregation, composition, generalization, association or just dependency?

I would like to ask whether the aggregation or dependency is used in this code? In other words, how is class StartPage() dependant on the class SeaofBTCapp?
import tkinter as tk
class SeaofBTCapp(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.frames = {}
frame = StartPage(container, self)
self.frames[StartPage] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
label = tk.Label(self, text="Start Page")
label.pack(pady=10,padx=10)
app = SeaofBTCapp()
app.mainloop()
how is class StartPage() dependant on the class SeaofBTCapp?
StartPage is designed to act as a single page in a multi-page app. It requires that some other object or program is responsible for creating it and causing it to be displayed. Loosly speaking, this is a view in the model, view, controller pattern.
SeaofBTCapp is a class designed to display one page at a time, where pages are implemented as frames composed of one or more additional widgets. It acts as the controller in the model, view, controller pattern.
Using your terminology, SeaofBTCapp is an aggregated object since it is composed of many pages.

Create a list or dictionary of Tkinter buttons to use in different frames

So I have my Tkinter application that consist of multiple frame
All these multiple frames contain the same basic structure of many buttons; the only difference is that the buttons have a different bg on each page.
In my actual project, these buttons contain so many options, and so having to write the same basic code each time for all pages makes my code look unnecessarily long.
So I'm thinking: Is there a way to put all these buttons into a dictionary or list, and pack them onto each separate frame? (Bear in mind the button will need to inherit the bg variable from the specific frame.)
I've created a minimal example to illustrate what I mean:
import tkinter as tk
from tkinter import *
listt = []
self = None
bg_colour_for_this_frame = None
button1 = Button(self,text="Button 1",bg=bg_colour_for_this_frame,fg='white')
button2 = Button(self,text="Button 2",bg=bg_colour_for_this_frame,fg='blue')
button3 = Button(self,text="Button 3",bg=bg_colour_for_this_frame,fg='orange')
listt.append(button1)
listt.append(button2)
listt.append(button3)
class Tkinter(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.frames = {}
for F in (StartPage, SecondPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
frame.winfo_toplevel().geometry("860x864")
frame.configure(bg='#000000')
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
Button(self,text='SecondPage',command=lambda:controller.show_frame(SecondPage)).pack()
for s in listt:
s.pack()
class SecondPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
Button(self,text='StartPage',command=lambda:controller.show_frame(StartPage)).pack()
for s in listt:
s.pack()
app = Tkinter()
app.mainloop()
Or maybe, instead of having a list, use a dictionary:
listt = {'button1':'Button[root,text="Button 1",bg=bg_colour_for_this_frame,fg="white"]',
'button2':'Button[root,text="Button 2",bg=bg_colour_for_this_frame,fg="red"]',
'button3':'Button[root,text="Button 3",bg=bg_colour_for_this_frame,fg="blue"]',
}
I get the error:
s.pack()
AttributeError: 'str' object has no attribute 'pack'
Since you can't create the Buttons before the page they're on exists, It would be simpler to make a function and call it during the initialization of each of the page classes — like the make_buttons() shown below:
import tkinter as tk
from tkinter import *
# Button options for all pages.
BTN_OPTS = [dict(text="Button 1", fg='white'),
dict(text="Button 2", fg='blue'),
dict(text="Button 3", fg='orange')]
def make_buttons(parent, bg_colour):
return [Button(parent, bg=bg_colour, **opts) for opts in BTN_OPTS]
class Tkinter(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.frames = {}
for F in (StartPage, SecondPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
frame.winfo_toplevel().geometry("860x864")
frame.configure(bg='#000000')
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
Button(self, text='SecondPage',
command=lambda: controller.show_frame(SecondPage)).pack()
for btn in make_buttons(self, 'Pink'):
btn.pack()
class SecondPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
Button(self, text='StartPage',
command=lambda: controller.show_frame(StartPage)).pack()
for btn in make_buttons(self, 'green'):
btn.pack()
app = Tkinter()
app.mainloop()
A more sophisticated and object-oriented approach would be to define a base class for all page classes that had a method in it something like the function above, and then derive the concrete subclasses from that allowing them just inherit the method. It also gets rid of the global data because the button options are now in a (base) class attribute.
Here's a runnable example of how it could be done that way. Note: it requires Python 3.6+ because it uses object.__init_subclass__() which was added in that version:
import tkinter as tk
class Tkinter(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.frames = {}
for F in (StartPage, SecondPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
frame.winfo_toplevel().geometry("860x864")
frame.configure(bg='#000000')
class BasePage(tk.Frame):
# Button options common to all pages.
BTN_OPTS = [dict(text="Button 1", fg='white'),
dict(text="Button 2", fg='blue'),
dict(text="Button 3", fg='orange')]
#classmethod
def __init_subclass__(cls, /, bg_color, **kwargs):
super().__init_subclass__(**kwargs)
cls.bg_color = bg_color
def __init__(self, parent, controller, text, command):
super().__init__(parent)
tk.Button(self, text=text, command=command).pack() # Next page button.
for btn in (tk.Button(self, bg=self.bg_color, **opts) for opts in self.BTN_OPTS):
btn.pack()
class StartPage(BasePage, bg_color='pink'):
def __init__(self, parent, controller):
super().__init__(parent, controller, text='SecondPage',
command=lambda: controller.show_frame(SecondPage))
class SecondPage(BasePage, bg_color='green'):
def __init__(self, parent, controller):
super().__init__(parent, controller, text='StartPage',
command=lambda: controller.show_frame(StartPage))
app = Tkinter()
app.mainloop()

Automatically updating a tkinter label that isn't directly accessible

I want to create a simple GUI for a constant data that comes through Serial. I decided to use tkinter. The value reading is updated and should be shown in a label. I created separate classes for the container and the other pages. I defined the container as such:
class Gui(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = Frame(self)
container.pack(side="top", fill = "both", expand = TRUE)
container.grid_rowconfigure(0, weight = 1)
self.frames={}
for F in (StartPage, PageOne):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
frame.UpdateMe()
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
And the page showing the label:
class PageOne(Frame):
def __init__(self, parent, controller):
Frame.__init__(self,parent)
global reading
self.label1text = StringVar()
self.label1 = Label(self, textvariable = label1text)
self.label1.pack()
button1 = Button (self, text = "Show Start Page", command = lambda: controller.show_frame(StartPage))
button1.pack()
self.label1text.set(reading)
def UpdateMe(self):
global reading
self.lable1text.set(reading)
Now, to initialize the GUI:
root = Gui()
root.mainloop()
However, since mainloop() is blocking, any argument coming after that wouldn't be executed; I could get around that with update and update_idletasks. However, I still don't know how I could call the function UpdateMe() inside PageOne()when I only created an instantiation of the Gui(). Is there a way for me to solve this or remediate my understanding of classes and object programming?
Since you cannot create StringVar without initializing Tk() (for your case, it is Gui()), so you need to create the reading variable inside Gui() and PageOne.label1 uses it as its textvariable. Below is an example based on your code:
from tkinter import *
from random import randint
class Gui(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container = Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.reading = StringVar() # create the StringVar for PageOne
self.frames = {}
for F in (StartPage, PageOne):
frame = F(container, self)
frame.grid(row=0, column=0, sticky="nsew")
self.frames[F] = frame
self.show_frame(StartPage)
def show_frame(self, cont):
self.frames[cont].tkraise()
class StartPage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
button1 = Button (self, text="Show Page 1", command=lambda: controller.show_frame(PageOne))
button1.pack(fill="both", expand=True)
class PageOne(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.label1 = Label(self, textvariable=controller.reading) # refer to Gui.reading StringVar
self.label1.pack(fill='x')
button1 = Button (self, text="Show Start Page", command=lambda: controller.show_frame(StartPage))
button1.pack(fill='x')
# use .after() to simulate the update of reading variable periodically
def update_reading():
app.reading.set(randint(0, 10000))
print('reading:', app.reading.get())
app.after(1000, update_reading)
app = Gui()
update_reading() # start the simulation task of updating reading variable
app.mainloop()
Note that I have created a function update_reading() to simulate the update of reading variable periodically using after() function.

attribute error when switching between frames

Using the method suggested in Switch between two frames in tkinter I have tried to switch between a login frame and a register frame. Nevertheless, when showFrame(register) is called after the register button is pressed on the login screen, an attribute error occurs:
(frame=self.frames[pageName]; AttributeError:'loginScreen' object has no
attribute 'frames')
class mainActivity(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self,*args,**kwargs)
container=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 (loginScreen, register):
frame = F(container, self)
self.frames[F]=frame
frame.grid(row=0, column=0, sticky="snew")
self.showFrame(loginScreen)
def showFrame(self, pageName):
frame=self.frames[pageName]
frame.tkraise()
#-------------------------------------------------
class loginScreen(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller=controller
self.regBtn =Button(self, text="Register", command=self.regBtnClicked)
self.regBtn.grid(row=2,column=1)
self.grid()
def regBtnClicked(self):
mainActivity.showFrame(self, register)
#send to register screen
#-------------------------------------------------
class register(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller=controller
self.grid(row=0, column=0)
self.cancelBtn = Button(self, text="Cancel",command=self.cancelBtnClicked)
self.cancelBtn.grid(row=4, column=1)
def cancelBtnClicked(self):
#return to login screen
mainActivity.showFrame(self, loginScreen)
app = mainActivity()
app.mainloop()
There are several issues with your code. First you are missing an important potion of the for F in (loginScreen, register): for loop.
You need to have:
page_name = F.__name__
This is the line of code that is used to provide the string name for the key portion of the self.frames dictionary.
So instead of doing:
self.frames[F]=frame
Do this instead:
self.frames[page_name] = frame
Try to use the print statement while troubleshooting. All it took was for me to add print(self.frames) right before the error was occurring to have an idea of what is going wrong here.
You will also need to change self.showFrame(loginScreen) to self.showFrame("loginScreen") as well as any other call to showFrame() to reflect the same change.
Another issue you were having was this line:
mainActivity.showFrame(self, loginScreen)
You need to reference the controller instead here like this:
self.controller.showFrame("loginScreen")
You do not need self.grid(row=0, column=0) in your frame class's because these have already been set in your for loop. No need to do it again.
With all that take a look at the below code:
from tkinter import *
class mainActivity(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
container=Frame(self)
container.pack(side="top", fill="both", expand=True)
self.frames={}
for F in (loginScreen, register):
page_name = F.__name__
frame = F(container, self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="snew")
print(self.frames)
self.showFrame("loginScreen")
def showFrame(self, pageName):
frame=self.frames[pageName]
frame.tkraise()
#-------------------------------------------------
class loginScreen(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller=controller
self.regBtn = Button(self, text="Register", command=self.regBtnClicked)
self.regBtn.grid(row=2,column=1)
def regBtnClicked(self):
self.controller.showFrame("register")
#-------------------------------------------------
class register(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.controller=controller
self.cancelBtn = Button(self, text="Cancel",command=self.cancelBtnClicked)
self.cancelBtn.grid(row=4, column=1)
def cancelBtnClicked(self):
self.controller.showFrame("loginScreen")
app = mainActivity()
app.mainloop()

Tkinter Application showing blank screen while running

When I try to execute my Tkinter application created in python, it is giving me a blank application window. No Buttons/Labels are displaying. What may be the Issue ?
Codes are as follows:
import tkinter as tk
from tkinter import ttk
LARGE_FONT=("Verdana", 18)
class VNMSapp(tk.Tk):
def __int__(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.frames= []
for F in (StartPage, AdminPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(text="THIS IS HOME PAGE", font=LARGE_FONT)
label.pack()
btn1 = tk.Button(self, text="Enter ADMIN PAGE",
command=lambda: controller.show_frame(AdminPage))
btn1.pack()
class AdminPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(text="THIS IS ADMIN PAGE", font=LARGE_FONT)
label.pack()
btn1 = tk.Button(self, text="Enter HOME PAGE",
command=lambda: controller.show_frame(StartPage))
btn1.pack()
app = VNMSapp()
app.mainloop()
It is not giving me any error also.
There is a typo in the definition of the __init__() method for class VNMSapp:
def __int__(self, *args, **kwargs):
should be
def __init__(self, *args, **kwargs):
As a result your __init__() method is not being called, so your widgets are not created.
Once you correct that you will find an additional problem where you are using a list, but I think that you meant to use a dictionary:
self.frames= []
...
self.frames[F] = frame
the second line will fail raising a TypeError exception because list indices must be integers, which frame objects are not.
Fix that by initialising self.frames to an empty dict:
self.frames= {}

Categories