Python tkinter.filedialog askfolder干扰clr

Itz*_*lan 6 .net python tk-toolkit tkinter python.net

我主要在Spyder工作,构建需要弹出文件夹或文件浏览窗口的脚本.

下面的代码在spyder中完美运行.在Pycharm,askopenfilename运作良好,而askdirectory什么都不做(卡住).但是,如果在调试模式下运行 - 脚本运行良好.我试图从SAS jsl运行脚本 - 同样的问题.

任何想法我该怎么办?Python 3.6 Pycharm 2017.2

谢谢.

我使用的准则包括:

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')
Run Code Online (Sandbox Code Playgroud)

编辑:似乎与pythonnet"imoprt clr"相关的问题,但我确实在代码中需要它.

类似的问题在这里问:https://github.com/pythonnet/pythonnet/issues/648

Com*_*nse 5

您的问题相当平庸,尽管不那么明显。问题不在tinker或中pythonnet,它源于COM线程模型。

首先,由于您使用clr,所以我们尝试直接使用对话框(导入tinker模块并非绝对必要):

#   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()
Run Code Online (Sandbox Code Playgroud)

如您所见,它像您的情况一样挂起。如上所述,其原因来自线程的单元状态([1][2])。

因此,clr隐式将此状态设置为MTA(多线程单元),可以通过以下CoGetApartmentType功能对其进行测试:

#   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!')
Run Code Online (Sandbox Code Playgroud)

但是,许多较旧的COM对象(例如外壳对话框)都需要STA模式。关于这两种状态之间差异的很好解释可以在这里那里找到。

最后,解决方案:

1)使用STA线程进行对话框:

#   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()
Run Code Online (Sandbox Code Playgroud)

2)对于MTA,在CLR之前通过CoInitialize/ 强制STA模式CoInitializeEx

#   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()
Run Code Online (Sandbox Code Playgroud)

  • 建议的解决方案适用于 Windows 对话框,但通常不适用于 tkinter,并显示消息“未处理的异常:Python.Runtime.PythonException:RuntimeError:主线程不在主循环中” (2认同)