Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python tkinter.filedialog askfolder interfering with clr

I'm mainly working in Spyder, building scripts that required a pop-up folder or file Browse window.

The code below works perfect in spyder. In Pycharm, the askopenfilename working well, while askdirectory do nothing (stuck). But, if running in debug mode - the script works well. I tried to run the script from SAS jsl - same issue.

Any Idea what should I do? Python 3.6 Pycharm 2017.2

Thanks.

The Code I'm using includes:

import clr #pythonnet 2.3.0
import os
import tkinter as tk
from tkinter.filedialog import (askdirectory,askopenfilename)

root = tk.Tk()
root.withdraw()
PPath=askdirectory(title="Please select your installation folder location", initialdir=r"C:\Program Files\\")

t="Please select jdk file"
if os.path.exists(os.path.expanduser('~\Documents')):
    FFile = askopenfilename(filetypes=(("jdk file", "*.jdk"),("All Files", "*.*")),title=t, initialdir=os.path.expanduser('~\Documents'))
else:
    FFile= askopenfilename(filetypes=(("jdk file", "*.jdk"),("All Files", "*.*")),title=t)

sys.path.append(marsDllPath)
a = clr.AddReference('MatlabFunctions')
aObj = a.CreateInstance('Example.MatlabFunctions.MatLabFunctions')

edit: seems like issue related to the pythonnet "imoprt clr", but I do need it in the code.

Similar question asked here: https://github.com/pythonnet/pythonnet/issues/648

like image 988
Itzik Kaplan Avatar asked Apr 12 '18 12:04

Itzik Kaplan


1 Answers

Your problem is rather mediocre, although not so obvious. The problem is not in tinker or pythonnet, it stems from the COM threading model.

To begin with, since you're using the clr, let's try to use dialogs directly with it (it's not absolutely necessary to import the tinker module):

#   importing pythonnet
import clr

#   adding reference (if necessary) to WinForms and importing dialogs
#   clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import OpenFileDialog, FolderBrowserDialog

#   creating instances of dialogs
folder_dialog = FolderBrowserDialog()
file_dialog = OpenFileDialog()

#   try to show any of them
folder_dialog.ShowDialog()
file_dialog.ShowDialog()

As you can see, it hangs just like in your case. The reason, as was mentioned above, stems from the threading's apartment state([1], [2]).

Therefore the clr implicilty sets this state to MTA (Multi-threaded apartment), which can be tested via CoGetApartmentType function:

#   importing ctypes stuff
import ctypes
get_apartment = ctypes.windll.ole32.CoGetApartmentType

#   comment/uncomment this import to see the difference
#   import clr

apt_type = ctypes.c_uint(0)
apt_qualifier = ctypes.c_uint(0)

if get_apartment(ctypes.byref(apt_type), ctypes.byref(apt_qualifier)) == 0:
    #   APPTYPE enum: https://msdn.microsoft.com/en-us/library/windows/desktop/ms693793(v=vs.85).aspx
    #   APTTYPEQUALIFIER enum: https://msdn.microsoft.com/en-us/library/windows/desktop/dd542638(v=vs.85).aspx
    print('APTTYPE = %d\tAPTTYPEQUALIFIER = %d' % (apt_type.value, apt_qualifier.value))
else:
    print('COM model not initialized!')

However, many older COM objects, such as shell dialogs, require STA mode. Good explanation about the difference between those two states can be found here or there.

Finally, the solutions:

1) Use STA thread for dialogs:

#   importing tkinter stuff
import tkinter as tk
from tkinter.filedialog import askdirectory, askopenfilename

#   importing pythonnet
import clr

#   adding reference (if necessary) to WinForms and importing dialogs
#clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import OpenFileDialog, FolderBrowserDialog

#   adding reference (if necessary) to Threading and importing Thread functionality
#clr.AddReference('System.Threading')
from System.Threading import Thread, ThreadStart, ApartmentState


#   WinForms thread function example
def dialog_thread():
    folder_dialog = FolderBrowserDialog()
    file_dialog = OpenFileDialog()

    folder_dialog.ShowDialog()
    file_dialog.ShowDialog()

#   Tk thread function example
def tk_dialog_thread():
    root = tk.Tk()
    root.withdraw()

    askdirectory()
    askopenfilename()

#   check again apartment state at start
current_state = Thread.CurrentThread.GetApartmentState()
if current_state == ApartmentState.STA:
    print('Current state: STA')
elif current_state == ApartmentState.MTA:
    print('Current state: MTA')

#   start dialogs via CLR
thread = Thread(ThreadStart(dialog_thread))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()

#   start dialogs via Tkinter
thread = Thread(ThreadStart(tk_dialog_thread))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()

2) Force STA mode via CoInitialize/CoInitializeEx before CLR does so for MTA:

#   importing ctypes stuff
import ctypes
co_initialize = ctypes.windll.ole32.CoInitialize

#   importing tkinter stuff
import tkinter as tk
from tkinter.filedialog import askdirectory, askopenfilename

#   Force STA mode
co_initialize(None)

# importing pythonnet
import clr 

#   dialogs test
root = tk.Tk()
root.withdraw()

askdirectory()
askopenfilename()
like image 97
CommonSense Avatar answered Oct 31 '22 22:10

CommonSense