Contrôles d’entrée manuscrite pour les applications WinUI

Important

InkCanvas, InkToolbaret InkPresenter sont des API expérimentales. La prise en charge de l’entrée manuscrite a été ajoutée dans SDK d'application Windows version expérimentale 2.4.1. Cet article décrit SDK d'application Windows 2.5.4-experimental, qui a modifié la façon de connecter un InkToolbar. Ces API ne sont pas disponibles dans le canal stable. Ils peuvent changer ou être supprimés dans une version ultérieure. Ne les utilisez pas dans les applications de production.

Utilisez le contrôle expérimental InkCanvas pour permettre aux utilisateurs de dessiner des entrées manuscrites avec un stylet, une souris ou avec le doigt. Ajoutez un InkToolbar pour donner aux utilisateurs un stylo à bille, un crayon, un surligneur et une gomme, avec des options de couleur et de taille. Chaque canevas possède un InkPresenter qui collecte et restitue l’entrée manuscrite. Le InkStrokeContainer du présentateur stocke les traits afin que vous puissiez enregistrer, charger, copier et créer de l’entrée manuscrite dans le code.

Pour essayer les contrôles, installez le package SDK d'application Windows 2.5.4-experimental et le runtime correspondant. Pour obtenir des liens de téléchargement et des informations d’installation, consultez Les derniers téléchargements SDK d'application Windows.

Est-ce le contrôle approprié ?

Utilisez un canevas d’entrée manuscrite lorsque les utilisateurs doivent :

  • Prenez des notes manuscrites ou annotez du contenu.
  • Esquissez, dessinez des diagrammes ou signez votre nom.
  • Créez du contenu à main levée que votre application enregistre et leur permet de le modifier plus tard.

Les API d’encrage expérimentales n’incluent pas la reconnaissance de l’écriture manuscrite ou des formes. Pour collecter du texte, utilisez une zone de texte.

Si vous avez besoin d’entrée manuscrite dans une application de production, consultez les limitations connues des composants pour connaître l’état de ces composants dans le canal stable et les solutions de remplacement.

Comprendre le modèle d’encrage

Les API d’entrée manuscrite fonctionnent ensemble :

API Rôle
InkCanvas Élément XAML qui affiche l’encre et reçoit une entrée à l’encre. La propriété InkPresenter retourne le présentateur du canevas.
InkPresenter Collecte les entrées manuscrites, affiche les traits et contient les paramètres d’entrée et les attributs de dessin par défaut. Vous ne pouvez pas en créer un InkPresenter vous-même. Obtenez-le depuis InkCanvas.InkPresenter.
InkStrokeContainer Stocke les tracés. Utilisez-le pour enregistrer, charger, sélectionner, copier et coller de l’encre. Obtenez-le de InkPresenter.StrokeContainer.
InkToolbar Barre d’outils facultative qui permet aux utilisateurs de choisir un stylet, un crayon, un surligneur ou une gomme, et de modifier la couleur et la taille de l’encre.

InkCanvas n’a pas de propriété Background. Pour donner à la zone de dessin un arrière-plan et une bordure, placez le canevas à l’intérieur d’un conteneur, par exemple, Borderet définissez la taille sur le conteneur. La couleur d’encre par défaut est noire. Par conséquent, les exemples de cet article utilisent un arrière-plan blanc.

Plusieurs de ces types ont les mêmes noms que les types dans l’espace de noms Windows Runtime Windows.UI.Input.Inking, tels que InkPresenter, InkStrokeContainer et InkHighContrastAdjustment. Les contrôles d’encrage utilisent les versions Microsoft.UI.Xaml.Controls. Les traits et les attributs de dessin utilisent toujours des types Windows Runtime, tels que InkStroke, InkDrawingAttributeset InkStrokeBuilder. Si vous importez les deux espaces de noms et utilisez un nom ambigu, le compilateur C# signale l’erreur CS0104.

Les exemples de cet article importent Microsoft.UI.Xaml.Controls et utilisent des directives d’alias pour les Windows.UI.Input.Inking types dont ils ont besoin :

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

