Windows 应用中的笔交互和 Windows Ink

Surface 触控笔的英雄图像。
Surface 触控笔(可在 Microsoft Store 购买)。

概述

针对笔输入优化 Windows 应用,以便为用户同时提供标准的指针设备功能和最佳的 Windows Ink 体验

注意

本主题重点介绍 Windows Ink 平台。 有关常规指针输入处理(类似于鼠标、触摸和触摸板),请参阅 “处理指针输入”。

在 Windows 应用中使用墨迹

使用 Windows 触控笔和墨迹生成更具吸引力的企业应用

Windows Ink 平台与笔设备一起提供了创建数字手写笔记、绘图和批注的自然方法。 该平台支持将数字化器输入捕获为墨迹数据、生成墨迹数据、管理墨迹数据、将墨迹数据呈现为输出设备上的墨迹笔划,以及通过手写识别将墨迹转换为文本。

除了在用户写入或绘图时捕获笔的基本位置和移动之外,你的应用还可以跟踪和收集整个笔划中使用的不同压力量。 此信息以及笔尖形状、大小和旋转、墨迹颜色和用途(纯墨迹、擦除、突出显示和选择)的设置,使你可以提供与使用笔、铅笔或画笔在纸上书写或绘图非常相似的用户体验。

注意

你的应用还可以支持来自其他基于指针的设备(包括触摸数字化器和鼠标设备)的墨迹输入。 

墨迹平台非常灵活。 它旨在支持各种级别的功能,具体取决于你的要求。

有关 Windows Ink UX 指南,请参阅 墨迹书写控件

Windows Ink 平台的组件

组件 说明
InkCanvas 默认情况下,XAML UI 平台控件接收来自笔的所有输入并将其显示为墨迹笔划或擦除笔划。
有关如何使用 InkCanvas 的详细信息,请参阅 将 Windows 墨迹笔划识别为文本存储和检索 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 控件如何呈现笔输入。

带有背景图像的空白 InkCanvas 的屏幕截图。 带有墨迹笔划的 InkCanvas 的屏幕截图。 擦除了一笔划的 InkCanvas 的屏幕截图。
带有背景图像的空白 InkCanvas 具有墨迹笔划的 InkCanvas 擦除了一个笔划的 InkCanvas (请注意擦除如何在整个笔划上操作,而不是一部分)。

InkCanvas 控件支持的墨迹书写功能由名为 InkPresenter 的代码隐藏对象提供。

对于基本墨迹书写,无需关注 InkPresenter 但是,若要在 InkCanvas自定义和配置墨迹书写行为,必须访问其相应的 InkPresenter 对象。

使用 InkPresenter 进行基本自定义

使用每个 InkCanvas 控件实例化 InkPresenter 对象。

注意

无法直接实例化 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 如何处理和自定义笔输入。

显示具有默认黑色墨迹笔划的 InkCanvas 的屏幕截图。

具有默认黑色墨迹笔划的 InkCanvas

InkCanvas 的屏幕截图,其中用户选择了红色墨迹笔划。

用户选择红色墨迹笔划的 InkCanvas

若要提供墨迹书写和擦除以外的功能(如笔划选择),你的应用必须标识 InkPresenter 的特定输入,以便通过未经处理的应用进行处理。

用于高级处理的直通输入

默认情况下, InkPresenter 将所有输入都作为墨迹笔划或擦除笔划进行处理,包括由辅助硬件提供(如笔桶按钮、鼠标右键或类似)修改的输入。 但是,用户通常期望使用这些辅助功能或修改行为。

在某些情况下,你可能还需要公开没有辅助功能(通常不与笔尖关联的功能)、其他输入设备类型或基于应用 UI 中的用户选择的某种类型的修改行为来公开笔的其他功能。

为了支持此功能, 可以将 InkPresenter 配置为保留未处理的特定输入。 然后,此未处理的输入将传递到应用进行处理。

