Partager via


Encre de rendu personnalisée

La propriété DrawingAttributes d’un trait vous permet de spécifier l’apparence d’un trait, comme sa taille, sa couleur et sa forme, mais il peut y avoir des moments où vous souhaitez personnaliser l’apparence au-delà de ce que DrawingAttributes autoriser. Vous pouvez personnaliser l’apparence de l’encre pour lui donner l’aspect d’un aérographe, d'une peinture à l’huile et de nombreux autres effets. La Windows Presentation Foundation (WPF) vous permet d'effectuer un rendu personnalisé de l'encre en implémentant des objets personnalisés DynamicRenderer et Stroke.

Cette rubrique contient les sous-sections suivantes :

Architecture

Le rendu de l'encre se produit deux fois : lorsqu'un utilisateur écrit de l'encre sur une surface d'encrage, puis une fois que le trait est ajouté à la surface compatible avec l'encre. Le DynamicRenderer restitue l’encre lorsque l’utilisateur déplace le stylet de tablette sur le numériseur, et le Stroke s’affiche une fois qu’il est ajouté à un élément.

Il existe trois classes à implémenter lors du rendu dynamique de l’encre.

  1. DynamicRenderer: implémentez une classe qui dérive de DynamicRenderer. Cette classe est une StylusPlugIn spécialisée qui restitue le trait tel qu’il est dessiné. L'DynamicRenderer effectue le rendu sur un thread distinct, de sorte que la surface d'encrage semble collecter de l'encre même lorsque le thread d’interface utilisateur de l’application est bloqué. Pour plus d’informations sur le modèle de thread, consultez Modèle de thread de l’encre. Pour personnaliser le rendu dynamique d’un trait, remplacez la méthode OnDraw.

  2. Trait : implémentez une classe qui dérive de Stroke. Cette classe est responsable du rendu statique des données StylusPoint une fois qu’elles ont été converties en objet Stroke. Remplacez la méthode DrawCore pour vous assurer que le rendu statique du trait est cohérent avec le rendu dynamique.

  3. InkCanvas : Implémenter une classe qui dérive de InkCanvas. Affectez le DynamicRenderer personnalisé à la propriété DynamicRenderer. Remplacez la méthode OnStrokeCollected et ajoutez un trait personnalisé à la propriété Strokes. Cela garantit que l’apparence de l’encre est cohérente.

Implémentation d’un renderer dynamique

Bien que la classe DynamicRenderer soit une partie standard de WPF, pour effectuer un rendu plus spécialisé, vous devez créer un renderer dynamique personnalisé qui dérive de la DynamicRenderer et remplacer la méthode OnDraw.

L’exemple suivant illustre un DynamicRenderer personnalisé qui dessine l’encre avec un effet de pinceau de dégradé linéaire.

using System;
using System.Windows.Media;
using System.Windows;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Input;
using System.Windows.Ink;
Imports System.Windows.Media
Imports System.Windows
Imports System.Windows.Input.StylusPlugIns
Imports System.Windows.Input
Imports System.Windows.Ink
// A StylusPlugin that renders ink with a linear gradient brush effect.
class CustomDynamicRenderer : DynamicRenderer
{
    [ThreadStatic]
    static private Brush brush = null;

    [ThreadStatic]
    static private Pen pen = null;

    private Point prevPoint;

    protected override void OnStylusDown(RawStylusInput rawStylusInput)
    {
        // Allocate memory to store the previous point to draw from.
        prevPoint = new Point(double.NegativeInfinity, double.NegativeInfinity);
        base.OnStylusDown(rawStylusInput);
    }

