Windows 應用程式中的手寫筆互動與 Windows Ink
Surface 手寫筆(可在 Microsoft Store 購買)。
概觀
將 Windows 應用程式優化以取得手寫筆輸入,以提供標準 指標裝置 功能,以及為您的使用者提供最佳的 Windows Ink 體驗。
注意
本主題著重於 Windows Ink 平臺。 如需一般指標輸入處理(類似於滑鼠、觸控和觸控板),請參閱 處理指標輸入。
在 Windows 應用程式中使用筆跡
使用 Windows 手寫筆和 Ink 來建置更具吸引力的企業應用程式
Windows Ink 平臺與手寫筆裝置一起提供自然的方式來建立數位手寫筆記、繪圖和批註。 平臺支援將數位板輸入擷取為筆跡數據、產生筆跡數據、管理筆跡數據、將筆跡數據轉譯為輸出裝置上的筆墨筆劃,以及透過手寫辨識將筆跡轉換成文字。
除了在使用者寫入或繪製時擷取手寫筆的基本位置和移動,您的應用程式也可以追蹤並收集整個筆劃中使用的不同壓力量。 這項資訊,以及筆尖形狀、大小和旋轉、筆跡色彩和用途的設定(純墨、清除、醒目提示和選取),可讓您提供與使用畫筆、鉛筆或筆刷在紙上撰寫或繪製非常類似的用戶體驗。
注意
您的應用程式也可以支援來自其他指標型裝置的筆跡輸入,包括觸控數位板和滑鼠裝置。
筆跡平臺非常有彈性。 其設計目的是要根據您的需求來支持各種功能層級。
如需 Windows Ink UX 指導方針,請參閱 筆跡控件。
Windows Ink 平臺的元件
元件 | 描述 |
---|---|
InkCanvas | 根據預設,XAML UI 平臺控制件會接收並顯示筆跡筆劃或清除筆劃的所有輸入。 如需如何使用 InkCanvas 的詳細資訊,請參閱 將 Windows Ink 筆劃辨識為文字 和 儲存及擷取 Windows Ink 筆劃數據。 |
InkPresenter (機器翻譯) | 程序代碼後置物件,與 InkCanvas 控件一起具現化(透過 InkCanvas.InkPresenter 屬性公開)。 此物件提供 InkCanvas 公開的所有預設筆跡功能,以及一組完整的 API,以供其他自定義和個人化使用。 如需如何使用 InkPresenter 的詳細資訊,請參閱 將 Windows Ink 筆劃辨識為文字 和 儲存及擷取 Windows Ink 筆劃數據。 |
InkToolbar | XAML UI 平臺控制件,其中包含可自定義且可延伸的按鈕集合,可在相關聯的 InkCanvas 中啟用筆跡相關功能。 如需如何使用 InkToolbar 的詳細資訊,請參閱 將 InkToolbar 新增至 Windows 應用程式筆跡應用程式。 |
IInkD2DRenderer | 啟用將筆墨筆劃轉譯到通用 Windows 應用程式的指定 Direct2D 裝置內容,而不是預設 InkCanvas 控制件。 這可讓您完整自定義手寫筆跡體驗。 如需詳細資訊,請參閱 複雜筆跡範例。 |
使用 InkCanvas 的基本筆跡
若要新增基本手寫筆跡功能,只要將 InkCanvas UWP 平臺控件放在應用程式的適當頁面上即可。
根據預設, InkCanvas 僅支援筆跡輸入。 輸入會使用色彩和粗細的預設設定來轉譯為筆墨筆劃(粗細為 2 圖元的黑色球點筆),或被視為筆劃橡皮擦(當輸入是來自橡皮擦筆尖或以清除按鈕修改的筆尖時)。
注意
如果橡皮擦提示或按鈕不存在,InkCanvas 可以設定為將筆尖的輸入當做清除筆劃來處理。
在此範例中 ,InkCanvas 會重疊背景影像。
注意
InkCanvas 的預設 Height 和 Width 屬性為零,除非它是自動調整其子元素大小的元素的子系,例如 StackPanel 或 Grid 控件。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0">
<TextBlock x:Name="Header"
Text="Basic ink sample"
Style="{ThemeResource HeaderTextBlockStyle}"
Margin="10,0,0,0" />
</StackPanel>
<Grid Grid.Row="1">
<Image Source="Assets\StoreLogo.png" />
<InkCanvas x:Name="inkCanvas" />
</Grid>
</Grid>
這一系列的影像顯示此 InkCanvas 控件如何呈現手寫筆輸入。
具有背景影像的空白 InkCanvas。 | 筆跡筆劃的InkCanvas。 | 已清除一筆劃的 InkCanvas (請注意清除如何在整個筆劃上運作,而不是部分)。 |
InkCanvas 控件支援的筆跡功能是由稱為 InkPresenter 的程式代碼後置物件所提供。
對於基本筆跡,您不需要擔心 InkPresenter。 不過,若要自定義及設定 InkCanvas 上的筆跡行為,您必須存取其對應的 InkPresenter 物件。
使用 InkPresenter 進行基本自定義
InkPresenter 物件會與每個 InkCanvas 控件具現化。
注意
InkPresenter 無法直接具現化。 而是透過 InkCanvas 的 InkPresenter 屬性來存取它。
InkPresenter 除了提供其對應 InkCanvas 控件的所有預設筆跡行為之外,還提供一組完整的 API,用於進一步自定義筆觸,以及更精細的管理手寫筆輸入(標準和修改)。 這包括筆劃屬性、支援的輸入裝置類型,以及對象處理輸入,還是傳遞至應用程式進行處理。
注意
標準筆跡輸入(從手寫筆尖或橡皮擦提示/按鈕)不會修改為次要硬體能供性,例如畫筆桶按鈕、滑鼠右鍵或類似的機制。
根據預設,只支援手寫筆輸入的筆跡。 在這裡,我們將 InkPresenter 設定為將畫筆和滑鼠的輸入數據解譯為筆墨筆劃。 我們也設定一些初始筆墨筆劃屬性,用於將筆劃轉譯至 InkCanvas。
若要啟用滑鼠和觸控筆跡,請將 InkPresenter 的 InputDeviceTypes 屬性設定為您想要的 CoreInputDeviceTypes 值組合。
public MainPage()
{
this.InitializeComponent();
// Set supported inking device types.
inkCanvas.InkPresenter.InputDeviceTypes =
Windows.UI.Core.CoreInputDeviceTypes.Mouse |
Windows.UI.Core.CoreInputDeviceTypes.Pen;
// Set initial ink stroke attributes.
InkDrawingAttributes drawingAttributes = new InkDrawingAttributes();
drawingAttributes.Color = Windows.UI.Colors.Black;
drawingAttributes.IgnorePressure = false;
drawingAttributes.FitToCurve = true;
inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes);
}
您可以動態設定筆跡筆劃屬性,以配合使用者喜好設定或應用程式需求。
在這裡,我們讓使用者從筆跡色彩清單中選擇。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0">
<TextBlock x:Name="Header"
Text="Basic ink customization sample"
VerticalAlignment="Center"
Style="{ThemeResource HeaderTextBlockStyle}"
Margin="10,0,0,0" />
<TextBlock Text="Color:"
Style="{StaticResource SubheaderTextBlockStyle}"
VerticalAlignment="Center"
Margin="50,0,10,0"/>
<ComboBox x:Name="PenColor"
VerticalAlignment="Center"
SelectedIndex="0"
SelectionChanged="OnPenColorChanged">
<ComboBoxItem Content="Black"/>
<ComboBoxItem Content="Red"/>
</ComboBox>
</StackPanel>
<Grid Grid.Row="1">
<Image Source="Assets\StoreLogo.png" />
<InkCanvas x:Name="inkCanvas" />
</Grid>
</Grid>
然後,我們會處理所選色彩的變更,並據以更新筆跡筆劃屬性。
// Update ink stroke color for new strokes.
private void OnPenColorChanged(object sender, SelectionChangedEventArgs e)
{
if (inkCanvas != null)
{
InkDrawingAttributes drawingAttributes =
inkCanvas.InkPresenter.CopyDefaultDrawingAttributes();
string value = ((ComboBoxItem)PenColor.SelectedItem).Content.ToString();
switch (value)
{
case "Black":
drawingAttributes.Color = Windows.UI.Colors.Black;
break;
case "Red":
drawingAttributes.Color = Windows.UI.Colors.Red;
break;
default:
drawingAttributes.Color = Windows.UI.Colors.Black;
break;
};
inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes);
}
}
這些影像顯示 InkPresenter 如何處理和自定義手寫筆輸入。
具有預設黑色筆墨筆劃的 InkCanvas。
InkCanvas,其中包含用戶選取的紅色筆墨筆劃。
若要提供筆跡和清除以外的功能,例如筆劃選取,您的應用程式必須識別 InkPresenter 的特定輸入,才能通過未處理的處理應用程式。
進階處理的傳遞輸入
根據預設, InkPresenter 會將所有輸入視為筆墨筆劃或清除筆劃來處理,包括次要硬體能供性修改的輸入,例如畫筆桶按鈕、滑鼠右鍵或類似專案。 不過,使用者通常會預期這些次要能供性會有一些額外的功能或修改行為。
在某些情況下,您可能也需要公開沒有次要能供性之手寫筆的額外功能(通常與手寫筆提示無關的功能)、其他輸入裝置類型,或根據應用程式 UI 中用戶選取的一些修改行為類型。
若要支援此功能, 可以設定 InkPresenter 讓特定輸入保持未處理。 接著,這個未處理的輸入會傳遞至您的應用程式進行處理。
範例 - 使用未處理的輸入來實作筆劃選取
Windows Ink 平臺不提供需要修改輸入之動作的內建支援,例如筆劃選取。 若要支援這類功能,您必須在應用程式中提供自定義解決方案。
下列程式代碼範例(所有程序代碼都在 MainPage.xaml 和 MainPage.xaml.cs 檔案中)逐步說明如何在輸入使用畫筆桶按鈕(或滑鼠右鍵)修改輸入時啟用筆劃選取。
首先,我們在 MainPage.xaml 中設定 UI。
在這裡,我們會新增畫布(在 InkCanvas 下方)繪製選取筆劃。 使用個別圖層繪製選取範圍筆劃,會讓 InkCanvas 及其內容保持不變。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel x:Name="HeaderPanel" Orientation="Horizontal" Grid.Row="0"> <TextBlock x:Name="Header" Text="Advanced ink customization sample" VerticalAlignment="Center" Style="{ThemeResource HeaderTextBlockStyle}" Margin="10,0,0,0" /> </StackPanel> <Grid Grid.Row="1"> <!-- Canvas for displaying selection UI. --> <Canvas x:Name="selectionCanvas"/> <!-- Inking area --> <InkCanvas x:Name="inkCanvas"/> </Grid> </Grid>
在MainPage.xaml.cs中,我們會宣告幾個全域變數,以保留選取 UI 層面的參考。 具體來說,選取套索筆劃和醒目提示選取筆劃的周框。
// Stroke selection tool. private Polyline lasso; // Stroke selection area. private Rect boundingRect;
接下來,我們將 InkPresenter 設定為將畫筆和滑鼠的輸入數據解譯為筆墨筆劃,並設定一些用於將筆劃轉譯為 InkCanvas 的初始筆墨筆劃屬性。
最重要的是,我們使用 InkPresenter 的 InputProcessingConfiguration 屬性來指出應用程式應該處理任何修改過的輸入。 修改的輸入是指派 InputProcessingConfiguration.RightDragAction InkInputRightDragAction.LeaveUnprocessed 的值來指定。 設定此值時, InkPresenter 會傳遞至 InkUnprocessedInput 類別,這是一組指標事件,可供您處理。
我們會為未處理的 PointerPressed、PointerMoved 和 PointerReleased 事件指派接聽程式,這些事件是由 InkPresenter 傳遞。 所有選取功能都會在這些事件的處理程序中實作。
最後,我們會為 InkPresenter 的 StrokeStarted 和 StrokesErased 事件指派接聽程式。 如果啟動新的筆劃或清除現有的筆劃,我們會使用這些事件的處理程式來清除選取UI。
public MainPage() { this.InitializeComponent(); // Set supported inking device types. inkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen; // Set initial ink stroke attributes. InkDrawingAttributes drawingAttributes = new InkDrawingAttributes(); drawingAttributes.Color = Windows.UI.Colors.Black; drawingAttributes.IgnorePressure = false; drawingAttributes.FitToCurve = true; inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes); // By default, the InkPresenter processes input modified by // a secondary affordance (pen barrel button, right mouse // button, or similar) as ink. // To pass through modified input to the app for custom processing // on the app UI thread instead of the background ink thread, set // InputProcessingConfiguration.RightDragAction to LeaveUnprocessed. inkCanvas.InkPresenter.InputProcessingConfiguration.RightDragAction = InkInputRightDragAction.LeaveUnprocessed; // Listen for unprocessed pointer events from modified input. // The input is used to provide selection functionality. inkCanvas.InkPresenter.UnprocessedInput.PointerPressed += UnprocessedInput_PointerPressed; inkCanvas.InkPresenter.UnprocessedInput.PointerMoved += UnprocessedInput_PointerMoved; inkCanvas.InkPresenter.UnprocessedInput.PointerReleased += UnprocessedInput_PointerReleased; // Listen for new ink or erase strokes to clean up selection UI. inkCanvas.InkPresenter.StrokeInput.StrokeStarted += StrokeInput_StrokeStarted; inkCanvas.InkPresenter.StrokesErased += InkPresenter_StrokesErased; }
接著,我們會定義 InkPresenter 所傳遞之未處理 PointerPressed、PointerMoved 和 PointerReleased 事件的處理程式。
所有選取功能都會在這些處理程式中實作,包括套索筆劃和周框。
// Handle unprocessed pointer events from modified input. // The input is used to provide selection functionality. // Selection UI is drawn on a canvas under the InkCanvas. private void UnprocessedInput_PointerPressed( InkUnprocessedInput sender, PointerEventArgs args) { // Initialize a selection lasso. lasso = new Polyline() { Stroke = new SolidColorBrush(Windows.UI.Colors.Blue), StrokeThickness = 1, StrokeDashArray = new DoubleCollection() { 5, 2 }, }; lasso.Points.Add(args.CurrentPoint.RawPosition); selectionCanvas.Children.Add(lasso); } private void UnprocessedInput_PointerMoved( InkUnprocessedInput sender, PointerEventArgs args) { // Add a point to the lasso Polyline object. lasso.Points.Add(args.CurrentPoint.RawPosition); } private void UnprocessedInput_PointerReleased( InkUnprocessedInput sender, PointerEventArgs args) { // Add the final point to the Polyline object and // select strokes within the lasso area. // Draw a bounding box on the selection canvas // around the selected ink strokes. lasso.Points.Add(args.CurrentPoint.RawPosition); boundingRect = inkCanvas.InkPresenter.StrokeContainer.SelectWithPolyLine( lasso.Points); DrawBoundingRect(); }
為了結束 PointerReleased 事件處理程式,我們會清除所有內容的選取層(套索筆劃),然後在套索區域所包含的筆墨筆劃周圍繪製單一周框。
// Draw a bounding rectangle, on the selection canvas, encompassing // all ink strokes within the lasso area. private void DrawBoundingRect() { // Clear all existing content from the selection canvas. selectionCanvas.Children.Clear(); // Draw a bounding rectangle only if there are ink strokes // within the lasso area. if (!((boundingRect.Width == 0) || (boundingRect.Height == 0) || boundingRect.IsEmpty)) { var rectangle = new Rectangle() { Stroke = new SolidColorBrush(Windows.UI.Colors.Blue), StrokeThickness = 1, StrokeDashArray = new DoubleCollection() { 5, 2 }, Width = boundingRect.Width, Height = boundingRect.Height }; Canvas.SetLeft(rectangle, boundingRect.X); Canvas.SetTop(rectangle, boundingRect.Y); selectionCanvas.Children.Add(rectangle); } }
最後,我們會定義 StrokeStarted 和 StrokesErased InkPresenter 事件的處理程式。
這兩者只要呼叫相同的清除函式,即可在偵測到新筆劃時清除目前的選取範圍。
// Handle new ink or erase strokes to clean up selection UI. private void StrokeInput_StrokeStarted( InkStrokeInput sender, Windows.UI.Core.PointerEventArgs args) { ClearSelection(); } private void InkPresenter_StrokesErased( InkPresenter sender, InkStrokesErasedEventArgs args) { ClearSelection(); }
以下是在啟動新筆劃或清除現有筆劃時,從選取畫布中移除所有選取專案 UI 的函式。
// Clean up selection UI. private void ClearSelection() { var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes(); foreach (var stroke in strokes) { stroke.Selected = false; } ClearDrawnBoundingRect(); } private void ClearDrawnBoundingRect() { if (selectionCanvas.Children.Any()) { selectionCanvas.Children.Clear(); boundingRect = Rect.Empty; } }
自定義筆跡轉譯
根據預設,筆跡輸入會在低延遲的背景線程上處理,並在繪製時呈現進行中或「濕潤」。 當筆劃完成時(手寫筆或手指隨即解除,或放開滑鼠按鈕),筆劃會在UI線程上進行處理,並將 「dry」 轉譯為 InkCanvas 層(在應用程式內容上方並取代濕墨)。
您可以覆寫此預設行為,並透過「自定義乾燥」濕墨筆劃完全控制筆跡體驗。 雖然預設行為通常足以用於大多數應用程式,但在某些情況下可能需要自定義乾燥,包括:
- 更有效率地管理大量或複雜的筆墨筆劃集合
- 在大型筆跡畫布上更有效率的移動瀏覽和縮放支援
- 交錯筆跡和其他物件,例如圖形或文字,同時維持迭置順序
- 將筆跡同步乾燥並轉換成 DirectX 圖形(例如直線或圖形點陣化,並整合到應用程式內容中,而不是作為個別 InkCanvas 層)。
自定義乾燥需要 IInkD2DRenderer 物件來管理筆跡輸入,並將其轉譯為通用 Windows 應用程式的 Direct2D 裝置內容,而不是預設 InkCanvas 控件。
藉由呼叫 ActivateCustomDrying (在載入 InkCanvas 之前),應用程式會建立 InkSynchronizer 物件,以自定義筆墨筆劃如何讓 SurfaceImageSource 或 VirtualSurfaceImageSource 乾燥轉譯。
SurfaceImageSource 和 VirtualSurfaceImageSource 都提供 DirectX 共用介面,讓您的 app 能夠繪製並撰寫至應用程式的內容,不過 VSIS 提供比螢幕更大的虛擬表面來進行高效能移動瀏覽和縮放。 由於這些介面的視覺更新會與 XAML UI 線程同步處理,因此當筆跡轉譯為任一時,可以同時從 InkCanvas 中移除濕墨。
您也可以將干墨自定義為 SwapChainPanel,但與 UI 線程的同步處理並不保證,而且當筆跡轉譯至 SwapChainPanel,以及從 InkCanvas 移除筆跡時,可能會有延遲。
如需這項功能的完整範例,請參閱 複雜筆跡範例。
注意
自訂乾燥和 InkToolbar
如果您的 app 使用自定義乾燥實作覆寫 InkPresenter 的預設筆跡轉譯行為,則 InkToolbar 的轉譯筆墨筆劃已不再可供 InkToolbar 使用,而且 InkToolbar 的內建清除命令無法如預期般運作。 若要提供清除功能,您必須處理所有指標事件、在每個筆劃上執行點擊測試,並覆寫內建的「清除所有筆跡」命令。
本節中的其他文章
主題 | 說明 |
---|---|
辨識筆墨筆劃 | 使用手寫辨識將筆跡筆劃轉換成文字,或使用自定義辨識將筆跡筆劃轉換成圖形。 |
儲存和擷取筆墨筆劃 | 使用內嵌的筆跡串行化格式 (ISF) 元數據,將筆跡筆劃數據儲存在圖形交換格式 (GIF) 檔案中。 |
將 InkToolbar 新增至 Windows 筆跡應用程式 | 將預設 InkToolbar 新增至 Windows 應用程式筆跡應用程式、將自定義畫筆按鈕新增至 InkToolbar,並將自定義畫筆按鈕系結至自定義畫筆定義。 |
相關文章
API
範例
- 開始使用教學課程:在您的 Windows 應用程式中支援筆跡
- 簡單筆跡範例 (C#/C++)
- 複雜的筆跡樣本 (C++)
- 筆跡範例 (JavaScript)
- 著色書籍範例
- 家庭筆記範例
- 基本輸入範例
- 低延遲輸入範例
- 用戶互動模式範例
- 焦點視覺效果範例 \(英文\)
封存範例
意見反應
https://aka.ms/ContentUserFeedback.
Coming soon: Throughout 2024 we will be phasing out GitHub Issues as the feedback mechanism for content and replacing it with a new feedback system. For more information see:提交並檢視相關的意見反應