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 —
test_1230_510.png

Asked By: albert

||

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)
Answered By: Lafexlos