Kontroller för penninmatning för WinUI-appar

Important

InkCanvas, InkToolbaroch InkPresenter är experimentella API:er. Pennanteckningsstöd lades till i Windows App SDK 2.4.1-experimental. Den här artikeln beskriver Windows App SDK 2.5.4-experimental, som ändrade hur du ansluter till en InkToolbar. Dessa API:er är inte tillgängliga i den stabila kanalen. De kan ändras eller tas bort i en framtida version. Använd dem inte i produktionsappar.

Använd den experimentella InkCanvas kontrollen för att låta användarna rita med bläck med penna, mus eller touch. Lägg till en InkToolbar för att ge användarna en kulspetspenna, penna, överstrykningspenna och suddgummi, med färg- och storleksalternativ. Varje arbetsyta har en InkPresenter som samlar in och återger bläckindata. InkStrokeContainer i presentatören lagrar linjerna så att du kan spara, läsa in, kopiera och skapa pennanteckningar i kod.

Om du vill prova kontrollerna installerar du det experimentella Windows App SDK 2.5.4-paketet och motsvarande körmiljö. För nedladdningslänkar och installationsinformation, se Senaste Windows App SDK-nedladdningar.

Är det här rätt kontroll?

Använd en arbetsyta för penninmatning när användarna behöver:

  • Gör handskrivna anteckningar eller annotera innehåll.
  • Skissa, rita diagram eller signera ditt namn.
  • Skapa frihandsinnehåll som din app sparar och låter användarna redigera senare.

API:erna för experimentell penninmatning innehåller inte handskrift eller formigenkänning. Om du vill samla in text använder du en textruta.

Om du behöver bläckinmatning i en produktionsapp kan du läsa Kända kontrollluckor för status för dessa kontroller i den stabila kanalen och för alternativ.

Förstå inkningsmodellen

API:erna för inkning fungerar tillsammans:

API Befattning
InkCanvas XAML-elementet som visar bläck och tar emot bläckindata. Dess InkPresenter egenskap returnerar presenter för ritytan.
InkPresenter Samlar in penninmatning, återger streck och innehåller indatainställningarna och standardritningsattributen. Du kan inte skapa en InkPresenter själv. Hämta den från InkCanvas.InkPresenter.
InkStrokeContainer Lagrar streck. Använd den för att spara, läsa in, välja, kopiera och klistra in bläck. Hämta den från InkPresenter.StrokeContainer.
InkToolbar Ett valfritt verktygsfält där användarna kan välja penna, blyertspenna, överstrykningspenna eller raderare och ändra bläckfärg och storlek på pennan.

InkCanvas har ingen Background egenskap. Om du vill ge ritningsområdet en bakgrund och en kantlinje placerar du arbetsytan i en behållare, till exempel en Border, och anger storleken på behållaren. Standardbläckfärgen är svart, så exemplen i den här artikeln använder en vit bakgrund.

Flera av dessa typer har samma namn som typer i namnrymden Windows Runtime Windows.UI.Input.Inking, till exempel InkPresenter, InkStrokeContainer och InkHighContrastAdjustment. Pennanteckningskontrollerna använder Microsoft.UI.Xaml.Controls-versionerna. Penseldrag och ritattribut använder fortfarande Windows Runtime-typer, till exempel InkStroke, InkDrawingAttributes och InkStrokeBuilder. Om du importerar båda namnrymderna och använder ett tvetydigt namn rapporterar C#-kompilatorn felet CS0104.

Exemplen i den här artikeln importerar Microsoft.UI.Xaml.Controls och använder aliasdirektiv för de Windows.UI.Input.Inking typer som de behöver:

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

Du kan också importera båda namnrymderna och använda fullständigt kvalificerade namn för de tvetydiga typerna, som WinUI 3-galleriexemplet gör.

InkPresenter innehåller också API:er på lägre nivå som den här artikeln inte omfattar:

  • StrokeInput genererar händelser när en stroke startar, fortsätter, slutar eller avbryts.
  • UnprocessedInput genererar pekarhändelser för indata som presentatören inte bearbetar som pennanteckning.
  • InputConfiguration anger om presentatörenheten bearbetar indata från en pennas suddgummi och primära sidoknapp.
  • InputProcessingConfiguration anger om indata ritar pennanteckning, raderar pennanteckningar eller inte bearbetas och hur presentatorkomponenten hanterar högerdrag.
  • ActivateCustomDrying returnerar en InkSynchronizer för appar som själva återger bekräftat bläck.

Konfigurera projektet

