当前位置: 首页 > news >正文

WindowsXamlHost:在 WPF 中使用 UWP 的控件(Windows Community Toolkit)

Windows Community Toolkit 再次更新到 5.0。以前可以在 WPF 中使用有限的 UWP 控件,而现在有了 WindowsXamlHost,则可以使用更多 UWP 原生控件了。


关于 Windows Community Toolkit 早期版本的 Xaml Bridge,可以参见:

  • WPF 使用 Edge 浏览器 - 林德熙

本文内容

      • 安装 NuGet 包
      • 配置 WPF 项目能访问 UWP 的类型
      • 开始在 WPF 中使用 UWP 的控件
      • 可以忽略的错误
      • 最终效果
      • 值得注意的地方
      • 关于 DPI 适配
      • 更复杂的 UWP 控件嵌入
        • 参考资料

安装 NuGet 包

你需要做的第一步,是在你的 WPF 项目中安装 Microsoft.Toolkit.Wpf.UI.XamlHost。建议直接在 项目的 NuGet 管理器中搜索并安装。

安装 Microsoft.Toolkit.Wpf.UI.XamlHost

安装好 NuGet 包后查看引用

配置 WPF 项目能访问 UWP 的类型

因为我们即将开始使用到 UWP 中的控件类型,所以需要配置项目能够访问到 Windows Runtime 的类型。

添加引用
▲ 添加引用

你需要在你的 WPF 项目中添加以下 6 个引用才能访问 UWP 的类型:

  • C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETCore\v4.5
    • 引用 System.Runtime.WindowsRuntime
    • 引用 System.Runtime.WindowsRuntime.UI.Xaml
    • 引用 System.Runtime.InteropServices.WindowsRuntime
  • C:\Program Files (x86)\Windows Kits\10\UnionMetadata\Facade
    • 引用 Windows.winmd
  • C:\Program Files (x86)\Windows Kits\10\References\
    • 在此目录下选择你的 SDK 版本(如 16299,17763 等)
      • Windows.Foundation.UniversalApiContract\
        • 在此目录下选择你的 API 版本(如 4.0.0.0)
          • 引用 Windows.Foundation.UniversalApiContract.winmd
      • Windows.Foundation.FoundationContract
        • 在此目录下选择你的 API 版本(如 3.0.0.0)
          • 引用 Windows.Foundation.FoundationContract.winmd

在你添加完这些引用之后,还需要选中这些引用,右击属性,把所有的 “复制到本地” 选项设置为 “否”。

不要复制到本地

添加 Windows Runtime 的 .NET Framework 类型引用
▲ 添加 Windows Runtime 的 .NET Framework 类型引用

添加 Windows.WinMD 的引用
▲ 添加 Windows.WinMD 的引用

在添加引用时注意选择 SDK 的版本号
▲ 在添加引用时注意选择 SDK 的版本号

添加 Windows.Foundation.UniversalApiContract.winmd
▲ 添加 Windows.Foundation.UniversalApiContract.winmd

添加 Windows.Foundation.FoundationContract.winmd
▲ 添加 Windows.Foundation.FoundationContract.winmd

开始在 WPF 中使用 UWP 的控件

你可以像使用普通 WPF 控件一样将 WindowsXamlHost 添加到你的 WPF 界面中:

  • 拖拽到界面设计器中
  • 拖拽到 XAML 代码行中
  • 直接在 XAML 代码中写

添加 WindowsXamlHost 控件
▲ 添加 WindowsXamlHost 控件

接着,指定 InitialTypeName 属性为 UWP 中的控件的名称(带命名空间)。这样,当 WindowsXamlHost 初始化的时候,也会初始化一个 UWP 的控件。

这里为了简单,我初始化一个 UWP 的按钮。但必须得为 UWP 的按钮进行一些初始化,所以我监听了 ChangedChanged 事件:

<XamlHost:WindowsXamlHost Grid.Column="1"
    InitialTypeName="Windows.UI.Xaml.Controls.Button"
    ChildChanged="WindowsXamlHost_ChildChanged" />
private void WindowsXamlHost_ChildChanged(object sender, EventArgs e)
{
    var host = (WindowsXamlHost) sender;
    var button = (Windows.UI.Xaml.Controls.Button) host.Child;
    button.Width = 120;
    button.Height = 40;
    button.Content = "walterlv.com";
    button.Click += UwpButton_Click;
}

private void UwpButton_Click(object sender, RoutedEventArgs e)
{
}

可以忽略的错误

在启动的时候,你可能会遇到一些异常。比如下面这个:

没有 Application

因为我们不是原生的 UWP,而是 Host 在 WPF 中的 UWP 控件,所以会没有 Application。这在 UWP 控件初始化内部已经 catch 了,所以你可以忽略。

最终效果

当将程序跑起来之后,你就能看到 WPF 窗口中的 UWP 控件了。

运行效果

