Compartilhar via


Interações com caneta e Windows Ink em aplicativos do Windows

Imagem principal da Caneta para Surface.
Caneta Surface (disponível para aquisição na Microsoft Store).

Visão geral

Otimize seu aplicativo do Windows para entrada de caneta para fornecer a funcionalidade padrão do dispositivo de ponteiro e a melhor experiência do Windows Ink para seus usuários.

Observação

Este tópico se concentra na plataforma Windows Ink. Para obter o tratamento geral de entrada de ponteiro (semelhante ao mouse, toque e touchpad), consulte Manipular entrada de ponteiro.

Usando tinta em seu aplicativo do Windows

Usar a Caneta e o Ink do Windows para criar aplicativos corporativos mais envolventes

A plataforma Windows Ink, juntamente com um dispositivo de caneta, fornece uma maneira natural de criar anotações manuscritas digitais, desenhos e anotações. A plataforma suporta a captura de entrada do digitalizador como dados de tinta, geração de dados de tinta, gerenciamento de dados de tinta, renderização de dados de tinta como traços de tinta no dispositivo de saída e conversão de tinta em texto por meio do reconhecimento de manuscrito.

Além de capturar a posição básica e o movimento da caneta à medida que o usuário escreve ou desenha, seu aplicativo também pode rastrear e coletar as quantidades variáveis de pressão usadas ao longo de um traço. Essas informações, juntamente com as configurações de formato, tamanho e rotação da ponta da caneta, cor da tinta e finalidade (tinta comum, apagamento, realce e seleção), permitem que você forneça experiências de usuário que se assemelham a escrever ou desenhar no papel com uma caneta, lápis ou pincel.

Observação

Seu aplicativo também pode dar suporte à entrada de tinta de outros dispositivos baseados em ponteiro, incluindo digitalizadores de toque e dispositivos de mouse. 

A plataforma de tinta é muito flexível. Ele foi projetado para suportar vários níveis de funcionalidade, dependendo de seus requisitos.

Para obter diretrizes de experiência do usuário do Windows Ink, consulte Controles de escrita à tinta.

Componentes da plataforma do Windows Ink

Componente Descrição
InkCanvas Um controle de plataforma de interface do usuário XAML que, por padrão, recebe e exibe todas as entradas de uma caneta como um traço de tinta ou um traço de apagamento.
Para obter mais informações sobre como usar o InkCanvas, consulte Reconhecer traços do Windows Ink como texto e Armazenar e recuperar dados de traço do Windows Ink.
InkPresenter Um objeto code-behind, instanciado em conjunto com um controle InkCanvas (exposto por meio da propriedade InkCanvas.InkPresenter). Esse objeto fornece toda a funcionalidade padrão de escrita à tinta exposta pelo InkCanvas, juntamente com um conjunto abrangente de APIs para personalização e customização adicionais.
Para obter mais informações sobre como usar o InkPresenter, consulte Reconhecer traços do Windows Ink como texto e Armazenar e recuperar dados de traço do Windows Ink.
InkToolbar Um controle de plataforma de interface do usuário XAML que contém uma coleção personalizável e extensível de botões que ativam recursos relacionados à tinta em um InkCanvas associado.
Para obter mais informações sobre como usar o InkToolbar, consulte Adicionar um InkToolbar a um aplicativo de escrita à tinta do aplicativo Windows.
IInkD2DRenderer Habilita a renderização de traços de tinta no contexto do dispositivo Direct2D designado de um aplicativo Universal do Windows, em vez do controle padrão InkCanvas. Isso permite a personalização completa da experiência de escrita à tinta.
Para obter mais informações, consulte o Exemplo de tinta complexa.

Escrita à tinta básica com InkCanvas

Para adicionar a funcionalidade básica de escrita à tinta, basta colocar um controle de plataforma UWP InkCanvas na página apropriada em seu aplicativo.

