Surface Dial 搭配 Surface Studio 和 Surface 手寫筆 (可在 Microsoft Store 購買)。

本教學課程逐步解說如何自定義滾輪裝置所支援的用戶互動體驗,例如 Surface Dial。 我們會使用範例應用程式的代碼段,您可以從 GitHub 下載這些代碼段(請參閱範例程式 代碼 ),來示範每個步驟中所討論的各種功能和相關聯的 RadialController API。

我們著重於下列事項:

  • 指定 RadialController 功能表上顯示哪些內建工具
  • 將自訂工具新增至功能表
  • 控制觸覺回饋
  • 自訂點選互動
  • 自定義旋轉互動
  • 如需了解這些和其他功能的實作詳情,請參閱 Surface Dial 互動 在 Windows 應用程式中。

    Surface Dial 是次要輸入裝置,可協助使用者在與手寫筆、觸控或滑鼠等主要輸入設備搭配使用時更具生產力。 作為次要輸入設備,Dial 通常搭配非慣用手使用,以便更便捷地存取系統命令以及其他更具情境的工具和功能。

    Dial 支援三種基本手勢:

  • 按住以顯示內建命令選單。
  • 旋轉以反白顯示功能表項(如果功能表為啟用狀態),或在應用程式中修改當前的動作(如果功能表未啟用)。
  • 按兩下即可選取醒目提示的功能表項(如果選單為使用中),或叫用應用程式中的命令(如果選單未使用中)。
  • 執行 Windows 10 Creators Update 或更新版本的電腦(或虛擬機器)
  • Visual Studio 2019 Windows 10 SDK (10.0.15063.0)
  • 滾輪裝置(目前只有 Surface Dial
  • 如果您不熟悉使用 Visual Studio 進行 Windows 應用程式開發,請先查看下列主題,再開始本教學課程:
      安裝 Windows 應用程式 SDK 的工具 建立 「Hello, world」 應用程式 (XAML)

      設定您的裝置

    • 請確定您的 Windows 裝置已開啟。
    • 移至 [ 開始] ,選取 [ 設定 > 裝置 > 藍牙和其他裝置 ],然後開啟 藍牙
    • 拿掉 Surface Dial 底部以開啟電池區間,並確定裡面有兩個 AAA 電池。
    • 如果 [轉盤] 底端有電池的標籤,請將其移除。
    • 按住電池旁邊的小型內嵌按鈕,直到藍牙燈閃爍為止。
    • 返回您的 Windows 裝置,然後選取 [ 新增藍牙或其他裝置 ]。
    • 在 [ [新增裝置 ] 對話框中,選取 [ 藍牙 > Surface Dial 。 您的 Surface Dial 現在應該會連線並新增至藍牙 和其他裝置 設定頁面上 滑鼠、鍵盤和手寫筆 下的裝置清單。
    • 測試 Dial 時,請按住撥號鍵數秒,顯示內建功能表。
    • 如果未在畫面上顯示功能表(Dial 也應該振動),請返回藍牙設定,移除裝置,然後再次嘗試連線裝置。
    • 滾輪裝置可以在 Wheel 設定中進行配置:

    • 在 [ 開始 ] 選單上,選取 [ 設定 ]。
    • 選取 [ 裝置] > [滾輪]

      範例程式碼

      在本教學課程中,我們會使用範例應用程式來示範所討論的概念和功能。

      GitHub 下載此 Visual Studio 範例和原始程式碼, windows-appsample-get-started-radialcontroller 範例

    • 選取綠色 複製或下載 按鈕。
    • 如果您有 GitHub 帳戶,您可以選擇 [在 Visual Studio 中開啟 ],將存放庫複製到本機計算機。
    • 如果您沒有 GitHub 帳戶,或只是想要專案的本地副本,請選擇 [下載 ZIP ] (您必須定期回來查看以下載最新的更新)。
    • 範例中的大部分程式代碼都會加上批注。當我們完成本主題中的每個步驟時,系統會要求您取消批注程式代碼的各個區段。 在 Visual Studio 中,只要選取程式碼,然後按 CTRL-K 再按 CTRL-U。

      支援轉輪功能的元件

      這些物件為 Windows 應用程式提供大量滾輪裝置體驗。

      IRadialControllerConfigurationInterop / IRadialControllerInterop
      我們在此並未涵蓋這項功能,如需詳細資訊,請參閱 Windows 桌面範例 。 啟用與 Windows 應用程式的互作性。

      步驟 1:執行範例

      下載 RadialController 範例應用程式之後,請確認它是否執行:

    • 在 Visual Studio 中開啟範例專案。
    • 將 [ 解決方案平臺] 下拉式清單設為非 Arm 的選項。
    • 按 F5 以編譯、部署和執行。
    • 或者,您可以選取 [ 偵錯] > [開始偵錯 ] 功能表項,或選取此處顯示的 [ 本機電腦 執行] 按鈕: Visual Studio [建置專案] 按鈕

      應用程式視窗開啟後,啟動畫面將顯示數秒,接著您會看到這個初始畫面。

      好吧,我們現在有基本的 Windows 應用程式,我們將在本教學課程的其餘部分使用。 在以下步驟中,我們將新增 RadialController 的功能。

      步驟 2:RadialController 基本功能

      當應用程式執行並在前景中時,按住 Surface Dial 以顯示 RadialController 選單。

      我們尚未為應用程式進行任何自定義,因此功能表包含一組預設的內容相關工具。

      這些影像顯示預設功能表的兩種變化。 當 Windows 桌面處於作用中,且前景沒有應用程式時,系統只會顯示基本系統工具。當存在 InkToolbar 時,會有額外的筆跡工具。當您使用地圖應用程式時,還會有相應的地圖工具。此外,還有許多其他工具可用。

      RadialController 功能表 (預設值) RadialController 選單 (預設為媒體播放) 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 功能表項,以提供訪問滑桿和切換開關的功能。

      Windows.UI.Input Windows.Storage.Streams 類型參考用於後續步驟中的功能:

      // Using directives for RadialController functionality.
      using Windows.UI.Input;
      
    • 這些全域物件(RadialControllerRadialControllerConfigurationRadialControllerMenuItem)會在整個應用程式中使用。

      private RadialController radialController;
      private RadialControllerConfiguration radialControllerConfig;
      private RadialControllerMenuItem radialControllerMenuItem;
      
    • 在這裡,我們會指定按鈕的 [按兩下 處理程式],以啟用控件並初始化自定義 RadialController 選單項。

      InitializeSampleButton.Click += (sender, args) =>
      { InitializeSample(sender, args); };
      
    • 接下來,我們會初始化 RadialController 物件,並設定 RotationChangedButtonClicked 事件的處理程式。

      // 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;
      

      好吧,我們來連接這些按鈕。

      步驟 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[] { });
      
    • 選取 移除項目 按鈕,然後按住並保持轉環以再次顯示功能表。

      請注意,選單現在包含預設的工具集合。 回想一下,在步驟 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;
      
    • 在我們的 RotationChangedButtonClicked 事件處理程式中,我們會將對應的滑桿和切換按鈕控制項連接到我們自定義的觸覺功能。

      // 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 Dial 與 Surface Studio 配對,可提供更獨特的用戶體驗。

      除了上述的默認按壓和長按功能表體驗之外,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 列舉
    • RadialController自訂設定

      著色本範例

      通用 Windows 平台範例 (C# 和 C++)

      Windows 桌面範例

  •