0

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.

gunardilin
  • 349
  • 3
  • 12
  • most GUIs doesn't like to work in thread - and `dash` may need to run in threads. I don't see `root.mainloop()` so it may run it automatically all time and when you try to open again then it may create new tread to run `callback` and finally `filedialog` may run in one thread but `mainloop` may run in another thread. – furas Feb 17 '22 at 18:33
  • frankly it look strange when web page uses desktop GUI. When you will try to run dash on external server then it will try to display this GUI on monitor connected directly to server, not to your computer, so it will be useless. I would rather try to use some HTLM widgets to ask for file (or upload file on server) ie. [dcc.upload](https://dash.plotly.com/dash-core-components/upload) – furas Feb 17 '22 at 18:39

2 Answers2

2

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()

furas
  • 134,197
  • 12
  • 106
  • 148
  • Thank you. Your suggestion solves my problem. Regarding your solution with askdirectory, it doesn't work. After replacing askopenfilename to askdirectory, I get following error: """_tkinter.TclError: bad option "-filetypes": must be -initialdir, -mustexist, -parent, or -title""". I have checked everything but couldn't find the cause of this error. – gunardilin Feb 18 '22 at 07:41
  • if you want to get directory then you don't need `filetypes` - and directories don't have types. – furas Feb 18 '22 at 12:52
  • @furas. You are missing mainloop() – toyota Supra Sep 22 '22 at 10:30
0

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

jaksco
  • 423
  • 7
  • 18