Kontrol tinta digital untuk aplikasi WinUI

Important

InkCanvas, InkToolbar, dan InkPresenter adalah API eksperimental. Dukungan Inking ditambahkan dalam SDK Aplikasi Windows 2.4.1-eksperimental. Artikel ini menjelaskan SDK Aplikasi Windows 2.5.4-eksperimental, yang mengubah cara Anda menyambungkan InkToolbaraplikasi. API-API ini tidak tersedia di saluran stabil. Mereka mungkin berubah atau dihapus dalam rilis mendatang. Jangan gunakan di aplikasi produksi.

Gunakan kontrol eksperimental InkCanvas untuk memungkinkan pengguna menggambar tinta dengan pena, mouse, atau sentuhan. Tambahkan InkToolbar untuk memberi pengguna pena ballpoint, pensil, penyorot, dan penghapus, dengan opsi warna dan ukuran. Setiap kanvas memiliki InkPresenter yang mengumpulkan dan merender input tinta. InkStrokeContainer milik presenter menyimpan goresan, sehingga Anda dapat menyimpan, memuat, menyalin, dan membuat tinta dalam kode.

Untuk mencoba kontrol, instal paket SDK Aplikasi Windows 2.5.4-eksperimental dan runtime yang cocok. Untuk tautan unduhan dan informasi penyiapan, lihat Unduhan SDK Aplikasi Windows terbaru.

Apakah ini kontrol yang tepat?

Gunakan kanvas tinta saat pengguna perlu:

  • Buat catatan tulis tangan atau menganotasi konten.
  • Buat sketsa, gambar diagram, atau membubuhkan tanda tangan.
  • Buat konten freehand yang disimpan oleh aplikasi Anda dan dapat mereka edit nanti.

API tinta eksperimental tidak menyertakan pengenalan tulisan tangan atau pengenalan bentuk. Untuk mengumpulkan teks, gunakan kotak teks.

Jika Anda memerlukan inking di aplikasi produksi, lihat Celah kontrol yang diketahui untuk status kontrol ini di saluran stabil dan untuk alternatif.

Memahami model inking

API inking berfungsi bersama:

API Peranan
InkCanvas Elemen XAML yang menampilkan tinta dan menerima input tinta. Properti InkPresenter-nya mengembalikan presenter untuk kanvas.
InkPresenter Mengumpulkan input tinta, merender goresan, dan menyimpan pengaturan input dan atribut gambar default. Anda tidak dapat membuat InkPresenter sendiri. Dapatkan dari InkCanvas.InkPresenter.
InkStrokeContainer Menyimpan goresan. Gunakan untuk menyimpan, memuat, memilih, menyalin, dan menempelkan tinta. Dapatkan itu dari InkPresenter.StrokeContainer.
InkToolbar Toolbar opsional yang memungkinkan pengguna memilih pena, pensil, penyorot, atau penghapus, dan mengubah warna dan ukuran tinta.

InkCanvas tidak memiliki properti Background. Untuk menambahkan latar belakang dan bingkai pada area gambar, tempatkan kanvas di dalam kontainer seperti Border, dan atur ukuran pada kontainer. Warna tinta default adalah hitam, sehingga contoh dalam artikel ini menggunakan latar belakang putih.

Beberapa jenis ini memiliki nama yang sama dengan jenis dalam namespace Windows Runtime Windows.UI.Input.Inking, seperti InkPresenter, InkStrokeContainer, dan InkHighContrastAdjustment. Kontrol penintaan menggunakan versi Microsoft.UI.Xaml.Controls tersebut. Stroke dan atribut gambar masih menggunakan jenis Windows Runtime, seperti InkStroke, InkDrawingAttributes, dan InkStrokeBuilder. Jika Anda mengimpor kedua namespace dan menggunakan nama ambigu, pengompilasi C# melaporkan kesalahan CS0104.

Contoh dalam artikel ini mengimpor Microsoft.UI.Xaml.Controls dan menggunakan arahan alias untuk jenis Windows.UI.Input.Inking yang diperlukan:

