Surface Dial 搭配 Surface Studio 與 Surface Pen (可在 Microsoft Store 購買)。
本教學將逐步說明如何自訂由 Surface Dial 等方向盤裝置所支援的使用者互動體驗。 我們使用範例應用程式的片段,您可以從 GitHub 下載(見 範例程式碼),來展示每步討論的各種功能及相關的 RadialController API。
我們專注於以下幾點:
- 指定 RadialController 選單中顯示哪些內建工具
- 在選單中新增自訂工具
- 控制觸覺回饋
- 自定義點擊互動
- 旋轉互動自訂
關於實作這些及其他功能的更多資訊,請參閱 Windows 應用程式中的 Surface Dial 互動。
簡介
Surface Dial是一種次要輸入裝置,當與筆、觸控或滑鼠等主要輸入裝置一起使用時,能幫助使用者提升生產力。 作為次要輸入裝置,Dial 通常由非慣用手操作,以存取系統指令以及其他更具情境性的工具與功能。
Dial支援三種基本手勢:
- 按住可顯示內建指令選單。
- 旋轉以選取選單項目(若選單已啟動),或在應用程式中修改當前動作(若選單未啟動)。
- 點擊選取高亮的選單項目(若選單已啟動),或在應用程式中呼叫指令(若選單未啟用)。
先決條件
- 一台運行 Windows 10 創作者更新或更新版本的電腦(或虛擬機器)
- Visual Studio 2019
- Windows 10 SDK (10.0.15063.0)
- 一個輪盤裝置(目前僅有 Surface Dial )
- 如果你是使用 Visual Studio 開發 Windows 應用程式的新手,建議在開始這個教學前先瀏覽這些主題:
設定你的裝置
- 請確保你的 Windows 裝置是開機的。
- 進入開始,選擇設定>、裝置>、藍牙及其他裝置,然後開啟藍牙。
- 取下 Surface Dial 底部以開啟電池槽,並確保裡面有兩顆 AAA 電池。
- 如果錶盤底部有電池卡,請將其拆除。
- 按住電池旁邊的小凹槽按鈕,直到藍牙燈閃爍。
- 回到你的 Windows 裝置,選擇 新增藍牙或其他裝置。
- 在 「新增裝置 」對話框中,選擇 藍牙>Surface Dial。 你的 Surface Dial 現在應該會連接,並在藍牙與其他裝置設定頁面下的滑鼠、鍵盤與筆裝置清單中顯示。
- 按住撥盤幾秒鐘來測試,即可顯示內建選單。
- 如果選單沒有顯示在螢幕上(Dial 也會震動),請回到藍牙設定,移除裝置,再嘗試連接裝置。
備註
輪子裝置可透過 輪子 設定進行配置:
- 在 開始 選單中,選擇 設定。
- 選擇 裝置>Wheel。
現在你準備好開始這個教學了。
範例程式碼
在整個教學過程中,我們使用範例應用程式來示範所討論的概念與功能。
請從 GitHub 上的 windows-appsample-get-started-radialcontroller sample 下載此 Visual Studio 範例和原始碼:
- 選擇綠色 複製或下載 按鈕。
- 如果你有 GitHub 帳號,你可以選擇 使用 Visual Studio 開啟 來將儲存庫複製到你的本地機器。
- 如果你沒有 GitHub 帳號,或只是想要專案的本地副本,請選擇 下載 ZIP (你需要定期回來下載最新更新)。
這很重要
範例中的大部分程式碼已經被註解掉。當我們進行這個主題的每個步驟時,你會被要求取消註解程式碼中的各個部分。 在 Visual Studio 裡,選取程式碼行,然後按 CTRL-K 再按 CTRL-U。
支援輪子功能的元件
這些物件為 Windows 應用程式提供了大部分的方向盤裝置體驗。
| 元件 | Description |
|---|---|
| RadialController 類別 及相關 | 代表輪盤輸入裝置或配件,如表面撥盤。 |
|
IRadialControllerConfigurationInterop / IRadialControllerInterop 我們此處未涵蓋此功能,更多資訊請參閱 Windows 桌面範例。 |
實現與 Windows 應用程式的互通性。 |
步驟一:測試樣本
下載完 RadialController 範例應用程式後,請確認它能執行:
- 在 Visual Studio 開啟範例專案。
- 將 解決方案平台 下拉選單設為非 Arm 選項。
- 按 F5 即可編譯、部署並執行。
備註
或者,您也可以選擇 「除錯>開始除錯 」選單項目,或點選此處的 「本地機器 執行」按鈕: 
應用程式視窗會打開,幾秒鐘後會出現啟動畫面,你就會看到這個初始畫面。
好了,我們現在有了基本的 Windows 應用程式,接下來的教學內容會用到它。 接下來的步驟,我們會加入 RadialController 功能。
步驟 2:基本「RadialController」功能
當應用程式運行且畫面在前景時,按住 Surface Dial,顯示 RadialController 選單。
我們還沒做過任何自訂,所以選單裡預設有一套情境工具。
這些圖片展示了兩種預設選單的變體。 (還有許多其他工具,包括當 Windows 桌面啟動且前景沒有應用程式時的基本系統工具、有 InkToolbar 時的額外描線工具,以及使用 Maps 應用程式時的映射工具。
| RadialController 選單(預設) | RadialController 選單(預設狀態下媒體播放) |
|---|---|
|
|
現在我們先從一些基本的自訂開始。
步驟 3:新增方向盤輸入控制
首先,讓我們加入應用程式的使用者介面:
打開 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;在這裡,我們指定了按鈕的點擊處理器,用於啟用控制項並初始化自訂 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[] { }); }選擇 「移除物品 」按鈕,然後長按撥號鍵再次顯示選單。
注意選單現在包含預設的工具集合。 記得在第三步設定自訂選單時,我們移除了所有預設工具,只新增自訂工具。 我們也注意到,當選單設定為空集合時,當前上下文的預設項目會被恢復。 (我們在移除預設工具之前,已經加入了自訂工具。)
選擇 新增項目 按鈕,然後長按撥號。
請注意,選單現在同時包含預設的工具集合和我們的自訂工具。
選擇 重設 RadialController 選單 按鈕,然後長按撥號。
注意選單回復到原始狀態。
步驟六:客製化裝置觸覺
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; }
現在再跑一次 App,透過更改滑桿值和切換開關狀態來嘗試自訂觸覺。
步驟7:定義Surface Studio及類似裝置的螢幕互動
搭配 Surface Studio 時,Surface Dial 能帶來更具特色的使用者體驗。
除了預設的長按選單體驗外,Surface Dial也可以直接放置在Surface Studio的螢幕上。 這讓一個特殊的「螢幕上」選單得以實現。
透過偵測 Surface Dial 的接觸位置與界限,系統能處理裝置的遮蔽問題,並顯示一個繞著 Dial 外側的更大版選單。 同樣的資訊也可以被你的應用程式用來調整使用者介面,以配合裝置的存在以及預期的使用方式,例如使用者的手和手臂位置。
本教學附帶的範例包含一個稍微複雜一點的範例,展示部分這些功能。
要看實際操作(你需要Surface Studio):
在安裝 Visual Studio 的 Surface Studio 裝置上下載範例
在 Visual Studio 開啟範例
打開App.xaml.cs檔案
尋找標示此步驟標題的程式碼(「步驟7:定義 Surface Studio 及類似裝置的螢幕互動」)
請註解第一和第二行(「MainPage_Basic」和「MainPage_Haptics」),取消留言第三行(「主頁」)。
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 所支援的更豐富使用者體驗。
相關文章
API 參考資料
- RadialController 類別
- RadialControllerButtonClickedEventArgs 類別
- RadialControllerConfiguration 類別
- RadialControllerControlAcquiredEventArgs 類別
- RadialControllerMenu 類別
- RadialControllerMenuItem 類別
- RadialControllerRotationChangedEventArgs 類別
- RadialControllerScreenContact 類別
- RadialControllerScreenContactContinuedEventArgs 類別
- RadialControllerScreenContactStartedEventArgs 類別
- RadialControllerMenuKnownIcon enum
- RadialControllerSystemMenuItemKind enum