尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

WinUI 3 RadioMenuFlyoutItem 完整指南:从规格设计到源码实现与分组互斥原理

WinUI 3 RadioMenuFlyoutItem 完整指南:从规格设计到源码实现与分组互斥原理 WinUI 3 RadioMenuFlyoutItem 完整指南从规格设计到源码实现与分组互斥原理【免费下载链接】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-xamlRadioMenuFlyoutItem 是 WinUI 3microsoft-ui-xaml中一个基于 MenuFlyoutItem 扩展出来的单选型菜单项控件它以「单选按钮」的视觉和行为出现在 MenuFlyout、MenuBarItem 或 MenuFlyoutSubItem 中同组内互斥、一次只能选中一项。本文以仓库中的 RadioMenuFlyoutItem 规格文档 为骨架结合 控件实现源码 与 交互测试完整讲解其 XAML 用法、GroupName 分组机制、API 设计决策以及源码层的互斥实现原理帮助你直接在自己的 WinUI 3 应用中落地「单选菜单」交互。什么是 RadioMenuFlyoutItem在 WinUI 的传统菜单体系中MenuFlyoutItem 是一个普通菜单项点击即执行不保留状态ToggleMenuFlyoutItem 是一个可勾选菜单项可以勾选/取消勾选。而 RadioMenuFlyoutItem 是规格文档中定义的第三种形态一个 MenuFlyoutItem 的子类其显示与行为都像单选按钮radio button——可以被选中/取消选中且在同一分组内同一时刻只有一个被选中。它的典型应用场景是给用户一组互斥选项例如图标大小小/中/大、视图方向横向/纵向、排序字段名称/日期/大小等用户只能从每组中选择一个。它既可以作为普通菜单项出现在右键菜单 MenuFlyout 中也可以作为 MenuBar 的菜单栏项 MenuBarItem 的内容还可以嵌在级联子菜单 MenuFlyoutSubItem 中。从 API 命名空间看控件归属Microsoft.UI.Xaml.Controls命名空间即 muxc这与仓库 idl 声明文件 RadioMenuFlyoutItem.idl 中namespace MU_XC_NAMESPACE映射为 Microsoft.UI.Xaml.Controls一致使用前缀muxc:即可在 XAML 中引用。快速上手创建 RadioMenuFlyoutItem规格文档给出了第一个最小示例在级联菜单MenuFlyoutSubItem中放置三个单选菜单项其中 Medium icons 通过IsCheckedTrue预先选中MenuFlyout MenuFlyoutSubItem TextView muxc:RadioMenuFlyoutItem TextSmall icons/ muxc:RadioMenuFlyoutItem TextMedium icons IsCheckedTrue/ muxc:RadioMenuFlyoutItem TextLarge icons/ /MenuFlyoutSubItem /MenuFlyout运行效果即上图中所示三个菜单项共处一个「隐含分组」用户点击任意一项时该项被选中其余项自动取消选中左侧以黑色圆点勾选字形标记当前选中项。需要注意这个示例中三个条目没有指定 GroupName它们因此同属一个默认分组行为上仍然互斥。规格文档明确指出RadioMenuFlyoutItem 以「分组」为互斥单位。用 GroupName 创建多组单选集合当单个菜单中需要多组互相独立的单选选项时就必须为每组指定GroupName。规格文档给出了 MenuBar 场景下的完整示例——View 菜单栏项内同时存在「方向组」OrientationGroup和「大小组」SizeGroup两组互不干扰的单选选项muxc:MenuBar muxc:MenuBarItem TitleView MenuFlyoutItem TextOpen/ MenuFlyoutSeparator/ muxc:RadioMenuFlyoutItem TextLandscape GroupNameOrientationGroup/ muxc:RadioMenuFlyoutItem TextPortrait GroupNameOrientationGroup IsCheckedTrue/ MenuFlyoutSeparator/ muxc:RadioMenuFlyoutItem TextSmall icons GroupNameSizeGroup/ muxc:RadioMenuFlyoutItem TextMedium icons IsCheckedTrue GroupNameSizeGroup/ muxc:RadioMenuFlyoutItem TextLarge icons GroupNameSizeGroup/ /muxc:MenuBarItem /muxc:MenuBar对应效果见下图方向组内 Portrait 被选中大小组内 Medium icons 被选中两组各自独立互斥跨组不受影响。GroupName 使用要点分组键是字符串GroupName 是一个String类型属性见下文 API同名的 RadioMenuFlyoutItem 视为同一分组未指定 GroupName 的项属于同一默认分组默认组内同样互斥分组以当前已加载的菜单实例为界互斥查找表按组名在控件内维护详见源码原理一节不同菜单实例、不同 XAML 页面的同名分组互不影响分隔线MenuFlyoutSeparator只负责视觉分隔不切断分组逻辑分组只由 GroupName 决定。API 设计继承自 MenuFlyoutItem 而非 ToggleMenuFlyoutItem规格文档的 API Details 一节给出了控件的完整公共 APIidl 形式。仓库中的实际声明 RadioMenuFlyoutItem.idl 与规格文档完全对应并额外补充了后续版本加入的AreCheckStatesEnabled附加属性[MUX_PUBLIC] [webhosthidden] [MUX_PROPERTY_CHANGED_CALLBACK(TRUE)] unsealed runtimeclass RadioMenuFlyoutItem : Microsoft.UI.Xaml.Controls.MenuFlyoutItem { RadioMenuFlyoutItem(); Boolean IsChecked; String GroupName; static Microsoft.UI.Xaml.DependencyProperty IsCheckedProperty{ get; }; static Microsoft.UI.Xaml.DependencyProperty GroupNameProperty{ get; }; // WinUI 3 后续版本引入控制 MenuFlyoutSubItem 是否显示勾选状态 [MUX_PUBLIC_V2] { static Microsoft.UI.Xaml.DependencyProperty AreCheckStatesEnabledProperty{ get; }; static void SetAreCheckStatesEnabled(Microsoft.UI.Xaml.Controls.MenuFlyoutSubItem object, Boolean value); static Boolean GetAreCheckStatesEnabled(Microsoft.UI.Xaml.Controls.MenuFlyoutSubItem object); } }核心属性属性类型说明IsCheckedBoolean获取或设置当前项是否被选中默认值为falseGroupNameString获取或设置分组名指定哪些 RadioMenuFlyoutItem 互斥IsCheckedPropertyDependencyPropertyIsChecked的依赖属性标识符静态GroupNamePropertyDependencyPropertyGroupName的依赖属性标识符静态AreCheckStatesEnabled附加作用于 MenuFlyoutSubItemBoolean设为true后级联子菜单会跟随内部单选项的选中状态切换Checked/Unchecked视觉状态为什么继承 MenuFlyoutItem 而不是 ToggleMenuFlyoutItem规格文档 Appendix 记录了一个关键设计决策RadioMenuFlyoutItem 派生自 MenuFlyoutItem 而非 ToggleMenuFlyoutItem正如 RadioButton 派生自 ToggleButton 一样。不继承 ToggleMenuFlyoutItem 的原因是它会引入「三态」ThreeState概念及其 API而 RadioMenuFlyoutItem 不需要、也不应该支持三态。单选语义就是二态的要么选中、要么不选中且组内必须恰好保持一项被选中。三态Checked/Unchecked/Indeterminate属于复选CheckBox/ToggleButton的语义会污染单选控件的公共 API 面。因此规格设计选择了「公开继承 MenuFlyoutItem 私下复用 ToggleMenuFlyoutItem 的勾选能力」的实现路线详见下节。勾选状态的视觉呈现控件默认样式定义在主题资源 RadioMenuFlyoutItem_themeresources.xaml 中。默认样式DefaultRadioMenuFlyoutItemStyle直接以ToggleMenuFlyoutItem为模板目标模板由三列网格构成左侧CheckGlyph勾选字形 FontIcon字形码#xE915;、中间图标与文本、右侧键盘加速键文本。CheckStates视觉状态组通过 Storyboard 把 CheckGlyph 的Opacity从 0 切换到 1 来呈现选中圆点CheckedWithIcon状态则同时处理图标占位保证带 Icon 与不带 Icon 的菜单项文本对齐。文件中还定义了RadioMenuFlyoutSubItemStyle用于让级联子菜单整体显示勾选状态配合AreCheckStatesEnabled。源码剖析分组互斥是如何实现的控件行为实现在 RadioMenuFlyoutItem.cpp 与 RadioMenuFlyoutItem.h 中理解这两份文件就能明白同组互斥、不可取消选中这两条核心规则的底层逻辑。1. 秘密继承 ToggleMenuFlyoutItem 获取勾选能力头文件中的注释点明了实现技巧This type exists for RadioMenuFlyoutItem to derive publically from MenuFlyoutItem, but secretly from ToggleMenuFlyoutItem.即通过中间模板类DeriveFromToggleMenuFlyoutItemHelper_base继承ToggleMenuFlyoutItemT让控件对公众暴露为 MenuFlyoutItem 子类满足规格文档的 API 设计同时私下获得 ToggleMenuFlyoutItem 的 IsChecked 勾选行为。构造函数中注册了内部属性变更监听m_InternalIsCheckedChangedRevoker RegisterPropertyChanged( *this, winrt::ToggleMenuFlyoutItem::IsCheckedProperty(), { this, RadioMenuFlyoutItem::OnInternalIsCheckedChanged });这就是行为上像单选按钮的来源底层勾选状态的变化会回调到OnInternalIsCheckedChanged。2. thread_local 分组查找表互斥关系的核心数据结构是头文件中的static thread_local std::unique_ptrstd::mapwinrt::hstring, winrt::weak_refwinrt::RadioMenuFlyoutItem s_selectionMap;这是一个线程局部的映射表键为GroupNamewinrt::hstring值为指向当前选中项的弱引用weak_ref。使用弱引用是为了避免在 UI 线程的某条线程上长期持有控件强引用导致的内存泄漏/悬挂注释还记录了历史上曾用get_weak()但遭遇外层对象多一次 Release 的引用计数问题因此改为winrt::make_weak。用thread_local表明该控件模型本身要求 UI 线程访问与 WinUI 的线程亲和模型一致。3. 选中切换与不可取消选中约束属性变更处理函数OnPropertyChanged负责同步公共IsChecked与内部的InternalIsChecked而OnInternalIsCheckedChanged实现了两条硬规则用户不能手动取消选中若内部状态变为未选中!InternalIsChecked()且不是m_isSafeUncheck标志位标记的安全路径代码会把状态强制恢复为选中InternalIsChecked(true)注释原文为 The uncheck is due to user interaction -- not allowed.——这正是单选菜单项无法被点掉的语义只能因同组另一项被选中而取消当另一项被选中导致本项被取消时m_isSafeUncheck置位此时允许同步IsChecked(false)。4. UpdateCheckedItemInGroup组内互斥的落点核心互斥逻辑集中在UpdateCheckedItemInGroup()if (IsChecked()) { const auto groupName GroupName(); // 若该组已有选中项则先取消其选中状态 if (const auto previousCheckedItemWeak (*s_selectionMap)[groupName]) { if (auto previousCheckedItem previousCheckedItemWeak.get()) { if (previousCheckedItem ! static_castwinrt::RadioMenuFlyoutItem(*this)) { RadioMenuFlyoutItem* rawPreviousCheckedItem winrt::get_selfRadioMenuFlyoutItem(previousCheckedItem); rawPreviousCheckedItem-IsChecked(false); // 取消上一选中项 } } } // 用弱引用把当前项登记为该组的选中项 auto weakThis{ winrt::make_weak(static_castwinrt::RadioMenuFlyoutItem(*this)) }; (*s_selectionMap)[groupName] weakThis; }逻辑清晰先查表 → 若组内已有其他选中项则取消它 → 再把当前项登记进表。所有状态在OnLoaded元素加载后调用UpdateCheckedItemInGroup用于恢复上次的选中项与OnUnloaded若本项是选中项则从表中移除防止悬挂引用时同步维护。这样即使菜单反复打开关闭选中状态也能正确保持与清理。5. 级联子菜单的勾选状态AreCheckStatesEnabledOnAreCheckStatesEnabledPropertyChanged在子菜单每次Loaded时遍历其Items()检测是否存在已选中的 RadioMenuFlyoutItem并通过VisualStateManager::GoToState(subMenu, isAnyItemChecked ? LChecked : LUnchecked, false)让 MenuFlyoutSubItem 呈现Checked/Unchecked视觉状态对应的视觉模板就在上文提到的RadioMenuFlyoutSubItemStyle中。这让你可以在其他这类级联子菜单上直观提示当前组内已有选中项。测试佐证行为契约验证仓库在 InteractionTests/RadioMenuFlyoutItemTests.cs 中提供了集成测试来锁定控件行为契约BasicTest验证初始状态Orange、Compact、Name 三项各自选中、点击 YellowItem 后组内选中迁移到 Yellow点击 ExpandedItem 后大小组选中迁移到 Expanded专门验证you cant uncheck an item再次点击已选中的 YellowItem选中状态保持不变——直接对应源码中m_isSafeUncheck与user interaction -- not allowed的强制恢复逻辑SubMenuTest验证级联子菜单RadioSubMenu中的单选项ArtistNameItem与父菜单项处于同一 GroupName 分组时同样参与互斥。测试 UI 页面 TestUI/RadioMenuFlyoutItemPage.xaml 展示了更多真实用法同一 MenuFlyout 内两组单选颜色组 Size 组、带 Icon 的单选菜单项、带 KeyboardAcceleratorCtrlS与 AccessKey 的单选菜单项以及应用了RadioMenuFlyoutSubItemStyle的级联子菜单。这些页面可作为你在自己应用中排列组合单选菜单项的直接参考模板。在应用中使用 RadioMenuFlyoutItem综合以上内容实际使用时可遵循以下步骤引入命名空间在 XAML 根元素声明xmlns:muxcusing:Microsoft.UI.Xaml.ControlsWinUI 3 项目通常已在默认模板中声明选择容器根据交互位置将 RadioMenuFlyoutItem 放入 MenuFlyout右键菜单/按钮 Flyout、MenuBarItem菜单栏或 MenuFlyoutSubItem级联菜单确定分组单组场景可不写 GroupName多组并存时务必为每组设置唯一的 GroupName 字符串设置初始选中为默认选项设置IsCheckedTrue默认值为 false可选增强需要图标时设置Icon需要键盘快捷键时添加 KeyboardAccelerator 或 AccessKey需要级联子菜单显示勾选状态时为子菜单应用 RadioMenuFlyoutSubItemStyle 并设置AreCheckStatesEnabled读取结果在代码中通过IsChecked属性判断当前选中项结合GroupName区分不同组。小结RadioMenuFlyoutItem 用极小的 API 面IsCheckedGroupName为 WinUI 3 菜单体系补齐了单选交互通过 规格文档 定义的公开继承 MenuFlyoutItem、秘密复用 ToggleMenuFlyoutItem 的设计配合 线程局部弱引用查找表 实现同组互斥并由 交互测试 锁定了组内唯一选中、不可手动取消的行为契约。无论你是要在菜单栏实现多组视图选项还是在右键菜单里做排序/显示偏好切换它都是开箱即用的标准控件。【免费下载链接】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),仅供参考
返回列表