tkinter frame: fix frame height when changing its content
Question:
I am working on a self written simulation tool with a appropriate GUI. In general there are two main user modes. In the first one the user can enter several values for the desired calculation. After all calculations finshed, the user can switch to “results mode” by clicking the “Show Results” button in the lower right corner. By doing this, the main window with image in the upper part of the GUI does not change. However, the former input entries in the lower part of the GUI should change to message area, where errors, warnings or other information (duration, convergency, …) are shown.
I was able to make both GUI modes switchable, but the frame height of the lower frame (frame called lowerframe
) changes, which looks quite ugly.
I think the described behaviour is a result of the different heights of the frame’s content. In the first mode there a several entry
widgets, whereas in the second mode there is only one small label
widget containing foo
.
How can I prevent the frame’s height from changing dynamically? In other words: Is there a way to fix the height of the lowerframe
?
I specified height=200
(as well as for btnframe
in the lower right corner of the GUI), which does not seem to have any effect because the frame containing the foo
-label widget is definitely smaller than those 200 px.
— MWE —
#!/usr/bin/env python3
# coding: utf-8
from tkinter import *
from tkinter import ttk
from PIL import Image, ImageTk
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.parameternames = []
# create master window
self.master = master
# create master window
self.create_masterwindow()
def create_masterwindow(self):
"""
creates master window with menubar and sets GUI to input mode
"""
self.master.title('GUI')
self.master.geometry('1280x850')
self.master.resizable(width=FALSE, height=FALSE)
self.gui_inputmode(firstcall=True)
self.handle_resultsbtn(firstcall=True, showbtn=False)
def gui_inputmode(self, firstcall=False):
"""
turn GUI inputmode on
"""
if firstcall:
self.master.grid_columnconfigure(0, weight=1)
self.master.grid_rowconfigure(0, weight=1)
self.create_upperframe()
self.upperframe.grid(row=0, column=0, pady=20, padx=20, columnspan=2, sticky='NEW')
self.create_lowerframe()
self.lowerframe.grid(row=1, column=0, pady=10, padx=20, sticky='SEW')
self.create_btnframe()
self.btnframe.grid(row=1, column=1, pady=5, padx=20, sticky='NSEW')
self.put_inputfields()
self.handle_inputbtn(firstcall=True, showbtn=True)
self.pid = ImageTk.PhotoImage(file='test_1230_510.png')
self.pidlabel = Label(self.upperframe, image=self.pid)
self.pidlabel.pid = self.pid # store a reference to the image as an attribute of the widget
self.pidlabel.grid(row=0, column=0)
else:
self.handle_resultsbtn(showbtn=False)
self.handle_inputbtn()
self.error_label.grid_remove()
for i, subdict in enumerate(self.parameternames):
if type(subdict) == dict:
self.master.nametowidget(subdict['pname_entry_table']).grid()
def gui_resultsmode(self):
"""
turn GUI resultsmode on
"""
self.handle_inputbtn(showbtn=False)
self.handle_resultsbtn()
for i, subdict in enumerate(self.parameternames):
if type(subdict) == dict:
self.master.nametowidget(subdict['pname_entry_table']).grid_remove()
self.error_label = ttk.Label(self.lowerframe, text='foo')
self.error_label.grid(row=0, column=0, sticky='E')
def create_upperframe(self):
"""
creates frame for PID and loads appropriate PID (GUI input mode)
"""
if hasattr(self, 'upperframe'):
del self.upperframe
self.upperframe = Frame(self.master, width=1235, height=510, relief=RIDGE, bd=2)
def create_lowerframe(self):
"""
creates lower frame
"""
if hasattr(self, 'lowerframe'):
del self.lowerframe
self.lowerframe = Frame(self.master, relief=RIDGE, bd=2, height=200)
def put_inputfields(self):
"""
creates entry widgets in lowerframe to read parameter as user input (GUI input mode)
"""
for i in range(0, 18):
r = (i // 4)
c = (i % 4)*3
self.lowerframe.grid_columnconfigure(c, weight=1)
self.lowerframe.grid_columnconfigure(c+2, weight=1)
entry_table = Entry(self.lowerframe, width=10, justify='right')
entry_table.insert(0, format(float(42), ".3f"))
entry_table.grid(row=r, column=c+1, pady=5, padx=0, sticky='EW')
subdict = {}
subdict['pname_entry_table'] = str(entry_table)
self.parameternames.append(subdict)
def handle_inputbtn(self, firstcall=False, showbtn=True):
"""
creates buttons in btnframe when GUI in input mode
"""
if firstcall:
self.startanalysis_btn = Button(self.btnframe, width=15, text='Start Analysis')
self.abortanalysis_btn = Button(self.btnframe, width=15, text='Abort Analysis')
self.resetanalysis_btn = Button(self.btnframe, width=15, text='Reset Analysis')
self.showresults_btn = Button(self.btnframe, width=15, text='Show Results', command=self.gui_resultsmode)
if showbtn:
self.startanalysis_btn.grid(pady=5)
self.abortanalysis_btn.grid(pady=5)
self.resetanalysis_btn.grid(pady=5)
self.showresults_btn.grid(pady=5, sticky='S')
if not showbtn:
self.startanalysis_btn.grid_remove()
self.abortanalysis_btn.grid_remove()
self.resetanalysis_btn.grid_remove()
self.showresults_btn.grid_remove()
def handle_resultsbtn(self, firstcall=False, showbtn=True):
"""
creates buttons in btnframe when GUI in result mode
"""
if firstcall:
self.exportdata_btn = Button(self.btnframe, width=15, text='Export Data')
self.exportgraphs_btn = Button(self.btnframe, width=15, text='Export Graphs')
self.createreport_btn = Button(self.btnframe, width=15, text='Create Report')
self.showinput_btn = Button(self.btnframe, width=15, text='Show Input', command=self.gui_inputmode)
if showbtn:
self.exportdata_btn.grid(pady=5)
self.exportgraphs_btn.grid(pady=5)
self.createreport_btn.grid(pady=5)
self.showinput_btn.grid(pady=5, sticky='S')
if not showbtn:
self.exportdata_btn.grid_remove()
self.exportgraphs_btn.grid_remove()
self.createreport_btn.grid_remove()
self.showinput_btn.grid_remove()
def create_btnframe(self):
"""
creates frame for buttons in lower right corner
"""
if hasattr(self, 'btnframe'):
del self.btnframe
self.btnframe = Frame(self.master, width=130, height=200)
self.btnframe.grid_rowconfigure(3, weight=1)
master = Tk()
app = Window(master)
master.mainloop()
— test image —
Answers:
.grid_propagate(flag)
is what you are looking for.
After setting height and width of your frame, if you disable propagation, its size will be same no matter how many widgets you have in it.
As an example, I wrote little code. Eventhough it looks ugly, shows how propagation works.
import tkinter as tk
root = tk.Tk()
root.geometry("500x300")
def add():
tk.Entry(frame).grid()
def disable():
frame.configure(height=frame["height"],width=frame["width"])
frame.grid_propagate(0)
def enable():
frame.grid_propagate(1)
frame = tk.Frame(root, height=100,width=150,bg="black")
frame.grid(row=1,column=0)
tk.Button(root, text="add widget", command=add).grid(row=0,column=0)
tk.Button(root, text="disable propagation", command=disable).grid(row=0,column=1)
tk.Button(root, text="enable propagation", command=enable).grid(row=0,column=2)
I am working on a self written simulation tool with a appropriate GUI. In general there are two main user modes. In the first one the user can enter several values for the desired calculation. After all calculations finshed, the user can switch to “results mode” by clicking the “Show Results” button in the lower right corner. By doing this, the main window with image in the upper part of the GUI does not change. However, the former input entries in the lower part of the GUI should change to message area, where errors, warnings or other information (duration, convergency, …) are shown.
I was able to make both GUI modes switchable, but the frame height of the lower frame (frame called lowerframe
) changes, which looks quite ugly.
I think the described behaviour is a result of the different heights of the frame’s content. In the first mode there a several entry
widgets, whereas in the second mode there is only one small label
widget containing foo
.
How can I prevent the frame’s height from changing dynamically? In other words: Is there a way to fix the height of the lowerframe
?
I specified height=200
(as well as for btnframe
in the lower right corner of the GUI), which does not seem to have any effect because the frame containing the foo
-label widget is definitely smaller than those 200 px.
— MWE —
#!/usr/bin/env python3
# coding: utf-8
from tkinter import *
from tkinter import ttk
from PIL import Image, ImageTk
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.parameternames = []
# create master window
self.master = master
# create master window
self.create_masterwindow()
def create_masterwindow(self):
"""
creates master window with menubar and sets GUI to input mode
"""
self.master.title('GUI')
self.master.geometry('1280x850')
self.master.resizable(width=FALSE, height=FALSE)
self.gui_inputmode(firstcall=True)
self.handle_resultsbtn(firstcall=True, showbtn=False)
def gui_inputmode(self, firstcall=False):
"""
turn GUI inputmode on
"""
if firstcall:
self.master.grid_columnconfigure(0, weight=1)
self.master.grid_rowconfigure(0, weight=1)
self.create_upperframe()
self.upperframe.grid(row=0, column=0, pady=20, padx=20, columnspan=2, sticky='NEW')
self.create_lowerframe()
self.lowerframe.grid(row=1, column=0, pady=10, padx=20, sticky='SEW')
self.create_btnframe()
self.btnframe.grid(row=1, column=1, pady=5, padx=20, sticky='NSEW')
self.put_inputfields()
self.handle_inputbtn(firstcall=True, showbtn=True)
self.pid = ImageTk.PhotoImage(file='test_1230_510.png')
self.pidlabel = Label(self.upperframe, image=self.pid)
self.pidlabel.pid = self.pid # store a reference to the image as an attribute of the widget
self.pidlabel.grid(row=0, column=0)
else:
self.handle_resultsbtn(showbtn=False)
self.handle_inputbtn()
self.error_label.grid_remove()
for i, subdict in enumerate(self.parameternames):
if type(subdict) == dict:
self.master.nametowidget(subdict['pname_entry_table']).grid()
def gui_resultsmode(self):
"""
turn GUI resultsmode on
"""
self.handle_inputbtn(showbtn=False)
self.handle_resultsbtn()
for i, subdict in enumerate(self.parameternames):
if type(subdict) == dict:
self.master.nametowidget(subdict['pname_entry_table']).grid_remove()
self.error_label = ttk.Label(self.lowerframe, text='foo')
self.error_label.grid(row=0, column=0, sticky='E')
def create_upperframe(self):
"""
creates frame for PID and loads appropriate PID (GUI input mode)
"""
if hasattr(self, 'upperframe'):
del self.upperframe
self.upperframe = Frame(self.master, width=1235, height=510, relief=RIDGE, bd=2)
def create_lowerframe(self):
"""
creates lower frame
"""
if hasattr(self, 'lowerframe'):
del self.lowerframe
self.lowerframe = Frame(self.master, relief=RIDGE, bd=2, height=200)
def put_inputfields(self):
"""
creates entry widgets in lowerframe to read parameter as user input (GUI input mode)
"""
for i in range(0, 18):
r = (i // 4)
c = (i % 4)*3
self.lowerframe.grid_columnconfigure(c, weight=1)
self.lowerframe.grid_columnconfigure(c+2, weight=1)
entry_table = Entry(self.lowerframe, width=10, justify='right')
entry_table.insert(0, format(float(42), ".3f"))
entry_table.grid(row=r, column=c+1, pady=5, padx=0, sticky='EW')
subdict = {}
subdict['pname_entry_table'] = str(entry_table)
self.parameternames.append(subdict)
def handle_inputbtn(self, firstcall=False, showbtn=True):
"""
creates buttons in btnframe when GUI in input mode
"""
if firstcall:
self.startanalysis_btn = Button(self.btnframe, width=15, text='Start Analysis')
self.abortanalysis_btn = Button(self.btnframe, width=15, text='Abort Analysis')
self.resetanalysis_btn = Button(self.btnframe, width=15, text='Reset Analysis')
self.showresults_btn = Button(self.btnframe, width=15, text='Show Results', command=self.gui_resultsmode)
if showbtn:
self.startanalysis_btn.grid(pady=5)
self.abortanalysis_btn.grid(pady=5)
self.resetanalysis_btn.grid(pady=5)
self.showresults_btn.grid(pady=5, sticky='S')
if not showbtn:
self.startanalysis_btn.grid_remove()
self.abortanalysis_btn.grid_remove()
self.resetanalysis_btn.grid_remove()
self.showresults_btn.grid_remove()
def handle_resultsbtn(self, firstcall=False, showbtn=True):
"""
creates buttons in btnframe when GUI in result mode
"""
if firstcall:
self.exportdata_btn = Button(self.btnframe, width=15, text='Export Data')
self.exportgraphs_btn = Button(self.btnframe, width=15, text='Export Graphs')
self.createreport_btn = Button(self.btnframe, width=15, text='Create Report')
self.showinput_btn = Button(self.btnframe, width=15, text='Show Input', command=self.gui_inputmode)
if showbtn:
self.exportdata_btn.grid(pady=5)
self.exportgraphs_btn.grid(pady=5)
self.createreport_btn.grid(pady=5)
self.showinput_btn.grid(pady=5, sticky='S')
if not showbtn:
self.exportdata_btn.grid_remove()
self.exportgraphs_btn.grid_remove()
self.createreport_btn.grid_remove()
self.showinput_btn.grid_remove()
def create_btnframe(self):
"""
creates frame for buttons in lower right corner
"""
if hasattr(self, 'btnframe'):
del self.btnframe
self.btnframe = Frame(self.master, width=130, height=200)
self.btnframe.grid_rowconfigure(3, weight=1)
master = Tk()
app = Window(master)
master.mainloop()
— test image —
.grid_propagate(flag)
is what you are looking for.
After setting height and width of your frame, if you disable propagation, its size will be same no matter how many widgets you have in it.
As an example, I wrote little code. Eventhough it looks ugly, shows how propagation works.
import tkinter as tk
root = tk.Tk()
root.geometry("500x300")
def add():
tk.Entry(frame).grid()
def disable():
frame.configure(height=frame["height"],width=frame["width"])
frame.grid_propagate(0)
def enable():
frame.grid_propagate(1)
frame = tk.Frame(root, height=100,width=150,bg="black")
frame.grid(row=1,column=0)
tk.Button(root, text="add widget", command=add).grid(row=0,column=0)
tk.Button(root, text="disable propagation", command=disable).grid(row=0,column=1)
tk.Button(root, text="enable propagation", command=enable).grid(row=0,column=2)