Por padrão, o InkCanvas dá suporte à entrada de tinta somente de uma caneta. A entrada é renderizada como um traço de tinta usando as configurações padrão de cor e espessura (uma caneta esferográfica preta com uma espessura de 2 pixels) ou tratada como uma borracha de traço (quando a entrada é de uma ponta de borracha ou a ponta da caneta é modificada com um botão de apagar).

Observação

Se uma ponta ou botão de borracha não estiver presente, o InkCanvas poderá ser configurado para processar a entrada da ponta da caneta como um traço de apagamento.

Neste exemplo, um InkCanvas sobrepõe uma imagem de plano de fundo.

Observação

Um InkCanvas tem propriedades padrão Height e Width de zero, a menos que seja filho de um elemento que dimensiona automaticamente seus elementos filho, como controles StackPanel ou 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>

Esta série de imagens mostra como a entrada de caneta é renderizada por esse controle InkCanvas .

Captura de tela do InkCanvas em branco com uma imagem de plano de fundo. Captura de tela do InkCanvas com traços de tinta. Captura de tela do InkCanvas com um traço apagado.
O InkCanvas em branco com uma imagem de plano de fundo. O InkCanvas com traços de tinta. O InkCanvas com um traço apagado (observe como apagar opera em um traço inteiro, não em uma parte).

A funcionalidade de escrita à tinta com suporte do controle InkCanvas é fornecida por um objeto code-behind chamado InkPresenter.

Para tinta básica, você não precisa se preocupar com o InkPresenter. No entanto, para personalizar e configurar o comportamento de escrita à tinta no InkCanvas, você deve acessar seu objeto InkPresenter correspondente.

Personalização básica com InkPresenter

Um objeto InkPresenter é instanciado com cada controle InkCanvas.

Observação

O InkPresenter não pode ser instanciado diretamente. Em vez disso, ele é acessado por meio da propriedade InkPresenter do InkCanvas

Além de fornecer todos os comportamentos de escrita à tinta padrão de seu controle InkCanvas correspondente, o InkPresenter fornece um conjunto abrangente de APIs para personalização de traço adicional e gerenciamento mais refinado da entrada da caneta (padrão e modificada). Isso inclui propriedades de traço, tipos de dispositivo de entrada com suporte e se a entrada é processada pelo objeto ou passada para o aplicativo para processamento.

Observação

A entrada de tinta padrão (da ponta da caneta ou da ponta/botão da borracha) não é modificada com uma funcionalidade de hardware secundária, como um botão de barril de caneta, botão direito do mouse ou mecanismo semelhante.

Por padrão, a tinta é suportada apenas para entrada de caneta. Aqui, configuramos o InkPresenter para interpretar dados de entrada da caneta e do mouse como traços de tinta. Também definimos alguns atributos de traço de tinta inicial usados para renderizar traços para o InkCanvas.

Para habilitar a escrita à tinta com mouse e touch, defina a propriedade InputDeviceTypes do InkPresenter como a combinação de valores CoreInputDeviceTypes que deseja.

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);
}

Os atributos de traço de tinta podem ser definidos dinamicamente para acomodar as preferências do usuário ou os requisitos do aplicativo.

Aqui, permitimos que um usuário escolha em uma lista de cores de tinta.

<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>

Em seguida, lidamos com as alterações na cor selecionada e atualizamos os atributos do traço de tinta de acordo.

// 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);
    }
}

Essas imagens mostram como a entrada de caneta é processada e personalizada pelo InkPresenter.

Captura de tela que mostra o InkCanvas com traços de tinta preta padrão.

O InkCanvas com traços de tinta preta padrão.

Captura de tela do InkCanvas com traços de tinta vermelha selecionados pelo usuário.

O InkCanvas com traços de tinta vermelha selecionados pelo usuário.

Para fornecer funcionalidade além da escrita à tinta e do apagamento, como seleção de traço, seu aplicativo deve identificar uma entrada específica para que o InkPresenter passe não processado para tratamento pelo aplicativo.

Entrada de passagem para processamento avançado

