Tom
Tom

Reputation: 401

Adding and removing tkinter frames - keeping track of instances

I'm trying to create a bunch of sub-frames in a Tkinter GUI, each containing some user input variables that I will need to be able to get at later via a unique ID for each sub-frame. I need to be able to add and delete sub-frames at will, and have them ID'd in a sensible order (integers counting up from 1, at the moment)

Three questions really:

1) Specifically, why does the Label widget showing the title of each frame not display? (this is probably something silly...)

2) Specifically, why does it sometimes take two presses of the add or remove button to add or remove a frame?

3) Generally: is there a better way to approach this? In the sense of keeping track of the frame instances. They're in a list now... I started out with a dict earlier but it was harder to work with... are there better suggestions?

from tkinter import *
from tkinter.ttk import *

class AllInstances(Tk):
    def __init__(self):
        Tk.__init__(self)
        self.master_frame = Frame(self)
        self.master_frame.grid()
        self.all_instances = []

root = AllInstances()

class OneInstance():
    def __init__(self):
        self.number = len(root.all_instances) + 1

        var1 = StringVar()
        var2 = StringVar()
        var3 = StringVar()

        self.sub_frame = Frame(root.master_frame)
        self.sub_frame.grid(column = self.number, row = 0)

        titletext = StringVar()
        titletext.set('%s %s' % ('Frame', self.number))
        print(titletext.get())
        title = Label(self.sub_frame, textvariable = titletext)
        title.grid() #work out why this label does not display!

        uservar1 = Entry(self.sub_frame, textvariable = var1)
        uservar1.grid()
        uservar2 = Entry(self.sub_frame, textvariable = var2)
        uservar2.grid()
        uservar3 = Entry(self.sub_frame, textvariable = var3)
        uservar3.grid()
        #etc etc

        add_button = Button(self.sub_frame, text = 'Add', command = lambda: Create())
        add_button.grid()

        def RemoveInstance(self):
            if len(root.all_instances) > 1:
                root.all_instances.remove(self)
                self.sub_frame.destroy()

                for instance in root.all_instances:
                    instance.number = (root.all_instances.index(instance) + 1)
            else:
                pass

        remove_button = Button(self.sub_frame, text = 'Remove', command = lambda: RemoveInstance(self))
        remove_button.grid()

        root.all_instances.append(self)

def Create():
    OneInstance()

Create()
root.mainloop()

Thanks... :)

Upvotes: 1

Views: 2597

Answers (1)

Bryan Oakley
Bryan Oakley

Reputation: 385940

Here's what I would do:

  1. Make AllInstances inherit from Frame (and rename to something like "FrameGroup"). It then becomes a "megawidget", with the subframes being children that it can directly control.
  2. Make your subframe a subclass of Frame (and call it "Subframe"). This is also a "megawidget", and it needs to be loosly coupled to its parent (ie: it shouldn't know about root.all_instances
  3. Have your add and remove buttons call methods on the FrameGroup object.

I also recommend not doing a global import. Since you're using both ttk and tkinter, one will overwrite the other depending on which order you order the import statements.

Here's a working example with those modifications:

import tkinter as tk

class FrameGroup(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.all_instances = []
        self.counter = 0

    def Add(self):
        self.counter += 1
        name = "Frame %s" % self.counter 
        subframe = Subframe(self, name=name)
        subframe.pack(side="left", fill="y")
        self.all_instances.append(subframe)

    def Remove(self, instance):
        # don't allow the user to destroy the last item
        if len(self.all_instances) > 1:
            index = self.all_instances.index(instance)
            subframe = self.all_instances.pop(index)
            subframe.destroy()

    def HowMany(self):
        return len(self.all_instances)

    def ShowMe(self):
        for instance in self.all_instances:
            print(instance.get())

class Subframe(tk.Frame):
    def __init__(self, parent, name):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.e1 = tk.Entry(self)
        self.e2 = tk.Entry(self)
        self.e3 = tk.Entry(self)
        label = tk.Label(self, text=name, anchor="center")
        add_button = tk.Button(self, text="Add", command=self.parent.Add)
        remove_button = tk.Button(self, text="Remove", command=lambda: self.parent.Remove(self))

        label.pack(side="top", fill="x")
        self.e1.pack(side="top", fill="x")
        self.e2.pack(side="top", fill="x")
        self.e3.pack(side="top", fill="x")
        add_button.pack(side="top")
        remove_button.pack(side="top")

    def get(self):
        return (self.e1.get(), self.e2.get(), self.e3.get())

class GUI(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.master_frame = tk.Frame(self)
        self.master_frame.grid()
        self.all_instances = FrameGroup(self.master_frame)
        self.all_instances.grid()

        # create the first frame
        self.all_instances.Add()

root = GUI()
root.mainloop()

Upvotes: 1

Related Questions