Compartilhar via


Adicionar um InkToolbar a um aplicativo do Windows

Existem dois controles diferentes que facilitam a escrita à tinta em aplicativos do Windows: o InkCanvas e o InkToolbar.

O controle do InkCanvas fornece funcionalidade básica do Windows Ink. Use-o para renderizar a entrada à caneta como um traço de tinta (usando as configurações padrão de cor e espessura) ou um traço de borracha.

Para obter detalhes de implementação do InkCanvas, consulte Interações com caneta em aplicativos do Windows.

Como uma sobreposição completamente transparente, o InkCanvas não fornece nenhuma interface do usuário interna para definir propriedades de traço de tinta. Existem duas opções se você quiser alterar a experiência de escrita à tinta padrão, permitir que os usuários definam as propriedades de traço de tinta e oferecer suporte a outros recursos de escrita à tinta personalizados:

  • Em code-behind, use o objeto subjacente InkPresenter vinculado ao InkCanvas.

    As APIs do InkPresenter oferecem suporte à ampla personalização da experiência de escrita à tinta. Para obter mais detalhes, confira Interações com canetas em aplicativos do Windows.

  • Vincule um InkToolbar ao InkCanvas. Por padrão, o InkToolbar fornece uma coleção personalizável e extensível de botões para ativar recursos relacionados à tinta, como tamanho do traço, a cor da tinta e a ponta da caneta.

    Falaremos sobre o InkToolbar neste tópico.

APIs importantes: classe InkCanvas, classe InkToolbar, classe InkPresenter, Windows.UI.Input.Inking

InkToolbar padrão

Por padrão, o InkToolbar inclui botões para desenhar, apagar, realçar e exibir um estêncil (régua ou transportador). Dependendo do recurso, outras configurações e comandos, como a cor da tinta, a espessura do traço, apagar toda a tinta, são fornecidos em um menu suspenso.

InkToolbar
Barra de ferramentas padrão do Windows Ink

Para adicionar um InkToolbar padrão a um aplicativo de escrita à tinta, coloque-o na mesma página que o InkCanvas e associe os dois controles.

  1. Em MainPage.xaml, declare um objeto contêiner (neste exemplo, usamos um controle Grid) para a superfície de escrita à tinta.
  2. Declare um objeto InkCanvas como filho do contêiner. (O tamanho do InkCanvas é herdado do contêiner.)
  3. Declare um InkToolbar e use o atributo TargetInkCanvas para vinculá-lo ao InkCanvas.

Observação

O InkToolbar deve ser declarado após o InkCanvas. Caso contrário, a sobreposição do InkCanvas torna o InkToolbar inacessível.

<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" />
        <InkToolbar x:Name="inkToolbar"
          VerticalAlignment="Top"
          TargetInkCanvas="{x:Bind inkCanvas}" />
    </Grid>
</Grid>

Personalização básica

Nesta seção, abordamos alguns cenários básicos de personalização da barra de ferramentas do Windows Ink.

Especificar a localização e a orientação

Ao adicionar uma barra de ferramentas de tinta ao seu aplicativo, você pode aceitar o local e a orientação padrão da barra de ferramentas ou defini-los conforme exigido pelo seu aplicativo ou usuário.

XAML

Especifique explicitamente a localização e a orientação da barra de ferramentas por meio as propriedades VerticalAlignment, HorizontalAlignment e Orientation.

Padrão Explícito
Localização e orientação padrão da barra de ferramentas de tinta Localização e orientação explícitas da barra de ferramentas de tinta
Orientação e localização padrão da barra de ferramentas do Windows Ink Orientação e localização explícita da barra de ferramentas do Windows Ink

Este é o código para definir explicitamente a localização e a orientação da barra de ferramentas de tinta em XAML.

<InkToolbar x:Name="inkToolbar" 
    VerticalAlignment="Center" 
    HorizontalAlignment="Right" 
    Orientation="Vertical" 
    TargetInkCanvas="{x:Bind inkCanvas}" />

Inicializar com base nas preferências do usuário ou no estado do dispositivo

Em alguns casos, convém definir a localização e a orientação da barra de ferramentas de tinta com base nas preferências do usuário ou no estado do dispositivo. O exemplo a seguir demonstra como definir a localização e a orientação da barra de ferramentas de tinta com base nas preferências de escrita destra ou canhota especificadas em Configurações > Dispositivos > Caneta e Windows Ink > Caneta > Escolha como você escreve.

Configuração da mão dominante
Configuração da mão dominante

É possível consultar essa configuração por meio da propriedade HandPreference do Windows.UI.ViewManagement e definir o HorizontalAlignment com base no valor retornado. Neste exemplo, a barra de ferramentas está localizada no lado esquerdo do aplicativo para uma pessoa canhota e no lado direito para uma pessoa destra.

Faça download deste exemplo em Exemplo de localização e orientação da barra de ferramentas de tinta (básico)