示例 - 使用未处理的输入实现笔划选择

Windows Ink 平台不提供对需要修改输入的操作的内置支持,例如笔划选择。 若要支持此类功能,必须在应用中提供自定义解决方案。

下面的代码示例(所有代码都在 MainPage.xaml 和 MainPage.xaml.cs 文件中)逐步介绍如何在使用笔桶按钮(或鼠标右键)修改输入时启用笔划选择。

  1. 首先,我们在 MainPage.xaml 中设置 UI。

    在这里,我们添加画布(在 InkCanvas 下方)绘制选择笔划。 使用单独的层绘制选择笔划会使 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>
    
  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。

    “高级墨迹自定义”示例应用的屏幕截图,其中显示了带有默认黑色墨迹笔划的 inkcanvas。

      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 事件定义处理程序。

    所有选择功能都在这些处理程序中实现,包括套索笔划和边界矩形。

    选择套索的屏幕截图。

      // 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 事件处理程序,我们清除所有内容(套索笔划)的选择层,然后在套索区域包含的墨迹笔划周围绘制单个边界矩形。

    所选内容边界矩形的屏幕截图。

      // 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 层(替换应用程序内容上方的湿墨迹)。

你可以重写此默认行为,并通过“自定义干燥”湿墨笔划完全控制墨迹书写体验。 虽然默认行为通常足以用于大多数应用程序,但在某些情况下可能需要自定义干燥,其中包括:

  • 更高效地管理大量或复杂的墨迹笔划集合
  • 在大型墨迹画布上更有效地平移和缩放支持
  • 交错墨迹和其他对象,如形状或文本,同时保持 z 顺序
  • 将墨迹同步干燥并转换为 DirectX 形状(例如,直线或形状光栅化并集成到应用程序内容中,而不是作为单独的 InkCanvas 层)。

自定义干燥需要 IInkD2DRenderer 对象来管理墨迹输入,并将其呈现到通用 Windows 应用的 Direct2D 设备上下文,而不是默认的 InkCanvas 控件。

通过调用 ActivateCustomDrying(在加载 InkCanvas 之前),应用会创建 InkSynchronizer 对象,以自定义墨迹笔划如何干化到 SurfaceImageSourceVirtualSurfaceImageSource。

SurfaceImageSource VirtualSurfaceImageSource为应用提供了一个 DirectX 共享图面,用于绘制和撰写应用程序内容,尽管 VSIS 提供了一个比屏幕更大的虚拟图面,用于执行平移和缩放。 由于这些图面的视觉更新与 XAML UI 线程同步,在向其中的任意一个呈现墨迹时,可以同时从 InkCanvas 中删除未干墨迹。

还可以将干墨自定义为 SwapChainPanel,但无法保证与 UI 线程同步,并且当墨迹呈现到 SwapChainPanel 以及从 InkCanvas 中删除墨迹时,可能存在延迟。

有关此功能的完整示例,请参阅 复杂墨迹示例

注意

自定义干墨和 InkToolbar
如果应用修改了 InkPresenter 的自定义干燥渲染行为,则渲染的墨迹笔划不再可用于 InkToolbar,并且 InkToolbar 的内置擦除命令无法按预期工作。 要提供擦除功能,必须处理所有指针事件、对每个笔划执行命中测试,并重写内置的“擦除所有墨迹”命令。

主题 说明
识别墨迹笔划 使用手写识别将墨迹笔划转换为文本,或使用自定义识别将墨迹笔划转换为形状。
存储和检索墨迹笔划 使用嵌入式墨迹序列化格式(ISF)元数据在图形交换格式(GIF)文件中存储墨迹笔划数据。
将 InkToolbar 添加到 Windows 墨迹书写应用 将默认的 InkToolbar 添加到 Windows 应用墨迹书写应用、将自定义笔按钮添加到 InkToolbar,并将自定义笔按钮绑定到自定义笔定义。

API

示例

存档示例