使用 XAML 岛在 C# WPF 应用中托管 UWP XAML 控件

重要

本主题使用或提及 CommunityToolkit/Microsoft.Toolkit.Win32 GitHub 存储库中的类型。 有关 XAML Islands 支持的重要信息,请参阅该存储库中的 XAML Islands 通知

本主题演示如何生成 C# Windows Presentation Foundation (WPF) 应用(面向 .NET Core 3.1),该应用使用 XAML 岛 托管通用 Windows 平台 (UWP) XAML 控件(即 Windows SDK 提供的第一方控件)。 我们演示执行此操作的两种方式:

  • 我们演示如何使用包装控件(在 Windows 社区工具包中获取)托管 UWP InkCanvasInkToolbar 控件。 包装控件可包装一小部分有用的 UWP XAML 控件的界面和功能。 可以在 WPF 或 Windows 窗体项目的设计图面中直接添加包装控件,然后在设计器中像使用任何其他 WPF 或 Windows 窗体控件那样使用该控件。

  • 我们还演示了如何使用 WindowsXamlHost 控件(在 Windows 社区工具包中获取)托管 UWP CalendarView 控件。 由于只有一小部分 UWP XAML 控件作为包装控件提供,因此可以使用 WindowsXamlHost 来托管任何 UWP XAML 控件。

在 WPF 应用中托管 UWP XAML 控件的过程类似于在 Windows 窗体应用中托管控件的过程。

重要

仅支持在面向 .NET Core 3.x 的应用中使用 XAML 岛(包装控件或 WindowsXamlHost)来托管 UWP XAML 控件。 不支持在面向 .NET 的应用中和面向任何 .NET Framework 版本的应用中使用 XAML 岛。

若要在 WPF 或 Windows 窗体应用中托管 UWP XAML 控件,建议在解决方案中包含以下组件。 本主题提供了创建各个组件的说明:

  • 项目和 WPF 或 Windows 窗体应用源代码

  • 定义派生自 XamlApplication 的根应用程序类的 UWP 项目Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication 类在 Windows 社区工具包中获取。 建议将 XamlApplication 派生的应用程序类定义为属于 WPF 或 Windows 窗体 Visual Studio 解决方案的一部分的单独 UWP 应用项目。

    注意

    若要托管第一方 UWP XAML 控件,实际上不需要向 WPF 或 Windows 窗体项目提供 XamlApplication 派生的对象。 但若要发现、加载和托管自定义 UWP XAML 控件,那么这是必要的。 因此,为了支持各种 XAML 岛方案,建议始终在使用 XAML 岛的任何解决方案中定义 XamlApplication 派生的对象。

    注意

    解决方案只能包含一个定义 XamlApplication 派生的对象的项目。 该项目必须引用任何其他库和通过 XAML 岛托管 UWP XAML 控件的项目。

创建 WPF 项目

可按照下面的说明创建新的 WPF 项目,并对它进行配置,用于托管 XAML 岛。 如果你有现成的 WPF 项目,可以使用以下步骤和代码示例处理它。

  1. 若尚未创建项目,请安装 .NET Core 3.1 的最新版本。

  2. 在 Visual Studio 中,通过 WPF 应用程序项目模板创建一个新的 C# 项目。 将“项目名称”设置为 MyWPFApp,这样就无需编辑本主题中的任何步骤或源代码。 将 Framework 设置为 .NET Core 3.1,然后单击“创建”。

重要

请勿使用“WPF 应用(.NET Framework)”项目模板。

