The issue
When creating a Tk window to generate a CheckboxTreeview widget for file selection, the user will select files for further analysis and click a button "Select files" which quits the window and returns the selection for further processing.
The issue is that when the user selects the "Select files" button and the program continues, but the Tk window will remain with the rainbow wheel and eventually crash my program throwing the error:
Tcl_AsyncDelete: async handler deleted by the wrong thread
This seems to be predominately related to issues with Tk and M1 processors on Mac.
My code
def selectFiles(DictionaryOfFiles):
from ttkwidgets import CheckboxTreeview
import tkinter as tk
from tkinter import ttk
window = tk.Tk()
tree = CheckboxTreeview(window)
tree.pack()
window.title('Select files for analysis')
window.geometry('400x400')
# iterate through dictionary of folders and files and create a
# checkbox tree - all files checked by default
key_lst = list(DictionaryOfFiles.keys())
val_lst = list(DictionaryOfFiles.values())
for n in key_lst:
tree.insert("","end",n,text=n)
tree.insert(n,"end",DictionaryOfFiles[n],text=DictionaryOfFiles[n])
tree.change_state(n,"checked")
tree.change_state(DictionaryOfFiles[n],"checked")
exit_button = ttk.Button(window,text="Select files",command=window.quit)
exit_button.pack(pady=20)
window.mainloop()
selection = tree.get_checked()
window.destroy()
selection_dict = {}
for n in selection:
position = val_lst.index([n])
selection_dict[key_lst[position]] = n
return selection_dict
The function selectFiles() is called from another script myprogram.py
which passes it 'DictionaryOfFiles' which consists of subfolder keys and filename values.
What I am using
I am running a MacBook Pro with an M1 Pro with Ventura 13.4. Program is being run with Visual Studio Code.
I have also run this on an Ubuntu 22.04 machine with the same Tcl:AsyncDelete error.
What I have tried
I have looked around at a few solutions, including installing Python 3.9.1 and Tkinter 8.6.1 from this issue. This resolved the commonly encountered 'blank Tk window' that gets generated after window = tk.Tk()
is called.
Changing command=window.quit
to comand=window.destroy
succesfully closes the window, but the mainloop
does not stop and the program gets stuck.
Running from the mac terminal python myprogram.py
results in the same error.
Finally, it seems like wait_window
is what I am after but I cannot figure out how to implement it.
Other notes
I am open to other ideas for checkbox trees, it does not have to be from Tk. I am doing the bulk of my development on this Mac so I would ideally like a solution, particularly if I am going to be deploying software to users that have Apple silicon machines.
I was able to resolve this after some deeper research, see below for details.
For starters, I had accidentally left a root = Tk()
window undestroyed in the myprogram.py
script. Destroying it meant that I did not have to revert my python or Tk version to a earlier one as it removed the empty Tk window that would show up.
Next, the command=window.quit
function was not appropriate. It needed to include a lambda
function instead, so the updated code was:
def destroy():
global selection
selection = tree.get_checked()
window.destroy()
exit_button = ttk.Button(window,text="Select files",command=lambda:destroy())
By using calling a def
function for command
I could also get it to output the selected checkboxes to selection
by using global
. The lambda
function also prevented the Button
from automatically running destroy()
so that it only destroyed the window and returned selection
when the button was actually pressed.
However, from this another issue arose - the window
window would hang after window.destroy()
was called. The program would now run fine without error, but the window hanging was annoying. To resolve this, I called window.update()
after window.destroy()
and it completely resolved the issue.
The final code is now:
def selectFiles(DictionaryOfFiles):
from ttkwidgets import CheckboxTreeview
import tkinter as tk
from tkinter import ttk
def destroy():
global selection
selection = tree.get_checked()
window.destroy()
window.update()
window = tk.Tk()
tree = CheckboxTreeview(window)
tree.pack()
window.title('Select files for analysis')
window.geometry('400x400')
# iterate through dictionary of folders and files and create a
# checkbox tree - all files checked by default
key_lst = list(DictionaryOfFiles.keys())
val_lst = list(DictionaryOfFiles.values())
for n in key_lst:
tree.insert("","end",n,text=n)
tree.insert(n,"end",DictionaryOfFiles[n],text=DictionaryOfFiles[n])
tree.change_state(n,"checked")
tree.change_state(DictionaryOfFiles[n],"checked")
exit_button = ttk.Button(window,text="Select files",command=lambda:destroy())
exit_button.pack(pady=20)
window.mainloop()
selection_dict = {}
for n in selection:
position = val_lst.index([n])
selection_dict[key_lst[position]] = n
return selection_dict
window.update()