在这里,我们指定按钮的 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;
RotationSlider.Value += args.RotationDeltaInDegrees;
// Connect wheel device click to toggle switch control.
private void RadialController_ButtonClicked(
object sender, RadialControllerButtonClickedEventArgs args)
ClickToggle.IsOn = !ClickToggle.IsOn;
好吧,让我们连接这些按钮。
在此步骤中,我们将 添加/删除项 按钮 和 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”菜单 按钮,然后按住拨盘。
请注意,菜单返回到其原始状态。
步骤 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 处理程序,在其中我们禁用默认的触觉反馈并初始化我们的触觉界面。
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 的接触位置和边界,系统处理设备的遮挡问题,并显示一个围绕 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 应用中支持滚轮设备所需的基本代码,以及如何通过 RadialController API 提供更高级的用户体验。
Surface Dial 的交互
API 参考
RadialController 类
RadialControllerButtonClickedEventArgs 类
RadialControllerConfiguration 类
RadialControllerControlAcquiredEventArgs 类
RadialControllerMenu 类
RadialControllerMenuItem 类
RadialControllerRotationChangedEventArgs 类
RadialControllerScreenContact 类
RadialControllerScreenContactContinuedEventArgs 类
RadialControllerScreenContactStartedEventArgs 类
RadialControllerMenuKnownIcon 枚举
RadialControllerSystemMenuItemKind 枚举类型
调节器的自定义设置
涂色本示例
通用 Windows 平台示例(C# 和 C++)
Windows 桌面示例