根據 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>
在 ChildChanged 中建立 UI 需要的 Controls,但改用 Windows.UI.Xaml.UIElement 的内容來組合。這個跟 WPF 使用的 System.Windows.Controls 完全不同。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; }
另外,如果您在編寫時 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):
上面的參數是讓 UWP 專案在建置時,把 xbf, xaml, xaml.cs 複製到 host app 的目錄,也就是 WPF 專案裏。<!-- 要加在 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 專案要怎麽使用:
InitialTypeName="MyUWPControls.BlankPage1" 放入的是 UWP 專案中的 namespace,再搭配 ChildChanged 事件來調整顯示的内容。<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>
改用這種方式會讓控制項目整合更加方便,雖然發現 XamlHost 相對容易很多,大部分的 Controls 都能相容,不過比較複雜的自定義控制項目或是比較新的控制項目就不一定會支援,建議參考 Limit 做比對。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 桌面应用程序现代化
沒有留言:
張貼留言