Поделиться через


Пользовательская отрисовка рукописных данных

Свойство DrawingAttributes штриха позволяет задавать внешний вид штриха, включая его размер, цвет и форму, однако возможны случаи, когда необходимые настройки превышают возможности DrawingAttributes. Может потребоваться настроить отображение рукописного фрагмента с эффектом аэрографа, масляной живописи и т. д. Windows Presentation Foundation (WPF) позволяет настроить отрисовку рукописных фрагментов путем реализации настраиваемого отрисовщика DynamicRenderer и объекта Stroke.

В этом разделе содержатся следующие подразделы:

Архитектура

Отрисовка рукописных фрагментов происходит два раза: когда пользователь осуществляет рукописный ввод на поверхности рукописного ввода, а затем еще раз после добавления штриха в область с поддержкой рукописного ввода. DynamicRenderer отрисовывает рукописные фрагменты при перемещении пера планшета по дигитайзеру, а Stroke отрисовывает сам себя после его добавления в элемент.

Предусмотрено три класса для реализации динамической отрисовки рукописных фрагментов.

  1. DynamicRenderer. Реализация класса, унаследованного от класса DynamicRenderer. Этот класс является специализированным StylusPlugIn, обрабатывающим штрих при его написании. DynamicRenderer осуществляет отрисовку в отдельном потоке, поэтому поверхность рукописного ввода появляется для сбора рукописных фрагментов даже при блокировке потока пользовательского интерфейса приложения. Дополнительные сведения о потоковой модели см. в статье Потоковая модель рукописного ввода. Чтобы настроить динамическую отрисовку штриха, переопределите метод OnDraw.

  2. Stroke. Реализация класса, унаследованного от класса Stroke. Этот класс отвечает за статическую отрисовку данных StylusPoint после их преобразования в объект Stroke. Переопределите метод DrawCore, чтобы гарантировать согласование статической отрисовки штриха с динамической отрисовкой.

  3. InkCanvas. Реализация класса, унаследованного от класса InkCanvas. Назначьте настроенный отрисовщик DynamicRenderer свойству DynamicRenderer. Переопределите метод OnStrokeCollected и добавьте пользовательский штрих в свойство Strokes. Это гарантирует согласованность внешнего вида рукописных фрагментов.

Реализация динамического отрисовщика

Хотя класс DynamicRenderer является стандартной частью WPF, для осуществления более специализированной отрисовки необходимо создать настроенный динамический отрисовщик, производный от DynamicRenderer, и переопределить метод OnDraw.

В следующем примере демонстрируется настроенный отрисовщик DynamicRenderer, который выполняет отрисовку рукописного ввода с эффектом кисти с линейным градиентом.

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

Реализация пользовательских штрихов

Реализация класса, унаследованного от класса Stroke. Этот класс отвечает за отрисовку данных StylusPoint после их преобразования в объект Stroke. Переопределите класс DrawCore для фактического рисования.

Класс Stroke также может хранить пользовательские данные с помощью метода AddPropertyData. Эти данные хранятся с данными штриха при сохранении.

Класс Stroke может также выполнять проверку совпадений. Кроме того, вы можете реализовать собственный алгоритм проверки совпадений путем переопределения метода HitTest в текущем классе.

Следующий код C# демонстрирует пользовательский класс Stroke, отрисовывающий данные StylusPoint в виде трехмерного штриха.

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

Реализация пользовательского объекта InkCanvas

Самым простым способом использования настроенного отрисовщика DynamicRenderer и штриха является реализация класса, который является производным от InkCanvas и использует эти классы. InkCanvas имеет свойство DynamicRenderer, указывающее, каким образом отображается штрих, когда пользователь рисует его.

Для настройки отрисовки штрихов для объекта InkCanvas выполните следующие действия.

  • Создайте класс, производный от класса InkCanvas.

  • Назначьте настроенный отрисовщик DynamicRenderer свойству InkCanvas.DynamicRenderer.

  • Переопределите метод OnStrokeCollected. В этом методе удалите исходный штрих, который был добавлен в InkCanvas. Затем создайте пользовательский штрих, добавьте его в свойство Strokes и вызовите базовый класс с новым InkCanvasStrokeCollectedEventArgs, содержащим пользовательский штрих.

Следующий код C# демонстрирует пользовательский класс InkCanvas, использующий настроенный отрисовщик DynamicRenderer и выполняющий сбор пользовательских штрихов.

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

InkCanvas может иметь несколько DynamicRenderer. В InkCanvas можно добавить несколько объектов DynamicRenderer посредством их добавления в свойство StylusPlugIns.

Заключение

Можно настроить внешний вид рукописных фрагментов путем создания собственных производных классов DynamicRenderer, Stroke и InkCanvas. Вместе эти классы гарантируют согласованность внешнего вида штриха в момент, когда пользователь рисует штрих, и после его сбора.

См. также