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?
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.