通过笔画的 DrawingAttributes 属性可以指定笔画的外观,例如笔画的大小、颜色和形状,但有时除了 DrawingAttributes 允许的那些外观之外,您可能还希望自定义外观。 您可能希望通过在喷枪、油彩和许多其他效果中呈现来自定义墨迹的外观。 Windows Presentation Foundation (WPF) 允许您通过实现自定义 DynamicRenderer 和 Stroke 对象来自定义呈现墨迹。
本主题包含以下小节:
体系结构
实现动态呈现程序
Implementing a Custom Stroke
实现自定义 InkCanvas
结束语
体系结构
墨迹呈现发生两次:当用户在墨迹图面上书写墨迹时,以及将笔画添加到支持墨迹的图面上之后。 当用户将触笔移动到数字化仪上时,DynamicRenderer 将呈现墨迹,一旦将Stroke添加到元素上,它会呈现其自身。
动态呈现墨迹时需要实现三个类。
DynamicRenderer:实现从 DynamicRenderer 派生的类。 此类是一个专用的 StylusPlugIn,它在绘制笔画时呈现笔画。 DynamicRenderer 在单独的线程上进行呈现,因此即使应用程序用户界面 (UI) 线程被阻止,墨迹图面也会显示为正在收集墨迹。 有关线程模型的更多信息,请参见墨迹线程处理模型。 若要自定义动态呈现笔画,请重写 OnDraw 方法。
Stroke:实现从 Stroke 派生的类。 此类负责在 StylusPoint 数据转换为 Stroke 对象后静态呈现该数据。 重写 DrawCore 方法,以确保笔画的静态呈现与动态呈现保持一致。
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 类以完成实际的绘制。
通过使用 AddPropertyData 方法,您的 Stroke 类还可以存储自定义数据。 持久性保存数据时,此数据与笔画数据存储在一起。
Stroke 类还可以执行命中测试。 通过重写 HitTest 方法,您还可以在当前类中实现自己的命中测试算法。
下面的 C# 代码演示将 StylusPoint 数据呈现为三维笔画的自定义 Stroke 类。
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 上自定义呈现笔画,请执行以下操作:
创建一个从 InkCanvas 派生的类。
将自定义的 DynamicRenderer 分配给 InkCanvas.DynamicRenderer 属性。
重写 OnStrokeCollected 方法。 在此方法中,移除已添加到 InkCanvas 中的原始笔画。 然后创建一个自定义笔画,将其添加到 Strokes 属性中,然后使用包含该自定义笔画的新 InkCanvasStrokeCollectedEventArgs 调用基类。
下面的 C# 代码演示使用自定义 DynamicRenderer 并收集自定义笔画的自定义 InkCanvas 类。
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 对象添加到 StylusPlugIns 属性中,从而将它们添加到 InkCanvas 中。
结束语
您可以通过派生自己的 DynamicRenderer、Stroke 和 InkCanvas 类来自定义墨迹的外观。 将这些类结合使用可以确保在用户绘制笔画以及收集笔画后,笔画的外观保持一致。