public MainPage()
{
    this.InitializeComponent();

    Windows.UI.ViewManagement.UISettings settings = 
        new Windows.UI.ViewManagement.UISettings();
    HorizontalAlignment alignment = 
        (settings.HandPreference == 
            Windows.UI.ViewManagement.HandPreference.LeftHanded) ? 
            HorizontalAlignment.Left : HorizontalAlignment.Right;
    inkToolbar.HorizontalAlignment = alignment;
}

Ajuste dinâmico ao usuário ou estado do dispositivo

Você também pode usar a associação para cuidar de atualizações da interface do usuário com base em alterações realizadas nas preferências do usuário, nas configurações do dispositivo ou nos estados do dispositivo. No exemplo a seguir, expandimos o exemplo anterior e mostramos como posicionar dinamicamente a barra de ferramentas de tinta com base na orientação do dispositivo usando uma associação, um objeto ViewMOdel e a interface INotifyPropertyChanged.

Faça download deste exemplo em Exemplo de localização e orientação da barra de ferramentas de tinta (dinâmico)

  1. Primeiro, vamos adicionar o ViewModel.

    1. Adicione uma nova pasta ao seu projeto e chame-a de ViewModels.

    2. Adicione uma nova classe à pasta ViewModels (neste exemplo, nós a chamamos de InkToolbarSnippetHostViewModel.cs).

      Observação

      Usamos o padrão Singleton já que precisamos de apenas um objeto desse tipo durante a vida útil do aplicativo

    3. Adicione o namespace using System.ComponentModel ao arquivo.

    4. Adicione uma variável de membro estático chamada instance e uma propriedade estática somente leitura chamada Instance. Torne o construtor privado para garantir que essa classe só possa ser acessada por meio da propriedade Instance.

      Observação

      Essa classe herda da interface INotifyPropertyChanged, que é usada para notificar clientes, geralmente clientes de associação, que um valor da propriedade foi alterado. Usaremos isso para lidar com alterações na orientação do dispositivo (expandiremos esse código e explicaremos mais em uma etapa posterior).

      using System.ComponentModel;
      
      namespace locationandorientation.ViewModels
      {
          public class InkToolbarSnippetHostViewModel : INotifyPropertyChanged
          {
              private static InkToolbarSnippetHostViewModel instance;
      
              public static InkToolbarSnippetHostViewModel Instance
              {
                  get
                  {
                      if (null == instance)
                      {
                          instance = new InkToolbarSnippetHostViewModel();
                      }
                      return instance;
                  }
              }
          }
      
          private InkToolbarSnippetHostViewModel() { }
      }
      
    5. Adicione duas propriedades bool à classe InkToolbarSnippetHostViewModel class: LeftHandedLayout (mesma funcionalidade do exemplo anterior somente XAML) e PortraitLayout (orientação do dispositivo).

      Observação

      A propriedade PortraitLayout é configurável e inclui a definição do evento PropertyChanged .

      public bool LeftHandedLayout
      {
          get
          {
              bool leftHandedLayout = false;
              Windows.UI.ViewManagement.UISettings settings =
                  new Windows.UI.ViewManagement.UISettings();
              leftHandedLayout = (settings.HandPreference ==
                  Windows.UI.ViewManagement.HandPreference.LeftHanded);
              return leftHandedLayout;
          }
      }
      
      public bool portraitLayout = false;
      public bool PortraitLayout
      {
          get
          {
              Windows.UI.ViewManagement.ApplicationViewOrientation winOrientation = 
                  Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().Orientation;
              portraitLayout = 
                  (winOrientation == 
                      Windows.UI.ViewManagement.ApplicationViewOrientation.Portrait);
              return portraitLayout;
          }
          set
          {
              if (value.Equals(portraitLayout)) return;
              portraitLayout = value;
              PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PortraitLayout"));
          }
      }
      
  2. Vamos adicionar algumas classes de conversor ao projeto. Cada classe contém um objeto Convert que retorna um valor de alinhamento (HorizontalAlignment ou VerticalAlignment).

    1. Adicione uma nova pasta ao projeto e chame-a de Converters.

    2. Adicione duas novas classes à pasta Converters (neste exemplo, nós as chamamos de HorizontalAlignmentFromHandednessConverter.cs e VerticalAlignmentFromAppViewConverter.cs).

    3. Adicione os namespaces using Windows.UI.Xaml e using Windows.UI.Xaml.Data para cada arquivo.

    4. Altere cada classe para public e especifique que ela implementa a interface IValueConverter.

    5. Adicione os métodos Convert e ConvertBack a cada arquivo, como mostrado aqui (deixamos o método ConvertBack não implementado).

      • O HorizontalAlignmentFromHandednessConverter posiciona a barra de ferramentas de tinta no lado direito do aplicativo para usuários destros e no lado esquerdo para usuários canhotos.
      using System;
      
      using Windows.UI.Xaml;
      using Windows.UI.Xaml.Data;
      
      namespace locationandorientation.Converters
      {
          public class HorizontalAlignmentFromHandednessConverter : IValueConverter
          {
              public object Convert(object value, Type targetType,
                  object parameter, string language)
              {
                  bool leftHanded = (bool)value;
                  HorizontalAlignment alignment = HorizontalAlignment.Right;
                  if (leftHanded)
                  {
                      alignment = HorizontalAlignment.Left;
                  }
                  return alignment;
              }
      
              public object ConvertBack(object value, Type targetType,
                  object parameter, string language)
              {
                  throw new NotImplementedException();
              }
          }
      }
      
      • O VerticalAlignmentFromAppViewConverter posiciona a barra de ferramentas de tinta no centro do aplicativo para orientação retrato e na parte superior para orientação paisagem (embora destinado a melhorar a usabilidade, essa é apenas uma escolha arbitrária para fins de demonstração).
      using System;
      
      using Windows.UI.Xaml;
      using Windows.UI.Xaml.Data;
      
      namespace locationandorientation.Converters
      {
          public class VerticalAlignmentFromAppViewConverter : IValueConverter
          {
              public object Convert(object value, Type targetType,
                  object parameter, string language)
              {
                  bool portraitOrientation = (bool)value;
                  VerticalAlignment alignment = VerticalAlignment.Top;
                  if (portraitOrientation)
                  {
                      alignment = VerticalAlignment.Center;
                  }
                  return alignment;
              }
      
              public object ConvertBack(object value, Type targetType,
                  object parameter, string language)
              {
                  throw new NotImplementedException();
              }
          }
      }
      
  3. Abra o arquivo MainPage.xaml.cs.

    1. Adicione using using locationandorientation.ViewModels à lista de namespaces para associar o ViewModel.
    2. Adicione using Windows.UI.ViewManagement à lista de namespaces para habilitar a escuta de alterações na orientação do dispositivo.
    3. Adicione o código WindowSizeChangedEventHandler.
    4. Defina o DataContext para a exibição para a instância singleton da classe InkToolbarSnippetHostViewModel.
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    
    using locationandorientation.ViewModels;
    using Windows.UI.ViewManagement;
    
    namespace locationandorientation
    {
        public sealed partial class MainPage : Page
        {
            public MainPage()
            {
                this.InitializeComponent();
    
                Window.Current.SizeChanged += (sender, args) =>
                {
                    ApplicationView currentView = ApplicationView.GetForCurrentView();
    
                    if (currentView.Orientation == ApplicationViewOrientation.Landscape)
                    {
                        InkToolbarSnippetHostViewModel.Instance.PortraitLayout = false;
                    }
                    else if (currentView.Orientation == ApplicationViewOrientation.Portrait)
                    {
                        InkToolbarSnippetHostViewModel.Instance.PortraitLayout = true;
                    }
                };
    
                DataContext = InkToolbarSnippetHostViewModel.Instance;
            }
        }
    }
    
  4. Abra o arquivo MainPage.xaml.

    1. Adicione xmlns:converters="using:locationandorientation.Converters" ao elemento Page para associação aos conversores.

      <Page
      x:Class="locationandorientation.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:locationandorientation"
      xmlns:converters="using:locationandorientation.Converters"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
      
    2. Adicione um elemento PageResources e especifique referências aos conversores.

      <Page.Resources>
          <converters:HorizontalAlignmentFromHandednessConverter x:Key="HorizontalAlignmentConverter"/>
          <converters:VerticalAlignmentFromAppViewConverter x:Key="VerticalAlignmentConverter"/>
      </Page.Resources>
      
    3. Adicione os elementos InkCanvas e InkToolbar e vincule as propriedades VerticalAlignment e HorizontalAlignment do InkToolbar.

      <InkCanvas x:Name="inkCanvas" />
      <InkToolbar x:Name="inkToolbar" 
                  VerticalAlignment="{Binding PortraitLayout, Converter={StaticResource VerticalAlignmentConverter} }" 
                  HorizontalAlignment="{Binding LeftHandedLayout, Converter={StaticResource HorizontalAlignmentConverter} }" 
                  Orientation="Vertical" 
                  TargetInkCanvas="{x:Bind inkCanvas}" />
      
  5. Retorne ao arquivo InkToolbarSnippetHostViewModel.cs para adicionar as propriedades bool PortraitLayout e LeftHandedLayout à classe InkToolbarSnippetHostViewModel, juntamente com o suporte para reassociação PortraitLayout quando o valor da propriedade for alterado.

    public bool LeftHandedLayout
    {
        get
        {
            bool leftHandedLayout = false;
            Windows.UI.ViewManagement.UISettings settings =
                new Windows.UI.ViewManagement.UISettings();
            leftHandedLayout = (settings.HandPreference ==
                Windows.UI.ViewManagement.HandPreference.LeftHanded);
            return leftHandedLayout;
        }
    }
    
    public bool portraitLayout = false;
    public bool PortraitLayout
    {
        get
        {
            Windows.UI.ViewManagement.ApplicationViewOrientation winOrientation = 
                Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().Orientation;
            portraitLayout = 
                (winOrientation == 
                    Windows.UI.ViewManagement.ApplicationViewOrientation.Portrait);
            return portraitLayout;
        }
        set
        {
            if (value.Equals(portraitLayout)) return;
            portraitLayout = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("PortraitLayout"));
        }
    }
    
    #region INotifyPropertyChanged Members
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected void OnPropertyChanged(string property)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
    }
    
    #endregion
    