值得注意的地方

  1. 目前 WindowsXamlHost 还不够稳定,会出现一些闪退
    • 这点就需要为 WindowsCommunityToolkit 贡献 Issues 或代码了
  2. Host 的 UWP 控件是一个新的 HwndSource,这相当于 UWP 的控件是通过子窗口的形式与 WPF 窗口放在一起的
    • 于是,只能指定一个矩形区域完全属于 UWP,在这个区域 WPF 控件无法与其获得交互或渲染叠加

关于 DPI 适配

为了让 UWP 控件能够在 WPF 窗口中获得正确的 Per-Monitor 的 DPI 适配效果,你需要设置为 PerMonitorV2 的 DPI 感知级别。

在 PerMonitorV2 的 DPI 感知级别下,UWP 控件能够正常获得 DPI 缩放。

在 100% DPI 的屏幕下:

100% DPI 下

在 150% DPI 的屏幕下:

PerMonitorV2 感知级别 150% DPI 下

而如果只是指定为 PerMonitor,那么切换 DPI 或者切换屏幕的时候,只有 WPF 部分会缩放,而 UWP 部分不会变化。

PerMonitor 感知级别 150% DPI 下

关于 PerMonitorV2 和 PerMonitor 的理解和区别,可以参见:

  • Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32) - walterlv

关于如何在 WPF 下开启 PerMonitorV2 级别的 DPI 感知可以参见:

  • 支持 Windows 10 最新 PerMonitorV2 特性的 WPF 多屏高 DPI 应用开发 - walterlv

更复杂的 UWP 控件嵌入

如果希望将更多的 WPF 窗口内的 UI 部分交给 UWP 来做,那么就不能只是仅仅初始化一个 Button 就完了。

你需要引入一个 UWP 控件库。阅读以下文章了解更多:

  • WindowsXamlHost:在 WPF 中使用 UWP 控件库中的控件 - walterlv

参考资料

  • WindowsXAMLHost control - Windows Community Toolkit - Microsoft Docs
  • Enhance your desktop application for Windows 10 - UWP app developer - Microsoft Docs

相关文章:

  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)
  • 编写 Target 检测 MSBuild / dotnet build 此次编译是否是差量编译
  • 使用 Win2D 绘制带图片纹理的圆(或椭圆)
  • Win2D 中的游戏循环:CanvasAnimatedControl
  • 使用 Windows 10 中的加速度计(Accelerometer,重力传感器)
  • 用 dotTrace 进行性能分析时,各种不同性能分析选项的含义和用途
  • 如何创建一个基于 .NET Core 3 的 WPF 项目
  • 将基于 .NET Framework 的 WPF 项目迁移到基于 .NET Core 3
  • 了解 .NET 的默认 TaskScheduler 和线程池(ThreadPool)设置,避免让 Task.Run 的性能急剧降低
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • 在有 UI 线程参与的同步锁(如 AutoResetEvent)内部使用 await 可能导致死锁
  • 不要使用 Dispatcher.Invoke,因为它可能在你的延迟初始化 LazyT 中导致死锁
  • 定义一组抽象的 Awaiter 的实现接口,你下次写自己的 await 可等待对象时将更加方便
  • .NET 除了用 Task 之外,如何自己写一个可以 await 的对象?
  • .NET 中什么样的类是可使用 await 异步等待的?
  • [nginx文档翻译系列] 控制nginx
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • canvas 高仿 Apple Watch 表盘
  • create-react-app项目添加less配置
  • Python 反序列化安全问题(二)
  • 从setTimeout-setInterval看JS线程
  • 官方解决所有 npm 全局安装权限问题
  • 基于web的全景—— Pannellum小试
  • 前端存储 - localStorage
  • 设计模式 开闭原则
  • 数组的操作
  • 硬币翻转问题,区间操作
  • 鱼骨图 - 如何绘制?
  • mysql面试题分组并合并列
  • ​iOS实时查看App运行日志
  • ​LeetCode解法汇总1276. 不浪费原料的汉堡制作方案
  • ![CDATA[ ]] 是什么东东
  • #Linux(权限管理)
  • #我与Java虚拟机的故事#连载13:有这本书就够了
  • (1) caustics\
  • (多级缓存)多级缓存
  • (论文阅读31/100)Stacked hourglass networks for human pose estimation
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • (转)setTimeout 和 setInterval 的区别
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • (转载)微软数据挖掘算法:Microsoft 时序算法(5)
  • .NET 5种线程安全集合
  • .net 7 上传文件踩坑
  • .NET 的程序集加载上下文
  • .NET 应用架构指导 V2 学习笔记(一) 软件架构的关键原则
  • .NET6 开发一个检查某些状态持续多长时间的类
  • .Net开发笔记(二十)创建一个需要授权的第三方组件
  • @TableId注解详细介绍 mybaits 实体类主键注解
  • [ C++ ] STL_list 使用及其模拟实现
  • [ 蓝桥杯Web真题 ]-布局切换
  • [2015][note]基于薄向列液晶层的可调谐THz fishnet超材料快速开关——
  • [AIGC] 开源流程引擎哪个好,如何选型?
  • [Angular] 笔记 9:list/detail 页面以及@Output