Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Important
InkCanvas, InkToolbarи InkPresenter являются экспериментальными API. Поддержка рукописного ввода была добавлена в Windows App SDK 2.4.1-experimental. В этой статье описывается Windows App SDK 2.5.4 (experimental), который изменил способ подключения InkToolbar. Эти API недоступны в стабильном канале. Они могут изменяться или удаляться в будущем выпуске. Не используйте их в продакшен-приложениях.
Используйте экспериментальный InkCanvas элемент управления, чтобы пользователи рисовали рукописный ввод с помощью пера, мыши или сенсорного ввода. Добавьте InkToolbar, чтобы предоставить пользователям шариковую ручку, карандаш, маркер и ластик с параметрами цвета и размера. Каждый холст имеет элемент InkPresenter, который собирает и отрисовывает данные рукописного ввода. Средство отображения InkStrokeContainer хранит штрихи, поэтому можно сохранять, загружать, копировать и создавать рукописный ввод программно.
Чтобы попробовать элементы управления, установите пакет Windows App SDK 2.5.4-experimental и соответствующую среду выполнения. Ссылки на скачивание и сведения о настройке см. в разделе Последние загрузки Windows App SDK.
Это правильный контроль?
Используйте рукописный холст, когда пользователям необходимо:
- Берите рукописные заметки или аннотируйте содержимое.
- Создавать эскизы, рисовать схемы или подписывать своё имя.
- Создайте рукописный контент, который сохраняется в приложении и который можно редактировать позже.
Экспериментальные API цифровых чернил не включают распознавание рукописного ввода или фигур. Чтобы ввести текст, используйте текстовое поле.
Если вам требуется рукописный ввод в рабочем приложении, ознакомьтесь с известными пробелами в элементах управления, чтобы узнать о состоянии этих элементов управления в стабильном канале и об альтернативных вариантах.
Понимание модели цифровых чернил
API цифровых чернил работают вместе:
| API | Role |
|---|---|
InkCanvas |
Элемент XAML, отображающий рукописный ввод и принимающий рукописный ввод. Его InkPresenter свойство возвращает презентер для холста. |
InkPresenter |
Собирает входные данные ввода цифровыми чернилами, отрисовывает штрихи и содержит входные параметры и атрибуты рисования по умолчанию. Вы не можете самостоятельно создать InkPresenter. Получите его с InkCanvas.InkPresenter. |
InkStrokeContainer |
Хранит штрихи. Используйте его для сохранения, загрузки, выбора, копирования и вставки рукописного ввода. Получите его с InkPresenter.StrokeContainer. |
InkToolbar |
Необязательная панель инструментов, которая позволяет пользователям выбирать перо, карандаш, маркер или ластик, а также изменять цвет и размер рукописного ввода. |
InkCanvas не имеет свойства Background. Чтобы предоставить области рисования фон и границу, поместите холст внутри контейнера, например Border, и задайте размер контейнера. Цвет рукописного ввода по умолчанию черный, поэтому в примерах в этой статье используется белый фон.
Некоторые из этих типов имеют те же имена, что и типы в пространстве имен среда выполнения Windows Windows.UI.Input.Inking, например InkPresenter, InkStrokeContainer и InkHighContrastAdjustment. Элементы управления рукописным вводом используют Microsoft.UI.Xaml.Controls версии. Штрихи и атрибуты рисования по-прежнему используют типы среда выполнения Windows, например InkStroke, InkDrawingAttributesи InkStrokeBuilder. При импорте обоих пространств имен и использовании неоднозначного имени компилятор C# сообщает об ошибке CS0104.
Примеры, приведенные в этой статье, импортируют Microsoft.UI.Xaml.Controls и используют директивы псевдонима для Windows.UI.Input.Inking необходимых типов:
using Microsoft.UI.Xaml.Controls;
using InkDrawingAttributes = Windows.UI.Input.Inking.InkDrawingAttributes;
using InkStroke = Windows.UI.Input.Inking.InkStroke;
Вы также можете импортировать оба пространства имен и использовать полные имена для неоднозначных типов, как это делает пример WinUI 3 Gallery.
InkPresenter также предоставляет ИНТЕРФЕЙСы API нижнего уровня, которые эта статья не охватывает:
-
StrokeInputвызывает события, когда штрих начинается, продолжается, завершается или отменяется. -
UnprocessedInputвызывает события указателя для входных данных, которые элемент представления не обрабатывает как рукописный ввод. -
InputConfigurationЗадает, обрабатывает ли указатель входные данные от ластика пера и основной боковой кнопки. -
InputProcessingConfigurationЗадает, приводит ли ввод к рисованию рукописного ввода, удалению рукописного ввода или не обрабатывается, а также как презентатор обрабатывает ввод при перетаскивании правой кнопкой мыши. -
ActivateCustomDryingвозвращаетInkSynchronizerдля приложений, которые самостоятельно отрисовывают завершенный рукописный ввод.
Настройте ваш проект
API цифровых чернил помечены как экспериментальные. При использовании их в проекте C# компилятор C# сообщает предупреждение CS8305 и предупреждение компилятора XAML WMC1501. Если проект обрабатывает предупреждения как ошибки, сборка останавливается из-за этих предупреждений.
После того как вы решите использовать экспериментальные API, вы можете отключить эти предупреждения. Добавьте следующие свойства в файл проекта C# (CSPROJ):
<PropertyGroup>
<NoWarn>$(NoWarn);CS8305</NoWarn>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);WMC1501</MSBuildWarningsAsMessages>
</PropertyGroup>
В этой статье используются примеры на C#. Каждый полный пример показывает XAML для страницы и класса программной части страницы, MainPage.
Создать область рукописного ввода
![]()
Приложение WinUI 3 Gallery содержит интерактивные примеры элементов управления и функций WinUI. Получите приложение из Microsoft Store или просмотрите исходный код в GitHub.
В следующем примере холст помещается внутрь белого Border. Код программной части задает типы устройств ввода, чтобы пользователи могли рисовать с помощью пера, мыши или сенсорного ввода.
<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;
}
}
Чтобы сохранить их короткими, некоторые более поздние примеры не повторяют этот код. Задайте InputDeviceTypes на каждой странице, где пользователи рисуют мышью.
Подключите панель инструментов рукописного ввода
Поместите рядом с холстом InkToolbar и задайте его свойству TargetInkCanvas значение, чтобы соединить их:
<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>
Начиная с Windows App SDK 2.5.4-experimental, можно вместо этого подключить панель инструментов непосредственно к InkPresenter, задав свойство TargetInkPresenter. Задайте его в коде:
inkToolbar.TargetInkPresenter = inkCanvas.InkPresenter;
Его также можно задать в XAML: TargetInkPresenter="{x:Bind inkCanvas.InkPresenter}". Задайте только одно из двух целевых свойств.
По умолчанию на панели инструментов отображаются кнопки пера, карандаша, выделения, ластика и трафарета (линейка). Чтобы изменить отображаемые кнопки, см. статью Настройка панели инструментов рукописного ввода.
Выберите устройства ввода
Свойство InputDeviceTypes использует CoreInputDeviceTypes перечисление флагов из Windows.UI.Core пространства имен. Ввод мышью по умолчанию не позволяет рисовать рукописные штрихи. Объедините значения, чтобы выбрать, какие устройства используют рукописный ввод:
// 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);
Чтобы прекратить сбор рукописного ввода с каждого устройства, например, чтобы отобразить рисунок, который пользователи не могут изменить, установите IsInputEnabled в значение false:
inkCanvas.InkPresenter.IsInputEnabled = false;
Чтобы пользователи могли включать или выключать устройство ввода на панели инструментов, см. статью "Добавить настраиваемую кнопку переключателя".
Задать атрибуты рисования
Атрибуты рисования, такие как цвет, размер и фигура кончика пера, управляют тем, как выглядят штрихи. Они используют класс среда выполнения WindowsInkDrawingAttributes. Чтобы изменить атрибуты для новых штрихов, вызовите CopyDefaultDrawingAttributes, измените копию и передайте её в UpdateDefaultDrawingAttributes:
InkDrawingAttributes attributes = inkCanvas.InkPresenter.CopyDefaultDrawingAttributes();
attributes.Color = Colors.DarkBlue;
attributes.Size = new Size(4, 4);
inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(attributes);
Colors находится в Microsoft.UI пространстве имен и Size находится в Windows.Foundation пространстве имен. Новые атрибуты применяются к штрихам, которые пользователи рисуют после звонка. Существующие штрихи сохраняют свои атрибуты.
При подключении InkToolbar активный инструмент и его параметры цвета и размера определяют атрибуты рисования. Чтобы прочитать текущие атрибуты, используйте свойство InkDrawingAttributes панели инструментов.
Создать предустановки пера
Если вы не используете панель инструментов, вы можете настроить собственные предустановки пера. В следующем примере применяются атрибуты шариковой ручки, карандаша, маркера или каллиграфии, когда пользователь выбирает предустановку или изменяет размер.
<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.CreateForPencilвозвращает атрибуты для штрихов карандаша. СвойствоKindдоступно только для чтения, поэтому используйте этот метод для создания атрибутов стилуса. -
DrawAsHighlighterОтрисовывает обводку как наложение маркера. -
PenTipзадает фигуру кончика пера иPenTipTransformповорачивает или масштабирует его. Повернутый прямоугольный кончик создает штрихи в стиле каллиграфии.
Реагирование на изменения цифровых чернил
Средство представления вызывает StrokesCollected после того, как пользователь рисует штрихи, и StrokesErased после того, как пользователь стирает их. Свойство Strokes аргументов каждого события содержит затронутые росчерки. Следующий код поддерживает актуальное количество штрихов в strokeCountText с именем TextBlock:
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";
}
Эти события происходят при рисовании или удалении рукописного ввода пользователями. Когда вы изменяете штрихи в коде, например с помощью Clear, LoadAsync или AddStroke, обновите пользовательский интерфейс после вызова.
Сохранить и загрузить рукописный ввод
InkStrokeContainer сохраняет рукописный ввод в сериализованном формате рукописного ввода (ISF). Чтобы выбрать способ хранения данных, передайте значение InkPersistenceFormat в SaveAsync:
| Format | Output |
|---|---|
GifWithEmbeddedIsf |
Изображение GIF, содержащее данные рукописного ввода. Средства просмотра изображений могут отображать файл, и приложение может снова загрузить рукописный ввод для редактирования. |
Isf |
Только рукописные данные без изображения. Это самый компактный формат. |
Добавьте следующие методы в кодовую часть страницы, содержащей элемент InkCanvas с именем inkCanvas.
SaveInkAsync записывает рукописный ввод в файл, и LoadInkAsync заменяет рукописный ввод на холсте рукописным вводом из файла.
LoadAsync удаляет существующие штрихи перед добавлением загруженных штрихов, поэтому сначала не нужно вызывать Clear .
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 и AsInputStream преобразуют потоки .NET в типы потоков среда выполнения Windows, которые используют SaveAsync и LoadAsync. Чтобы ожидать эти методы среда выполнения Windows, файлу требуется директиваusing System;. Без него компилятор сообщает об ошибке CS4036.
Чтобы разрешить пользователям выбирать файл, используйте средства выбора Windows App SDK файлов, возвращающие путь выбранного файла. Дополнительные сведения см. в статье "Сохранение файла с помощью средства выбора Windows App SDK" и "Открытие файлов и папок с помощью средства выбора Windows App SDK".
Чтобы сохранить рукописный ввод в памяти, например восстановить его позже в том же сеансе, сохраните его в объект InMemoryRandomAccessStream. Передайте результат потока SaveAsync в GetOutputStreamAt(0), а его результат GetInputStreamAt(0) — в LoadAsync. В примере WinUI 3 Gallery используется этот подход.
Копирование и вставка рукописного ввода
InkStrokeContainer может скопировать штрихи в буфер обмена и вставить рукописный ввод из него.
CopySelectedToClipboard копирует только выбранные штрихи, поэтому следующий пример выбирает каждый штрих на левом холсте перед их копированием.
CanPasteFromClipboard проверяет, содержит ли буфер обмена рукописный ввод.
PasteFromClipboard добавляет штрих в положение, которое вы передаете, и возвращает ограничивающий прямоугольник области, которая изменилась.
<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.";
}
}
Чтобы работать с некоторыми штрихами, задайте свойство Selected для каждого нужного штриха или вызовите SelectWithLine или SelectWithPolyLine. Чтобы удалить или переместить выбранные штрихи, вызовите DeleteSelected или MoveSelected.
Создание обводок в коде
Используйте класс среда выполнения Windows InkStrokeBuilder для создания штрихов из точек и добавления их на холст с помощью AddStroke. В следующем примере добавляется звезда и строка подписи.
<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);
}
}
-
CreateStrokeFromInkPointsсоздает обводку из значенийInkPoint. Каждая точка содержит позицию на холсте и значение давления. - В документации по среда выполнения Windows рекомендуется использовать
CreateStrokeFromInkPointsвместоCreateStrokeдля росчерков, которые вы добавляете в объект рукописного ввода. - Когда
FitToCurveимеет значениеfalse, штрих использует прямые сегменты между точками вместо кривых.
Штрих может принадлежать только одному контейнеру. Чтобы скопировать штрихи на другой холст, добавьте клоны штрихов:
private void CopyStrokesTo(InkCanvas target)
{
foreach (InkStroke stroke in inkCanvas.InkPresenter.StrokeContainer.GetStrokes())
{
target.InkPresenter.StrokeContainer.AddStroke(stroke.Clone());
}
}
Настройте панель инструментов рукописного ввода
Выберите начальные контролы
Свойство InitialControls определяет, какие встроенные кнопки создает панель инструментов:
| Value | Встроенные кнопки |
|---|---|
All (по умолчанию) |
Шариковая ручка, карандаш, маркер, ластик и трафарет (линейка) |
PensOnly |
Шариковая ручка, карандаш и маркер |
AllExceptPens |
Ластик и трафарет (линейка) |
None |
None. Объявите кнопки, которые вы хотите использовать в качестве дочерних элементов панели инструментов. |
В следующем примере объявляются те же четыре кнопки, что и в примере коллекции WinUI 3, каждой из них присваивается доступное имя, а при загрузке панели инструментов выбирается шариковая ручка:
<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;
}
};
}
}
Чтобы отобразить команду, которая удаляет все рукописные фрагменты, задайте свойству кнопки ластика IsClearAllVisible значение true. Панель инструментов вызывает событие EraseAllClicked, когда пользователь выбирает команду.
Настройте цвета и ширину пера
Каждая кнопка пера содержит набор Palette кистей и параметры ширины штриха. Задайте Palette в коде. Дополнительные сведения см. в разделе со сведениями об известных ограничениях. Следующий пример заменяет палитру шариковой ручки четырьмя цветами бренда, ограничивает ширину штриха и показывает атрибуты активного пера.
<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 Выберите цвет из Palette.
SelectedStrokeWidth, MinStrokeWidth и MaxStrokeWidth задают текущую толщину штриха и диапазон, из которого пользователи могут выбирать. Панель инструментов генерирует событие InkDrawingAttributesChanged при изменении атрибутов активного средства.
Выберите активный инструмент
Задайте ActiveTool для выбора инструмента и обработайте ActiveToolChanged, чтобы реагировать, когда пользователь выбирает другой инструмент.
GetToolButton возвращает встроенную кнопку инструмента того типа, который вы передаете. Встроенные кнопки могут не существовать до загрузки панели инструментов, поэтому выберите инструмент, выбранный по умолчанию, в обработчике события Loaded панели инструментов.
<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}";
}
}
Изменить ориентацию
Чтобы отобразить панель инструментов по вертикали, например рядом с холстом, установите Orientation значение 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>
Свойство ButtonFlyoutPlacement задает, где всплывающие меню кнопки открываются относительно панели инструментов. Значение Autoпо умолчанию позволяет панели инструментов выбрать позицию.
Добавить настраиваемую кнопку переключения
Используйте элемент InkToolbarCustomToggleButton, чтобы добавить параметр, который пользователи включают и отключают с панели инструментов. В следующем примере добавляется переключатель, который управляет сенсорным вводом. Ввод с помощью пера и мыши всегда выполняет рукописный ввод.
<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}";
}
}
Переключатель отображает только значок, поэтому в примере ему задаются доступное имя и подсказка. В этом примере панель инструментов также создает свои кнопки по умолчанию, а настраиваемый переключатель появляется после них.
Панель инструментов поддерживает другие пользовательские кнопки:
-
InkToolbarCustomToolButtonдобавляет ваш собственный инструмент. -
InkToolbarCustomPenButtonдобавляет перо с собственными атрибутами рисования. Присвойте свойствуCustomPenэкземпляр класса, который наследуется отCreateInkDrawingAttributesCoreи переопределяет методInkToolbarCustomPen.
Создайте панель инструментов в коде
Вы также можете создать холст и панель инструментов в коде. В следующем примере они добавляются в объект Border с именем inkHost, подключаются к панели инструментов с помощью TargetInkPresenter, и при загрузке панели инструментов выбирается карандаш.
<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;
}
}
Известные ограничения
- Элементы управления рукописным вводом доступны только в экспериментальных выпусках Windows App SDK. Они недоступны в стабильном канале.
- API для рукописного ввода не включают распознавание рукописного текста или фигур.
- Ввод с помощью мыши не отображает рукописный ввод, пока вы не добавите
CoreInputDeviceTypes.MouseвInputDeviceTypes.
Следующие проблемы были зарегистрированы в Windows App SDK 2.4.1-experimental. Заметки о выпуске 2.5.4-experimental не перечисляют их как исправленные:
- Трафареты линейки и транспортира не работают.
-
RenderTargetBitmapне захватывает ввод пером. Чтобы создать изображение чернил, сохраните его в форматеGifWithEmbeddedIsf. - Установка свойства кнопки стилуса
Paletteв XAML приводит к ошибке синтаксического анализа XAML. ЗадайтеPaletteв коде, как показано в разделе "Настройка цветов и ширины пера". - Всплывающие элементы кнопки могут отображаться пустыми, и изменение
ButtonFlyoutPlacementможет не изменить место, где они открываются.
Изменения в 2.4.1-experimental
Если вы использовали API рукописного ввода в Windows App SDK 2.4.1-экспериментальной версии, обновите код для этих изменений в 2.5.4-экспериментальной версии:
| API в версии 2.4.1-experimental | Изменение в 2.5.4-experimental |
|---|---|
InkToolbar.TargetInkPresenter (тип Object) |
Тип свойства — InkPresenter. |
методы InkPresenter.GetHighContrastAdjustment и InkPresenter.SetHighContrastAdjustment |
Заменено свойством InkPresenter.HighContrastAdjustment . |
методы InkStrokeInput.GetInkPresenter и InkUnprocessedInput.GetInkPresenter |
Заменяется свойством InkPresenter в каждом классе. |
InkToolbarEraserButton.SelectedEraser, IsStrokeEraserVisible и InkToolbarEraserKind свойства; InkToolbar.EraserFlyoutItemClicked событие; InkToolbarEraserFlyoutItemKind, InkToolbarEraserFlyoutItemClickedEventArgs и ArePrecisionErasersVisible типы |
Удалено. |
| Недоступно |
InkPresenter.ActivateCustomDrying добавлен метод и InkSynchronizer класс. |
Специальные возможности и высокая контрастность
Цифровые чернила — это визуальное содержимое, поэтому спланируйте, как все пользователи могут использовать функции рукописного ввода:
- Присвойте каждому холсту и панели инструментов доступное имя с помощью
AutomationProperties.Name, как показано в примерах в этой статье. Присвойте пользовательским кнопкам, отображающим только значок, имя и всплывающую подсказку. -
InkCanvasAutomationPeerделает холст доступным для модель автоматизации пользовательского интерфейса, но он не описывает штрихи. Если рукописный ввод имеет значение, например подпись или помеченную схему, укажите замещающий текст. - Для рисования требуется ввод с помощью указателя. Не делайте ввод с помощью пера единственным способом выполнения задачи. Например, позвольте пользователям вводить свое имя вместо подписи.
- Одноранговые объекты автоматизации для встроенных кнопок панели инструментов поддерживают шаблоны элементов управления SelectionItem и ExpandCollapse. Проверьте навигацию с помощью клавиатуры и всплывающие меню кнопок с конфигурацией панели инструментов, которую использует ваше приложение.
Если тема контраста активна, выступающий может заменить цвета рукописного ввода системными цветами. Чтобы выбрать это поведение, задайте свойству HighContrastAdjustment значение InkHighContrastAdjustment из пространства имен Microsoft.UI.Xaml.Controls. В документации среда выполнения Windows для перечисления с тем же именем значения описываются следующим образом:
| Value | Цвет чернил в теме контрастности |
|---|---|
UseSystemColorsWhenNecessary |
Собственный цвет чернил, если он достаточно контрастен фону. В противном случае используется системный цвет. |
UseSystemColors |
Системный цвет. Чернила маркера используют системный цвет средства выделения. |
UseOriginalColors |
Собственный цвет чернил. |
inkCanvas.InkPresenter.HighContrastAdjustment = InkHighContrastAdjustment.UseSystemColorsWhenNecessary;
Для краткости примеры в этой статье задают белый фон напрямую. В приложении определите фон холста в словарях тем, чтобы он следовал активной теме, как описано в Контрастные темы. Затем протестируйте приложение с каждой темой контрастности и убедитесь, что цвет переднего плана, фон и граница остаются различимыми.
Дополнительные сведения см. в разделе Предоставление основных сведений о специальных возможностях и Клавиатурная доступность.
См. также
- Исходный код примера InkCanvas для коллекции WinUI 3
- Примечания к выпуску Windows App SDK 2.5.4 experimental
- Пространство имен Microsoft.UI.Xaml.Controls
- пространство имен Windows.UI.Input.Inking
- Обратная связь пера в приложениях Windows
- Взаимодействие пера и Windows Ink (UWP)
- Элементы управления рукописным вводом (UWP)
- Что поддерживается при миграции с UWP на WinUI 3
Windows developer