Por padrão, o InkPresenter processa todas as entradas como um traço de tinta ou um traço de apagamento, incluindo a entrada modificada por uma funcionalidade de hardware secundária, como um botão de barril de caneta, um botão direito do mouse ou semelhante. No entanto, os usuários normalmente esperam alguma funcionalidade adicional ou comportamento modificado com essas funcionalidades secundárias.

Em alguns casos, talvez você também precise expor funcionalidades adicionais para canetas sem funcionalidades secundárias (funcionalidade geralmente não associada à ponta da caneta), outros tipos de dispositivo de entrada ou algum tipo de comportamento modificado com base em uma seleção de usuário na interface do usuário do aplicativo.

Para dar suporte a isso, o InkPresenter pode ser configurado para deixar uma entrada específica não processada. Essa entrada não processada é então passada para seu aplicativo para processamento.

Exemplo - Usar entrada não processada para implementar a seleção de traçado

A plataforma Windows Ink não fornece suporte interno para ações que exigem entrada modificada, como seleção de traço. Para dar suporte a recursos como esse, você deve fornecer uma solução personalizada em seus aplicativos.

O exemplo de código a seguir (todo o código está nos arquivos MainPage.xaml e MainPage.xaml.cs) explica como habilitar a seleção de traço quando a entrada é modificada com um botão de barril de caneta (ou botão direito do mouse).

  1. Primeiro, configuramos a interface do usuário em MainPage.xaml.

    Aqui, adicionamos uma tela (abaixo do InkCanvas) para desenhar o traço de seleção. Usar uma camada separada para desenhar o traço de seleção deixa o InkCanvas e seu conteúdo intocados.

    Captura de tela do InkCanvas em branco com uma tela de seleção subjacente.

      <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. Em MainPage.xaml.cs, declaramos algumas variáveis globais para manter referências a aspectos da interface do usuário de seleção. Especificamente, o traço de laço de seleção e o retângulo delimitador que realça os traços selecionados.

      // Stroke selection tool.
      private Polyline lasso;
      // Stroke selection area.
      private Rect boundingRect;
    
  3. Em seguida, configuramos o InkPresenter para interpretar dados de entrada da caneta e do mouse como traços de tinta e definimos alguns atributos iniciais de traço de tinta usados para renderizar traços para o InkCanvas.

    Mais importante ainda, usamos a propriedade InputProcessingConfiguration do InkPresenter para indicar que qualquer entrada modificada deve ser processada pelo aplicativo. A entrada modificada é especificada atribuindo a InputProcessingConfiguration.RightDragAction um valor de InkInputRightDragAction.LeaveUnprocessed. Quando esse valor é definido, o InkPresenter passa para a classe InkUnprocessedInput , um conjunto de eventos de ponteiro para você manipular.

    Atribuímos ouvintes para os eventos PointerPressed, PointerMoved e PointerReleased não processados passados pelo InkPresenter. Toda a funcionalidade de seleção é implementada nos manipuladores para esses eventos.

    Por fim, atribuímos ouvintes para os eventos StrokeStarted e StrokesErased do InkPresenter. Usamos os manipuladores desses eventos para limpar a interface do usuário de seleção se um novo traço for iniciado ou um traço existente for apagado.

    Captura de tela do aplicativo de exemplo de personalização de tinta avançada mostrando o inkcanvas com traços de tinta preta padrão.

      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. Em seguida, definimos manipuladores para os eventos PointerPressed, PointerMoved e PointerReleased não processados passados pelo InkPresenter.

    Toda a funcionalidade de seleção é implementada nesses manipuladores, incluindo o traço de laço e o retângulo delimitador.

    Captura de tela do laço de seleção.

      // 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. Para concluir o manipulador de eventos PointerReleased, limpamos a camada de seleção de todo o conteúdo (o traço de laço) e, em seguida, desenhamos um único retângulo delimitador ao redor dos traços de tinta englobados pela área de laço.

    Captura de tela do retângulo delimitador da seleção.

      // 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. Por fim, definimos manipuladores para os eventos StrokeStarted e StrokesErased InkPresenter.

    Ambos apenas chamam a mesma função de limpeza para limpar a seleção atual sempre que um novo traço é detectado.

      // 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. Aqui está a função para remover toda a interface do usuário de seleção da tela de seleção quando um novo traço é iniciado ou um traço existente é apagado.

      // 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;
        }
      }
    

