Agregar un control InkToolbar a una aplicación de Windows

En las aplicaciones de Windows existen dos controles diferentes que facilitan la entrada manuscrita: InkCanvas e InkToolbar.

El control de InkCanvas proporciona la funcionalidad básica de Windows Ink. Úselo para representar la entrada manuscrita como trazo de entrada manuscrita (mediante la configuración predeterminada para el color y el grosor) o un trazo de borrado.

Para información detallada sobre la implementación de InkCanvas, consulte Interacciones de bolígrafo y lápiz en aplicaciones de Windows.

Como superposición completamente transparente, InkCanvas no proporciona ninguna UI integrada para establecer propiedades de trazo de entrada manuscrita. Si quiere cambiar la experiencia de entrada manuscrita predeterminada, permita a los usuarios establecer propiedades de trazo de entrada manuscrita y admitir otras características de entrada manuscrita personalizadas, tiene las siguientes dos opciones:

  • En el código subyacente, use el objeto InkPresenter enlazado a InkCanvas.

    Las API de InkPresenter admiten una mayor personalización de la experiencia de entrada manuscrita. Para más detalles, consulte Interacciones de bolígrafo y lápiz en aplicaciones de Windows.

  • Enlace un control de InkToolbar a InkCanvas. De manera predeterminada, InkToolbar proporciona una colección personalizable y extensible de botones para activar características relacionadas con la entrada de lápiz, como el tamaño del trazo, el color del trazo y la punta del lápiz.

    Abordamos InkToolbar en este tema.

API importantes: InkCanva class, InkToolbar class, InkPresenter class, Windows.UI.Input.Inking

InkToolbar predeterminado

De manera predeterminada, InkToolbar incluye botones para dibujar, borrar, resaltar y mostrar una galería de símbolos (regla o transportador). En función de la característica, en un control flotante se proporcionan otras opciones de configuración y comandos, como el color de la entrada de lápiz, el grosor del trazo o borrar toda la entrada de lápiz.

InkToolbar
Barra de herramientas predeterminada de Windows Ink

Para agregar una InkToolbar predeterminada a una aplicación de entrada manuscrita, colóquela en la misma página que inkCanvas y asocie los dos controles.

  1. En MainPage.xaml, declare un objeto contenedor (para este ejemplo, usamos un control Grid) para la superficie de entrada manuscrita.
  2. Declare un objeto InkCanvas como elemento secundario del contenedor. (El tamaño del objeto InkCanvas se hereda del contenedor).
  3. Declare una InkToolbar y use el atributo TargetInkCanvas para enlazarlo a InkCanvas.

Nota:

Asegúrese de que InkToolbar se declara después de InkCanvas. De lo contrario, la superposición de InkCanvas hace que InkToolbar no sea accesible.

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

Personalización básica

En esta sección, tratamos algunos escenarios básicos de personalización de la barra de herramientas de Windows Ink.

Especificar la ubicación y la orientación

Al agregar una barra de herramientas de entrada de lápiz a la aplicación, puedes aceptar la ubicación y la orientación predeterminadas de la barra de herramientas o establecerlas según lo requiera la aplicación o el usuario.

XAML

Especifique explícitamente la ubicación y la orientación de la barra de herramientas a través de las propiedades VerticalAlignment, HorizontalAlignment y Orientation.

Valor predeterminado Explícito
Default ink toolbar location and orientation Explicit ink toolbar location and orientation
Ubicación y orientación predeterminadas de la barra de herramientas de Windows Ink Ubicación y orientación explícitas de la barra de herramientas de Windows Ink

Este es el código para establecer explícitamente la ubicación y la orientación de la barra de herramientas de entrada de lápiz en XAML.

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

Inicializar según las preferencias del usuario o el estado del dispositivo

