Python tkinter.filedialog askfolder 干扰 clr
Python tkinter.filedialog askfolder interfering with clr
我主要在 Spyder 中工作,构建需要弹出文件夹或文件浏览的脚本 window。
下面的代码在 spyder 中运行完美。
在 Pycharm 中,askopenfilename
运行良好,而 askdirectory
什么也不做(卡住了)。
但是,如果 运行ning 处于调试模式 - 脚本运行良好。
我尝试 运行 来自 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')
编辑:似乎是与 pythonnet "imoprt clr" 相关的问题,但我在代码中确实需要它。
我已经用安装在 win-7 64 位机器上的 python-3.6.5 测试了您在 Pycharm 2018.1.3 上粘贴的代码。它工作正常,没有任何错误。 2017 版本的 bug 很少。尝试升级到最新版本的 Pycharm
你的问题比较一般,虽然不是很明显。问题不在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()
如您所见,它像您的情况一样挂起。原因,如上所述,源于线程的单元状态([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!')
但是,许多较旧的 COM 对象(例如 shell 对话框)需要 STA 模式。
here or there.
可以很好地解释这两种状态之间的区别
最后,解决方案:
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()
2) 在 CLR 为 MTA 这样做之前通过 CoInitialize
/CoInitializeEx
强制 STA 模式:
# 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()
我主要在 Spyder 中工作,构建需要弹出文件夹或文件浏览的脚本 window。
下面的代码在 spyder 中运行完美。
在 Pycharm 中,askopenfilename
运行良好,而 askdirectory
什么也不做(卡住了)。
但是,如果 运行ning 处于调试模式 - 脚本运行良好。
我尝试 运行 来自 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')
编辑:似乎是与 pythonnet "imoprt clr" 相关的问题,但我在代码中确实需要它。
我已经用安装在 win-7 64 位机器上的 python-3.6.5 测试了您在 Pycharm 2018.1.3 上粘贴的代码。它工作正常,没有任何错误。 2017 版本的 bug 很少。尝试升级到最新版本的 Pycharm
你的问题比较一般,虽然不是很明显。问题不在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()
如您所见,它像您的情况一样挂起。原因,如上所述,源于线程的单元状态([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!')
但是,许多较旧的 COM 对象(例如 shell 对话框)需要 STA 模式。 here or there.
可以很好地解释这两种状态之间的区别最后,解决方案:
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()
2) 在 CLR 为 MTA 这样做之前通过 CoInitialize
/CoInitializeEx
强制 STA 模式:
# 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()