配置 WPF 项目

  1. 以下步骤启用包引用

    1. 在 Visual Studio 中,单击“工具”>“NuGet 包管理器”>“包管理器设置”。
    2. 在右侧找到“包管理”>“默认包管理格式”设置,将其设置为 PackageReference。
  2. 使用以下步骤安装 Microsoft.Toolkit.Wpf.UI.Controls NuGet 包:

    1. 在“解决方案资源管理器”中右键单击“MyWPFApp”项目代码,然后选择“管理 NuGet 包...”。

    2. 在“浏览”选项卡上,将 Microsoft.Toolkit.Wpf.UI.Controls 键入或粘贴到搜索框。 选择最新的稳定版本,然后单击“安装”。 此包提供使用适用于 WPF 的已包装 UWP XAML 控件所需的所有内容(包括 InkCanvasInkToolbarWindowsXamlHost 控件)。

    注意

    对于 Windows 窗体应用,请改为引用 Microsoft.Toolkit.Forms.UI.Controls 包。

  3. 大多数 XAML 岛方案在面向任何 CPU 的项目中不受支持。 因此,若要面向特定体系结构(如 x86 或 x64),请执行以下操作:

    1. 右键单击“解决方案资源管理器”中的解决方案节点(而不是项目节点),并选择“属性”。
    2. 选择左侧的“配置属性”。
    3. 单击“配置管理器...”按钮。
    4. 在“活动解决方案平台” 下,选择“新建” 。
    5. 在“新建解决方案平台”对话框中,选择“x64”或“x86”,并按“确认”。
    6. 关闭打开的对话框。

在新 UWP 项目中定义 XamlApplication 类

在本部分,需将 UWP 项目添加到解决方案,并将此项目中的默认 App 类修改为派生自 Windows 社区工具包提供的 Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication 类。 此类支持 IXamlMetadataProvider 接口,该接口使应用能够在运行时发现和加载应用程序的当前目录内程序集中的自定义 UWP XAML 控件的元数据。 此类还为当前线程初始化 UWP XAML 框架。

  1. 在“解决方案资源管理器”中,右键单击解决方案节点,然后选择“添加”>“新建项目...”。

  2. 选择 C#“空白应用(通用 Windows)”项目模板。 将“项目名称”设置为 MyUWPApp,这样就无需编辑本主题中的任何步骤或源代码。 将目标版本和最低版本设置为 Windows 10 版本 1903(内部版本 18362)或更高版本。

    注意

    请勿在 MyWPFApp 的子文件夹中创建 MyUWPApp。 如果执行此操作,MyWPFApp 将尝试生成 UWP XAML 标记,就像它是 WPF XAML 一样。

  3. 在 MyUWPApp 中,安装 Microsoft.Toolkit.Win32.UI.XamlApplication NuGet 包(最新稳定版)。 上一部分介绍了 NuGet 包的安装过程。

  4. 在 MyUWPApp 中,打开 App.xaml 文件,并将其内容替换为以下 XAML:

    <xaml:XamlApplication
        x:Class="MyUWPApp.App"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:xaml="using:Microsoft.Toolkit.Win32.UI.XamlHost"
        xmlns:local="using:MyUWPApp">
    </xaml:XamlApplication>
    
  5. 同样地,打开 App.xaml.cs,将其内容替换为以下代码:

    namespace MyUWPApp
    {
        public sealed partial class App : Microsoft.Toolkit.Win32.UI.XamlHost.XamlApplication
        {
            public App()
            {
                this.Initialize();
            }
        }
    }
    
  6. 删除 MainPage.xamlMainPage.xaml.cs 文件。

  7. 生成 MyUWPApp 项目。

在 MyWPFApp 中,添加对 MyUWPApp 项目的引用

  1. 在 MyWPFApp 的项目文件中指定兼容的框架版本,如下所示:

    1. 在“解决方案资源管理器”中,单击 MyWPFApp 项目节点,在编辑器中打开项目文件。

    2. 在第一个 PropertyGroup 元素中添加以下子元素。 根据需要更改值的 19041 部分,以匹配 MyWPFApp 项目的目标版本和最低 OS 版本。

      <AssetTargetFallback>uap10.0.19041</AssetTargetFallback>
      
  2. 在“解决方案资源管理器”中,右键单击“MyWPFApp”>“依赖项”,选择“添加项目引用...”,然后添加对 MyUWPApp 项目的引用。

在 MyWPFApp 的入口点中实例化 XamlApplication 对象