Vous pouvez également importer à la fois des espaces de noms et utiliser des noms pleinement qualifiés pour les types ambigus, comme le fait l’exemple de galerie WinUI 3.

InkPresenter fournit également des API de niveau inférieur que cet article ne couvre pas :

  • StrokeInput déclenche des événements lorsqu’un trait démarre, continue, se termine ou est annulé.
  • UnprocessedInput déclenche des événements de pointeur pour l’entrée que le présentateur ne traite pas comme entrée manuscrite.
  • InputConfiguration définit si le présentateur traite l’entrée à partir de la gomme d’un stylet et du bouton latéral principal.
  • InputProcessingConfiguration définit si la saisie dessine des entrées manuscrites, efface les entrées manuscrites ou n’est pas traitée, et comment le présentateur gère le glisser avec le bouton droit.
  • ActivateCustomDrying retourne un(e) InkSynchronizer pour les applications qui affichent elles-mêmes les entrées manuscrites validées.

Configuration de votre projet

Les API d’entrée manuscrite sont marquées expérimentales. Lorsque vous les utilisez dans un projet C#, le compilateur C# signale l’avertissement CS8305 et le compilateur XAML signale l’avertissement WMC1501. Si votre projet traite les avertissements comme des erreurs, ces avertissements arrêtent la génération.

Après avoir décidé d’utiliser les API expérimentales, vous pouvez supprimer ces avertissements. Ajoutez les propriétés suivantes à votre fichier projet C# (.csproj) :

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

Les exemples de cet article utilisent C#. Chaque exemple complet montre le code XAML d’une page et la classe code-behind de la page, MainPage.

Créer une zone de dessin manuscrit

Icône galerie WinUI 3 L’application Galerie WinUI 3 inclut des exemples interactifs de contrôles et de fonctionnalités WinUI. Obtenez l’application à partir du Microsoft Store ou parcourez le code source sur GitHub.

L’exemple suivant place un canevas à l’intérieur d’un élément blanc Border. Le code-behind définit les types d’appareils d’entrée afin que les utilisateurs puissent dessiner avec un stylet, une souris ou le tactile.

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

Pour les raccourcir, certains exemples ultérieurs ne répètent pas ce code. Définissez InputDeviceTypes dans chaque page où les utilisateurs dessinent avec une souris.

Connectez une barre d’outils d'encrage

Placez un InkToolbar près du canevas et définissez sa propriété TargetInkCanvas pour connecter les deux :

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

À partir de SDK d'application Windows 2.5.4-experimental, vous pouvez connecter une barre d’outils directement à une InkPresenter en définissant la TargetInkPresenter propriété. Définissez-le dans le code :

inkToolbar.TargetInkPresenter = inkCanvas.InkPresenter;

Vous pouvez également le définir en XAML : TargetInkPresenter="{x:Bind inkCanvas.InkPresenter}". Définissez uniquement l’une des deux propriétés cibles.

Par défaut, la barre d’outils affiche des boutons de stylo à bille, crayon, surbrillance, gomme et gabarit (règle). Pour modifier les boutons qui s’affichent, consultez Personnaliser la barre d’outils d’entrée manuscrite.

Choisir les périphériques d’entrée

La propriété InputDeviceTypes utilise l’énumération d’indicateurs CoreInputDeviceTypes de l’espace de noms Windows.UI.Core. La saisie à la souris ne dessine pas d’encre par défaut. Combinez les valeurs pour choisir les appareils qui utilisent l’encre :

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

Pour arrêter la collecte d’encre à partir de chaque appareil, par exemple pour afficher un dessin que les utilisateurs ne peuvent pas modifier, définissez IsInputEnabled sur false:

inkCanvas.InkPresenter.IsInputEnabled = false;

Pour permettre aux utilisateurs d’activer ou de désactiver un appareil d’entrée à partir de la barre d’outils, voir Ajouter un bouton bascule personnalisé.

Définir les attributs de dessin

Attributs de dessin, tels que la couleur, la taille et la forme de pointe du stylet, contrôlent l’apparence des traits. Ils utilisent la classe Windows RuntimeInkDrawingAttributes. Pour modifier les attributs des nouveaux traits, appeler CopyDefaultDrawingAttributes, modifier la copie et la passer à UpdateDefaultDrawingAttributes:

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

