-2

I'm trying to create a Windows console application which simply opens a folder picker dialog box trough which the user can select a folder. The application would then change current working directory of the command prompt to the path of the selected folder.

This is the entirety of the application, it's only purpose is to allow me to easily change the current working directory of a command prompt by simply typing the name of the executable and selecting the desired folder (provided that the directory where the executable is stored is listed in the PATH environment variable).

I know about the existence of the FolderBrowserDialog class and the CFolderPickerDialog class in MFC, but I'd just like it to look and function as close as possible to the OpenFileDialog class (and MFC doens't seem like much of an option since it is a console app).

Never mind the fact that an application can only change it's own current folder, this is another issue on it's own and it is not related to this question. A work arround for this issue is described here.

What would the easiest way to achieve such behavior be?

Gark Garcia
  • 450
  • 6
  • 14
  • @start Yep, but we can overcome this by executing the application from inside of a batch script and having it change the caller directory. I already have a working prototype [here](https://github.com/GarkGarcia/DirNav) but it is an electron app, which is really not the appropriate tool for this job... – Gark Garcia Oct 02 '18 at 22:29
  • 1
    [`IFileOpenDialog`](https://learn.microsoft.com/en-us/windows/desktop/api/shobjidl_core/nn-shobjidl_core-ifileopendialog) with [`FOS_PICKFOLDERS`](https://learn.microsoft.com/en-us/windows/desktop/api/shobjidl_core/ne-shobjidl_core-_fileopendialogoptions) option can be used to create a modern folder picker dialog. This is what MFC's `CFolderPickerDialog` is based on. BTW, you *can* use MFC in a console application, if you [initialize it properly](https://msdn.microsoft.com/en-us/library/w04bs753.aspx). – zett42 Oct 02 '18 at 22:53
  • 1
    Use a GUI Framework that already has a folder picker dailog box widget. – Thomas Matthews Oct 02 '18 at 23:07
  • 1
    @ThomasMatthews That would be overkill as Windows already provides such a widget. – zett42 Oct 02 '18 at 23:37

1 Answers1

4

Use the shell class IFileOpenDialog and set the FOS_PICKFOLDERS option via a call to IFileOpenDialog::SetOptions() to create a standard folder picker dialog.

Here is a minimal example. It uses the COM smart pointers CComPtr and CComHeapPtr to simplify the code. These free us from the duties of manual Release() and CoTaskMemFree() calls. Note that you should check the HRESULT return value of each function call for errors, which I have omitted for brevity.

#include <iostream>
#include <ShlObj.h>
#include <atlbase.h>  // for CComPtr, CComHeapPtr

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

int main()
{
    // 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 );

        std::wcout << L"Selected folder: " << pPath.m_pData << std::endl;
    }
    // Else dialog has been canceled. 

    // The destructor of ComInit calls CoUninitialize() here after all
    // other objects have been destroyed.  
}
zett42
  • 25,437
  • 3
  • 35
  • 72
  • 2
    This is attempting to call COM methods after COM has been uninitialized on the calling thread. See [Do you know when your destructors run? Part 1.](https://blogs.msdn.microsoft.com/oldnewthing/20040520-00/?p=39243) – IInspectable Oct 03 '18 at 08:41
  • 1
    @IInspectable Fixed in code. Thanks. – zett42 Oct 03 '18 at 10:14
  • Almost correct. In an RAII context like this you will want to have your constructor throw an exception in case it fails to initialize (i.e. in case it fails to initialize COM on the calling thread). An implementation without exceptions would have to persist a value, indicating whether or not the destructor needs to call `CoUninitialize`. – IInspectable Oct 03 '18 at 11:57
  • @IInspectable _"Note that you should check the HRESULT return value of each function call for errors, which I have omitted for brevity."_ – zett42 Oct 03 '18 at 12:00
  • You cannot check the `HRESULT` return value when using this `ComInit` type. There is no public interface for that. You would have to do either of those things I mentioned in my previous comment. – IInspectable Oct 03 '18 at 12:04
  • 1
    @IInspectable This is just sample code. Readers can modify the code to implement proper error handling. This question is not about how to do proper error handling. – zett42 Oct 03 '18 at 12:07