Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Important
InkCanvas, InkToolbari InkPresenter są eksperymentalnymi interfejsami API. Dodano obsługę pisma odręcznego w Zestaw SDK do aplikacji systemu Windows 2.4.1-experimental. W tym artykule opisano Zestaw SDK do aplikacji systemu Windows 2.5.4-experimental, które zmieniło sposób łączenia InkToolbar. Te interfejsy API nie są dostępne w stabilnym kanale. Mogą one ulec zmianie lub zostać usunięte w przyszłej wersji. Nie używaj ich w aplikacjach produkcyjnych.
Użyj eksperymentalnej kontrolki InkCanvas, aby umożliwić użytkownikom rysowanie odręczne za pomocą pióra, myszy lub dotyku. Dodaj element InkToolbar, aby umożliwić użytkownikom korzystanie z pióra kulkowego, ołówka, zakreślacza i gumki z opcjami koloru i rozmiaru. Każdy obszar roboczy zawiera element InkPresenter, który zbiera i renderuje dane wejściowe pisma odręcznego. Moduł prezentera InkStrokeContainer przechowuje ślady, dzięki czemu można zapisywać, ładować, kopiować i tworzyć zapis odręczny w kodzie.
Aby wypróbować kontrolki, zainstaluj pakiet eksperymentalny Zestaw SDK do aplikacji systemu Windows 2.5.4 i pasujące środowisko uruchomieniowe. Aby uzyskać linki do pobrania i informacje o konfiguracji, zobacz Najnowsze pliki do pobrania pakietu Zestaw SDK do aplikacji systemu Windows.
Czy jest to właściwa kontrola?
Użyj kanwy pisma odręcznego, gdy użytkownicy muszą:
- Rób notatki odręczne lub dodawaj adnotacje do treści.
- Szkicować, rysować diagramy lub podpisać się.
- Utwórz zawartość freehand, którą aplikacja zapisuje i pozwala edytować później.
Eksperymentalne interfejsy API obsługi pisma odręcznego nie obejmują rozpoznawania pisma odręcznego ani rozpoznawania kształtów. Aby zebrać tekst, użyj pola tekstowego.
Jeśli potrzebujesz obsługi pisma odręcznego w aplikacji produkcyjnej, zobacz Znane braki kontrolek, aby sprawdzić stan tych kontrolek w stabilnym kanale oraz alternatywne rozwiązania.
Poznaj model obsługi pisma cyfrowego
Interfejsy API obsługi pisma odręcznego współpracują ze sobą:
| API | Rola |
|---|---|
InkCanvas |
Element XAML, który wyświetla pismo odręczne i odbiera dane wejściowe pisma odręcznego. Jego InkPresenter właściwość zwraca presenter dla obszaru roboczego. |
InkPresenter |
Zbiera dane z wprowadzania pisma odręcznego, renderuje pociągnięcia i przechowuje ustawienia wejściowe i domyślne atrybuty rysunku. Nie można samodzielnie utworzyć InkPresenter. Pobierz go z witryny InkCanvas.InkPresenter. |
InkStrokeContainer |
Przechowuje kreski. Służy do zapisywania, ładowania, wybierania, kopiowania i wklejania atramentu. Pobierz to z InkPresenter.StrokeContainer. |
InkToolbar |
Opcjonalny pasek narzędzi, który umożliwia użytkownikom wybieranie pióra, ołówka, zakreślacza lub gumki oraz zmienianie koloru i rozmiaru pisma odręcznego. |
InkCanvas nie ma właściwości Background. Aby nadać obszarowi rysunku tło i obramowanie, umieść kanwę w kontenerze, takim jak Border, i ustaw rozmiar kontenera. Domyślny kolor atramentu jest czarny, więc przykłady w tym artykule używają białego tła.
Kilka z tych typów ma takie same nazwy jak typy w przestrzeni nazw środowisko wykonawcze systemu Windows Windows.UI.Input.Inking, takie jak InkPresenter, InkStrokeContainer i InkHighContrastAdjustment. Kontrolki pisma odręcznego używają wersji Microsoft.UI.Xaml.Controls. Atrybuty pociągnięć i rysunku nadal używają typów środowisko wykonawcze systemu Windows, takich jak InkStroke, InkDrawingAttributesi InkStrokeBuilder. Jeśli importujesz obie przestrzenie nazw i używasz niejednoznacznej nazwy, kompilator języka C# zgłasza błąd CS0104.
Przykłady w tym artykule importują Microsoft.UI.Xaml.Controls i używają dyrektyw aliasów dla typów Windows.UI.Input.Inking, których potrzebują:
using Microsoft.UI.Xaml.Controls;
using InkDrawingAttributes = Windows.UI.Input.Inking.InkDrawingAttributes;
using InkStroke = Windows.UI.Input.Inking.InkStroke;
Możesz również zaimportować obie przestrzenie nazw i użyć w pełni kwalifikowanych nazw dla niejednoznacznych typów, tak jak robi to przykład z galerii WinUI 3.
InkPresenter również udostępnia interfejsy API niższego poziomu, których ten artykuł nie obejmuje:
-
StrokeInputzgłasza zdarzenia, gdy pociągnięcie rozpoczyna się, jest kontynuowane, kończy lub jest anulowane. -
UnprocessedInputzgłasza zdarzenia wskaźnika dla danych wejściowych, których prezenter nie przetwarza jako pismo odręczne. -
InputConfigurationokreśla, czy moduł prezentujący przetwarza dane wejściowe z gumki pióra i głównego przycisku bocznego. -
InputProcessingConfigurationUstawia, czy dane wejściowe powodują rysowanie atramentem, wymazują atrament, czy nie są przetwarzane, oraz jak prezenter obsługuje dane wejściowe przeciągania prawym przyciskiem. -
ActivateCustomDryingfunkcja zwraca wartośćInkSynchronizerdla aplikacji, które samodzielnie renderują zatwierdzone pismo odręczne.
Skonfiguruj projekt
Interfejsy API obsługi atramentu cyfrowego są oznaczone jako eksperymentalne. Gdy używasz ich w projekcie języka C#, kompilator języka C# zgłasza ostrzeżenie CS8305 i kompilator XAML zgłasza ostrzeżenie WMC1501. Jeśli projekt traktuje ostrzeżenia jako błędy, te ostrzeżenia zatrzymają kompilację.
Po podjęciu decyzji o użyciu eksperymentalnych interfejsów API można wyciszyć te ostrzeżenia. Dodaj następujące właściwości do pliku projektu C# (csproj):
<PropertyGroup>
<NoWarn>$(NoWarn);CS8305</NoWarn>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);WMC1501</MSBuildWarningsAsMessages>
</PropertyGroup>
W przykładach w tym artykule użyto języka C#. Każdy kompletny przykład przedstawia kod XAML strony oraz klasę kodu źródłowego tej strony, MainPage.
Utwórz kanwę pisma odręcznego
- Ważne interfejsy API: klasa InkCanvas, klasa InkPresenter, klasa InkStrokeContainer, klasa InkToolbar, przestrzeń nazw Windows.UI.Input.Inking
![]()
Aplikacja Galeria WinUI 3 zawiera interaktywne przykłady kontrolek i funkcji WinUI. Pobierz aplikację z Microsoft Store lub przejrzyj kod źródłowy w GitHub.
Poniższy przykład umieszcza obszar roboczy wewnątrz białego elementu Border. Kod ustawia typy urządzeń wejściowych, aby użytkownicy mogli rysować piórem, myszą lub dotykiem.
<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;
}
}
Aby były krótkie, niektóre późniejsze przykłady nie powtarzają tego kodu. Ustaw InputDeviceTypes na każdej stronie, na której użytkownicy rysują za pomocą myszy.
Połącz pasek narzędzi odręcznych
Umieść obiekt w InkToolbar pobliżu obszaru roboczego i ustaw jej TargetInkCanvas właściwość, aby połączyć te dwa elementy:
<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>
Począwszy od Zestaw SDK do aplikacji systemu Windows 2.5.4 w wersji eksperymentalnej, można zamiast tego połączyć pasek narzędzi bezpośrednio z obiektem InkPresenter, ustawiając właściwość TargetInkPresenter. Ustaw go w kodzie:
inkToolbar.TargetInkPresenter = inkCanvas.InkPresenter;
Można go również ustawić w języku XAML: TargetInkPresenter="{x:Bind inkCanvas.InkPresenter}". Ustaw tylko jedną z dwóch właściwości docelowych.
Domyślnie na pasku narzędzi są wyświetlane przyciski długopisu, ołówka, zakreślacza, gumki i szablonu (linijki). Aby zmienić wyświetlane przyciski, zobacz Dostosowywanie paska narzędzi pisma odręcznego.
Wybierz urządzenia wejściowe
Właściwość InputDeviceTypes używa CoreInputDeviceTypes wyliczenia flag z Windows.UI.Core przestrzeni nazw. Domyślnie wprowadzanie za pomocą myszy nie tworzy odręcznego pisma. Połącz wartości, aby wybrać, które urządzenia używają pisma odręcznego:
// 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);
Aby zatrzymać zbieranie atramentu cyfrowego z każdego urządzenia, na przykład w celu wyświetlenia rysunku, którego użytkownicy nie mogą zmienić, ustaw wartość IsInputEnabled na false:
inkCanvas.InkPresenter.IsInputEnabled = false;
Aby umożliwić użytkownikom włączanie lub wyłączanie urządzenia wejściowego z paska narzędzi, zobacz Dodawanie niestandardowego przycisku przełącznika.
Ustaw atrybuty rysunku
Atrybuty rysunku, takie jak kolor, rozmiar i kształt końcówki pióra, kontrolują wygląd pociągnięć. Używają klasy środowisko wykonawcze systemu WindowsInkDrawingAttributes. Aby zmienić atrybuty nowych pociągnięć, wywołaj CopyDefaultDrawingAttributes, zmień kopię i przekaż ją do UpdateDefaultDrawingAttributes:
InkDrawingAttributes attributes = inkCanvas.InkPresenter.CopyDefaultDrawingAttributes();
attributes.Color = Colors.DarkBlue;
attributes.Size = new Size(4, 4);
inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(attributes);
Colors znajduje się w Microsoft.UI przestrzeni nazw i Size znajduje się w Windows.Foundation przestrzeni nazw. Nowe atrybuty mają zastosowanie do kresek, które użytkownicy rysują po wywołaniu tej funkcji. Istniejące obrysy zachowują swoje atrybuty.
Po podłączeniu InkToolbar aktywne narzędzie i jego ustawienia koloru i rozmiaru określają atrybuty rysunku. Aby odczytać bieżące atrybuty, użyj właściwości InkDrawingAttributes paska narzędzi.
Utwórz presety pióra
Jeśli nie używasz paska narzędzi, możesz udostępnić własne ustawienia wstępne pióra. W poniższym przykładzie zastosowuje się atrybuty długopisu, ołówka, zakreślacza lub kaligrafii, gdy użytkownik wybierze ustawienie wstępne lub zmieni rozmiar.
<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.CreateForPencilzwraca atrybuty dla pociągnięć ołówka. WłaściwośćKindjest tylko do odczytu, więc użyj tej metody, aby utworzyć atrybuty pisaka. -
DrawAsHighlighterrenderuje pociągnięcie jako nakładkę wyróżnienia. -
PenTipustawia kształt końcówki pióra, aPenTipTransformobraca ją lub skaluje. Obrócona prostokątna końcówka tworzy pociągnięcia w stylu kaligrafii.
Reaguj na zmiany atramentu cyfrowego
Prezenter wywołuje StrokesCollected po tym, jak użytkownik rysuje pociągnięcia, i StrokesErased po usunięciu ich przez użytkownika. Właściwość Strokes argumentów każdego zdarzenia zawiera udary, których dotyczą. Poniższy kod aktualizuje liczbę obrysów w elemencie TextBlock o nazwie 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";
}
Te zdarzenia występują, gdy użytkownicy rysują lub usuwają pismo odręczne. Gdy zmieniasz kreski w kodzie, na przykład za pomocą Clear, LoadAsync lub AddStroke, zaktualizuj interfejs użytkownika po wywołaniu.
Zapisywanie i ładowanie odręcznych notatek
InkStrokeContainer zapisuje pismo odręczne w formacie serializacji pisma odręcznego (ISF). Aby wybrać sposób przechowywania danych, przekaż wartość InkPersistenceFormat do SaveAsync:
| Format | Output |
|---|---|
GifWithEmbeddedIsf |
Obraz GIF zawierający dane pisma odręcznego. Osoby przeglądające obrazy mogą wyświetlać plik, a aplikacja może ponownie załadować pismo odręczne do edycji. |
Isf |
Tylko dane atramentu, bez obrazu. Jest to najbardziej kompaktowy format. |
Dodaj następujące metody do pliku code-behind strony zawierającej InkCanvas o nazwie inkCanvas.
SaveInkAsync zapisuje atrament do pliku i LoadInkAsync zastępuje atrament w obszarze roboczym atramentem z pliku.
LoadAsync Usuwa istniejące pociągnięcia przed dodaniem załadowanych pociągnięć, więc nie trzeba najpierw wywoływać 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 i AsInputStream konwertują strumienie .NET na typy strumieni środowisko wykonawcze systemu Windows, których używają SaveAsync i LoadAsync. Aby używać tych metod środowisko wykonawcze systemu Windows z await, plik wymaga dyrektywy using System;. Bez niego kompilator zgłasza błąd CS4036.
Aby umożliwić użytkownikom wybranie pliku, użyj selektorów plików Zestaw SDK do aplikacji systemu Windows, które zwracają ścieżkę wybranego pliku. Aby uzyskać więcej informacji, zobacz Zapisywanie pliku przy użyciu selektora Zestaw SDK do aplikacji systemu Windows oraz Otwieranie plików i folderów przy użyciu selektorów Zestaw SDK do aplikacji systemu Windows.
Aby zachować pismo odręczne w pamięci, na przykład w celu przywrócenia go w dalszej części tej samej sesji, zapisz je w pliku InMemoryRandomAccessStream. Przekaż wynik strumienia GetOutputStreamAt(0) do SaveAsyncelementu i przekaż jego GetInputStreamAt(0) wynik do LoadAsync. Przykład z galerii WinUI 3 korzysta z tego podejścia.
Kopiowanie i wklejanie atramentu
InkStrokeContainer może kopiować pociągnięcia do schowka i wklejać z niego odręczne wpisy.
CopySelectedToClipboard Kopiuje tylko wybrane pociągnięcia, więc w poniższym przykładzie zaznacza wszystkie pociągnięcia na lewym obszarze roboczym przed ich skopiowaniem.
CanPasteFromClipboard sprawdza, czy schowek zawiera atrament.
PasteFromClipboard Dodaje tusz w pozycji, którą przekazujesz, i zwraca prostokąt ograniczający obszaru, który uległ zmianie.
<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.";
}
}
Aby pracować z niektórymi śladami pisma odręcznego, ustaw właściwość Selected dla każdego wybranego śladu pisma odręcznego albo wywołaj metodę SelectWithLine lub SelectWithPolyLine. Aby usunąć lub przenieść zaznaczone pociągnięcia, wywołaj DeleteSelected lub MoveSelected.
Tworzenie kresek w kodzie
Użyj klasy środowisko wykonawcze systemu WindowsInkStrokeBuilder, aby utworzyć pociągnięcia z punktów i dodać je do canvas za pomocą polecenia AddStroke. Poniższy przykład dodaje gwiazdkę i wiersz podpisu.
<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);
}
}
-
CreateStrokeFromInkPointstworzy obrys na podstawie wartościInkPoint. Każdy punkt składa się z pozycji na obszarze roboczym i wartości ciśnienia. - Dokumentacja środowisko wykonawcze systemu Windows zaleca
CreateStrokeFromInkPointszamiastCreateStrokedla kresek dodanych do prezentera pisma. - Gdy wartość
FitToCurvetofalse, obrys używa prostych segmentów między punktami zamiast krzywych.
Pociągnięcie może należeć tylko do jednego kontenera. Aby skopiować pociągnięcia na inny obszar roboczy, dodaj klony pociągnięć:
private void CopyStrokesTo(InkCanvas target)
{
foreach (InkStroke stroke in inkCanvas.InkPresenter.StrokeContainer.GetStrokes())
{
target.InkPresenter.StrokeContainer.AddStroke(stroke.Clone());
}
}
Dostosowywanie paska narzędzi pisma odręcznego
Wybieranie początkowych elementów sterujących
Właściwość InitialControls określa, które wbudowane przyciski tworzy pasek narzędzi:
| Value | Wbudowane przyciski |
|---|---|
All (ustawienie domyślne) |
Pióro kulkowe, ołówek, zakreślacz, gumka i szablon (linijka) |
PensOnly |
Długopis, ołówek i zakreślacz |
AllExceptPens |
Gumka i szablon (linijka) |
None |
None. Zadeklaruj przyciski, które mają być elementami podrzędnymi paska narzędzi. |
Poniższy przykład deklaruje te same cztery przyciski co przykład z galerii WinUI 3, nadaje każdemu z nich dostępną nazwę i wybiera długopis po załadowaniu paska narzędzi:
<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;
}
};
}
}
Aby wyświetlić polecenie, które wymazuje cały atrament, ustaw właściwość przycisku IsClearAllVisible gumki na wartość true. Pasek narzędzi zgłasza zdarzenie EraseAllClicked, gdy użytkownik wybierze polecenie.
Dostosuj kolory i szerokości pisaka
Każdy przycisk pióra ma zestaw Palette ustawień pędzli i szerokości kreski. Ustaw Palette w kodzie. Aby uzyskać szczegółowe informacje, zobacz Znane ograniczenia. Poniższy przykład zastępuje paletę długopisu czterema kolorami marki, ogranicza szerokość pociągnięcia i pokazuje atrybuty aktywnego pióra.
<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 służy do wybierania koloru z Palette.
SelectedStrokeWidth, MinStrokeWidth i MaxStrokeWidth ustawiają bieżącą szerokość pociągnięcia i zakres, z którego użytkownicy mogą wybrać. Pasek narzędzi wywołuje InkDrawingAttributesChanged, gdy zmieniają się atrybuty aktywnego narzędzia.
Wybierz aktywne narzędzie
Ustaw ActiveTool, aby wybrać narzędzie, i obsłuż ActiveToolChanged, gdy użytkownik wybierze inne.
GetToolButton Zwraca wbudowany przycisk narzędzia rodzaju, który przekazujesz. Wbudowane przyciski mogą nie istnieć, dopóki pasek narzędzi nie zostanie załadowany, dlatego wybierz narzędzie początkowe w procedurze obsługi zdarzenia paska narzędzi 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}";
}
}
Zmień orientację
Aby wyświetlić pasek narzędzi pionowo, na przykład obok obszaru roboczego, ustaw Orientation na 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>
Właściwość ButtonFlyoutPlacement określa, gdzie względem paska narzędzi otwierają się wysuwane menu przycisków. Wartość domyślna, Auto pozwala paskowi narzędzi wybrać pozycję.
Dodaj niestandardowy przycisk przełączania
Użyj przełącznika InkToolbarCustomToggleButton, aby dodać ustawienie, które użytkownicy włączają i wyłączają z paska narzędzi. W poniższym przykładzie dodano przełącznik sterujący wprowadzaniem dotykowym. Wprowadzanie piórem i myszą zawsze umożliwia pisanie odręczne.
<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}";
}
}
Przełącznik pokazuje tylko ikonę, więc w przykładzie nadano mu dostępną nazwę i etykietkę narzędzia. W tym przykładzie pasek narzędzi tworzy również jego przyciski domyślne, a po nich pojawia się przełącznik niestandardowy.
Pasek narzędzi obsługuje inne przyciski niestandardowe:
-
InkToolbarCustomToolButtondodaje Twoje własne narzędzie. -
InkToolbarCustomPenButtondodaje pióro, które ma własne atrybuty rysowania. Ustaw jej właściwośćCustomPenna wystąpienie klasy, która dziedziczy poInkToolbarCustomPeni przesłania metodęCreateInkDrawingAttributesCore.
Utwórz pasek narzędzi w kodzie
Możesz również utworzyć kanwę i pasek narzędzi w kodzie. Poniższy przykład dodaje je do Border nazwy inkHost, łączy pasek narzędzi z elementem TargetInkPresenteri wybiera ołówek po załadowaniu paska narzędzi.
<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;
}
}
Znane ograniczenia
- Kontrolki inkowania są dostępne tylko w wersjach eksperymentalnych Zestaw SDK do aplikacji systemu Windows. Nie są one dostępne w stabilnym kanale.
- Interfejsy API do obsługi pisma cyfrowego nie obejmują rozpoznawania pisma ręcznego ani kształtów.
- Dane wejściowe myszy nie umożliwiają pisania odręcznego do momentu dodania
CoreInputDeviceTypes.Mousedo elementuInputDeviceTypes.
W przypadku wersji Zestaw SDK do aplikacji systemu Windows 2.4.1-experimental zgłoszono następujące problemy. Informacje o wydaniu wersji 2.5.4-experimental nie wymieniają ich jako naprawionych:
- Wzorniki linijki i kątomierza nie działają.
-
RenderTargetBitmapnie przechwytuje wprowadzania odręcznego. Aby utworzyć obraz atramentu, zapisz go w formacieGifWithEmbeddedIsf. - Ustawienie właściwości
Paletteprzycisku stylusa w języku XAML powoduje błąd analizy składni XAML. UstawPalettewartość w kodzie, jak pokazano w temacie Dostosowywanie kolorów i szerokości pióra. - Menu wysuwane przycisku mogą być puste, a zmiana
ButtonFlyoutPlacementmoże nie zmieniać miejsca ich otwierania.
Zmiany z 2.4.1-experimental
Jeśli używasz interfejsów API obsługi pisma odręcznego w Zestaw SDK do aplikacji systemu Windows 2.4.1-experimental, zaktualizuj kod pod kątem tych zmian w wersji 2.5.4-experimental:
| API w 2.4.1-experimental | Zmiana w wersji 2.5.4-eksperymentalnej |
|---|---|
InkToolbar.TargetInkPresenter (typ Object) |
Typ właściwości to InkPresenter. |
metody InkPresenter.GetHighContrastAdjustment i InkPresenter.SetHighContrastAdjustment |
Zastąpione przez właściwość InkPresenter.HighContrastAdjustment. |
metody InkStrokeInput.GetInkPresenter i InkUnprocessedInput.GetInkPresenter |
Zastąpione przez właściwość InkPresenter w każdej klasie. |
właściwości InkToolbarEraserButton.SelectedEraser, IsStrokeEraserVisible i ArePrecisionErasersVisible; zdarzenie InkToolbar.EraserFlyoutItemClicked; typy InkToolbarEraserKind, InkToolbarEraserFlyoutItemKind i InkToolbarEraserFlyoutItemClickedEventArgs |
Usunięte. |
| Niedostępne |
InkPresenter.ActivateCustomDrying dodano metodę i InkSynchronizer klasę. |
Ułatwienia dostępu i duży kontrast
Pismo odręczne to zawartość wizualna, więc zaplanuj, jak wszyscy użytkownicy mogą korzystać z funkcji pisma odręcznego:
- Nadaj każdej kanwie i paskowi narzędzi nazwę dostępności za pomocą
AutomationProperties.Name, tak jak w przykładach w tym artykule. Nadaj przyciski niestandardowe, które wyświetlają tylko ikonę, nazwę i etykietkę narzędzia. -
InkCanvasAutomationPeerudostępnia kanwę interfejsowi automatyzacja interfejsu użytkownika, ale nie opisuje pociągnięć. Jeśli odręczne pismo lub rysunek mają znaczenie, takie jak podpis lub diagram oznaczony etykietą, podaj alternatywę tekstową. - Rysowanie odręczne wymaga wprowadzania wskaźnikiem. Nie każ użytkownikom korzystać wyłącznie z wprowadzania odręcznego, aby ukończyć zadanie. Możesz na przykład zezwolić użytkownikom na wpisywanie imienia i nazwiska jako alternatywy dla podpisu.
- Elementy AutomationPeer dla wbudowanych przycisków paska narzędzi obsługują wzorce kontrolek SelectionItem i ExpandCollapse. Przetestuj nawigację za pomocą klawiatury oraz menu wysuwane z przycisków przy użyciu konfiguracji paska narzędzi używanej przez aplikację.
Gdy motyw kontrastu jest aktywny, prezenter może zamienić kolory pisma odręcznego na kolory systemowe. Aby wybrać to zachowanie, ustaw właściwość HighContrastAdjustment na wartość InkHighContrastAdjustment z przestrzeni nazw Microsoft.UI.Xaml.Controls. W dokumentacji środowisko wykonawcze systemu Windows dla wyliczenia o tej samej nazwie opisano wartości w następujący sposób:
| Value | Kolor pisma odręcznego w motywie o wysokim kontraście |
|---|---|
UseSystemColorsWhenNecessary |
Własny kolor atramentu, jeśli wystarczająco kontrastuje z tłem. W przeciwnym razie używany jest kolor systemowy. |
UseSystemColors |
Kolor systemu. Tusz zakreślacza używa systemowego koloru zakreślenia. |
UseOriginalColors |
Własny kolor atramentu. |
inkCanvas.InkPresenter.HighContrastAdjustment = InkHighContrastAdjustment.UseSystemColorsWhenNecessary;
Aby były krótkie, przykłady w tym artykule ustawiają bezpośrednio białe tło. W aplikacji zdefiniuj tło obszaru roboczego w słownikach motywów, tak aby było zgodne z aktywnym motywem, zgodnie z opisem w Motywy kontrastu. Następnie przetestuj aplikację przy użyciu każdego motywu kontrastu i sprawdź, czy kolor tekstu, tło i obramowanie pozostają rozróżnialne.
Aby uzyskać więcej informacji, zobacz Uwidaczniaj podstawowe informacje o ułatwieniach dostępu i Ułatwienia dostępu za pomocą klawiatury.
Informacje dodatkowe
- Przykładowy kod źródłowy galerii WinUI 3 InkCanvas
- informacje o wersji eksperymentalnej Zestaw SDK do aplikacji systemu Windows 2.5.4
- Przestrzeń nazw Microsoft.UI.Xaml.Controls
- przestrzeń nazw Windows.UI.Input.Inking
- Haptyczne sprzężenie zwrotne pióra w aplikacjach Windows
- Interakcje piórem i Windows Ink (UWP)
- Kontrolki pisma cyfrowego (UWP)
- Co jest obsługiwane podczas migracji z UWP do WinUI 3