Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Importante
InkCanvas, InkToolbare InkPresenter são APIs experimentais. O suporte à escrita à tinta foi adicionado em SDK do Aplicativo Windows 2.4.1-experimental. Este artigo descreve SDK do Aplicativo Windows 2.5.4-experimental, que alterou a forma como você conecta um InkToolbar. Essas APIs não estão disponíveis no canal estável. Eles podem mudar ou ser removidos em uma versão futura. Não os use em aplicativos de produção.
Use o controle experimental InkCanvas para permitir que os usuários desenhem com tinta digital usando uma caneta, um mouse ou o toque. Adicione um InkToolbar para dar aos usuários uma caneta esferográfica, lápis, marcador e borracha, com opções de cor e tamanho. Cada canvas tem um InkPresenter que coleta e renderiza a entrada de tinta. O apresentador InkStrokeContainer armazena os traços, para que você possa salvar, carregar, copiar e criar anotações a tinta via código.
Para experimentar os controles, instale o pacote experimental SDK do Aplicativo Windows 2.5.4 e o runtime correspondente. Para obter links de download e informações de instalação, consulte os downloads de SDK do Aplicativo Windows mais recentes.
Esse é o controle correto?
Use uma tela de desenho a tinta quando os usuários precisarem:
- Faça anotações manuscritas ou anote conteúdo.
- Esboçar, desenhar diagramas ou assinar seu nome.
- Crie conteúdo à mão livre que seu aplicativo salva e permite editá-lo mais tarde.
As APIs experimentais de escrita à tinta não incluem reconhecimento de escrita manual nem de formas. Para coletar texto, use uma caixa de texto.
Se você precisar de entrada por tinta em um aplicativo de produção, consulte lacunas de controle conhecidas para o status desses controles no canal estável e para obter alternativas.
Entender o modelo de tinta digital
As APIs de tinta funcionam em conjunto:
| API | Role |
|---|---|
InkCanvas |
O elemento XAML que exibe tinta e recebe entrada de tinta. Sua propriedade InkPresenter retorna o apresentador do canvas. |
InkPresenter |
Coleta entrada de tinta, renderiza traços e mantém as configurações de entrada e os atributos de desenho padrão. Você não pode criar um InkPresenter por conta própria. Obtenha-o de InkCanvas.InkPresenter. |
InkStrokeContainer |
Armazena traços. Use isso para salvar, carregar, selecionar, copiar e colar tinta. Obtenha-o de InkPresenter.StrokeContainer. |
InkToolbar |
Uma barra de ferramentas opcional que permite que os usuários escolham uma caneta, lápis, marcador ou borracha e alterem a cor e o tamanho da tinta. |
InkCanvas não tem uma Background propriedade. Para dar à área de desenho um plano de fundo e uma borda, coloque o canvas dentro de um contêiner, como um Border, e defina o tamanho no contêiner. A cor da tinta padrão é preta, portanto, os exemplos neste artigo usam uma tela de fundo branca.
Vários desses tipos têm os mesmos nomes que tipos no namespace do Windows Runtime Windows.UI.Input.Inking, como InkPresenter, InkStrokeContainer e InkHighContrastAdjustment. Os controles de desenho a tinta usam as versões Microsoft.UI.Xaml.Controls. Traços e atributos de desenho ainda usam tipos Windows Runtime, como InkStroke, InkDrawingAttributese InkStrokeBuilder. Se você importar os dois namespaces e usar um nome ambíguo, o compilador C# relatará o erro CS0104.
Os exemplos neste artigo importam Microsoft.UI.Xaml.Controls e usam diretivas de alias para os Windows.UI.Input.Inking tipos de que precisam:
using Microsoft.UI.Xaml.Controls;
using InkDrawingAttributes = Windows.UI.Input.Inking.InkDrawingAttributes;
using InkStroke = Windows.UI.Input.Inking.InkStroke;
Você também pode importar namespaces e usar nomes totalmente qualificados para os tipos ambíguos, como o exemplo da Galeria do WinUI 3.
InkPresenter também fornece APIs de nível inferior que este artigo não aborda:
-
StrokeInputgera eventos quando um traço é iniciado, continua, termina ou é cancelado. -
UnprocessedInputgera eventos de ponteiro para entrada que o renderizador não processa como traço à tinta. -
InputConfigurationdefine se o aplicativo processa a entrada da borracha da caneta e do botão lateral principal. -
InputProcessingConfigurationdefine se a entrada desenha tinta, apaga a tinta ou não é processada e como o apresentador lida com a entrada de arraste com o botão direito. -
ActivateCustomDryingretorna umInkSynchronizerpara aplicativos que renderizam a tinta finalizada por conta própria.
Configure seu projeto
As APIs de tinta estão marcadas como experimentais. Quando você os usa em um projeto em C#, o compilador C# relata o aviso CS8305 e o compilador XAML relata WMC1501 de aviso. Se o projeto tratar avisos como erros, esses avisos interromperão o build.
Depois de decidir usar as APIs experimentais, você poderá suprimir esses avisos. Adicione as seguintes propriedades ao arquivo de projeto C# (.csproj):
<PropertyGroup>
<NoWarn>$(NoWarn);CS8305</NoWarn>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);WMC1501</MSBuildWarningsAsMessages>
</PropertyGroup>
Os exemplos neste artigo usam C#. Cada exemplo completo mostra o XAML de uma página e a classe code-behind da página, MainPage.
Criar uma tela de entrada manuscrita
![]()
O aplicativo Da Galeria do WinUI 3 inclui exemplos interativos de controles e recursos do WinUI. Obtenha o aplicativo na Microsoft Store ou navegue pelo código-fonte em GitHub.
O exemplo a seguir coloca uma tela dentro de um contêiner branco Border. O code-behind define os tipos de dispositivo de entrada para que os usuários possam desenhar com uma caneta, mouse ou toque.
<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 mantê-los curtos, alguns exemplos posteriores não repetem esse código. Defina InputDeviceTypes em todas as páginas em que os usuários desenham com um mouse.
Conectar uma barra de ferramentas de tinta à caneta
Coloque um InkToolbar próximo à tela e defina sua TargetInkCanvas propriedade para conectar os dois:
<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>
Começando com o SDK do Aplicativo Windows 2.5.4-experimental, você pode, em vez disso, conectar uma barra de ferramentas diretamente a um InkPresenter definindo a propriedade TargetInkPresenter. Defina-o no código:
inkToolbar.TargetInkPresenter = inkCanvas.InkPresenter;
Você também pode defini-lo em XAML: TargetInkPresenter="{x:Bind inkCanvas.InkPresenter}". Defina apenas uma das duas propriedades de destino.
Por padrão, a barra de ferramentas mostra os botões caneta esferográfica, lápis, realce, borracha e estêncil (régua). Para alterar quais botões aparecem, consulte Personalizar a barra de ferramentas de tinta.
Selecionar dispositivos de entrada
A propriedade InputDeviceTypes usa a enumeração de sinalizadores CoreInputDeviceTypes do namespace Windows.UI.Core. A entrada via mouse não permite desenhar com tinta por padrão. Combine valores para escolher quais dispositivos usam tinta:
// 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 parar de capturar entrada de tinta de todos os dispositivos, por exemplo, para mostrar um desenho que os usuários não podem alterar, defina IsInputEnabled como false:
inkCanvas.InkPresenter.IsInputEnabled = false;
Para permitir que os usuários ativem ou desativem um dispositivo de entrada na barra de ferramentas, consulte Adicionar um botão de alternância personalizado.
Definir atributos de desenho
Atributos de desenho, como cor, tamanho e forma da ponta da caneta, controlam a aparência dos traços. Eles usam a classe Windows RuntimeInkDrawingAttributes. Para alterar os atributos para novos traços, chame CopyDefaultDrawingAttributes, altere a cópia e passe-a para UpdateDefaultDrawingAttributes:
InkDrawingAttributes attributes = inkCanvas.InkPresenter.CopyDefaultDrawingAttributes();
attributes.Color = Colors.DarkBlue;
attributes.Size = new Size(4, 4);
inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(attributes);
Colors está no Microsoft.UI namespace e Size está no Windows.Foundation namespace. Os novos atributos se aplicam aos traços que os usuários desenham após a chamada da função. Os traços existentes mantêm seus atributos.
Quando você conecta uma InkToolbar, a ferramenta ativa e suas configurações de cor e tamanho determinam os atributos de desenho. Para ler os atributos atuais, use a propriedade InkDrawingAttributes da barra de ferramentas.
Criar predefinições de caneta
Se você não usar uma barra de ferramentas, poderá definir suas próprias predefinições de caneta. O exemplo a seguir aplica atributos de caneta esferográfica, lápis, realce ou caligrafia quando o usuário seleciona um predefinido ou altera o tamanho.
<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.CreateForPencilretorna atributos para traços de lápis. AKindpropriedade é somente leitura, portanto, use esse método para criar atributos de lápis. -
DrawAsHighlighterrenderiza o traço como uma sobreposição de realce. -
PenTipdefine a forma da ponta da caneta, ePenTipTransforma gira ou dimensiona. Uma ponta retangular girada produz traços no estilo de caligrafia.
Responder a alterações de tinta digital
O apresentador é acionado StrokesCollected depois que um usuário desenha traços e StrokesErased depois que um usuário os apaga. A propriedade Strokes dos argumentos de cada evento contém os traçados afetados. O código a seguir mantém uma contagem de traços atualizada em um TextBlock chamado 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";
}
Esses eventos ocorrem quando os usuários desenham ou apagam anotações à tinta. Quando você altera traços no código, por exemplo, com Clear, LoadAsync ou AddStroke, atualize sua interface do usuário após a chamada.
Salvar e carregar tinta digital
InkStrokeContainer salva tinta no formato Ink Serialized Format (ISF). Para escolher como os dados são armazenados, passe um InkPersistenceFormat valor para SaveAsync:
| Format | Saída |
|---|---|
GifWithEmbeddedIsf |
Uma imagem GIF que contém os dados de tinta. Os visualizadores de imagem podem exibir o arquivo e seu aplicativo pode carregar a tinta digital novamente para edição. |
Isf |
Somente dados de tinta, sem uma imagem. Esse é o formato mais compacto. |
Adicione os seguintes métodos ao code-behind de uma página que contém um InkCanvas chamado inkCanvas.
SaveInkAsync grava a tinta em um arquivo e LoadInkAsync substitui a tinta na tela pela tinta de um arquivo.
LoadAsync remove os traços existentes antes de adicionar os traços carregados, portanto, você não precisa chamar Clear primeiro.
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 e AsInputStream convertem fluxos .NET para os tipos de fluxo do Windows Runtime que SaveAsync e LoadAsync usam. Para aguardar esses métodos Windows Runtime, o arquivo precisa da diretiva using System;. Sem ele, o compilador relata o erro CS4036.
Para permitir que os usuários escolham um arquivo, use os seletores de arquivo SDK do Aplicativo Windows, que retornam o caminho do arquivo selecionado. Para obter mais informações, consulte Salvar um arquivo com um seletor de SDK do Aplicativo Windows e abrir arquivos e pastas com seletores de SDK do Aplicativo Windows.
Para manter as anotações à tinta na memória, por exemplo, para restaurá-las posteriormente na mesma sessão, salve-as em um InMemoryRandomAccessStream. Passe o resultado do GetOutputStreamAt(0) fluxo para SaveAsync, e passe seu GetInputStreamAt(0) resultado para LoadAsync. O exemplo da Galeria do WinUI 3 usa essa abordagem.
Copiar e colar ink
InkStrokeContainer pode copiar traços para a área de transferência e colar tinta digital nela.
CopySelectedToClipboard copia apenas os traços selecionados, de modo que o exemplo a seguir seleciona cada traço na tela esquerda antes de copiá-los.
CanPasteFromClipboard verifica se a área de transferência contém tinta digital.
PasteFromClipboard adiciona a anotação de tinta na posição que você passa e retorna o retângulo delimitador da área que foi alterada.
<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 trabalhar com alguns dos traços, defina a Selected propriedade de cada traço desejado ou chame SelectWithLine ou SelectWithPolyLine. Para excluir ou mover os traços selecionados, chamar DeleteSelected ou MoveSelected.
Criar contornos no código
Use a classe Windows Runtime InkStrokeBuilder para criar traços a partir de pontos e adicioná-los à tela com AddStroke. O exemplo a seguir adiciona uma estrela e uma linha de assinatura.
<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);
}
}
-
CreateStrokeFromInkPointscria um traçado a partir dos valores deInkPoint. Cada ponto combina uma posição na tela com um valor de pressão. - A documentação do Windows Runtime recomenda
CreateStrokeFromInkPointsem vez deCreateStrokepara os traços que você adiciona a um apresentador de tinta. - Quando
FitToCurveéfalse, o traço usa segmentos retos entre pontos em vez de curvas.
Um traço pode pertencer a apenas um contêiner. Para copiar traços para outra tela, adicione clones dos traços:
private void CopyStrokesTo(InkCanvas target)
{
foreach (InkStroke stroke in inkCanvas.InkPresenter.StrokeContainer.GetStrokes())
{
target.InkPresenter.StrokeContainer.AddStroke(stroke.Clone());
}
}
Personalizar a barra de ferramentas de tinta
Escolha os controles iniciais
A propriedade InitialControls determina quais botões internos a barra de ferramentas cria:
| Valor | Botões internos |
|---|---|
All (predefinição) |
Caneta esferográfica, lápis, marcador, borracha e molde (régua) |
PensOnly |
Caneta esferográfica, lápis e marcador |
AllExceptPens |
Borracha e molde (régua) |
None |
None. Declare os botões desejados como filhos da barra de ferramentas. |
O exemplo a seguir declara os mesmos quatro botões que o exemplo da Galeria do WinUI 3, fornece a cada um um nome acessível e seleciona a caneta de esferográfica quando a barra de ferramentas é carregada:
<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 um comando que apaga toda a tinta, defina a propriedade IsClearAllVisible do botão da borracha como true. A barra de ferramentas aciona o EraseAllClicked evento quando o usuário seleciona o comando.
Personalize as cores e a largura de uma caneta
Cada botão de caneta tem um conjunto Palette de pincéis e configurações de largura do traço. Definir Palette no código. Para obter detalhes, confira Limitações conhecidas. O exemplo a seguir substitui a paleta de uma caneta esferográfica por quatro cores de marca, limita a largura do traço e mostra os atributos da caneta ativa.
<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 seleciona uma cor em Palette.
SelectedStrokeWidth, MinStrokeWidth e MaxStrokeWidth definem a largura do traço atual e o intervalo do qual os usuários podem escolher. A barra de ferramentas aciona InkDrawingAttributesChanged quando os atributos da ferramenta ativa são alterados.
Selecione a ferramenta ativa
Defina ActiveTool para selecionar uma ferramenta e trate ActiveToolChanged para responder quando o usuário selecionar outra.
GetToolButton retorna um botão de ferramenta interno do tipo que você fornece como argumento. Os botões internos podem não existir até que a barra de ferramentas seja carregada, portanto, selecione uma ferramenta inicial no manipulador de eventos Loaded da barra de ferramentas.
<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}";
}
}
Alterar a orientação
Para mostrar a barra de ferramentas verticalmente, por exemplo, ao lado da tela, defina Orientation como 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>
A propriedade ButtonFlyoutPlacement define onde os menus desdobráveis do botão são abertos em relação à barra de ferramentas. O valor Autopadrão permite que a barra de ferramentas escolha a posição.
Adicionar um botão de alternância personalizado
Use um InkToolbarCustomToggleButton para adicionar uma configuração que os usuários ativam e desativam na barra de ferramentas. O exemplo a seguir adiciona uma alternância que controla a entrada por toque. A entrada da caneta e do mouse sempre desenha traços de tinta.
<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}";
}
}
O botão de alternância mostra apenas um ícone, de modo que o exemplo fornece um nome acessível e uma dica de ferramenta. Neste exemplo, a barra de ferramentas também cria seus botões padrão e a alternância personalizada é exibida após eles.
A barra de ferramentas dá suporte a outros botões personalizados:
-
InkToolbarCustomToolButtonadicione uma ferramenta sua. -
InkToolbarCustomPenButtonadiciona uma caneta que cria seus próprios atributos de desenho. Defina sua propriedadeCustomPencomo uma instância de uma classe que deriva deInkToolbarCustomPene substitui o métodoCreateInkDrawingAttributesCore.
Criar a barra de ferramentas no código
Você também pode criar o canvas e a barra de ferramentas no código. O exemplo a seguir os adiciona a um Border nomeado inkHost, conecta a barra de ferramentas com TargetInkPresenter e seleciona o lápis quando a barra de ferramentas é carregada.
<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;
}
}
Limitações conhecidas
- Os controles de escrita à tinta estão disponíveis apenas em versões experimentais do SDK do Aplicativo Windows. Eles não estão disponíveis no canal estável.
- As APIs de escrita à tinta não incluem escrita manual nem reconhecimento de forma.
- A entrada do mouse não desenha traços à tinta até que você adicione
CoreInputDeviceTypes.MouseaInputDeviceTypes.
Os problemas a seguir foram relatados com SDK do Aplicativo Windows 2.4.1-experimental. As notas de versão 2.5.4 experimentais não as listam como corrigidas:
- A régua e os estênceis de transferidor não funcionam.
-
RenderTargetBitmapnão captura nanquim. Para criar uma imagem da tinta digital, salve-a no formatoGifWithEmbeddedIsf. - Definir a propriedade
Palettede um botão de caneta em XAML causa um erro de análise XAML. Em vez disso, definaPaletteem código, conforme mostrado em Personalizar as cores e larguras de uma caneta. - Submenus de botão podem aparecer vazios e alterar
ButtonFlyoutPlacementpode não mudar onde eles abrem.
Alterações desde a versão 2.4.1-experimental
Se você usou as APIs de escrita à tinta no SDK do Aplicativo Windows 2.4.1-experimental, atualize seu código para essas alterações no 2.5.4-experimental:
| API em 2.4.1-experimental | Alteração em 2.5.4-experimental |
|---|---|
InkToolbar.TargetInkPresenter (tipo Object) |
O tipo de propriedade é InkPresenter. |
Métodos InkPresenter.GetHighContrastAdjustment e InkPresenter.SetHighContrastAdjustment |
Substituído pela propriedade InkPresenter.HighContrastAdjustment. |
Métodos InkStrokeInput.GetInkPresenter e InkUnprocessedInput.GetInkPresenter |
Substituído por uma InkPresenter propriedade em cada classe. |
InkToolbarEraserButton.SelectedEraser, IsStrokeEraserVisiblee ArePrecisionErasersVisible propriedades; evento InkToolbar.EraserFlyoutItemClicked; InkToolbarEraserKind, InkToolbarEraserFlyoutItemKinde InkToolbarEraserFlyoutItemClickedEventArgs tipos |
Removido. |
| Não disponível |
InkPresenter.ActivateCustomDrying método e InkSynchronizer classe adicionados. |
Acessibilidade e alto contraste
Tinta é conteúdo visual, portanto, planeje como todos os usuários podem usar seus recursos de escrita à tinta:
- Dê a cada canvas e barra de ferramentas um nome acessível,
AutomationProperties.Namecomo fazem os exemplos neste artigo. Dê um nome e uma dica de ferramenta a botões personalizados que mostram apenas um ícone. -
InkCanvasAutomationPeerexpõe o canvas à Automação da Interface do Usuário, mas não descreve os traços. Se a tinta tiver significado, como uma assinatura ou um diagrama rotulado, forneça uma alternativa de texto. - A tinta de desenho requer entrada por caneta. Não faça da tinta a única forma de concluir uma tarefa. Por exemplo, permitir que os usuários digitem seu nome como uma alternativa para assiná-lo.
- Os pares de automação para os botões de ferramenta integrados dão suporte aos padrões de controle SelectionItem e ExpandCollapse. Teste a navegação do teclado e os menus suspensos dos botões com a configuração da barra de ferramentas que seu aplicativo usa.
Quando um tema de contraste está ativo, o apresentador pode substituir cores de tinta por cores do sistema. Para escolher esse comportamento, defina a HighContrastAdjustment propriedade como um InkHighContrastAdjustment valor do Microsoft.UI.Xaml.Controls namespace. A documentação Windows Runtime para a enumeração com o mesmo nome descreve os valores dessa maneira:
| Valor | Cor da tinta em um tema de contraste |
|---|---|
UseSystemColorsWhenNecessary |
A cor da tinta se ela contrasta o suficiente com o plano de fundo. Caso contrário, uma cor do sistema. |
UseSystemColors |
Uma cor do sistema. A tinta do marca-texto usa a cor de realce do sistema. |
UseOriginalColors |
A cor da tinta. |
inkCanvas.InkPresenter.HighContrastAdjustment = InkHighContrastAdjustment.UseSystemColorsWhenNecessary;
Para mantê-los curtos, os exemplos neste artigo definem um plano de fundo branco diretamente. Em seu aplicativo, defina o plano de fundo da tela em dicionários de temas para que ele siga o tema ativo, conforme descrito em Temas de contraste. Em seguida, teste seu aplicativo com cada tema de contraste e verifique se o primeiro plano, o plano de fundo e a borda permanecem distinguíveis.
Para obter mais informações, consulte Expor informações básicas de acessibilidade e acessibilidade do teclado.
Consulte também
- Galeria do WinUI 3: código-fonte de exemplo do InkCanvas
- SDK do Aplicativo Windows notas de versão 2.5.4-experimental
- namespace Microsoft.UI.Xaml.Controls
- Windows.UI.Input.Inking namespace
- Feedback háptico da caneta em aplicativos do Windows
- Interações com caneta e Windows Ink (UWP)
- Controles de tinta (UWP)
- O que tem suporte ao migrar da UWP para o WinUI 3
Windows developer