//Build 2018 提出 XAML Islands 幫助 WPF/WinForm 應用程式使用 UWP 的 XAML controls,讓既有的應用程式可以在不同 Windows 10 設備有更好的體驗(例如:Windows Ink 或 Fluent Design)。本篇介紹基本導入與使用時遇到的問題。
根據 UWP controls in desktop applications 與 Windows Community Toolkit Documentation 的介紹,WPF 加入 UWP 控制項有幾個做法:
- 利用 Wrapped controls:
Wrapped controls 由 Windows Community Toolkit 提供,包裝幾個常用的類型:WebView, WebViewCompatible, InkCanvs / InkToolbar, MediaPlayerElmenet 與 MapControl。
需要注意:不同的 Controls 支援的 OS 版本不同;
以 WebView 的使用方式爲例,如下:- 安裝 Microsoft.Toolkit.Wpf.UI.Controls.WebView;
- 為 WPF 加入 application manifest file (link),並設定下面的參數:
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> <application> <!-- Windows 10 --> <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> </application> </compatibility> <application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <!-- The combination of below two tags have the following effect : 1) Per-Monitor for >= Windows 10 Anniversary Update 2) System < Windows 10 Anniversary Update --> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware> </windowsSettings> </application>
- 最後在畫面中加入就可以使用了:
<Window xmlns:local="clr-namespace:WpfWrappedControls" xmlns:toolkit="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls.WebView" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <toolkit:WebView Source="http://www.dotblogs.com.tw/pou" /> </Grid> </Window>
- 利用 WindowsXamlHost:
XAMLHost 能代理 Windows.UI.Xaml.UIElement 所有 controls,但至少需要 Windows 10 1809(17763) 以上。
因此,在使用 WindowsXamlHost 需要先為 WPF 專案加入 C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.17763.0\Windows.winmd 參考。
參考架構圖: 可得知 XAML Host API 與 WebView Win32 API 隨著 OS 已經推出,但是任然有些限制:Limitations。
往下介紹 WindowsXamlHost 的使用:- 安裝 Microsoft.Toolkit.Wpf.UI.XamlHost 並設定 .NET Framework 4.6.2 以上;如果您的專案是 WinForms 可以參考 Get started 的步驟;
- 在 WPF 的 XAML 中加入 XamlHost 包裝的控制項目,並設定 InitialTypeName 為何種 Control 類型與 ChildChanged 事件來加入 XamlHost 實際要顯示的内容與對應注冊的事件:
<Window x:Class="WpfAppHost.MainWindow" xmlns:xamlHost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"> <Grid> <xamlHost:WindowsXamlHost InitialTypeName="Windows.UI.Xaml.Controls.Button" ChildChanged="WindowsXamlHost_ChildChanged"/> </Grid> </Window>
private void WindowsXamlHost_ChildChanged(object sender, EventArgs e) { WindowsXamlHost windowsXamlHost = (WindowsXamlHost)sender; // 利用 Windows.UI.Xaml.Controls 做為轉換 Windows.UI.Xaml.Controls.Button button = (Windows.UI.Xaml.Controls.Button)windowsXamlHost.Child; Windows.UI.Xaml.Controls.TextBlock txt = new Windows.UI.Xaml.Controls.TextBlock(); txt.Text = "Click me"; button.Content = txt; }
這個跟 WPF 使用的 System.Windows.Controls 完全不同。
另外,如果您在編寫時 Windows.UI.Xaml.UIElement 編譯失敗,請檢查是否匯入 C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.17763.0\Windows.winmd 參考了。 - 搭配自定義的 UWP Controls 到 WPF 專案中:
除了上述介紹需要在 ChildChanged 事件中 code-behind 的逐一加入 UWP Controls 外,XamlHost 還提供直接匯入您在 UWP 建立好的 custom controls。
- 準備一個 UWP Class Library 的專案,裏面放置您要導入 WPF 的 UWP custom controls,例如:範例的專案名稱:MyUWPControls;
- 編輯 MyUWPControls 的專案檔案 (*.csproj):
<!-- 要加在 Microsoft.Windows.UI.Xaml.CSharp.targets 之前 --> <PropertyGroup> <EnableTypeInfoReflection>false</EnableTypeInfoReflection> <EnableXBindDiagnostics>false</EnableXBindDiagnostics> </PropertyGroup> <Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" /> <!-- 要加在 Microsoft.Windows.UI.Xaml.CSharp.targets 之後 --> <PropertyGroup> <!-- WpfAppHost 是我的 WPF 專案名稱,它代表是 UWP 專案輸出的整合對象 --> <HostFrameworkProject>WpfAppHost</HostFrameworkProject> </PropertyGroup> <PropertyGroup> <!-- Copy source and build output files to hostapp folders --> <!-- Default Winforms/WPF projects do not use $Platform for build output folder --> <PostBuildEvent> xcopy "$(TargetDir)*.xbf" "$(SolutionDir)$(HostFrameworkProject)\bin\$(Configuration)\$(ProjectName)\" /Y xcopy "$(ProjectDir)*.xaml" "$(SolutionDir)$(HostFrameworkProject)\bin\$(Configuration)\$(ProjectName)\" /Y xcopy "$(ProjectDir)*.xaml.cs" "$(SolutionDir)$(HostFrameworkProject)\$(ProjectName)\" /Y xcopy "$(ProjectDir)$(IntermediateOutputPath)*.g.*" "$(SolutionDir)$(HostFrameworkProject)\$(ProjectName)\" /Y </PostBuildEvent> </PropertyGroup>
此時,您到 WPF 專案會發現多了一個跟 UWP 專案的目錄名稱,請把它加入 WPF 的專案裏面,如下圖: 可發現 UWP 專案中的 BlankPage1.xaml 被編程多個 *.g.cs 與 *.xaml.cs,如果您開發 UWP 其實就會發現 XAML 編譯好的内容在建置本來就有這些,這些檔案記錄的是 XAML 配置的内容與參數。
WPF 專案加入這些參考後,XamlHost 再使用時就會被匯入。 BlankPage1.xaml 是我加入的内容:<Page> <Grid> <TextBlock Text="{x:Bind WPFMessage}" FontSize="50"></TextBlock> </Grid> </Page>
public sealed partial class BlankPage1 : Page { // 作爲 x:bind 的來源,如果您本身使用 ViewModel 要記得開放入口讓外部可以使用 public string WPFMessage { get; set; } public BlankPage1() { this.InitializeComponent(); } }
- 匯入之後在 WPF 專案要怎麽使用:
<Window x:Class="WpfAppHost.MainWindow" xmlns:xamlHost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"> <Grid> <!-- 利用 WindowsXamlHost 包裝 UWP 專案的内容 --> <xamlHost:WindowsXamlHost InitialTypeName="MyUWPControls.BlankPage1" ChildChanged="MyUWPPage_ChildChanged" /> </Grid> </Window>
private void MyUWPPage_ChildChanged(object sender, EventArgs e) { // 利用 GetUwpInternalObject() 把 UWP 的内容截取出來,並轉型成對應的控制項 WindowsXamlHost windowsXamlHost = (WindowsXamlHost)sender; global::MyUWPControls.BlankPage1 myUWPPage = windowsXamlHost.GetUwpInternalObject() as global::MyUWPControls.BlankPage1; if (myUWPPage != null) { myUWPPage.WPFMessage = this.WPFMessage; } }
補充:
- 讓 WinForm 程式支援高 DPI 環境可以參考 Configuring your Windows Forms app for high DPI support 裏面的設定
- 如果是 C++ 的專案要加入 UWP XAML Control 可參考 Using the XAML hosting API in a desktop application 説明
[範例程式]
31-WPFAndXamlHostSample
======
對於微軟在 Windows Apps 的發展,可發現它爲了讓既有的應用程式可以更好地運作在 Windows 10 的所有設備下不少苦心。
如果您的公司或是自己的專案是用 WPF 開發希望可以納入更多 UWP 的效果,真的可以考慮使用 XamlHost。
更可以期待的是 .NET Core 3.0 更支援 Window Forms 應用程式的編程。
References:
- Integrating UWP components into Win32 applications
- XAML Islands – A deep dive – Part 1
- UWP controls in desktop applications
- WindowsXamlHost control for Windows Forms and WPF
- Getting Started with XAML Islands: Hosting a UWP Control in WPF and WinForms Apps
- Desktop Applications with XAML. Part 3: XAML Islands
- UWP XAML hosting API
- What's New in Visual Studio 2019
- The Fluent Design System for Windows app creators
- Modernizing Windows Desktop Applications with XAML Islands
- 通过 XAML Islands 使 Windows 桌面应用程序现代化