Reputation: 174
I'm attempting to make a dynamic GUI where clicking a button causes the creation of a new frame that is placed above the button with 3 entry widgets (user options) inside of it, and I need to be able to read the user input from the 3 entry widgets & possibly alter them. Each time the button is pressed, three new callable entry widgets should appear.
I know that this is wrong because it has been giving me errors, but could something similar to the lists be used to create the widgets dynamically?
from Tkinter import *
app = Tk()
frameNames = []
widgetNames = []
def createwidgets():
global widgetNames
global frameNames
frameNames += (str("g"+str(len(frameNames)))) #why does the letter & number get added as seperate elements?
widgetNames += [(str("w"+str(len(widgetNames)-1))),
(str("w"+str(len(widgetNames)))),
(str("w"+str(len(widgetNames)+1)))]
frameNames[len(frameNames) - 1] = Frame(app)
frameNames[len(frameNames) - 1].pack()
widgetNames[len(widgetNames) - 3] = Entry(frameNames[len(frameNames) - 1])
widgetNames[len(widgetNames) - 3].pack()
widgetNames[len(widgetNames) - 2] = Entry(frameNames[len(frameNames - )- 1])
widgetNames[len(widgetNames) - 2].pack()
widgetNames[len(widgetNames) - 1] = Entry(frameNames[len(frameNames) - 1])
widgetNames[len(widgetNames) - 1].pack()
createWidgetButton = Button(app, text="createWidgets", command=createwidgets())
createWidgetButton.grid(sticky=S)
app.mainloop()
Upvotes: 0
Views: 7599
Reputation: 385800
The main problem is these four lines of code:
frameNames[len(frameNames) - 1] = Frame(app)
frameNames[len(frameNames) - 1].pack()
...
createWidgetButton = Button(app, text="createWidgets", command=createwidgets())
createWidgetButton.grid(sticky=S)
You are creating both the frame and button as a child of app
, but you are using grid
for one and pack
for the other. You must be consistent with all direct descendants of app
- they must all use pack
or they must all use grid
.
The second problem is this line:
frameNames += (str("g"+str(len(frameNames)))) #why does the letter & number get added as seperate elements?
Here, frameNames
is a list and you are trying to add it with a string. Adding is not the same as appending. You need to append the new name, or put the new name in a temporary list before adding it.
frameNames.append(str(...))
The third problem is this line:
createWidgetButton = Button(app, text="createWidgets", command=createwidgets())
The above is exactly the same as this:
result = createWidgets()
createWidgetButton = Button(app, text="createWidgets", command=result)
You must pass a reference to a function, not call the function. Change the line to this (notice the lack of parenthesis after createWidgets
):
createWidgetButton = Button(app, text="createWidgets", command=createwidgets)
Unrelated to the problem, but your code would be much easier to read if you used temporary variables instead of repeating the pattern (str("w"+str(len(widgetNames)-1)
. As written, your code is almost impossible to read. Also, you don't want to be storing widget names, you need to store the actual widgets themselves.
And finally, don't do a wildcard import. There is simply no good reason to do it.
Here is how I would rewrite your code:
import Tkinter as tk
app = tk.Tk()
frames = []
widgets = []
def createwidgets():
global widgetNames
global frameNames
frame = tk.Frame(app, borderwidth=2, relief="groove")
frames.append(frame)
frame.pack(side="top", fill="x")
for i in range(3):
widget = tk.Entry(frame)
widgets.append(widget)
widget.pack(side="left")
createWidgetButton = tk.Button(app, text="createWidgets", command=createwidgets)
createWidgetButton.pack(side="bottom", fill="x")
app.mainloop()
Upvotes: 3
Reputation: 4730
This is relatively simple.
We can do this by creating a Frame
widget but not packing it, filling it with whatever we need and then having a Button
call the pack on the Frame
widget.
Much like the below:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.create = Button(self.root, text="Create", command=self.draw)
self.create.pack(side="bottom")
self.frame = Frame(self.root)
self.entry1 = Entry(self.frame)
self.entry2 = Entry(self.frame)
self.entry3 = Entry(self.frame)
self.entry1.pack()
self.entry2.pack()
self.entry3.pack()
self.submit = Button(self.frame, text="Submit", command=self.command)
self.submit.pack()
def draw(self):
self.frame.pack(side="top")
def command(self):
print(self.entry1.get())
print(self.entry2.get())
print(self.entry3.get())
root = Tk()
App(root)
root.mainloop()
If you need to add multiple of these forms you can do something like the below which makes use of anonymous functions (lambda
) in order to have "self aware" buttons which "know" which frame they're in:
from tkinter import *
class App:
def __init__(self, root):
self.frames = []
self.entries = []
self.count = 0
self.root = root
self.create = Button(self.root, text="Create", command=self.draw)
self.create.pack(side="bottom")
def draw(self):
self.frames.append(Frame(self.root, borderwidth=1, relief="solid"))
self.frames[self.count].pack(side="top")
self.entries.append([Entry(self.frames[self.count]), Entry(self.frames[self.count]), Entry(self.frames[self.count])])
for i in self.entries[self.count]:
i.pack()
Button(self.frames[self.count], text="Submit", command=lambda c=self.count: self.submit(c)).pack()
self.count += 1
def submit(self, c):
for i in self.entries[c]:
print(i.get())
root = Tk()
App(root)
root.mainloop()
Upvotes: 0