1

I have basically a similar question, though I do not feel it has been answered properly:

Tkinter: How can I dynamically create a widget that can then be destroyed or removed?

The accepted answer is:

You'll want to store the dynamically-created widgets in a list. Have something like

     dynamic_buttons = []

     def onDoubleClick(event):
     ...
     button = Button(...)
     dynamic_buttons.append(button)
     button.pack() 

You can then access the buttons for removal with, say,

     dynamic_buttons[0].destroy()

You can see that the reference they speak of is not variable, here the number 0 is used. But when dynamically creating widgets, how do you connect these references to the buttons?

Say that you create a Toplevel widget (displays a file's content), and want to have a button to close the widget. The dynamic creation will allow multiple files to be open. The problem is that even with this list, how will the button "know" to which widget it belongs, as there is no hard reference (great that you have a list of the items, but toplevel 5 + button 5 have no clue they are 5th in their lists). There will always be just one "active" version of the button and the toplevel, and this one can be deleted.

aanstuur_files = []
aanstuur_frames = []
aanstuur_buttons = []

def editAanstuur():
    openfiles = filedialog.askopenfilenames()
    if not openfiles:
        return 
    for file in openfiles:
        newtop = Toplevel(nGui, height=100, width=100)
        labelTitle = Label(newtop, text=file).pack()
        newButton = Button(newtop, text="save & close", command= ...).pack()
        aanstuur_files.append(file)
        aanstuur_buttons.append(newButton)
        aanstuur_frames.append(newtop)
Community
  • 1
  • 1
PascalVKooten
  • 20,643
  • 17
  • 103
  • 160

2 Answers2

1

How does the button know which window it belongs to? You tell it:

newButton = Button(newtop, command=lambda top=newtop: top.destroy())

By the way, you're assigning None to newButton in your code. This is because you are doing newbutton = Button(...).pack(), which means newbutton gets the value of pack() which is always None.

If you are going to save a reference to a widget, you must create the widget in a separate step from when you place it in a window.

An even better solution is to take advantage of classes and objects. Create your own subclass of Toplevel, and the instance will keep track of all of the subwidgets for you. For example:

class MyToplevel(Toplevel):
    def __init__(self, parent, filename, *args, **kwargs):
        Toplevel.__init__(self, parent, *args, **kwargs)
        self.filename = filename
        self.savebutton = Button(..., command=self.save)
        ...
    def save(self):
        print "saving...", self.filename
        ...
        self.destroy()
...
openfiles = filedialog.askopenfilenames()
if not openfiles:
    return 
for file in openfiles:
    newtop = MyToplevel(nGui, file, height=100, width=100)
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Ah yes, I am aware of that `.pack()` issue. The thing is that I want to save the file which is a child of this top widget before closing, how to access that Text field's content and write it to a file ? – PascalVKooten Jul 07 '13 at 13:19
  • Got it, giving it extra arguments. Thank you very much. – PascalVKooten Jul 07 '13 at 13:29
  • @Dualinity: an even better solution than to keep piling on arguments is to create your own class. I've updated my answer to give an indication of how you can do that. – Bryan Oakley Jul 07 '13 at 13:40
0

APass in a index to your command function using the enumerate() function:

def editAanstuur():
    openfiles = filedialog.askopenfilenames()
    if not openfiles:
        return 
    for i, file in enumerate(openfiles):
        newtop = Toplevel(nGui, height=100, width=100)
        labelTitle = Label(newtop, text=file).pack()
        newButton = Button(newtop, text="Save", command=lambda index=i: print(index)).pack()
        aanstuur_files.append(file)
        aanstuur_buttons.append(newButton)
        aanstuur_frames.append(newtop)

Make sure that you pass the index as a keyword parameter to bind the value when defining the lambda (a closure would use the last value of i).

enumerate() takes a second argument, the index to start at, which defaults at 0.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • When the function `editAanstuur()` will be used again, how will it be able to continue where it left off? (after multiple files have been opened, even more will be opened) Or is the index sent this way a hard reference? – PascalVKooten Jul 07 '13 at 12:56
  • @Dualinity: You can pass a start index to `enumerate()`. It'll start at 0 by default. – Martijn Pieters Jul 07 '13 at 12:59
  • Am I missing something with how to use `command`? Would `lambda: saveButton(i)` call the function saveButton with the correct, hardlinked,`i`? Here it shows something in between `lambda` and `:` – PascalVKooten Jul 07 '13 at 13:01
  • You are missing something; that is a closure, and `i` will not be looked up until the button is clicked. That will *not* be the right index. – Martijn Pieters Jul 07 '13 at 13:02
  • The closure making sure it will be a hard reference? – PascalVKooten Jul 07 '13 at 13:07
  • No, the *keyword parameter* making a value reference. – Martijn Pieters Jul 07 '13 at 13:12
  • I copied what you have. I used two files, but it prints two times a `1` (one for each button). Any idea what's wrong? – PascalVKooten Jul 07 '13 at 13:12
  • See [Local scope, beyond the scope of the enclosing](http://stackoverflow.com/a/17167841) for more detail on closures. – Martijn Pieters Jul 07 '13 at 13:13
  • That's because I managed to muck it up anyway. :-) The `i` variable is the closure, `index` is the keyword parameter. Corrected. – Martijn Pieters Jul 07 '13 at 13:14
  • why go to the trouble of enumerating it and saving it to a list and passing in the index, when you can just pass in a reference to the toplevel and avoid the need for the list? Also, your code could have a fatal flaw if the code that deletes the window also removes it from the list, since the index may become invalid if you delete the windows out of order. – Bryan Oakley Jul 07 '13 at 13:14
  • @BryanOakley Please, do help out. The only way it would work is if the list could only grow... – PascalVKooten Jul 07 '13 at 13:17
  • @BryanOakley: I was addressing the 'what place in the list' question; if all the button has to do is remove the GUI elements again then yes, just referencing `newtop` is plenty enough. – Martijn Pieters Jul 07 '13 at 13:21