ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

WinUI NavigationView 渲染机制深度解析:从 ControlTemplate 部件到双显示模式的 UI 实现

WinUI NavigationView 渲染机制深度解析:从 ControlTemplate 部件到双显示模式的 UI 实现 WinUI NavigationView 渲染机制深度解析从 ControlTemplate 部件到双显示模式的 UI 实现【免费下载链接】microsoft-ui-xamlWinUI: a modern UI framework with a rich set of controls and styles to build dynamic and high-performing Windows applications.项目地址: https://gitcode.com/GitHub_Trending/mi/microsoft-ui-xaml本指南以微软 WinUI 3microsoft-ui-xaml仓库中 NavigationView 渲染文档 为主体结合 NavigationView.xaml 控制模板、NavigationView.cpp 代码后台与 NavigationViewItemsFactory.cpp 实现完整剖析 NavigationView 的 UI 构成ItemTemplate 如何与 NavigationViewItem 协作、左右两种 DisplayMode 各自使用哪些模板部件、代码后台如何引用这些部件完成布局与交互。读完你将能够理解 NavigationView 模板的每一块拼图并具备自定义/复刻其模板结构的能力。NavigationView 渲染概述NavigationView 的 UI 渲染与DisplayMode显示模式强相关左侧导航Left与顶部导航Top两种模式下控件模板ControlTemplate中处于激活状态的可视化部件完全不同。因此在 rendering.md 中作者将模板部件按两种模式共用 / 仅 Left 使用 / 仅 Top 使用三组分别说明。实际的模板定义位于 NavigationView.xaml 的ControlTemplate TargetTypecontrols:NavigationView中模板根部是一个x:NameRootGrid的Grid内部通过VisualStateManager.VisualStateGroups声明了DisplayModeGroup、TogglePaneGroup、PaneStateGroup、PaneOverlayGroup等状态组再由VisualState.Setters动态切换各个命名部件的可见性、边距与样式——这正是渲染差异的实现机制。ItemTemplates 与 NavigationViewItems容器化的两条路径文档明确了 NavigationView 同时支持MenuItemsSource与ItemTemplate二者叠加时的行为规则如下如果提供的MenuItems本身就是NavigationViewItem且未设置ItemTemplate则这些NavigationViewItem会原样直接使用不做任何包装如果设置了ItemTemplate则NavigationViewItem根据模板内容行动模板返回的是NavigationViewItem→ 直接使用该模板结果不加包装模板返回的不是NavigationViewItem例如一个Button→ 返回的元素会被包装进一个NavigationViewItem内。这套规则在源码中有直接对应的实现。NavigationView 内部为所有项容器统一使用NavigationViewItemsFactory一个ElementFactory其GetElementCore方法见 NavigationViewItemsFactory.cpp按如下逻辑解析数据项设置项Settings不做模板化直接返回存在 ItemTemplateWrapper 时用模板生成元素如果解析出的元素已经派生自NavigationViewItemBase直接返回——对应模板返回 NavigationViewItem 时不加包装否则从对象池取出或新建一个NavigationViewItem把模板结果作为其 Content 装入——对应非 NavigationViewItem 元素被包装。此外NavigationViewItemsFactory内部维护了一个navigationViewItemPool对象池被包装的 NavigationViewItem 在回收后可以复用这与容器化wrapping流程配合减少了频繁创建容器的开销。另外文档还提示设置Settings项会被渲染为 FooterMenu 项列表中的一个元素对应代码中NavigationViewItemsFactory::SettingsItem的注入逻辑。ControlTemplate 共用部件两种模式都需要的模板结构文档列出的两个 DisplayMode 都需要的模板部件及其在代码后台code behind中的用途如下完整继承自原文档模板部件名代码后台中的用途RootGrid模板根用于键盘导航XYFocusKeyboardNavigationPaneToggleButtonGrid未使用TogglePaneTopPadding被引用用于调整内边距标题栏 TopPaddingButtonHolderGrid未使用NavigationViewBackButton允许触发BackRequested事件NavigationViewBackButtonToolTip返回按钮 ToolTip 的本地化NavigationViewCloseButton关闭按钮功能NavigationViewCloseButtonToolTip关闭按钮 ToolTip 的本地化TogglePaneButton打开/关闭 Pane 的功能PaneTitleTextBlockPane 标题的绑定与渲染PaneTitleHolder承载 Pane 标题 presenterPaneTitlePresenter渲染 Pane 标题这些部件的实际引用逻辑位于 NavigationView.cpp 的OnApplyTemplate代码通过GetTemplateChildTT(名称)逐一抓取模板子元素并挂接事件。例如TogglePaneButton抓取后挂接Click事件OnPaneToggleButtonClick并追加WinBackspace键盘加速键见 NavigationView.cppNavigationViewBackButton抓取后挂接点击事件以触发BackRequested并设置自动化名称NavigationView.cppNavigationViewCloseButton与TogglePaneButton共享同一个点击处理函数NavigationView.cppNavigationViewBackButtonToolTip/NavigationViewCloseButtonToolTip在模板应用时通过ResourceAccessor::GetLocalizedStringResource写入本地化文本RootGrid上开启XYFocusKeyboardNavigation(Enabled)而内容区域的ContentGrid被显式设为Disabled——这样游戏手柄/键盘方向键导航只作用于 Pane 与汉堡按钮区域不进入内容区NavigationView.cpp。模式切换时的部件搬运文档特别指出当在 Top 与 Left 两种显示模式之间切换时NavigationView 会移动某些元素如 PaneHeader 与 AutoSuggestBox原因是 UI 元素只能被加入 VisualTree 一次而这些元素必须出现在当前可见的区域中。从模板结构可以印证这一点模板中同时声明了两组宿位——左侧模式下的PaneHeaderContentBorder、PaneAutoSuggestBoxPresenter与顶部模式下的PaneHeaderOnTopPane、TopPaneAutoSuggestBoxPresenter是各自独立的ContentControl。代码后台在OnApplyTemplate中同时抓取这两组宿位NavigationView.cpp并在切换显示模式时通过UpdatePaneTitleFrameworkElementParents等更新方法决定将同一个逻辑内容如 AutoSuggestBox挂到哪一组 presenter 上实现单实例元素的搬家。DisplayMode Left基于 SplitView 的左侧导航渲染左侧模式使用的模板部件文档列出的 Left 模式部件及用途如下完整继承模板部件名代码后台中的用途RootSplitView渲染内容区与左侧 PanePaneContentGrid左侧 Pane 的布局动画也需要它ItemsContainerRow菜单项/页脚项的高度分配ContentPaneTopPadding高度调整PaneHeaderContentBorderRow尺寸调整时被引用PaneHeaderCloseButtonColumn用于 CompactPaneLength 下的关闭按钮列宽调整PaneHeaderToggleButtonColumn用于 CompactPaneLength 下的切换按钮列宽调整PaneHeaderContentBorderPane 头的手动尺寸控制AutoSuggestArea未被引用PaneAutoSuggestBoxPresenter用于检查 AutoSuggest 区域是否有内容PaneAutoSuggestButton按钮被调用时打开 Pane搜索按钮PaneCustomContentBorderPane 头渲染自定义内容ItemsContainerGrid菜单项与页脚项视图的尺寸计算MenuItemsScrollViewer限制菜单项宿主的最大高度MenuItemsHost渲染 NavigationViewItemsVisualItemsSeparator需要时动态显示/隐藏分隔线FooterContentBorder渲染 Pane 页脚PaneFooterFooterMenuItemsHost渲染页脚菜单项ContentGrid阴影处理与焦点行为ContentTopPadding未被引用ContentLeftPadding内容区左内边距HeaderContent未被引用在 NavigationView.xaml 中可以看到这些部件的真实形态RootSplitView使用DisplayModeInlineIsPaneOpen与NavigationView.IsPaneOpen双向绑定Pane 内PaneContentGrid以 7 行 RowDefinition 组织顶栏 → 关闭/返回按钮行 → PaneHeader → AutoSuggestArea → 自定义内容 → 菜单/页脚区MenuItemsHost与FooterMenuItemsHost都是ItemsRepeater分别以StackLayout纵向排布并被包在ItemsRepeaterScrollHostScrollViewer中。VisualItemsSeparator是一个默认Collapsed的NavigationViewItemSeparator由PaneSeparatorStates状态组控制显隐。SplitView 驱动的显隐逻辑文档指出Left 模式下由 SplitView 根据显示模式与 NavigationView 宽度来决定 Pane 的显示/隐藏当PaneDisplayMode为Auto时Pane 依据宽度自动隐藏。这一自适应逻辑在 NavigationView.cpp 的UpdateAdaptiveLayout中实现由OnSizeChanged触发见 NavigationView.cppPaneDisplayMode Auto宽度 ExpandedModeThresholdWidth时切换为Expanded宽度 0且 CompactModeThresholdWidth时切换为Minimal介于两者之间保持CompactPaneDisplayMode Left恒定ExpandedPaneDisplayMode LeftCompact恒定CompactPaneDisplayMode LeftMinimal恒定Minimal。切换过程中代码还会协同调用OpenPane/ClosePane如进入Expanded且 Pane 可见时自动打开进入Minimal时自动关闭并同步更新NavigationViewTemplateSettings.OpenPaneLength供模板绑定使用UpdateOpenPaneLength会把OpenPaneLength限制在不超过当前宽度范围内见 NavigationView.cpp。菜单区与页脚区的空间分配算法ItemsContainerRow、MenuItemsScrollViewer与FooterItemsScrollViewer的真正用途体现在 NavigationView.cpp 的UpdatePaneLayout中代码将 Pane 内可用高度在菜单项与页脚组FooterItems PaneFooter之间按优先级划分——页脚优先因为通常包含设置、个人资料等重要入口。其分配策略没有页脚项且不显示设置项收起分隔线菜单项占用全部高度没有菜单项限制页脚滚动区域高度为全部可用高度收起分隔线空间足够容纳两者各自按所需高度分配收起分隔线页脚超过一半高度限制页脚高度显示分隔线菜单超过一半高度限制菜单高度显示分隔线双方都超过一半对半平分显示分隔线。这就是VisualItemsSeparator动态显示/隐藏的完整决策逻辑——它并不是装饰而是空间不足、双方开始争抢高度时出现的信号。当m_footerItemsSource.Count() 0 !IsSettingsVisible()等条件变化、或OnSizeChanged、OnItemsContainerSizeChanged、页脚集合变化时UpdatePaneLayout都会被再次调用以重新计算。DisplayMode Top基于 ItemsRepeater 的顶部导航渲染顶部模式使用的模板部件文档列出的 Top 模式部件及用途如下完整继承模板部件名代码后台中的用途TopNavArea未被引用TopNavLeftPadding未被引用TopNavGrid用于低版本down level支持BackButtonPlaceholderOnTopNav未被引用PaneHeaderOnTopPane渲染头部PaneHeaderPaneTitleOnTopPane渲染 Pane 标题TopNavMenuItemsHost在顶部模式下渲染 NavigationViewItemsTopNavOverflowButton空间不足时打开溢出菜单TopNavMenuItemsOverflowHost渲染溢出 Flyout 中的项PaneCustomContentOnTopPane渲染自定义 Pane 内容TopPaneAutoSuggestArea为搜索框预留空间TopPaneAutoSuggestBoxPresenter渲染 AutoSuggestBoxTopFooterMenuItemsHost渲染页脚菜单项PaneFooterOnTopPane显示 Pane 页脚TopNavContentOverlayAreaGrid用于内容叠加ContentOverlay模板中TopNavArea是一个StackPanel内含TopNavGridNavigationView.xaml。TopNavGrid是一个 9 列 Grid返回按钮占位列、TopNavLeftPadding、PaneHeaderOnTopPane、PaneTitleOnTopPane、横向ItemsRepeater的TopNavMenuItemsHost、TopNavOverflowButton、PaneCustomContentOnTopPane、TopPaneAutoSuggestArea内含TopPaneAutoSuggestBoxPresenter、PaneFooterOnTopPane以及TopFooterMenuItemsHost。溢出Overflow机制的模板结构TopNavOverflowButton在模板中是一个带Flyout的按钮TopNavMenuItemsOverflowHost位于该 Flyout 内部NavigationView.xaml。从代码后台NavigationView.cpp可以看到顶部主菜单与溢出菜单各自持有独立的ItemsRepeaterm_topNavRepeater与m_topNavRepeaterOverflowView数据源分别来自TopNavigationViewDataProvider::GetPrimaryItems()与GetOverflowItems()溢出按钮在模板应用时写入本地化文本与 ToolTip并监听 Flyout 的Closing事件当溢出集合变为空时溢出按钮会被折叠OnOverflowItemsSourceCollectionChanged见 NavigationView.cpp。当用户在溢出菜单中选中某个叶子项时NavigationView 会把该项从溢出区移入主菜单区SelectandMoveOverflowItem见 NavigationView.cpp这正是溢出项被选中后提升为主项的行为来源。顶部模式的层级结构Hierarchical NavigationView与左栏不同顶部导航的层级子项并非展开在面板内而是通过ChildrenFlyout弹出。相关结构在 NavigationViewItem 渲染文档 中有配套说明NavigationViewItem的模板由NVIRootGrid根、NavigationViewItemPresenter负责项的实际渲染、NavigationViewItemMenuItemsHost渲染子项与ChildrenFlyout顶部模式下子项弹出的 Flyout组成。在 NavigationView.xaml 的 NavigationViewItem 模板中可以看到OnTopNavigationPrimary状态会把 presenter 切换为顶部专用样式、并把ChildrenFlyout的Placement改为BottomEdgeAlignedLeftOnTopNavigationOverflow状态则使用溢出专用样式。NavigationViewItemPresenter在 NavigationViewItemPresenter.idl 中定义其具体样式定义在 NavigationView_themeresources.xaml 中。从仓库历史看NavigationView 内部容器已从早期 ListView 全面迁移到 ItemsRepeater SelectionModel详见 NavigationView_Overview.md顶部导航的所有项宿主主菜单、溢出菜单、左右页脚菜单均为ItemsRepeater这为层级展开、溢出提升等复杂交互提供了基础。总结如何阅读与自定义 NavigationView 模板把本文档与模板、代码后台对照阅读的路径可以归纳为看模板NavigationView.xaml 中的ControlTemplate声明了全部命名部件按TopNavArea顶部区与RootSplitView左侧区两大块组织对状态组模板中的VisualStateGroupDisplayModeGroup、PaneStateGroup、PaneSeparatorStates等决定了各部件在不同模式/状态下的可见性与样式追代码NavigationView.cpp 的OnApplyTemplateL392-L709列出了所有被实际引用的部件名常量L33-L102凡是在这份名单里的部件才会被代码后台驱动文档中标注未被引用/Not referenced的部件则仅作为模板结构占位存在验行为UpdateAdaptiveLayout自适应宽度、UpdatePaneLayout菜单/页脚空间分配、NavigationViewItemsFactory::GetElementCore项容器化分别对应文档中按宽度隐藏 Pane、分隔线动态显隐、模板返回非 NavigationViewItem 时自动包装三条核心结论。若你要为 NavigationView 编写自定义模板最稳妥的做法是以本模板为基线保留所有被代码后台引用的x:Name尤其是上表与OnApplyTemplate常量表中出现的名称再针对性地修改样式与视觉状态删除或重命名被引用的部件会导致代码后台无法抓取到对应元素进而使返回按钮、Pane 开关、搜索按钮等交互失效。进一步的控件实现细节可参考 NavigationView.cpp 全文、NavigationView.idl 的 API 定义以及 NavigationView 目录 下的交互测试如 NavigationView_InteractionTests/PaneBehaviorTests.cs 与 TopModeTests.cs中针对各模式行为的验证用例。【免费下载链接】microsoft-ui-xamlWinUI: a modern UI framework with a rich set of controls and styles to build dynamic and high-performing Windows applications.项目地址: https://gitcode.com/GitHub_Trending/mi/microsoft-ui-xaml创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表