6

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

denfromufa
  • 5,610
  • 13
  • 81
  • 138
Itzik Kaplan
  • 63
  • 1
  • 6
  • I tried this code on Python 3.6 Pycharm 2017.2 and it is working correctly for both folders and files. Are you sure that there is no errors appearing to you? – Ahmed Hussein Apr 12 '18 at 12:37
  • Thank you Ahmed. Actually in my code I'm using pythonnet 2.3.0 to add reference. So in the code I have "import clr" as well. I tried to remove it and its worked well for me. but yet I need it... I'll updtae the question. Maybe it related to another clr object cojuvting with pythonnet clr? – Itzik Kaplan Apr 12 '18 at 13:13
  • the code doesn't work when it run from python command line – constructor May 14 '18 at 10:47
  • @constructor if you really want this resolved, try opening a bounty! – denfromufa May 14 '18 at 15:03
  • @denfromufa I added bounty. – constructor May 14 '18 at 16:45
  • What happens if you delay the clr import to after the dialog? – Tarun Lalwani May 14 '18 at 18:28
  • I see in the related github link the version being used is an Anaconda distro. Are you using Anaconda or a clean install of python 3.6? – Mike - SMT May 14 '18 at 20:04
  • @Mike-SMT I use Anaconda distributive. – constructor May 15 '18 at 08:29
  • @TarunLalwani when I move import clr after the dialog, it works. But we need define import and use clr also before dialog – constructor May 15 '18 at 08:33
  • @constructor, not a solution but i would try and do all the tkinter imports before importing clr and see if it solves the issue for the time being – Tarun Lalwani May 15 '18 at 08:34
  • I believe your issues may be caused by Anaconda and as such will probably not be easy to correct here. Anaconda is great for having many library ready to go but I have seen many bugs come from Anaconda. I would follow this link: [anaconda-issues](https://github.com/ContinuumIO/anaconda-issues) and submit the report for your problem there. – Mike - SMT May 16 '18 at 14:25
  • @ItzikKaplan this might be a dumb idea but have you tried importing `clr` as something else. Say like this: `import clr as my_clr` and see if that helps. Maybe there is some kind of import conflict in the name space however I don't really see how that could be when you import as tk for tkinter. – Mike - SMT May 17 '18 at 15:23
  • By the way in your code you are missing the `import sys`. Is this in your original code? Without it `sys.path.append(marsDllPath)` wont work. If we could get an example jdk code for testing that would help. – Mike - SMT May 18 '18 at 12:58
  • 2
    just to confirm, using standard CPython (3.4) i get the same non working behaviour, i've tried different ways of importing the module, and both as a script and interactively, it seems as soon as the clr module is loaded it does something to the runtime, but not sure what... – James Kent May 18 '18 at 15:11
  • @Mike-SMT `import clr as my_clr` doesn't solve the issue – constructor May 22 '18 at 08:37

2 Answers2

7

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()
CommonSense
  • 4,232
  • 2
  • 14
  • 38
  • 2
    The suggested solution works for Windows dialogs, but usually doesn't work for tkinter with message `Unhandled Exception: Python.Runtime.PythonException: RuntimeError : main thread is not in main loop` – constructor May 22 '18 at 20:48
  • 1
    @constructor, my goal was to show the root of the problem, and that in Windows there is no such thing as a "tkinter dialog", except for the API calls to the native dialogs under the hood of the `tkinter`. It's well known that `tkinter` isn't a thread-safe thing on its own, therefore you should move `clr`-related stuff to the separate child thread/process and plan your communication between `tkinter` and `clr`. But that's another story. Long story short: final solution heavily depends on the design and requirements. – CommonSense May 22 '18 at 21:12
  • 1
    @constructor, anyway, from the point of view of the OP, they should have been separated initially - it's a bad idea to run some of the Matlab calculations in one thread with a graphical interface. However, if they were separated in the first place, then the described problem wouldn't occur! – CommonSense May 22 '18 at 21:31
  • @CommonSense thank you very much for the explanation and the solution. – Itzik Kaplan May 23 '18 at 10:55
0

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

Niranjan
  • 67
  • 7