c++winui-3c++-winrt

FileOpenPicker "invalid window handle" exception


This is a follow-on question to Guarang Dave's question. With @Simon_Mourier's help, he figured it out, but I still haven't. I'm trying to do what ought to be a simple task: open a FileOpenPicker from MainWindow. The straightforward code is this:

    fire_and_forget MainWindow::FileOpenClickHandler(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&)
    {
        auto lifetime = get_strong();

        // Create the file picker
        auto picker = winrt::Windows::Storage::Pickers::FileOpenPicker();
        picker.SuggestedStartLocation(winrt::Windows::Storage::Pickers::PickerLocationId::DocumentsLibrary);
        picker.FileTypeFilter().Append(L"*");

        // Open the file picker
        try {
            auto file = co_await picker.PickSingleFileAsync();
            if (file != nullptr)
            {
                // read the file
            }
            else
            {
                // The user did not pick a file
            }
        }
        catch (winrt::hresult_error const& ex)
        {
            // Open a window to show the error message
            winrt::Microsoft::UI::Xaml::Controls::ContentDialog dialog;
            dialog.Title(winrt::box_value(L"Error"));
            dialog.Content(winrt::box_value(ex.message().c_str()));
            dialog.CloseButtonText(L"OK");
            dialog.XamlRoot(canvasControl().XamlRoot());
            dialog.ShowAsync();
        }
    }

The result is an "invalid window handle" exception. Window handle!? What window handle? This sent me down the rabbit hole of trying to figure out how to get PickSingleFileAsync() associated with the correct file handle.

