Compartilhar via


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

Imagem principal da Caneta Surface.
Caneta Surface (disponível para compra na Microsoft Store).

Visão geral

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

Observação

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

Usando tinta em seu aplicativo do Windows

Usar a Caneta e a Tinta do Windows para criar aplicativos empresariais mais atraentes

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 dá suporte à 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 grava ou desenha, seu aplicativo também pode acompanhar e coletar as diferentes quantidades de pressão usadas ao longo de um traço. Essas informações, juntamente com as configurações de forma de ponta de caneta, tamanho e rotação, cor da tinta e finalidade (tinta simples, apagamento, realce e seleção), permitem que você forneça experiências do usuário que se assemelham à escrita ou desenho 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 dar suporte a vários níveis de funcionalidade, dependendo de seus requisitos.

Para obter diretrizes do Windows Ink UX, consulte Inking controls.

Componentes da plataforma Windows Ink

Componente Description
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 junto com um controle InkCanvas (exposto por meio da propriedade InkCanvas.InkPresenter ). Esse objeto fornece todas as funcionalidades padrão de escrita à tinta expostas pelo InkCanvas, além de um conjunto abrangente de APIs para customização e personalizaçã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 de dispositivo Direct2D designado de um aplicativo Universal do Windows, substituindo o 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.

Uso básico de tinta com o InkCanvas

Para adicionar a funcionalidade básica de escrita à tinta, basta colocar um controle de plataforma UWP do 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 configurações padrão para cor e espessura (uma caneta esferográfica preta com 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 apagamento).

Observação

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

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

Observação

Um InkCanvas tem propriedades padrão de Altura e Largura de zero, a menos que seja filho de um elemento que dimensiona automaticamente seus elementos filho, como os 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 mostrando traços de tinta. Captura de tela do InkCanvas com um traço apagado.
O InkCanvas vazio com uma imagem de plano de fundo. O InkCanvas com marcas de tinta. O InkCanvas com um traço foi apagado (observe como o apagamento opera sobre um traço inteiro, e não em uma parte).

A funcionalidade de escrita à tinta compatível com o controle InkCanvas é fornecida por um objeto code-behind chamado InkPresenter.

Para escrita à 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 o 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 modificado). 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 borracha) não é modificada por um recurso de hardware secundário, como um botão lateral da caneta, botão direito do mouse ou mecanismo semelhante.

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

Para habilitar a escrita por mouse e toque, defina a propriedade InputDeviceTypes do InkPresenter com a combinação de valores CoreInputDeviceTypes desejados.

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 entre 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, manipulamos as alterações na cor selecionada e atualizamos os atributos de traço de tinta adequadamente.

// 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 padrão de tinta preta.

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

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

Para fornecer funcionalidades além de escrita à tinta e apagamento, como a seleção de traço, seu aplicativo deve identificar entradas específicas para que o InkPresenter seja passado sem processamento, permitindo a manipulação por seu aplicativo.

Entrada direta para processamento avançado

Por padrão, o InkPresenter processa toda a entrada como um traço de tinta ou um traço de apagamento, incluindo a entrada modificada por recursos de hardware secundários, como um botão lateral da caneta, um botão direito do mouse ou similar. 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 recursos secundários (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 do usuário na interface do aplicativo.

Para dar suporte a isso, o InkPresenter pode ser configurado para deixar a 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ço

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çados 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 os 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, usamos a propriedade InputProcessingConfiguration do InkPresenter para indicar que qualquer entrada modificada deve ser processada pelo aplicativo. A entrada modificada é especificada atribuindo 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 não processados PointerPressed, PointerMoved e PointerReleased passados pelo InkPresenter. Todas as funcionalidades de seleção são implementadas nos manipuladores desses eventos.

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

    Captura de tela do aplicativo de exemplo de personalização de tinta Advances mostrando o InkCanvas com traços padrão de tinta preta.

      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.

    Todas as funcionalidades de seleção são implementadas 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 do laço) e desenhamos um único retângulo delimitador em torno dos traços de tinta englobados pela área do laço.

    Captura de tela do retângulo delimitador de 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 chamam apenas 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 de plano de fundo de baixa latência e renderizada em andamento ou "molhada", conforme é desenhada. Quando o traço é concluído (com a caneta ou dedo levantado, ou o botão do mouse liberado), ele é processado na thread específica da interface do usuário e então exibido como "seco" na camada InkCanvas (situada 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 por "secagem personalizada" dos traços de tinta molhada. Embora o comportamento padrão seja normalmente suficiente para a maioria dos aplicativos, há alguns casos em que a secagem personalizada pode ser necessária, estes incluem:

  • Gerenciamento mais eficiente de grandes ou complexos conjuntos de traços de tinta
  • Suporte mais eficiente para movimento panorâmico e zoom em telas de tinta grandes
  • Intercalando tinta e outros objetos, como formas ou texto, mantendo a ordem z
  • Secagem e conversão de 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.

Chamando ActivateCustomDrying (antes do InkCanvas ser carregado), um aplicativo cria um objeto InkSynchronizer para personalizar como um traço de tinta é renderizado seco para um SurfaceImageSource ou VirtualSurfaceImageSource.

Tanto o SurfaceImageSource quanto o VirtualSurfaceImageSource fornecem uma superfície compartilhada 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 deslocamento panorâmico e zoom com desempenho. Como as atualizações visuais dessas superfícies são sincronizadas com o thread da interface do usuário XAML, quando a tinta é renderizada para qualquer uma delas, a tinta molhada pode ser removida do InkCanvas simultaneamente.

Você também pode usar tinta seca personalizada em um SwapChainPanel, mas a sincronização com o thread da interface do usuário não é garantida e pode haver um atraso entre quando a tinta é renderizada para o SwapChainPanel e quando 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 o aplicativo substituir o comportamento padrão de renderização de tinta do InkPresenter com 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 de apagamento internos do InkToolbar não funcionarão conforme o esperado. Para fornecer funcionalidade de apagamento, você deve lidar com todos os eventos de ponteiro, executar o teste de clique em cada traço e substituir o comando interno "Apagar toda a tinta".

Tópico Description
Reconhecer traços de tinta Converter traços de tinta em texto usando reconhecimento de escrita manual ou em formas usando reconhecimento personalizado.
Armazenar e recuperar traços de tinta Armazene dados de traço de tinta em um arquivo GIF (Formato de Intercâmbio gráfico) usando metadados do ISF (Formato Serializado de Tinta) inseridos.
Adicionar um InkToolbar a um aplicativo de escrita à tinta do Windows Adicione um InkToolbar padrão a um aplicativo de escrita do aplicativo Windows, adicione um botão de caneta personalizado ao InkToolbar e associe o botão de caneta personalizada a uma definição de caneta personalizada.

APIs

Samples

Exemplos de arquivos