Colors se trouve dans l’espace Microsoft.UI de noms et Size se trouve dans l’espace Windows.Foundation de noms. Les nouveaux attributs s’appliquent aux tracés que les utilisateurs dessinent après l’appel de fonction. Les traits existants conservent leurs attributs.

Lorsque vous connectez un InkToolbar, l’outil actif et ses paramètres de couleur et de taille déterminent les attributs de dessin. Pour lire les attributs actuels, utilisez la propriété InkDrawingAttributes de la barre d’outils.

Créer des paramètres prédéfinis de stylet

Si vous n’utilisez pas de barre d’outils, vous pouvez proposer vos propres préréglages du stylet. L’exemple suivant applique les attributs de stylo à bille, de crayon, de surligneur ou de calligraphie lorsque l’utilisateur sélectionne un préréglage ou modifie la taille.

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

Options prédéfinies de stylet pour le stylo à bille, le crayon, le surligneur et la calligraphie, avec la calligraphie sélectionnée, au-dessus d’un curseur Taille et d’une zone de dessin blanche. Le canevas montre des traits en boucle dans l’encre de stylo à bille noire, l’encre de crayon gris, l’encre de surligneur jaune et l’encre de calligraphie bleu foncé.

  • InkDrawingAttributes.CreateForPencil retourne des attributs pour les traits de crayon. La propriété Kind est en lecture seule. Utilisez donc cette méthode pour créer des attributs de stylet.
  • DrawAsHighlighter affiche le trait sous forme de superposition de surligneur.
  • PenTip définit la forme de l’extrémité du stylet et PenTipTransform la fait pivoter ou la met à l’échelle. Une pointe rectangulaire inclinée produit des traits de calligraphie.

Répondre aux modifications d’encre

Le présentateur déclenche StrokesCollected après qu’un utilisateur dessine des traits et StrokesErased après qu’un utilisateur les efface. La Strokes propriété des arguments de chaque événement contient les traits affectés. Le code suivant conserve un compteur de frappes à jour dans un TextBlock nommé 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";
}

Ces événements se produisent lorsque les utilisateurs dessinent ou effacent de l’encre. Lorsque vous modifiez des traits dans le code, par exemple avec Clear, LoadAsyncou AddStroke, mettez à jour votre interface utilisateur après l’appel.

Enregistrer et charger de l’encre

InkStrokeContainer enregistre l’encre au format sérialisé manuscrit (ISF). Pour choisir la façon dont les données sont stockées, transmettez une InkPersistenceFormat valeur à SaveAsync:

Format Output
GifWithEmbeddedIsf Image GIF qui contient les données manuscrites. Les visionneuses d’images peuvent afficher le fichier, et votre application peut charger à nouveau l’encre pour modification.
Isf Données manuscrites uniquement, sans image. Il s’agit du format le plus compact.

Ajoutez les méthodes suivantes au code-behind d’une page qui contient un InkCanvas nommé inkCanvas. SaveInkAsync écrit l’entrée manuscrite dans un fichier et LoadInkAsync remplace l’entrée manuscrite sur la zone de dessin par l’entrée manuscrite d’un fichier. LoadAsync supprime les traits existants avant d’ajouter les traits chargés. Vous n’avez donc pas besoin d’appeler Clear en premier.

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 et AsInputStream convertissent les flux .NET en types de flux du runtime Windows utilisés par SaveAsync et LoadAsync. Pour attendre ces méthodes Windows Runtime, le fichier a besoin de la directive using System;. Sans cela, le compilateur signale l’erreur CS4036.

Pour permettre aux utilisateurs de choisir un fichier, utilisez les sélecteurs de fichiers SDK d'application Windows, qui retournent le chemin d’accès du fichier sélectionné. Pour plus d’informations, consultez Enregistrer un fichier avec un sélecteur de SDK d'application Windows et Ouvrir des fichiers et dossiers avec les sélecteurs SDK d'application Windows.