Agora você deve ter um aplicativo de escrita à tinta que se adapta à preferência de mão dominante do usuário e responda dinamicamente à orientação do dispositivo do usuário.

Especificar o botão selecionado

Botão de lápis selecionado na inicialização
Barra de ferramentas do Windows Ink com botão de lápis selecionado na inicialização

Por padrão, o primeiro botão (ou o mais à esquerda) é selecionado quando o aplicativo é iniciado e a barra de ferramentas é inicializada. Na barra de ferramentas padrão do Windows Ink, esse é o botão de caneta esferográfica.

Como a estrutura define a ordem dos botões internos, o primeiro botão pode não ser a caneta ou a ferramenta que você deseja ativar por padrão.

É possível substituir esse comportamento padrão e especificar o botão selecionado na barra de ferramentas.

Neste exemplo, inicializamos a barra de ferramentas padrão com o botão de lápis selecionado e o lápis ativado (em vez da caneta esferográfica).

  1. Use a declaração XAML do exemplo anterior para o InkCanvas e o InkToolbar.
  2. Em code-behind, configure um manipulador para o evento Loaded do objeto InkToolbar.
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// Here, we set up InkToolbar event listeners.
/// </summary>
public MainPage_CodeBehind()
{
    this.InitializeComponent();
    // Add handlers for InkToolbar events.
    inkToolbar.Loaded += inkToolbar_Loaded;
}
  1. No manipulador do evento Loaded:

    1. Obtenha uma referência ao InkToolbarPencilButton integrado.

    Passar o objeto InkToolbarTool.Pencil no método GetToolButton retorna um objeto InkToolbarToolButton para InkToolbarPencilButton.

    1. Defina ActiveTool para o objeto retornado na etapa anterior.
