Windows-universal-samples 仓库 RadialController 示例深度解析:为 Surface Dial 打造自定义径向菜单
示例工程【免费下载链接】Windows-universal-samplesAPI samples for the Universal Windows Platform.项目地址https://gitcode.com/gh_mirrors/wi/Windows-universal-samples点击查看免费下载导读本文围绕 Windows-universal-samples 仓库中的 RadialController 示例 展开讲解如何通过 UWP 的Windows.UI.Input.RadialController系列 API为 Surface Dial 等径向控制器设备创建自定义菜单项、控制触觉反馈haptic feedback、配置系统默认菜单项以及抑制系统默认菜单将输入直接导向应用。读完本文你将掌握径向控制器从控制器获取、事件挂接、菜单项四类图标来源系统图标、应用位图、系统字体字形、应用字体字形到系统默认菜单增删改的完整实战方案。RadialController 示例整体概览该示例位于仓库的 Samples/RadialController 目录同时提供了C#cs、C/CXcpp两种实现UI 布局由shared目录下的 XAML 共享。示例通过SampleConfiguration.cs注册了两个核心场景Event And Menu Hookup场景一演示径向控制器的事件挂接与自定义菜单项的增、删、选图标覆盖系统提供图标、应用位图、系统字体字形与应用字体字形四类Modifying System Default Menu场景二演示对系统默认菜单项的配置包括将默认项替换为仅音量与滚动、单独选中音量、清空默认项以及恢复出厂默认。示例面向的 API 全部位于Windows.UI.Input命名空间核心类型有四个RadialController控制器本体、RadialControllerMenu菜单容器、RadialControllerMenuItem单个菜单项与RadialControllerConfiguration系统配置。场景一控制器获取、事件挂接与自定义菜单项1. 获取控制器并设置旋转分辨率径向控制器的入口是静态方法RadialController.CreateForCurrentView()它返回与当前视图绑定的控制器实例。场景一在页面构造函数中依次完成初始化与菜单创建代码见 Scenario1_EventAndMenuHookup.xaml.cs。private void InitializeController() { Controller RadialController.CreateForCurrentView(); Controller.RotationResolutionInDegrees 1; // Wire events Controller.RotationChanged Controller_RotationChanged; Controller.ButtonClicked Controller_ButtonClicked; Controller.ScreenContactStarted Controller_ScreenContactStarted; Controller.ScreenContactContinued Controller_ScreenContactContinued; Controller.ScreenContactEnded Controller_ScreenContactEnded; Controller.ControlAcquired Controller_ControlAcquired; Controller.ControlLost Controller_ControlLost; }其中RotationResolutionInDegrees控制旋转事件触发的最小角度增量示例设为1度即每旋转 1 度触发一次RotationChanged。C/CX 版本完全对应见 Scenario1_EventAndMenuHookup.xaml.cpp。2. 径向控制器的六大事件示例挂接的六个事件覆盖了径向控制器的完整输入模型事件触发时机事件参数中携带的数据RotationChanged用户旋转拨盘RotationDeltaInDegrees本次旋转的角度增量与ContactButtonClicked用户按下拨盘Contact屏幕触点信息ScreenContactStarted手指开始接触屏幕Contact.Bounds、Contact.PositionScreenContactContinued手指在屏幕上持续接触并移动Contact.Bounds、Contact.PositionScreenContactEnded手指离开屏幕无ControlAcquired应用取得控制器控制权ContactControlLost应用失去控制器控制权无事件处理函数中示例将旋转增量累加到当前活动菜单项对应的Slider.Value上见 Scenario1_EventAndMenuHookup.xaml.csprivate void Controller_RotationChanged(RadialController sender, RadialControllerRotationChangedEventArgs args) { log.Text \n Rotation Changed Delta args.RotationDeltaInDegrees; LogContactInfo(args.Contact); sliders[activeItemIndex].Value args.RotationDeltaInDegrees; }而ButtonClicked则翻转当前活动项对应ToggleSwitch的开关状态演示了点按拨盘 触发当前项动作的交互模型。LogContactInfo将屏幕触点的Bounds与Position打印到日志区用于观察手指接触屏幕时的位置变化ScreenContactStarted 等事件的 LogContactInfo 调用。3. 四类菜单项图标来源CreateMenuItems依次调用三个私有方法创建 6 个菜单项代码位置随后为每个菜单项注册Invoked回调通过闭包捕获索引实现哪个项被激活的追踪for (int i 0; i menuItems.Count; i) { RadialControllerMenuItem radialControllerItem menuItems[i]; int index i; radialControllerItem.Invoked (sender, args) { OnItemInvoked(index); }; }菜单项图标共分四类对应四个工厂方法1系统已知图标Known Icon——CreateFromKnownIcon(string displayText, RadialControllerMenuKnownIcon icon)menuItems.Add(RadialControllerMenuItem.CreateFromKnownIcon(Item0, RadialControllerMenuKnownIcon.InkColor)); menuItems.Add(RadialControllerMenuItem.CreateFromKnownIcon(Item1, RadialControllerMenuKnownIcon.NextPreviousTrack));RadialControllerMenuKnownIcon枚举由系统提供如InkColor墨迹颜色、NextPreviousTrack上一首/下一首等无需额外资源文件。2应用位图Custom Icon——CreateFromIcon(string displayText, RandomAccessStreamReference icon)图标来自应用包内的 PNG 位图通过ms-appx:///Assets/Item2.png这类 URI 构造流引用menuItems.Add(RadialControllerMenuItem.CreateFromIcon(Item2, RandomAccessStreamReference.CreateFromUri(new Uri(ms-appx:///Assets/Item2.png)))); menuItems.Add(RadialControllerMenuItem.CreateFromIcon(Item3, RandomAccessStreamReference.CreateFromUri(new Uri(ms-appx:///Assets/Item3.png))));位图资源位于 shared/Item0.png 至 Item3.pngC 版本使用相同的ms-appx:///Assets/...URI 模式AddCustomIconItems。3系统字体字形Font Glyph——CreateFromFontGlyph(string displayText, string glyph, string fontFamily)本例使用Segoe UI Emoji字体渲染心形字符\x2764menuItems.Add(RadialControllerMenuItem.CreateFromFontGlyph(Item4, \x2764, Segoe UI Emoji));4应用字体字形——CreateFromFontGlyph增加第四个重载参数指定字体文件 URI本例使用Symbols字体文件ms-appx:///Assets/Symbols.ttf中的字形\ue102menuItems.Add(RadialControllerMenuItem.CreateFromFontGlyph(Item5, \ue102, Symbols, new Uri(ms-appx:///Assets/Symbols.ttf)));对应的 XAML 预览区域Scenario1_EventAndMenuHookup.xaml用Segoe UI Emoji字体渲染心形、用Assets\fontawesome-webfont.ttf#FontAwesome渲染#xf0a1;与代码中的字形一一对应方便开发者在界面上直接预览每个菜单项图标。4. 菜单项的动态增、删、选场景一的 XAML 为每个菜单项提供 Add / Select / Remove 三个按钮通过CommandParameter绑定项索引见 XAML 中 Item 0 的按钮组代码侧的核心逻辑如下完整实现private void AddItem(object sender, RoutedEventArgs e) { RadialControllerMenuItem item GetRadialControllerMenuItemFromSender(sender); if (!Controller.Menu.Items.Contains(item)) { Controller.Menu.Items.Add(item); log.Text \n Added : item.DisplayText; } } private void RemoveItem(object sender, RoutedEventArgs e) { RadialControllerMenuItem item GetRadialControllerMenuItemFromSender(sender); if (Controller.Menu.Items.Contains(item)) { Controller.Menu.Items.Remove(item); log.Text \n Removed : item.DisplayText; } } private void SelectItem(object sender, RoutedEventArgs e) { RadialControllerMenuItem item GetRadialControllerMenuItemFromSender(sender); if (Controller.Menu.Items.Contains(item)) { Controller.Menu.SelectMenuItem(item); PrintSelectedItem(); } }值得注意的三个要点增删前的存在性检查通过Menu.Items.Contains判断项是否已在菜单中避免重复添加或删除不存在的项导致异常C 版本则用Items-IndexOf实现相同的幂等语义IsItemInMenu / GetItemIndex。编程式选中Menu.SelectMenuItem(item)可直接将指定项设为当前激活项无需用户物理操作拨盘。回到上一选中项Menu.TrySelectPreviouslySelectedMenuItem()会尝试重新选中上一次激活的菜单项返回bool表示是否成功。GetSelectedMenuItem()用于查询当前选中项示例代码里通过它区分自定义项被选中与系统项被选中当返回null时打印System Item这正是自定义菜单与系统默认菜单共存时常见的判断手法PrintSelectedItem。另外页面在OnNavigatedFrom中调用Controller.Menu.Items.Clear()清空菜单避免页面切换后菜单残留影响其他场景这是多场景示例中必要的状态清理。5. 触觉反馈开关触觉反馈haptic feedback即拨盘旋转/点击时设备产生的震动反馈。场景一在 XAML 中通过数据绑定直接控制XAML 的 Haptics 开关ToggleSwitch IsOn{x:Bind Controller.UseAutomaticHapticFeedback, ModeTwoWay}Haptics/ToggleSwitchController.UseAutomaticHapticFeedback为布尔属性置true时系统在用户操作拨盘时自动触发震动反馈置false则关闭。由于使用ModeTwoWay双向绑定开关切换会实时写回控制器属性。6. 抑制系统默认菜单默认情况下用户按下 Surface Dial 时会弹出系统菜单音量、滚动等。若应用希望完全接管拨盘输入可将菜单抑制并把输入直接导向指定控制器。场景一的实现ToggleMenuSuppressionprivate void ToggleMenuSuppression(Object sender, RoutedEventArgs args) { RadialControllerConfiguration radialControllerConfig RadialControllerConfiguration.GetForCurrentView(); if (MenuSuppressionToggleSwitch.IsOn) { radialControllerConfig.ActiveControllerWhenMenuIsSuppressed Controller; } radialControllerConfig.IsMenuSuppressed MenuSuppressionToggleSwitch.IsOn; }两个关键属性均来自RadialControllerConfigurationIsMenuSuppressed置true时系统不再弹出默认菜单ActiveControllerWhenMenuIsSuppressed菜单被抑制时将输入导向哪个控制器——示例直接指定当前视图的控制器保证抑制菜单后旋转与点击事件仍能送达应用。场景二系统默认菜单项的配置场景二Scenario2_SystemDefaultMenu.xaml.cs专注于RadialControllerConfiguration对系统默认菜单的操作。页面初始化时同样创建控制器并挂接RotationChanged同时用CreateFromKnownIcon预置一个名为 Item1、图标为InkColor的自定义项用于演示自定义项与系统项的共存与互选。1. 将默认菜单替换为仅音量与滚动private void ModifySystemDefaults(object sender, RoutedEventArgs e) { config RadialControllerConfiguration.GetForCurrentView(); config.SetDefaultMenuItems(new[] { RadialControllerSystemMenuItemKind.Volume, RadialControllerSystemMenuItemKind.Scroll }); }SetDefaultMenuItems接收RadialControllerSystemMenuItemKind枚举数组完整枚举包含Volume音量、Scroll滚动、UndoRedo撤销/重做等系统项。传入的数组即替换后仅保留的默认项集合示例效果是系统菜单只保留音量与滚动两项。2. 单独选中某个系统默认项private void Select_Volume(object sender, RoutedEventArgs e) { config RadialControllerConfiguration.GetForCurrentView(); config.TrySelectDefaultMenuItem(RadialControllerSystemMenuItemKind.Volume); }TrySelectDefaultMenuItem以编程方式选中指定的系统默认项返回bool表示是否成功例如该项不在当前默认集合中时会失败。3. 清空与恢复系统默认项private void Remove_Defaults(object sender, RoutedEventArgs e) { config RadialControllerConfiguration.GetForCurrentView(); config.SetDefaultMenuItems(new RadialControllerSystemMenuItemKind[] { }); } private void Reset_ToDefault(object sender, RoutedEventArgs e) { config RadialControllerConfiguration.GetForCurrentView(); config.ResetToDefaultMenuItems(); }传空数组给SetDefaultMenuItems表示移除全部系统默认项ResetToDefaultMenuItems()恢复系统出厂默认的菜单项集合是一键还原的便捷入口。4. 自定义项与系统项的选中追踪场景二同样提供自定义项 Add / Select / Remove 与TrySelectPreviouslySelectedMenuItem按钮。PrintSelectedItem中通过GetSelectedMenuItem()与预置的customItem比较相同则打印自定义项名称否则打印Selected System Item代码位置。这说明当系统默认项被选中时Menu.GetSelectedMenuItem()返回的不是自定义项对象开发者需要以此区分两种选中状态。构建与运行系统要求客户端Windows 10示例清单 Package.appxmanifest 中TargetDeviceFamily的MinVersion为10.0.14393.0即周年更新版及以上MaxVersionTested为10.0.22621.0需要Visual Studio与Windows 10 及以上操作系统运行验证需要真实的 Surface Dial 设备或支持 RadialController 的兼容硬件。构建步骤如果以 ZIP 方式下载示例集请解压整个压缩包而非仅解压单个示例目录因为示例依赖仓库根目录的SharedContent共享内容README.md的 front-matter 中extendedZipContent也声明了SharedContent与LICENSE的展开规则启动 Visual Studio选择File Open Project/Solution进入解压目录下的Samples\RadialController子目录再进入语言子目录cs或cpp双击解决方案文件.sln即 cs/RadialController.sln 或 cpp/RadialController.sln按CtrlShiftB或Build Build Solution编译。运行与调试仅部署选择Build Deploy Solution部署并调试运行按F5或选择Debug Start Debugging部署并运行不调试按CtrlF5或选择Debug Start Without Debugging。运行后场景一可实际操作拨盘观察Rotation Changed Delta累加到滑块、点按拨盘翻转开关切换 Suppress Default Menu 开关可验证菜单抑制场景二可分别点击按钮观察系统默认菜单项集合的变化。相关主题与参考开发者与 UX 设计指南Surface Dial 交互设计Windows 轮式交互指南涉及菜单项数量上限、图标可读性、触觉反馈节奏等设计约束API 参考均属于Windows.UI.Input命名空间RadialController——控制器本体负责事件、旋转分辨率与触觉反馈RadialControllerMenu——菜单容器负责项的增删与选中RadialControllerMenuItem——单个菜单项负责图标来源与Invoked回调RadialControllerConfiguration——系统级配置负责默认菜单项与菜单抑制桌面端对应实现经典桌面应用C 与 WinForms也有对应的 RadialController 示例可对照阅读本仓库其他输入类示例如需进一步了解 Windows 输入体系可参考仓库中的 BasicInput 等相邻示例。小结通过 Windows-universal-samples 的 RadialController 示例可以完整掌握 UWP 径向控制器的开发链路CreateForCurrentView()获取控制器 → 挂接旋转/点击/触点/控制权六大事件 → 用系统图标、位图、字体字形四种方式创建菜单项 → 动态增删选并追踪选中状态 → 用UseAutomaticHapticFeedback控制触觉反馈 → 用RadialControllerConfiguration管理系统默认菜单与菜单抑制。这套 API 模型不仅适用于 Surface Dial也适用于任何支持 RadialController 的 Windows 输入设备是构建硬件旋钮 软件菜单交互体验的标准范式。赞分享示例工程【免费下载链接】Windows-universal-samplesAPI samples for the Universal Windows Platform.项目地址https://gitcode.com/gh_mirrors/wi/Windows-universal-samples点击查看免费下载相关推荐Windows-universal-samples 的 ContextMenu 示例深度解析基于 PopupMenu 的上下文菜单创建与文本默认菜单替换Windows universal samples 的 ContextMenu 示例深度解析基于 PopupMenu 的上下文菜单创建与文本默认菜单替换 本指示例工程Windows-universal-samples ComplexInk 示例深度解析InkPresenter 自定义干燥、墨迹选择与复制粘贴实战Windows universal samples ComplexInk 示例深度解析InkPresenter 自定义干燥、墨迹选择与复制粘贴实战 Compl示例工程5分钟快速释放100GB硬盘空间终极游戏清理神器SteamCleaner完整指南5分钟快速释放100GB硬盘空间终极游戏清理神器SteamCleaner完整指南 您是否曾发现卸载了多个大型游戏后硬盘空间却没有明显增加这很可能是因为St示例工程上一篇Unity游戏翻译插件终极指南3个步骤实现多语言自动翻译下一篇Firefox for iOS 贡献指南从 Issue 认领、SwiftLint 门禁到 Danger 自动化评审的完整实战创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考