using Microsoft.UI.Xaml.Controls;
using InkDrawingAttributes = Windows.UI.Input.Inking.InkDrawingAttributes;
using InkStroke = Windows.UI.Input.Inking.InkStroke;

Anda juga dapat mengimpor kedua namespace dan menggunakan nama yang sepenuhnya memenuhi syarat untuk jenis ambigu, seperti yang dilakukan sampel Galeri WinUI 3.

InkPresenter juga menyediakan API tingkat bawah yang tidak dibahas artikel ini:

  • StrokeInput memicu event ketika stroke dimulai, berlanjut, berakhir, atau dibatalkan.
  • UnprocessedInput memunculkan event pointer untuk input yang tidak diproses oleh penyaji sebagai tinta.
  • InputConfiguration mengatur apakah penyaji memproses input dari penghapus pena dan tombol barel utama.
  • InputProcessingConfiguration mengatur apakah input menggambar tinta digital, menghapus tinta, atau tidak diproses, dan bagaimana presenter menangani input seret kanan.
  • ActivateCustomDrying mengembalikan InkSynchronizer untuk aplikasi yang merender sendiri tinta yang dikomitkan.

Siapkan proyek Anda

API inking ditandai sebagai eksperimental. Saat Anda menggunakannya dalam proyek C#, pengkompilasi C# melaporkan peringatan CS8305 dan kompilator XAML melaporkan peringatan WMC1501. Jika proyek Anda memperlakukan peringatan sebagai kesalahan, peringatan ini menghentikan build.

Setelah Memutuskan untuk menggunakan API eksperimental, Anda dapat menyembunyikan peringatan ini. Tambahkan properti berikut ke file proyek C# Anda (.csproj):

<PropertyGroup>
  <NoWarn>$(NoWarn);CS8305</NoWarn>
  <MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);WMC1501</MSBuildWarningsAsMessages>
</PropertyGroup>

Contoh dalam artikel ini menggunakan C#. Setiap contoh lengkap menunjukkan XAML untuk halaman dan kelas code-behind halaman, MainPage.

Buat kanvas tinta

Ikon Galeri WinUI 3 Aplikasi Galeri WinUI 3 mencakup contoh interaktif kontrol dan fitur WinUI. Dapatkan aplikasi dari Microsoft Store atau telusuri kode sumber pada GitHub.

Contoh berikut menempatkan kanvas di dalam area putih Border. Code-behind mengatur jenis perangkat input sehingga pengguna dapat menggambar dengan pena, mouse, atau sentuhan.

<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;
    }
}

Untuk mempersingkatnya, beberapa contoh selanjutnya tidak mengulangi kode ini. Atur InputDeviceTypes di setiap halaman tempat pengguna menggambar dengan mouse.

Hubungkan toolbar tinta

Tempatkan InkToolbar di dekat kanvas, dan atur properti TargetInkCanvas-nya untuk menghubungkan keduanya:

<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>

Dimulai dengan SDK Aplikasi Windows 2.5.4-eksperimental, Anda dapat menyambungkan toolbar langsung ke InkPresenter dengan mengatur properti TargetInkPresenter. Atur dalam kode:

inkToolbar.TargetInkPresenter = inkCanvas.InkPresenter;

Anda juga dapat mengaturnya di XAML: TargetInkPresenter="{x:Bind inkCanvas.InkPresenter}". Atur hanya satu dari dua properti target.

Secara default, toolbar menampilkan tombol pena, pensil, penyorot, penghapus, dan tombol stensil (penggaris). Untuk mengubah tombol mana yang muncul, lihat Menyesuaikan toolbar tinta.

Pilih perangkat input

Properti InputDeviceTypes menggunakan CoreInputDeviceTypes enumerasi bendera dari Windows.UI.Core namespace. Input dari mouse tidak membuat goresan tinta secara bawaan. Gabungkan nilai untuk memilih perangkat mana yang menggunakan tinta:

// Add mouse input and keep the other device types.
inkCanvas.InkPresenter.InputDeviceTypes |= CoreInputDeviceTypes.Mouse;

// Stop accepting ink from touch input.
inkCanvas.InkPresenter.InputDeviceTypes &= ~CoreInputDeviceTypes.Touch;

// Check whether pen input is enabled.
bool isPenEnabled = inkCanvas.InkPresenter.InputDeviceTypes.HasFlag(CoreInputDeviceTypes.Pen);

Untuk berhenti mengumpulkan tinta dari setiap perangkat, misalnya untuk menampilkan gambar yang tidak dapat diubah pengguna, atur IsInputEnabled ke false:

inkCanvas.InkPresenter.IsInputEnabled = false;

Untuk memungkinkan pengguna mengaktifkan atau menonaktifkan perangkat input dari toolbar, lihat Menambahkan tombol pengalih kustom.

Atur atribut objek gambar

Atribut gambar, seperti warna, ukuran, dan bentuk ujung pena, mengontrol tampilan goresan. Mereka menggunakan kelas Windows RuntimeInkDrawingAttributes. Untuk mengubah atribut untuk goresan baru, panggil CopyDefaultDrawingAttributes, ubah salinan, dan teruskan ke UpdateDefaultDrawingAttributes:

InkDrawingAttributes attributes = inkCanvas.InkPresenter.CopyDefaultDrawingAttributes();
attributes.Color = Colors.DarkBlue;
attributes.Size = new Size(4, 4);
inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(attributes);

Colors berada di Microsoft.UI namespace layanan, dan Size berada di Windows.Foundation namespace layanan. Atribut baru berlaku untuk goresan yang digambar pengguna setelah panggilan berakhir. Coretan yang ada mempertahankan atributnya.

Saat Anda menghubungkan InkToolbar, alat aktif dan pengaturan warna dan ukurannya menentukan atribut gambar. Untuk membaca atribut saat ini, gunakan properti InkDrawingAttributes milik toolbar.

Buat preset pena

Jika Anda tidak menggunakan toolbar, Anda dapat membuat preset pena Anda sendiri. Contoh berikut menerapkan atribut ballpoint, pensil, highlighter, atau kaligrafi saat pengguna memilih preset atau mengubah ukuran.

<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);
    }
}

Opsi prasetel pena untuk Ballpoint, Pensil, Highlighter, dan Kaligrafi, dengan Kaligrafi dipilih, di atas slider Ukuran dan kanvas putih. Kanvas menunjukkan goresan berulang dalam tinta ballpoint hitam, tinta pensil abu-abu, tinta penyorot kuning, dan tinta kaligrafi biru tua.

  • InkDrawingAttributes.CreateForPencil mengembalikan atribut untuk goresan pensil. Properti Kind bersifat baca-saja, jadi gunakan metode ini untuk membuat atribut pensil.
  • DrawAsHighlighter merender stroke sebagai overlay penyorot.
  • PenTip mengatur bentuk ujung pena, dan PenTipTransform memutar atau menskalakannya. Tip persegi panjang yang diputar menghasilkan goresan gaya kaligrafi.

Merespons perubahan tinta

Penyaji ditampilkan StrokesCollected setelah pengguna menarik goresan dan StrokesErased setelah pengguna menghapusnya. Properti Strokes pada argumen setiap peristiwa berisi goresan yang terpengaruh. Kode berikut terus memperbarui jumlah stroke dalam TextBlock bernama 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";
}

Peristiwa ini terjadi ketika pengguna menggambar atau menghapus coretan tinta. Saat Anda mengubah stroke dalam kode, misalnya dengan Clear, LoadAsync, atau AddStroke, perbarui antarmuka pengguna Anda setelah panggilan.

Menyimpan dan memuat tinta

InkStrokeContainer menyimpan tinta dalam Ink Serialized Format (ISF). Untuk memilih bagaimana data disimpan, teruskan InkPersistenceFormat nilai ke SaveAsync:

Format Output
GifWithEmbeddedIsf Gambar GIF yang berisi data tinta. Penampil gambar dapat menampilkan file, dan aplikasi Anda dapat memuat ink lagi untuk pengeditan.
Isf Hanya data tinta, tanpa gambar. Ini adalah format yang paling ringkas.

Tambahkan metode berikut ke code-behind halaman yang berisi sebuah InkCanvas bernama inkCanvas. SaveInkAsync menulis tinta ke file, dan LoadInkAsync mengganti tinta pada kanvas dengan tinta dari file. LoadAsync menghapus goresan yang ada sebelum menambahkan goresan yang dimuat, sehingga Anda tidak perlu memanggil Clear terlebih dahulu.

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 dan AsInputStream mengonversi aliran .NET ke jenis aliran Windows Runtime yang digunakan oleh SaveAsync dan LoadAsync. Untuk menggunakan await pada metode Windows Runtime ini, file memerlukan arahan using System;. Tanpa itu, kompilator melaporkan kesalahan CS4036.

Untuk mengizinkan pengguna memilih file, gunakan pemilih file SDK Aplikasi Windows, yang mengembalikan jalur file yang dipilih. Untuk informasi selengkapnya, lihat Menyimpan file dengan pemilih SDK Aplikasi Windows dan Membuka file dan folder dengan pemilih SDK Aplikasi Windows.

Untuk menyimpan tinta dalam memori, misalnya untuk memulihkannya nanti dalam sesi yang sama, simpan ke dalam InMemoryRandomAccessStream. Teruskan hasil GetOutputStreamAt(0) pada aliran ke SaveAsync, dan teruskan hasil GetInputStreamAt(0) pada aliran tersebut ke LoadAsync. Sampel Galeri WinUI 3 menggunakan pendekatan ini.

Menyalin dan menempelkan tinta digital

InkStrokeContainer dapat menyalin goresan ke clipboard dan menempelkan tinta darinya. CopySelectedToClipboard menyalin hanya goresan yang dipilih, sehingga contoh berikut memilih setiap goresan di kanvas kiri sebelum menyalinnya. CanPasteFromClipboard memeriksa apakah papan klip berisi goresan tinta digital. PasteFromClipboard menambahkan tinta pada posisi yang Anda berikan, dan mengembalikan persegi panjang pembatas area yang berubah.

<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.";
    }
}

Tombol

Untuk bekerja dengan beberapa goresan, atur properti Selected dari setiap goresan yang Anda inginkan, atau panggil SelectWithLine atau SelectWithPolyLine. Untuk menghapus atau memindahkan goresan yang dipilih, gunakan DeleteSelected atau MoveSelected.

Membuat stroke dalam kode

Gunakan kelas Windows Runtime InkStrokeBuilder untuk membuat goresan dari titik, dan menambahkannya ke kanvas dengan AddStroke. Contoh berikut menambahkan bintang dan baris tanda tangan.

<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);
    }
}

tombol Tambahkan bintang, Tambahkan garis tanda tangan, dan Hapus di atas kanvas putih yang menunjukkan bintang emas berujung lima dan garis tanda tangan abu-abu dengan X di ujung kirinya.

  • CreateStrokeFromInkPoints membuat stroke dari nilai InkPoint. Setiap titik menggabungkan posisi pada kanvas dengan nilai tekanan.
  • Dokumentasi Windows Runtime merekomendasikan CreateStrokeFromInkPoints alih-alih CreateStroke untuk goresan yang Anda tambahkan ke penyaji tinta.
  • Ketika FitToCurve adalah false, garis menggunakan segmen lurus antara titik alih-alih kurva.

Stroke hanya dapat dimiliki oleh satu wadah. Untuk menyalin goresan ke kanvas lain, tambahkan klon goresan:

private void CopyStrokesTo(InkCanvas target)
{
    foreach (InkStroke stroke in inkCanvas.InkPresenter.StrokeContainer.GetStrokes())
    {
        target.InkPresenter.StrokeContainer.AddStroke(stroke.Clone());
    }
}