Pour conserver l’entrée manuscrite en mémoire, par exemple pour la restaurer ultérieurement dans la même session, enregistrez-la dans un InMemoryRandomAccessStream. Passez le résultat du GetOutputStreamAt(0) flux à SaveAsync, puis passez son GetInputStreamAt(0) résultat à LoadAsync. L’exemple de galerie WinUI 3 utilise cette approche.

Copier et coller de l’encre

InkStrokeContainer peut copier des tracés dans le presse-papiers et coller de l’encre à partir de celui-ci. CopySelectedToClipboard ne copie que les traits sélectionnés, de sorte que l’exemple suivant sélectionne chaque trait sur le canevas gauche avant de les copier. CanPasteFromClipboard vérifie si le Presse-papiers contient des entrées manuscrites. PasteFromClipboard ajoute l’encre à la position que vous passez et retourne le rectangle englobant de la zone qui a changé.

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

Copiez le canevas gauche et collez-le dans les boutons du canevas droit au-dessus de deux canevas vierges qui affichent les mêmes traits en forme de boucle, de spirale ou d'ondulation. Le texte ci-dessous indique : Encre collée dans une zone de 255 x 69.

Pour manipuler certains traits, définissez la propriété Selected de chaque trait souhaité, ou appelez SelectWithLine ou SelectWithPolyLine. Pour supprimer ou déplacer les traits sélectionnés, appelez DeleteSelected ou MoveSelected.

Créer des tracés dans le code

Utilisez la classe Windows Runtime InkStrokeBuilder pour créer des traits à partir de points et les ajouter au canevas avec AddStroke. L’exemple suivant ajoute une étoile et une ligne de signature.

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

Ajouter une étoile, ajouter une ligne de signature et le bouton Effacer au-dessus d’un canevas blanc qui montre une étoile à cinq pointes d’or et une ligne de signature grise avec un X à son extrémité gauche.

  • CreateStrokeFromInkPoints crée un trait à partir de InkPoint valeurs. Chaque point combine une position sur le canvas avec une valeur de pression.
  • La documentation Windows Runtime recommande CreateStrokeFromInkPoints plutôt que CreateStroke pour les traits que vous ajoutez à un présentateur d’encre.
  • Quand FitToCurve c’est false le cas, le trait utilise des segments droits entre des points plutôt que des courbes.

Un trait ne peut appartenir qu’à un seul conteneur. Pour copier des traits dans une autre zone de dessin, ajoutez des clones des traits :

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

Personnaliser la barre d’outils d’entrée manuscrite

Choisir les contrôles initiaux

La propriété InitialControls détermine quels boutons intégrés la barre d’outils crée :

Value Boutons intégrés
All (valeur par défaut) stylo à bille, crayon, surligneur, gomme et gabarit (règle)
PensOnly Stylo à bille, crayon et surligneur
AllExceptPens Gomme et gabarit (règle)
None None. Déclarez les boutons souhaités en tant qu’enfants de la barre d’outils.

Quatre barres d’outils d’entrée manuscrite, chacune au-dessus d’un canevas avec des exemples de traits. La barre d’outils intitulée InitialControls All (valeur par défaut) affiche le stylo à bille, le crayon, le surligneur, la gomme et les boutons de règle. PensOnly affiche les trois boutons de stylet. AllExceptPens affiche les boutons gomme et règle. None avec un crayon n’affiche qu’un bouton de crayon, et son canevas montre des traits de crayon gris.

L’exemple suivant déclare les quatre mêmes boutons que l’exemple de galerie WinUI 3, donne à chacun un nom accessible et sélectionne le stylo à bille lorsque la barre d’outils se charge :

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

Pour afficher une commande qui efface toutes les entrées manuscrites, définissez la propriété du bouton gomme true sur IsClearAllVisible. La barre d’outils déclenche l’événement EraseAllClicked lorsque l’utilisateur sélectionne la commande.

Personnaliser les couleurs et les largeurs d’un stylet

Chaque bouton de stylet a une sélection Palette de pinceaux et de réglages de largeur de trait. Définir Palette dans le code. Pour plus d’informations, consultez Limites connues. L’exemple suivant remplace la palette d’un stylo à bille par quatre couleurs de marque, limite la largeur du trait et affiche les attributs du stylet actif.

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

