A deep-dive into WinUI 3 in desktop apps

XAML Islands是第一个能使UWP XAML运行在win32 desktop上的技术。XAML Islands的使用统计,许多新App会完全使用XAML来构建整个UI。许多应用只是使用WPF或者WinForms的窗口来搭载XAML Islands。C++开发者也希望使用XAML来替代MFC。

Windows developers were hungry for creating beautiful, modern, and refreshing apps reusing the desktop code (in the form of .NET or C++ Win32) that they had already been using. In most cases, this code needed to run outside of the security sandbox (AppContainer) and work in all versions of Windows 10 in the market.

支持.NET5。可以使用SDK-style project创建项目。使用C#/WinRT projections作为API接口层。

WinUI 3 NuGet package 依赖于C#/WinRT NuGet package ,后者引入winrt.runtime.dll以及cswinrt.exe,用于WinRT的interop。此外WinUI 3 NuGet Packet还依赖于Microsoft.Windows.SDK.NET ,后者包含Microsoft.Windows.SDK.NET.dll,用于.NET5的interop。

Window class and Win32 APIs

WinUI可以使用XAML标记来创建Windows类型现例。这需要扩展XAML的窗口类,使其能在不同应用模型的窗口实现中使用。例如:

  • UWP应用模型使用CoreWindows
  • Win32引用模型使用HWND

假设使用User32提供的ShowWindow这个API。引用PInvoke.User32可以让.NET使用User32.dll的API。下面是一个例子:

 private void myButton_Click(object sender, RoutedEventArgs e)
{
myButton.Content = Clicked;

IntPtr hwnd = (App.Current as App).MainWindowWindowHandle;
PInvoke.User32.ShowWindow(hwnd, PInvoke.User32.WindowShowStyle.SW_MAXIMIZE);
}

使用GetActiveWindow可以获取当前活动窗口的控把。

MainWindow 实在App.xaml.cs的OnLaunched事件中处理的:

IntPtr m_windowhandle;
public IntPtr MainWindowWindowHandle { get { return m_windowhandle; } }
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
	m_window = new MainWindow();
	m_window.Activate();
	m_windowhandle = PInvoke.User32.GetActiveWindow();
}

Application这个类型也被扩展用于同时支持UWP和Win32应用模型。Application可以察觉当前的运行环境是Win32还是UWP,并由此触发合适的application life cycle events。比如:UWP的resume事件在Win32中不触发。

Full trust desktop apps

开发者不喜欢AppContainer,所以希望有WinRT所谓的“full trust permission”。有了Full Trust,就可以调用许多UWP不能调用的API。比如查询当前进程并获取加载的模块列表。示例如下:

private async void myButton_Click(object sender, RoutedEventArgs e)
{
	myButton.Content = Clicked;

	var description = new System.Text.StringBuilder();
	var process = System.Diagnostics.Process.GetCurrentProcess();
	foreach (System.Diagnostics.ProcessModule module in process.Modules)
	{
		description.AppendLine(module.FileName);
	}

	cdTextBlock.Text = description.ToString();
	contentDialog.XamlRoot = myButton.XamlRoot;
	await contentDialog.ShowAsync();
}

有了HWND,桌面WinUI 3应用就可以使用大部分Win32 API了。

WinUI-3-Demos

一个Desktop的WinUI展示。

DemoBuildCpp/App.xaml.cpp

void App::OnLaunched(LaunchActivatedEventArgs const&)
{
    HWND hwnd;
    window = make<MainWindow>();
    window.as<IWindowNative>()->get_WindowHandle(&hwnd);
    window.Activate();

    // The Window object doesn't have Width and Height properties in WInUI 3 Desktop yet.
    // To set the Width and Height, you can use the Win32 API SetWindowPos.
    // Note, you should apply the DPI scale factor if you are thinking of dpi instead of pixels.
    setWindowSize(hwnd, 800, 600);
}

DemoBuildCpp/MainWindow.xaml.cpp

Open Picker

fire_and_forget  winrt::DemoBuildCpp::implementation::MainWindow::PickFolder()
{
	auto picker = winrt::Windows::Storage::Pickers::FolderPicker();
	HWND hwnd = GetWindowHandle();
	winrt::check_hresult(picker.as<IInitializeWithWindow>()->Initialize(hwnd));
	
	//File and Folder pickers on desktop requires nonempty filters
	picker.FileTypeFilter().Append(L"*");
	auto storageFolder = co_await picker.PickSingleFolderAsync();
	
	if (!storageFolder)
	{
		co_return;
	}
	folderTextBox().Text(storageFolder.Path());
}

Get HWND

HWND winrt::DemoBuildCpp::implementation::MainWindow::GetWindowHandle()
{
	HWND hwnd;
	Window window = this->try_as<Window>();
	window.as<IWindowNative>()->get_WindowHandle(&hwnd);
	return hwnd;
}

(本篇完)