Here's my next attempt, based on Guarang Dave's question:

        // Create the file picker
        auto picker = winrt::Windows::Storage::Pickers::FileOpenPicker();
        picker.SuggestedStartLocation(winrt::Windows::Storage::Pickers::PickerLocationId::DocumentsLibrary);
        picker.FileTypeFilter().Append(L"*");

        auto windowNative{ this->try_as<::IWindowNative>() };
        winrt::check_bool(windowNative);
        HWND hWnd{ 0 };
        windowNative->get_WindowHandle(&hWnd);
        auto initializeWithWindow{ picker.as<IInitializeWithWindow>() };
        initializeWithWindow->Initialize(hWnd);

        // Open the file picker
        try {
            auto file = co_await picker.PickSingleFileAsync();

Unfortunately, this still gives the "invalid window handle" exception. Replacing fire_and_forget with Windows::Foundation::IAsyncAction doesn't help either. My next step was to implement Simon Mournier's GetProcessFirstWindowHandle(). Here's the code:

        // Create the file picker
        auto picker = winrt::Windows::Storage::Pickers::FileOpenPicker();
        picker.SuggestedStartLocation(winrt::Windows::Storage::Pickers::PickerLocationId::DocumentsLibrary);
        picker.FileTypeFilter().Append(L"*");

        auto hWnd = GetProcessFirstWindowHandle();
        auto initializeWithWindow{ picker.as<IInitializeWithWindow>() };
        initializeWithWindow->Initialize(hWnd);

        // Open the file picker
        try {
            auto file = co_await picker.PickSingleFileAsync();

The result is the same exception.

Finally, I implemented a method of having a static Window member of MainWindow, and a convoluted one of having a Window member of App based onthis C# code, but neither helped. Here's my code with all sorts of non-working methods commented out:

    fire_and_forget MainWindow::FileOpenClickHandler(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::RoutedEventArgs const&)
    {
        auto lifetime = get_strong();

        // ref: https://stackoverflow.com/questions/75436438/how-to-get-main-window-handle-on-page-in-winui-3-using-c
        //auto hWnd = GetProcessFirstWindowHandle();    // invalid window handle
        // ref: CoPilot, possibly based on https://stackoverflow.com/questions/71432263/how-to-retrieve-the-window-handle-of-the-current-winui-3-mainwindow-from-a-page/71440820#71440820
        //auto hWnd = App().MainWindow().as<::IWindowNative>()->WindowHandle();    // invalid window handle
        // ref: https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/win32/microsoft.ui.xaml.window/nf-microsoft-ui-xaml-window-iwindownative-get_windowhandle
        auto window = MainWindow::Current();
        auto windowNative{ window.as<::IWindowNative>() };
        HWND hWnd{ 0 };
        windowNative->get_WindowHandle(&hWnd);    // invalid window handle

        // Create the file picker
        auto picker = winrt::Windows::Storage::Pickers::FileOpenPicker();
        picker.SuggestedStartLocation(winrt::Windows::Storage::Pickers::PickerLocationId::DocumentsLibrary);
        picker.FileTypeFilter().Append(L"*");

        // Simplest option: Get the IInitializeWithWindow interface
        //auto initializeWithWindow{ picker.as<IInitializeWithWindow>() };
      
        // Option 2: Query for the IInitializeWithWindow interface
        winrt::com_ptr<IUnknown> unknownPicker = picker.as<IUnknown>();
        winrt::com_ptr<IInitializeWithWindow> initializeWithWindow;
        unknownPicker->QueryInterface(IID_PPV_ARGS(&initializeWithWindow));

        // Initialize the file picker with the window handle
        initializeWithWindow->Initialize(hWnd);

        // Open the file picker
        try {
            auto file = co_await picker.PickSingleFileAsync();
            if (file != nullptr)
            {
                path = file.Path();
                // read the file
            }
            else
            {
                // The user did not pick a file
            }
        }
        catch (winrt::hresult_error const& ex)
        {
            // Open a window to show the error message
            winrt::Microsoft::UI::Xaml::Controls::ContentDialog dialog;
            dialog.Title(winrt::box_value(L"Error"));
            dialog.Content(winrt::box_value(ex.message().c_str()));
            dialog.CloseButtonText(L"OK");
            dialog.XamlRoot(rootPanel().XamlRoot());
            dialog.ShowAsync();
        }
    }

So far, everything I've tried leads to an "invalid window handle" exception on the first line of the try block. One thing that's still totally opaque to me is Simon's reminder that the FileOpenPicker must run on the UI thread. I have not intentionally created any other threads, so is any MainWindow::function on the UI thread or not? The main question is, of course, how do I get a valid window handle?


Solution

  • Here's the final working code:

    #include "shobjidl_core.h"                  // for IInitializeWithWindow
    #include <Microsoft.UI.Xaml.Window.h>       // for IWindowNative
    
        HWND MainWindow::GetWindowHandleFromThis()
        {
            HWND hWnd{ 0 };
            auto windowNative{ this->try_as<::IWindowNative>() };
            winrt::check_bool(windowNative);
            winrt::check_hresult(windowNative->get_WindowHandle(&hWnd));
            return hWnd;
        }
        fire_and_forget MainWindow::FileOpenClickHandler(IInspectable const&, RoutedEventArgs const&)
        {
            auto lifetime = get_strong();
    
            HWND hWnd = GetWindowHandleFromThis();
            assert(::IsWindow(hWnd) != 0);
    
            // Create the file picker
            auto picker = winrt::Windows::Storage::Pickers::FileOpenPicker();
            picker.as<IInitializeWithWindow>()->Initialize(hWnd);   // ref: https://devblogs.microsoft.com/oldnewthing/20190412-00/?p=102413
            picker.SuggestedStartLocation(winrt::Windows::Storage::Pickers::PickerLocationId::DocumentsLibrary);
            picker.FileTypeFilter().Append(L"*");
    
            // Open the file picker
            try {
                auto file = co_await picker.PickSingleFileAsync();
                if (file != nullptr)
                {
                    // read the file
                }
                else
                {
                    // The user did not pick a file
                }
            }
            catch (winrt::hresult_error const& ex)
            {
                // Open a window to show the error message
                winrt::Microsoft::UI::Xaml::Controls::ContentDialog dialog;
                dialog.Title(winrt::box_value(L"Error"));
                dialog.Content(winrt::box_value(ex.message().c_str()));
                dialog.CloseButtonText(L"OK");
                dialog.XamlRoot(OpenBtn().XamlRoot());
                dialog.ShowAsync();
            }
        }
    

    Although I don't know why the earlier code doesn't work and this does, I hope it's useful to someone else.