在 WindowsXamlHost:在 WPF 中使用 UWP 的控件(Windows Community Toolkit) 一文中,我們說到了在 WPF 中引入簡單的 UWP 控件以及相關的注意事項。不過,通常更有實際價值的是更復雜的 UWP 控件的引入,通常是一整個 Page。
本文將介紹如何在 WPF 項目中引用 UWP 的控件庫。
本文內容
創建一個 UWP 控件庫
建議專門為你復雜的 UWP 控件創建一個 UWP 控件庫。在這個控件庫中的開發就像普通 UWP 應用一樣。這樣比較容易創建出更復雜的 UWP 控件出來,而不會與 WPF 項目產生太多的影響。
▲ 創建一個 UWP 控件庫
▲ 選擇 SDK 版本
對 WPF 項目的准備工作
你依然需要閱讀 WindowsXamlHost:在 WPF 中使用 UWP 的控件(Windows Community Toolkit) 一文,以便將你的 WPF 項目改造成可以訪問 UWP 類型的項目。
不方便的引入方式
你如果直接讓 WPF 項目添加 UWP 項目的引用,將會得到一個錯誤提示:
也就是說並不能直接完成這樣的引用。
也許將來 WPF 項目格式更新或者 Visual Studio 的更新能為我們帶來這樣更直接此引用方式。不過現在來看,還不能如此方便地使用。
編輯 UWP 項目文件
是的,你需要手工編寫 UWP 的項目文件。
如果你閱讀過 (1/2) 為了理解 UWP 的啟動流程,我從零開始創建了一個 UWP 程序 這篇文章,或者已經 理解了 C# 項目 csproj 文件格式的本質和編譯流程,那么對這里 csproj 文件的編輯應該不會感覺到陌生或者害怕。當然,即便你沒有編輯過或者不理解 csproj 也不用擔心,你只需要按照本文要求進行操作即可。
現在,右擊卸載項目,再右擊編輯項目文件:
▲ 編輯項目文件
找到 Import
targets 的哪一行,你需要在那一行前面的任意位置添加以下特別標注為新增的幾行:
++ <PropertyGroup>
++ <EnableTypeInfoReflection>false</EnableTypeInfoReflection>
++ <EnableXBindDiagnostics>false</EnableXBindDiagnostics>
++ </PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
隨后,還要在以上 targets 之后再添加以下代碼:
<PropertyGroup>
<!-- 這里需要填寫你的 WPF 項目的路徑 -->
<HostFrameworkProjectFolder>$(ProjectDir)..\Whitman.Wpf</HostFrameworkProjectFolder>
<ObjPath>obj\$(Platform)\$(Configuration)\</ObjPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Platform)' == 'AnyCPU' ">
<ObjPath>obj\$(Configuration)\</ObjPath>
</PropertyGroup>
<PropertyGroup>
<!-- 把此項目的輸出文件都拷貝到 WPF 項目的生成路徑下 -->
<PostBuildEvent>
md $(HostFrameworkProjectFolder)\$(ProjectName)
md $(HostFrameworkProjectFolder)\bin\$(Configuration)\$(ProjectName)
copy $(TargetDir)*.xbf $(HostFrameworkProjectFolder)\bin\$(Configuration)\$(ProjectName)
copy $(ProjectDir)*.xaml $(HostFrameworkProjectFolder)\bin\$(Configuration)\$(ProjectName)
copy $(ProjectDir)*.xaml.cs $(HostFrameworkProjectFolder)\$(ProjectName)
copy $(ProjectDir)$(ObjPath)*.g.* $(HostFrameworkProjectFolder)\$(ProjectName)
</PostBuildEvent>
</PropertyGroup>
需要注意:
- 一定要在 targets 之后添加這些代碼,因為
$(TargetDir)
、$(ProjectName)
等屬性是在那里的 targets 執行完后才生成的。 - 你的 UWP 項目中需要有 xaml,比如可以添加一個 MainPage.xaml 和 MainPage.xaml.cs,不然編譯的時候可能會出現錯誤。
重新加載項目並編譯
現在,重新加載那個 UWP 控件庫,將其編譯,以便將 UWP 項目的生成文件復制到 WPF 目錄下。
▲ 生成的文件已復制到 WPF 目錄下
在 WPF 項目中間接引用 UWP 控件庫
現在,在 WPF 項目中開啟所有文件夾的顯示,然后將 UWP 項目中生成的文件添加到 WPF 項目中:
▲ 在 WPF 的項目中添加 UWP 的控件庫
為了能夠在每次編譯 WPF 項目的時候確保 UWP 項目先編譯,需要為 WPF 項目設置項目依賴。在依賴對話框中將 UWP 項目設為依賴。
▲ 添加項目依賴
現在,編譯 WPF 項目的時候,會將 UWP 項目編譯后的源碼也一起編譯到 WPF 項目中;相當於間接使用了 UWP 的控件庫。
特別的,如果你的項目被 git 進行版本管理,你可能需要忽略 UWP 控件庫項目中的文件。方法是在 WPF 項目內生成的 UWP 文件夾下添加一個 .gitignore 文件,填寫所有內容忽略:
*.*
但記得需要額外通過 git add ./Whitman.Wpf/Whitman.Uwp/.gitignore
把這個文件添加到版本管理中,不然其他人不會生效。
在 WPF 項目中使用 UWP 控件庫中的控件
這時,在 WindowsXamlHost
中就可以添加 UWP 控件庫中的 MainPage 了。
<XamlHost:WindowsXamlHost InitialTypeName="Walterlv.Whitman.Universal.MainPage" />
於是,你可以在局部獲得 UWP 完整 Page 的支持。或者你整個界面都是用 UWP 開發都沒問題,並且還能獲得 .NET Framework 的完全訪問支持。(當然,未來一定是 .NET Core。)
▲ 運行后的效果
可以使用 UWP 的 Page,並且也能彈出 UWP 的 MessageDialog
。
而 MainPage 就是普通的 UWP MainPage:
<Page x:Class="Walterlv.Whitman.Universal.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Walterlv.Whitman.Universal" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Width="400" VerticalAlignment="Center">
<TextBlock>
<Run Text="歡迎訪問 呂毅的博客" />
<LineBreak />
<Run Text="https://walterlv.com" />
</TextBlock>
<Button Content="Click" Click="DemoButton_Click" />
</StackPanel>
</Page>
using System;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace Walterlv.Whitman.Universal
{
public sealed partial class MainPage : Page
{
public MainPage() => InitializeComponent();
private async void DemoButton_Click(object sender, RoutedEventArgs e)
{
var button = (Button) sender;
await new MessageDialog("UWP 的消息框,在 WPF 的窗口中。", "walterlv").ShowAsync();
}
}
}