共用方式為


自訂呈現筆墨

更新:2007 年 11 月

筆劃的 DrawingAttributes 屬性可讓您指定筆劃的外觀,例如大小、色彩和形狀,不過,您有時想要自訂外觀的部分可能超出 DrawingAttributes 允許的範圍。您可能會想自訂筆墨的外觀,在呈現的外觀加上噴槍、油畫和其他許多效果。Windows Presentation Foundation (WPF) 可讓您實作自訂 DynamicRendererStroke 物件,藉此方式自訂呈現筆墨。

本主題包含下列子章節:

  • 架構

  • 實作動態產生器

  • 實作自訂筆劃

  • 實作自訂 InkCanvas

  • 結論

架構

筆墨呈現會執行兩次,一是在使用者將筆墨寫到筆墨表面時,另一則是在筆劃加入到啟用筆墨的表面之後。當使用者在數位板上移動 Tablet 畫筆時,DynamicRenderer 會呈現筆墨,而 Stroke 在一加入項目時會呈現本身。

動態呈現筆墨時,有三個類別要實作。

  1. DynamicRenderer:實作衍生自 DynamicRenderer 的類別。此類別是特殊的 StylusPlugIn,會在繪製筆劃時呈現筆劃。DynamicRenderer 會在另外的執行緒上呈現筆墨,所以即使當應用程式使用者介面 (UI) 執行緒遭封鎖,筆墨表面也會像是在收集筆墨。如需執行緒模型的詳細資訊,請參閱筆墨執行緒模型。若要自訂動態呈現筆墨,請覆寫 OnDraw 方法。

  2. Stroke:實作衍生自 Stroke 的類別。此類別負責在 StylusPoint 資料轉換成 Stroke 物件之後靜態呈現資料。請覆寫 DrawCore 方法,以確保筆劃的靜態呈現與動態呈現一致。

  3. InkCanvas:實作衍生自 InkCanvas 的類別。將自訂的 DynamicRenderer 指派給 DynamicRenderer 屬性。覆寫 OnStrokeCollected 方法,並將自訂筆劃加入到 Strokes 屬性。這可確保筆墨的外觀一致。

實作動態產生器

雖然 DynamicRenderer 類別是 WPF 的標準組件,但若要執行更特殊的呈現效果,就必須建立衍生自 DynamicRenderer 的自訂動態產生器,並覆寫 OnDraw 方法。

下列範例示範自訂的 DynamicRenderer,會以線形漸層筆刷效果繪製筆墨。

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

實作自訂筆劃

實作衍生自 Stroke 的類別。此類別負責在 StylusPoint 資料轉換成 Stroke 物件之後呈現資料。覆寫 DrawCore 類別以進行實際繪製。

Stroke 類別也可以使用 AddPropertyData 方法儲存自訂資料。此資料會在保存時與筆劃資料一起儲存。

Stroke 類別也可以執行點擊測試。您也可以覆寫目前類別中的 HitTest 方法,實作自己的點擊測試演算法。

下列 C# 程式碼示範自訂 Stroke 類別,此類別會將 StylusPoint 資料呈現為立體筆劃。

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

實作自訂 InkCanvas

使用自訂 DynamicRenderer 和筆劃最簡單的方法是實作衍生自 InkCanvas 的類別,並使用這些類別。InkCanvas 具有 DynamicRenderer 屬性,會指定使用者在繪製筆劃時,如何呈現筆劃。

若要自訂 InkCanvas 上的呈現筆墨,請執行下列步驟:

下列 C# 程式碼示範自訂 InkCanvas 類別,此類別會使用自訂的 DynamicRenderer 並收集自訂筆劃。

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

    }

}

InkCanvas 可以有一個以上的 DynamicRenderer。您可以將多個 DynamicRenderer 物件加入到 InkCanvas,方法是將它們加入到 StylusPlugIns 屬性。

結論

您可以衍生自己的 DynamicRendererStrokeInkCanvas 類別,來自訂筆墨的外觀。在一起使用時,這些類別能確保筆劃的外觀在使用者繪製筆劃時以及在收集之後都是一致的。

請參閱

其他資源

筆墨進階處理