En algunos casos, es posible que quiera establecer la ubicación y la orientación de la barra de herramientas de entrada de lápiz según la preferencia del usuario o el estado del dispositivo. En el ejemplo siguiente, se muestra cómo establecer la ubicación y la orientación de la barra de herramientas de entrada de lápiz en función de las preferencias de escritura zurda o diestra especificadas mediante Configuración > Dispositivos > Bolígrafo y Windows Ink > Bolígrafo > Elegir la mano con la que escribe.

Dominant hand setting
Configuración de mano dominante

Puede consultar esta configuración a través de la propiedad HandPreference de Windows.UI.ViewManagement y establecer HorizontalAlignment en función del valor devuelto. En este ejemplo, ubicamos la barra de herramientas en el lado izquierdo de la aplicación para una persona zurda y en el lado derecho de una persona diestra.

Descargue este ejemplo desde muestra de ubicación y orientación de la barra de herramientas de entrada de lápiz (básica)

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

Ajustar dinámicamente al estado del usuario o dispositivo

También puede usar el enlace para buscar las actualizaciones de la UI en función de los cambios en las preferencias del usuario, la configuración del dispositivo o los estados del dispositivo. En el ejemplo siguiente, desarrollamos el ejemplo anterior y se muestra cómo colocar dinámicamente la barra de herramientas de entrada de lápiz en función de la orientación del dispositivo mediante el enlace, un objeto ViewMOdel y la interfaz INotifyPropertyChanged.

Descargue esta muestra de muestra de ubicación y orientación de la barra de herramientas de entrada manuscrita (dinámica)

  1. En primer lugar, vamos a agregar nuestro objeto ViewModel.

    1. Agregue una nueva carpeta al proyecto y llámela ViewModels.

    2. Agregue una clase nueva a la carpeta ViewModels (para este ejemplo, la hemos llamado InkToolbarSnippetHostViewModel.cs).

      Nota:

      Usamos el patrón Singleton, ya que solo necesitamos un objeto de este tipo durante la vida útil de la aplicación.

    3. Agregue el espacio de nombres using System.ComponentModel al archivo.

    4. Agregue una variable de miembro estática denominada instance y una propiedad estática de solo lectura denominada Instance. Haga que el constructor sea privado para garantizar que solo se puede acceder a esta clase a través de la propiedad Instance.

      Nota:

      Esta clase hereda de la interfaz INotifyPropertyChanged, que se usa para notificar que cambió un valor de propiedad, normalmente a los clientes de enlace. Vamos a usar esto para controlar los cambios en la orientación del dispositivo (profundizaremos en este código y explicaremos con más detalle más adelante en un paso 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. Agregue dos propiedades bool a la clase InkToolbarSnippetHostViewModel: LeftHandedLayout (la misma funcionalidad que el ejemplo de solo XAML anterior) y PortraitLayout (orientación del dispositivo).

      Nota:

      La propiedad PortraitLayout se puede establecer e incluye la definición del 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. Ahora, vamos a agregar un par de clases converter a nuestro proyecto. Cada clase contiene un objeto Convert que devuelve un valor de alineación (HorizontalAlignment o VerticalAlignment).

    1. Agregue una nueva carpeta al proyecto y llámela Converters.

    2. Agregue dos clases nuevas a la carpeta Converters (para este ejemplo, las llamamos HorizontalAlignmentFromHandednessConverter.cs y VerticalAlignmentFromAppViewConverter.cs).

    3. Agregue los espacios de nombres using Windows.UI.Xaml y using Windows.UI.Xaml.Data a cada archivo.

    4. Cambie cada clase a public y especifique que implementa la interfaz IValueConverter.

    5. Agregue los métodos Convert y ConvertBack a cada archivo, como se muestra aquí (se deja el método ConvertBack sin implementar).

      • HorizontalAlignmentFromHandednessConverter coloca la barra de herramientas de la entrada de lápiz en el lado derecho de la aplicación para los usuarios diestros y en el lado izquierdo de la aplicación para los usuarios zurdos.
      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();
              }
          }
      }
      
      • VerticalAlignmentFromAppViewConverter coloca la barra de herramientas de entrada de lápiz en el centro de la aplicación para la orientación vertical y en la parte superior de la aplicación para la orientación horizontal (mientras se intenta mejorar la facilidad de uso, esto es simplemente una opción arbitraria para fines de demostración).
      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. Ahora, abra el archivo MainPage.xaml.cs.

    1. Agregue using using locationandorientation.ViewModels a la lista de espacios de nombres para asociar nuestro objeto ViewModel.
    2. Agregue using Windows.UI.ViewManagement a la lista de espacios de nombres para habilitar la escucha de cambios en la orientación del dispositivo.
    3. Agregue el código WindowSizeChangedEventHandler.
    4. Establezca DataContext para la vista en la instancia singleton de la clase 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. Ahora, abra el archivo MainPage.xaml.

    1. Agregue xmlns:converters="using:locationandorientation.Converters" al elemento Page para enlazar a nuestros convertidores.

      <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. Agregue un elemento PageResources y especifique referencias a nuestros convertidores.

      <Page.Resources>
          <converters:HorizontalAlignmentFromHandednessConverter x:Key="HorizontalAlignmentConverter"/>
          <converters:VerticalAlignmentFromAppViewConverter x:Key="VerticalAlignmentConverter"/>
      </Page.Resources>
      
    3. Agregue los elementos InkCanvas e InkToolbar y enlace las propiedades VerticalAlignment y HorizontalAlignment de 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. Vuelva al archivo InkToolbarSnippetHostViewModel.cs para agregar nuestras propiedades bool PortraitLayout y LeftHandedLayout a la clase InkToolbarSnippetHostViewModel, junto con la compatibilidad con la reenlazamiento PortraitLayout cuando cambia ese valor de propiedad.

    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
    