/// <summary>
/// Handle the Loaded event of the InkToolbar.
/// By default, the active tool is set to the first tool on the toolbar.
/// Here, we set the active tool to the pencil button.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void inkToolbar_Loaded(object sender, RoutedEventArgs e)
{
    InkToolbarToolButton pencilButton = inkToolbar.GetToolButton(InkToolbarTool.Pencil);
    inkToolbar.ActiveTool = pencilButton;
}

Especificar os botões internos

Botões específicos incluídos na inicialização
Botões específicos incluídos na inicialização

Como mencionado, a barra de ferramentas do Windows Ink inclui uma coleção de botões internos padrão. Esses botões são exibidos na seguinte ordem (da esquerda para a direita):

Para este exemplo, inicializamos a barra de ferramentas apenas com os botões internos de caneta esferográfica, lápis e borracha.

É possível fazer isso usando XAML ou code-behind.

XAML

Modifique a declaração XAML do primeiro exemplo para o InkCanvas e o InkToolbar.

Observação

Os botões são adicionados à barra de ferramentas na ordem definida pela estrutura, não na ordem especificada aqui.

<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" />
        <!-- Clear the default InkToolbar buttons by setting InitialControls to None. -->
        <!-- Set the active tool to the pencil button. -->
        <InkCanvas x:Name="inkCanvas" />
        <InkToolbar x:Name="inkToolbar"
                    VerticalAlignment="Top"
                    TargetInkCanvas="{x:Bind inkCanvas}"
                    InitialControls="None">
            <!--
             Add only the ballpoint pen, pencil, and eraser.
             Note that the buttons are added to the toolbar in the order
             defined by the framework, not the order we specify here.
            -->
            <InkToolbarEraserButton />
            <InkToolbarBallpointPenButton />
            <InkToolbarPencilButton/>
        </InkToolbar>
    </Grid>
</Grid>

Code-behind

  1. Use a declaração XAML do primeiro exemplo para o InkCanvas e o InkToolbar.
<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" />
        <InkToolbar x:Name="inkToolbar"
        VerticalAlignment="Top"
        TargetInkCanvas="{x:Bind inkCanvas}" />
    </Grid>
</Grid>
  1. Em code-behind, configure um manipulador para o evento Loading do objeto InkToolbar.
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// Here, we set up InkToolbar event listeners.
/// </summary>
public MainPage_CodeBehind()
{
    this.InitializeComponent();
    // Add handlers for InkToolbar events.
    inkToolbar.Loading += inkToolbar_Loading;
}
  1. Defina InitialControls como “None”.
  2. Crie referências de objeto para os botões exigidos pelo aplicativo. Aqui, adicionamos apenas InkToolbarBallpointPenButton, InkToolbarPencilButton e InkToolbarEraserButton.