Sesuaikan toolbar tinta

Pilih kontrol awal

Properti InitialControls menentukan tombol bawaan mana yang dibuat toolbar:

Nilai Tombol bawaan
All (standar) Pena ballpoint, pensil, penyorot, penghapus, dan mal (penggaris)
PensOnly Pulpen, pensil, dan stabilo
AllExceptPens Penghapus dan stensil (penggaris)
None None. Deklarasikan tombol yang Anda inginkan sebagai turunan toolbar.

Empat toolbar tinta, masing-masing di atas kanvas dengan sampel goresan. Bilah alat berlabel InitialControls All (default) menunjukkan pena ballpoint, pensil, penyorot, penghapus, dan tombol penggaris. PensOnly menunjukkan tiga tombol pena. AllExceptPens menunjukkan tombol penghapus dan penggaris. None plus a pencil hanya menunjukkan tombol pensil, dan kanvasnya menunjukkan goresan pensil abu-abu.

Contoh berikut mendeklarasikan empat tombol yang sama dengan sampel Galeri WinUI 3, memberi masing-masing satu nama yang dapat diakses, dan memilih pena ballpoint saat toolbar dimuat:

<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;
            }
        };
    }
}

Untuk menampilkan perintah yang menghapus semua tinta, atur properti tombol penghapus IsClearAllVisible ke true. Toolbar memicu event EraseAllClicked saat pengguna memilih perintah.

Mengkustomisasi warna dan lebar-lebar pena

Setiap tombol pena memiliki Palette sekumpulan pengaturan kuas dan lebar goresan. Atur Palette dalam kode. Untuk detailnya, lihat Batasan yang diketahui. Contoh berikut mengganti palet pena ballpoint dengan empat warna merek, membatasi lebar goresan, dan menunjukkan atribut pena aktif.

<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})";
        }
    }
}

Toolbar tinta dengan tombol pena, penghapus, dan penggaris di atas opsi warna pena Biru, Hijau, Oranye, dan Ungu. Teks berbunyi Warna #FF0078D4, lebar 6 (rentang 2-16), dan kanvas menunjukkan goresan yang diulang dalam warna biru, hijau, oranye, dan ungu.

SelectedBrushIndex digunakan untuk memilih warna dari Palette. SelectedStrokeWidth, MinStrokeWidth, dan MaxStrokeWidth atur lebar goresan saat ini dan rentang yang dapat dipilih pengguna. Toolbar memicu InkDrawingAttributesChanged ketika atribut alat aktif berubah.

Pilih alat aktif

Atur ActiveTool untuk memilih alat, dan penangan ActiveToolChanged untuk merespons saat pengguna memilih alat lain. GetToolButton mengembalikan tombol alat bawaan dari jenis yang Anda berikan. Tombol bawaan mungkin tidak ada sampai toolbar dimuat, jadi pilih alat awal di penangan peristiwa toolbar 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}";
    }
}

Bilah alat tinta dengan tombol pensil dipilih. Teks di bawahnya berbunyi Pensil: Tinta pensil, warna #FF000000, ukuran 4, dan kanvas menunjukkan goresan pensil abu-abu.

Ubah orientasi

Untuk menampilkan toolbar secara vertikal, misalnya di samping kanvas, atur Orientation ke 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>

Toolbar tinta vertikal dengan pena ballpoint, pensil, penyorot, penghapus, dan tombol penggaris di sebelah kiri kanvas dengan goresan tinta hitam.

Properti ButtonFlyoutPlacement mengatur tempat flyout tombol terbuka relatif terhadap toolbar. Nilai default, Auto, memungkinkan toolbar memilih posisi.

Tambahkan tombol toggle kustom

Gunakan InkToolbarCustomToggleButton untuk menambahkan pengaturan yang diaktifkan dan dinonaktifkan pengguna dari toolbar. Contoh berikut menambahkan sakelar yang mengontrol input sentuh. Input pena dan mouse selalu menghasilkan coretan tinta.