Ahora debería tener una aplicación de entrada manuscrita que se adapte a la preferencia de mano dominante del usuario y responda dinámicamente a la orientación del dispositivo del usuario.

Especificar el botón seleccionado

Pencil button selected at initialization
Barra de herramientas de Windows Ink con el botón de lápiz seleccionado en la inicialización

De manera predeterminada, se selecciona el primer botón (o en el extremo izquierdo) cuando se inicia la aplicación y la barra de herramientas. En la barra de herramientas predeterminada de Windows Ink, este es el botón del lápiz.

Dado que el marco define el orden de los botones integrados, es posible que el primer botón no sea el lápiz o la herramienta que quiere activar de manera predeterminada.

Puede invalidar este comportamiento predeterminado y especificar el botón seleccionado en la barra de herramientas.

En este ejemplo, inicializamos la barra de herramientas predeterminada con el botón de lápiz seleccionado y el lápiz activado (en lugar del bolígrafo).

  1. Use la declaración XAML para InkCanvas e InkToolbar del ejemplo anterior.
  2. En el código subyacente, configure un controlador para el evento Loaded del 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. En el controlador del evento Loaded:

    1. Obtenga una referencia al control integrado InkToolbarPencilButton.

    Pasar un objeto InkToolbarTool.Pencil en el método GetToolButton devuelve un objeto InkToolbarToolButton para inkToolbarPencilButton.

    1. Establezca ActiveTool en el objeto devuelto del paso 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 los botones integrados

Specific buttons included at initialization
Botones específicos incluidos en la inicialización

Como se mencionó, la barra de herramientas de Windows Ink incluye una colección de botones integrados predeterminados. Estos botones se muestran en el orden siguiente (de izquierda a derecha):

En este ejemplo, inicializamos la barra de herramientas solo con los botones integrados de bolígrafo, lápiz y borrador.

Para ello, use XAML o código subyacente.

XAML

Modifique la declaración XAML para InkCanvas e InkToolbar del primer ejemplo.

Nota:

Los botones se agregan a la barra de herramientas en el orden definido por el marco, no el orden especificado aquí.

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