Observação

Os botões são adicionados à barra de ferramentas na ordem definida pela estrutura, não na ordem especificada aqui.

  1. Adicione os botões ao InkToolbar.
/// <summary>
/// Handles the Loading event of the InkToolbar.
/// Here, we identify the buttons to include on the InkToolbar.
/// </summary>
/// <param name="sender">The InkToolbar</param>
/// <param name="args">The InkToolbar event data.
/// If there is no event data, this parameter is null</param>
private void inkToolbar_Loading(FrameworkElement sender, object args)
{
    // Clear all built-in buttons from the InkToolbar.
    inkToolbar.InitialControls = InkToolbarInitialControls.None;

    // Add only the ballpoint pen, pencil, and eraser.
    // Note that the buttons are added to the toolbar in the order
    // defined by the framework, not the order we specify here.
    InkToolbarBallpointPenButton ballpoint = new InkToolbarBallpointPenButton();
    InkToolbarPencilButton pencil = new InkToolbarPencilButton();
    InkToolbarEraserButton eraser = new InkToolbarEraserButton();
    inkToolbar.Children.Add(eraser);
    inkToolbar.Children.Add(ballpoint);
    inkToolbar.Children.Add(pencil);
}

Botões personalizados e recursos de escrita à tinta

Você pode personalizar e estender a coleção de botões (e recursos de escrita à tinta associados) fornecidos por meio do InkToolbar.

O InkToolbar consiste em dois grupos distintos de tipos de botões:

  1. Um grupo de botões de “ferramenta” que contém os botões internos de desenhar, apagar e realçar. Ferramentas e canetas personalizadas são adicionadas aqui.

Observação: a seleção de recursos é de exclusão mútua.

  1. Um grupo de botões de “alternância” que contém o botão de régua interno. Alternâncias personalizadas são adicionadas aqui.

Observação: os recursos não são de exclusão mútua e podem ser usados simultaneamente com outras ferramentas ativas.

Dependendo do aplicativo e da funcionalidade de escrita à tinta necessária, você pode adicionar qualquer um dos seguintes botões (vinculados aos recursos de escrita à tinta personalizados) ao InkToolbar:

  • Caneta personalizada: uma caneta para a qual o aplicativo host define a paleta de cores de tinta e as propriedades da ponta da caneta, como forma, rotação e tamanho.
  • Ferramenta personalizada: uma ferramenta sem caneta que o aplicativo host define.
  • Alternância personalizada: define o estado de um recurso que o aplicativo define como ativado ou desativado. Quando ativado, o recurso funciona em conjunto com a ferramenta ativa.

Observação: não é possível alterar a ordem de exibição dos botões internos. A ordem de exibição padrão é: caneta esferográfica, lápis, marca-texto, borracha e régua. As canetas personalizadas são acrescentadas à última caneta padrão, os botões de ferramenta personalizados são adicionados entre o último botão de caneta e o botão de borracha e os botões de alternância personalizados são adicionados após o botão da régua. (Os botões personalizados são adicionados na ordem em que são especificados.)

Caneta personalizada

Você pode criar uma caneta personalizada (ativada por meio de um botão de caneta personalizada) na qual é possível definir a paleta de cores da tinta e as propriedades da ponta da caneta, como forma, rotação e tamanho.

Botão de caneta caligráfica personalizado
Botão de caneta caligráfica personalizado

Neste exemplo, definimos uma caneta personalizada com uma ponta larga que permite traços de tinta caligráfica básicos. Também personalizamos a coleção de pincéis na paleta exibida no menu suspenso de botões.

Code-behind

Primeiro, definimos nossa caneta personalizada e especificamos os atributos de desenho em code-behind. Faremos referência a essa caneta personalizada do XAML posteriormente.

  1. Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e selecione Adicionar -> Novo Item.
  2. Em Visual C# -> Código, adicione um arquivo de nova classe e dê o nome de CalligraphicPen.cs.
  3. Em Calligraphic.cs, substitua o padrão usando um bloco com o seguinte:
using System.Numerics;
using Windows.UI;
using Windows.UI.Input.Inking;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
  1. Especifique que a classe CalligraphicPen é derivada de InkToolbarCustomPen.
class CalligraphicPen : InkToolbarCustomPen
{
}
  1. Substitua CreateInkDrawingAttributesCore para especificar seu próprio tamanho de pincel e traço.
class CalligraphicPen : InkToolbarCustomPen
{
    protected override InkDrawingAttributes
      CreateInkDrawingAttributesCore(Brush brush, double strokeWidth)
    {
    }
}
  1. Crie um objeto InkDrawingAttributes e defina a forma da ponta da caneta, a rotação da ponta, o tamanho do traço e a cor da tinta.