Renderização de tinta personalizada

Por padrão, a entrada de tinta é processada em um thread em segundo plano de baixa latência e renderizada em andamento, ou "úmida", à medida que é desenhada. Quando o traço é concluído (caneta ou dedo levantado ou botão do mouse liberado), o traço é processado no thread da IU e renderizado “seco” para a camada do InkCanvas (acima do conteúdo do aplicativo e substituindo a tinta molhada).

Você pode substituir esse comportamento padrão e controlar completamente a experiência de escrita à tinta "secando personalizada" os traços de tinta úmida. Embora o comportamento padrão seja normalmente suficiente para a maioria das aplicações, há alguns casos em que a secagem personalizada pode ser necessária, incluindo:

  • Gerenciamento mais eficiente de coleções grandes ou complexas de traços de tinta
  • Suporte a panorâmica e zoom mais eficientes em telas de tinta grandes
  • Intercalar tinta e outros objetos, como formas ou texto, mantendo a ordem z
  • Secar e converter tinta de forma síncrona em uma forma DirectX (por exemplo, uma linha reta ou forma rasterizada e integrada ao conteúdo do aplicativo em vez de como uma camada InkCanvas separada).

A secagem personalizada requer um objeto IInkD2DRenderer para gerenciar a entrada de tinta e renderizá-la no contexto do dispositivo Direct2D do seu aplicativo Universal do Windows, em vez do controle InkCanvas padrão.

Ao chamar ActivateCustomDrying (antes que o InkCanvas seja carregado), um aplicativo cria um objeto InkSynchronizer para personalizar como um traço de tinta é renderizado seco para um SurfaceImageSource ou VirtualSurfaceImageSource.

SurfaceImageSource e VirtualSurfaceImageSource fornecem uma superfície compartilhada do DirectX para seu aplicativo desenhar e compor no conteúdo do aplicativo, embora o VSIS forneça uma superfície virtual maior que a tela para movimento panorâmico e zoom de alto desempenho. Como as atualizações visuais para essas superfícies são sincronizadas com o thread da interface do usuário XAML, quando a tinta é renderizada para qualquer uma delas, a tinta úmida pode ser removida do InkCanvas simultaneamente.

Você também pode personalizar a tinta seca para um SwapChainPanel, mas a sincronização com o thread da interface do usuário não é garantida e pode haver um atraso entre o momento em que a tinta é renderizada para o SwapChainPanel e o momento em que a tinta é removida do InkCanvas.

Para obter um exemplo completo dessa funcionalidade, consulte o Exemplo de tinta complexa.

Observação

Secagem personalizada e o InkToolbar
Se seu aplicativo substituir o comportamento padrão de renderização de tinta do InkPresenter por uma implementação de secagem personalizada, os traços de tinta renderizados não estarão mais disponíveis para o InkToolbar e os comandos internos de borracha do InkToolbar não funcionarão conforme o esperado. Para fornecer a funcionalidade de borracha, você deve manipular todos os eventos de ponteiro, executar testes de clique em cada traço e substituir o comando interno “Apagar toda a tinta”.

Tópico Descrição
Reconhecer traços de tinta Converta traços de tinta em texto usando o reconhecimento de manuscrito ou em formas usando o reconhecimento personalizado.
Armazenar e recuperar traços de tinta Armazene dados de traçado de tinta em um arquivo GIF (Graphics Interchange Format) usando metadados ISF (Ink Serialized Format) incorporados.
Adicionar um InkToolbar a um aplicativo de escrita à tinta do Windows Adicione um InkToolbar padrão a um aplicativo de escrita à tinta de um aplicativo do Windows, adicione um botão de caneta personalizada ao InkToolbar e vincule o botão de caneta personalizada a uma definição de caneta personalizada.

APIs

Amostras

Exemplos de arquivos