Código subyacente

  1. Utilice la declaración XAML para InkCanvas e InkToolbar del primer ejemplo.
<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. En el código subyacente, configure un controlador para el evento Loading del 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. Establezca InitialControls en "None".
  2. Cree referencias de objeto para los botones necesarios para la aplicación. Aquí, agregamos solo InkToolbarBallpointPenButton, InkToolbarPencilButton e InkToolbarEraserButton.

Nota:

Los botones se agregan a la barra de herramientas en el orden definido por el marco, no el orden especificado aquí.

  1. Agregue los botones a 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);
}

Botones personalizados y características de entrada manuscrita

Puede personalizar y ampliar la colección de botones (y características de entrada manuscrita asociadas) que se proporcionan a través de InkToolbar.

InkToolbar consta de dos grupos distintos de tipos de botón:

  1. Un grupo de botones de "herramienta" que contienen los botones de dibujo, borrado y resaltado integrados. Aquí se agregan los lápices y herramientas personalizados.

Nota La selección de características es mutuamente excluyente.

  1. Un grupo de botones de alternancia que contiene el botón de regla integrado. Aquí se agregan alternancias personalizadas.

Nota Las características no son mutuamente excluyentes y se pueden usar de manera simultánea con otras herramientas activas.

Según la aplicación y la funcionalidad de entrada manuscrita requerida, puede agregar cualquiera de los botones siguientes (enlazados a las características de entrada de lápiz personalizadas) a InkToolbar:

  • Lápiz personalizado: un lápiz en el que la paleta de colores del trazo y las propiedades de la punta del lápiz, como la forma, la rotación y el tamaño, las define la aplicación host.
  • Herramienta personalizada: una herramienta que no es lápiz que define la aplicación host.
  • Alternancia personalizada: establece el estado de una característica que define la aplicación en activada o desactivada. Cuando está activada, la característica funciona junto con la herramienta activa.

Nota No puede cambiar el orden de presentación de los botones integrados. El orden de visualización predeterminado es: bolígrafo, lápiz, marcador, borrador y regla. Los lápices personalizados se anexan al último lápiz predeterminado, los botones de herramientas personalizados se agregan entre el último botón de lápiz y el botón de alternancia personalizado y se agregan después del botón de regla. (Los botones personalizados se agregan en el orden en que se especifican).

Lápiz personalizado

Puede crear un lápiz personalizado (se activa mediante un botón de lápiz personalizado) donde define la paleta de colores del trazo y las propiedades de la punta del lápiz, como la forma, la rotación y el tamaño.

Custom calligraphic pen button
Botón del lápiz caligráfico personalizado

En este ejemplo, definimos un lápiz personalizado con una punta ancha que permite trazos de entrada manuscrita caligráficos básicos. También personalizamos la colección de pinceles de la paleta que se muestra en el control flotante del botón.

Código subyacente

En primer lugar, definimos el lápiz personalizado y especificamos los atributos de dibujo en código subyacente. Se hace referencia a este lápiz personalizado de XAML más adelante.

  1. Haga clic con el botón derecho en el Explorador de soluciones y seleccione Agregar -> Nuevo elemento.
  2. En Visual C# -> Código, agregue un nuevo archivo de clase y llámelo CalligraphicPen.cs.
  3. En Calligraphic.cs, reemplace el bloque using predeterminado por lo siguiente:
using System.Numerics;
using Windows.UI;
using Windows.UI.Input.Inking;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
  1. Especifique que la clase CalligraphicPen se derive de InkToolbarCustomPen.
class CalligraphicPen : InkToolbarCustomPen
{
}
  1. Invalide CreateInkDrawingAttributesCore para especificar su propio tamaño de pincel y trazo.
