教學課程:在您的 Windows 應用程式中支援 Surface Dial (及其他轉輪裝置)
Surface Dial 與 Surface Studio 和 Surface 觸控筆 (可在 Microsoft Store 購買)。
本教學逐步介紹如何自訂 Surface Dial 等滾輪裝置支援的使用者互動體驗。 我們使用範例應用程式中的片段 (您可以從 GitHub 下載該片段 (請參閱範例程式碼)) 來示範每個步驟中討論的各種功能和關聯的 RadialController API。
著重於以下內容:
- 指定 RadialController 功能表上顯示哪些內建工具
- 將自訂工具新增至功能表
- 控制觸覺反饋
- 自訂點選互動
- 自訂旋轉互動
有關實現這些功能和其他功能的更多資訊,請參閱 Windows 應用中的 Surface Dial 互動。
簡介
Surface Dial 是一種輔助輸入裝置,與筆、觸控或滑鼠等主要輸入裝置一起使用時,可協助使用者提高工作效率。 作為輔助輸入裝置,轉盤通常與非慣用手一起使用,以提供對系統命令和其他更具上下文的工具和功能的存取。
Dial 支援三種基本手勢:
- 按住以顯示命令的內建功能表。
- 旋轉以反白顯示功能表項目 (如果功能表處於活動狀態) 或修改應用程式中的目前操作 (如果功能表未處於活動狀態)。
- 按兩下即可選取醒目提示的功能表項 (如果功能表為使用中),或叫用應用程式中的命令 (如果功能表未使用中)。
必要條件
- 執行 Windows 10 Creators Update 或更高版本的電腦 (或虛擬機器)
- Visual Studio 2019
- Windows 10 SDK (10.0.15063.0)
- 輪子裝置 (目前只有 Surface Dial)
- 如果您還不熟悉使用 Visual Studio 進行 Windows 應用程式開發,請在開始本教學課程之前查看以下主題:
設定您的裝置
- 請確定您的 Windows 裝置已開啟。
- 前往開始,選擇設定>裝置>藍牙&其他裝置,然後開啟藍牙。
- 取下 Surface Dial 的底部以打開電池倉,並確保裡面有兩顆 AAA 電池。
- 如果電池標籤位於轉盤底部,請將其取下。
- 按住電池旁的嵌入式小按鈕,直到藍牙指示燈閃爍。
- 返回您的 Windows 裝置並選擇新增藍牙或其他裝置。
- 在新增裝置對話方塊中,選擇Bluetooth>Surface Dial。 您的 Surface Dial 現在應該已連接並新增到藍牙&其他裝置設定頁面上的滑鼠、鍵盤&筆下 的裝置清單中。
- 按下並按住撥號鍵數秒即可顯示內建功能表,以測試 Dial。
- 如果螢幕上未顯示功能表 (Dial 也應該振動),請返回藍牙設置,移除裝置,然後嘗試再次連接裝置。
注意
可以透過 Wheel 設定來配置 Wheel 裝置:
- 在開始功能表上,選取設定。
- 選取裝置>滾輪。
現在您已準備好開始本教學課程。
範例指令碼
在本教學課程中,我們使用範例應用程式來示範所討論的概念和功能。
從 GitHub 的 windows-appsample-get-started-radialcontroller sample 範例下載此 Visual Studio 範例和原始碼:
- 選取綠色的複製或下載按鈕。
- 如果您有 GitHub 帳戶,您可以選擇 [在 Visual Studio 中開啟],將存放庫複製到本機電腦。
- 如果您沒有 GitHub 帳戶,或只是想要專案的本機複本,請選擇 [下載 ZIP] (您必須定期回來查看以下載最新的更新)。
重要
範例中的大部分程式碼都會標記為註解。當我們完成本主題中的每個步驟時,系統會要求您取消註解程式碼的各個區段。 在 Visual Studio 中,只要反白顯示程式碼行,接著按 CTRL-K,然後按 CTRL-U。
支援滾輪功能的組件
這些物件為 Windows 應用程式提供了大部分滾輪裝置體驗。
元件 | 描述 |
---|---|
RadialController 類別和相關內容 | 表示滾輪輸入裝置或配件,例如 Surface Dial。 |
IRadialControllerConfigurationInterop / IRadialControllerInterop 此處未介紹此功能,如需詳細資訊,請參閱 Windows 桌面範例。 |
啟用與 Windows 應用的互操作性。 |
步驟 1:執行範例
下載 RadialController 範例應用程式之後,請確認它是否執行:
- 在 Visual Studio 中開啟範例專案。
- 將 [方案平台] 下拉式清單設定為非 Arm 選取。
- 按 F5 編譯、部署並執行。
注意
或者,可以選取偵錯>開始偵錯功能表項目,或選取此處顯示的本機計算機執行按鈕顯示於此:
應用程式視窗隨即開啟,並在啟動顯示畫面出現幾秒鐘之後,您將看到這個初始畫面。
好了,我們現在有基本的 Windows 應用程式,在本教學課程接下來的所有部分我們都會用到它。 在以下步驟中,我們會新增 RadialController 功能。
步驟 2:基本 RadialController 功能
執行應用程式並在前景中,按住 Surface Dial 以顯示 RadialController 功能表。
我們尚未為應用程式進行任何自訂,因此功能表包含一組預設的內容相關工具。
這些影像顯示預設功能表的兩種變化。 (還有其他許多專案,包括 Windows 桌面作用中時只有基本系統工具,而且沒有應用程式在前景、InkToolbar 存在時的額外筆跡工具,以及當您使用 地圖 應用程式時對應工具。
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 功能表項目清除功能表只留下我們的自訂工具。
// 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 和其他滾輪裝置可為使用者提供對應至目前互動的觸覺反饋 (根據按兩下或旋轉)。
在此步驟中,我們將展示如何透過關聯滑桿和切換開關控制項並使用它們動態指定觸覺反饋行為來自訂觸覺反饋。 對於此範例,必須將切換開關設為開啟才能啟用反饋,而滑桿值指定重複點擊反饋的頻率。
注意
使用者可以在 >SettingsDevices>Wheel 頁面中停用觸覺反饋。
開啟 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 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 支援且更豐富的使用者體驗。
相關文章
API 參考
- RadialController 類別
- RadialControllerButtonClickedEventArgs 類別
- RadialControllerConfiguration 類別
- RadialControllerControlAcquiredEventArgs 類別
- RadialControllerMenu 類別
- RadialControllerMenuItem 類別
- RadialControllerRotationChangedEventArgs 類別
- RadialControllerScreenContact 類別
- RadialControllerScreenContactContinuedEventArgs 類別
- RadialControllerScreenContactStartedEventArgs 類別
- RadialControllerMenuKnownIcon 列舉
- RadialControllerSystemMenuItemKind 列舉