Aly Abdelaziz
Aly Abdelaziz

Reputation: 290

Python grid_remove

I am still a newbie in Python and am creating a GUI for an application. In this application, the user chooses the element he/she wants and based on his choice the necessary number of entry grids are shown.

eg. Square = 2 , Sphere = 1 , etc...

My problem lies in whenever the user wishes to update, lets say first he/she chose Square (hence two entry fields show) but after that if he presses Sphere (still the two entry fields show).

The grid_forget doesnt seem to function when the new number of fields is less then the current number of fields.

Part of the code below:

    def domain_dimensions(self, req_dim):
        if self.domain.get() == "Square":
            req_dim = 2
        elif self.domain.get() == 'Sphere':
            req_dim = 1
        else:
            req_dim = 5
        for x in range(0,int(req_dim)):
            self.domain_dim = Entry(self)
            self.domain_dim.grid(row=11, column=1+int(x))
        self.domain_dim.grid_forget() #doesnt seem to forget when value is less on reclick.

self.domain = StringVar()
self.domain.set('Square')
domain_dropdown = OptionMenu(self, self.domain,*neper_domains1, command=self.domain_dimensions).grid(row=10, column=1, columnspan=3)

Updated Code after Steven Help

def domain_dimensions(self, req_dim):
    global container

    for wid in container:
        wid.destroy()

    container = []

    for type, req_dim in neper_domains.iteritems():
        if type == self.domain.get():
            for x in range(0,int(req_dim)):
                domain_dim = Entry(root)
                domain_dim.grid(row=25 + x, column=1)
                container.append(domain_dim)

Upvotes: 0

Views: 2951

Answers (1)

Steven Summers
Steven Summers

Reputation: 5384

Because of the nature of grid, you can have widgets stacked on each other if they are given the same co-ordinates (row and column). If you swapped over to pack, you would see Entry boxes continuously being created below the old ones.

Additionally you are not using grid_forget correctly. All you are doing is forgetting the last widget created after the for loop. So when you select square you would only get 1 Entry widget.

Additionally, if you are not planning on returning the forgotten widget back into the window then I suggest using destroy instead. Forget keeps the widget values in memory, and so if you don't plan on re-using it, the application will just keep accumulating forgotten Entry widgets instead of removing them.

Now for the code example. What you want to do is remove all the existing Entry widgets before adding the new ones. winfo_children returns a list of all the widgets contained in the widget.

import tkinter as tk

def onChange(val):
    if val == "Square":
        r = 2
    elif val == "Sphere":
        r = 1
    else:
        r = 5

    # Destroys all existing widgets in the container frame
    for wid in container.winfo_children():
        wid.destroy() # Using destory instead of grid_forget

    # Dynamically create Entry widgets in container frame
    # Number is equal to r
    for x in range(r):
        e = tk.Entry(container)
        e.grid(row = 1 + x, column = 0)

root = tk.Tk()
root.minsize(200, 200)

options = ["Square", "Sphere", "Other"]
var = tk.StringVar()
var.set("Square")

op_menu = tk.OptionMenu(root, var, *options, command = onChange)
op_menu.grid(row = 0, column = 0)

# Container frame for the Entry widgets
container = tk.Frame(root)
container.grid()

If you want to keep the values of the Entry widgets that are not going to be removed then you can use the following for the onChange function.

def onChange(val):
    if val == "Square":
        r = 2
    elif val == "Sphere":
        r = 1
    else:
        r = 5

    c = container.winfo_children()
    old_r = len(c) # Number of widgets in container frame

    # Destroys all excess widgets in the container frame
    # Eg. old_r = 5, r = 2 | Last 3 widgets in frame are destroyed
    if old_r > r:
        for wid in c[r:old_r]:
            wid.destroy()

    # Dynamically create Entry widgets in container frame
    # Only creates extra to meet r value
    # Eg. old_r = 2, r = 5 | Creates 3 widgets in frame after original 2
    elif old_r < r:
        for x in range(old_r, r):
            e = tk.Entry(container)
            e.grid(row = 1 + x, column = 0)

If you do not want to use a new frame for the container. Then there are a couple of options.

First, if the frame you're using only has the dynamically created Entry widgets then you can use an if statement to exclude all the non Entry widgets from being destroyed.

for wid in container.winfo_children():
    if type(wid).__name__ == "Entry":
        wid.destroy()

Or you can add the dynamically created entry widgets to a list and iterate through that instead of the list from winfo_children

def onChange(val):
    ...
    ...

    global container # Global is used here because this example is not done using classes

    for wid in container:
        wid.destroy() # Using destory instead of grid_forget

    container = [] # Reset container to empty list

    for x in range(r):
        e = tk.Entry(root)
        e.grid(row = 1 + x, column = 0)
        container.append(e) # Append widget to container list   
...
...

# Container list for the Entry widgets
container = []

Upvotes: 3

Related Questions