API:erna för bläck är markerade som experimentella. När du använder dem i ett C#-projekt rapporterar C#-kompilatorn varning CS8305 och XAML-kompilatorn rapporterar varning WMC1501. Om ditt projekt behandlar varningar som fel stoppar dessa varningar bygget.

När du har bestämt dig för att använda de experimentella API:erna kan du ignorera dessa varningar. Lägg till följande egenskaper i C#-projektfilen (.csproj):

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

Exemplen i den här artikeln är skrivna i C#. Varje fullständigt exempel visar XAML för en sida och sidans code-behind-klass, MainPage.

Skapa en bläckarbetsyta

WinUI 3-galleriikon WinUI 3-galleriappen innehåller interaktiva exempel på WinUI-kontroller och funktioner. Hämta appen från Microsoft Store eller bläddra i källkoden på GitHub.

I följande exempel placeras en canvas inuti en vit Border. Code-behind anger enhetstyperna för indata så att användarna kan rita med en penna, mus eller touch.

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

För att hålla dem korta upprepar vissa senare exempel inte den här koden. Ange InputDeviceTypes på varje sida där användare ritar med en mus.

Anslut ett ritverktygsfält

Placera en InkToolbar nära arbetsytan och ställ in dess TargetInkCanvas egenskap för att ansluta de två:

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

Från och med Windows App SDK 2.5.4-experimentell kan du i stället ansluta ett verktygsfält direkt till ett InkPresenter genom att ange egenskapen TargetInkPresenter. Ställ in det i kod:

inkToolbar.TargetInkPresenter = inkCanvas.InkPresenter;

Du kan också ange den i XAML: TargetInkPresenter="{x:Bind inkCanvas.InkPresenter}". Ange endast en av de två egenskaperna för målet.

Som standard visar verktygsfältet knapparna kulspetspenna, blyertspenna, överstrykningspenna, radergummi och stencil (linjal). Om du vill ändra vilka knappar som visas, se Anpassa verktygsfältet för pennanteckningar.

Välj indataenheter

Egenskapen InputDeviceTypes använder uppräkning av CoreInputDeviceTypes flaggor från Windows.UI.Core namnområdet. Musinmatning ritar inte bläck som standard. Kombinera värden för att välja vilka enheter som ska rita med bläck:

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

Om du vill sluta samla in digitalt bläck från varje enhet, till exempel för att visa en ritning som användarna inte kan ändra, ställer du in IsInputEnabled på false:

inkCanvas.InkPresenter.IsInputEnabled = false;

Information om hur du låter användare aktivera eller inaktivera en indataenhet från verktygsfältet finns i Lägg till en anpassad växlingsknapp.

Ange ritattribut

Ritattribut, till exempel färg, storlek och pennspetsform, styr hur linjer ser ut. De använder klassen Windows RuntimeInkDrawingAttributes. Om du vill ändra attributen för nya linjer, anropa CopyDefaultDrawingAttributes, ändra kopian och skicka den till UpdateDefaultDrawingAttributes:

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

Colors finns i Microsoft.UI namnområdet och Size finns i Windows.Foundation namnområdet. De nya attributen gäller för streck som användarna ritar efter funktionsanropet. Befintliga linjer behåller sina attribut.

När du ansluter ett InkToolbar avgör det aktiva verktyget och dess färg- och storleksinställningar ritattributen. Om du vill läsa de aktuella attributen använder du verktygsfältets InkDrawingAttributes egenskap.

Skapa pennförinställningar

Om du inte använder ett verktygsfält kan du skapa egna pennförinställningar. I följande exempel används attribut för kulspetspenna, blyertspenna, highlighter eller kalligrafi när användaren väljer en förinställning eller ändrar storleken.

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

Pennförinställningsalternativ för Kulspets, Penna, Överstrykningspenna och Kalligrafi, med Kalligrafi valt, ovanför ett skjutreglage för storlek och en vit arbetsyta. Arbetsytan visar öglade pennstreck i svart kulspetsbläck, grått pennstreck, gult överstrykningsbläck och mörkblått kalligrafibläck.

  • InkDrawingAttributes.CreateForPencil returnerar attribut för pennstreck. Egenskapen Kind är skrivskyddad, så använd den här metoden för att skapa blyertsattribut.
  • DrawAsHighlighter renderar strecket som ett markeringsöverlägg.
  • PenTip bestämmer pennspetsens form och PenTipTransform roterar eller skalar den. En roterad rektangulär penna ger linjer i kalligrafistil.

Svara på bläckändringar

