Tkinter MultiLevel Inheritance for Frames - python

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.

Related

how to remove an extra window in python Tkinter?

M trying to create a desktop app but facing some problem while switching between frames using button. Its working all fine but it gives me an extra blank window(consist nothing) when I run my project.
Below is my code. Please suggest me any changes or error in my code.
import tkinter as tk
class Toplevel1(tk.Tk):
def __init__(self, top=None, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
top.geometry("600x450+306+137")
top.minsize(120, 1)
top.maxsize(1370, 749)
top.resizable(1, 1)
top.title("New Toplevel")
top.configure(background="#d9d9d9")
self.MenuFrame = tk.LabelFrame(top)
self.MenuFrame.place(relx=0.0, rely=0.0, relheight=0.989, relwidth=0.25)
self.MenuFrame.configure(relief='groove')
self.MenuFrame.configure(foreground="black")
self.MenuFrame.configure(background="#400080")
self.Button1 = tk.Button(self.MenuFrame)
self.Button1.place(relx=0.133, rely=0.067, height=24, width=107, bordermode='ignore')
self.Button1.configure(background="#00ff80")
self.Button1.configure(foreground="#000000")
self.Button1.configure(pady="0")
self.Button1.configure(text='''Button 1''')
self.Button1.configure(command= lambda : self.show_frame(ButtonOne))
self.MainWindow = tk.LabelFrame(top)
self.MainWindow.place(relx=0.267, rely=0.111, relheight=0.767, relwidth=0.7)
self.MainWindow.configure(relief='groove')
self.MainWindow.configure(foreground="black")
self.MainWindow.configure(background="#808040")
self.frames = {}
for F in (StartPage, ButtonOne):
frame = F(self.MainWindow)
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):
tk.Frame.__init__(self, parent)
# label of frame Layout 2
# second window frame page1
class ButtonOne(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Button 1 is pressed")
label.pack()
if __name__ == '__main__':
root = tk.Tk()
app = Toplevel1(root)
root.mainloop()
This causes a window to be created:
class Toplevel1(tk.Tk):
def __init__(self, top=None, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
...
...
app = Toplevel1(root)
And this causes a window to be created:
root = tk.Tk()
If you don't want Toplevel1 to be a separate window, don't inherit from tk.Tk. Instead, you can inherit from tk.Frame, and then you can call pack, place, or grid to add this to the root window.
However, it looks like you're intending for your Toplevel1 to be the true root window, so you can remove root = tk.Tk(). You can then do app.mainloop() rather than root.mainloop() You'll also have to make a few other adjustments, such as using self rather than top inside Toplevel1.__init__.
Put another way, if you want only one window then either inherit from tk.Tk or create an instance of tk.Tk, but don't do both.
As on tkinter callbacks, tk.Tk in class Toplevel1 is about the same as Toplevel1=tk.Tk() which, in a sesne opens a new window. the third line from whitespace, tk.Tk.__init__(self, *args, **kwargs), it becomes useless.

Setting the size of a tkinter frame inside a class

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()

How to switch between different tkinter canvases from a start-up page and return back to start-up page in sub-canvas

I have created a start-up canvas, which contains buttoms to shfit to two other sub-canvases. In addition, in those two sub-canvases, buttom to return to start-up canvas is created. However, after I enter the sub-canvas, I fail to return to start-up canvas. That is to say, when I click the buttom to return to the start-up canvas, it will create a start-up canvas beside the sub-canvas instead of closing the subcanvas and shifting to the start-up canvas. Is there any way to switch between canvases and return to the main canvas? Thank you!
import tkinter as tk
from tkinter import ttk
import re
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._Canvas= None
self.switch_Canvas(StartUpPage)
def switch_Canvas(self, Canvas_class):
new_Canvas = Canvas_class(self)
if self._Canvas is not None:
self._Canvas.destroy()
self._Canvas = new_Canvas
self._Canvas.pack()
class StartUpPage(tk.Canvas):
def __init__(self, master, *args, **kwargs):
tk.Canvas.__init__(self, master, *args, **kwargs)
tk.Frame(self)
tk.Label(self, text="Example").grid(column = 0, row = 0)
tk.Button(self, text="Canvas1",
command=lambda: master.switch_Canvas(PageOne)).grid(column = 0, row = 1)
tk.Button(self, text="Canvas2",
command=lambda: master.switch_Canvas(PageTwo)).grid(column = 0, row = 2)
class PageOne(tk.Canvas, tk.Tk):
def __init__(self, master, *args, **kwargs):
root = tk.Canvas.__init__(self, *args, **kwargs)
self.frame1 = tk.Frame(root, width=430)
self.frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
tk.Label(self.frame1, text="First Canvas").pack(side="top", fill="x", pady=5)
tk.Button(self.frame1, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage)).pack()
class PageTwo(tk.Canvas, tk.Tk):
def __init__(self, master, *args, **kwargs):
root = tk.Canvas.__init__(self, *args, **kwargs)
self.frame2 = tk.Frame(root, width=430)
self.frame2.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
tk.Label(self.frame2, text="Second Canvas").pack(side="top", fill="x", pady=5)
tk.Button(self.frame2, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage)).pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
So, few things:
First: There's the wrong parent/master relationship in PageOne and PageTwo in
def __init__(self, master, *args, **kwargs):
Since you're inheriting from tk.Canvas, the new instance of the class has parent master which in itself get's passed from
SampleApp's switchCanvas as Canvas_class(self) meaning master is . or the (main/root) window.
But then (we're still inside PageOne) you're making root = tk.Canvas.__init__(self, *args, **kwargs) which becomes None since initializers return None.
Then you're creating a new Frame whose parent is root which is None.
In short
PageOne and PageTwo are (also) new instances of Canvas whose parent is the main window, or the instance made by SampleApp.
root aka the Frame's parent is None (Which I even don't know how that affects it when you position it byself.frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True))
Hence I assume you're trying to making PageOne and PageTwo instances of Frame and put a Canvas inside them.
These are the few changes I made to the two classes: Their instances are now both of (type) tk.Frame that contains the other widgets.
class PageTwo(tk.Frame): # Sub-lcassing tk.Frame
def __init__(self, master, *args, **kwargs):
# self is now an istance of tk.Frame
tk.Frame.__init__(self,master, *args, **kwargs)
# make a new Canvas whose parent is self.
self.canvas = tk.Canvas(self,bg='yellow', width=430)
self.label = tk.Label(self, text="Second Canvas").pack(side="top", fill="x", pady=5)
self.button = tk.Button(self, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage))
self.button.pack()
# pack the canvas inside the self (frame).
self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
#print('is instance',isinstance(self,tk.Frame))
Second: Keeping track of which instances/objects are created, instead of continuously spawning new ones. I decided to keep it simple and create an internal dictionary whose keys are the classes of StratUpPage,PageOne or PageTwo meaning each class gets to have one instance that can be switched to.
def switch_Canvas(self, Canvas_class):
# Unless the dictionary is empty, hide the current Frame (_mainCanvas is a frame)
if self._mainCanvas:
self._mainCanvas.pack_forget()
# Modification 2: is the Class type passed is a one we have seen before?
canvas = self._allCanvases.get(Canvas_class, False)
# if Canvas_class is a new class type, canvas is False
if not canvas:
# Instantiate the new class
canvas = Canvas_class(self)
# Store it's type in the dictionary
self._allCanvases[Canvas_class] = canvas
# Pack the canvas or self._mainCanvas (these are all frames)
canvas.pack(pady = 60)
# and make it the 'default' or current one.
self._mainCanvas = canvas
Entire Code
import tkinter as tk
from tkinter import ttk
import re
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._mainCanvas= None
# The dictionary to hold the class type to switch to
# Each new class passed here, will only have instance or object associated with it (i.e the result of the Key)
self._allCanvases = dict()
# Switch (and create) the single instance of StartUpPage
self.switch_Canvas(StartUpPage)
def switch_Canvas(self, Canvas_class):
# Unless the dictionary is empty, hide the current Frame (_mainCanvas is a frame)
if self._mainCanvas:
self._mainCanvas.pack_forget()
# is the Class type passed one we have seen before?
canvas = self._allCanvases.get(Canvas_class, False)
# if Canvas_class is a new class type, canvas is False
if not canvas:
# Instantiate the new class
canvas = Canvas_class(self)
# Store it's type in the dictionary
self._allCanvases[Canvas_class] = canvas
# Pack the canvas or self._mainCanvas (these are all frames)
canvas.pack(pady = 60)
# and make it the 'default' or current one.
self._mainCanvas = canvas
class StartUpPage(tk.Canvas):
def __init__(self, master, *args, **kwargs):
tk.Canvas.__init__(self, master, *args, **kwargs)
tk.Frame(self) # Here the parent of the frame is the self instance of type tk.Canvas
tk.Label(self, text="Example").grid(column = 0, row = 0)
tk.Button(self, text="Canvas1",
command=lambda: master.switch_Canvas(PageOne)).grid(column = 0, row = 1)
tk.Button(self, text="Canvas2",
command=lambda: master.switch_Canvas(PageTwo)).grid(column = 0, row = 2)
class PageOne(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self,master, *args, **kwargs)
self.canvas = tk.Canvas(self,bg='blue', width=430)
print('got',self,master,args,kwargs)
tk.Label(self, text="First Canvas").pack(side="top", fill="x", pady=5)
tk.Button(self, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage)).pack()
self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
class PageTwo(tk.Frame): # Sub-lcassing tk.Frame
def __init__(self, master, *args, **kwargs):
# self is now an istance of tk.Frame
tk.Frame.__init__(self,master, *args, **kwargs)
# make a new Canvas whose parent is self.
self.canvas = tk.Canvas(self,bg='yellow', width=430)
self.label = tk.Label(self, text="Second Canvas").pack(side="top", fill="x", pady=5)
self.button = tk.Button(self, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage))
self.button.pack()
# pack the canvas inside the self (frame).
self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
#print('is instance',isinstance(self,tk.Frame))
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
EDIT: Visual Demo