class CalligraphicPen : InkToolbarCustomPen
{
    protected override InkDrawingAttributes
      CreateInkDrawingAttributesCore(Brush brush, double strokeWidth)
    {
        InkDrawingAttributes inkDrawingAttributes =
          new InkDrawingAttributes();
        inkDrawingAttributes.PenTip = PenTipShape.Circle;
        inkDrawingAttributes.Size =
          new Windows.Foundation.Size(strokeWidth, strokeWidth * 20);
        SolidColorBrush solidColorBrush = brush as SolidColorBrush;
        if (solidColorBrush != null)
        {
            inkDrawingAttributes.Color = solidColorBrush.Color;
        }
        else
        {
            inkDrawingAttributes.Color = Colors.Black;
        }

        Matrix3x2 matrix = Matrix3x2.CreateRotation(45);
        inkDrawingAttributes.PenTipTransform = matrix;

        return inkDrawingAttributes;
    }
}

XAML

Em seguida, adicionamos as referências necessárias à caneta personalizada em MainPage.xaml.

  1. Declaramos um dicionário de recursos de página local que cria uma referência à caneta personalizada (CalligraphicPen) definida em CalligraphicPen.cs e uma coleção de pincéis compatícel com a caneta personalizada (CalligraphicPenPalette).
<Page.Resources>
    <!-- Add the custom CalligraphicPen to the page resources. -->
    <local:CalligraphicPen x:Key="CalligraphicPen" />
    <!-- Specify the colors for the palette of the custom pen. -->
    <BrushCollection x:Key="CalligraphicPenPalette">
        <SolidColorBrush Color="Blue" />
        <SolidColorBrush Color="Red" />
    </BrushCollection>
</Page.Resources>
  1. Em seguida, adicionamos um InkToolbar com um elemento filho InkToolbarCustomPenButton.

O botão de caneta personalizado inclui as duas referências de recursos estáticos declaradas nos recursos da página: CalligraphicPen e CalligraphicPenPalette.

Também especificamos o intervalo para o controle deslizante do tamanho do traço (MinStrokeWidth, MaxStrokeWidth e SelectedStrokeWidth) o pincel selecionado (SelectedBrushIndex) e o ícone para o botão de caneta personalizada (SymbolIcon).

<Grid Grid.Row="1">
    <InkCanvas x:Name="inkCanvas" />
    <InkToolbar x:Name="inkToolbar"
                VerticalAlignment="Top"
                TargetInkCanvas="{x:Bind inkCanvas}">
        <InkToolbarCustomPenButton
            CustomPen="{StaticResource CalligraphicPen}"
            Palette="{StaticResource CalligraphicPenPalette}"
            MinStrokeWidth="1" MaxStrokeWidth="3" SelectedStrokeWidth="2"
            SelectedBrushIndex ="1">
            <SymbolIcon Symbol="Favorite" />
            <InkToolbarCustomPenButton.ConfigurationContent>
                <InkToolbarPenConfigurationControl />
            </InkToolbarCustomPenButton.ConfigurationContent>
        </InkToolbarCustomPenButton>
    </InkToolbar>
</Grid>

Alternância personalizada

Você pode criar uma alternância personalizada (ativada por meio de um botão de alternância personalizada) para definir o estado de um recurso definido pelo aplicativo como ativado ou desativado. Quando ativado, o recurso funciona em conjunto com a ferramenta ativa.

Neste exemplo, definimos um botão de alternância personalizada que habilita a escrita à tinta com entrada touch (por padrão, a escrita à tinta por touch não é habilitada).

Observação

Se você precisar oferecer suporte à escrita à tinta com touch, recomendamos habilitá-la usando um CustomToggleButton com o ícone e a dica de ferramenta especificados neste exemplo.

Normalmente, a entrada touch é usada para manipulação direta de um objeto ou da interface do usuário do aplicativo. Para demonstrar as diferenças de comportamento quando a escrita à tinta touch está habilitada, colocamos o InkCanvas dentro de um contêiner ScrollViewer e definimos as dimensões do ScrollViewer como menores que o InkCanvas.

Quando o aplicativo é iniciado, somente a escrita à tinta da caneta é compatível e o touch é usado para aplicar panorâmica ou aplicar zoom na superfície de escrita à tinta. Quando a escrita à tinta touch está habilitada, a superfície de tinta não pode ser panorâmica ou ampliada por meio da entrada touch.

Observação

Consulte Controles de escrita à tinta para obter as diretrizes de experiência do usuário para InkCanvas e InkToolbar. As recomendações a seguir são relevantes para este exemplo:

  • A melhor maneira de usar o InkToolbar, e escrita à tinta em geral, é com uma caneta ativa. No entanto, poderá haver suporte para escrita à tinta com mouse e touch, se seu aplicativo exigir.
  • Para oferecer suporte à escrita à tinta com entrada touch, recomendamos usar o ícone “ED5F” da fonte “Segoe MLD2 Assets” para o botão de alternância, com uma dica de ferramenta “Escrita por toque”.