<StackPanel Spacing="12">
    <InkToolbar AutomationProperties.Name="Drawing tools" TargetInkCanvas="{x:Bind inkCanvas}">
        <InkToolbarCustomToggleButton
            x:Name="touchWritingToggle"
            AutomationProperties.Name="Touch writing"
            Checked="TouchWritingToggle_Changed"
            ToolTipService.ToolTip="Touch writing"
            Unchecked="TouchWritingToggle_Changed">
            <SymbolIcon Symbol="TouchPointer" />
        </InkToolbarCustomToggleButton>
    </InkToolbar>
    <TextBlock x:Name="deviceInfo" />
    <Border
        Width="480"
        Height="280"
        HorizontalAlignment="Left"
        Background="White"
        BorderBrush="{ThemeResource SystemControlForegroundChromeHighBrush}"
        BorderThickness="1">
        <InkCanvas x:Name="inkCanvas" AutomationProperties.Name="Drawing surface" />
    </Border>
</StackPanel>
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.UI.Core;

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        InitializeComponent();

        // Pen and mouse input always draw ink. The custom toggle controls touch input.
        inkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Mouse;
        ShowDevices();
    }

    private void TouchWritingToggle_Changed(object sender, RoutedEventArgs e)
    {
        if (touchWritingToggle.IsChecked == true)
        {
            inkCanvas.InkPresenter.InputDeviceTypes |= CoreInputDeviceTypes.Touch;
        }
        else
        {
            inkCanvas.InkPresenter.InputDeviceTypes &= ~CoreInputDeviceTypes.Touch;
        }

        ShowDevices();
    }

    private void ShowDevices()
    {
        deviceInfo.Text = $"InputDeviceTypes = {inkCanvas.InkPresenter.InputDeviceTypes}";
    }
}

Toolbar tinta dengan tombol pena bolpoin, pensil, penyorot, penghapus, dan penggaris diikuti dengan tombol pengalih kustom dengan ikon sentuh, yang diaktifkan. Teks membaca InputDeviceTypes = Touch, Pen, Mouse, dan kanvas menunjukkan goresan tinta hitam.

Sakelar hanya menampilkan ikon, sehingga contohnya memberinya nama yang dapat diakses dan tooltip. Dalam contoh ini, toolbar juga membuat tombol defaultnya, dan toggle kustom muncul setelah tombol tersebut.

Toolbar mendukung tombol kustom lainnya:

Buat toolbar dalam kode

Anda juga dapat membuat kanvas dan toolbar dalam kode. Contoh berikut menambahkannya ke Border yang bernama inkHost, menyambungkan toolbar dengan TargetInkPresenter, dan memilih pensil saat toolbar dimuat.

<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;
    }
}

Toolbar tinta dengan tombol pensil yang dipilih di atas kanvas dengan goresan pensil abu-abu.

Batasan yang diketahui

  • Kontrol tinta digital hanya tersedia dalam rilis eksperimental SDK Aplikasi Windows. Mereka tidak tersedia di saluran stabil.
  • API inking tidak menyertakan pengenalan tulisan tangan digital atau bentuk.
  • Input mouse tidak menggambar tinta hingga Anda menambahkan CoreInputDeviceTypes.Mouse ke InputDeviceTypes.

Masalah berikut dilaporkan dengan SDK Aplikasi Windows 2.4.1-eksperimental. Catatan rilis eksperimental 2.5.4 tidak mencantumkannya sebagai diperbaiki:

  • Stensil penggaris dan protraktor tidak berfungsi.
  • RenderTargetBitmap tidak mendeteksi input tinta. Untuk membuat gambar dari tinta, simpan dalam format GifWithEmbeddedIsf.
  • Mengatur properti Palette tombol pena di XAML menyebabkan kesalahan penguraian XAML. Atur Palette dalam kode sebagai gantinya, seperti yang ditunjukkan dalam Menyesuaikan warna dan lebar pena.
  • Flyout tombol dapat tampak kosong, dan mengubah ButtonFlyoutPlacement mungkin tidak mengubah lokasi flyout terbuka.