Presentatören visar StrokesCollected efter att en användare ritat linjer och StrokesErased när en användare har raderat dem. Egenskapen Strokes för varje händelses argument innehåller de berörda pennstrecken. Följande kod håller antalet streck uppdaterat i ett TextBlock namngivet 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";
}

Dessa händelser inträffar när användare ritar eller raderar bläck. När du ändrar streck i koden, till exempel med Clear, LoadAsync eller AddStroke, uppdaterar du användargränssnittet efter anropet.

Spara och läs in bläck

InkStrokeContainer sparar digitalt bläck i Ink Serialized Format (ISF). Om du vill välja hur data lagras skickar du ett InkPersistenceFormat värde till SaveAsync:

Format Output
GifWithEmbeddedIsf En GIF-bild som innehåller bläckdata. Bildvisningsprogram kan visa filen och appen kan läsa in pennanteckningen igen för redigering.
Isf Endast bläckdata, utan en bild. Det här är det mest kompakta formatet.

Lägg till följande metoder i koden bakom på en sida som innehåller en InkCanvas med namnet inkCanvas. SaveInkAsync skriver pennanteckningen till en fil och LoadInkAsync ersätter pennanteckningen på arbetsytan med pennanteckningen från en fil. LoadAsync tar bort befintliga streck innan de inlästa strecken läggs till, så du behöver inte anropa Clear först.

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 och AsInputStream konverterar .NET-strömmar till de Windows Runtime-strömtyper som SaveAsync och LoadAsync använder. För att invänta dessa Windows Runtime-metoder behöver filen direktivet using System;. Utan den rapporterar kompilatorn felet CS4036.

Om du vill låta användarna välja en fil använder du Windows App SDK filväljare, som returnerar sökvägen till den valda filen. Mer information finns i Spara en fil med en filväljare i Windows App SDK och Öppna filer och mappar med filväljare i Windows App SDK.

Om du vill behålla bläck i minnet, till exempel för att återställa det senare i samma session, sparar du det i en InMemoryRandomAccessStream. Skicka strömmens GetOutputStreamAt(0)-resultat till SaveAsync och skicka dess GetInputStreamAt(0)-resultat till LoadAsync. WinUI 3-galleriexemplet använder den här metoden.

Kopiera och klistra in bläck

InkStrokeContainer kan kopiera penndrag till Urklipp och klistra in pennanteckningar från det. CopySelectedToClipboard kopierar endast markerade penseldrag, så i följande exempel markeras varje penseldrag på den vänstra arbetsytan innan de kopieras. CanPasteFromClipboard kontrollerar om urklipp innehåller bläck. PasteFromClipboard lägger till bläcket vid den position som du skickar och returnerar avgränsningsrektangeln för området som ändrades.

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

Knapparna Kopiera vänster arbetsyta och Klistra in i höger arbetsyta ovanför två vita arbetsytor som visar samma loopade, spiral- och vågiga linjer. Texten nedan lyder Inklistrade pennanteckningar i ett område på 255 x 69.

Om du vill arbeta med några av strecken anger du egenskapen Selected för varje streck som du vill använda, eller anropar SelectWithLine eller SelectWithPolyLine. Om du vill ta bort eller flytta de markerade linjerna använder du DeleteSelected eller MoveSelected.

Skapa streck i kod

Använd klassen Windows Runtime InkStrokeBuilder för att skapa streck från punkter och lägga till dem på arbetsytan med AddStroke. I följande exempel läggs en stjärna och en signaturrad till.

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

Lägg till stjärna-, Lägg till signaturrad- och Rensa-knappar ovanför en vit arbetsyta som visar en femuddig guldstjärna och en grå signaturlinje med ett X i vänster ände.

  • CreateStrokeFromInkPoints skapar en linje från InkPoint värden. Varje punkt kombinerar en position på arbetsytan med ett tryckvärde.
  • Dokumentationen för Windows Runtime rekommenderar CreateStrokeFromInkPoints i stället för CreateStroke för pennstreck som du lägger till i en pennanteckningspresentatör.
  • När FitToCurve är falseanvänder stroken raka segment mellan punkter i stället för kurvor.

En kontur kan bara tillhöra en container. Om du vill kopiera streck till en annan arbetsyta lägger du till kloner av strecken:

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

Anpassa verktygsfältet för bläck

Välj de ursprungliga kontrollerna

Egenskapen InitialControls avgör vilka inbyggda knappar som verktygsfältet skapar:

Value Inbyggda knappar
All (standardinställning) Kulspetspenna, blyertspenna, överstrykningspenna, radergummi och mall (linjal)
PensOnly Kulspetspenna, blyertspenna och överstrykningspenna
AllExceptPens Radergummi och schablon (linjal)
None None. Deklarera de knappar som du vill ha som underordnade till verktygsfältet.

Fyra verktygsfält för bläck, var och en ovanför ett ritområde med exempelstreck. Verktygsfältet med etiketten InitialControls All (standard) visar knappar för kulspetspenna, blyertspenna, överstrykningspenna, suddgummi och linjal. PensOnly visar de tre pennknapparna. AllExceptPens visar knapparna suddgummi och linjal. None plus en blyertspenna visar bara en knapp för blyertspenna, och dess ritområde visar grå blyertsstreck.

I följande exempel deklarerar samma fyra knappar som WinUI 3-galleriexemplet, ger var och en ett tillgängligt namn och väljer kulspetspennan när verktygsfältet läses in:

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

Om du vill visa ett kommando som raderar alla pennanteckningar anger du egenskapen för suddknappen IsClearAllVisible till true. Verktygsfältet genererar EraseAllClicked händelsen när användaren väljer kommandot.

Anpassa en pennas färger och bredder

Varje pennknapp har en Palette penslar och inställningar för linjebredd. Ange Palette i kod. Mer information finns i Kända begränsningar. I följande exempel ersätts en kulspetspennas palett med fyra varumärkesfärger, linjebredden begränsas och attributen för den aktiva pennan visas.

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

Ett pennverktygsfält med kulspetspenna, radergummi och linjalknappar ovanför färgalternativen Blå, Grön, Orange och Lila. Texten visar Färg #FF0078D4, bredd 6 (intervall 2-16) och arbetsytan visar loopade linjer i blått, grönt, orange och lila.

Välj en färg från Palette. SelectedStrokeWidth, MinStrokeWidthoch MaxStrokeWidth ange den aktuella linjebredden och det intervall som användarna kan välja mellan. Verktygsfältet utlöser InkDrawingAttributesChanged när attributen för det aktiva verktyget ändras.

Välj det aktiva verktyget

Ställ in ActiveTool för att välja ett verktyg och hantera ActiveToolChanged för att svara när användaren väljer ett annat. GetToolButton returnerar en inbyggd verktygsknapp av den typ som du anger. De inbyggda knapparna kanske inte finns förrän verktygsfältet har lästs in, så välj ett startverktyg i verktygsfältets Loaded händelsehanterare.

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

Ett pennverktygsfält med pennknappen markerad. Text under den läser Penna: Pennbläck, färg #FF000000, storlek 4 och ritytan visar grå pennstreck.

Ändra orienteringen

Om du vill visa verktygsfältet lodrätt, till exempel bredvid arbetsytan, ställer du in Orientation på 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>

Ett lodrätt bläckverktygsfält med kulspetspenna, blyertspenna, överstrykningspenna, radergummi och linjalknappar till vänster om en arbetsyta med svarta pennstreck.

Egenskapen ButtonFlyoutPlacement anger var knapputfällningar öppnas i förhållande till verktygsfältet. Standardvärdet, Auto, gör att verktygsfältet kan välja position.

Lägg till en anpassningsbar växlare

Använd en InkToolbarCustomToggleButton för att lägga till en inställning som användarna aktiverar och inaktiverar från verktygsfältet. I följande exempel läggs en växlingsknapp till som styr pekindata. Inmatning med penna och mus ritar alltid med bläck.

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

Ett verktygsfält med kulspetspenna, penna, överstrykningspenna, radergummi och linjalknappar följt av en anpassad växlingsknapp med en pekikon, som är aktiverad. Texten visar InputDeviceTypes = Touch, Pen, Mouse och arbetsytan visar svarta pennstreck.

Växlingsknappen visar bara en ikon, så exemplet ger den ett tillgängligt namn och en knappbeskrivning. I det här exemplet skapar verktygsfältet också sina standardknappar och den anpassade växlingsknappen visas efter dem.

Verktygsfältet stöder andra anpassade knappar:

Skapa verktygsfältet i kod

Du kan också skapa arbetsytan och verktygsfältet i koden. I följande exempel läggs de till i ett Border med namnet inkHost, verktygsfältet ansluts med TargetInkPresenter och pennan väljs när verktygsfältet läses in.

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

Ett pennverktygsfält med pennknappen markerad ovanför en arbetsyta med grå pennstreck.

