[C #] Let’s easily utilize WinRT API from desktop application (WPF)

5 minute read

Introduction

The WinRT (Windows Runtime) API is a new API implemented in Windows 8 and later.
It has a modern UI, and you can use various functions that were not supported by Win32 API.

↓ When it comes to UI, this difference is very much in one message box. (Top: WinRT, Bottom: Win32 API based (MessageBox.Show ()))
image.png
image.png

However, although WinRT has a section for UWP (Universal Windows Platform) (for so-called store apps), there are many functions that are difficult to implement with UWP. ** From ordinary Windows desktop apps (WPF, etc.) to WinRT You may want to “pick and eat” a function. ** **

Can be called via C ++ / WinRT

You can use the functions of WinRT API by ** creating a library with C ++ / WinRT and referencing it from the C # side **.
Since it can be built as a normal desktop application (of course, it can not be executed if the version of Windows is old), you will be freed from the trouble that you can not develop without changing the Windows settings (side loading etc.) or you can only start from the start menu.
[\ C # ] How to make a C ++ / WinRT bridge and call it from C # –Qiita

The official Microsoft sample below also does this.
microsoft/RegFree_WinRT: Sample Code showing use of Registration-free WinRT to access a 3rd party native WinRT Component from a non-packaged Desktop app

But easier

However, it is a fact that the hurdles are a little high for just eating a little bit.
So, in this article, I’ll summarize ** how to call WinRT functions more easily **.

Verification environment

  • Windows 10 Home 1903
  • Visual Studio Community 2019

Advance preparation

Create a C # ** WPF App (.NET Core) ** project.
From the NuGet Package Manager, add Microsoft.Windows.SDK.Contracts. At this time, specify the version of the package according to the target Windows version (such as 1903). [^ Contracts]
This time I chose ** 10.0.18362.2005 ** to target 1903.

[^ Contracts]: [Call the Windows Runtime API in your desktop app Microsoft Docs](https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/desktop-to-uwp-enhance)

I left the project property “Target Framework” at the default ** .NET Core 3.1 **.

You can also choose ** WPF apps (.NET Framework) ** to target the .NET Framework (4.5+), but in that case even more

  • C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.18362.0\Facade\windows.winmd
  • C:\Program Files (x86)\Windows Kits\10\References\10.0.18362.0\Windows.Foundation.UniversalApiContract\8.0.0.0\Windows.Foundation.UniversalApiContract.winmd
  • C:\Program Files (x86)\Windows Kits\10\References\10.0.18362.0\Windows.Foundation.FoundationContract\3.0.0.0\Windows.Foundation.FoundationContract.winmd

I had to manually add a reference to. [^ netfw]

coding

Adding the Microsoft.Windows.SDK.Contracts package will give you access to WinRT features under the Windows namespace, just like UWP apps.
For example, you can access the MessageDialog class like this:

c#:MainWindow.xaml.cs


using System;
using System.Windows;
using Windows.UI.Popups;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var dlg = new MessageDialog("Hello World!");
            await dlg.ShowAsync();
        }
    }
}

However, although this program can be built, I get an exception when I try to run it.

System.Exception: 'The window handle is invalid.

You must either call this API from a thread using CoreWindow or set the window explicitly.'

To solve this problem, cast the created MessageDialog instance to the IInitializeWithWindow`` interface in advance and then call theInitialize () method. At this time, you need to get and specify the handle of the parent window. [^ InvalidHandle] It seems that there are many samples that the window handle is obtained by Process.GetCurrentProcess (). MainWindowHandle, but I think that you may want to specify a specific window instead of the main window, so in the following for application Obtained using new WindowInteropHelper (this) .Handle. [^ WindowHandle] If you are developing based on Windows Forms (WinForms), you can get the window handle with this.Handle``.

The definition of ʻIInitializeWithWindow` is magical. [^ IInitializeWithWindow]

[^ InvalidHandle]: Using FileOpenPicker from C # .net Framework 4.7.2 with Microsoft.Windows.SDK.Contracts without UWP results in “invalid window handle” error]
[^ IInitializeWithWindow]: Pinning secondary tiles from desktop applications-UWP applications | Microsoft Docs
[^ WindowHandle]: How to get a window handle in WPF –present

c#:MainWindow.xaml.cs


using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using Windows.UI.Popups;

namespace WpfApp1
{
    [ComImport]
    [Guid("3E68D4BD-7135-4D10-8018-9FB6D9F33FA1")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IInitializeWithWindow
    {
        void Initialize(IntPtr hwnd);
    }

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var dlg = new MessageDialog("Hello World!");
            ((IInitializeWithWindow)(object)dlg).Initialize(new WindowInteropHelper(this).Handle);
            await dlg.ShowAsync();
        }
    }
}

image.png
Did the message appear in a Windows 10-like UI instead of the old-fashioned UI?
Since it is a normal desktop application, it can be started directly from the built exe. Easy to distribute to other machines. (Although .NET Core must be installed on the destination machine)

You can call it like UWP without thinking.
For example, you can use the Windows.Data.Json class to parse a JSON string. [^ Json]

[^Json]: [Using JavaScript Object Notation (JSON) (Windows Runtime app using C++, C#, or Visual Basic) (Windows) Microsoft Docs](https://docs.microsoft.com/en-us/previous-versions/windows/apps/hh770289%28v=win.10%29)
var obj = Windows.Data.Json.JsonObject.Parse("{\"a\": 1, \"b\": 2}");
System.Diagnostics.Debug.WriteLine(obj["a"].GetNumber());

An example of performing some processing using the copied contents when the clipboard is updated. [^ Clipboard]

Windows.ApplicationModel.DataTransfer.Clipboard.ContentChanged += async (object? sender, object e) =>
{
    Windows.ApplicationModel.DataTransfer.DataPackageView content = Windows.ApplicationModel.DataTransfer.Clipboard.GetContent();
    if (content.Contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.Text))
    {
        var text = await content.GetTextAsync();
        System.Diagnostics.Debug.WriteLine(text);
    }
};

Japanese morphological analysis (dividing sentences into words and reading them) can also be done with standard functions. [^ Japanese Phonetic Analyzer]

var result = Windows.Globalization.JapanesePhoneticAnalyzer.GetWords("Its a sunny day");
foreach (var word in result)
{
    System.Diagnostics.Debug.WriteLine("{0},{1}", word.DisplayText, word.YomiText);
}

For functions such as MessageDialog that display a dialog, you need to call ```IInitializeWithWindow :: Initialize () `` once for the created instance. The rest is the same.

With FileOpenPicker, it looks like this. [^ FileOpenPicker]

[^FileOpenPicker]: [FileOpenPicker Class (Windows.Storage.Pickers) - Windows UWP applications Microsoft Docs](https://docs.microsoft.com/ja-jp/uwp/api/Windows.Storage.Pickers.FileOpenPicker?view=winrt-19041)
var openPicker = new FileOpenPicker();
((IInitializeWithWindow)(object)openPicker).Initialize(new WindowInteropHelper(this).Handle);
openPicker.ViewMode = PickerViewMode.Thumbnail;
openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
openPicker.FileTypeFilter.Add(".jpg ");
StorageFile file = await openPicker.PickSingleFileAsync();
if (file != null)
{
    System.Diagnostics.Debug.WriteLine(file.Path);
}

Finally

It seems that it is easier to call with .NET 5 or later.
Calling the Windows Runtime API from .NET 5 has become a lot easier –Qiita