Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Importante
InkCanvas, InkToolbar, e InkPresenter são APIs experimentais. O suporte para entintagem foi adicionado no SDK de Aplicações Windows 2.4.1-experimental. Este artigo descreve o SDK de Aplicações Windows 2.5.4-experimental, que alterou a forma como se estabelece a ligação a um InkToolbar. Estas APIs não estão disponíveis no canal estável. Podem mudar ou ser removidos numa versão futura. Não os uses em aplicações de produção.
Use o controlo experimental InkCanvas para permitir que os utilizadores desenhem a tinta com caneta, rato ou toque. Adicione um InkToolbar para dar aos utilizadores uma caneta esferográfica, lápis, marcador e borracha, com opções de cor e tamanho. Cada canvas tem um InkPresenter que recolhe e renderiza a entrada de tinta. O InkStrokeContainer do apresentador armazena os traços, para que possas guardar, carregar, copiar e criar anotações de tinta por código.
Para experimentar os controlos, instale o pacote SDK de Aplicações Windows 2.5.4-experimental e o runtime correspondente. Para links de download e informações de configuração, consulte Downloads mais recentes do SDK de Aplicações Windows.
Será este o controlo correto?
Use uma tela de desenho a tinta quando os utilizadores precisarem de:
- Tire notas manuscritas ou anote conteúdos.
- Esboçar, desenhar diagramas ou assinar o próprio nome.
- Crie conteúdo à mão livre que a sua aplicação guarde e permita editá-lo mais tarde.
As APIs experimentais de tinta digital não incluem escrita manual nem reconhecimento de formas. Para introduzir texto, use uma caixa de texto.
Se precisar de suporte para tinta digital numa aplicação de produção, veja Limitações conhecidas dos controlos para o estado destes controlos no canal estável e para alternativas.
Compreenda o modelo de entintagem
As APIs de entintagem funcionam em conjunto:
| API | Role |
|---|---|
InkCanvas |
O elemento XAML que mostra a tinta e recebe a entrada de tinta. A sua propriedade InkPresenter devolve o apresentador para a canvas. |
InkPresenter |
Recolhe a entrada de tinta, renderiza traços e mantém as definições de entrada e os atributos padrão de desenho. Não se pode criar um InkPresenter a si próprio. Obtenha-o de InkCanvas.InkPresenter. |
InkStrokeContainer |
Armazena traços. Usa-o para guardar, abrir, selecionar, copiar e colar tinta. Obtenha-o de InkPresenter.StrokeContainer. |
InkToolbar |
Uma barra de ferramentas opcional que permite aos utilizadores escolher uma caneta, lápis, marcador ou borracha, e alterar a cor e o tamanho da tinta. |
InkCanvas não tem propriedade Background . Para dar à área de desenho um fundo e uma borda, coloque o canvas dentro de um recipiente como um Border, e defina o tamanho no recipiente. A cor padrão da tinta é preta, por isso os exemplos neste artigo usam um fundo branco.
Vários destes tipos têm os mesmos nomes que os dos tipos no espaço de nomes Windows Runtime Windows.UI.Input.Inking, como InkPresenter, InkStrokeContainer e InkHighContrastAdjustment. Os controlos de entintagem usam as versões Microsoft.UI.Xaml.Controls. Traços e atributos de desenho continuam a usar tipos de Windows Runtime, como InkStroke, InkDrawingAttributes, e InkStrokeBuilder. Se importar ambos os namespaces e usar um nome ambíguo, o compilador C# reporta 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 necessitam:
using Microsoft.UI.Xaml.Controls;
using InkDrawingAttributes = Windows.UI.Input.Inking.InkDrawingAttributes;
using InkStroke = Windows.UI.Input.Inking.InkStroke;
Também pode importar ambos os namespaces e usar nomes totalmente qualificados para os tipos ambíguos, como faz 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 começa, continua, termina ou é cancelado. -
UnprocessedInputgera eventos de ponteiro para entrada que o presenter não processa como tinta digital. -
InputConfigurationdetermina se o apontador processa a entrada da borracha e do botão lateral principal da caneta. -
InputProcessingConfigurationdefine-se se a entrada desenha tinta, apaga tinta ou não é processada, e como o apresentador lida com a entrada de arrastar à direita. -
ActivateCustomDryingretorna umInkSynchronizerpara aplicações que renderizam tinta confirmada elas próprias.
Configure o seu projeto
As APIs de escrita à mão estão marcadas como experimentais. Quando os usa num projeto C#, o compilador C# reporta o aviso CS8305 e o compilador XAML reporta o aviso WMC1501. Se o seu projeto tratar os avisos como erros, esses avisos impedem a compilação.
Depois de decidir usar as APIs experimentais, pode suprimir esses avisos. Adicione as seguintes propriedades ao seu ficheiro 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.
Crie uma tela de tinta digital
![]()
A aplicação WinUI 3 Gallery inclui exemplos interativos de controlos e funcionalidades WinUI. Descarrega a aplicação na Microsoft Store ou navega pelo código-fonte no GitHub.
O exemplo seguinte coloca uma canvas dentro de um contentor branco Border. O code-behind define os tipos de dispositivos de entrada para que os utilizadores possam desenhar com caneta, rato ou toque no ecrã.
<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 resumir, alguns exemplos posteriores não repetem este código. Define InputDeviceTypes em cada página onde os utilizadores desenham com rato.
Ligar uma barra de ferramentas de tinta
Coloque um InkToolbar perto da tela e defina a sua propriedade TargetInkCanvas para ligar ambos:
<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 do SDK de Aplicações Windows 2.5.4-experimental, pode, em vez disso, ligar uma barra de ferramentas diretamente a um InkPresenter definindo a TargetInkPresenter propriedade. Defina em código:
inkToolbar.TargetInkPresenter = inkCanvas.InkPresenter;
Também podes definir em XAML: TargetInkPresenter="{x:Bind inkCanvas.InkPresenter}". Defina apenas uma das duas propriedades alvo.
Por defeito, a barra de ferramentas mostra os botões de caneta esferográfica, lápis, marcador, borracha e estêncil (régua). Para alterar quais os botões que aparecem, veja Personalizar a barra de ferramentas de tinta.
Escolha de dispositivos de entrada
A InputDeviceTypes propriedade utiliza a CoreInputDeviceTypes enumeração de flags do Windows.UI.Core namespace. O input do rato não desenha com tinta digital por defeito. Combina valores para escolher que dispositivos consomem 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 deixar de recolher traços à tinta de todos os dispositivos, por exemplo, para mostrar um desenho que os utilizadores não podem alterar, defina IsInputEnabled para false:
inkCanvas.InkPresenter.IsInputEnabled = false;
Para permitir que os utilizadores liguem ou desliguem um dispositivo de entrada a partir da barra de ferramentas, veja 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 o aspeto dos traços. Eles usam a classe Windows RuntimeInkDrawingAttributes. Para alterar os atributos dos novos traços, chame CopyDefaultDrawingAttributes, altere a cópia e transmita-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 espaço de nomes, e Size está no Windows.Foundation espaço de nomes. Os novos atributos aplicam-se aos traços que os utilizadores desenham após a invocação. Os traços existentes mantêm as suas características.
Quando ligas um InkToolbar, a ferramenta ativa e as suas definiçõ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 da caneta
Se não usares uma barra de ferramentas, podes oferecer as tuas próprias predefinições de caneta. O exemplo seguinte aplica características de esferográfica, lápis, marcador ou caligrafia quando o utilizador seleciona um preset 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 os atributos dos traços de lápis. A propriedadeKindé apenas de leitura, por isso use este 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 ePenTipTransformroda-a ou dimensiona-a. Uma ponta retangular girada produz traços ao estilo caligrafia.
Responder a alterações de tinta
O reconhecedor levanta StrokesCollected depois de um utilizador desenhar traços e StrokesErased depois de o utilizador os apagar. A propriedade Strokes dos argumentos de cada evento contém os traços afetados. O código seguinte mantém a contagem de traços atualizada num 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";
}
Estes eventos ocorrem quando os utilizadores desenham ou apagam tinta digital. Quando mudas traços no código, por exemplo com Clear, LoadAsync, ou AddStroke, atualiza a interface após a chamada.
Guardar e carregar tinta
InkStrokeContainer poupa tinta em Ink Serialized Format (ISF). Para escolher como os dados são armazenados, passe um InkPersistenceFormat valor para SaveAsync:
| Format | Output |
|---|---|
GifWithEmbeddedIsf |
Uma imagem GIF que contém os dados de tinta. Os visualizadores de imagens podem mostrar o ficheiro, e a tua aplicação pode carregar novamente as anotações a tinta para edição. |
Isf |
Apenas dados de tinta digital, sem imagem. Este é o formato mais compacto. |
Adicione os seguintes métodos ao código por trás de uma página que contenha um InkCanvas com o nome inkCanvas.
SaveInkAsync escreve a tinta num ficheiro e LoadInkAsync substitui a tinta no canvas pela tinta de um ficheiro.
LoadAsync remove os traços existentes antes de adicionar os traços carregados, por isso não precisas de 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 utilizam. Para aguardar estes métodos do Windows Runtime, o ficheiro precisa da using System; diretiva. Sem ele, o compilador reporta o erro CS4036.
Para permitir que os utilizadores escolham um ficheiro, utilize os seletores de ficheiros do SDK de Aplicações Windows, que retornam o caminho do ficheiro selecionado. Para mais informações, consulte Guardar um ficheiro com um seletor de SDK de Aplicações Windows e Abrir ficheiros e pastas com selecionadores de SDK de Aplicações Windows.
Para manter a ink na memória, por exemplo, para a restaurar mais tarde na mesma sessão, guarde-a num InMemoryRandomAccessStream. Passe o resultado do fluxo GetOutputStreamAt(0) para SaveAsync, e passe o respetivo resultado GetInputStreamAt(0) para LoadAsync. O exemplo da Galeria WinUI 3 utiliza esta abordagem.
Copiar e colar ink
InkStrokeContainer Podes copiar traços para a área de transferência e colar tinta digital a partir dela.
CopySelectedToClipboard Copia apenas traços selecionados, por isso o exemplo seguinte seleciona todos os traços na tela esquerda antes de os copiar.
CanPasteFromClipboard verifica se a área de transferência contém tinta digital.
PasteFromClipboard Adiciona a tinta na posição que indicar e devolve o retângulo delimitador da área que mudou.
<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 propriedade Selected de cada traço que quiser, ou chame SelectWithLine ou SelectWithPolyLine. Para eliminar ou mover os traços selecionados, chame DeleteSelected ou MoveSelected.
Criar contornos em código
Utilize a classe Windows Runtime InkStrokeBuilder para criar traços a partir de pontos e adicione-os ao canvas com AddStroke. O exemplo seguinte 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 contorno a partir dosInkPointvalores. Cada ponto combina uma posição na tela com um valor de pressão. - A documentação do Windows Runtime recomenda
CreateStrokeFromInkPointsem vez deCreateStrokepara traços que adiciona a um processador de tinta. - Quando
FitToCurveéfalse, o traço utiliza segmentos retos entre pontos em vez de curvas.
Um traço pode pertencer apenas a um contentor. Para copiar traços para outro canvas, 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 controlos iniciais
A InitialControls propriedade determina quais os botões incorporados que a barra de ferramentas cria:
| Valor | Botões incorporados |
|---|---|
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. Declara os botões que pretende como filhos da barra de ferramentas. |
O exemplo seguinte declara os mesmos quatro botões do exemplo da Galeria WinUI 3, atribui a cada um um nome acessível e seleciona a caneta 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 do IsClearAllVisible botão de borracha para true. A barra de ferramentas gera o evento EraseAllClicked quando o utilizador seleciona o comando.
Personalize as cores e larguras de uma caneta
Cada botão da caneta tem Palette uma variedade de pincéis e definições de largura do traço. Defina Palette no código. Para obter detalhes, consulte Limitações conhecidas. O exemplo seguinte substitui a paleta de uma caneta esferográfica por quatro cores de marca, limita a largura do traço e mostra as características 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 atual do traço e o intervalo que os utilizadores podem escolher. A barra de ferramentas emite InkDrawingAttributesChanged quando os atributos da ferramenta ativa mudam.
Selecione a ferramenta ativa
Defina ActiveTool para selecionar uma ferramenta e use ActiveToolChanged para responder quando o utilizador selecionar outra.
GetToolButton devolve um botão de ferramenta incorporado do tipo que fornece. Os botões incorporados podem não existir até a barra de ferramentas carregar, por isso seleciona uma ferramenta inicial no processador do evento 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}";
}
}
Mude a orientação
Para mostrar a barra de ferramentas verticalmente, por exemplo ao lado do canvas, defina Orientation para 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 dos botões se abrem em relação à barra de ferramentas. O valor predefinido, Auto, permite que a barra de ferramentas escolha a posição.
Adicione um botão de alternância personalizado
Utilize um InkToolbarCustomToggleButton para adicionar uma definição que os utilizadores ativam e desativam na barra de ferramentas. O exemplo seguinte adiciona um interruptor que controla a entrada por toque. A entrada de caneta e rato desenha sempre 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 alternar mostra apenas um ícone, por isso o exemplo dá-lhe um nome acessível e uma dica de ferramenta. Neste exemplo, a barra de ferramentas também cria os seus botões predefinidos, e o botão personalizado aparece depois deles.
A barra de ferramentas suporta outros botões personalizados:
-
InkToolbarCustomToolButtonAdiciona uma ferramenta sua. -
InkToolbarCustomPenButtonrefere-se a uma caneta que cria os seus próprios atributos de desenho. Defina a propriedadeCustomPencomo uma instância de uma classe que deriva deInkToolbarCustomPene sobrescreve o métodoCreateInkDrawingAttributesCore.
Criar a barra de ferramentas em código
Também podes criar o ecrã e a barra de ferramentas em código. O exemplo seguinte adiciona-as a um Border chamado inkHost, liga a barra de ferramentas com TargetInkPresenter, e seleciona o lápis quando a barra de ferramentas carrega.
<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 controlos de entintagem estão disponíveis apenas em versões experimentais do SDK de Aplicações Windows. Não estão disponíveis no canal estável.
- As APIs de tinta não incluem escrita à mão nem reconhecimento de formas.
- O input do rato não desenha tinta até adicionar
CoreInputDeviceTypes.MouseaInputDeviceTypes.
Os seguintes problemas foram reportados com o SDK de Aplicações Windows 2.4.1-experimental. As notas de lançamento 2.5.4-experimental não os listam como corrigidos:
- Os estênceis da régua e do transportador não funcionam.
-
RenderTargetBitmapnão reconhece a tinta manuscrita. Para criar uma imagem da anotação, guarde-a no formatoGifWithEmbeddedIsf. - Definir a propriedade
Palettede um botão de caneta no XAML causa um erro de análise sintática do XAML. DefinaPaletteno código em vez disso, como mostrado em Personalizar as cores e larguras da caneta. - Os menus desdobráveis dos botões podem parecer vazios, e mudar
ButtonFlyoutPlacementpode não alterar onde abrem.
Alterações em relação a 2.4.1-experimental
Se usou as APIs de entintagem no SDK de Aplicações Windows 2.4.1-experimental, atualize o seu código para estas alterações na 2.5.4-experimental:
| API na versão 2.4.1-experimental | Mudança 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. |
propriedades InkToolbarEraserButton.SelectedEraser, IsStrokeEraserVisible e ArePrecisionErasersVisible; evento InkToolbar.EraserFlyoutItemClicked; tipos InkToolbarEraserKind, InkToolbarEraserFlyoutItemKind e InkToolbarEraserFlyoutItemClickedEventArgs |
Removido. |
| Não disponível |
InkPresenter.ActivateCustomDrying método e InkSynchronizer classe adicionados. |
Acessibilidade e alto contraste
A tinta é conteúdo visual, por isso planeie como todos os utilizadores podem usar as suas funcionalidades de tinta:
- Dê a cada canvas e barra de ferramentas um nome acessível com
AutomationProperties.Name, tal como fazem os exemplos neste artigo. Atribua um nome e uma dica de ferramenta aos botões personalizados que mostrem apenas um ícone. -
InkCanvasAutomationPeerexpõe o canvas ao Automatização da Interface de Utilizador, mas não descreve os traços. Se a tinta tiver significado, como uma assinatura ou um diagrama rotulado, forneça uma alternativa em texto. - A escrita a tinta requer entrada por apontador. Não faças da tinta a única forma de completar uma tarefa. Por exemplo, permitir que os utilizadores escrevam o seu nome como alternativa a assiná-lo.
- Os elementos de automação para os botões de ferramentas incorporados suportam os padrões de controlo SelectionItem e ExpandCollapse. Teste a navegação por teclado e os menus desdobráveis dos botões com a configuração da barra de ferramentas que a sua aplicação utiliza.
Quando um tema de contraste está ativo, o apresentador pode substituir as cores da tinta por cores do sistema. Para escolher este comportamento, defina a HighContrastAdjustment propriedade para um InkHighContrastAdjustment valor do Microsoft.UI.Xaml.Controls namespace. A documentação do Windows Runtime para a enumeração com o mesmo nome descreve os valores da seguinte forma:
| Valor | Cor da tinta num tema de contraste |
|---|---|
UseSystemColorsWhenNecessary |
A própria cor da tinta, se contrastar o suficiente com o fundo. Caso contrário, uma cor do sistema. |
UseSystemColors |
Uma cor de sistema. A tinta de realce usa a cor de realce do sistema. |
UseOriginalColors |
A própria cor da tinta. |
inkCanvas.InkPresenter.HighContrastAdjustment = InkHighContrastAdjustment.UseSystemColorsWhenNecessary;
Para os manter curtos, os exemplos deste artigo apresentam um fundo branco diretamente. Na sua aplicação, defina o fundo do canvas nos dicionários de temas para que siga o tema ativo, conforme descrito nos temas Contrast. Depois testa a tua aplicação com cada tema de contraste e verifica se o primeiro plano, o fundo e o rebordo se mantêm distinguíveis.
Para mais informações, consulte Exposição de informações básicas de acessibilidade e Acessibilidade do teclado.
Ver também
- Código fonte de exemplo WinUI 3 Gallery InkCanvas
- SDK de Aplicações Windows 2.5.4-notas de lançamento experimental
- espaço de nomes Microsoft.UI.Xaml.Controls
- espaço de nomes Windows.UI.Input
- Feedback háptico da caneta nas aplicações do Windows
- Interações com canetas e Windows Ink (UWP)
- Controlos de tinta manuscrita (UWP)
- O que é suportado ao migrar do UWP para o WinUI 3
Windows developer