接下来,将代码添加到 MyWPFApp 的入口点,以创建刚刚在 MyUWPApp 中定义的 App 类(派生自 XamlApplication)的实例。

  1. 右键单击 MyWPFApp 项目节点,选择“添加”>“新建项目...”,然后选择“类”。 将“名称”设置为 Program.cs,然后单击“添加”。

  2. Program.cs 的内容替换为以下 XAML(然后保存文件并生成 MyWPFApp 项目):

    namespace MyWPFApp
    {
        public class Program
        {
            [System.STAThreadAttribute()]
            public static void Main()
            {
                using (new MyUWPApp.App())
                {
                    var app = new MyWPFApp.App();
                    app.InitializeComponent();
                    app.Run();
                }
            }
        }
    }
    
  3. 右键单击 MyWPFApp 项目节点,然后选择“属性”。

  4. 在“应用程序”>“通用”中,单击“启动对象”下拉列表,然后选择“MyWPFApp.Program”(刚刚添加的 Program 类的完全限定名称)。 如果未看到此名称,请尝试关闭并重新打开 Visual Studio。

    注意

    默认情况下,WPF 项目在不可修改的生成代码文件中定义 Main 入口点函数。 上述步骤将项目的入口点更改为新 Program 类的 Main 方法,这样添加的代码即可在应用启动过程中尽可能早地运行。

  5. 保存对项目属性的更改。

使用包装控件托管 InkCanvas 和 InkToolbar

现已将项目配置为使用 UWP XAML 岛,接下来可以将 InkCanvasInkToolbar 包装的 UWP XAML 控件添加到应用了。

  1. 在 MyWPFApp 中,打开 MainWindow.xaml 文件。

  2. 在 XAML 文件顶部附近的 Window 元素中,添加以下属性。 此特性引用 InkCanvas 和 InkToolbar 包装的 UWP XAML 控件的 XAML 命名空间,并将其映射到控件 XML 命名空间。

    xmlns:controls="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls"
    
  3. 同样在 MainWindow.xaml 中,编辑现有的 Grid 元素,使其类似于下面的 XAML。 此 XAML 向 Grid 添加一个 InkCanvas 控件和一个 InkToolbar 控件(以在上一步中定义的控件 XML 命名空间为前缀)。

    <Grid Margin="10,50,10,10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <controls:InkToolbar x:Name="myInkToolbar" TargetInkCanvas="{x:Reference myInkCanvas}" Grid.Row="0" Width="300"
            Height="50" Margin="10,10,10,10" HorizontalAlignment="Left" VerticalAlignment="Top" />
        <controls:InkCanvas x:Name="myInkCanvas" Grid.Row="1" HorizontalAlignment="Left" Width="600" Height="400"
            Margin="10,10,10,10" VerticalAlignment="Top" />
    </Grid>
    

    注意

    还可以将这些控件和其他包装控件从“工具箱”的“Windows 社区工具包”部分拖到设计器中,通过这种方式将它们添加到该窗口中。

  4. 保存 MainWindow.xaml

    如果设备支持数字笔(如 Surface),并且你是在物理计算机上执行操作,则现在可以使用笔生成并运行应用,并在屏幕上绘制数字墨迹。 但如果你尝试使用鼠标进行书写,则不会发生任何变化,因为默认情况下,仅为数字笔启用 InkCanvas。 下面介绍了如何为鼠标启用 InkCanvas。

  5. 同样在 MyWPFApp 中,打开 MainWindow.xaml.cs

  6. 将下面的命名空间指令添加到文件顶部:

    using Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT;
    
  7. 找到 MainWindow 构造函数。 调用 InitializeComponent 后,立即添加以下代码行:

    myInkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Pen;
    

    可以使用 InkPresenter 对象自定义默认墨迹书写体验。 上述代码使用 InputDeviceTypes 属性启用鼠标和笔输入。

  8. 保存、生成并运行。 如果你使用的是带鼠标的计算机,请确认可以用鼠标在墨迹画布空间中绘制一些内容。

使用主机控件托管 CalendarView

在本部分,我们将使用 WindowsXamlHost 控件向应用添加 CalendarView

注意

