Important
InkCanvas, InkToolbar및 InkPresenter 실험적 API입니다. 수동 입력 지원은 Windows 앱 SDK 2.4.1-experimental에서 추가되었습니다. 이 문서에서는 InkToolbar을 연결하는 방법이 변경된 Windows 앱 SDK 2.5.4-experimental에 대해 설명합니다. 이러한 API는 안정적인 채널에서 사용할 수 없습니다. 이후 릴리스에서 변경되거나 제거될 수 있습니다. 프로덕션 앱에서는 사용하지 마세요.
실험적 InkCanvas 컨트롤을 사용하여 사용자가 펜, 마우스 또는 터치로 잉크를 그릴 수 있도록 합니다.
InkToolbar를 추가하여 색 및 크기 옵션과 함께 사용자에게 볼펜, 연필, 형광펜 및 지우개를 제공합니다. 각 캔버스에는 잉크 입력을 수집하고 렌더링하는 InkPresenter가 있습니다. presenter의 InkStrokeContainer는 스트로크를 저장하므로 코드에서 저장, 로드, 복사하고 잉크를 만들 수 있습니다.
컨트롤을 사용해 보려면 Windows 앱 SDK 2.5.4 실험적 패키지 및 일치하는 런타임을 설치하세요. 다운로드 링크 및 설정 정보는 최신 Windows 앱 SDK 다운로드를 참조하세요.
이것이 올바른 컨트롤인가요?
사용자가 다음을 수행할 필요한 경우 잉크 캔버스를 사용합니다.
- 손글씨로 메모하거나 콘텐츠에 주석을 답니다.
- 스케치하거나, 다이어그램을 그리거나, 이름에 서명할 수 있습니다
- 앱이 저장하고 나중에 사용자가 편집할 수 있는 자유형 콘텐츠를 만듭니다.
실험적 잉킹 API에는 필기 또는 도형 인식이 포함되지 않습니다. 텍스트를 수집하려면 텍스트 상자를 사용하세요.
프로덕션 앱에서 잉크 입력이 필요한 경우 안정적인 채널에서 이러한 컨트롤의 상태 및 대안에 대한 알려진 컨트롤 격차를 참조하세요.
잉킹 모델 이해
잉킹 API는 함께 작동합니다.
| API | 역할 |
|---|---|
InkCanvas |
잉크를 표시하고 잉크 입력을 수신하는 XAML 요소입니다. 해당 속성은 InkPresenter 캔버스에 대한 프레젠터를 반환합니다. |
InkPresenter |
잉크 입력을 수집하고, 스트로크를 렌더링하고, 입력 설정 및 기본 그리기 특성을 보유합니다. 직접 InkPresenter을 만들 수 없습니다.
InkCanvas.InkPresenter에서 가져오세요. |
InkStrokeContainer |
획을 저장합니다. 잉크를 저장, 로드, 선택, 복사 및 붙여넣는 데 사용합니다. 여기서 가져옵니다. |
InkToolbar |
사용자가 펜, 연필, 형광펜 또는 지우개를 선택하고 잉크 색과 크기를 변경할 수 있는 선택적 도구 모음입니다. |
InkCanvas에는 Background 속성이 없습니다. 그리기 영역에 배경과 테두리를 지정하려면 캔버스를 컨테이너(예: Border) 안에 놓고 컨테이너의 크기를 설정합니다. 기본 잉크 색은 검은색이므로 이 문서의 예제에서는 흰색 배경을 사용합니다.
이러한 형식 중 일부는 Windows.UI.Input.Inking 네임스페이스의 Windows 런타임 형식(예: InkPresenter, InkStrokeContainer, InkHighContrastAdjustment)과 이름이 같습니다. 잉크 입력 컨트롤은 Microsoft.UI.Xaml.Controls 버전을 사용합니다. 스트로크 및 그리기 특성은 여전히 Windows 런타임 형식(예: InkStroke, InkDrawingAttributes및 InkStrokeBuilder)을 사용합니다. 두 네임스페이스를 모두 가져오고 모호한 이름을 사용하는 경우 C# 컴파일러는 오류 CS0104를 보고합니다.
이 문서의 예제에서는 Windows.UI.Input.Inking를 가져오고 필요한 Microsoft.UI.Xaml.Controls 형식에 대해 별칭 지시문을 사용합니다.
using Microsoft.UI.Xaml.Controls;
using InkDrawingAttributes = Windows.UI.Input.Inking.InkDrawingAttributes;
using InkStroke = Windows.UI.Input.Inking.InkStroke;
WinUI 3 갤러리 샘플처럼 두 네임스페이스를 모두 가져오고 모호한 형식에 완전 수식 이름을 사용할 수도 있습니다.
InkPresenter 또한 이 문서에서 다루지 않는 하위 수준 API도 제공합니다.
-
StrokeInput는 스트로크가 시작되거나, 계속되거나, 종료되거나, 취소될 때 이벤트를 발생합니다. -
UnprocessedInput는 프레젠터가 잉크로 처리하지 않는 입력에 대한 포인터 이벤트를 발생합니다. -
InputConfiguration는 프레젠터가 펜의 지우개 및 기본 배럴 단추에서 입력을 처리하는지 여부를 설정합니다. -
InputProcessingConfiguration는 입력이 잉크를 그리거나, 잉크를 지우는지, 처리되지 않는지, 프레젠터가 오른쪽 끌기 입력을 처리하는 방법을 설정합니다. -
ActivateCustomDrying는 커밋된InkSynchronizer잉크를 직접 렌더링하는 앱에 대해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 갤러리 앱에는 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 앱 SDK 2.5.4-experimental부터는 InkPresenter 속성을 설정하여 도구 모음을 TargetInkPresenter에 직접 연결할 수 있습니다. 코드로 설정합니다.
inkToolbar.TargetInkPresenter = inkCanvas.InkPresenter;
XAML로도 설정할 수 있습니다. 두 대상 속성 중 하나만 설정합니다.
기본적으로 도구 모음에는 볼펜, 연필, 형광펜, 지우개 및 스텐실(눈금자) 단추가 표시됩니다. 표시되는 단추를 변경하려면 잉크 도구 모음 사용자 지정을 참조하세요.
입력 장치 선택
이 속성은 Windows.UI.Core 네임스페이스의 CoreInputDeviceTypes 플래그 열거형을 사용합니다. 마우스 입력은 기본적으로 잉크가 그려지지 않습니다. 값을 결합하여 잉크를 입력할 디바이스를 선택합니다.
// 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);
사용자가 변경할 수 없는 드로잉을 표시하기 위해 모든 장치에서 잉크 수집을 중지하려면 false를 IsInputEnabled로 설정합니다.
inkCanvas.InkPresenter.IsInputEnabled = false;
사용자가 도구 모음에서 입력 디바이스를 켜거나 끌 수 있도록 하려면 사용자 지정 토글 단추 추가를 참조하세요.
그리기 특성 설정
색, 크기 및 펜 팁 모양과 같은 그리기 특성은 스트로크의 모양을 제어합니다. Windows 런타임 InkDrawingAttributes 클래스를 사용합니다. 새 스트로크의 특성을 변경하려면 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는 이를 회전하거나 크기를 조정합니다. 회전된 사각형 팁은 서예 스타일 스트로크를 생성합니다.
잉크 변경에 반응
발표자는 사용자가 스트로크를 그린 후 StrokesErased를 발생시키고, 사용자가 스트로크를 지운 후 StrokesCollected를 발생시킵니다. 각 이벤트 인수의 Strokes 속성에는 영향을 받은 스트로크가 포함됩니다. 다음 코드는 TextBlock라는 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";
}
이러한 이벤트는 사용자가 잉크를 그리거나 지울 때 발생합니다. 코드에서 스트로크를 변경할 때(예: Clear, LoadAsync, 또는 AddStroke 사용) 호출 후 UI를 업데이트하세요.
잉크 저장 및 로드
InkStrokeContainer 는 잉크 ISF(직렬화된 형식)로 잉크를 저장합니다. 데이터가 저장되는 방법을 선택하려면 SaveAsync에 InkPersistenceFormat 값을 전달합니다:
| Format | 출력 |
|---|---|
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 스트림을 SaveAsync 및 LoadAsync에서 사용하는 Windows 런타임 스트림 형식으로 변환합니다. 이러한 Windows 런타임 메서드를 await하려면 파일에는 using System; 지시문이 필요합니다. 그것이 없으면 컴파일러는 오류 CS4036을 보고합니다.
사용자가 파일을 선택할 수 있도록 하려면 선택한 파일의 경로를 반환하는 Windows 앱 SDK 파일 선택기를 사용합니다. 자세한 내용은 Windows 앱 SDK 선택기를 사용하여 파일 저장 및 Windows 앱 SDK 선택기를 사용하여 파일 및 폴더 열기를 참조하세요.
잉크를 메모리에 유지하려면(예: 같은 세션에서 나중에 복원하려면) InMemoryRandomAccessStream에 저장합니다. 스트림의 GetOutputStreamAt(0) 결과를 SaveAsync에 전달하고, 그 GetInputStreamAt(0) 결과를 LoadAsync에 전달합니다. WinUI 3 갤러리 샘플에서는 이 방법을 사용합니다.
잉크를 복사하여 붙여넣기
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를 설정합니다. 자세한 내용은 알려진 제한 사항을 참조하세요. 다음 예제에서는 볼펜의 색상표를 4가지 브랜드 색으로 바꾸고 스트로크 너비를 제한하며 활성 펜의 특성을 보여 줍니다.
<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}";
}
}
방향을 변경
도구 모음을 세로로 표시하려면(예: 캔버스 옆) Vertical를 Orientation로 설정합니다:
<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 도구 모음을 기준으로 단추 플라이아웃이 열리는 위치를 설정합니다. 기본값인 를 사용하면 도구 모음이 위치를 선택하도록 합니다.
사용자 지정 토글 단추 추가
도구 모음에서 사용자가 켜고 끄는 설정을 추가하려면 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는 고유한 그리기 속성을 가진 펜을 추가합니다.CreateInkDrawingAttributesCore에서 파생되고CustomPen메서드를 재정의하는 클래스의 인스턴스로 해당InkToolbarCustomPen속성을 설정합니다.
코드에서 도구 모음 만들기
코드에서 캔버스와 도구 모음을 만들 수도 있습니다. 다음 예제에서는 inkHost(이)라는 이름의 Border에 추가하고, 도구 모음을 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 앱 SDK 실험적 릴리스에서만 사용할 수 있습니다. 안정적인 채널에서는 제공되지 않습니다.
- 잉킹 API에는 필기 또는 도형 인식이 포함되지 않습니다.
- 마우스 입력은
CoreInputDeviceTypes.Mouse를InputDeviceTypes에 추가할 때까지 잉크를 그리지 않습니다.
Windows 앱 SDK 2.4.1-experimental에서 다음과 같은 문제가 보고되었습니다. 2.5.4 실험적 릴리스 정보에는 그것들이 수정된 것으로 나열되어 있지 않습니다.
- 눈금자 및 각도기 스텐실이 작동하지 않습니다.
-
RenderTargetBitmap는 잉크를 인식하지 않습니다. 잉크의 이미지를 만들려면GifWithEmbeddedIsf형식으로 저장합니다. - XAML에서 펜 단추의
Palette속성을 설정하면 XAML 구문 분석 오류가 발생합니다. 대신 코드에서 설정합니다.Palette에 표시된 것처럼. - 단추 플라이아웃은 비어 있는 것처럼 보일 수 있으며,
ButtonFlyoutPlacement을 변경해도 열리는 위치가 바뀌지 않을 수 있습니다.
2.4.1-experimental의 변경 사항
Windows 앱 SDK 2.4.1 실험에서 수동 입력 API를 사용한 경우 2.5.4 실험에서의 이러한 변경 내용에 맞게 코드를 업데이트하세요.
| 2.4.1-실험용 API | 2.5.4-experimental의 변경 |
|---|---|
InkToolbar.TargetInkPresenter (type Object) |
속성 유형은 InkPresenter입니다. |
InkPresenter.GetHighContrastAdjustment 및 InkPresenter.SetHighContrastAdjustment 메서드 |
InkPresenter.HighContrastAdjustment 속성으로 대체되었습니다. |
InkStrokeInput.GetInkPresenter 및 InkUnprocessedInput.GetInkPresenter 메서드 |
각 클래스의 InkPresenter 속성으로 대체되었습니다. |
InkToolbarEraserButton.SelectedEraser, IsStrokeEraserVisible, 및 ArePrecisionErasersVisible 속성; InkToolbar.EraserFlyoutItemClicked 이벤트; InkToolbarEraserKind, InkToolbarEraserFlyoutItemKind및 InkToolbarEraserFlyoutItemClickedEventArgs 형식 |
제거됩니다. |
| 사용할 수 없음 |
InkPresenter.ActivateCustomDrying 메서드 및 InkSynchronizer 클래스가 추가되었습니다. |
접근성 및 고대비
잉크는 시각적 콘텐츠이므로 모든 사용자가 잉킹 기능을 사용할 수 있도록 계획하세요:
- 이 문서의 예제와
AutomationProperties.Name같이 각 캔버스와 도구 모음에 액세스 가능한 이름을 지정합니다. 아이콘만 표시하는 사용자 지정 버튼에 이름과 도구 설명을 제공하세요. -
InkCanvasAutomationPeer는 캔버스를 UI 자동화에 노출하지만 스트로크를 설명하지는 않습니다. 잉크에 서명 또는 레이블이 지정된 다이어그램과 같은 의미가 있는 경우 텍스트 대체 콘텐츠를 제공하세요. - 잉크 그리기에는 포인터 입력이 필요합니다. 작업을 완료하는 유일한 방법으로 잉크만 사용하지 마세요. 예를 들어 사용자가 서명 대신 자신의 이름을 입력하도록 합니다.
- 기본 제공 도구 단추의 자동화 피어는 SelectionItem 및 ExpandCollapse 컨트롤 패턴을 지원합니다. 앱에서 사용하는 도구 모음 구성을 사용하여 키보드 탐색 및 단추 플라이아웃을 테스트합니다.
대비 테마가 활성화된 경우 발표자는 잉크 색을 시스템 색으로 바꿀 수 있습니다. 이 동작을 선택하려면 HighContrastAdjustment 속성을 InkHighContrastAdjustment 네임스페이스의 Microsoft.UI.Xaml.Controls 값으로 설정합니다.
이름이 같은 열거형에 대한 Windows 런타임 설명서에서는 다음과 같은 방식으로 값을 설명합니다.
| Value | 대비 테마의 잉크 색 |
|---|---|
UseSystemColorsWhenNecessary |
배경과 충분히 대비되는 경우 잉크 자체의 색상 그렇지 않으면 시스템 색상입니다. |
UseSystemColors |
시스템 색 형광펜 잉크는 시스템 형광펜 색을 사용합니다. |
UseOriginalColors |
잉크 고유의 색입니다. |
inkCanvas.InkPresenter.HighContrastAdjustment = InkHighContrastAdjustment.UseSystemColorsWhenNecessary;
짧게 유지하기 위해 이 문서의 예제에서는 흰색 배경을 직접 설정합니다. 앱에서 대비 테마에 설명된 대로 활성 테마를 따르도록 테마 사전에서 캔버스 배경을 정의합니다. 그런 다음 각 대비 테마를 사용하여 앱을 테스트하고 잉크, 배경 및 테두리를 구별할 수 있는지 확인합니다.
자세한 내용은 기본 접근성 정보 노출 및 키보드 접근성을 참조하세요.
또한, 다음을 참조하세요.
Windows developer