Why tkinter grid, columnconfigure and rowconfigure value not changing dynamically?

Please help anyone
You can read all details as an inline comment.
I have created three classes LeftFrame, RightFrame, DynamicWindow
In DynamicWindow I am inheriting RightFrame
Step 1
LeftFrame, in column 0 with minsize 350
Step 2
RightFrame, in column 1 with weight 1, capturing all available space
Step 3
DynamicWindow, ingeriting RightFrame , Here is main problem, Please read the code.
import tkinter as tk
from win32api import GetMonitorInfo, MonitorFromPoint
root = tk.Tk()
monitor_info = GetMonitorInfo(MonitorFromPoint((0, 0)))['Work']
root.geometry(f'{monitor_info[2]}x{monitor_info[3]}')
root.state('zoomed')
root.columnconfigure(0, minsize=350) # Width of left frame
root.columnconfigure(1, weight=1) # All available space for right frame
root.rowconfigure(0, weight=1) # Full screen height for both frame
class LeftFrame(tk.Frame):
"""
Left Frame
"""
def __init__(self, container):
super().__init__(container)
self.config(bg='red')
self.grid(row=0, column=0, sticky='nsew')
class RightFrame(tk.Frame):
"""
Right Frame:
Divided into three section head frame, middle frame and bottom frame
head frame contains button
middle frame contains dynamically changeable frame. Here I am facing problem,
frame not able to take actual size according to weight and minsize that I given.
"""
def __init__(self, container):
super().__init__(container)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, minsize=30)
self.rowconfigure(1, weight=1)
self.rowconfigure(2, minsize=30)
self.config(bg='green')
self.grid(row=0, column=1, sticky='nsew')
self.head_frame = tk.Frame(self, bg='orange')
self.head_frame.grid(row=0, column=0, sticky='nsew')
self.head_frame.rowconfigure(0, weight=1)
self.first_screen_button = tk.Button(self.head_frame, text='First Screen')
self.first_screen_button.grid(row=0, column=0, sticky='nsew', ipadx=20)
self.second_screen_button = tk.Button(self.head_frame, text='Second Screen')
self.second_screen_button.grid(row=0, column=1, sticky='nsew', ipadx=20)
self.middle_frame = tk.Frame(self, bg='green')
self.middle_frame.grid(row=1, column=0, sticky='nsew')
self.bottom_frame = tk.Frame(self, bg='orange')
self.bottom_frame.grid(row=2, column=0, sticky='nsew')
class DynamicWindow(RightFrame):
def __init__(self, container):
super().__init__(container)
self.first_screen_button.config(command=self.first_screen)
self.second_screen_button.config(command=self.second_screen)
self.first_screen() # I am calling this here becuase on first click on first screen button
# window don't appear. You can check by comment this code.
# Can anyone please tell me why first screen not appear on first click.
def first_screen(self):
"""
First screen that I want to appear when I click on button
It has two frame
"""
for widget in self.middle_frame.winfo_children():
# Want to destroy all available widget in middle frame
widget.destroy()
self.middle_frame.columnconfigure(0, weight=1)
# Configuring size and weight but this is not working properly
self.middle_frame.columnconfigure(1, minsize=30)
self.middle_frame.rowconfigure(0, weight=1)
self.middle_frame.rowconfigure(1, weight=0)
main_chart_window = tk.Frame(self.middle_frame, bg='#4d4d4d')
main_chart_window.grid(row=0, column=0, sticky='nsew')
toolbar = tk.Frame(self.middle_frame, bg='red')
toolbar.grid(row=0, column=1, sticky='nsew')
def second_screen(self):
"""
This is not working in proper way
I am not able to reconfigure weight of middle frame
I want this window in full screen in middle frame
Here you will notice column 1 taking minsize 30, can anyone solve this
"""
for widget in self.middle_frame.winfo_children():
widget.destroy()
self.middle_frame.columnconfigure(0, weight=1)
self.middle_frame.rowconfigure(0, weight=1)
second_screen_window = tk.Frame(self.middle_frame, bg='purple')
second_screen_window.grid(row=0, column=0, sticky='nsew')
left_frame = LeftFrame(root)
dynamic_window = DynamicWindow(root)
root.mainloop()
I made it all work for you. I lost interest in fighting with your code/method so, I completely rewrote the code from scratch and devised a different method. All of the issues that you highlighted have been resolved. The structure of my code should be much easier to work with. The main issue is that you were destroying children, but you weren't destroying the column and/or row that the children were in. You basically can't. Using grid_forget() even in conjunction with destroy() or grid_remove() doesn't seem to remove the grid cell.
changes:
Every major widget has been separated into it's own class
Names have been changed to reflect the actual purpose of each widget (to the best of my ability based on your example)
We swap 'main display' widgets by removing/re-instating the entire widget ~ instead of destroying/recreating all of it's children
a lambda is used in the button command to pass the desired 'main display' to the method that does the swapping
we never use super() to instantiate a class. We specifically refer to the super by classname
all args and kwargs are maintained, so we can treat our custom widgets like their super
we only import exactly what we need (my preference)
The comments should tell you the rest, but if there is confusion, point it out to me in the comment section and I will respond with a more detailed explanation.
widgets.py
from tkinter import Frame, Button
from typing import List, Dict, Callable
from dataclasses import dataclass
#a simple "typedef" for storing menu button data
#dataclass
class MenuData_t:
func:Callable #method the command lambda will call
buttons:List[Dict] #Button(**kwargs)
targets:List[Frame] #'main display' to switch to
griddata:List[Dict] #.grid(**kwargs)
'''
this replaces your 'head_frame'
it also provides an interface to concoct all of the buttons that will swap 'main displays'
if you need other types of buttons you will need to manually create them in __init__
considerations have been made in init_displayswap_menu for existing buttons
'''
class MenuFrame(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
def init_displayswap_menu(self, md:MenuData_t):
c = len(self.winfo_children())
for i, (b, t, g) in enumerate(zip(md.buttons, md.targets, md.griddata)):
self.__dict__[f'swap_btn{i+1}'] = Button(self, command=lambda m=t: md.func(m), **b)
self.__dict__[f'swap_btn{i+1}'].grid(row=0, column=i+c, **g)
#this replaces your "bottom_frame"
class Footer(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
#this replaces your "first_screen"
class PrimaryFrame(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.chart = Frame(self, bg='#4d4d4d')
self.chart.grid(row=0, column=0, sticky='nswe')
self.toolbar = Frame(self, bg='red')
self.toolbar.grid(row=0, column=1, sticky='nswe')
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, minsize=30)
self.grid_rowconfigure(0, weight=1)
#this replaces your "second_screen"
class SecondaryFrame(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
#this replaces your "LeftFrame"
class Sidebar(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
#this replaces your "RightFrame" AND "DynamicWindow"
class MainFrame(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
##INSTANTIATE
#menu
self.menu = MenuFrame(self, bg='orange')
self.menu.grid(row=0, column=0, sticky='nsew')
#main display
self.current = None #for storing currently used 'main display'
self.primary = PrimaryFrame(self, bg='green')
self.secondary = SecondaryFrame(self, bg='purple')
#footer
self.footer = Footer(self, bg='orange')
self.footer.grid(row=2, column=0, sticky='nsew')
##UTILIZE
#concoct main display swap menu
'''
append accordingly to the 3 lists to create more buttons that will switch frames
done this way so button creation can remain in MenuFrame but use remote data
row, column and command are managed in MenuFrame
'''
self.menu.init_displayswap_menu(MenuData_t(
self.main_display, #method the command lambda will call
[{'text':'Primary'}, #Button(**kwargs)
{'text':'Secondary'},
],
[self.primary, #'main display' to switch to
self.secondary,
],
[{'sticky':'nswe','ipadx':20}, #.grid(**kwargs)
{'sticky':'nswe','ipadx':20},
]
))
#init main display
'''
I could have called main_display directly but this illustrates 2 things
1: how to virtually click a button
2: how to access the buttons that MenuFrame created in it's __dict__
'''
self.menu.swap_btn1.invoke()
#configure grid
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, minsize=30)
self.grid_rowconfigure(1, weight=1)
self.grid_rowconfigure(2, minsize=30)
#replaces your 'first_screen' AND 'second_screen' methods
def main_display(self, frame):
if self.current is not frame: #only swap if we aren't requesting the current 'main display'
if self.current:
self.current.grid_remove() #remove current from the grid, instead of destroy
self.current = frame #set new current and add it to the grid
self.current.grid(row=1, column=0, sticky='nsew')
main.py
from win32api import GetMonitorInfo, MonitorFromPoint
from widgets import Sidebar, MainFrame
from tkinter import Tk
#This is your "root"
class Application(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
Sidebar(self, bg='red').grid(row=0, column=0, sticky='nswe')
MainFrame(self, bg='black').grid(row=0, column=1, sticky='nswe')
#configure grid
self.grid_columnconfigure(0, minsize=350)
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(0, weight=1)
#kick off the entire app with proper PEP8
if __name__ == '__main__':
monitor_info = GetMonitorInfo(MonitorFromPoint((0, 0)))['Work']
app = Application()
app.title("Manish Pushpam's Bad-Ass Application")
app.geometry(f'{monitor_info[2]}x{monitor_info[3]}')
app.minsize(800, 600)
app.mainloop()

Unable to change background color of Frame widget in Tkinter?

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!

Categories