So I am trying to set the size of my VideoFrame, however, I can't seem to be able to access its size options. when using self.config I can access the background colour but the size does not change.
So I suspect it has something to do with the propagate option, but I don't know where, nor how to specify it as I am still getting used to object-oriented programing and this self notation is still a bit unclear for me.
So thanks for your help, I have attached the relevant part of the code, please note that VideoFrame is nested as follow: MainWindow>Display>VideoFrame
class MainWindow(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.side = Display(self, SideVideoMenu)
self.side.grid(row=1, column=1)
self.main_box = Display(self, VideoFrame)
self.main_box.grid(row=1, column=0, rowspan=2)
self.top_menu = TopMenu(self)
self.top_menu.grid(row=0, column=0, columnspan=5, sticky=tk.W)
# Display template with the option to change frame
class Display(tk.Frame):
def __init__(self, master, start_class):
tk.Frame.__init__(self, master) # The master is the window in which the object (of class display) will be packed
self.frame = None
self.Switch_frame(start_class)
def Switch_frame(self, frame_class):
new_page = frame_class(self)
if self.frame:
self.frame.destroy()
self.frame = new_page
self.frame.pack()
# Frame option for MainDisplay that shows the video preview
class VideoFrame(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.config(width=500, height=500, bg='blue')
tk.Label(self, text='video place holder', bg='#313335', fg='white').pack()
Related
I'm having trouble getting widgets to show on a frame that uses multi-level inheritance. I have these 2 classes:
class ScrollableFrame(Frame):
def __init__(self, parent, name, width=0, height=0, *args, **kwargs):
Frame.__init__(self, parent, *args, **kwargs, width=width, height=height)
self.parent = parent
self.pack_propagate(False)
self.label = Label(self, text=name)
self.label.pack()
self.sp = Separator(self)
self.sp.pack(fill="x")
self.v = Scrollbar(self)
self.v.pack(side=RIGHT, fill=Y)
self.c = Canvas(self, width=width, height=height, yscrollcommand=self.v.set)
self.interior = Frame(self.c)
self.interior.bind("<Configure>", self.reset_scrollregion)
self.interior.pack()
self.c.create_window((0, 0), window=self.interior, anchor="nw")
self.c.bind("<Configure>", lambda e: self.c.configure(scrollregion=self.c.bbox("all")))
self.c.pack(side=TOP, fill=X)
self.v.config(command=self.c.yview)
def reset_scrollregion(self, event):
self.c.configure(scrollregion=self.c.bbox("all"))
class GroupFrame(ScrollableFrame):
def __init__(self, parent, *args, **kwargs):
ScrollableFrame.__init__(self, parent, "Groups", *args, **kwargs, width=200, height=600)
self.group_list = []
# Populate Header
self.input = Entry(self, width=150)
self.input.pack()
self.input.insert(0, "Enter Group Name...")
self.submit_btn = Button(self, width=150, text="Submit", command=self.group_entry)
self.submit_btn.pack()
self.sp_header = Separator(self)
self.sp_header.pack(fill=X, pady=5)
def group_entry(self):
group = Group(self.interior, self.input.get())
self.group_list.append(group)
When I make a GroupFrame object and display it, I only get this:
No widgets that I added in the GroupFrame class show up. I think it's because in the GroupFrame class, 'self' doesn't count as a Frame since it inherits and initializes from ScrollableFrame. Is there any way to fix this or do I have to scrap the GroupFrame class and code it procedural instead?
As #jasonharper mentioned above, I missed the fact that the interior frame that was meant to be scrollable pushed the rest of the widgets out the bottom of the frame. All I had to do is put the widgets above the interior in another frame called header and in the GroupFrame class I could choose to put input and button in the header frame.
I have code below originally taken from the link here i have read all documentation in there but i feel im overlooking something, i have pages in separate modules as well as a separate module with a Add class for adding pages to the window.
The idea is to later be able to drop a module in a sub folder with a Pagexxx object within it and call the add page class to allow Tkinter to display it but i cannot seem to get the frames to stack.
Nav.py
import tkinter as tk
from Page import PageList
import Mypages
class Windowhandler(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)
PageList("Page1", container, self, Mypages.PageOne)
PageList("Page2", container, self, Mypages.PageTwo)
self.show_frame("Page1")
def show_frame(self, cont):
frameref = PageList.frames[cont]
print(frameref)
frameref.tkraise()
app = Windowhandler()
app.mainloop()
Page.py
class PageList():
frames = {}
def __init__(self, name, parent, cont, ref):
self.frames[name] = ref(parent=parent, controller=cont)
Mypages.py
import tkinter as tk
class PageOne(tk.Frame):
def __init__(self, parent, controller):
this = tk.Frame.__init__(self, parent)
label = tk.Label(this, text="Welcome to Page 1")
label.pack(pady=10, padx=10)
button1 = tk.Button(this, text="Back to Home",
command=lambda: controller.show_frame("Page2"))
button1.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
this = tk.Frame.__init__(self, parent)
label = tk.Label(this, text="Welcome to Page 2")
label.pack(pady=10, padx=10)
button1 = tk.Button(this, text="Back to Home",
command=lambda: controller.show_frame("Page1"))
button1.pack()
Before asking for help from others, the first step is to validate your assumptions. You're assuming that tk.Frame.__init__(self, parent) returns something useful, but you never validated that assumption by checking to see if it is what you think it is.
The first problem is illustrated by two lines, which are essentially the same in both pages:
this = tk.Frame.__init__(self, parent)
label = tk.Label(this, text="Welcome to Page 1")
I'm guessing you were assuming that this would be set to the instance of the Frame. However, the __init__ function returns None so this is set to None. When None is passed as the parent of a widget, that widget becomes a child of the root window.
The solution is to not use this. Use self:
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Welcome to Page 2")
The second problem is that you never add the page to the container with grid, pack, or place, so they will never be visible.
You need to change Add to actually add the page to the container:
def Add(name, parent, cont, ref):
PageList.frames[name] = ref(parent=parent, controller=cont)
PageList.frames[name].grid(row=0, column=0, sticky="nsew")
I've been trying for days to figure out how to just put grids in grids of objects. I've got two frames (which I guess are widgets in Tk?) I add one to the other, but the position of its widgets don't appear to respect the parent widgets (and it just overwrites them).
here is my MCVE
import tkinter as tk
class TestDoubleNested(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.grid()
self.test_label = tk.Label(text="AAAAAAAAA")
self.test_label.grid(row=0, column=0)
class TestNested(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.grid()
self.test_label = tk.Label(text="top_level_test_label")
self.test_label.grid(row=0, column=0)
self.test_label2 = tk.Label(text="top_level_test_label2")
self.test_label2.grid(row=0, column=1)
# expected to be in 3rd column...
self.test = TestDoubleNested(master)
self.test.grid(row=0, column=4)
test = TestNested()
test.master.title("Test Example")
test.master.maxsize(1000, 400)
test.master.wm_geometry("400x300")
test.mainloop()
No matter how I move around self.grid() invokations or change the column stuff, the display is the same:
As you can see AAAAAAAA displays on top of another widget from the parent, where ideally it should display to the side of everything.
You aren't specifying the master for each widget, so they are all being added to the root window. If you want widgets to be inside frames, you must specify the frame as the first argument when creating the widgets:
class TestDoubleNested(tk.Frame):
def __init__(self, master=None):
...
self.test_label = tk.Label(self, text="AAAAAAAAA")
...
class TestNested(tk.Frame):
def __init__(self, master=None):
...
self.test_label = tk.Label(self, ...)
self.test_label2 = tk.Label(self, ...)
self.test = TestDoubleNested(self)
...
I'm using Python and Tkinter to create a game, and I'm attempting to have different "screens" (main menu and a level editor so far) which are each a tkinter.Frame object, and in the class definition for the main menu screen (ScreenMainMenu), in __init__, on line 11, I'm attempting to use self.config() in order to change the width, height, and background color of the "main menu" tkinter.Frame. But when I run this code, the background is still grey. I'm guessing I am missing something obvious (I am still fairly new to Tkinter and classes). Any help would be much appreciated - thank you.
import tkinter as tk
WIDTH = {"MainMenu": 600, "Editor": 450}
HEIGHT = {"MainMenu": 500, "Editor": 501}
class ScreenMainMenu(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
parent.parent.geometry("{}x{}".format(WIDTH["MainMenu"], HEIGHT["MainMenu"]))
parent.config(width=WIDTH["MainMenu"], height=HEIGHT["MainMenu"])
self.config(width=WIDTH["MainMenu"], height=HEIGHT["MainMenu"], background="red")
self.grid()
self.create_widgets()
def create_widgets(self):
# self.logo = tk.Label(self, image=r"res\logo.png")
self.logo = tk.Label(self, text="Build The Galaxy")
self.button_new_game = tk.Button(self, text="New Game")
self.button_load_game = tk.Button(self, text="Load Game")
self.button_editor = tk.Button(self, text="Editor")
self.button_exit = tk.Button(self, text="Exit")
self.logo.grid(sticky="EW")
self.button_new_game.grid(row=1, sticky="EW")
self.button_load_game.grid(row=2, sticky="EW")
self.button_editor.grid(row=3, sticky="EW")
self.button_exit.grid(row=4, sticky="EW")
class ScreenEditor(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.grid()
parent.parent.geometry("{}x{}".format(WIDTH["Editor"], HEIGHT["Editor"]))
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.grid()
self.screen_open_main_menu()
def screen_open_main_menu(self):
self.screen_mainmenu = ScreenMainMenu(self)
def screen_open_editor(self):
self.screen_editor = ScreenEditor(self)
if __name__ == "__main__":
root = tk.Tk()
root.resizable(False, False)
root.title("Build The Galaxy")
main_app = MainApplication(root)
root.mainloop()
In Tkinter, normally all widgets adjust their size to fit the contents. That means, your frame is actually red (or whatever), it just fits its content. (You can check it commenting out the self.create_widgets() method.) You can force the size with the .grid_propagate() method, passing a 0 as parameter:
class ScreenMainMenu(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
parent.parent.geometry("{}x{}".format(WIDTH["MainMenu"], HEIGHT["MainMenu"]))
parent.config(width=WIDTH["MainMenu"], height=HEIGHT["MainMenu"])
self.config(width=WIDTH["MainMenu"], height=HEIGHT["MainMenu"], background="red")
self.grid()
self.create_widgets()
self.grid_propagate(0) # force the widget to a certain size
BTW, you can use super() to initialize the parent:
super().__init__(parent) # yes, without self!
Ive been trying out OOP for use with Tkinter - Im getting there (I think) slowly...
I wanted to build a structure where each frame is handled by its own class, including all of its widgets and functions. Perhaps I am coming from the wrong angle but that is what makes most logical sense to me. - Feel free to tell me if you agree / disagree!
I know why the problem is happening - when im calling each class my __init__ runs everytime and builds the relevant widgets regardless of whether they are already present in the frame. However, the only way I can think of getting round this would be to build each frame in the __init__ of my primary class GUI_Start. - Although this seems like a messy and un-organised soloution to the problem.
Is there a way I can achieve a structure where each class takes care of its own functions and widgets but doesn't build the frame each time?
See below for minimal example of the issue:
from Tkinter import *
class GUI_Start:
def __init__(self, master):
self.master = master
self.master.geometry('300x300')
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(0, weight=1)
self.win_colour = '#D2B48C'
self.frames = {}
for window in ['win1', 'win2']:
frame = Frame(self.master, bg=self.win_colour, bd=10, relief=GROOVE)
frame.grid(row=0, column=0, sticky='news')
setattr(self, window, frame)
self.frames[window] = frame
Page_1(self.frames)
def Next_Page(self, frames, controller):
controller(frames)
class Page_1(GUI_Start):
def __init__(self, master):
self.master = master
self.master['win1'].tkraise()
page1_label = Label(self.master['win1'], text='PAGE 1')
page1_label.pack(fill=X)
page1_button = Button(self.master['win1'], text='Visit Page 2...', command=lambda: self.Next_Page(self.master, Page_2))
page1_button.pack(fill=X, side=BOTTOM)
class Page_2(GUI_Start):
def __init__(self, master):
self.master = master
self.master['win2'].tkraise()
page2_label = Label(self.master['win2'], text='PAGE 2')
page2_label.pack(fill=X)
page2_button = Button(self.master['win2'], text='Back to Page 1...', command=lambda: self.Next_Page(self.master, Page_1))
page2_button.pack(fill=X, side=BOTTOM)
root = Tk()
gui = GUI_Start(root)
root.mainloop()
Feel free to critique the structure as I may be trying to approach this from the wrong angle!
Any feedback would be much appreciated!
Luke
The point of using classes is to encapsulate a bunch of behavior as a single unit. An object shouldn't modify anything outside of itself. At least, not by simply creating the object -- you can have methods that can have side effects.
In my opinion, the proper way to create "pages" is to inherit from Frame. All of the widgets that belong to the "page" must have the object itself as its parent. For example:
class PageOne(tk.Frame):
def __init__(self, parent):
# use the __init__ of the superclass to create the actual frame
tk.Frame.__init__(self, parent)
# all other widgets use self (or some descendant of self)
# as their parent
self.label = tk.Label(self, ...)
self.button = tk.Button(self, ...)
...
Once done, you can treat instances of this class as if they were a single widget:
root = tk.Tk()
page1 = PageOne(root)
page1.pack(fill="both", expand=True)
You can also create a base Page class, and have your actual pages inherit from it, if all of your pages have something in common (for example, a header or footer)
class Page(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
<code common to all pages goes here>
class PageOne(Page):
def __init__(self, parent):
# initialize the parent class
Page.__init__(self, parent)
<code unique to page one goes here>
Your use of OOP is not very logical here. Your main program is in the class GUI_start. If your pages inherit from GUI_start, basically you create a whole new program with every page instance you create. You should instead inherit from Frame as Bryan Oakley has pointed our in the comments. Here is a somewhat repaired version of what you have posted. The original one by Bryan is still much better.
from Tkinter import *
class GUI_Start:
def __init__(self, master):
self.master = master
self.master.geometry('300x300')
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(0, weight=1)
self.win_colour = '#D2B48C'
self.current_page=0
self.pages = []
for i in range(5):
page = Page(self.master,i+1)
page.grid(row=0,column=0,sticky='nsew')
self.pages.append(page)
for i in range(2):
page = Page_diff(self.master,i+1)
page.grid(row=0,column=0,sticky='nsew')
self.pages.append(page)
self.pages[0].tkraise()
def Next_Page():
next_page_index = self.current_page+1
if next_page_index >= len(self.pages):
next_page_index = 0
print(next_page_index)
self.pages[next_page_index].tkraise()
self.current_page = next_page_index
page1_button = Button(self.master, text='Visit next Page',command = Next_Page)
page1_button.grid(row=1,column=0)
class Page(Frame):
def __init__(self,master,number):
super().__init__(master,bg='#D2B48C')
self.master = master
self.master.tkraise()
page1_label = Label(self, text='PAGE '+str(number))
page1_label.pack(fill=X,expand=True)
class Page_diff(Frame):
def __init__(self,master,number):
super().__init__(master)
self.master = master
self.master.tkraise()
page1_label = Label(self, text='I am different PAGE '+str(number))
page1_label.pack(fill=X)
root = Tk()
gui = GUI_Start(root)
root.mainloop()