共用方式為


教學:在你的 Windows 應用程式中支援 Surface Dial(以及其他方向盤裝置)

Surface Dial 與 Surface Studio 的圖片
Surface Dial 搭配 Surface Studio 與 Surface Pen (可在 Microsoft Store 購買)。

本教學將逐步說明如何自訂由 Surface Dial 等方向盤裝置所支援的使用者互動體驗。 我們使用範例應用程式的片段,您可以從 GitHub 下載(見 範例程式碼),來展示每步討論的各種功能及相關的 RadialController API。

我們專注於以下幾點:

  • 指定 RadialController 選單中顯示哪些內建工具
  • 在選單中新增自訂工具
  • 控制觸覺回饋
  • 自定義點擊互動
  • 旋轉互動自訂

關於實作這些及其他功能的更多資訊,請參閱 Windows 應用程式中的 Surface Dial 互動

簡介

Surface Dial是一種次要輸入裝置,當與筆、觸控或滑鼠等主要輸入裝置一起使用時,能幫助使用者提升生產力。 作為次要輸入裝置,Dial 通常由非慣用手操作,以存取系統指令以及其他更具情境性的工具與功能。

Dial支援三種基本手勢:

  • 按住可顯示內建指令選單。
  • 旋轉以選取選單項目(若選單已啟動),或在應用程式中修改當前動作(若選單未啟動)。
  • 點擊選取高亮的選單項目(若選單已啟動),或在應用程式中呼叫指令(若選單未啟用)。

先決條件

設定你的裝置

  1. 請確保你的 Windows 裝置是開機的。
  2. 進入開始,選擇設定>、裝置>、藍牙及其他裝置,然後開啟藍牙
  3. 取下 Surface Dial 底部以開啟電池槽,並確保裡面有兩顆 AAA 電池。
  4. 如果錶盤底部有電池卡,請將其拆除。
  5. 按住電池旁邊的小凹槽按鈕,直到藍牙燈閃爍。
  6. 回到你的 Windows 裝置,選擇 新增藍牙或其他裝置
  7. 「新增裝置 」對話框中,選擇 藍牙>Surface Dial。 你的 Surface Dial 現在應該會連接,並在藍牙與其他裝置設定頁面下的滑鼠、鍵盤與筆裝置清單中顯示。
  8. 按住撥盤幾秒鐘來測試,即可顯示內建選單。
  9. 如果選單沒有顯示在螢幕上(Dial 也會震動),請回到藍牙設定,移除裝置,再嘗試連接裝置。

備註

輪子裝置可透過 輪子 設定進行配置:

  1. 開始 選單中,選擇 設定
  2. 選擇 裝置>Wheel
    方向盤設定畫面

現在你準備好開始這個教學了。

範例程式碼

在整個教學過程中,我們使用範例應用程式來示範所討論的概念與功能。

請從 GitHub 上的 windows-appsample-get-started-radialcontroller sample 下載此 Visual Studio 範例和原始碼:

  1. 選擇綠色 複製或下載 按鈕。
    複製儲存庫
  2. 如果你有 GitHub 帳號,你可以選擇 使用 Visual Studio 開啟 來將儲存庫複製到你的本地機器。
  3. 如果你沒有 GitHub 帳號,或只是想要專案的本地副本,請選擇 下載 ZIP (你需要定期回來下載最新更新)。

這很重要

範例中的大部分程式碼已經被註解掉。當我們進行這個主題的每個步驟時,你會被要求取消註解程式碼中的各個部分。 在 Visual Studio 裡,選取程式碼行,然後按 CTRL-K 再按 CTRL-U。

支援輪子功能的元件

這些物件為 Windows 應用程式提供了大部分的方向盤裝置體驗。

元件 Description
RadialController 類別 及相關 代表輪盤輸入裝置或配件,如表面撥盤。
IRadialControllerConfigurationInterop / IRadialControllerInterop
我們此處未涵蓋此功能,更多資訊請參閱 Windows 桌面範例
實現與 Windows 應用程式的互通性。

步驟一:測試樣本

下載完 RadialController 範例應用程式後,請確認它能執行:

  1. 在 Visual Studio 開啟範例專案。
  2. 解決方案平台 下拉選單設為非 Arm 選項。
  3. 按 F5 即可編譯、部署並執行。

備註

或者,您也可以選擇 「除錯>開始除錯 」選單項目,或點選此處的 「本地機器 執行」按鈕: Visual Studio 建置專案按鈕

應用程式視窗會打開,幾秒鐘後會出現啟動畫面,你就會看到這個初始畫面。

Empty app

好了,我們現在有了基本的 Windows 應用程式,接下來的教學內容會用到它。 接下來的步驟,我們會加入 RadialController 功能。

步驟 2:基本「RadialController」功能

當應用程式運行且畫面在前景時,按住 Surface Dial,顯示 RadialController 選單。

我們還沒做過任何自訂,所以選單裡預設有一套情境工具。