XAML

  1. Primeiro, declaramos um elemento InkToolbarCustomToggleButton (toggleButton) com um ouvinte de eventos Click que especifica o manipulador de eventos (Toggle_Custom).
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel Grid.Row="0" 
                x:Name="HeaderPanel" 
                Orientation="Horizontal">
        <TextBlock x:Name="Header" 
                   Text="Basic ink sample" 
                   Style="{ThemeResource HeaderTextBlockStyle}" 
                   Margin="10" />
    </StackPanel>

    <ScrollViewer Grid.Row="1" 
                  HorizontalScrollBarVisibility="Auto" 
                  VerticalScrollBarVisibility="Auto">
        
        <Grid HorizontalAlignment="Left" VerticalAlignment="Top">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            
            <InkToolbar Grid.Row="0" 
                        Margin="10"
                        x:Name="inkToolbar" 
                        VerticalAlignment="Top"
                        TargetInkCanvas="{x:Bind inkCanvas}">
                <InkToolbarCustomToggleButton 
                x:Name="toggleButton" 
                Click="CustomToggle_Click" 
                ToolTipService.ToolTip="Touch Writing">
                    <SymbolIcon Symbol="{x:Bind TouchWritingIcon}"/>
                </InkToolbarCustomToggleButton>
            </InkToolbar>
            
            <ScrollViewer Grid.Row="1" 
                          Height="500"
                          Width="500"
                          x:Name="scrollViewer" 
                          ZoomMode="Enabled" 
                          MinZoomFactor=".1" 
                          VerticalScrollMode="Enabled" 
                          VerticalScrollBarVisibility="Auto" 
                          HorizontalScrollMode="Enabled" 
                          HorizontalScrollBarVisibility="Auto">
                
                <Grid x:Name="outputGrid" 
                      Height="1000"
                      Width="1000"
                      Background="{ThemeResource SystemControlBackgroundChromeWhiteBrush}">
                    <InkCanvas x:Name="inkCanvas"/>
                </Grid>
                
            </ScrollViewer>
        </Grid>
    </ScrollViewer>
</Grid>

Code-behind

  1. No trecho anterior, declaramos um ouvinte de eventos e um manipulador Click (Toggle_Custom) no botão de alternância personalizada para escrita à tinta touch (toggleButton). Esse manipulador alterna o suporte para CoreInputDeviceTypes.Touch através da propriedade InputDeviceTypes do InkPresenter.

    Também especificamos um ícone para o botão usando o elemento SymbolIcon e a extensão de marcação {x:Bind} que o vincula a um campo definido no arquivo code-behind (TouchWritingIcon).

    O trecho a seguir inclui o manipulador de eventos Click e a definição de TouchWritingIcon.

namespace Ink_Basic_InkToolbar
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage_AddCustomToggle : Page
    {
        Symbol TouchWritingIcon = (Symbol)0xED5F;

        public MainPage_AddCustomToggle()
        {
            this.InitializeComponent();
        }

        // Handler for the custom toggle button that enables touch inking.
        private void CustomToggle_Click(object sender, RoutedEventArgs e)
        {
            if (toggleButton.IsChecked == true)
            {
                inkCanvas.InkPresenter.InputDeviceTypes |= CoreInputDeviceTypes.Touch;
            }
            else
            {
                inkCanvas.InkPresenter.InputDeviceTypes &= ~CoreInputDeviceTypes.Touch;
            }
        }
    }
}

Ferramenta personalizada

Você pode criar um botão de ferramenta personalizada para invocar uma ferramenta que não seja caneta definida pelo aplicativo.

Por padrão, um InkPresenter processa todas as entradas como um traço de tinta ou um traço de borracha. Isso inclui entrada modificada por uma funcionalidade de hardware secundária, como um botão da caneta, um botão direito do mouse ou similar. No entanto, o InkPresenter pode ser configurado para deixar entradas específicas não processadas, que podem ser passadas para o aplicativo para processamento personalizado.

Neste exemplo, definimos um botão de ferramenta personalizada que, quando selecionado, faz com que os traços subsequentes sejam processados e renderizados como um laço de seleção (linha tracejada) em vez de tinta. Todos os traços de tinta dentro dos limites da área de seleção são definidos como Selecionado.

Observação

Consulte Controles de escrita à tinta para obter as diretrizes de experiência do usuário para InkCanvas e InkToolbar. As recomendações a seguir são relevantes para este exemplo:

  • Caso você forneça seleção de traço, recomendamos usar o ícone “EF20” da fonte “Segoe MLD2 Assets” para o botão de ferramenta, com uma dica “Ferramenta Seleção”.

