1

I have a piece of code that creates few widgets and places them in a grid (for example 3x2). When a particular event occurs, I want the grid to be redrawn, but this time the switching positions (thus creating a grid 2x3). For the sake of simplicity, I've created a sample code, where a button click does the swapping.

PROBLEM: clicking the button, the widgets get rearranged on the grid as expected, but then the window shows a "third" empty column (and symmetrically, transposing them again creates a third empty row). It is like the grid pattern remembers that there was a third column before swapping. I've tried with destroy()ing the widgets, grid_forget() and grid_remove(), but still the grid pattern "remembers" the extra column/row it had. I'm suspecting the problem lies in the grid_rowconfigure and grid_columnconfigure that I have to call to get the resizable effect. But I'm not sure how to un-configure them... so to speak.

Does anyone know how to get the grid redrawn with only the column and rows needed for the new layout of displayed widgets?

import tkinter as tk


class TestGrid(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master, bg='white')
        colors = ['cyan', 'magenta', 'green', 'gold', 'lavender', 'purple']
        self.switch = True
        self.widgets = []
        for i in range(6):
            self.widgets.append(tk.Label(self, text=i, bg=colors[i]))

    def refresh(self, *args):
        self.switch = not self.switch
        for w in self.widgets:
            w.grid_forget()

        positions = [(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1)]
        for i in range(6):
            c, r = positions[i][0], positions[i][1]
            if self.switch:
                c, r = r, c
            self.grid_columnconfigure(c, weight=1, uniform="aaa")
            self.grid_rowconfigure(r, weight=1, uniform="bbb")
            self.widgets[i].grid(column=c, row=r, sticky=tk.NSEW)

def main():
    root = tk.Tk()
    root.geometry('400x400+20+20')
    btn = tk.Button(root, text="click me")
    btn.pack(side=tk.TOP, fill=tk.X)
    frm = TestGrid(root)
    frm.pack(fill=tk.BOTH, expand=1, padx=5, pady=5)
    frm.refresh()
    btn.bind("<Button-1>", frm.refresh)

    root.mainloop()

if __name__ == '__main__':
    main()
geizio
  • 105
  • 1
  • 16
  • grid_forget() and grid_remove() only hide widgets, they do not destroy them. One workaround is to destroy the widget directly, i.e., btn.destroy(), as pointed out here https://stackoverflow.com/questions/66299025/how-to-delete-a-widget-in-tkinter – yamex5 Mar 04 '22 at 02:24

2 Answers2

0

Just wanted to share that the only way I could find to solve the problem was to simply destroy() the whole frame and recreate it. Here is the code, in case someone encountered the same issue.

import tkinter as tk


class TestGrid(tk.Frame):
    def __init__(self, master, switch):
        tk.Frame.__init__(self, master, bg='white')
        colors = ['cyan', 'magenta', 'green', 'gold', 'lavender', 'purple']
        self.switch = switch

        self.widgets = []
        for i in range(6):
            self.widgets.append(tk.Label(self, text=i, bg=colors[i]))

    def draw(self):
        for w in self.widgets:
            w.grid_forget()

        positions = [(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1)]
        for i in range(6):
            c, r = positions[i][0], positions[i][1]
            if self.switch:
                c, r = r, c
            self.grid_columnconfigure(c, weight=1, uniform="aaa")
            self.grid_rowconfigure(r, weight=1, uniform="bbb")
            self.widgets[i].grid(column=c, row=r, sticky=tk.NSEW)


class MyApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.geometry('400x400+20+20')

        btn = tk.Button(self, text="click me")
        btn.pack(side=tk.TOP, fill=tk.X)
        btn.bind("<Button-1>", self.transpose)

        self.switch = False
        self.frm = None
        self.create_frm()

    def create_frm(self):
        self.frm = TestGrid(self, self.switch)
        self.frm.draw()
        self.frm.pack(fill=tk.BOTH, expand=1, padx=5, pady=5)

    def transpose(self, *args):
        self.frm.destroy()
        self.switch = not self.switch
        self.create_frm()


def main():
    root = MyApp()
    root.mainloop()


if __name__ == '__main__':
    main()
0

Rather late to the party, but I also ran into this issue while handling dynamic grid resizing with fixed size cells when the enclosing frame is shrunk. From my own investigations, it appears that the grid geometry manager remembers the column was there even though there are no longer any widgets in that column.

The trick is to tell the grid manager to give no weight to the empty columns or rows by setting weight to 0 and changing the uniform attribute to something different from the non-empty columns or rows. This way, it isn't necessary to tear down everything and start from scratch.

Here is a modified version of your program that shows how it works:

import tkinter as tk


class TestGrid(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master, bg='white')
        colors = ['cyan', 'magenta', 'green', 'gold', 'lavender', 'purple']
        self.switch = True
        self.widgets = []
        for i in range(6):
            self.widgets.append(tk.Label(self, text=i, bg=colors[i]))

    def refresh(self, *args):
        self.switch = not self.switch
        for w in self.widgets:
            w.grid_forget()

        positions = [(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1)]
        for i in range(6):
            c, r = positions[i][0], positions[i][1]
            if self.switch:
                c, r = r, c
            self.widgets[i].grid(column=c, row=r, sticky=tk.NSEW)
        for i in range(2):
            # set the weight for the always present columns and rows
            self.grid_columnconfigure(i, weight=1, uniform="aaa")
            self.grid_rowconfigure(i, weight=1, uniform="bbb")
        if self.switch:
            # tell grid manager to give no weight to the empty column
            self.grid_columnconfigure(2, weight=0, uniform="ignore")
            self.grid_rowconfigure(2, weight=1, uniform="bbb")
        else:
            # tell grid manager to give no weight to the empty row
            self.grid_columnconfigure(2, weight=1, uniform="aaa")
            self.grid_rowconfigure(2, weight=0, uniform="ignore")


def main():
    root = tk.Tk()
    root.geometry('400x400+20+20')
    btn = tk.Button(root, text="click me")
    btn.pack(side=tk.TOP, fill=tk.X)
    frm = TestGrid(root)
    frm.pack(fill=tk.BOTH, expand=1, padx=5, pady=5)
    frm.refresh()
    btn.bind("<Button-1>", frm.refresh)

    root.mainloop()

if __name__ == '__main__':
    main()

This is a bit clunky, but I thought it better to make it clear what was going on than to use some elegant pythonesque way of doing it.

AGDownie
  • 528
  • 4
  • 9