這些圖片展示了兩種預設選單的變體。 (還有許多其他工具,包括當 Windows 桌面啟動且前景沒有應用程式時的基本系統工具、有 InkToolbar 時的額外描線工具,以及使用 Maps 應用程式時的映射工具。

RadialController 選單(預設) RadialController 選單(預設狀態下媒體播放)
預設 RadialController 選單 具有音樂功能的預設 RadialController 選單

現在我們先從一些基本的自訂開始。

步驟 3:新增方向盤輸入控制

首先,讓我們加入應用程式的使用者介面:

  1. 打開 MainPage_Basic.xaml 檔案。

  2. 找到標示此步驟標題的程式碼(「<--步驟3:新增輪盤輸入控制 --」)>。

  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 存取控制系統所需的程式碼。

  1. 打開MainPage_Basic.xaml.cs檔案。
  2. 找到標示此步驟標題的程式碼(「// 步驟 4:基本 RadialController 選單自訂」)。
  3. 請刪除以下幾句:
    • Windows.UI.InputWindows.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;
          }
          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;
      }
      
  4. 現在,再執行一次應用程式。
  5. 選擇 初始化徑向控制器 按鈕。
  6. 當應用程式在前景時,長按 Surface Dial 即可顯示選單。 請注意,所有預設工具都已被移除(透過使用 RadialControllerConfiguration.SetDefaultMenuItems 方法),只剩下自訂工具。 這是我們自訂工具的選單。
RadialController 選單(自訂)
自訂 RadialController 選單
  1. 選擇自訂工具,並嘗試 Surface Dial 現在支援的互動:
    • 旋轉動作會移動滑桿。
    • 點擊即可切換開關為開啟或關閉。

好,我們把按鈕連接起來。

步驟 5:執行時設定選單

在此步驟中,我們連接了 新增/移除項目重置 RadialController 選單 按鈕,展示如何動態自訂選單。

  1. 打開MainPage_Basic.xaml.cs檔案。

  2. 找到標示此步驟標題的程式碼(「// 步驟 5:執行時設定選單」)。

  3. 在以下方法中取消註解程式碼,然後重新執行應用程式,但不要選擇任何按鈕(這部分留到下一步再選)。

    // 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[] { });
    }
    
  4. 選擇 「移除物品 」按鈕,然後長按撥號鍵再次顯示選單。

    注意選單現在包含預設的工具集合。 記得在第三步設定自訂選單時,我們移除了所有預設工具,只新增自訂工具。 我們也注意到,當選單設定為空集合時,當前上下文的預設項目會被恢復。 (我們在移除預設工具之前,已經加入了自訂工具。)

  5. 選擇 新增項目 按鈕,然後長按撥號。

    請注意,選單現在同時包含預設的工具集合和我們的自訂工具。

  6. 選擇 重設 RadialController 選單 按鈕,然後長按撥號。

    注意選單回復到原始狀態。

步驟六:客製化裝置觸覺

Surface Dial 及其他方向盤裝置,能根據當前互動(基於點擊或旋轉)提供觸覺回饋。

在此步驟中,我們將展示如何透過結合滑桿和切換開關控件來自訂觸覺回饋,並利用它們動態地指定觸覺回饋行為。 在這個例子中,撥動開關必須開啟才能啟用回饋,而滑桿值則指定點擊回饋重複的頻率。

備註

使用者可在 設定>裝置>輪頁 中關閉觸覺回饋。

  1. 打開App.xaml.cs檔案。

  2. 找到標示此步驟標題的代碼(「步驟6:自訂裝置觸覺」)。

  3. 請註解第一和第三行(「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);
    
  4. 打開 MainPage_Haptics.xaml 檔案。

  5. 找到標有此步驟標題的代碼(「<--步驟6:自訂裝置觸覺裝置 -->」)。

  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>
    
  7. 打開MainPage_Haptics.xaml.cs檔案

  8. 尋找標示此步驟標題的程式碼(「步驟6:觸覺自訂」)。

  9. 請刪除以下幾句:

    • 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;
          }
      }
      
    • 在我們的 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;
      }
      

現在再跑一次 App,透過更改滑桿值和切換開關狀態來嘗試自訂觸覺。

步驟7:定義Surface Studio及類似裝置的螢幕互動

搭配 Surface Studio 時,Surface Dial 能帶來更具特色的使用者體驗。

除了預設的長按選單體驗外,Surface Dial也可以直接放置在Surface Studio的螢幕上。 這讓一個特殊的「螢幕上」選單得以實現。

透過偵測 Surface Dial 的接觸位置與界限,系統能處理裝置的遮蔽問題,並顯示一個繞著 Dial 外側的更大版選單。 同樣的資訊也可以被你的應用程式用來調整使用者介面,以配合裝置的存在以及預期的使用方式,例如使用者的手和手臂位置。

本教學附帶的範例包含一個稍微複雜一點的範例,展示部分這些功能。

要看實際操作(你需要Surface Studio):

  1. 在安裝 Visual Studio 的 Surface Studio 裝置上下載範例

  2. 在 Visual Studio 開啟範例

  3. 打開App.xaml.cs檔案

  4. 尋找標示此步驟標題的程式碼(「步驟7:定義 Surface Studio 及類似裝置的螢幕互動」)

  5. 請註解第一和第二行(「MainPage_Basic」和「MainPage_Haptics」),取消留言第三行(「主頁」)。

    rootFrame.Navigate(typeof(MainPage_Basic), e.Arguments);
    rootFrame.Navigate(typeof(MainPage_Haptics), e.Arguments);
    rootFrame.Navigate(typeof(MainPage), e.Arguments);
    
  6. 執行應用程式,將 Surface Dial 放在兩個控制區,交替使用。
    螢幕上的 RadialController

總結

恭喜你,你已經完成了入 門教學:在你的 Windows 應用程式中支援 Surface Dial(以及其他滾輪裝置)! 我們展示了支援 Windows 應用程式中方向盤裝置的基本程式碼,以及如何提供 RadialController API 所支援的更豐富使用者體驗。

Surface Dial 互動

API 參考資料

Samples

主題範例

RadialController 自訂化

其他範例

著色書範例

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

Windows 桌面範例