Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Importante
InkCanvas, InkToolbary InkPresenter son API experimentales. La compatibilidad con la entrada manuscrita se agregó en SDK de Aplicaciones para Windows 2.4.1-experimental. En este artículo se describe SDI de Aplicaciones para Windows 2.5.4-experimental, que ha cambiado la forma de conectar un InkToolbar. Estas API no están disponibles en el canal estable. Pueden cambiar o quitarse en una versión futura. No los use en aplicaciones de producción.
Use el control experimental InkCanvas para permitir que los usuarios dibujen con lápiz, mouse o entrada táctil. Agregue un InkToolbar para proporcionar a los usuarios un bolígrafo, un lápiz, un subrayador y un borrador, con opciones de color y tamaño. Cada lienzo tiene un InkPresenter que recopila, procesa y muestra la entrada manuscrita. El InkStrokeContainer del presentador almacena los trazos, por lo que puede guardar, cargar, copiar y crear entradas manuscritas mediante código.
Para probar los controles, instale el paquete experimental SDK de Aplicaciones para Windows 2.5.4 y el entorno de ejecución coincidente. Para obtener vínculos de descarga e información de configuración, consulte Descargas de SDK de Aplicaciones para Windows más recientes.
¿Es este el control adecuado?
Use un lienzo de entrada manuscrita cuando los usuarios necesiten:
- Tome notas manuscritas o anote el contenido.
- Haga bocetos, dibuje diagramas o firme su nombre.
- Crea contenido a mano alzada que la aplicación guarda y les permite editarlo más adelante.
Las API experimentales de entintado no incluyen reconocimiento de escritura a mano ni de formas. Para recopilar texto, use un cuadro de texto.
Si necesita la entrada manuscrita en una aplicación de producción, consulte Brechas de control conocidas para el estado de estos controles en el canal estable y para ver alternativas.
Comprender el modelo de entrada manuscrita
Las APIs de entintado funcionan juntas:
| API | Rol |
|---|---|
InkCanvas |
Elemento XAML que muestra la entrada manuscrita y recibe la entrada manuscrita. Su propiedad InkPresenter devuelve el presentador del lienzo. |
InkPresenter |
Recopila la entrada de tinta, representa los trazos y contiene la configuración de entrada y los atributos de dibujo predeterminados. No puede crear un InkPresenter por sí mismo. Obtenerlo de InkCanvas.InkPresenter. |
InkStrokeContainer |
Almacena trazos. Úselo para guardar, cargar, seleccionar, copiar y pegar trazos de tinta. Obtenerlo de InkPresenter.StrokeContainer. |
InkToolbar |
Una barra de herramientas opcional que permite a los usuarios elegir un bolígrafo, un lápiz, un subrayador o un borrador, y cambiar el color y el tamaño de la entrada manuscrita. |
InkCanvas no tiene la propiedad Background. Para proporcionar al área de dibujo un fondo y un borde, coloque el lienzo dentro de un contenedor como Border, y establezca el tamaño en el contenedor. El color de tinta predeterminado es negro, por lo que los ejemplos de este artículo usan un fondo blanco.
Varios de estos tipos tienen los mismos nombres que los tipos del espacio de nombres Windows.UI.Input.Inking de Windows Runtime, como InkPresenter, InkStrokeContainer y InkHighContrastAdjustment. Los controles de entrada manuscrita usan las versiones Microsoft.UI.Xaml.Controls. Los trazos y los atributos de dibujo siguen usando tipos de Windows Runtime, como InkStroke, InkDrawingAttributesy InkStrokeBuilder. Si importa ambos espacios de nombres y usa un nombre ambiguo, el compilador de C# notifica el error CS0104.
Los ejemplos de este artículo importan Microsoft.UI.Xaml.Controls y usan directivas de alias para los Windows.UI.Input.Inking tipos que necesitan:
using Microsoft.UI.Xaml.Controls;
using InkDrawingAttributes = Windows.UI.Input.Inking.InkDrawingAttributes;
using InkStroke = Windows.UI.Input.Inking.InkStroke;
También puede importar espacios de nombres y usar nombres completos para los tipos ambiguos, como hace el ejemplo de la galería de WinUI 3.
InkPresenter también proporciona API de nivel inferior que este artículo no cubre:
-
StrokeInputgenera eventos cuando se inicia, continúa, finaliza o se cancela un trazo. -
UnprocessedInputgenera eventos de puntero para la entrada que el presentador no procesa como entrada manuscrita. -
InputConfigurationestablece si el presentador procesa la entrada desde el borrador de un lápiz y el botón contextual principal. -
InputProcessingConfigurationestablece si la entrada dibuja tinta, borra la tinta o no se procesa, y cómo controla el presentador el arrastre con el botón derecho. -
ActivateCustomDryingdevuelve unInkSynchronizerpara las aplicaciones que representan los trazos de tinta confirmados.
Configura tu proyecto
Las API de entrada manuscrita están marcadas como experimentales. Cuando los usas en un proyecto de C#, el compilador de C# notifica la advertencia CS8305 y el compilador XAML notifica la advertencia WMC1501. Si el proyecto trata las advertencias como errores, estas advertencias detienen la compilación.
Después de decidir usar las API experimentales, puede suprimir estas advertencias. Agregue las siguientes propiedades al archivo de proyecto de C# (.csproj):
<PropertyGroup>
<NoWarn>$(NoWarn);CS8305</NoWarn>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);WMC1501</MSBuildWarningsAsMessages>
</PropertyGroup>
En los ejemplos de este artículo se usa C#. Cada ejemplo completo muestra el XAML de una página y la clase de code-behind de la página, MainPage.
Crear un lienzo de tinta
![]()
La aplicación Galería de WinUI 3 incluye ejemplos interactivos de características y controles winUI. Obtenga la aplicación desde el Microsoft Store o busque el código fuente en GitHub.
En el ejemplo siguiente se coloca un lienzo dentro de un Border blanco. El código subyacente establece los tipos de dispositivo de entrada para que los usuarios puedan dibujar con un lápiz, un mouse o una entrada táctil.
<Border
Height="300"
Background="White"
BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
BorderThickness="1">
<InkCanvas x:Name="inkCanvas" AutomationProperties.Name="Drawing surface" />
</Border>
using Microsoft.UI.Xaml.Controls;
using Windows.UI.Core;
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
// Mouse input doesn't draw ink by default. Choose every device type that can draw ink.
inkCanvas.InkPresenter.InputDeviceTypes =
CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Touch;
}
}
Para mantenerlos cortos, algunos ejemplos posteriores no repiten este código. Establece InputDeviceTypes en cada página donde los usuarios dibujan con un mouse.
Conectar una barra de herramientas de entrada manuscrita
Coloque un InkToolbar cerca del lienzo y establezca su propiedad TargetInkCanvas para conectar los dos:
<StackPanel Spacing="8">
<InkToolbar
x:Name="inkToolbar"
AutomationProperties.Name="Drawing tools"
TargetInkCanvas="{x:Bind inkCanvas}" />
<Border
Height="300"
Background="White"
BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
BorderThickness="1">
<InkCanvas x:Name="inkCanvas" AutomationProperties.Name="Drawing surface" />
</Border>
</StackPanel>
A partir de SDK de Aplicaciones para Windows 2.5.4-experimental, puede conectar una barra de herramientas directamente a un InkPresenter estableciendo la TargetInkPresenter propiedad . Establézcalo en el código:
inkToolbar.TargetInkPresenter = inkCanvas.InkPresenter;
También puedes establecerlo en XAML: TargetInkPresenter="{x:Bind inkCanvas.InkPresenter}". Establezca solo una de las dos propiedades de destino.
De forma predeterminada, la barra de herramientas muestra los botones bolígrafo, lápiz, subrayador, borrador y plantilla (regla). Para cambiar qué botones aparecen, consulte Personalizar la barra de herramientas de entrada de lápiz.
Seleccionar dispositivos de entrada
La InputDeviceTypes propiedad usa la CoreInputDeviceTypes enumeración flags del Windows.UI.Core espacio de nombres . La entrada del mouse no realiza entradas manuscritas forma predeterminada. Combine valores para elegir qué dispositivos realizan entradas manuscritas:
// Add mouse input and keep the other device types.
inkCanvas.InkPresenter.InputDeviceTypes |= CoreInputDeviceTypes.Mouse;
// Stop accepting ink from touch input.
inkCanvas.InkPresenter.InputDeviceTypes &= ~CoreInputDeviceTypes.Touch;
// Check whether pen input is enabled.
bool isPenEnabled = inkCanvas.InkPresenter.InputDeviceTypes.HasFlag(CoreInputDeviceTypes.Pen);
Para dejar de recopilar trazos de tinta de todos los dispositivos, por ejemplo, para mostrar un dibujo que los usuarios no pueden cambiar, establezca IsInputEnabled en false:
inkCanvas.InkPresenter.IsInputEnabled = false;
Para permitir que los usuarios activen o desactiven un dispositivo de entrada desde la barra de herramientas, consulte Agregar un botón de alternancia personalizado.
Establecer atributos de dibujo
Los atributos de dibujo, como color, tamaño y forma de punta del lápiz, controlan el aspecto de los trazos. Usan la clase Windows RuntimeInkDrawingAttributes. Para cambiar los atributos de los nuevos trazos, llame a CopyDefaultDrawingAttributes, cambie la copia y pásela a UpdateDefaultDrawingAttributes:
InkDrawingAttributes attributes = inkCanvas.InkPresenter.CopyDefaultDrawingAttributes();
attributes.Color = Colors.DarkBlue;
attributes.Size = new Size(4, 4);
inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(attributes);
Colors está en el Microsoft.UI espacio de nombres y Size está en el Windows.Foundation espacio de nombres . Los nuevos atributos se aplican a los trazos que los usuarios dibujan después de la llamada. Los trazos existentes conservan sus atributos.
Al conectar un InkToolbar, la herramienta activa y su configuración de color y tamaño determinan los atributos de dibujo. Para leer los atributos actuales, use la propiedad InkDrawingAttributes de la barra de herramientas.
Crear preajustes de bolígrafo
Si no usa una barra de herramientas, puede ofrecer sus propios preajustes del lápiz. En el ejemplo siguiente se aplican los atributos de bolígrafo, lápiz, resaltador o caligrafía cuando el usuario selecciona un valor preestablecido o cambia el tamaño.
<StackPanel Spacing="12">
<RadioButtons
x:Name="presetButtons"
Header="Pen preset"
MaxColumns="4"
SelectedIndex="0"
SelectionChanged="PresetButtons_SelectionChanged">
<x:String>Ballpoint</x:String>
<x:String>Pencil</x:String>
<x:String>Highlighter</x:String>
<x:String>Calligraphy</x:String>
</RadioButtons>
<Slider
x:Name="sizeSlider"
Width="240"
HorizontalAlignment="Left"
Header="Size"
Maximum="16"
Minimum="1"
Value="4"
ValueChanged="SizeSlider_ValueChanged" />
<Border
Width="480"
Height="280"
HorizontalAlignment="Left"
Background="White"
BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
BorderThickness="1">
<InkCanvas x:Name="inkCanvas" AutomationProperties.Name="Drawing surface" />
</Border>
</StackPanel>
using System;
using System.Numerics;
using Microsoft.UI;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Windows.Foundation;
using Windows.UI.Core;
using InkDrawingAttributes = Windows.UI.Input.Inking.InkDrawingAttributes;
using PenTipShape = Windows.UI.Input.Inking.PenTipShape;
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
inkCanvas.InkPresenter.InputDeviceTypes =
CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Touch;
ApplyPreset();
}
private void PresetButtons_SelectionChanged(object sender, SelectionChangedEventArgs e) => ApplyPreset();
private void SizeSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e) => ApplyPreset();
private void ApplyPreset()
{
// SelectionChanged and ValueChanged can occur while the page is still loading.
if (inkCanvas is null || presetButtons is null || sizeSlider is null)
{
return;
}
double size = sizeSlider.Value;
InkDrawingAttributes attributes;
switch (presetButtons.SelectedItem as string)
{
case "Pencil":
// CreateForPencil returns attributes for pencil-style strokes.
attributes = InkDrawingAttributes.CreateForPencil();
attributes.Color = Colors.DimGray;
attributes.Size = new Size(size, size);
break;
case "Highlighter":
attributes = new InkDrawingAttributes
{
Color = Colors.Yellow,
DrawAsHighlighter = true,
PenTip = PenTipShape.Rectangle,
Size = new Size(size, size * 3),
};
break;
case "Calligraphy":
attributes = new InkDrawingAttributes
{
Color = Colors.DarkBlue,
PenTip = PenTipShape.Rectangle,
Size = new Size(Math.Max(1, size / 2), size * 2),
// Rotate the nib 45 degrees so the stroke width changes with the stroke direction.
PenTipTransform = Matrix3x2.CreateRotation(MathF.PI / 4),
};
break;
default:
attributes = new InkDrawingAttributes
{
Color = Colors.Black,
PenTip = PenTipShape.Circle,
Size = new Size(size, size),
};
break;
}
// The new attributes apply to strokes that the user draws after this call.
inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(attributes);
}
}
-
InkDrawingAttributes.CreateForPencildevuelve atributos para trazos de lápiz. LaKindpropiedad es de solo lectura, por lo que usa este método para crear atributos de lápiz. -
DrawAsHighlightermuestra el trazo como una superposición de resaltador. -
PenTipestablece la forma de la punta del bolígrafo yPenTipTransformla gira o la escala. Una punta rectangular inclinada genera trazos de estilo caligráfico.
Responder a los cambios de entrada manuscrita
El presentador activa StrokesCollected después de que un usuario dibuje trazos y StrokesErased después de que un usuario los borre. La Strokes propiedad de los argumentos de cada evento contiene los trazos afectados. El código siguiente mantiene actualizado un recuento de pulsaciones en un TextBlock denominado strokeCountText:
inkCanvas.InkPresenter.StrokesCollected += (sender, args) => UpdateStrokeCount();
inkCanvas.InkPresenter.StrokesErased += (sender, args) => UpdateStrokeCount();
private void UpdateStrokeCount()
{
int count = inkCanvas.InkPresenter.StrokeContainer.GetStrokes().Count;
strokeCountText.Text = $"{count} strokes";
}
Estos eventos se producen cuando los usuarios realizan entradas manuscritas o las borran. Al cambiar los trazos en el código, por ejemplo, con Clear, LoadAsync o AddStroke, se actualiza la interfaz de usuario después de la llamada.
Guardar y cargar tinta
InkStrokeContainer guarda la entrada manuscrita en Formato serializado de tinta (ISF). Para elegir cómo se almacenan los datos, pase un InkPersistenceFormat valor a SaveAsync:
| Formato | Output |
|---|---|
GifWithEmbeddedIsf |
Imagen GIF que contiene los datos de tinta. Los visores de imágenes pueden mostrar el archivo y la aplicación puede volver a cargar el trazo de tinta para su edición. |
Isf |
Solo datos de entrada manuscrita, sin una imagen. Este es el formato más compacto. |
Agregue los métodos siguientes al código subyacente de una página que contiene un InkCanvas denominado inkCanvas.
SaveInkAsync escribe la entrada manuscrita en un archivo y LoadInkAsync reemplaza la entrada manuscrita en el lienzo por la entrada manuscrita de un archivo.
LoadAsync quita los trazos existentes antes de agregar los trazos cargados, por lo que no es necesario llamar Clear primero.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Controls;
using Windows.Storage.Streams;
using InkPersistenceFormat = Windows.UI.Input.Inking.InkPersistenceFormat;
public sealed partial class MainPage : Page
{
private async Task SaveInkAsync(string path)
{
InkStrokeContainer container = inkCanvas.InkPresenter.StrokeContainer;
// File.Create replaces any existing file, so the file contains only the new ink.
using FileStream fileStream = File.Create(path);
using IOutputStream output = fileStream.AsOutputStream();
// GifWithEmbeddedIsf writes a GIF image that also contains the editable ink data.
await container.SaveAsync(output, InkPersistenceFormat.GifWithEmbeddedIsf);
await output.FlushAsync();
}
private async Task LoadInkAsync(string path)
{
InkStrokeContainer container = inkCanvas.InkPresenter.StrokeContainer;
using FileStream fileStream = File.OpenRead(path);
using IInputStream input = fileStream.AsInputStream();
// LoadAsync removes the current strokes before it adds the loaded strokes.
await container.LoadAsync(input);
}
}
AsOutputStream y AsInputStream convierten secuencias de .NET en los tipos de flujo de Windows Runtime que SaveAsync y LoadAsync usan. Para utilizar await con estos métodos de Windows Runtime, el archivo necesita la directiva using System;. Sin él, el compilador notifica el error CS4036.
Para permitir que los usuarios elijan un archivo, use los selectores de archivos SDK de Aplicaciones para Windows, que devuelven la ruta de acceso del archivo seleccionado. Para obtener más información, vea Guardar un archivo con un selector de SDK de Aplicaciones para Windows y Abrir archivos y carpetas con selectores de SDK de Aplicaciones para Windows.
Para mantener la tinta digital en la memoria, por ejemplo, para restaurarla más adelante en la misma sesión, guárdela en un InMemoryRandomAccessStream. Pase el resultado GetOutputStreamAt(0) de la secuencia a SaveAsync, y pase su resultado de GetInputStreamAt(0) a LoadAsync. El ejemplo de la galería de WinUI 3 usa este enfoque.
Copiar y pegar entrada manuscrita
InkStrokeContainer puede copiar trazos en el portapapeles y pegar entrada manuscrita desde el mismo.
CopySelectedToClipboard copia solo los trazos seleccionados, por lo que en el ejemplo siguiente se seleccionan todos los trazos del lienzo izquierdo antes de copiarlos.
CanPasteFromClipboard comprueba si el Portapapeles contiene tinta digital.
PasteFromClipboard agrega la entrada manuscrita en la posición que se pasa y devuelve el rectángulo delimitador del área que cambió.
<StackPanel Spacing="12">
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Click="CopyButton_Click" Content="Copy left canvas" />
<Button
x:Name="pasteButton"
Click="PasteButton_Click"
Content="Paste into right canvas"
IsEnabled="False" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="12">
<Border
Width="300"
Height="220"
Background="White"
BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
BorderThickness="1">
<InkCanvas x:Name="sourceCanvas" AutomationProperties.Name="Source drawing surface" />
</Border>
<Border
Width="300"
Height="220"
Background="White"
BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
BorderThickness="1">
<InkCanvas x:Name="targetCanvas" AutomationProperties.Name="Target drawing surface" />
</Border>
</StackPanel>
<TextBlock x:Name="statusText" Text="Draw on the left canvas, then copy it." TextWrapping="Wrap" />
</StackPanel>
using System.Collections.Generic;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Foundation;
using Windows.UI.Core;
using InkStroke = Windows.UI.Input.Inking.InkStroke;
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
foreach (InkCanvas canvas in new[] { sourceCanvas, targetCanvas })
{
canvas.InkPresenter.InputDeviceTypes =
CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Touch;
}
}
private void CopyButton_Click(object sender, RoutedEventArgs e)
{
InkStrokeContainer source = sourceCanvas.InkPresenter.StrokeContainer;
IReadOnlyList<InkStroke> strokes = source.GetStrokes();
if (strokes.Count == 0)
{
statusText.Text = "Draw on the left canvas first.";
return;
}
// CopySelectedToClipboard copies only selected strokes, so select all strokes first.
foreach (InkStroke stroke in strokes)
{
stroke.Selected = true;
}
source.CopySelectedToClipboard();
foreach (InkStroke stroke in strokes)
{
stroke.Selected = false;
}
pasteButton.IsEnabled = targetCanvas.InkPresenter.StrokeContainer.CanPasteFromClipboard();
statusText.Text = $"Copied {strokes.Count} stroke(s) to the clipboard.";
}
private void PasteButton_Click(object sender, RoutedEventArgs e)
{
InkStrokeContainer target = targetCanvas.InkPresenter.StrokeContainer;
if (!target.CanPasteFromClipboard())
{
statusText.Text = "The clipboard doesn't contain ink.";
return;
}
// Place the pasted ink near the upper-left corner of the target canvas.
Rect pasted = target.PasteFromClipboard(new Point(16, 16));
statusText.Text = $"Pasted ink into a {pasted.Width:0} x {pasted.Height:0} area.";
}
}
Para trabajar con algunos de los trazos, establezca la Selected propiedad de cada trazo que desee, o llame a SelectWithLine o SelectWithPolyLine. Para eliminar o mover los trazos seleccionados, use DeleteSelected o MoveSelected.
Crear trazos en el código
Use la clase Windows Runtime InkStrokeBuilder para crear trazos a partir de puntos y agréguelos al lienzo con AddStroke. En el ejemplo siguiente se agrega una estrella y una línea de firma.
<StackPanel Spacing="12">
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Click="AddStarButton_Click" Content="Add star" />
<Button Click="AddSignatureLineButton_Click" Content="Add signature line" />
<Button Click="ClearButton_Click" Content="Clear" />
</StackPanel>
<Border
Width="480"
Height="280"
HorizontalAlignment="Left"
Background="White"
BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
BorderThickness="1">
<InkCanvas x:Name="inkCanvas" AutomationProperties.Name="Drawing surface" />
</Border>
</StackPanel>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Foundation;
using Windows.UI;
using InkDrawingAttributes = Windows.UI.Input.Inking.InkDrawingAttributes;
using InkPoint = Windows.UI.Input.Inking.InkPoint;
using InkStroke = Windows.UI.Input.Inking.InkStroke;
using InkStrokeBuilder = Windows.UI.Input.Inking.InkStrokeBuilder;
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
}
private void AddStarButton_Click(object sender, RoutedEventArgs e)
{
// Alternate between outer and inner points to draw a five-pointed star.
Point center = new(inkCanvas.ActualWidth / 2, 90);
List<Point> points = new();
for (int i = 0; i <= 10; i++)
{
double radius = i % 2 == 0 ? 70 : 28;
double angle = -Math.PI / 2 + i * Math.PI / 5;
points.Add(new Point(center.X + radius * Math.Cos(angle), center.Y + radius * Math.Sin(angle)));
}
AddStroke(points, Colors.Goldenrod, 4);
}
private void AddSignatureLineButton_Click(object sender, RoutedEventArgs e)
{
double left = 40;
double right = inkCanvas.ActualWidth - 40;
double baseline = inkCanvas.ActualHeight - 40;
AddStroke(new[] { new Point(left, baseline), new Point(right, baseline) }, Colors.Gray, 2);
AddStroke(new[] { new Point(left, baseline - 28), new Point(left + 18, baseline - 10) }, Colors.Gray, 2);
AddStroke(new[] { new Point(left + 18, baseline - 28), new Point(left, baseline - 10) }, Colors.Gray, 2);
}
private void ClearButton_Click(object sender, RoutedEventArgs e)
{
inkCanvas.InkPresenter.StrokeContainer.Clear();
}
private void AddStroke(IEnumerable<Point> points, Color color, double size)
{
InkStrokeBuilder builder = new();
builder.SetDefaultDrawingAttributes(new InkDrawingAttributes
{
Color = color,
Size = new Size(size, size),
// Keep straight segments and sharp corners instead of smoothing them into curves.
FitToCurve = false,
});
// Give every point the same pressure so that the stroke has a uniform width.
IEnumerable<InkPoint> inkPoints = points.Select(point => new InkPoint(point, 0.5f));
InkStroke stroke = builder.CreateStrokeFromInkPoints(inkPoints, Matrix3x2.Identity);
inkCanvas.InkPresenter.StrokeContainer.AddStroke(stroke);
}
}
-
CreateStrokeFromInkPointscrea un trazo a partir deInkPointvalores. Cada punto combina una posición en el lienzo con un valor de presión. - La documentación de Windows Runtime recomienda
CreateStrokeFromInkPointsen lugar deCreateStrokepara trazos que agregue a un presentador de entrada manuscrita. - Cuando
FitToCurveesfalse, el trazo usa segmentos rectos entre puntos en lugar de curvas.
Un trazo solo puede pertenecer a un único contenedor. Para copiar trazos en otro lienzo, agregue clones de los trazos:
private void CopyStrokesTo(InkCanvas target)
{
foreach (InkStroke stroke in inkCanvas.InkPresenter.StrokeContainer.GetStrokes())
{
target.InkPresenter.StrokeContainer.AddStroke(stroke.Clone());
}
}
Personalizar la barra de herramientas de entrada manuscrita
Elegir los controles iniciales
La InitialControls propiedad determina qué botones integrados crea la barra de herramientas:
| Value | Botones integrados |
|---|---|
All (valor predeterminado) |
Bolígrafo, lápiz, subrayador, borrador y plantilla (regla) |
PensOnly |
Bolígrafo, lápiz y resaltador |
AllExceptPens |
Goma de borrar y plantilla (regla) |
None |
None. Declare los botones que desee como elementos secundarios de la barra de herramientas. |
En el ejemplo siguiente se declaran los mismos cuatro botones que el ejemplo de la galería de WinUI 3, se asigna a cada uno un nombre accesible y se selecciona el bolígrafo cuando se carga la barra de herramientas:
<StackPanel Spacing="8">
<InkToolbar
x:Name="inkToolbar"
AutomationProperties.Name="Drawing tools"
InitialControls="None"
TargetInkCanvas="{x:Bind inkCanvas}">
<InkToolbarBallpointPenButton x:Name="ballpointButton" AutomationProperties.Name="Ballpoint pen" />
<InkToolbarPencilButton AutomationProperties.Name="Pencil" />
<InkToolbarHighlighterButton AutomationProperties.Name="Highlighter" />
<InkToolbarEraserButton AutomationProperties.Name="Eraser" />
</InkToolbar>
<Border
Height="300"
Background="White"
BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
BorderThickness="1">
<InkCanvas x:Name="inkCanvas" AutomationProperties.Name="Drawing surface" />
</Border>
</StackPanel>
using Microsoft.UI.Xaml.Controls;
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
// Select a starting tool after the toolbar loads.
inkToolbar.Loaded += (sender, args) =>
{
if (inkToolbar.ActiveTool is null)
{
inkToolbar.ActiveTool = ballpointButton;
}
};
}
}
Para mostrar un comando que borra toda la entrada manuscrita, establezca la propiedad IsClearAllVisible del botón del borrador en true. La barra de herramientas genera el EraseAllClicked evento cuando el usuario selecciona el comando.
Personalizar los colores y anchos de un bolígrafo
Cada botón de lápiz tiene una Palette selección de pinceles y ajustes de ancho de trazo. Establezca Palette en el código. Para más información, consulte las limitaciones conocidas. En el ejemplo siguiente se reemplaza la paleta de un bolígrafo por cuatro colores de marca, se limita el ancho del trazo y se muestran los atributos del bolígrafo activo.
<StackPanel Spacing="12">
<InkToolbar
x:Name="inkToolbar"
AutomationProperties.Name="Drawing tools"
InitialControls="AllExceptPens"
TargetInkCanvas="{x:Bind inkCanvas}">
<InkToolbarBallpointPenButton
x:Name="brandPen"
AutomationProperties.Name="Brand pen"
MaxStrokeWidth="16"
MinStrokeWidth="2"
SelectedStrokeWidth="6" />
</InkToolbar>
<RadioButtons
x:Name="colorButtons"
Header="Pen color"
MaxColumns="4"
SelectedIndex="0"
SelectionChanged="ColorButtons_SelectionChanged">
<x:String>Blue</x:String>
<x:String>Green</x:String>
<x:String>Orange</x:String>
<x:String>Purple</x:String>
</RadioButtons>
<TextBlock x:Name="penInfo" />
<Border
Width="480"
Height="240"
HorizontalAlignment="Left"
Background="White"
BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
BorderThickness="1">
<InkCanvas x:Name="inkCanvas" AutomationProperties.Name="Drawing surface" />
</Border>
</StackPanel>
using System.Linq;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
public sealed partial class MainPage : Page
{
private static readonly Color[] BrandColors =
{
Color.FromArgb(0xFF, 0x00, 0x78, 0xD4), // Blue
Color.FromArgb(0xFF, 0x10, 0x7C, 0x10), // Green
Color.FromArgb(0xFF, 0xD8, 0x3B, 0x01), // Orange
Color.FromArgb(0xFF, 0x5C, 0x2D, 0x91), // Purple
};
public MainPage()
{
InitializeComponent();
// Replace the pen's default palette and select the first color.
brandPen.Palette = BrandColors.Select(color => (Brush)new SolidColorBrush(color)).ToList();
brandPen.SelectedBrushIndex = 0;
inkToolbar.InkDrawingAttributesChanged += (sender, args) => ShowPenInfo();
// The toolbar sets up its buttons when it loads, so select the starting tool then.
inkToolbar.Loaded += (sender, args) =>
{
inkToolbar.ActiveTool = brandPen;
ShowPenInfo();
};
}
private void ColorButtons_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// SelectionChanged can occur while the page is still loading.
if (brandPen is null || colorButtons is null || penInfo is null || colorButtons.SelectedIndex < 0)
{
return;
}
// SelectedBrushIndex selects an entry in Palette, like choosing a color in the pen flyout.
brandPen.SelectedBrushIndex = colorButtons.SelectedIndex;
inkToolbar.ActiveTool = brandPen;
ShowPenInfo();
}
private void ShowPenInfo()
{
// InkDrawingAttributes describes the ink that the active tool draws.
if (inkToolbar.InkDrawingAttributes is { } attributes)
{
penInfo.Text = $"Color {attributes.Color}, width {attributes.Size.Width:0} (range {brandPen.MinStrokeWidth:0}-{brandPen.MaxStrokeWidth:0})";
}
}
}
SelectedBrushIndex selecciona un color en Palette.
SelectedStrokeWidth, MinStrokeWidth y MaxStrokeWidth establecen el ancho del trazo actual y el rango de valores entre los que los usuarios pueden elegir. La barra de herramientas emite InkDrawingAttributesChanged cuando cambian los atributos de la herramienta activa.
Seleccione la herramienta activa
Use ActiveTool para seleccionar una herramienta y controle ActiveToolChanged para responder cuando el usuario selecciona otra.
GetToolButton devuelve un botón de herramienta integrado del tipo que se le pasa. Es posible que los botones integrados no existan hasta que se cargue la barra de herramientas, por lo que seleccione una herramienta de inicio en el controlador de eventos de Loaded de la barra de herramientas.
<StackPanel Spacing="12">
<InkToolbar
x:Name="inkToolbar"
AutomationProperties.Name="Drawing tools"
TargetInkCanvas="{x:Bind inkCanvas}" />
<TextBlock x:Name="toolInfo" />
<Border
Width="480"
Height="280"
HorizontalAlignment="Left"
Background="White"
BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
BorderThickness="1">
<InkCanvas x:Name="inkCanvas" AutomationProperties.Name="Drawing surface" />
</Border>
</StackPanel>
using Microsoft.UI.Xaml.Controls;
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
inkToolbar.ActiveToolChanged += (sender, args) => ShowToolInfo();
inkToolbar.InkDrawingAttributesChanged += (sender, args) => ShowToolInfo();
// The toolbar creates its built-in buttons when it loads, so select the starting tool then.
inkToolbar.Loaded += (sender, args) =>
{
inkToolbar.ActiveTool = inkToolbar.GetToolButton(InkToolbarTool.Pencil);
ShowToolInfo();
};
}
private void ShowToolInfo()
{
if (inkToolbar.ActiveTool is not { } tool)
{
toolInfo.Text = "No active tool";
return;
}
toolInfo.Text = inkToolbar.InkDrawingAttributes is { } attributes
? $"{tool.ToolKind}: {attributes.Kind} ink, color {attributes.Color}, size {attributes.Size.Width:0}"
: $"{tool.ToolKind}";
}
}
Cambiar la orientación
Para mostrar la barra de herramientas verticalmente, por ejemplo junto al lienzo, establezca Orientation en Vertical:
<Grid HorizontalAlignment="Left" ColumnSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<InkToolbar
VerticalAlignment="Top"
AutomationProperties.Name="Drawing tools"
Orientation="Vertical"
TargetInkCanvas="{x:Bind inkCanvas}" />
<Border
Grid.Column="1"
Width="440"
Height="320"
VerticalAlignment="Top"
Background="White"
BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
BorderThickness="1">
<InkCanvas x:Name="inkCanvas" AutomationProperties.Name="Drawing surface" />
</Border>
</Grid>
La ButtonFlyoutPlacement propiedad establece dónde se abren los desplegables del botón en relación con la barra de herramientas. El valor predeterminado, Auto, permite que la barra de herramientas elija la posición.
Agregar un botón de alternancia personalizado
Use un InkToolbarCustomToggleButton para agregar un ajuste que los usuarios activen y desactiven desde la barra de herramientas. En el ejemplo siguiente se agrega un botón de alternancia que controla la entrada táctil. La entrada del lápiz y el mouse siempre generan entradas manuscritas.
<StackPanel Spacing="12">
<InkToolbar AutomationProperties.Name="Drawing tools" TargetInkCanvas="{x:Bind inkCanvas}">
<InkToolbarCustomToggleButton
x:Name="touchWritingToggle"
AutomationProperties.Name="Touch writing"
Checked="TouchWritingToggle_Changed"
ToolTipService.ToolTip="Touch writing"
Unchecked="TouchWritingToggle_Changed">
<SymbolIcon Symbol="TouchPointer" />
</InkToolbarCustomToggleButton>
</InkToolbar>
<TextBlock x:Name="deviceInfo" />
<Border
Width="480"
Height="280"
HorizontalAlignment="Left"
Background="White"
BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
BorderThickness="1">
<InkCanvas x:Name="inkCanvas" AutomationProperties.Name="Drawing surface" />
</Border>
</StackPanel>
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.UI.Core;
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
// Pen and mouse input always draw ink. The custom toggle controls touch input.
inkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Mouse;
ShowDevices();
}
private void TouchWritingToggle_Changed(object sender, RoutedEventArgs e)
{
if (touchWritingToggle.IsChecked == true)
{
inkCanvas.InkPresenter.InputDeviceTypes |= CoreInputDeviceTypes.Touch;
}
else
{
inkCanvas.InkPresenter.InputDeviceTypes &= ~CoreInputDeviceTypes.Touch;
}
ShowDevices();
}
private void ShowDevices()
{
deviceInfo.Text = $"InputDeviceTypes = {inkCanvas.InkPresenter.InputDeviceTypes}";
}
}
El botón de alternancia muestra solo un icono, por lo que el ejemplo le proporciona un nombre accesible y una información sobre herramientas. En este ejemplo, la barra de herramientas también crea sus botones predeterminados y el botón de alternancia personalizado aparece después de ellos.
La barra de herramientas admite otros botones personalizados:
-
InkToolbarCustomToolButtonañade una herramienta propia. -
InkToolbarCustomPenButtonagrega un bolígrafo que crea sus propios atributos de dibujo. Establezca su propiedadCustomPenen una instancia de una clase que derive deInkToolbarCustomPeny sobrescriba el métodoCreateInkDrawingAttributesCore.
Cree la barra de herramientas en el código
También puede crear el lienzo y la barra de herramientas mediante código. En el ejemplo siguiente, los agrega a un Border denominado inkHost, se conecta la barra de herramientas con TargetInkPresenter y se selecciona el lápiz cuando se carga la barra de herramientas.
<Border x:Name="inkHost" HorizontalAlignment="Left" />
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Windows.UI.Core;
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
CreateInkingUI();
}
private void CreateInkingUI()
{
InkCanvas canvas = new();
AutomationProperties.SetName(canvas, "Drawing surface");
canvas.InkPresenter.InputDeviceTypes =
CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Touch;
InkToolbar toolbar = new() { TargetInkPresenter = canvas.InkPresenter };
AutomationProperties.SetName(toolbar, "Drawing tools");
// The toolbar creates its built-in buttons when it loads.
toolbar.Loaded += (sender, args) => toolbar.ActiveTool = toolbar.GetToolButton(InkToolbarTool.Pencil);
Border canvasFrame = new()
{
Width = 480,
Height = 280,
Background = new SolidColorBrush(Colors.White),
BorderBrush = new SolidColorBrush(Colors.DarkGray),
BorderThickness = new Thickness(1),
Child = canvas,
};
StackPanel layout = new() { Spacing = 8 };
layout.Children.Add(toolbar);
layout.Children.Add(canvasFrame);
inkHost.Child = layout;
}
}
Limitaciones conocidas
- Los controles de entrada manuscrita solo están disponibles en versiones experimentales del SDK de Aplicaciones para Windows. No están disponibles en el canal estable.
- Las API de entrada de lápiz no incluyen reconocimiento de escritura manuscrita ni de formas.
- La entrada del mouse no dibuja tinta hasta que se agrega
CoreInputDeviceTypes.MouseaInputDeviceTypes.
Los siguientes problemas se notificaron con SDK de Aplicaciones para Windows 2.4.1-experimental. Las notas de la versión 2.5.4-experimental no las enumeran como corregidas:
- Las plantillas de regla y de transportador no funcionan.
-
RenderTargetBitmapno captura la entrada manuscrita. Para crear una imagen de la entrada manuscrita, guárdela en el formatoGifWithEmbeddedIsf. - Al establecer la propiedad
Palettede un botón de bolígrafo en XAML, se produce un error de análisis de XAML. EstablezcaPaletteen el código en su lugar, como se muestra en Personalizar los colores y anchos de una pluma. - Los menús desplegables de botón pueden aparecer vacíos, y es posible que cambiar
ButtonFlyoutPlacementno cambie dónde se abren.
Cambios de 2.4.1-experimental
Si ha usado las API de entrada manuscrita en SDK de Aplicaciones para Windows 2.4.1-experimental, actualice el código para estos cambios en la versión 2.5.4-experimental:
| API en 2.4.1-experimental | Cambio en 2.5.4-experimental |
|---|---|
InkToolbar.TargetInkPresenter (tipo Object) |
El tipo de propiedad es InkPresenter. |
métodos InkPresenter.GetHighContrastAdjustment y InkPresenter.SetHighContrastAdjustment |
Reemplazado por la propiedad InkPresenter.HighContrastAdjustment. |
métodos InkStrokeInput.GetInkPresenter y InkUnprocessedInput.GetInkPresenter |
Reemplazado por una InkPresenter propiedad en cada clase. |
InkToolbarEraserButton.SelectedEraser, IsStrokeEraserVisible y ArePrecisionErasersVisible propiedades; InkToolbar.EraserFlyoutItemClicked evento; InkToolbarEraserKind, InkToolbarEraserFlyoutItemKind, y InkToolbarEraserFlyoutItemClickedEventArgs tipos |
Eliminado. |
| No disponible |
InkPresenter.ActivateCustomDrying método y clase InkSynchronizer agregados. |
Accesibilidad y contraste alto
La tinta digital es contenido visual, así que planee cómo todos los usuarios pueden usar las características de entrada manuscrita:
- Asigne a cada lienzo y barra de herramientas un nombre accesible con
AutomationProperties.Name, como lo hacen los ejemplos de este artículo. Asigne botones personalizados que muestren solo un icono, un nombre e información sobre una herramienta. -
InkCanvasAutomationPeerexpone el lienzo a Automatización de la interfaz de usuario, pero no describe los trazos. Si la entrada manuscrita tiene significado, como una firma o un diagrama etiquetado, proporcione una alternativa de texto. - La entrada manuscrita requiere la entrada de puntero. No haga que la entrada manuscrita sea la única manera de completar una tarea. Por ejemplo, permitir que los usuarios escriban su nombre como alternativa a firmarlo.
- Los elementos de automatización del mismo nivel para los botones de herramientas integrados admiten los patrones de control SelectionItem y ExpandCollapse. Pruebe la navegación del teclado y los desplegables del botón con la configuración de la barra de herramientas que usa la aplicación.
Cuando un tema de contraste está activo, el presentador puede reemplazar los colores de la tinta por colores del sistema. Para elegir este comportamiento, establezca la HighContrastAdjustment propiedad en un InkHighContrastAdjustment valor del Microsoft.UI.Xaml.Controls espacio de nombres. La documentación de Windows Runtime para la enumeración con el mismo nombre describe los valores de esta manera:
| Value | Color de tinta en un tema de contraste |
|---|---|
UseSystemColorsWhenNecessary |
El propio color de la tinta si contrasta lo suficiente con el fondo. De lo contrario, un color del sistema. |
UseSystemColors |
Color del sistema. La tinta del resaltador utiliza el color del resaltador del sistema. |
UseOriginalColors |
El propio color de la tinta. |
inkCanvas.InkPresenter.HighContrastAdjustment = InkHighContrastAdjustment.UseSystemColorsWhenNecessary;
Para mantenerlos cortos, los ejemplos de este artículo establecen directamente un fondo blanco. En la aplicación, defina el fondo del lienzo en diccionarios de temas para que siga el tema activo, como se describe en Temas de contraste. A continuación, pruebe la aplicación con cada tema de contraste y compruebe que la entrada manuscrita, el fondo y el borde se distinguen en todo momento.
Para obtener más información, vea Exponer información de accesibilidad básica y accesibilidad de teclado.
Consulte también
- Código fuente del ejemplo InkCanvas de la galería de WinUI 3
- notas de la versión experimental de SDK de Aplicaciones para Windows 2.5.4
- Microsoft. Interfaz de usuario. Espacio de nombres Xaml.Controls
- espacio de nombres Windows.UI.Input.Inking
- Respuesta háptica del lápiz en aplicaciones de Windows
- Interacciones de lápiz y Windows Ink (UWP)
- Controles de entrada manuscrita (UWP)
- Lo que se admite al migrar de UWP a WinUI 3