pythontkintergrid-layouttkinter-layouttkinter-text

Tkinter text widget unexpected overflow inside grid manager


Assume a simple 3row-1col grid, where 2nd widget is a label, while 1st and 3rd widgets are Text. sticky and weight settings are most certainly correct. Grid dimensions are defined and shouldn't be dictated by its content.

The problem is that Texts in 1st and 3rd rows share the space as if the Label in the 2nd row didn't exist. Both Texts occupy half the grid height each.

Weirder still, the Label is certainly there. You can see it if the grid gets stretched enough to exceed default Text height, which is around 24 lines.

I will appreciate any clarification on this weird behavior.

I am open to alternatives (pack?) that would allow me to combine Text-Label-Text in one column so that each one takes all the width available, Label takes minimal necessary height, and Texts share the remaining grid height equally.

What I've tried

The docs (1,2) regarding the grid manager show, that parent.rowconfigure(n, weight=1) for nth row ensures correct resizing, while child.grid(row=r, column=c, sticky="news") stretches the widget in the r,c cell of the grid.

Sadly, all the other SO questions dance around these concepts, which fail to help in this situation.

I've made a test application. If you run it with with_text=False, you can see that grid height is distributed as expected between three Labels. If you then run it with with_text=True), you can see that 1st and 3rd rows with Text widgets occupy half the grid height both. If you stretch the app enough vertically, the 2nd row with the Label does appear.

from tkinter import *
import tkinter.font as tkFont

def application(master, with_text):
    for r in range(3):
        master.rowconfigure(r, weight=1)
        for c in range(1):
            master.columnconfigure(c, weight=1)
            if r == 1 and c == 0:
                lbl = Label(master, text="Hello")
                lbl.grid(row=r, column=c, sticky="news")
                continue
            if with_text:
                lbl = Text(master, bd=3, relief=SUNKEN)
                lbl.grid(row=r, column=c, sticky="news")
                lbl.insert(END, 1000 + r + c)
            else:
                lbl = Label(master, text="Hi")
                lbl.grid(row=r, column=c, sticky="news")

root = Tk()
root.geometry("200x100")
root.title('Text grid overflow')
mono_font = tkFont.nametofont("TkFixedFont")
mono_font.configure(size=8)

with_text=False
application(root, with_text)
root.mainloop()

with_text = False

a 200by100 window, with three rows, each being a Label

with_text = True

a 200by100 window with apparently two rows, each being a Text. The third row in the middle, which is a Label, is not visible

with_text=True, stretched vertically

a 200by750 window with three rows: 1st and 3rd are Texts, 2nd is a Label. Grid height accommodated all 24 lines for both Texts, and only then the Label became visible


Solution

  • Overview

    The factors that contribute to the problem:

    This is what happens:

    grid tries to fit two 80 character tall widgets and one one-character tall widget into the window. They won't fit because you forced the window to be 200x100 pixels.

    So, it has to start shrinking each widget down from its preferred size to make them fit. To do this, it takes one pixel from each widget since they all share the same weight.

    If it still doesn't fit, it takes one more from each widget. After a dozen or so attempts, the label will become zero in height so it no longer is visible. It then continues to shrink the text widgets one pixel at a time until they fit in the window.

    Note: this behavior is documented in the grid man page, where it states "For containers whose size is smaller than the requested layout, space is taken away from columns and rows according to their weights."

    Keeping the label visible

    To keep the label at the minimum size and have the text widgets expand to take up all of the extra space, give the row with the label a weight of zero. That will force the grid algorithm to skip over the label when removing pixels in order to make everything fit into the window.

    master.rowconfigure(1, weight=0)