Barre d’outils d’encre avec stylet à bille, gomme et boutons de règle au-dessus des options de couleur bleu, vert, orange et violet. Le texte lit la couleur #FF0078D4, la largeur 6 (plage 2-16) et la zone de dessin affiche des traits en boucle en bleu, vert, orange et violet.

SelectedBrushIndex sélectionne une couleur dans Palette. SelectedStrokeWidth, MinStrokeWidth et MaxStrokeWidth définissent la largeur actuelle du trait et la plage parmi laquelle les utilisateurs peuvent choisir. La barre d’outils émet InkDrawingAttributesChanged lorsque les attributs de l’outil actif changent.

Sélectionner l’outil actif

Définissez ActiveTool pour sélectionner un outil et gérez ActiveToolChanged pour réagir lorsque l’utilisateur en sélectionne un autre. GetToolButton retourne un bouton d’outil intégré du type que vous passez en paramètre. Les boutons intégrés peuvent ne pas exister tant que la barre d’outils n’est pas chargée ; sélectionnez donc un outil initial dans le gestionnaire d’événements Loaded de la barre d’outils.

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

Barre d’outils manuscrite avec le bouton crayon sélectionné. Le texte ci-dessous indique crayon : encre de crayon, couleur #FF000000, taille 4 et le canevas montre des traits de crayon gris.

Changer l’orientation

Pour afficher la barre d’outils verticalement, par exemple en regard du canevas, définissez Orientation sur 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>

Barre d’outils d’encre verticale avec boutons de stylo à bille, crayon, surligneur, gomme et règle à gauche d’une zone de dessin avec traits d’encre noirs.

La ButtonFlyoutPlacement propriété définit l’emplacement où les menus volants de bouton s’ouvrent par rapport à la barre d’outils. La valeur par défaut, Auto, permet à la barre d’outils de choisir la position.

Ajouter un bouton bascule personnalisé

Utilisez un InkToolbarCustomToggleButton pour ajouter un paramètre que les utilisateurs activent et désactivent à partir de la barre d’outils. L’exemple suivant ajoute un bouton bascule qui contrôle l’entrée tactile. La saisie au stylet et à la souris tracent toujours de l’encre.

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

Barre d’outils d’encre avec stylo à bille, crayon, surligneur, gomme et boutons de règle suivis d’un bouton bascule personnalisé avec une icône tactile, qui est activée. Le texte lit InputDeviceTypes = Touch, Pen, Mouse et le canevas affiche des traits d’encre noir.

Le bouton bascule affiche uniquement une icône, de sorte que l’exemple lui donne un nom accessible et une info-bulle. Dans cet exemple, la barre d’outils crée également ses boutons par défaut et le bouton bascule personnalisé s’affiche après eux.

La barre d’outils prend en charge d’autres boutons personnalisés :

Créer la barre d’outils dans le code

Vous pouvez également créer le canevas et la barre d’outils dans le code. L’exemple suivant les ajoute à un Border nommé inkHost, connecte la barre d’outils avec TargetInkPresenter, puis sélectionne le crayon lorsque la barre d’outils se charge.

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

Barre d’outils manuscrite avec le bouton crayon sélectionné au-dessus d’une zone de dessin avec traits de crayon gris.

Limitations connues

  • Les contrôles d’entrée manuscrite sont disponibles uniquement dans les versions expérimentales du SDK Windows App. Ils ne sont pas disponibles dans le canal stable.
  • Les API d’encrage n’incluent pas la reconnaissance de l’écriture manuscrite ou des formes.
  • La saisie à la souris n’affiche pas l’encre tant que vous n’avez pas ajouté CoreInputDeviceTypes.Mouse à InputDeviceTypes.

Les problèmes suivants ont été signalés avec SDK d'application Windows 2.4.1-experimental. Les notes de publication 2.5.4-experimental ne les répertorient pas comme corrigés :

  • Les pochoirs de règle et de rapporteur ne fonctionnent pas.
  • RenderTargetBitmap ne détecte pas l’encre. Pour créer une représentation de l’encre, enregistrez-la au format GifWithEmbeddedIsf.
  • La définition de la propriété Palette d’un bouton du stylet en XAML provoque une erreur d’analyse XAML. Définissez Palette dans le code à la place, comme indiqué dans Personnaliser les couleurs et les largeurs d’un stylet.
  • Les menus déroulants des boutons peuvent apparaître vides, et le fait de modifier ButtonFlyoutPlacement peut ne pas changer l’endroit où ils s’ouvrent.

