I'm building a Python version of a popular party game, and I'm using tkinter to build the setup GUI with player name and game preferences.When I run the code the window opens and looks right except for one critical thing.
There are radio buttons to select the type of deck, and there's a listbox that's meant to display the names of the decks of cards in the game, that should update whenever a radio button is clicked.
Getting the list of deck names worked fine on the command line, but I can't make it work in the GUI.
The problem is that when the window loads only the first name in the deck list is shown. When I click a radio button I get an error the console.
TypeError: 'StringVar' object is not iterable
I'm sure there's something simple that I'm missing. Here's the relevant code:
master_deck_list = []
deck_type_selection = tk.StringVar(value="official")
with open('cah-cards-full.json', encoding="utf8") as c:
master_deck_list = json.loads(c.read())
def get_available_decks(master_deck_list, deck_type_selection):
# Assign the list of deck names to deck_list
decks_list = []
for av_deck in master_deck_list:
if deck_type_selection == 'official':
if av_deck['official'] == True:
decks_list.append(av_deck['name'])
elif deck_type_selection == 'community':
if av_deck['official'] == False:
decks_list.append(av_deck['name'])
elif deck_type_selection == 'all':
decks_list.append(av_deck['name'])
return decks_list
decklist = get_available_decks(master_deck_list, deck_type_selection="official")
list_items = tk.StringVar(value=decklist)
def update_decklist(list_items):
for item in list_items:
listbox.insert(tk.END, item)
...
# Deck type radio buttons
radio_1 = tk.Radiobutton(decktypes_frame, text="Official", variable=deck_type_selection, value="official", command=lambda: update_decklist(list_items))
radio_1.grid(column=0, row=0, padx=5, pady=5)
radio_2 = tk.Radiobutton(decktypes_frame, text="Community", variable=deck_type_selection, value="community", command=lambda: update_decklist(list_items))
radio_2.grid(column=1, row=0, padx=5, pady=5)
radio_3 = tk.Radiobutton(decktypes_frame, text="All", variable=deck_type_selection, value="all", command=lambda: update_decklist(list_items))
radio_3.grid(column=2, row=0, padx=5, pady=5)
# decksframe holds the available and selected decks lists
decksframe = tk.LabelFrame(main, text="Available Decks")
decksframe.columnconfigure(0, weight=3)
decksframe.columnconfigure(1, weight=3)
# Available Decks
listbox = tk.Listbox(decksframe, selectmode="multiple", listvariable=list_items, exportselection=0)
listbox.grid(column=0, row=0, padx=5, pady=5)
Here's the full traceback (thanks, Michael):
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.8_3.8.2800.0_x64__qbz5n2kfra8p0\lib\tkinter\__init__.py", line 1892, in __call__
return self.func(*args)
File "setup.py", line 63, in <lambda>
radio_2 = tk.Radiobutton(decktypes_frame, text="Community", variable=deck_type_selection, value="community", command=lambda: update_decklist(list_items))
File "setup.py", line 40, in update_decklist
for item in list_items:
TypeError: 'StringVar' object is not iterable
The error message means exactly what it says. list_items
is not a list and it's not a string, it's a StringVar
. You can't do for item in list_items
because a StringVar
isn't something that supports iteration.
The solution seems to be to simply set list_items
to decklist
(or just throw away list_items
and use decklist
directly). Then, just insert the items into the listbox rather than using listvariable
. For example:
listbox = tk.Listbox(decksframe, selectmode="multiple", exportselection=0)
listbox.insert("end", *decklist)
listvariable
works much better when using Tcl rather than python, since tkinter doesn't have a built-in ListVar
object type.