class CalligraphicPen : InkToolbarCustomPen
{
    protected override InkDrawingAttributes
      CreateInkDrawingAttributesCore(Brush brush, double strokeWidth)
    {
    }
}
  1. Cree un objeto InkDrawingAttributes y establezca la forma de la punta del lápiz, la rotación de la punta, el tamaño del trazo y el color del trazo.
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

A continuación, agregamos las referencias necesarias al lápiz personalizado en MainPage.xaml.

  1. Declaramos un diccionario de recursos de página local que crea una referencia al lápiz personalizado (CalligraphicPen) que se define en CalligraphicPen.cs y una colección de pinceles compatible con el lápiz personalizado (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. A continuación, agregamos una InkToolbar con un elemento secundario InkToolbarCustomPenButton.

El botón de lápiz personalizado incluye las dos referencias de recursos estáticos declaradas en los recursos de página: CalligraphicPen y CalligraphicPenPalette.

También especificamos el intervalo para el control deslizante del tamaño del trazo (MinStrokeWidth, MaxStrokeWidth y SelectedStrokeWidth), el pincel seleccionado (SelectedBrushIndex) y el icono del botón de lápiz personalizado (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>

Alternancia personalizada

Puede crear un botón de alternancia personalizado (se activa mediante un botón de alternancia personalizada) para establecer el estado de una característica que define la aplicación en activado o desactivado. Cuando está activada, la característica funciona junto con la herramienta activa.

En este ejemplo, se define un botón de alternancia personalizada que habilita la entrada manuscrita con entrada táctil (de manera predeterminada, la entrada manuscrita táctil no está habilitada).

Nota:

Si debe admitir la entrada manuscrita con entrada táctil, se recomienda habilitarla mediante CustomToggleButton, con el icono y la información sobre herramientas que se especifican en este ejemplo.

Normalmente, la entrada táctil se usa para manipular directamente un objeto o la UI de la aplicación. Para demostrar las diferencias del comportamiento cuando la entrada manuscrita táctil está habilitada, se coloca InkCanvas en un contenedor ScrollViewer y se establecen las dimensiones de ScrollViewer para que sean más pequeñas que inkCanvas.

Cuando se inicia la aplicación, solo se admite la entrada manuscrita de lápiz y la función táctil se usa para aplicar panorámica o acercar la superficie de la entrada manuscrita. Cuando se habilita la entrada manuscrita táctil, la superficie de entrada manuscrita no puede desplazarse ni ampliarse mediante la entrada táctil.

Nota:

Consulte Controles de entrada manuscrita para las instrucciones de la experiencia de usuario de InkCanvas e InkToolbar. Las siguientes recomendaciones son relevantes para este ejemplo:

  • InkToolbar y la entrada manuscrita en general se experimentan mejor mediante un lápiz activo. Sin embargo, la aplicación puede admitir la entrada manuscrita con el ratón y la función táctil.
  • Si admite la entrada manuscrita con la entrada táctil, se recomienda usar el icono de "ED5F" de la fuente "Segoe MLD2 Assets" para el botón de alternancia, con una información sobre herramientas "Escritura táctil".

XAML

  1. En primer lugar, se declara un elemento InkToolbarCustomToggleButton (toggleButton) con un agente de escucha de eventos de clics que especifica el controlador 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>

Código subyacente

  1. En el fragmento anterior, se declaró un agente de escucha de eventos de clics y un controlador (Toggle_Custom) en el botón de alternancia personalizada para la entrada manuscrita táctil (toggleButton). Este controlador simplemente alterna la compatibilidad con CoreInputDeviceTypes.Touch a través de la propiedad InputDeviceTypes de InkPresenter.

    También se especificó un icono para el botón mediante el elemento SymbolIcon y la extensión de marcado {x:Bind} que lo enlaza a un campo definido en el archivo de código subyacente (TouchWritingIcon).

    El fragmento siguiente incluye tanto el controlador de eventos de clics como la definición 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;
            }
        }
    }
}

Herramienta personalizada

Puede crear un botón de herramienta personalizada para invocar una herramienta que no sea de lápiz que defina la aplicación.