Modifications de 2.4.1-expérimental

Si vous avez utilisé les API d’entrée manuscrite dans SDK d'application Windows version 2.4.1 expérimentale, mettez à jour votre code pour ces modifications dans la version 2.5.4 expérimentale :

API dans 2.4.1-experimental Changement dans 2.5.4-experimental
InkToolbar.TargetInkPresenter (type Object) Le type de propriété est InkPresenter.
Méthodes InkPresenter.GetHighContrastAdjustment et InkPresenter.SetHighContrastAdjustment Remplacé par la propriété InkPresenter.HighContrastAdjustment.
Méthodes InkStrokeInput.GetInkPresenter et InkUnprocessedInput.GetInkPresenter Remplacé par une InkPresenter propriété sur chaque classe.
InkToolbarEraserButton.SelectedEraser, IsStrokeEraserVisibleet ArePrecisionErasersVisible propriétés ; InkToolbar.EraserFlyoutItemClicked événement ; InkToolbarEraserKind, InkToolbarEraserFlyoutItemKindet InkToolbarEraserFlyoutItemClickedEventArgs types Supprimé.
Non disponible Méthode InkPresenter.ActivateCustomDrying et classe InkSynchronizer ajoutées.

Accessibilité et contraste élevé

L’entrée manuscrite est du contenu visuel. Planifiez la façon dont tous les utilisateurs peuvent utiliser vos fonctionnalités d’entrée manuscrite :

  • Donnez à chaque canevas et barre d’outils un nom accessible avec AutomationProperties.Name, comme le montrent les exemples de cet article. Donnez aux boutons personnalisés qui affichent uniquement une icône un nom et une info-bulle.
  • InkCanvasAutomationPeer expose le canevas à UI Automation, mais il ne décrit pas les tracés. Si l’encre a une signification, telle qu’une signature ou un diagramme étiqueté, fournissez une alternative textuelle.
  • Le dessin à l’encre nécessite une saisie au pointeur. Ne faites pas de l’encre la seule façon d’effectuer une tâche. Par exemple, laissez les utilisateurs taper leur nom comme alternative à la signature.
  • Les pairs d’automatisation pour les boutons intégrés de la barre d’outils prennent en charge les modèles de contrôle SelectionItem et ExpandCollapse. Testez la navigation au clavier et les menus déroulants des boutons avec la configuration de la barre d’outils utilisée par votre application.

Lorsqu’un thème de contraste est actif, le présentateur peut remplacer les couleurs de l’encre par les couleurs système. Pour choisir ce comportement, définissez la propriété HighContrastAdjustment sur une valeur InkHighContrastAdjustment de l’espace de noms Microsoft.UI.Xaml.Controls. La documentation Windows Runtime de l’énumération portant le même nom décrit les valeurs de cette façon :

Value Couleur de l’encre dans un thème de contraste
UseSystemColorsWhenNecessary La propre couleur de l’encre si elle contraste suffisamment avec l’arrière-plan. Sinon, une couleur système.
UseSystemColors Une couleur système. L’encre du surligneur utilise la couleur du surligneur système.
UseOriginalColors Couleur propre à l’encre.
inkCanvas.InkPresenter.HighContrastAdjustment = InkHighContrastAdjustment.UseSystemColorsWhenNecessary;

Pour rester concis, les exemples de cet article définissent directement un arrière-plan blanc. Dans votre application, définissez l’arrière-plan du canevas dans les dictionnaires de thèmes afin qu’il suive le thème actif, comme décrit dans Les thèmes Contrast. Testez ensuite votre application avec chaque thème de contraste et vérifiez que l’encre, l’arrière-plan et la bordure restent distincts.

Pour plus d’informations, consultez Exposer les informations d’accessibilité de base et Accessibilité au clavier.

Voir aussi