How can a desktop app use a Windows Runtime object that infers UI context from its thread? The IInitializeWithWindow pattern

Raymond Chen

Raymond

Many objects in the Windows Runtime can be used from desktop apps. For today’s example, we’ll use the File­Open­Picker. This is a rather artificial example because you could just use the IFile­Dialog interface to get equivalent functionality in a desktop app, but I just picked it for use as an example.

Start with our scratch program and make these changes:

#include <winrt/windows.storage.pickers.h>

winrt::Windows::Foundation::IAsyncAction
ShowFilePickerAsync(HWND hwnd)
{
    auto picker = winrt::Windows::Storage::Pickers::FileOpenPicker();
    picker.FileTypeFilter().Append(L".jpg");
    auto file = co_await picker.PickSingleFileAsync();
}

winrt::fire_and_forget OnChar(HWND hwnd, TCHAR ch, int cRepeat)
{
    co_await ShowFilePickerAsync(hwnd);
}

// Add to WndProc
    HANDLE_MSG(hwnd, WM_CHAR, OnChar);

Run this program and press a key. The program will crash because the File­Open­Picker looks for a Core­Window on the current thread to serve as the owner of the dialog. But we are a Win32 desktop app without a Core­Window.

The solution is to use the IInitialize­With­Window interface. Many Windows Runtime objects which infer the Core­Window from the current thread support the IInitialize­With­Window interface to allow a Win32 desktop app to specify an explicit window.

Make the following changes to the program:

#include <shobjidl.h>
#include <winrt/windows.storage.pickers.h>

winrt::Windows::Foundation::IAsyncAction
ShowFilePickerAsync(HWND hwnd)
{
    auto picker = winrt::Windows::Storage::Pickers::FileOpenPicker();
    picker.as<IInitializeWithWindow>()->Initialize(hwnd);
    picker.FileTypeFilter().Append(L".jpg");
    auto file = co_await picker.PickSingleFileAsync();
}

This time, the File Open dialog opens because we explicitly provided a window handle to use as the owner.

The IInitialize­Window­Window pattern is used mostly in the case where an object is simply constructed. There is another pattern for the case where an object is obtained by calling a method: The interop pattern, which I covered some time ago.

Raymond Chen
Raymond Chen

Follow Raymond   

8 Comments
Avatar
Me Gusta 2019-04-12 15:07:14
This is interesting. This really helps for interoping with UWP. I knew you could get the StorageFolder from GetFolderFromPathAsync and work from that, but knowing how to get the pickers to work really helps
Avatar
Ji Luo 2019-04-12 19:59:12
I inferred that pattern a few weeks ago when I was playing with the Pin to Start dialog. I wonder whether that pattern could be used in production, since MSDN doesn’t mention this pattern, nor does the documentation for FilePickers say a word on it. For another thing, FilePickers are quite different from IFileDialog (contrary to the MSDN statement saying that desktop apps don’t need them as they already have IFileDialog and full access to the file system). They have the benefit of being able to consume UWPs that provide pickers. They are also process-isolated in PickerHost.exe. This provides the benefit of being immune to bad in-process shell handlers that come into play when you open a common dialog. Finally, there’s something funny about the pickers. If a UWP app uses the picker, the dialog will not be able to process IPreviewHandler correctly, yet works okay with IThumbnailProvider. If you use common dialogs or file pickers from a desktop app, you will see both preview handlers and thumbnail providers. I think that’s a bug, is it?
Avatar
John Selbie 2019-04-14 22:43:13
If you are trying this at home.  The needed headers include:     #include <shobjidl_core.h>     #include <winrt/windows.storage.pickers.h> And the library to link with:     runtimeobject.lib
Avatar
Gunnar Dalsnes 2019-04-16 15:06:04
is the as method specific to the picker (or some base class) or is it a c++ thing (ala. extension method)? how is it diffetent from regular cast?
Avatar
Dave Bacher 2019-04-17 12:27:02
I actually might use this - the UWP File Picker is a lot nicer for any case involving cloud providers, and is potentially more secure for some cases.