Condividi tramite


Personalizzare il rendering dell'input penna

La proprietà DrawingAttributes di un tratto consente di specificarne l'aspetto, ovvero dimensioni, colore e forma. In alcuni casi potrebbe, tuttavia, essere necessario personalizzare particolari dell'aspetto non coperti dalla proprietà DrawingAttributes. È possibile personalizzare l'aspetto dell'input penna eseguendo il rendering nell'aspetto di un aerografo, di una pittura a olio e di molti altri effetti. Windows Presentation Foundation (WPF) consente di personalizzare il rendering dell'input penna implementando un oggetto DynamicRenderer o Stroke personalizzato.

In questo argomento sono contenute le seguenti sottosezioni:

  • Architettura

  • Implementazione di un renderer dinamico

  • Implementing a Custom Stroke

  • Implementazione di un oggetto InkCanvas personalizzato

  • Conclusione

Architettura

Il rendering dell'input penna viene eseguito due volte: quando l'utente scrive tramite una penna su un'apposita superficie e dopo l'aggiunta del tratto alla superficie di input penna. L'oggetto DynamicRenderer esegue il rendering dell'input penna quando l'utente sposta la penna del Tablet PC sul digitalizzatore, mentre l'oggetto Stroke esegue il rendering di se stesso dopo essere stato aggiunto a un elemento.

Per l'esecuzione dinamica del rendering dell'input penna è necessario implementare tre classi.

  1. DynamicRenderer: implementare una classe che deriva da DynamicRenderer. Questa classe è un oggetto StylusPlugIn specializzato che esegue il rendering del tratto mentre viene tracciato. L'oggetto DynamicRenderer esegue il rendering su un thread separato, pertanto la superficie di input penna sembra raccogliere l'input penna anche quando il thread dell'interfaccia utente dell'applicazione è bloccato. Per ulteriori informazioni sul modello di threading, vedere Modello di threading dell'input penna. Per personalizzare l'esecuzione dinamica 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 dell'oggetto StylusPoint dopo la relativa conversione in un oggetto Stroke. Eseguire l'override del metodo DrawCore per assicurarsi che il rendering statico del tratto sia coerente con il rendering dinamico.

  3. InkCanvas: implementare una classe che deriva da InkCanvas. Assegnare l'oggetto DynamicRenderer personalizzato alla proprietà DynamicRenderer. Eseguire l'override del metodo OnStrokeCollected e aggiungere un tratto personalizzato alla proprietà Strokes. In questo modo, è possibile assicurarsi che l'aspetto dell'input penna sia coerente.

Implementazione di un renderer dinamico

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

Nell'esempio riportato di seguito viene illustrato un oggetto DynamicRenderer personalizzato che disegna l'input penna con un effetto pennello a sfumatura lineare.

Imports System
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
    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 'OnStylusDown


    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 'OnDraw
End Class 'CustomDynamicRenderer
using System;
using System.Windows.Media;
using System.Windows;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Input;
using 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.
        if (brush == null)
        {
            brush = new LinearGradientBrush(Colors.Red, Colors.Blue, 20d);
        }

        // Create a new Pen, if necessary.
        if (pen == null)
        {
            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;
            }
        }
    }
}

Implementazione di tratti personalizzati

Implementare una classe che derivi dall'oggetto Stroke. Questa classe è responsabile del rendering dei dati dell'oggetto StylusPoint dopo la relativa conversione in un oggetto Stroke. Eseguire l'override della classe DrawCore per ottenere il disegno effettivo.

Nella classe Stroke è inoltre possibile archiviare dati personalizzati utilizzando il metodo AddPropertyData. Questi dati vengono archiviati con i dati del tratto quando sono resi persistenti.

La classe Stroke può inoltre eseguire l'hit testing. È inoltre possibile implementare un proprio algoritmo di hit testing eseguendo l'override del metodo HitTest nella classe corrente.

Nel codice C# riportato di seguito viene illustrata una classe Stroke personalizzata che esegue il rendering dei dati dell'oggetto StylusPoint come tratto 3D.

Imports System
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
    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 'New


    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 'DrawCore
End Class 'CustomStroke
using System;
using System.Windows.Media;
using System.Windows;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Input;
using 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;
            }
        }
    }
}

Implementazione di un oggetto InkCanvas personalizzato

Il modo più semplice per utilizzare il tratto e l'oggetto DynamicRenderer personalizzato consiste nell'implementare una classe che deriva da InkCanvas e utilizza queste classi. L'oggetto InkCanvas presenta una proprietà DynamicRenderer che specifica la modalità di rendering del tratto mentre l'utente lo disegna.

Per eseguire il rendering personalizzato dei tratti su un oggetto InkCanvas, eseguire le seguenti operazioni:

Nel codice C# riportato di seguito viene illustrata una classe InkCanvas personalizzata che utilizza un oggetto DynamicRenderer personalizzato e raccoglie tratti personalizzati.

Public Class CustomRenderingInkCanvas
    Inherits InkCanvas

    Private customRenderer As New CustomDynamicRenderer()

    Public Sub New()
        ' Use the custom dynamic renderer on the
        ' custom InkCanvas.
        Me.DynamicRenderer = customRenderer

    End Sub 'New

    Protected Overrides Sub OnStrokeCollected(ByVal e As InkCanvasStrokeCollectedEventArgs)

        ' Remove the original stroke and add a custom stroke.
        Me.Strokes.Remove(e.Stroke)
        Dim customStroke As New CustomStroke(e.Stroke.StylusPoints)
        Me.Strokes.Add(customStroke)

        ' Pass the custom stroke to base class' OnStrokeCollected method.
        Dim args As New InkCanvasStrokeCollectedEventArgs(customStroke)
        MyBase.OnStrokeCollected(args)

    End Sub 'OnStrokeCollected 
End Class 'CustomRenderingInkCanvas
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 oggetto InkCanvas può avere più di un oggetto DynamicRenderer. È possibile aggiungere più oggetti DynamicRenderer all'oggetto InkCanvas aggiungendoli alla proprietà StylusPlugIns.

Conclusione

È possibile personalizzare l'aspetto dell'input penna derivando classi DynamicRenderer, Stroke e InkCanvas personalizzate. Insieme, queste classi assicurano che il tratto abbia un aspetto coerente quando l'utente lo disegna e dopo che viene raccolto.

Vedere anche

Altre risorse

Gestione avanzata dell'input penna