Python tkinter.filedialog askfolder interfering with clr

余生长醉 提交于 2021-02-07 07:01:22

问题


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


回答1:


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



回答2:


I have tested the code which you pasted on Pycharm 2018.1.3 with python-3.6.5 installed on win-7 64 bit machine. It works fine without any error. There are few bugs in 2017 version. Try upgrading to latest version of Pycharm



来源:https://stackoverflow.com/questions/49796024/python-tkinter-filedialog-askfolder-interfering-with-clr

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!