Surface Dial与Surface Studio和Surface Pen。
本教程逐步讲解如何自定义轮设备(如Surface Dial)支持的用户交互体验。 我们使用示例应用中的代码片段(请参阅 GitHub Sample 代码),演示每个步骤中讨论的各种功能和关联的 RadialController API。
我们专注于以下事项:
- 指定 在 RadialController 菜单上显示哪些内置工具
- 向菜单添加自定义工具
- 控制触觉反馈
- 自定义单击交互
- 自定义旋转交互
有关实现这些和其他功能的详细信息,请参阅Windows 应用中的 Surface Dial 交互。
介绍
Surface Dial是辅助输入设备,可帮助用户在与主要输入设备(如笔、触摸或鼠标)一起使用时提高工作效率。 作为辅助输入设备,Dial 通常与非主导手一起使用,以提供对系统命令和其他更上下文、工具和功能的访问权限。
Dial 支持三种基本手势:
- 按住以显示包含命令的内置菜单。
- 旋转以突出显示菜单项(如果菜单处于活动状态),或者修改应用中的当前作(如果菜单未处于活动状态)。
- 单击此项可选择突出显示的菜单项(如果菜单处于活动状态),或调用应用中的命令(如果菜单未处于活动状态)。
先决条件
- 运行 Windows 10 Creators Update 或更高版本的计算机(或虚拟机)
- 设置 Visual Studio 以进行 UWP 开发。
- 滚轮设备(目前仅Surface Dial)
设置你的设备
- 确保Windows设备已打开。
- 转到 “开始”,选择 “设置>设备>蓝牙”和其他设备,然后打开 蓝牙 。
- 取下 Surface Dial 底部以打开电池舱,并确保里面有两节 AAA 电池。
- 如果在旋钮的下方有电池标签,请将其移除。
- 按住电池旁边的小型内插按钮,直到蓝牙灯闪烁。
- 返回到Windows设备,然后选择Add Bluetooth 或其他设备。
- 在“添加设备对话框中,选择Bluetooth>Surface Dial。 现在,您的 Surface Dial 应该连接并添加到 鼠标、键盘和笔下的设备列表中,在 蓝牙和其他设备 设置页面中。
- 按住拨盘几秒钟以测试其功能,并显示内置菜单。
- 如果屏幕上未显示菜单(拨号还应振动),请返回到蓝牙设置,删除设备,然后再次尝试连接设备。
注释
滚轮设备可以通过 Wheel 设置进行配置:
- 在 “开始 ”菜单上,选择 “设置”。
- 选择 “设备>滚轮”。
现在,你已准备好开始本教程。
代码示例
在本教程中,我们使用示例应用来演示所讨论的概念和功能。
从 GitHub 上的 windows-appsample-get-started-radialcontroller 示例 下载此 Visual Studio 示例和源代码。
- 选择绿色的 “克隆或下载” 按钮。
- 如果你有一个 GitHub 帐户,可以选择“在 Visual Studio 中打开”来将存储库克隆到本地计算机。
- 如果没有GitHub帐户,或者只需要项目的本地副本,请选择 Download ZIP(必须定期查看以下载最新更新)。
重要
示例中的大部分代码都会注释掉。当我们完成本主题中的每个步骤时,系统会要求你取消注释代码的各个部分。 在Visual Studio中,只需突出显示代码行,然后按 CTRL-K,然后按 Ctrl-U。
支持滚轮功能的组件
这些对象为Windows应用提供大部分滚轮设备体验。
| 组件 | 说明 |
|---|---|
| RadialController 类和相关内容 | 表示滚轮输入设备或配件,例如Surface Dial。 |
|
IRadialControllerConfigurationInterop / IRadialControllerInterop 此处未介绍此功能,有关详细信息,请参阅 Windows 桌面示例。 |
启用与Windows应用程序的互操作性。 |
步骤 1:运行示例
下载 RadialController 示例应用后,请验证它是否运行:
- 在 Visual Studio 中打开示例项目。
- 将 “解决方案平台” 下拉列表设置为非 Arm 选择。
- 按 F5 编译、部署和运行。
注释
或者,可以选择 Debug>启动调试菜单项, 或选择此处显示的Local Machine运行按钮:
应用窗口打开后,经过几秒钟的启动画面,你将看到这个初始屏幕。
好吧,我们现在有基本的Windows应用,我们将在整个本教程的其余部分使用。 在以下步骤中,我们添加了 RadialController 功能。
步骤 2:RadialController 的基本功能
当应用在前台运行时,按住Surface Dial以显示 RadialController 菜单。
我们尚未为应用进行任何自定义,因此菜单包含一组默认的上下文工具。
这些图像显示了默认菜单的两种变体。 (还有其他许多工具,包括在Windows桌面处于活动状态且没有应用程序位于前台时显示的基本系统工具,在存在墨迹工具栏时的附加墨迹工具,以及使用地图应用时的映射工具。
| RadialController 菜单 (默认值) | RadialController 菜单(默认状态为媒体播放) |
|---|---|
|
|
现在,我们将从一些基本自定义开始。
步骤 3:为滚轮输入添加控件
首先,让我们为应用添加 UI:
打开 MainPage_Basic.xaml 文件。
查找标记为此步骤标题的代码(“<--步骤 3:为滚轮输入添加控件 -->”
取消注释以下行。
<Button x:Name="InitializeSampleButton" HorizontalAlignment="Center" Margin="10" Content="Initialize sample" /> <ToggleButton x:Name="AddRemoveToggleButton" HorizontalAlignment="Center" Margin="10" Content="Remove Item" IsChecked="True" IsEnabled="False"/> <Button x:Name="ResetControllerButton" HorizontalAlignment="Center" Margin="10" Content="Reset RadialController menu" IsEnabled="False"/> <Slider x:Name="RotationSlider" Minimum="0" Maximum="10" Width="300" HorizontalAlignment="Center"/> <TextBlock Text="{Binding ElementName=RotationSlider, Mode=OneWay, Path=Value}" Margin="0,0,0,20" HorizontalAlignment="Center"/> <ToggleSwitch x:Name="ClickToggle" MinWidth="0" Margin="0,0,0,20" HorizontalAlignment="center"/>
此时,仅启用 “初始化示例 ”按钮、滑块和切换开关。 其他按钮用于在后续步骤中添加和删除 RadialController 菜单项,这些菜单项提供滑块和切换开关的访问权限。
步骤 4:自定义基本 RadialController 菜单
现在,让我们添加启用 RadialController 访问控件所需的代码。
- 打开MainPage_Basic.xaml.cs文件。
- 查找以此步骤标题标记的代码(“//步骤 4:基本自定义 RadialController 菜单”)。
- 取消注释以下行:
Windows.UI.Input 和 Windows.Storage.Streams 类型引用用于后续步骤中的功能:
// Using directives for RadialController functionality. using Windows.UI.Input;这些全局对象(RadialController、 RadialControllerConfiguration、 RadialControllerMenuItem)在整个应用中使用。
private RadialController radialController; private RadialControllerConfiguration radialControllerConfig; private RadialControllerMenuItem radialControllerMenuItem;在这里,我们为按钮指定了 Click 处理程序,以启用控件并初始化自定义 RadialController 菜单项。
InitializeSampleButton.Click += (sender, args) => { InitializeSample(sender, args); };接下来,初始化 RadialController 对象,并为 RotationChanged 和 ButtonClicked 事件设置处理程序。
// Set up the app UI and RadialController. private void InitializeSample(object sender, RoutedEventArgs e) { ResetControllerButton.IsEnabled = true; AddRemoveToggleButton.IsEnabled = true; ResetControllerButton.Click += (resetsender, args) => { ResetController(resetsender, args); }; AddRemoveToggleButton.Click += (togglesender, args) => { AddRemoveItem(togglesender, args); }; InitializeController(sender, e); }在这里,我们初始化自定义 RadialController 菜单项。 我们使用 CreateForCurrentView 获取对 RadialController 对象的引用, 我们使用 RotationResolutionInDegrees 属性将旋转敏感度设置为“1”,然后使用 CreateFromFontGlyph 创建 RadialControllerMenuItem,我们将菜单项添加到 RadialController 菜单项集合,最后,我们使用 SetDefaultMenuItems 清除默认菜单项,只保留自定义工具。
// Configure RadialController menu and custom tool. private void InitializeController(object sender, RoutedEventArgs args) { // Create a reference to the RadialController. radialController = RadialController.CreateForCurrentView(); // Set rotation resolution to 1 degree of sensitivity. radialController.RotationResolutionInDegrees = 1; // Create the custom menu items. // Here, we use a font glyph for our custom tool. radialControllerMenuItem = RadialControllerMenuItem.CreateFromFontGlyph("SampleTool", "\xE1E3", "Segoe MDL2 Assets"); // Add the item to the RadialController menu. radialController.Menu.Items.Add(radialControllerMenuItem); // Remove built-in tools to declutter the menu. // NOTE: The Surface Dial menu must have at least one menu item. // If all built-in tools are removed before you add a custom // tool, the default tools are restored and your tool is appended // to the default collection. radialControllerConfig = RadialControllerConfiguration.GetForCurrentView(); radialControllerConfig.SetDefaultMenuItems( new RadialControllerSystemMenuItemKind[] { }); // Declare input handlers for the RadialController. // NOTE: These events are only fired when a custom tool is active. radialController.ButtonClicked += (clicksender, clickargs) => { RadialController_ButtonClicked(clicksender, clickargs); }; radialController.RotationChanged += (rotationsender, rotationargs) => { RadialController_RotationChanged(rotationsender, rotationargs); }; } // Connect wheel device rotation to slider control. private void RadialController_RotationChanged( object sender, RadialControllerRotationChangedEventArgs args) { if (RotationSlider.Value + args.RotationDeltaInDegrees >= RotationSlider.Maximum) { RotationSlider.Value = RotationSlider.Maximum; } else if (RotationSlider.Value + args.RotationDeltaInDegrees < RotationSlider.Minimum) { RotationSlider.Value = RotationSlider.Minimum; } else { RotationSlider.Value += args.RotationDeltaInDegrees; } } // Connect wheel device click to toggle switch control. private void RadialController_ButtonClicked( object sender, RadialControllerButtonClickedEventArgs args) { ClickToggle.IsOn = !ClickToggle.IsOn; }
- 现在,再次运行应用。
- 选择 “初始化径向控制器 ”按钮。
- 在应用处于前台时,按住 Surface Dial 以显示菜单。 请注意,所有默认工具都已删除(使用 RadialControllerConfiguration.SetDefaultMenuItems 方法),只保留自定义工具。 下面是带有自定义工具的菜单。
| RadialController 菜单 (自定义) |
|---|
|
- 选择自定义工具并试用现在通过Surface Dial支持的交互:
- 通过旋转来移动滑块。
- 单击将开关设置为打开或关闭。
好,让我们连接这些按钮。
步骤 5:在运行时配置菜单
在此步骤中,我们将“添加/删除项”和“重置 RadialController 菜单”的按钮连接起来,以显示如何动态自定义菜单。
打开MainPage_Basic.xaml.cs文件。
查找标记为此步骤标题的代码(“//步骤 5:在运行时配置菜单”)。
取消注释以下方法中的代码后再次运行应用程序,但先不要选择任何按钮(这一操作留在下一步)。
// Add or remove the custom tool. private void AddRemoveItem(object sender, RoutedEventArgs args) { if (AddRemoveToggleButton?.IsChecked == true) { AddRemoveToggleButton.Content = "Remove item"; if (!radialController.Menu.Items.Contains(radialControllerMenuItem)) { radialController.Menu.Items.Add(radialControllerMenuItem); } } else if (AddRemoveToggleButton?.IsChecked == false) { AddRemoveToggleButton.Content = "Add item"; if (radialController.Menu.Items.Contains(radialControllerMenuItem)) { radialController.Menu.Items.Remove(radialControllerMenuItem); // Attempts to select and activate the previously selected tool. // NOTE: Does not differentiate between built-in and custom tools. radialController.Menu.TrySelectPreviouslySelectedMenuItem(); } } } // Reset the RadialController to initial state. private void ResetController(object sender, RoutedEventArgs arg) { if (!radialController.Menu.Items.Contains(radialControllerMenuItem)) { radialController.Menu.Items.Add(radialControllerMenuItem); } AddRemoveToggleButton.Content = "Remove item"; AddRemoveToggleButton.IsChecked = true; radialControllerConfig.SetDefaultMenuItems( new RadialControllerSystemMenuItemKind[] { }); }选择 “删除项 ”按钮,然后按并按住 Dial 再次显示菜单。
请注意,菜单现在包含工具的默认集合。 回想一下,在步骤 3 中设置自定义菜单时,我们删除了所有默认工具,并仅添加了自定义工具。 我们还指出,当菜单设置为空集合时,将恢复当前上下文的默认项。 (我们在删除默认工具之前添加了自定义工具。
选择 “添加项目 ”按钮,然后按并按住 Dial。
请注意,菜单现在包含默认的工具集合和自定义工具。
选择 “重置 RadialController”菜单 按钮,然后按并按住 Dial。
请注意,菜单返回到其原始状态。
步骤 6:自定义设备触觉
Surface Dial和其他滚轮设备可以为用户提供与当前交互对应的触觉反馈(基于单击或旋转)。
在此步骤中,我们将展示如何自定义触觉反馈行为:通过关联滑块和切换开关控件,以及使用这些控件来动态指定触觉反馈的表现方式。 对于本示例,切换开关必须设置为启用,以便启用反馈,而滑块值指定单击反馈的重复频率。
注释
用户可以在“设置>>”页中禁用触觉反馈。
打开App.xaml.cs文件。
查找标记为此步骤标题的代码(“步骤 6:自定义设备触觉”)。
注释第一行和第三行(“MainPage_Basic”和“MainPage”),并取消注释第二行(“MainPage_Haptics”)。
rootFrame.Navigate(typeof(MainPage_Basic), e.Arguments); rootFrame.Navigate(typeof(MainPage_Haptics), e.Arguments); rootFrame.Navigate(typeof(MainPage), e.Arguments);打开 MainPage_Haptics.xaml 文件。
查找标记为此步骤标题的代码(“<--步骤 6:自定义设备触觉 -->”
取消注释以下行。 (此 UI 代码只是指示当前设备支持哪些触觉功能。
<StackPanel x:Name="HapticsStack" Orientation="Vertical" HorizontalAlignment="Center" BorderBrush="Gray" BorderThickness="1"> <TextBlock Padding="10" Text="Supported haptics properties:" /> <CheckBox x:Name="CBDefault" Content="Default" Padding="10" IsEnabled="False" IsChecked="True" /> <CheckBox x:Name="CBIntensity" Content="Intensity" Padding="10" IsEnabled="False" IsThreeState="True" IsChecked="{x:Null}" /> <CheckBox x:Name="CBPlayCount" Content="Play count" Padding="10" IsEnabled="False" IsThreeState="True" IsChecked="{x:Null}" /> <CheckBox x:Name="CBPlayDuration" Content="Play duration" Padding="10" IsEnabled="False" IsThreeState="True" IsChecked="{x:Null}" /> <CheckBox x:Name="CBReplayPauseInterval" Content="Replay/pause interval" Padding="10" IsEnabled="False" IsThreeState="True" IsChecked="{x:Null}" /> <CheckBox x:Name="CBBuzzContinuous" Content="Buzz continuous" Padding="10" IsEnabled="False" IsThreeState="True" IsChecked="{x:Null}" /> <CheckBox x:Name="CBClick" Content="Click" Padding="10" IsEnabled="False" IsThreeState="True" IsChecked="{x:Null}" /> <CheckBox x:Name="CBPress" Content="Press" Padding="10" IsEnabled="False" IsThreeState="True" IsChecked="{x:Null}" /> <CheckBox x:Name="CBRelease" Content="Release" Padding="10" IsEnabled="False" IsThreeState="True" IsChecked="{x:Null}" /> <CheckBox x:Name="CBRumbleContinuous" Content="Rumble continuous" Padding="10" IsEnabled="False" IsThreeState="True" IsChecked="{x:Null}" /> </StackPanel>打开MainPage_Haptics.xaml.cs文件
查找标有此步骤标题的代码(“步骤 6: 触觉自定义”)
取消注释以下行:
Windows.Devices.Haptics 类型引用用于实现后续步骤中的功能。
using Windows.Devices.Haptics;在这里,我们指定在选择自定义 RadialController 菜单项时触发的 ControlAcquired 事件的处理程序。
radialController.ControlAcquired += (rc_sender, args) => { RadialController_ControlAcquired(rc_sender, args); };接下来,定义 ControlAcquired 处理程序,其中禁用默认触觉反馈并初始化触觉 UI。
private void RadialController_ControlAcquired( RadialController rc_sender, RadialControllerControlAcquiredEventArgs args) { // Turn off default haptic feedback. radialController.UseAutomaticHapticFeedback = false; SimpleHapticsController hapticsController = args.SimpleHapticsController; // Enumerate haptic support. IReadOnlyCollection<SimpleHapticsControllerFeedback> supportedFeedback = hapticsController.SupportedFeedback; foreach (SimpleHapticsControllerFeedback feedback in supportedFeedback) { if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.BuzzContinuous) { CBBuzzContinuous.IsEnabled = true; CBBuzzContinuous.IsChecked = true; } else if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Click) { CBClick.IsEnabled = true; CBClick.IsChecked = true; } else if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Press) { CBPress.IsEnabled = true; CBPress.IsChecked = true; } else if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.Release) { CBRelease.IsEnabled = true; CBRelease.IsChecked = true; } else if (feedback.Waveform == KnownSimpleHapticsControllerWaveforms.RumbleContinuous) { CBRumbleContinuous.IsEnabled = true; CBRumbleContinuous.IsChecked = true; } } if (hapticsController?.IsIntensitySupported == true) { CBIntensity.IsEnabled = true; CBIntensity.IsChecked = true; } if (hapticsController?.IsPlayCountSupported == true) { CBPlayCount.IsEnabled = true; CBPlayCount.IsChecked = true; } if (hapticsController?.IsPlayDurationSupported == true) { CBPlayDuration.IsEnabled = true; CBPlayDuration.IsChecked = true; } if (hapticsController?.IsReplayPauseIntervalSupported == true) { CBReplayPauseInterval.IsEnabled = true; CBReplayPauseInterval.IsChecked = true; } }在我们的 RotationChanged 和 ButtonClicked 事件监听器中,我们将相应的滑块和切换按钮控件连接到我们的自定义触觉反馈。
// Connect wheel device rotation to slider control. private void RadialController_RotationChanged( object sender, RadialControllerRotationChangedEventArgs args) { ... if (ClickToggle.IsOn && (RotationSlider.Value > RotationSlider.Minimum) && (RotationSlider.Value < RotationSlider.Maximum)) { SimpleHapticsControllerFeedback waveform = FindWaveform(args.SimpleHapticsController, KnownSimpleHapticsControllerWaveforms.BuzzContinuous); if (waveform != null) { args.SimpleHapticsController.SendHapticFeedback(waveform); } } } private void RadialController_ButtonClicked( object sender, RadialControllerButtonClickedEventArgs args) { ... if (RotationSlider?.Value > 0) { SimpleHapticsControllerFeedback waveform = FindWaveform(args.SimpleHapticsController, KnownSimpleHapticsControllerWaveforms.Click); if (waveform != null) { args.SimpleHapticsController.SendHapticFeedbackForPlayCount( waveform, 1.0, (int)RotationSlider.Value, TimeSpan.Parse("1")); } } }最后,我们获取请求的触觉反馈的波形(如果支持的话)。
// Get the requested waveform. private SimpleHapticsControllerFeedback FindWaveform( SimpleHapticsController hapticsController, ushort waveform) { foreach (var hapticInfo in hapticsController.SupportedFeedback) { if (hapticInfo.Waveform == waveform) { return hapticInfo; } } return null; }
现在再次运行应用,通过更改滑块值和切换开关状态来试用自定义触觉。
步骤 7:为Surface Studio和类似设备定义屏幕交互
与Surface Studio配对,Surface Dial可以提供更独特的用户体验。
除了描述的默认按下和按住菜单体验之外,Surface Dial还可以直接放置在Surface Studio的屏幕上。 这将启用特殊的“屏幕”菜单。
通过检测 Surface Dial 的接触位置和范围,系统处理设备的遮挡,并显示一个更大的菜单版本,该菜单环绕设备外部。 你的应用还可以使用此相同的信息来调整 UI,以便同时适应设备的存在及其预期使用情况,例如用户的手部和手臂的位置。
本教程附带的示例包含一个稍微复杂一些的示例,该示例演示了其中一些功能。
要查看实际效果,您需要使用Surface Studio:
在Surface Studio设备上下载示例(已安装Visual Studio)
在 Visual Studio 中打开示例
打开App.xaml.cs文件
查找标记为此步骤标题的代码(“步骤 7:定义Surface Studio和类似设备的屏幕交互”)
注释第一行和第二行(“MainPage_Basic”和“MainPage_Haptics”)并取消注释第三行(“MainPage”)
rootFrame.Navigate(typeof(MainPage_Basic), e.Arguments); rootFrame.Navigate(typeof(MainPage_Haptics), e.Arguments); rootFrame.Navigate(typeof(MainPage), e.Arguments);先运行应用程序,然后将Surface Dial交替放置在两个控制区域中的每一个。
总结
恭喜,你已完成 入门教程:在 Windows 应用中支持 Surface Dial(和其他旋转设备)! 我们向您展示了在 Windows 应用中支持 wheel 设备所需的基本代码,以及如何通过 RadialController API 提供更丰富的用户体验。
相关文章
API 参考
- RadialController 类
- RadialControllerButtonClickedEventArgs 类
- RadialControllerConfiguration class
- RadialControllerControlAcquiredEventArgs 类
- RadialControllerMenu 类
- RadialControllerMenuItem 类
- RadialControllerRotationChangedEventArgs 类
- RadialControllerScreenContact 类
- RadialControllerScreenContactContinuedEventArgs 类
- RadialControllerScreenContactStartedEventArgs 类
- RadialControllerMenuKnownIcon枚举
- RadialControllerSystemMenuItemKind 枚举