Python, Dash: Tkinter openfile dialog works fine only the first time but fails afterwards

Question:

I am struggling with the code below the whole day already.

import webbrowser
import dash
from dash import html, dcc
from dash.dependencies import Input, Output, State
from Open_Save_File import open_file_dialog

import tkinter
from tkinter import filedialog as fd

FILE_DIR = 'H:/Codes/'

webbrowser.get('windows-default').open('http://localhost:8050', new=2)

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    html.H4('Update my list',
            style={
                'textAlign': 'center'
            }),
    html.Br(),
    html.Hr(),
    
    html.Div([
        html.H5('Excel Directory:', 
                style = {'width': '20%', 'display': 'inline-block', 
                    'text-align': 'left'}),
        html.Div(id='selected_directory', children='No file selected!', 
            style={'width': '30%', 'display': 'inline-block'}),
        html.Button('Browse', id='open_excel_button', 
            n_clicks=0, style={'float': 'right', 'display': 'inline-block'})
    ]),
])

# 1. Callback for open_excel button
@app.callback(
    Output(component_id='selected_directory', component_property='children'),
    Input(component_id='open_excel_button', component_property='n_clicks'),
    prevent_initial_call=True
)
def open_excel_function(open_excel): 
    print ('*** 1A. Callback open_file_dialog')
    ctx = dash.callback_context
    trigger = ctx.triggered[0]['prop_id'].split('.')[0]
    print("***", trigger, "is triggered.")
    root = tkinter.Tk()
    root.withdraw()
    # root.iconbitmap(default='Extras/transparent.ico')
    if trigger == 'open_excel_button':
        file_directory = tkinter.filedialog.askopenfilename(initialdir=FILE_DIR) <-- Source of all evil....
        print('***', file_directory)
    else:
        file_directory = None
    return file_directory

if __name__ == '__main__':
    app.run_server(debug=True, use_reloader=False)  

It should open following UI in browser using Dash library and Tkinter:

enter image description here

If you click "browser" button, a open file dialog will open at the specified initial directory:
enter image description here

It works fine the first time, the next times I will get the following error:

enter image description here

Can anyone help find out what is wrong with the line with tkinter.filedialog…?

I have tried other solutions, e.g. this. But I tried everything with this one, but don’t know how to set initial directory. The InitialDir for this doesn’t work.

With tkinkter, I could set the initial directory, but get the error (after works once) as can be seen in above screenshot. Basically I am stuck.

Thank you in advance for any pointer.

Asked By: gunardilin

||

Answers:

In callback you

  • create tkinter main window root = tkinter.Tk()
  • hide it root.withdraw()

but you never destroy it when you exit file dialog.

When it runs again callback then it tries to create second main window and it makes conflict with previous (hidden) main window.

If I use root.destroy() then code works correctly.

app.callback(
    Output(component_id='selected_directory', component_property='children'),
    Input(component_id='open_excel_button', component_property='n_clicks'),
    prevent_initial_call=True
)
def open_excel_function(open_excel): 
    print ('*** 1A. Callback open_file_dialog')
    ctx = dash.callback_context
    trigger = ctx.triggered[0]['prop_id'].split('.')[0]
    print("***", trigger, "is triggered.")

    root = tkinter.Tk()
    root.withdraw()
    # root.iconbitmap(default='Extras/transparent.ico')

    if trigger == 'open_excel_button':
        file_directory = tkinter.filedialog.askopenfilename(initialdir=FILE_DIR)
        print('***', file_directory)
    else:
        file_directory = None

    root.destroy()  # <--- SOLUTION
    
    return file_directory

or even

def open_excel_function(open_excel): 
    print ('*** 1A. Callback open_file_dialog')
    ctx = dash.callback_context
    trigger = ctx.triggered[0]['prop_id'].split('.')[0]
    print("***", trigger, "is triggered.")

    if trigger == 'open_excel_button':
        root = tkinter.Tk()
        root.withdraw()
       # root.iconbitmap(default='Extras/transparent.ico')

        file_directory = tkinter.filedialog.askopenfilename(initialdir=FILE_DIR)
        print('***', file_directory)

        root.destroy()  # <--- SOLUTION
    else:
        file_directory = None
    
    return file_directory

EDIT:

If you want to get only dirname then maybe you should use tkinter.filedialog.askdirectory() instead of tkinter.filedialog.askopenfilename()

Answered By: furas

You could also do something like this:

def simple_ask_delete_gui():
    root = Tk()
    root.withdraw()

    try:
        return messagebox.askyesno(title="Library", message="Delete")
    finally:
        root.destroy()

finally is evaluated on the way out

Answered By: jaksco
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.