Kända begränsningar

  • Pennanteckningskontrollerna är endast tillgängliga i experimentella versioner av Windows App SDK. De är inte tillgängliga i den stabila kanalen.
  • API:erna för digitalt bläck omfattar inte handskrifts- eller formigenkänning.
  • Musinmatning ritar inte digitalt bläck förrän du lägger till CoreInputDeviceTypes.Mouse i InputDeviceTypes.

Följande problem rapporterades med Windows App SDK 2.4.1-experimentell. De 2.5.4-experimentella versionsanteckningarna listar dem inte som åtgärdade:

  • Linjalen och gradmallarna fungerar inte.
  • RenderTargetBitmap registrerar inte bläckinmatning. Om du vill skapa en bild av bläcket sparar du det i formatet GifWithEmbeddedIsf.
  • Att ange en pennknapps Palette egenskap i XAML orsakar ett XAML-parsfel. Ange Palette i koden i stället, som du ser i Anpassa en pennas färger och bredder.
  • Knappmenyer kan visas tomma och att ändra ButtonFlyoutPlacement kanske inte ändrar var de öppnas.

Ändringar från 2.4.1-experimental

Om du använde pennantecknings-API:erna i Windows App SDK 2.4.1-experimental uppdaterar du koden för dessa ändringar i 2.5.4-experimental:

API i 2.4.1-experimental Ändring i 2.5.4-experimental
InkToolbar.TargetInkPresenter (typ Object) Egenskapstypen är InkPresenter.
InkPresenter.GetHighContrastAdjustment och InkPresenter.SetHighContrastAdjustment metoder Ersatt av InkPresenter.HighContrastAdjustment egenskapen.
InkStrokeInput.GetInkPresenter och InkUnprocessedInput.GetInkPresenter metoder Ersatt av en InkPresenter egenskap för varje klass.
InkToolbarEraserButton.SelectedEraser, IsStrokeEraserVisible, och ArePrecisionErasersVisible egenskaper; InkToolbar.EraserFlyoutItemClicked händelse; InkToolbarEraserKind, InkToolbarEraserFlyoutItemKindoch InkToolbarEraserFlyoutItemClickedEventArgs typer Avlägsnad.
Inte tillgänglig InkPresenter.ActivateCustomDrying metoden och InkSynchronizer klassen har lagts till.

Tillgänglighet och hög kontrast

Digitalt bläck är visuellt innehåll, så planera hur alla användare kan använda dina bläckfunktioner:

  • Ge varje arbetsyta och verktygsfält ett tillgängligt namn med AutomationProperties.Name, som exemplen i den här artikeln gör. Ge anpassade knappar som bara visar en ikon ett namn och en knappbeskrivning.
  • InkCanvasAutomationPeergör arbetsytan tillgänglig för UI Automation, men den beskriver inte penseldragen. Om pennanteckningen har betydelse, till exempel en signatur eller ett etiketterat diagram, ange ett textalternativ.
  • Bläckinmatning kräver pekarindata. Låt inte bläck vara det enda sättet att slutföra en uppgift. Låt till exempel användarna skriva sitt namn som ett alternativ till att signera det.
  • Automatiserings-peers för de inbyggda verktygsknapparna stöder kontrollmönstren SelectionItem och ExpandCollapse. Testa tangentbordsnavigeringen och knapparnas utfällningar med den konfiguration av verktygsfältet som appen använder.

När ett kontrasttema är aktivt kan presentatören ersätta bläckfärger med systemfärger. Om du vill välja det här beteendet anger du HighContrastAdjustment egenskapen till ett InkHighContrastAdjustment värde från Microsoft.UI.Xaml.Controls namnområdet. Den Windows Runtime dokumentationen för uppräkningen med samma namn beskriver värdena på följande sätt:

Value Bläckfärg i ett kontrasttema
UseSystemColorsWhenNecessary Bläckets egen färg om den kontrasterar tillräckligt med bakgrunden. Annars används en systemfärg.
UseSystemColors En systemfärg. Överstrykningspennans bläck använder systemets överstrykningsfärg.
UseOriginalColors Bläckets egen färg.
inkCanvas.InkPresenter.HighContrastAdjustment = InkHighContrastAdjustment.UseSystemColorsWhenNecessary;

För att hålla dem korta anger exemplen i den här artikeln en vit bakgrund direkt. I din app definierar du ritytans bakgrund i temaordlistor så att den följer det aktiva temat enligt beskrivningen i Kontrastteman. Testa sedan appen med varje kontrasttema och kontrollera att förgrundsfärgen, bakgrunden och kantlinjen förblir urskiljbara.

Mer information finns i Exponera grundläggande hjälpmedelsinformation och Tangentbordstillgänglighet.

Se även