0

In a Python script, I display twice the same tkinter dialog.

The first time, it's correctly filled with the value returned from get_items(), the second time the list is shown empty.

Here is a simplified version of my code, that reproduces the behavior:

import tkinter as tk

class ItemSelector:
    def __init__(self, title='title'):
        self.selection = None

        self.root = tk.Tk()
        self.root.attributes('-type', 'dialog')
        self.root.title(title)
        self.root.bind('<Escape>', self.cancel)
        self.root.bind('<Return>', self.send)

        frame = tk.Frame(self.root) # we need a frame, if we want scrollbar
        frame.pack()

        items = self.get_items()

        self.items_tk = tk.Variable(value=items) # FIX: tk.Variable(frame, value=items)
        self.items_ui = tk.Listbox(frame, listvariable=self.items_tk, height=12)

        scrollbar = tk.Scrollbar(frame, orient="vertical")
        scrollbar.config(command=self.items_ui.yview)
        scrollbar.pack(side="right", fill="y")

        self.items_ui.config(yscrollcommand=scrollbar.set)

        self.items_ui.pack()

        submitButton = tk.Button(self.root, text='Submit', command=self.send)
        submitButton.pack()
        self.root.mainloop()

    def get_items(self):
        return ['a', 'b', 'c', 'd']

    def cancel(self, *args):
        self.root.quit()
        self.root.withdraw()

    def send(self, *args):
        self.root.withdraw()
        self.root.quit()

def main():
    itemSelector = ItemSelector('A')
    itemSelector = ItemSelector('B')

if __name__ == '__main__':
    main()

How can I get the list of items to be shown also in the second call?

First solution

Swifty's answer helped me understand and circumscribe the issue: It's because I have multiple Tk instances.

His solution with root as a class variable does work, but, if I have one single root, I'd prefer to have it outside of the list dialog and share it with both classes. (I don't have working code for that solution but I will post if and when I get to that.)

As a first solution, I've added a #FIX in the original code applying TheLizzard's hints from https://stackoverflow.com/a/69062053/5239250 to my code: if you have multiple Tk instances you need to always explicitly pass the Tk context when creating Tk variables!

a.l.e
  • 792
  • 8
  • 16

1 Answers1

1

It seems the problems comes from each ItemSelector instance creating a new Tk instance, while tkinter only supports one. A fix could be to create the Tk instance at the class level, and associating each ItemSelector instance to a Toplevel window:

import tkinter as tk

class ItemSelector:
    root = tk.Tk()
    root.withdraw()
    def __init__(self, title='title'):
        self.selection = None
        self.root = tk.Toplevel()
        #self.root.attributes('-type', 'dialog')
        self.root.title(title)
        self.root.bind('<Escape>', self.cancel)
        self.root.bind('<Return>', self.send)

        frame = tk.Frame(self.root)     # we need a frame, if we want scrollbar
        frame.pack()

        items = self.get_items()

        self.items_tk = tk.Variable(value=items)
        self.items_ui = tk.Listbox(frame, listvariable=self.items_tk, height=12)

        scrollbar = tk.Scrollbar(frame, orient="vertical")
        scrollbar.config(command=self.items_ui.yview)
        scrollbar.pack(side="right", fill="y")

        self.items_ui.config(yscrollcommand=scrollbar.set)

        self.items_ui.pack()

        submitButton = tk.Button(self.root, text='Submit', command=self.send)
        submitButton.pack()
        self.root.mainloop()

    def get_items(self):
        return ['a', 'b', 'c', 'd']

    def cancel(self, *args):
        self.root.quit()
        self.root.withdraw()

    def send(self, *args):
        self.root.withdraw()
        self.root.quit()

def main():
    itemSelector = ItemSelector('A')
    itemSelector = ItemSelector('B')

if __name__ == '__main__':
    main()
Swifty
  • 2,630
  • 2
  • 3
  • 21
  • Thanks, I'm not 100% happy with your answer, but it helped me track down the issue! https://stackoverflow.com/a/69062053/5239250 shows well what is wrong with my code, and keeping the two instanced of tkinter. I will try to follow your hint though, but I'm trying to keep the only Tk instance out of the class, in the `main()`. for now, having the root outside of the class just crashes my code, but I will post also my code, if I find a way to do it. – a.l.e Jul 16 '23 at 09:09
  • Indeed, I defined the root in the class itself more as a placeholder than anything else; it can actually be defined at the start of `main()`, or at any other time before instantiating the classes. – Swifty Jul 16 '23 at 20:24