Freigeben über


Benutzerdefiniertes Rendern von Freihandeingaben

Mithilfe der DrawingAttributes-Eigenschaft eines Strichs können Sie die Darstellung eines Strichs angeben, wie etwa seine Größe, Farbe und Form. Es gibt jedoch möglicherweise Situationen, in denen Sie Anpassungen in der Darstellung vornehmen möchten, die über die Möglichkeiten von DrawingAttributes hinausgehen. Sie können beispielsweise die Darstellung von Freihandeingaben so anpassen, dass sie wie ein Airbrush- oder Ölgemälde aussieht oder mit vielen weiteren Effekten gerendert wird. Mit dem Windows Presentation Foundation (WPF) können Sie benutzerdefinierte Freihandeingaben erstellen, indem Sie ein benutzerdefiniertes DynamicRenderer- und Stroke-Objekt implementieren.

Dieses Thema enthält folgende Unterabschnitte:

Aufbau

Freihandeingaben werden zweimal gerendert: einmal beim Schreiben auf einer Freihandoberfläche und noch einmal, nachdem der Strich der freihandeingabefähigen Oberfläche hinzugefügt wurde. DynamicRenderer rendert die Tinte, wenn der Benutzer den Tablettstift auf dem Digitalisierer bewegt, und Stroke rendert sich selbst, sobald er einem Element hinzugefügt wird.

Beim dynamischen Rendern von Freihandeingaben gibt es drei Klassen zur Implementierung.

  1. DynamicRenderer: Implementieren Sie eine Klasse, die sich von DynamicRenderer ableitet. Diese Klasse ist ein spezialisierter StylusPlugIn, der den Strich beim Zeichnen rendert. DynamicRenderer rendert in einem separaten Thread, wodurch die Freihandeingaben auf der Freihandoberfläche scheinbar erfasst werden, obwohl der Benutzeroberflächenthread der Anwendung blockiert ist. Weitere Informationen zum Threadmodell finden Sie unter Das Threadmodell für Freihandeingaben. Um das dynamische Rendern eines Strichs anzupassen, überschreiben Sie die OnDraw-Methode.

  2. Pinselstrich: Implementieren Sie eine Klasse, die von Stroke abgeleitet ist. Diese Klasse ist für das statische Rendering der StylusPoint-Daten verantwortlich, nachdem sie in ein Stroke-Objekt umgewandelt worden sind. Überschreiben Sie die DrawCore-Methode, um sicherzustellen, dass das statische Rendern des Strichs mit dem dynamischen Rendern konsistent ist.

  3. InkCanvas: Implementieren Sie eine Klasse, die von InkCanvas abgeleitet ist. Weisen Sie der angepassten DynamicRenderer der DynamicRenderer-Eigenschaft zu. Überschreiben Sie die OnStrokeCollected-Methode und fügen Sie der Strokes-Eigenschaft einen eigenen Strich hinzu. Dadurch wird sichergestellt, dass die Darstellung der Freihandeingaben konsistent ist.

Implementieren eines dynamischen Renderers

Obwohl die DynamicRenderer-Klasse ein Standardbestandteil von WPF ist, müssen Sie, um ein spezielleres Rendering durchzuführen, einen angepassten dynamischen Renderer erstellen, der von der DynamicRenderer abgeleitet ist und die OnDraw-Methode außer Kraft setzt.

Das folgende Beispiel veranschaulicht einen benutzerdefinierten DynamicRenderer, der Freihandeingaben mit einem linearen Farbverlaufspinsel zeichnet.

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

Implementieren von benutzerdefinierten Strichen

Implementieren Sie eine Klasse, die von Stroke abgeleitet ist. Diese Klasse ist für das Rendern von StylusPoint-Daten zuständig, nachdem sie in ein Stroke-Objekt umgewandelt wurden. Überschreiben Sie die DrawCore-Klasse, die das eigentliche Zeichnen übernimmt.

Ihre Stroke-Klasse kann auch benutzerdefinierte Daten speichern, indem Sie die AddPropertyData-Methode verwenden. Diese Daten werden mit den Strichdaten gespeichert, wenn sie beibehalten werden.

Die Stroke-Klasse kann auch Treffertests durchführen. Durch Überschreiben der HitTest-Methode in der aktuellen Klasse können Sie auch einen eigenen Treffertestalgorithmus implementieren.

Der folgende C#-Code veranschaulicht eine benutzerdefinierte Stroke-Klasse, die StylusPoint-Daten als 3D-Striche rendert.

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

Implementieren eines benutzerdefinierten InkCanvas

Der einfachste Weg, Ihre benutzerdefinierten DynamicRenderer und Striche zu verwenden, besteht darin, eine Klasse zu implementieren, die von diesen InkCanvas ableitet und diese Klassen verwendet. Die InkCanvas hat eine DynamicRenderer-Eigenschaft, die angibt, wie der Strich gerendert wird, wenn der Benutzer ihn zeichnet.

Um Striche auf einem InkCanvas zu rendern, gehen Sie wie folgt vor:

Der folgende C#-Code veranschaulicht eine benutzerdefinierte InkCanvas-Klasse, die eine angepasste DynamicRenderer verwendet und angepaste Pinselstriche sammelt.

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

Ein InkCanvas kann über mehrere DynamicRenderer verfügen. Sie können mehrere DynamicRenderer-Objekte zu dem InkCanvas hinzufügen, indem Sie sie der StylusPlugIns-Eigenschaft hinzufügen.

Zusammenfassung

Sie können das Erscheinungsbild von ink anpassen, indem Sie Ihre eigenen DynamicRenderer-, Stroke- und InkCanvas-Klassen ableiten. Zusammen stellen diese Klassen sicher, dass die Darstellung des Strichs während des Zeichnens durch den Benutzer mit der Darstellung nach seiner Erfassung konsistent ist.

Weitere Informationen