De manera predeterminada, un InkPresenter procesa toda la entrada como un trazo de entrada manuscrita o un trazo de borrado. Esto incluye la entrada modificada por una prestación de hardware secundaria, como un botón de menú contextual de lápiz, un botón derecho del ratón o similar. Sin embargo, InkPresenter se puede configurar para dejar una entrada específica sin procesar, que luego se puede pasar a la aplicación para su procesamiento personalizado.

En este ejemplo, definimos un botón de herramienta personalizada que, cuando se selecciona, hace que los trazos posteriores se procesen y represente como una lazo de selección (línea discontinua) en lugar de la entrada de lápiz. Todos los trazos de entrada manuscrita dentro de los límites del área de selección se establecen en Seleccionado.

Nota:

Consulte Controles de entrada manuscrita para las instrucciones de la experiencia de usuario de InkCanvas e InkToolbar. La siguiente recomendación es relevante para este ejemplo:

  • Si se proporciona selección de trazo, se recomienda usar el icono de "EF20" de la fuente "Segoe MLD2 Assets" para el botón de herramienta, con una información sobre herramientas "Herramienta de selección".

XAML

  1. En primer lugar, declaramos un elemento InkToolbarCustomToolButton (customToolButton) con un agente de escucha de eventos de clics que especifica el controlador de eventos (customToolButton_Click) donde se configura la selección de trazo. (También hemos agregado un conjunto de botones para copiar, cortar y pegar la selección del trazo).

  2. También agregamos un elemento de lienzo para dibujar nuestro trazo de selección. El uso de una capa independiente para dibujar el trazo de selección garantiza que InkCanvas y su contenido permanezcan intactos.

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

Código subyacente

  1. A continuación, controlamos el evento de clics de InkToolbarCustomToolButton en el archivo de código subyacente MainPage.xaml.cs.

    Este controlador configura InkPresenter para pasar la entrada sin procesar a través de la aplicación.

    Para un paso más detallado a través de este código, consulte la sección Entrada de tránsito para el procesamiento avanzado de Interacciones de lápiz y Windows Ink en aplicaciones de Windows.

    También se especificó un icono para el botón mediante el elemento SymbolIcon y la extensión de marcado {x:Bind} que lo enlaza a un campo definido en el archivo de código subyacente (SelectIcon).

    El fragmento siguiente incluye tanto el controlador de eventos de clics como la definición 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 modifed 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);
            }
        }
    }
}

Personalizar la representación de la entrada manuscrita

De manera predeterminada, la entrada de lápiz se procesa en un subproceso en segundo plano de baja latencia y se representa como "provisional" cuando se dibuja. Cuando se completa el trazo (lápiz o movimiento del dedo, o el botón del ratón), el trazo se procesa en el subproceso de interfaz de usuario y se representa como "definitivo" en la capa de InkCanvas (encima del contenido de la aplicación y reemplaza la entrada de lápiz provisional).

La plataforma de entrada de lápiz permite invalidar este comportamiento y personalizar completamente la experiencia de entrada manuscrita mediante el efecto definitivo personalizado de la entrada de lápiz.

Para más información sobre el secado personalizado, consulte Interacciones de lápiz y Windows Ink en aplicaciones de Windows.

Nota:

Efecto definitivo personalizado e InkToolbar
Si la aplicación invalida el comportamiento de representación de entrada de lápiz predeterminada de InkPresenter con una implementación de efecto definitivo personalizado, los trazos de entrada manuscrita representados ya no estarán disponibles para InkToolbar y los comandos de borrado integrados de InkToolbar no funcionarán según lo previsto. Para proporcionar funcionalidad de borrado, debe controlar todos los eventos de puntero, hacer pruebas de posicionamiento en cada trazo e invalidar el comando integrado "Borrar toda la entrada de lápiz".

Ejemplos del tema

Otros ejemplos