自定义呈现墨迹
笔划的 DrawingAttributes 属性允许你指定笔划的外观,例如其大小、颜色和形状,但有时你可能想要自定义超出 DrawingAttributes 允许的外观。 建议通过使用喷笔、油画和多种其他效果呈现外观,从而自定义墨迹的外观。 Windows Presentation Foundation (WPF) 允许通过实现自定义 DynamicRenderer 和 Stroke 对象来自定义呈现墨迹。
本主题包含以下小节:
建筑
墨迹呈现会出现两次:用户将墨迹写入墨迹书写表面时,以及将笔划添加到启用了墨迹的表面之后。 当用户在数字化器上移动平板电脑笔时,DynamicRenderer 会呈现墨迹,而 Stroke 在被添加到元素后会显示自己。
动态渲染墨迹时,需要实现三个类。
DynamicRenderer:实现派生自 DynamicRenderer的类。 此类是专用的 StylusPlugIn,可按绘制的原本形式呈现笔划。 DynamicRenderer 在一个单独线程上执行呈现,因此即使在应用程序用户界面 (UI) 线程被阻止时也会出现墨迹书写表面来收集墨迹。 有关线程模型的详细信息,请参阅 墨迹线程模型。 若要自定义动态呈现笔划,请重写 OnDraw 方法。
笔划:实现派生自 Stroke 的类。 此类负责在 StylusPoint 数据被转换为 Stroke 对象后静态呈现该数据。 重写 DrawCore 方法是为了确保笔划的静态呈现与动态呈现一致。
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 数据呈现为 3D 笔划。
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。 可以通过将多个 DynamicRenderer 对象添加到 StylusPlugIns 属性来将其添加到 InkCanvas。
结论
可以通过派生自己的 DynamicRenderer、Stroke 和 InkCanvas 类来自定义墨迹的外观。 将这些类结合使用可以确保用户绘制笔划时和笔划被收集后的笔划外观保持一致。