    protected override void OnDraw(DrawingContext drawingContext,
                                   StylusPointCollection stylusPoints,
                                   Geometry geometry, Brush fillBrush)
    {
        // Create a new Brush, if necessary.
        brush ??= new LinearGradientBrush(Colors.Red, Colors.Blue, 20d);

        // Create a new Pen, if necessary.
        pen ??= new Pen(brush, 2d);

        // Draw linear gradient ellipses between
        // all the StylusPoints that have come in.
        for (int i = 0; i < stylusPoints.Count; i++)
        {
            Point pt = (Point)stylusPoints[i];
            Vector v = Point.Subtract(prevPoint, pt);

            // Only draw if we are at least 4 units away
            // from the end of the last ellipse. Otherwise,
            // we're just redrawing and wasting cycles.
            if (v.Length > 4)
            {
                // Set the thickness of the stroke based
                // on how hard the user pressed.
                double radius = stylusPoints[i].PressureFactor * 10d;
                drawingContext.DrawEllipse(brush, pen, pt, radius, radius);
                prevPoint = pt;
            }
        }
    }
}
' A StylusPlugin that renders ink with a linear gradient brush effect.
Class CustomDynamicRenderer
    Inherits DynamicRenderer
    <ThreadStatic()> _
    Private Shared brush As Brush = Nothing

    <ThreadStatic()> _
    Private Shared pen As Pen = Nothing

    Private prevPoint As Point


    Protected Overrides Sub OnStylusDown(ByVal rawStylusInput As RawStylusInput)
        ' Allocate memory to store the previous point to draw from.
        prevPoint = New Point(Double.NegativeInfinity, Double.NegativeInfinity)
        MyBase.OnStylusDown(rawStylusInput)

    End Sub


    Protected Overrides Sub OnDraw(ByVal drawingContext As DrawingContext, _
                                   ByVal stylusPoints As StylusPointCollection, _
                                   ByVal geometry As Geometry, _
                                   ByVal fillBrush As Brush)

        ' Create a new Brush, if necessary.
        If brush Is Nothing Then
            brush = New LinearGradientBrush(Colors.Red, Colors.Blue, 20.0)
        End If

        ' Create a new Pen, if necessary.
        If pen Is Nothing Then
            pen = New Pen(brush, 2.0)
        End If

        ' Draw linear gradient ellipses between 
        ' all the StylusPoints that have come in.
        Dim i As Integer
        For i = 0 To stylusPoints.Count - 1

            Dim pt As Point = CType(stylusPoints(i), Point)
            Dim v As Vector = Point.Subtract(prevPoint, pt)

            ' Only draw if we are at least 4 units away 
            ' from the end of the last ellipse. Otherwise, 
            ' we're just redrawing and wasting cycles.
            If v.Length > 4 Then
                ' Set the thickness of the stroke based 
                ' on how hard the user pressed.
                Dim radius As Double = stylusPoints(i).PressureFactor * 10.0
                drawingContext.DrawEllipse(brush, pen, pt, radius, radius)
                prevPoint = pt
            End If
        Next i

    End Sub
End Class

Implémentation de traits personnalisés

Implémentez une classe qui dérive de Stroke. Cette classe est responsable du rendu des données StylusPoint une fois qu’elles ont été converties en objet Stroke. Remplacez la classe DrawCore pour effectuer le dessin réel.

Votre classe Stroke peut également stocker des données personnalisées à l’aide de la méthode AddPropertyData. Ces données sont stockées avec les données du trait quand elles sont rendues persistantes.

La classe Stroke peut également effectuer des tests de collision. Vous pouvez également implémenter votre propre algorithme de test d'impact en remplaçant la méthode HitTest de la classe actuelle.

Le code C# suivant illustre une classe de Stroke personnalisée qui affiche StylusPoint données sous la forme d’un trait 3D.

using System;
using System.Windows.Media;
using System.Windows;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Input;
using System.Windows.Ink;
Imports System.Windows.Media
Imports System.Windows
Imports System.Windows.Input.StylusPlugIns
Imports System.Windows.Input
Imports System.Windows.Ink
// A class for rendering custom strokes
class CustomStroke : Stroke
{
    Brush brush;
    Pen pen;

    public CustomStroke(StylusPointCollection stylusPoints)
        : base(stylusPoints)
    {
        // Create the Brush and Pen used for drawing.
        brush = new LinearGradientBrush(Colors.Red, Colors.Blue, 20d);
        pen = new Pen(brush, 2d);
    }

