0

For several hours now, I have been trying to open the modern folder selection dialog using python.

My application is currently using pywebview's create_file_dialog. Which works as intended, but it's really not very convient for the user. See picture below.

enter image description here

What I would like, is this one:

enter image description here

Besides looking nicer and having a much more intuative design, it also uses the last selected path as the initial selection.

I have tried to use pywin32, but I have not found a way to create this exact dialog (only different forms of the default file selection dialog).

From my research I'd say that I probably need the IFileOpenDialog. But there seems to be no way to use IFileOpenDialog using Python, or is there?

Does anyone know how to create said dialog?

1
  • As an aside, I can't recommend PySimpleGUI more if you're only needing a GUI... I've run into trouble using the folder dialog because despite still being around for things, it seems like MS Windows has dropped all support in modern frameworks. Commented Feb 26, 2024 at 18:50

2 Answers 2

1

It is not impossible -- see Access COM methods from Python as an example how to use an arbitrary COM interface in Python.

It requires very careful work to port IFileOpenDialog like an interface in that answer, and mistakes likely to result in crashes.


Note that Python has an easy way to access COM objects, like

obj = win32com.client.Dispatch('Program.Name')

But it only works for IDispatch-based interfaces, and IFileOpenDialog isn't such.


Instead of trying to use IFileOpenDialog directly from Python, I'd recommend creating a wrapper in a language that can easily use IFileOpenDialog directly, that is in C++, and putting that in a DLL you'll use from Python.

Sign up to request clarification or add additional context in comments.

1 Comment

Yes, creating a DLL seems to be the only feasible option here. It's crazy to me that there seems to be no established solution to this problem. I also looked at the COM objects, but holy hell, that is a whole nother set of problems.
1

Thanks @Alex Guteniev for pointing me in the right direction!

Here is what I ended up using (copied mostly from here):

PLEASE NOTE: This solution is not perfect. The created IModalDialog has no parent. So if you have a Python GUI running, this dialog will not be forced in front and can even be closed after the main window is closed.

WinDialogs.h
#pragma once
extern "C" __declspec(dllexport) wchar_t * open_folder_dialog();
WinDialogs.cpp
#include "pch.h"
#include "WinDialogs.h"
#include <iostream>
#include <ShlObj.h>
#include <atlbase.h>  // for CComPtr, CComHeapPtr

struct ComInit
{
    ComInit() { CoInitialize(nullptr); }
    ~ComInit() { CoUninitialize(); }
};

wchar_t * open_folder_dialog() {
    // Initialize COM to be able to use classes like IFileOpenDialog.
    ComInit com;

    // Create an instance of IFileOpenDialog.
    CComPtr<IFileOpenDialog> pFolderDlg;
    pFolderDlg.CoCreateInstance(CLSID_FileOpenDialog);

    // Set options for a filesystem folder picker dialog.
    FILEOPENDIALOGOPTIONS opt{};
    pFolderDlg->GetOptions(&opt);
    pFolderDlg->SetOptions(opt | FOS_PICKFOLDERS | FOS_PATHMUSTEXIST | FOS_FORCEFILESYSTEM);

    // Show the dialog modally.
    if (SUCCEEDED(pFolderDlg->Show(nullptr)))
    {
        // Get the path of the selected folder and output it to the console.

        CComPtr<IShellItem> pSelectedItem;
        pFolderDlg->GetResult(&pSelectedItem);

        CComHeapPtr<wchar_t> pPath;
        pSelectedItem->GetDisplayName(SIGDN_FILESYSPATH, &pPath);

        return pPath.m_pData;
    }
    // Else dialog has been canceled. 

    // The destructor of ComInit calls CoUninitialize() here after all
    // other objects have been destroyed.
    return NULL;
}
Python
import ctypes

mydll = ctypes.windll.LoadLibrary("WinDialogs.dll")
mydll.open_folder_dialog.restype = ctypes.c_wchar_p
print(mydll.open_folder_dialog())

4 Comments

Smart pointers are awesome, but they can outsmart you, too. pPath.m_pData is a dangling pointer by the time open_folder_dialog() returns control to the caller. Every time you cross a } C++ initiates a garbage collector run.
@IInspectable Good catch! C++ is not at all my strength, how could this be fixed? Also feel free to edit.
The fix is simple: return pPath.Detach();. But that also transfers responsibility for cleanup to the caller. Best to implement a Python wrapper that assigns the return value to a Python string and releases the memory.
There's also a latent bug in the ComInit class: The c'tor can fail, but the d'tor will run either way, potentially calling CoUninitialize() without a previous successful call to CoInitialize(). There is no one correct way to fix this. And while I was thinking up a simple solution, I spotted another bug: CComPtr::CoCreateInstance() can fail, and if it does the subsequent invocation of operator->() will attempt to dereference a null pointer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.