I need some help with updating informations between two or more frames in tkinter. I am currelty working on a small project, where I have serval frames with information, each frame is one class.
First frame is a setting frame - so where the user can click some buttons and select several things.
The second frame should show the selected image with the made choices.
So far I can make the choices on the setting page but when i click forward to the result page it shows me empty frames and no image and not the made choices.
I tried to do some shared_data with the information that should be passed between the two frames/classes, so I can acess them on the resultpage but some how it is not updating the information. So it doesn't show me the image because mode on the results page is 0 like at the beginng.
class xyz (tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
...
self.shared_data = {
"no": tk.IntVar(),
"selectedname": tk.StringVar(),
"selectedpath": tk.StringVar(),
.....
}
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 (SettingPage, PageResults):
page_name = F.__name__
frame = F(container, self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("SettingPage")
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
class SettingPage(tk.Frame):
def __init__(self, parent, controller, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.controller = controller
self.controller.shared_data["no"] = 0
...
# after selecting serval buttons
self.controller.shared_data["no"] = 1
buttonContinue = ttk.Button(self, text='''CONTINUE >>''', command=lambda: controller.show_frame("PageResults"))
buttonContinue.place(relx=0.84, rely=0.9, height=43, width=186)
class PageResults(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
mode = self.controller.shared_data["no"]
if mode == 1 :
# show image
#......
I can't show you the exact real code I am using so I tried so simplify it. I am new to tkinter so any help would be much apprechiated.
Thank you!!
The problem is because you do this:
self.shared_data = {
"no": tk.IntVar(),
...
}
And then later you do this:
self.controller.shared_data["no"] = 0
So, shared_data["no"] starts out as an IntVar and then you replace it with an integer.
When you use an IntVar you must use the set method to change the value, and the get method to get the value:
self.controller.shared_data["no"].set(0)
...
if self.controller.shared_data["no"] == 0:
...
Related
I have a question about why my child class can't see parents method(it can't see them, but it can use them). I'm creating a GUI in tkinter and I need to use some methods from my parent window.
My parent class look like this:
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
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)
manager_of_action = Action_manager()
self.frames = {}
for F in (MenuView, LoginView, GameView, ResultView ):
page_name = F.__name__
frame = F(parent=container, controller=self,action_manager= manager_of_action)
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("MenuView")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
and child class look like this:
class MenuView(tk.Frame):
def __init__(self, parent, controller, action_manager):
tk.Frame.__init__(self, parent)
self.controller = controller
button1 = tk.Button(self, text="Multiplayer",command=lambda: controller.show_frame("LoginView"),height=3,width=6, bg=("white"), font=controller.title_font)
button2 = tk.Button(self, text="Exit",command=lambda: exit(0),height=3,width=6,bg=("white"), font=controller.title_font )
button1.pack(side="top", fill="both", expand=True)
button2.pack(side="top", fill="both", expand=True)
But VS Code can't see .title_font() and .show_frame() but if I run my program, it is running as it should. Why it's happening and can I somehow fix it? (If I would have 50 methods in my parent class, I don't want to always look back to parent class and copy + paste my desired fuction.)
Thanks a lot for every help.
EDIT:
VScode doesn't highlight .title_font() and .show_frame() as it can be seen on picture. This means that VS code is not showing them in IntelliSense (Pylance) menu (. + space menu that will pop up after calling some object). Normally, functions are highlighted in yellow.
no highlight for .show_frame, .title_font
Pardon me for my bad grammar or explanation, since I didn't know how to explain this properly.
I try to build some gui that could switch between frame, using script from this as base Switch between two frames in tkinter.
In this case, I will have a few frame that had similar design, but different function when the button is pressed. For example, I have 2 frames that have similar 2 entries and 1 button, but the button do different command (where at sub01 frame it will multiply and at sub02 frame will divide)
This is my code:
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.grid(row=1,columnspan=4,sticky='nsew')
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (sub01, sub02):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=1,sticky="nsew")
self.choices = {'sub01','sub02'}
self.tkvar = tk.StringVar()
self.tkvar.set('sub01')
self.popMenu = tk.OptionMenu(self,self.tkvar,*self.choices)
self.popMenu.grid(row=0)
self.show_frame()
self.button1 = tk.Button(self, text="Go to Layer",command=lambda: self.show_frame())
self.button1.grid(row=0, column=1)
def show_frame(self):
'''Show a frame for the given page name'''
page_name = self.tkvar.get()
frame = self.frames[page_name]
frame.tkraise()
class sub01(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This SubLayer 1")
label.grid(row=0)
self.entries=[]
i = 0
while i < 2:
self.entries.append(tk.Entry(self,width=10))
self.entries[i].grid(row=i+1,columnspan=2,sticky='we')
i += 1
self.btn = tk.Button(self,text="multiply", command=lambda : self.multiply())
self.btn.grid(row=i+1, columnspan=2,sticky='we')
def multiply(self):
pass
class sub02(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This SubLayer 2")
label.grid(row=0)
self.entries=[]
i = 0
while i < 2:
self.entries.append(tk.Entry(self,width=10))
self.entries[i].grid(row=i+1,columnspan=2,sticky='w')
i += 1
self.btn = tk.Button(self,text="divide",command=lambda : self.divide())
self.btn.grid(row=i+1, columnspan=2,sticky='we')
def divide(self):
pass
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
This code itself works, but when I need to create more of these frames, it becomes inconvenient. How could I make this code simpler? Like having that similar frame as a class, and the button as other class that do differ behaviour depend of the layer shown.
Thank you in advance
The canonical way to do this sort of thing is to create a class hierarchy for your Page classes and put common functionality in the base classes and derive subclasses from them that specify the behavior that differs between them. Below is how you could do that with the sample code in your question.
Since the things that are different between them are:
The text displayed on the Label.
The text displayed on the Button.
The code in that's execute when the Button is clicked.
This means the derived classes only need to know what code to run in a generically named btn_func() method and what the text to displayed on the two widgets. The code below illustrates how to do that.
Note that I've changed the spelling of your class names to conform to the naming conventions describe in PEP 8 - Style Guide for Python Code.
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.grid(row=1,columnspan=4,sticky='nsew')
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (Sub01, Sub02):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=1,sticky="nsew")
self.choices = {'Sub01','Sub02'}
self.tkvar = tk.StringVar()
self.tkvar.set('Sub01')
self.popMenu = tk.OptionMenu(self,self.tkvar,*self.choices)
self.popMenu.grid(row=0)
self.show_frame()
self.button1 = tk.Button(self, text="Go to Layer",command=lambda: self.show_frame())
self.button1.grid(row=0, column=1)
def show_frame(self):
'''Show a frame for the given page name'''
page_name = self.tkvar.get()
frame = self.frames[page_name]
frame.tkraise()
class BaseSubLayer(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text=self.lbl_text)
label.grid(row=0)
self.entries=[]
i = 0
while i < 2:
self.entries.append(tk.Entry(self,width=10))
self.entries[i].grid(row=i+1,columnspan=2,sticky='we')
i += 1
self.btn = tk.Button(self,text=self.btn_func_name, command=self.btn_func)
self.btn.grid(row=i+1, columnspan=2,sticky='we')
def btn_func(self):
raise NotImplementedError
class Sub01(BaseSubLayer):
lbl_text = 'This SubLayer 1'
btn_func_name = 'multiply'
def btn_func(self):
print('Running multiply() method.')
class Sub02(BaseSubLayer):
lbl_text = 'This SubLayer 2'
btn_func_name = 'divide'
def btn_func(self):
print('Running divide() method.')
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
I am creating a password manager software. The passwords are stored in different categories. When I click on the category it should open a ViewPasswordsInsideCategory page.
I am unable to pass the category name to the ViewPasswordsInsideCategory page.
Please help! I am new to Python and cant solve this issue.
I tried to pass an argument when I can showframe function but I couldn't achieve the goal.
class PasswordPage(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs)
self.shared_data = {
"category": tk.StringVar(),
"password": tk.StringVar()}
self.passwordScreen = Frame(self)
self.passwordScreen.pack(fill="both", expand=True)
self.passwordScreen.columnconfigure(0, weight=1)
self.passwordScreen.rowconfigure(0, weight=1)
self.frames = {}
for F in (PasswordCategoryPage,ViewPasswords_InCategory,CreateNewPassword,ViewPassword,ModifyPassword):
page_name = F.__name__
frame = F(parent=self.passwordScreen, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("PasswordCategoryPage")
def show_frame(self, page_name, arg=None):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class PasswordCategoryPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
# passwords categories content
self.labels = []
self.dict = {'Home': 'Blue', 'Work': 'Red', 'School': 'Yellow', "Technical": 'Pink', "Office": 'Orange'}
self.button = []
j = 0
for key, value in self.dict.items():
self.name = key
self.key = Label(self.pass_page_container,text=self.name,bg=value)
# Add the Label to the list
self.labels.append(key)
self.key.grid(row=j, column=0, sticky=(N, S, E, W))
self.key.bind("<Double-Button-1>", self.showPasswordPage)
j = j + 1
def showPasswordPage(self,event):
#here need to pass the label that was clicked to the ViewPasswords_InCategory page
self.controller.show_frame("ViewPasswords_InCategory")
class ViewPasswords_InCategory(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
#here need the name of the category selected to extract data from database
# home banner
home_label = Label(self, text=here need the name of the category, bg="blue", width=200, height=3, fg='white')
home_label.pack(side=TOP, expand=False)
The simply way is to add a new function in class ViewPasswords_InCategory to update the banner. Then whenever you switch to the page, call the new function. My proposed changes are:
1) return the frame that is raised in function PasswordPage.show_frame(...):
class PasswordPage(Page):
...
def show_frame(self, page_name, arg=None):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
return frame # return the raised frame
2) add new function, for example set_category(...) inside class ViewPasswords_InCategory to update its banner:
class ViewPasswords_InCategory(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
# home banner
self.home_label = Label(self, text='', bg="blue", width=200, height=3, fg='white') # changed to instance variable
self.home_label.pack(side=TOP, expand=False)
# new function to update banner
def set_category(self, category):
self.home_label['text'] = category
3) update PasswordCategoryPage.showPasswordPage(...) to call the new fucntion:
class PasswordCategoryPage(tk.Frame):
...
def showPasswordPage(self,event):
#here need to pass the label that was clicked to the ViewPasswords_InCategory page
self.controller.show_frame("ViewPasswords_InCategory").set_category(event.widget['text'])
I have my Tkinker app where I create more frame and based on what I click I show one or another, the problem now is that I want them to refresh with new information every time that I call them; do you know how can I do?
This is my code:
class HFlair(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(10, weight=1)
container.grid_columnconfigure(10, weight=1)
self.container=container
self.frames = {}
for F in ((StartPage, None), (PageOne, None)):
page_name = F[0].__name__
frame = F[0](parent=container, controller=self, attr=F[1])
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name,arg=None):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
if arg:
frame.users(arg)
class StartPage(tk.Frame):
def __init__(self, parent, controller,attr=None):
tk.Frame.__init__(self, parent,attr)
self.controller = controller
self.title=tk.Frame(self)
self.title.pack(side="top")
self.menu=tk.Frame(self)
self.menu.pack(side="top")
self.app=tk.Frame(self) #### frame that I want refresh
self.app.pack(side="top") #### frame that I want refresh
class PageOne(tk.Frame):
def __init__(self, parent, controller, attr=None):
self.controller=controller
tk.Frame.__init__(self, parent,attr)
self.controller = controller
self.title=tk.Frame(self)
self.title.pack(side="top")
self.menu=tk.Frame(self)
self.menu.pack(side="top")
self.app=tk.Frame(self) #### frame that I want refresh
self.app.pack(side="top") #### frame that I want refresh
My problem is that in the frame self.app I have some information that I take from the DB so every time that I go to this specific page I want the Frame refresh to take update information.
Is there some easy way to do it?
I try with .update() but didn't work, more than other didn't give me any issue but the Frame didn't refresh.
I am trying to access the Text widget defined in class FirstPage from outside of the class.
I tried to solve this problem by creating a new instance of FirstPage, but could not find the right arguments to use. Also tried to use instance of GUI to gain the access, but unsuccessfully.
My problem is solved when I can use text.insert(0.0, t) from outside of the classes. It would help me modify the text displayed with Tkinter by functions that are not directly related with the GUI.
The origin of the code I am trying to use is found: Switch between two frames in tkinter
Also I removed lines that were not necessary for this question..
import Tkinter as tk
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.geometry(self, '580x410')
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 = FirstPage(container, self)
self.frames[FirstPage] = frame
frame.grid(row=0, column=0, sticky="nsew")
frame = self.frames[FirstPage]
frame.tkraise()
class FirstPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
text = tk.Text(self , height=25, width=80)
text.grid(column=0, row=0, sticky="nw")
app = GUI()
app.mainloop()
EDIT:
Here is the working code:
import Tkinter as tk
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.geometry(self, '580x410')
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 = FirstPage(container, self)
self.frames[FirstPage] = frame
frame.grid(row=0, column=0, sticky="nsew")
frame = self.frames[FirstPage]
frame.tkraise()
page_name = FirstPage.__name__
self.frames[page_name] = frame
def get_page(self, page_name):
return self.frames[page_name]
class FirstPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.text = tk.Text(self , height=25, width=80)
self.text.grid(column=0, row=0, sticky="nw")
app = GUI()
app.get_page("FirstPage").text.insert("1.0", "Hello, world")
app.mainloop()
There's nothing special you need to do. As with any python object, you simply need a reference to the object in order to manipulate it.
The concept in the code you started with is to have a "controller" that controls access to all of the pages, since that object is where the pages are created. You can add a function in the controller that gives you a reference to a page, and then you can use that to call a function on that page.
Here's the changes you need to make to the controller:
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
...
page_name = FirstPage.__name__
self.frames[page_name] = frame
...
def get_page(self, page_name):
return self.frames[page_name]
You also need to modify FirstPage to keep a reference to the widget so that you can access it later:
class FirstPage(tk.Frame):
def __init__(self, parent, controller):
...
self.text = tk.Text(...)
...
From within any other code you can now access the text widget via get_page (but your pages must save a reference to the controller for this to work).
class AnotherPage(tk.Frame):
def __init__(self, parent, controller):
...
self.controller = controller
...
def some_function(self):
...
first_page = self.controller.get_page("FirstPage")
text = first_page.text.get("1.0", "end-1c")
...
first_page.text.insert("end", "some new text\n")
Note that this technique works outside of any GUI pages. In your code, app is the controller, so you can do something like this:
app = GUI()
app.get_page("FirstPage").text.insert("1.0", "Hello, world")