XAML

  1. Primeiro, declaramos um elemento InkToolbarCustomToolButton (customToolButton) com um ouvinte de eventos Click que especifica o manipulador de eventos (customToolButton_Click) onde a seleção de traço está configurada. (Também adicionamos um conjunto de botões para copiar, recortar e colar a seleção de traço.)

  2. Também adicionamos um elemento de Tela para desenhar nosso traço de seleção. Usar uma camada separada para desenhar o traço de seleção garante que o InkCanvas e seu conteúdo permaneçam inalterados.

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <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>
    <StackPanel x:Name="ToolPanel" Orientation="Horizontal" Grid.Row="1">
        <InkToolbar x:Name="inkToolbar" 
                    VerticalAlignment="Top" 
                    TargetInkCanvas="{x:Bind inkCanvas}">
            <InkToolbarCustomToolButton 
                x:Name="customToolButton" 
                Click="customToolButton_Click" 
                ToolTipService.ToolTip="Selection tool">
                <SymbolIcon Symbol="{x:Bind SelectIcon}"/>
            </InkToolbarCustomToolButton>
        </InkToolbar>
        <Button x:Name="cutButton" 
                Content="Cut" 
                Click="cutButton_Click"
                Width="100"
                Margin="5,0,0,0"/>
        <Button x:Name="copyButton" 
                Content="Copy"  
                Click="copyButton_Click"
                Width="100"
                Margin="5,0,0,0"/>
        <Button x:Name="pasteButton" 
                Content="Paste"  
                Click="pasteButton_Click"
                Width="100"
                Margin="5,0,0,0"/>
    </StackPanel>
    <Grid Grid.Row="2" x:Name="outputGrid" 
              Background="{ThemeResource SystemControlBackgroundChromeWhiteBrush}" 
              Height="Auto">
        <!-- Canvas for displaying selection UI. -->
        <Canvas x:Name="selectionCanvas"/>
        <!-- Canvas for displaying ink. -->
        <InkCanvas x:Name="inkCanvas" />
    </Grid>
</Grid>

Code-behind

  1. Em seguida, manipulamos o evento Click para o InkToolbarCustomToolButton no arquivo code-behind MainPage.xaml.cs.

    Esse manipulador configura o InkPresenter para passar entrada não processada para o aplicativo.

    Para obter uma etapa mais detalhada deste código: consulte a seção Entrada de passagem para processamento avançado de Interações com caneta e Windows Ink em aplicativos do Windows.

    Também especificamos um ícone para o botão usando o elemento SymbolIcon e a extensão de marcação {x:Bind} que o vincula a um campo definido no arquivo code-behind (SelectIcon).

    O trecho a seguir inclui o manipulador de eventos Click e a definição de SelectIcon.

namespace Ink_Basic_InkToolbar
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage_AddCustomTool : Page
    {
        // Icon for custom selection tool button.
        Symbol SelectIcon = (Symbol)0xEF20;

        // Stroke selection tool.
        private Polyline lasso;
        // Stroke selection area.
        private Rect boundingRect;

        public MainPage_AddCustomTool()
        {
            this.InitializeComponent();

            // Listen for new ink or erase strokes to clean up selection UI.
            inkCanvas.InkPresenter.StrokeInput.StrokeStarted +=
                StrokeInput_StrokeStarted;
            inkCanvas.InkPresenter.StrokesErased +=
                InkPresenter_StrokesErased;
        }

        private void customToolButton_Click(object sender, RoutedEventArgs e)
        {
            // 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;
        }

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

        private void cutButton_Click(object sender, RoutedEventArgs e)
        {
            inkCanvas.InkPresenter.StrokeContainer.CopySelectedToClipboard();
            inkCanvas.InkPresenter.StrokeContainer.DeleteSelected();
            ClearSelection();
        }

        private void copyButton_Click(object sender, RoutedEventArgs e)
        {
            inkCanvas.InkPresenter.StrokeContainer.CopySelectedToClipboard();
        }

        private void pasteButton_Click(object sender, RoutedEventArgs e)
        {
            if (inkCanvas.InkPresenter.StrokeContainer.CanPasteFromClipboard())
            {
                inkCanvas.InkPresenter.StrokeContainer.PasteFromClipboard(
                    new Point(0, 0));
            }
            else
            {
                // Cannot paste from clipboard.
            }
        }

        // Clean up selection UI.
        private void ClearSelection()
        {
            var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
            foreach (var stroke in strokes)
            {
                stroke.Selected = false;
            }
            ClearBoundingRect();
        }

        private void ClearBoundingRect()
        {
            if (selectionCanvas.Children.Any())
            {
                selectionCanvas.Children.Clear();
                boundingRect = Rect.Empty;
            }
        }

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

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

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 “molhada” à 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).

A plataforma de tinta permite que você substitua esse comportamento e personalize completamente a experiência de escrita à tinta secando a entrada de tinta personalizada.

Para saber mais sobre secagem personalizada, consulte Interações com caneta e Windows Ink em aplicativos do Windows.

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

Amostras de tópico

Outras amostras