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
Related
This is the window provides the container and methods which allow frame swapping:
class Login_Window(ctk.CTk):
def __init__(self, *args, **kwargs):
super().__init__()
self.geometry('400x400')
self.title('Music Mayhem')
self.resizable(False, False)
container = ctk.CTkFrame(master=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 (LoginFrame, RegEmailFrame):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky= 'nsew')
frame.grid_columnconfigure(0,weight=1)
frame.grid_rowconfigure(0,weight=1)
self.show_frame(LoginFrame)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
In order to swap the frames, a button has to be created in the frame that is going to be swapped. How would i go about creating an instance of another class within these frames which will also call the show_frame method? Here is the code for the frames- this could be ran as long as you have tkinter and custom tkinter installed. The only aspect that should supposedly not work are the buttons in the menu frame.
Yes, in this situation the menu frame is not needed but this is just a simple example because the actual code is way too long to be included here.
I have tried adding the menu frame into the list of frames to be swapped (in the class above) and giving it the same parent and controller attributes as the other frame but that required a parent and controller argument to be passed through when it is called in the Login and Register frames.
Is there a way to get round this or a simpler method that could be implemented instead?
class LoginFrame (tk.Frame):
def __init__(self,parent, controller):
tk.Frame.__init__(self, parent)
self.menu = Menu(self)
self.menu.grid(row=0, column=0)
self.loginBtn = ctk.CTkButton(master=self, width=100, height = 20,text='Login',
state='normal',
command=lambda: controller.show_frame(RegEmailFrame)
self.loginBtn.grid(row=1, column=0)
class RegEmailFrame(tk.Frame):
def __init__(self, parent, controller,header_name="Register Email"):
tk.Frame.__init__(self, parent)
self.menu = Menu(self)
self.menu.grid(row=0, column=0)
self.emailLabel = ctk.CtKLabel(master=self,width=100, height=20 text='Frame swapped')
self.emailLabel.grid(row=1, column=0)
class Menu(tk.Frame):
def __init__(self, *args, header_name="Logo Frame",
width=175, height=175,**kwargs):
super().__init__(*args, width=width, height=height, **kwargs)
self.menuloginBtn = ctk.CTkButton(master=self, width=100, height = 20,text='Login',
state='normal',
command=lambda: controller.show_frame(LoginFrame)
self.menuloginBtn.grid(row=0, column=0)
self.menuRegBtn = ctk.CTkButton(master=self, width=100, height = 20,text='Login',
state='normal',
command=lambda: controller.show_frame(RegEmailFrame)
self.menuRegBtn.grid(row=1, column=0)
In the current implementation, the Menu class does not have access to the controller object that is used to switch between frames in the Login_Window class. One way to fix this would be to pass the controller object to the Menu class during instantiation.
You can do this by adding a parameter called controller in the Menu class constructor and then passing it as an argument when creating an instance of the Menu class in the LoginFrame and RegEmailFrame classes.
For example, in the LoginFrame class:
def __init__(self,parent, controller):
tk.Frame.__init__(self, parent)
self.menu = Menu(self, controller)
self.menu.grid(row=0, column=0)
And in the Menu class constructor:
def __init__(self, parent, controller, *args, header_name="Logo Frame",
width=175, height=175,**kwargs):
super().__init__(parent, *args, width=width, height=height, **kwargs)
self.controller = controller
With this changes, the Menu class now has access to the controller object and can use it to switch between frames using the show_frame method.
You should also make the same changes in the RegEmailFrame class and in the constructor of the Menu class.
Hope this helps!
I am creating a Tkinter GUI with a lot of pages. A lot of them have the same display of different databases.
I want to create multiple pages following the list of databases
I have a list of pandas dataframes in a list called Databases and I want to create pages automatically fitted to the databases
global Pages
Pages=[]
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
Pages_List=[Studies_List]+Pages
self.frames = {}
for F in Pages_List: #dont forget to add the different pages
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(Studies_Page)
def show_frame(self, c):
frame = self.frames[c]
frame.tkraise()
class Studies_Page(tk.Frame): #Should, from each Site, show only the XRays corresponding to this Site. Plots sheet is an option too.
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent,background=bg_color)
self.controller=controller
tk.Label(self, text="Home > Studies ",bg=bg_color,font=('Arial',9,'italic')).pack(side='top')
label = tk.Label(self, text="Choose the Study you want the data from :",bg=bg_color, font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
Studies_frame=tk.Frame(self,bg=bg_color)
############ Studies Buttons
for i in range(len(Databases)):
tk.Button(Studies_frame, text=Pages[i]['Study'][0],command=lambda: controller.show_frame(Pages[i])).pack(in_=Studies_frame)
tk.Label(Studies_frame, text=" ",bg=bg_color,font=('Arial',11,)).pack()
Studies_frame.pack(expand=True,side='top')
for i in range(len(Databases)):
class page(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
self.controller=controller
df=Databases[i]
f=tk.Frame(self)
f.pack()
self.table=pt=Table(f,dataframe=df)
pt.show()
return
Pages.append(page)
if __name__ == "__main__":
app = SampleApp()
app.title("X-Rays App")
app.mainloop()
It is particularly needed because I have multiple buttons in the startpage, each of them leading to a database.
I was expecting a page for each database but instead every occurence of the Pages List returned the last list.
I have a lot of pages to create with tkinter and they have a lot in common so I would like to use lists with classes in a dynamic way but I didn't find how.
I tried changing the class name everytime but it still returns the last database of the Databases lists
There are no reasons to redefine the class Page(tk.Frame): multiple times in a for loop.
What you should probably do, is create instances of that class inside the loop.
Maybe like this:
class Page(tk.Frame):
def __init__(self, parent, controller, db_index):
tk.Frame.__init__(self, parent)
self.controller=controller
self.db_index = db_index
df = Databases[self.db_index]
self.pack()
self.table = Table(f, dataframe=df)
pt.show()
pages = []
for idx in range(len(database_collection)):
pages.append(Page(parent, controller, idx)) # create an instance of Page
I have a tkinter application I'm trying to create. I've deiced to use the grid geometry manger instead of pack. It has been working well so far, but I've come across a strange problem.
The basic layout I want in my application is to have a toolbar frame on the top side of the window, which will expand to fill the window horizontally, and take up 1/5 of the screen space in terms of height. Another frame will fill the reaming 4/5 screen space(again in terms of height), and will also fill the screen horizontally. Another third frame will be under the second, and I will use frame.tkraise() to switch between the two.
Here is a minimal example of what I'm trying to accomplish:
import tkinter as tk
class Toolbar(tk.Frame):
def __init__(self, parent, root, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.root = root
self.lbl = tk.Label(self, text='Toolbar')
self.lbl.pack()
class PageOne(tk.Frame):
def __init__(self, parent, root, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.root = root
self.lbl = tk.Label(self, text='PageOne')
self.lbl.pack()
self.btn = tk.Button(self, text='MainPage', command=lambda: self.root.show_frame(MainPage))
self.btn.pack()
class MainPage(tk.Frame):
def __init__(self, parent, root, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.root = root
self.lbl = tk.Label(self, text='MainPage')
self.lbl.pack()
self.btn = tk.Button(self, text='PageOne', command=lambda: self.root.show_frame(PageOne))
self.btn.pack()
class App(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._setup_window()
self.frames = {}
container = tk.Frame(self, bg='yellow')
container.pack(side='top', fill='both', expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
container.grid_rowconfigure(1, weight=3)
self.toolbar = Toolbar(container, self, bg='red')
self.toolbar.grid(row=0, column=0, sticky='new')
for page in (MainPage, PageOne):
frame = page(container, self, bg='blue')
self.frames[page] = frame
frame.grid(row=1, column=0, sticky='nsew')
self.show_frame(MainPage)
def _setup_window(self):
self.geometry('420x280')
self.title('backup')
def show_frame(self, page_name):
frame = self.frames[page_name]
frame.tkraise()
if __name__ == '__main__':
app = App()
app.mainloop()
The code above displays the following frame:
The yellow frame is the window which holds the tool bar and two other frames. The red frame is the tool bar. And the blue frames are the two main windows, which can be toggled between using the "MainPage" and "PageOne" buttons respectively.
As you can probably tell, my problem is that the tool bar frame(the red part)is not fully expanding to fill the reaming yellow space. No matter how much space I allocate for the tool bar frame, it does not expand.
My understanding was that if I put the tool bar frame in the zeroth column, and the
two main windows in the first, and I did container.grid_rowconfigure(0, weight=1) and container.grid_rowconfigure(1, weight=4) that would meet my needs. I made this assumption based on this excerpt from here:
To make a column or row stretchable, use this option and supply a value that gives the relative weight of this column or row when distributing the extra space. For example, if a widget w contains a grid layout, these lines will distribute three-fourths of the extra space to the first column and one-fourth to the second column:
w.columnconfigure(0, weight=3)
w.columnconfigure(1, weight=1)
If this option is not used, the column or row will not stretch.
Was I wrong to assume this? Is there something I'm missing? How can I make my tool bar frame expand to fill the extra space?
You just need to tell your toolbar to stick to the south side of the available space as well:
self.toolbar.grid(row=0, column=0, sticky='nesw')
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:
...
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")