Condividi tramite


Inchiostro di rendering personalizzato

La proprietà DrawingAttributes di un tratto consente di specificare l'aspetto di un tratto, come dimensioni, colore e forma, ma potrebbe essere necessario personalizzare l'aspetto oltre ciò che DrawingAttributes consente. È possibile personalizzare l'aspetto dell'inchiostro eseguendo il rendering nell'aspetto di un pennello d'aria, una vernice a olio e molti altri effetti. Windows Presentation Foundation (WPF) consente di eseguire il rendering personalizzato dell'inchiostro implementando un oggetto DynamicRenderer e Stroke personalizzati.

Questo argomento contiene le sottosezioni seguenti:

Architettura

Il rendering dell'inchiostro si verifica due volte: quando un utente scrive inchiostro su una superficie di inchiostro, e di nuovo dopo l'aggiunta del tratto alla superficie abilitata per l'inchiostro. Il DynamicRenderer esegue il rendering dell'inchiostro quando l'utente sposta la penna del tablet sul digitalizzatore e il Stroke esegue il rendering di se stesso una volta aggiunto a un elemento.

Sono disponibili tre classi da implementare durante il rendering dinamico dell'inchiostro.

  1. DynamicRenderer: implementare una classe che deriva da DynamicRenderer. Questa classe è un StylusPlugIn specializzato che renderizza il tratto mentre viene disegnato. Il DynamicRenderer esegue il rendering su un thread separato, quindi la superficie di inchiostrazione sembra raccogliere inchiostro anche quando il thread dell'interfaccia utente dell'applicazione è bloccato. Per ulteriori informazioni sul modello di gestione dei thread per la penna, vedere Modello di Gestione dei Thread per la Penna. Per personalizzare il rendering dinamico di un tratto, eseguire l'override del metodo OnDraw.

  2. Stroke: implementare una classe che deriva da Stroke. Questa classe è responsabile del rendering statico dei dati StylusPoint dopo la conversione in un oggetto Stroke. Sovrascrivere il metodo DrawCore per garantire che il rendering statico del tratto sia coerente con il rendering dinamico.

  3. InkCanvas: Implementare una classe che deriva da InkCanvas. Assegna la DynamicRenderer personalizzata alla proprietà DynamicRenderer. Sovrascrivere il metodo OnStrokeCollected e aggiungere un tratto personalizzato alla proprietà Strokes. In questo modo si garantisce che l'aspetto dell'inchiostro sia coerente.

Implementazione di un renderer dinamico

Sebbene la classe DynamicRenderer sia una parte standard di WPF, per eseguire il rendering più specializzato, è necessario creare un renderer dinamico personalizzato che deriva dalla DynamicRenderer ed eseguire l'override del metodo OnDraw.

Nell'esempio seguente viene illustrato un DynamicRenderer personalizzato che disegna l'inchiostro con un effetto di sfumatura lineare del pennello.

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

Implementazione di tratti personalizzati

Implementare una classe che deriva da Stroke. Questa classe è incaricata del rendering dei dati StylusPoint dopo che sono stati convertiti in un oggetto Stroke. Eseguire l'override della classe DrawCore per eseguire il disegno effettivo.

La classe Stroke può anche archiviare dati personalizzati usando il metodo AddPropertyData. Questi dati vengono archiviati insieme ai dati del tratto quando vengono resi permanenti.

La classe Stroke può anche eseguire il rilevamento delle collisioni. È anche possibile implementare un algoritmo di hit testing personalizzato eseguendo l'override del metodo HitTest nella classe corrente.

Il codice C# seguente illustra una classe Stroke personalizzata che esegue il rendering dei dati StylusPoint come tratto 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

Implementazione di un "InkCanvas" personalizzato

Il modo più semplice per usare i DynamicRenderer personalizzati e il tratto consiste nell'implementare una classe che deriva da InkCanvas e usa queste classi. Il InkCanvas dispone di una proprietà DynamicRenderer che specifica come viene reso il tratto quando l'utente lo disegna.

Per eseguire il rendering personalizzato dei tratti in un InkCanvas seguire i passaggi seguenti:

Il codice C# seguente illustra una classe InkCanvas personalizzata che usa un DynamicRenderer personalizzato e raccoglie tratti personalizzati.

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 può avere più di un DynamicRenderer. È possibile aggiungere più oggetti DynamicRenderer al InkCanvas aggiungendoli alla proprietà StylusPlugIns.

Conclusione

È possibile personalizzare l'aspetto dell'inchiostro derivando le tue classi DynamicRenderer, Stroke, e InkCanvas. Insieme, queste classi assicurano che l'aspetto del tratto sia coerente quando l'utente disegna il tratto e dopo la raccolta.

Vedere anche