Reputation: 401
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
Reputation: 385940
Here's what I would do:
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.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
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