Compartir a través de


Agregar un control InkToolbar a una aplicación de Windows

Hay dos controles diferentes que facilitan la entrada manuscrita en aplicaciones de Windows: InkCanvas y InkToolbar.

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

Para obtener información detallada sobre la implementación de InkCanvas, consulta Interacciones de lápiz y lápiz en aplicaciones de Windows.

Como superposición completamente transparente, InkCanvas no proporciona ninguna interfaz de usuario integrada para establecer propiedades de trazos de lápiz. Si desea cambiar la experiencia de entrada manuscrita predeterminada, permita a los usuarios establecer propiedades de trazo de lápiz y admitir otras características de entrada manuscrita personalizadas, tiene dos opciones:

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

    Las API InkPresenter admiten una amplia personalización de la experiencia de tinta digital. Para obtener más información, consulta Interacciones de lápiz y pluma en aplicaciones de Windows.

  • Enlace un InkToolbar al InkCanvas. De forma 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 de lápiz y la punta del lápiz.

    Hablamos sobre el InkToolbar en este tema.

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

Barra de herramientas de tinta predeterminada

De forma predeterminada, InkToolbar incluye botones para dibujar, borrar, resaltar y mostrar una plantilla (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 un control InkToolbar predeterminado a una aplicación de dibujo, colóquelo en la misma página que el control InkCanvas y asocie los dos controles.

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

Nota:

Asegúrese de que InkToolbar se declare después de InkCanvas. Si no es así, la superposición 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 ubicación y 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 sus propiedades VerticalAlignment, HorizontalAlignment y Orientation .

Predeterminado Explicit
Ubicación y orientación predeterminadas de la barra de herramientas de tinta Ubicación y orientación explícita de la barra de herramientas de tinta
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

Aquí tienes el código para configurar explícitamente la ubicación y orientación de la barra de herramientas de tinta en XAML.

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

Inicializar en función de las preferencias del usuario o el estado del dispositivo

En algunos casos, es posible que desee establecer la ubicación y la orientación de la barra de herramientas de entrada de lápiz en función de 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 tinta en función de las preferencias de escritura con la mano izquierda o derecha especificadas a través de Configuración > Dispositivos > Lápiz y Windows Ink > Lápiz > Elija con qué mano escribe.

Configuración de mano dominante
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, colocamos la barra de herramientas en el lado izquierdo para una persona zurda y en el lado derecho para una persona diestra.

Descargue este ejemplo desde el ejemplo básico de ubicación y orientación de la barra de herramientas Ink

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 al estado del usuario o del dispositivo

También puede usar la vinculación para gestionar las actualizaciones de la interfaz de usuario 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, ampliamos el ejemplo anterior y mostramos cómo colocar dinámicamente la barra de herramientas de tinta en función de la orientación del dispositivo usando la vinculación, un objeto ViewModel y la interfaz INotifyPropertyChanged.

Descargue este ejemplo del ejemplo dinámico de ubicación y orientación de la barra de herramientas Ink

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

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

    2. Agregue una nueva clase a la carpeta ViewModels (por este ejemplo, la llamamos InkToolbarSnippetHostViewModel.cs).

      Nota:

      Hemos usado 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 instancia y una propiedad estática de solo lectura denominada Instance. Haga que el constructor sea privado para garantizar que solo se pueda acceder a esta clase a través de la propiedad Instance.

      Nota:

      Esta clase hereda de la interfaz INotifyPropertyChanged, que se utiliza para notificar a los clientes, normalmente los clientes de enlace, que un valor de propiedad ha cambiado. Vamos a usar esto para controlar los cambios en la orientación del dispositivo (expandiremos este código y explicaremos 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 (misma funcionalidad que el ejemplo de solo XAML anterior) y VerticalLayout (orientación del dispositivo).

      Nota:

      La propiedad PortraitLayout es ajustable 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 de convertidor 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 Convertidores.

    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 tinta 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 tinta 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 (aunque está destinado a mejorar la facilidad de uso, esto es simplemente una elecció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. Añade using using locationandorientation.ViewModels a la lista de namespaces para asociar nuestro 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. A continuación, abra el archivo MainPage.xaml.

    1. Agregue xmlns:converters="using:locationandorientation.Converters" al Page elemento 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 PageResources elemento 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 y 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 PortraitLayout y LeftHandedLayout propiedades bool a la InkToolbarSnippetHostViewModel clase, junto con la funcionalidad para admitir el 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ías tener una aplicación de escritura que se adapte a la preferencia de mano dominante del usuario y responda de manera dinámica a la orientación del dispositivo del usuario.

Especificar el botón seleccionado

Botón de lápiz seleccionado en la inicialización
Barra de herramientas de Windows Ink con el botón de lápiz seleccionado en la inicialización

De forma predeterminada, se selecciona el primer botón (o más izquierdo) cuando se inicia la aplicación y se inicializa la barra de herramientas. En la barra de herramientas predeterminada de Ink de Windows, este es el botón del bolígrafo.

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 desea activar de forma 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 lápiz).

  1. Usa la declaración XAML para InkCanvas y 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 InkToolbarPencilButton incorporado.

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

    1. Establezca ActiveTool en el objeto devuelto en el 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

Botones específicos incluidos en la inicialización
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.

Puedes hacerlo mediante XAML o código subyacente.

XAML

Modifique la declaración XAML para InkCanvas y InkToolbar en el 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. Use la declaración XAML para InkCanvas y 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 InkToolbarBallpointPenButton, InkToolbarPencilButton y InkToolbarEraserButton solo.

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 funcionalidades de escritura a mano

Puede personalizar y ampliar la colección de botones (y funcionalidades de escritura manual 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 simultáneamente 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 se 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 (activado a través de un botón de lápiz personalizado) donde defina la paleta de colores de lápiz y las propiedades de la punta del lápiz, como la forma, la rotación y el tamaño.

Botón de pluma caligráfica personalizada
Botón de lápiz caligráfico personalizado

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

Código subyacente

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

  1. Haga clic con el botón derecho en el proyecto 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 deriva de InkToolbarCustomPen.
class CalligraphicPen : InkToolbarCustomPen
{
}
  1. Sobrescriba CreateInkDrawingAttributesCore para especificar su propio pincel y tamaño de 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 puntas, el tamaño del trazo y el color de entrada de lápiz.
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) definido 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 un 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 de tamaño de 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 (activado a través de un botón de alternancia personalizado) para establecer el estado de una característica definida por la aplicación en activado o desactivado. Cuando está activada, la característica funciona junto con la herramienta activa.

En este ejemplo, definimos un botón de alternancia personalizado que habilita la entrada manuscrita con entrada táctil (de forma predeterminada, la entrada manuscrita táctil no está habilitada).

Nota:

Si necesita admitir la entrada manuscrita con entrada táctil, se recomienda habilitarla mediante customToggleButton, con el icono y la información sobre herramientas especificadas en este ejemplo.

Normalmente, la entrada táctil se usa para manipular directamente un objeto o la interfaz de usuario de la aplicación. Para demostrar las diferencias en el comportamiento cuando el entintado táctil está habilitado, colocamos InkCanvas dentro de un contenedor ScrollViewer y establecemos las dimensiones del ScrollViewer para que sean más pequeñas que InkCanvas.

Cuando se inicia la aplicación, solo se admite la escritura con lápiz y la entrada táctil se utiliza para desplazar o acercar la superficie de escritura. Cuando se habilita la tinta táctil, la superficie de tinta no puede desplazarse ni ampliarse a través de la entrada de toque.

Nota:

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

  • InkToolbar y el dibujo digital en general se experimentan mejor a través de 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 se admite la tinta con entrada táctil, recomendamos usar el icono "ED5F" de la fuente "Segoe MLD2 Assets" para el botón de alternancia, con un mensaje emergente "Escritura táctil".

XAML

  1. En primer lugar, declaramos un elemento InkToolbarCustomToggleButton (toggleButton) con un agente de escucha de eventos Click 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 de código anterior, declaramos un agente de escucha de eventos Click y un controlador (Toggle_Custom) en el botón de alternancia personalizado para la entrada manuscrita táctil (toggleButton). Este controlador simplemente activa o desactiva 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 de código siguiente incluye tanto el controlador de eventos Click 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

Puedes crear un botón de herramienta personalizado para invocar una herramienta que no sea de lápiz definida por la aplicación.

De forma predeterminada, un InkPresenter procesa toda la entrada como un trazo de lápiz o un trazo de borrado. Esto incluye la entrada modificada por una prestación de hardware secundaria, como un botón lateral del 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 personalizado que, cuando se selecciona, hace que los trazos subsiguientes se procesen y se representen como un lazo de selección (línea discontinua) en lugar de tinta. Todos los trazos de lápiz dentro de los límites del área de selección se establecen en Seleccionado.

Nota:

Consulte los controles de escritura a mano y las directrices de UX de InkCanvas e InkToolbar. La recomendación siguiente es relevante para este ejemplo:

  • Si se proporciona la selección de trazo, recomendamos usar el icono "EF20" de la fuente "Segoe MLD2 Assets" para el botón de herramienta, con una descripción emergente "Herramienta de selección".

XAML

  1. En primer lugar, declaramos un elemento InkToolbarCustomToolButton (customToolButton) con un agente de escucha de eventos Click 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 Canvas 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, gestionamos el evento de clic para InkToolbarCustomToolButton en el archivo de código subyacente de MainPage.xaml.cs.

    Este controlador configura InkPresenter para pasar la entrada sin procesar a la aplicación.

    Para obtener una explicación más detallada de este código, consulte la sección de entrada de paso directo para el procesamiento avanzado de interacciones del 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 de código siguiente incluye tanto el controlador de eventos Click 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 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);
            }
        }
    }
}

Representación de entrada de lápiz personalizada

De forma predeterminada, la entrada de lápiz se procesa en un subproceso de fondo de baja latencia y se representa como "húmedo" cuando se dibuja. Al completar el trazo (lápiz, se levanta el dedo o se suelta el botón del ratón), el trazo se procesa en el hilo de la interfaz de usuario y se muestra como "seco" en la capa de InkCanvas (encima del contenido de la aplicación y reemplazando la tinta mojada).

La plataforma de tinta digital te permite invalidar este comportamiento y personalizar completamente la experiencia de escritura a mano mediante el secado personalizado de la entrada de tinta.

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

Nota:

Secado personalizado y InkToolbar
Si la aplicación invalida el comportamiento de representación de entrada de lápiz predeterminada de InkPresenter con una implementación de secado personalizada, los trazos de lápiz 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, realizar pruebas de impacto en cada trazo y sobrescribir el comando integrado "Borrar toda la tinta digital".

Ejemplos de temas

Otros ejemplos