WindowsXamlHost 控件由 Microsoft.Toolkit.Wpf.UI.XamlHost 包提供。 此包包含在你之前安装的 Microsoft.Toolkit.Wpf.UI.Controls 包中。

  1. 在“解决方案资源管理器”的“MyWPFApp”中,打开 MainWindow.xaml 文件。

  2. 在 XAML 文件顶部附近的 Window 元素中,添加以下属性。 此特性引用 WindowsXamlHost 控件的 XAML 命名空间,并将其映射到 xamlhost XML 命名空间。

    xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"
    
  3. 同样在 MainWindow.xaml 中,编辑现有的 Grid 元素,使其类似于下面的 XAML。 此 XAML 向 Grid 添加一个 WindowsXamlHost 控件(以在上一步中定义的 xamlhost XML 命名空间为前缀)。 为了托管 UWP CalendarView 控件,此 XAML 将 InitialTypeName 属性设置为控件的完全限定名称。 此 XAML 还为 ChildChanged 事件定义了一个事件处理程序,该事件是在呈现托管控件时引发的。

    <Grid Margin="10,50,10,10">
        <xamlhost:WindowsXamlHost x:Name="myCalendar" InitialTypeName="Windows.UI.Xaml.Controls.CalendarView"
              Margin="10,10,10,10" Width="600" Height="300" ChildChanged="MyCalendar_ChildChanged" />
    </Grid>
    
  4. 保存 MainWindow.xaml 并打开 MainWindow.xaml.cs

  5. 删除在上一部分中添加的此代码行:myInkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Pen;

  6. 将下面的命名空间指令添加到文件顶部:

    using Microsoft.Toolkit.Wpf.UI.XamlHost;
    
  7. 将下面的 ChildChanged 事件处理程序方法添加到 MainWindow 类。 如果已呈现托管控件,此事件处理程序将运行并为日历控件的 SelectedDatesChanged 事件创建一个简单的事件处理程序。

    private void MyCalendar_ChildChanged(object sender, EventArgs e)
    {
        WindowsXamlHost windowsXamlHost = (WindowsXamlHost)sender;
    
        var calendarView =
            (Windows.UI.Xaml.Controls.CalendarView)windowsXamlHost.Child;
    
        if (calendarView != null)
        {
            calendarView.SelectedDatesChanged += (obj, args) =>
            {
                if (args.AddedDates.Count > 0)
                {
                    MessageBox.Show("The user selected a new date: " +
                        args.AddedDates[0].DateTime.ToString());
                }
            };
        }
    }
    
  8. 保存、生成并运行。 确认日历控件显示在窗口中,并在选择日期时显示一个消息框。

打包应用

可以选择在 MSIX 包中打包 WPF 应用以供部署。 MSIX 是适用于 Windows 的可靠的现代化应用打包技术。

下面的说明介绍了如何在 Visual Studio 中使用 Windows 应用程序打包项目将解决方案中的所有组件打包到 MSIX 包(请参阅在 Visual Studio 中设置用于 MSIX 打包的桌面应用程序)。 只有在需要将 WPF 应用打包到 MSIX 包时,才需要使用这些步骤。

注意

如果选择不在 MSIX 包中打包应用程序以供部署,则运行应用的计算机必须安装有 Visual C++ 运行时

  1. 将一个新项目添加到通过 Windows 应用程序打包项目项目模板创建的解决方案。 创建项目时,请选择与 UWP 项目相同的目标版本和最低版本 。

  2. 在打包项目中,右键单击“依赖项”节点,然后选择“添加项目引用...”。在项目列表中,选择“MyWPFApp”,然后单击“确定”。

    注意

    如果你想要在 Microsoft Store 中发布应用,则还必须在打包项目中添加对 UWP 项目的引用。

  3. 如果你一直按照上述步骤操作,则解决方案中的所有项目都将面向同一特定平台(x86 或 x64)。 若要使用 Windows 应用程序打包项目将 WPF 应用生成到 MSIX 包中,必须执行此操作。 若要确认这一点,可以执行以下步骤:

    1. 右键单击“解决方案资源管理器”中的解决方案节点(而不是项目节点),并选择“属性”。
    2. 选择左侧的“配置属性”。
    3. 单击“配置管理器...”按钮。
    4. 确认所有列出的项目在“平台”(x86 或 x64)下都具有相同的值。
  4. 右键单击刚刚添加的打包项目的项目节点,然后单击“设为启动项目”。

  5. 生成并运行打包项目。 确认 WPF 应用按预期运行且 UWP 控件按预期显示。

  6. 有关分发/部署包的信息,请参阅管理 MSIX 部署