Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify using UWP file pickers from desktop apps #1063

Open
JaiganeshKumaran opened this issue Jul 15, 2021 · 9 comments
Open

Simplify using UWP file pickers from desktop apps #1063

JaiganeshKumaran opened this issue Jul 15, 2021 · 9 comments
Assignees

Comments

@JaiganeshKumaran
Copy link
Contributor

JaiganeshKumaran commented Jul 15, 2021

To use UWP file pickers such as file open picker or file save picker in desktop apps, you need to cast the object reference to IInitializeWithWindow and invoke its Initialize method. Although one can use Win32 file pickers, those lack some features like the ability to use picker contract apps. To simplify usage, I would suggest adding an overload for the constructor of FileOpenPicker and FileSavePicker to take a window handle. Since HWND can't be represented with the Windows Runtime type system, it can take an int64_t or the new WindowId structure instead.

@angelazhangmsft
Copy link

@JaiganeshKumaran - as FYI in C#/.NET 5, we have just added support for several COM WinRT interop interfaces. Here are docs for more information: https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/winrt-com-interop-csharp

With these new APIs, you can add the following code in a C#/WinUI3 app which simplifies the code:

var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
var folderPicker = new Windows.Storage.Pickers.FolderPicker();
WinRT.Interop.InitializeWithWindow.Initialize(folderPicker, hwnd);

@JaiganeshKumaran
Copy link
Contributor Author

@JaiganeshKumaran - as FYI in C#/.NET 5, we have just added support for several COM WinRT interop interfaces. Here are docs for more information: https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/winrt-com-interop-csharp

With these new APIs, you can add the following code in a C#/WinUI3 app which simplifies the code:

var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
var folderPicker = new Windows.Storage.Pickers.FolderPicker();
WinRT.Interop.InitializeWithWindow.Initialize(folderPicker, hwnd);

Right but that's still a lot of work compared to an UWP app where you just construct a picker and use it. Also these helpers must be rewritten for each supported programming language so I think it's better to have a constructor overload that does the initialization with window automatically.

@Scottj1s
Copy link
Member

Scottj1s commented Jul 28, 2021

Agreed that this interop pattern is cumbersome and not discoverable. Ideally, the constructor overload would flow from a metadata attribute, as was done for FastABI support. If a class was decorated with a CanInitializeWithWindowAttribute, a constructor (or factory method) could be generated to accept an HWND source object (i.e., implementing IWindowNative), from which the given class could then call IInitializeWithWindow.Initialize. That would accrue to all language projections, rather just C#/WinRT.

@riverar
Copy link
Contributor

riverar commented Jul 28, 2021

Ideally, you'd just internalize all this goop in the Window class, give it a HWND property, and everything else just falls into place. It's unfortunate how complicated this has all become over what appears to be some misguided attempt to make Window "pure".

@Scottj1s
Copy link
Member

As @JaiganeshKumaran points out, it stems from HWND not being a WinRT type. Such types require a backdoor (COM interop interface) to access. But we haven't done a good job of supporting language projections there, and have effectively dropped WinRT/COM interop support on the floor.

@Scottj1s
Copy link
Member

Out API design team is going to investigate making this interop pattern simpler (hopefully nonexistent in most cases). Meantime, I've improved discoverability of the need for the pattern in C#/WinRT. Failure to call IInitializeWithWindow now presents a diagnostic that points to an interop code sample to copy:
image

@dotMorten
Copy link
Contributor

dotMorten commented Jul 30, 2021

@Scottj1s The improved error message is good to see. I'm a big fan of help messages, instead of error messages. I had some comments here: microsoft/microsoft-ui-xaml#4167 (comment) that touched on some of this and used the pickers as an example.

@Scottj1s
Copy link
Member

Thanks @dotMorten, and for linking the related issue. Totally agree!

@JaiganeshKumaran
Copy link
Contributor Author

I think C++/WinRT should do something like this:

template <typename T>
concept WindowLike = requires(T const& t)
{
    // Function resolved with ADL, add GetWindowHandle in your type's namespace. 
    { GetWindowHandle(t) } -> std::convertible_to<HWND>;
};

HWND GetWindowHandle(HWND value)
{
    return value;
}

namespace winrt::Microsoft::UI::Xaml
{
    HWND GetWindowHandle(Window const& window)
    {
        HWND result;
        check_hresult(window.as<IWindowNative>()->get_WindowHandle(&result));
        return result;
    }
}

namespace winrt::Microsoft::UI::Windowing
{
    HWND GetWindowHandle(AppWindow const& appWindow)
    {
        return GetWindowFromWindowId(appWindow.Id());
    }
}

namespace winrt::Windows::Storage::Pickers
{
    FileOpenPicker::FileOpenPicker(WindowLike auto const& windowLike)
    {
        check_hresult(try_as<IInitializeWithWindow>()->Initialize(GetWindowHandle(windowLike)));
    }

    // Repeat the same for other picker types.
}

Since the number of types using IInitializeWithWindow is not large and is documented, can manually add constructor overloads taking any type that satisfies WindowLike, basically needing to have a way to get HWND.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants