Windows 應用程式中的手寫筆互動與 Windows Ink

Hero image of the Surface Pen.
Surface 手寫筆 (可在 Microsoft Store 購買)。

概觀

針對筆輸入最佳化 Windows 應用程式,為使用者提供標準指標裝置功能和最佳 Windows Ink 體驗。

注意

本主題著重於 Windows Ink 平台。 如需一般指標輸入處理 (類似於滑鼠、觸控和觸控板),請參閱處理指標輸入

在 Windows 應用程式中使用筆跡

使用 Windows Pen 和 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 的預設 HeightWidth 屬性為零,除非它是自動調整其子元素大小的元素的子元素,例如 StackPanelGrid 控制項。

<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 控制項如何呈現手寫筆輸入。

Screenshot of the blank InkCanvas with a background image. Screenshot of the InkCanvas with ink strokes. Screenshot of the InkCanvas with one stroke erased.
帶有背景影像的空白 InkCanvas 具有筆跡筆畫的 InkCanvas 擦除一筆畫的 InkCanvas (請注意擦除如何對整個筆畫而不是一部分進行操作)。

InkCanvas 控制項支援的筆跡書寫功能由稱為 InkPresenter 的程式碼隱藏物件提供。

對於基本筆跡,您不需要擔心 InkPresenter。 但是,要在 InkCanvas 上自訂和設定筆跡書寫行為,您必須存取其對應的 InkPresenter 物件。

使用 InkPresenter 進行基本自訂

InkPresenter 物件是使用每個 InkCanvas 控制項執行個體化的。

注意

InkPresenter 無法直接具現化。 相反,它是透過 InkCanvasInkPresenter 屬性存取的。 

除了提供其對應 InkCanvas 控制項的所有預設筆跡書寫行為外,InkPresenter 還提供了一組全面的 API,用於額外的筆畫自訂和筆輸入的更細粒度管理 (標準和修改)。 這包括筆劃屬性、支援的輸入裝置類型,以及對象處理輸入,還是傳遞至應用程式進行處理。

注意

標準筆跡輸入 (筆尖或橡皮擦尖/按鈕) 不會使用輔助硬體功能修改,例如:筆身按鈕、滑鼠右鍵或類似機制。

根據預設,只支援手寫筆輸入的筆跡。 在這裡,我們配置 InkPresenter 將來自筆和滑鼠的輸入資料解釋為筆跡筆畫。 我們還設定了一些初始筆跡筆畫屬性,用於將筆畫轉譯到 InkCanvas

若要啟用滑鼠和觸控筆跡書寫,請將 InkPresenterInputDeviceTypes 屬性設定為所需的 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 如何處理和自訂手寫筆輸入。

Screenshot that shows the InkCanvas with default black ink strokes.

具有預設黑色筆墨筆劃的 InkCanvas

Screenshot of the InkCanvas with user selected red ink strokes.

InkCanvas,其中包含使用者選取的紅色筆墨筆劃。

為了提供筆跡書寫和擦除以外的功能 (例如筆畫選擇),您的應用程式必須識別 InkPresenter 的特定輸入,以便未經處理地傳遞給您的應用程式進行處理。

進階處理的傳遞輸入

根據預設,InkPresenter 會將所有輸入視為筆墨筆劃或清除筆劃來處理,包括次要硬體能供性修改的輸入,例如畫筆桶按鈕、滑鼠右鍵或類似專案。 然而,使用者通常期望透過這些次要功能獲得一些額外的功能或修改的行為。

在某些情況下,您可能還需要公開沒有輔助功能的筆的附加功能 (通常與筆尖不相關的功能)、其他輸入裝置類型或基於應用程式 UI 中的使用者選擇的某種類型的修改行為。

為了支援這一點,可以將 InkPresenter 配置為不處理特定輸入。 然後,此未處理的輸入將傳遞到您的應用程式進行處理。

範例 - 使用未處理的輸入來實現筆畫選擇

Windows Ink 平台不提供需要修改輸入的操作 (例如筆畫選擇) 的內建支援。 若要支援此類功能,您必須在應用程式中提供自訂解決方案。

以下程式碼範例 (所有程式碼都在 MainPage.xaml 和 MainPage.xaml.cs 檔案中) 逐步示範如何在使用筆筒按鈕 (或滑鼠右鍵) 修改輸入時啟用筆畫選擇。

  1. 首先,我們在 MainPage.xaml 中設定 UI。

    在這裡,我們會新增畫布 (在 InkCanvas 下方) 繪製選取筆劃。 使用個別圖層繪製選取的筆劃可確保 InkCanvas 及其內容保持不變。

    Screenshot of the blank InkCanvas with an underlying selection canvas.

      <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>
    
  2. 在 MainPage.xaml.cs 中,我們會宣告幾個全域變數,以保留選取 UI 層面的參考。 具體來說,選取套索筆劃和醒目提示選取筆劃的周框。

      // Stroke selection tool.
      private Polyline lasso;
      // Stroke selection area.
      private Rect boundingRect;
    
  3. 接下來,我們配置 InkPresenter 將來自筆和滑鼠的輸入資料解釋為筆跡筆劃,並設定一些用於將筆畫轉譯到 InkCanvas 的初始筆跡筆畫屬性。

    最重要的是,我們使用 InkPresenterInputProcessingConfiguration 屬性來指出應用程式應該處理任何修改過的輸入。 透過為 InputProcessingConfiguration.RightDragAction 指派 InkInputRightDragAction.LeaveUnprocessed 的值來指定已修改的輸入。 設定此值後,InkPresenter 會傳遞到 InkUnprocessedInput 類,這是一組供您處理的指標事件。

    我們為 InkPresenter 傳遞的未處理的 PointerPressedPointerMovedPointerReleased 事件指派偵聽器。 所有選擇功能都在這些事件的處理程序中實作。

    最後,我們為 InkPresenterStrokeStartedStrokesErased 事件指派偵聽器。 如果開始新筆劃或刪除現有筆劃,我們將使用這些事件的處理程序來清理選擇 UI。

    Screenshot of the Advances ink customization sample app showing the inkcanvas with default black ink strokes.

      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;
      }
    
  4. 然後,我們為 InkPresenter 傳遞的未處理的 PointerPressedPointerMovedPointerReleased 事件定義處理程序。

    所有選取功能都會在這些處理程式中實作,包括套索筆劃和周框。

    Screenshot of the selection lasso.

      // 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();
        }
    
  5. 為了結束 PointerReleased 事件處理程序,我們清除所有內容 (套索筆畫) 的選擇層,然後在套索區域所包圍的筆跡筆畫周圍繪製單個邊界矩形。

    Screenshot of the selection bounding rect.

      // 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);
          }
        }
    
  6. 最後,我們定義 StrokeStartedStrokesErased 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();
      }
    
  7. 以下是在啟動新筆劃或清除現有筆劃時,從選取畫布中移除所有選取專案 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 執行緒中處理筆劃,並將「原始」轉譯到 InkCanvas 層 (替換應用程式內容上方的已修改筆跡)。

您可以覆寫此預設行為,並透過「自訂乾燥」濕墨筆劃完全控制筆跡體驗。 雖然預設行為通常足以用於大多數應用程式,但在某些情況下可能需要自訂乾燥,包括:

  • 更有效率地管理大量或複雜的筆墨筆劃集合
  • 在大型筆跡畫布上更有效率的移動瀏覽和縮放支援
  • 交錯筆跡和其他物件,例如圖形或文字,同時維持迭置順序
  • 將筆跡同步乾燥並轉換為 DirectX 形狀 (例如,光柵化的直線或形狀並整合到應用程式內容中,而不是作為單獨的 InkCanvas 層)。

自訂乾燥需要 IInkD2DRenderer 物件來管理筆跡輸入,並將其轉譯到通用 Windows 應用程式的 Direct2D 裝置內容,而不是預設的 InkCanvas 控制項。

透過呼叫 ActivateCustomDrying (在載入 InkCanvas 之前),應用程式會建立一個 InkSynchronizer 物件來自訂如何將筆跡筆畫轉譯到 SurfaceImageSourceVirtualSurfaceImageSource

SurfaceImageSourceVirtualSurfaceImageSource 都提供了一個 DirectX 共享表面,供您的應用程式繪製和組合到應用程式的內容中,儘管 VSIS 提供了一個比螢幕更大的虛擬表面,以實現高效能平移和縮放。 由於這些介面的視覺更新會與 XAML UI 執行緒同步處理,因此當筆跡轉譯為任一時,可以同時從 InkCanvas 中移除濕墨。

您也可以將乾墨自訂到 SwapChainPanel,但無法保證與 UI 執行緒的同步,並且在將筆跡轉譯到 SwapChainPanel 和從 InkCanvas 移除筆跡之間可能存在延遲。

如需這項功能的完整範例,請參閱複雜筆跡範例

注意

自訂原始和 InkToolbar
如果應用程式使用自訂原始實作覆寫 InkPresenter 的預設筆跡轉譯行為,則轉譯筆墨筆劃已不再供 InkToolbar 使用,而且 InkToolbar 的內建擦除命令無法如預期工作。 若要提供擦除功能,必須處理所有指標事件、對每個筆劃執行點擊測試,並覆寫內建的「擦除所有筆跡」命令。

主題 說明
識別墨蹟筆劃 使用手寫辨識將筆跡筆劃轉換成文字,或使用自訂辨識將筆跡筆劃轉換成圖形。
存儲和檢索墨蹟筆劃 使用嵌入的筆跡序列化格式 (ISF) 元資料將筆跡筆畫資料儲存在圖形交換格式 (GIF) 檔案中。
將 InkToolbar 新增至 Windows 筆跡應用程式 將預設 InkToolbar 新增至 Windows 應用程式筆跡應用程式、將自訂畫筆按鈕新增至 InkToolbar,並將自訂畫筆按鈕繫結至自訂畫筆定義。

API

範例

封存範例