    protected override void DrawCore(DrawingContext drawingContext,
                                     DrawingAttributes drawingAttributes)
    {
        // Allocate memory to store the previous point to draw from.
        Point prevPoint = new Point(double.NegativeInfinity,
                                    double.NegativeInfinity);

        // Draw linear gradient ellipses between
        // all the StylusPoints in the Stroke.
        for (int i = 0; i < this.StylusPoints.Count; i++)
        {
            Point pt = (Point)this.StylusPoints[i];
            Vector v = Point.Subtract(prevPoint, pt);

            // Only draw if we are at least 4 units away
            // from the end of the last ellipse. Otherwise,
            // we're just redrawing and wasting cycles.
            if (v.Length > 4)
            {
                // Set the thickness of the stroke
                // based on how hard the user pressed.
                double radius = this.StylusPoints[i].PressureFactor * 10d;
                drawingContext.DrawEllipse(brush, pen, pt, radius, radius);
                prevPoint = pt;
            }
        }
    }
}
' A class for rendering custom strokes
Class CustomStroke
    Inherits Stroke
    Private brush As Brush
    Private pen As Pen


    Public Sub New(ByVal stylusPoints As StylusPointCollection)
        MyBase.New(stylusPoints)
        ' Create the Brush and Pen used for drawing.
        brush = New LinearGradientBrush(Colors.Red, Colors.Blue, 20.0)
        pen = New Pen(brush, 2.0)

    End Sub


    Protected Overrides Sub DrawCore(ByVal drawingContext As DrawingContext, _
                                     ByVal drawingAttributes As DrawingAttributes)

        ' Allocate memory to store the previous point to draw from.
        Dim prevPoint As New Point(Double.NegativeInfinity, Double.NegativeInfinity)

        ' Draw linear gradient ellipses between 
        ' all the StylusPoints in the Stroke.
        Dim i As Integer
        For i = 0 To Me.StylusPoints.Count - 1
            Dim pt As Point = CType(Me.StylusPoints(i), Point)
            Dim v As Vector = Point.Subtract(prevPoint, pt)

            ' Only draw if we are at least 4 units away 
            ' from the end of the last ellipse. Otherwise, 
            ' we're just redrawing and wasting cycles.
            If v.Length > 4 Then
                ' Set the thickness of the stroke 
                ' based on how hard the user pressed.
                Dim radius As Double = Me.StylusPoints(i).PressureFactor * 10.0
                drawingContext.DrawEllipse(brush, pen, pt, radius, radius)
                prevPoint = pt
            End If
        Next i

    End Sub
End Class

Implémentation d’un InkCanvas personnalisé

La manière la plus simple d’utiliser votre DynamicRenderer et votre trait personnalisés consiste à implémenter une classe qui dérive de InkCanvas et utilise ces classes. Le InkCanvas a une propriété DynamicRenderer qui spécifie le rendu du trait lorsque l’utilisateur le dessine.

Pour personnaliser les traits de rendu sur un InkCanvas procédez comme suit :

Le code C# suivant illustre une classe de InkCanvas personnalisée qui utilise un DynamicRenderer personnalisé et collecte des traits personnalisés.

public class CustomRenderingInkCanvas : InkCanvas
{
    CustomDynamicRenderer customRenderer = new CustomDynamicRenderer();

    public CustomRenderingInkCanvas() : base()
    {
        // Use the custom dynamic renderer on the
        // custom InkCanvas.
        this.DynamicRenderer = customRenderer;
    }

    protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
    {
        // Remove the original stroke and add a custom stroke.
        this.Strokes.Remove(e.Stroke);
        CustomStroke customStroke = new CustomStroke(e.Stroke.StylusPoints);
        this.Strokes.Add(customStroke);

        // Pass the custom stroke to base class' OnStrokeCollected method.
        InkCanvasStrokeCollectedEventArgs args =
            new InkCanvasStrokeCollectedEventArgs(customStroke);
        base.OnStrokeCollected(args);
    }
}

Un InkCanvas peut avoir plusieurs DynamicRenderer. Vous pouvez ajouter plusieurs objets DynamicRenderer au InkCanvas en les ajoutant à la propriété StylusPlugIns.

Conclusion

Vous pouvez personnaliser l’apparence de l’encre en dérivant vos propres classes DynamicRenderer, Strokeet InkCanvas. Ensemble, ces classes garantissent que l’apparence du trait est cohérente quand l’utilisateur dessine le trait et après qu’il a été collecté.

Voir aussi