Perubahan dari 2.4.1-eksperimental

Jika Anda menggunakan API penintaan di SDK Aplikasi Windows 2.4.1-eksperimental, perbarui kode Anda untuk perubahan ini dalam 2.5.4-eksperimental:

API di 2.4.1-experimental Perubahan pada 2.5.4-experimental
InkToolbar.TargetInkPresenter (jenis Object) Jenis properti adalah InkPresenter.
metode InkPresenter.GetHighContrastAdjustment dan InkPresenter.SetHighContrastAdjustment Digantikan oleh properti InkPresenter.HighContrastAdjustment.
metode InkStrokeInput.GetInkPresenter dan InkUnprocessedInput.GetInkPresenter Digantikan oleh InkPresenter properti pada setiap kelas.
InkToolbarEraserButton.SelectedEraser, IsStrokeEraserVisible, dan ArePrecisionErasersVisible properti; InkToolbar.EraserFlyoutItemClicked event; InkToolbarEraserKind, InkToolbarEraserFlyoutItemKind, dan InkToolbarEraserFlyoutItemClickedEventArgs tipe Dihapus.
Tidak tersedia InkPresenter.ActivateCustomDrying metode dan InkSynchronizer kelas ditambahkan.

Aksesibilitas dan kontras tinggi

Tinta adalah konten visual, jadi rencanakan bagaimana semua pengguna dapat menggunakan fitur penintaan Anda:

  • Beri setiap kanvas dan toolbar nama yang dapat diakses dengan AutomationProperties.Name, seperti contoh dalam artikel ini. Beri tombol kustom yang hanya menampilkan ikon nama dan tooltip.
  • InkCanvasAutomationPeermengekspos kanvas ke UI Automation, tetapi tidak menggambarkan goresan. Jika tinta memiliki arti, seperti tanda tangan atau diagram berlabel, berikan alternatif teks.
  • Tinta gambar memerlukan input dari perangkat penunjuk. Jangan jadikan tinta sebagai satu-satunya cara untuk menyelesaikan tugas. Misalnya, biarkan pengguna mengetikkan namanya sebagai alternatif untuk menandatangani.
  • Peer otomatisasi untuk tombol alat bawaan mendukung pola kontrol SelectionItem dan ExpandCollapse. Uji navigasi keyboard dan flyout tombol dengan konfigurasi toolbar yang digunakan aplikasi Anda.

Ketika tema kontras aktif, penyaji dapat mengganti warna tinta dengan warna sistem. Untuk memilih perilaku ini, atur properti HighContrastAdjustment ke nilai InkHighContrastAdjustment dari namespace Microsoft.UI.Xaml.Controls. Dokumentasi Windows Runtime untuk enumerasi dengan nama yang sama menjelaskan nilai dengan cara ini:

Nilai Warna tinta dalam tema kontras
UseSystemColorsWhenNecessary Warna tinta itu sendiri digunakan jika cukup kontras dengan latar belakang. Jika tidak, warna sistem.
UseSystemColors Warna sistem. Tinta penyorot menggunakan warna penyorot sistem.
UseOriginalColors Warna tintanya sendiri.
inkCanvas.InkPresenter.HighContrastAdjustment = InkHighContrastAdjustment.UseSystemColorsWhenNecessary;

Agar tetap pendek, contoh dalam artikel ini mengatur latar belakang putih secara langsung. Di aplikasi Anda, tentukan latar belakang kanvas dalam kamus tema sehingga mengikuti tema aktif, seperti yang dijelaskan dalam tema Kontras. Kemudian uji aplikasi Anda dengan setiap tema kontras, dan periksa apakah tinta, latar belakang, dan batas tetap dapat dibedakan.

Untuk informasi selengkapnya, lihat Mengekspos informasi aksesibilitas dasar dan Aksesibilitas keyboard.

Baca juga