Renderização Personalizada de Tinta
A propriedade DrawingAttributes de um traço permite que você especifique a aparência de um traço, como seu tamanho, cor e forma, mas pode haver ocasiões em que você deseja personalizar a aparência além do que DrawingAttributes permite. Convém personalizar a aparência da tinta renderizando na aparência de um pincel de ar, pintura a óleo e muitos outros efeitos. O Windows Presentation Foundation (WPF) permite renderização personalizada de tinta implementando um objeto personalizado DynamicRenderer e Stroke.
Este tópico contém as subseções a seguir:
Architecture
Implementing a Dynamic Renderer
Implementing a Custom Stroke
Implementing a Custom InkCanvas
Conclusion
Arquitetura
Renderização de tinta ocorre duas vezes; quando um usuário grava tinta em uma superfície de escrita à tinta, e novamente após o traço ser adicionado à superfície que permite tinta. O DynamicRenderer renderiza a tinta quando o usuário move a caneta eletrônica no digitalizador, e o Stroke renderiza a si próprio assim que ele é adicionado a um elemento.
Há três classes para implementar quando se realiza renderização dinâmica de tinta.
DynamicRenderer: Implemente uma classe que seja derivada de DynamicRenderer. Essa classe é um StylusPlugIn especializado que processa o traço conforme ele é desenhado. O DynamicRenderer faz a renderização em uma thread separada, para que a superfície de tinta pareça coletar tinta mesmo que a thread de interface de usuário (UI) da aplicação esteja bloqueada. Para obter mais informações sobre o modelo de threads, consulte O modelo de encadeamento de tinta. Para personalizar a renderização dinâmica de um traço, substitua o método OnDraw.
Traçado: Implemente uma classe que seja derivada de Stroke. Essa classe é responsável pela renderização estática dos dados de StylusPoint depois de ele ter sido convertido em um objeto Stroke. Sobrescrever o método DrawCore para garantir que renderização estática do traço está consistente com a renderização dinâmica.
InkCanvas: Implementar uma classe que deriva de InkCanvas. Atribua o DynamicRenderer personalizado à propriedade DynamicRenderer. Sobrescrever o método OnStrokeCollected e adicionar um traço personalizado à propriedade Strokes. Isso assegura que a aparência da tinta seja consistente.
Implementando um Dynamic Renderer
Embora a classe DynamicRenderer seja uma parte padrão de WPF, para executar renderização mais especializada, você deve criar um Dynamic Renderer personalizado que é derivado de DynamicRenderer e substituir o método OnDraw.
O exemplo a seguir demonstra um DynamicRenderer personalizado que desenha tinta com um efeito de pincel de gradiente linear.
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;
}
}
}
}
Implementando Traços Personalizados
Implemente uma classe que seja derivada de Stroke. Essa classe é responsável pela renderização dos dados de StylusPoint depois de ele ter sido convertido em um objeto Stroke. Substitua a classe DrawCore para fazer o desenho real.
A classe Stroke também pode armazenar dados personalizados usando o método AddPropertyData. Esses dados são armazenados com os dados de traço quando persistidos.
A classe Stroke também pode executar teste de clique. Você também pode implementar seu próprio algoritmo de teste de clique substituindo o método HitTest na classe atual.
O seguinte código C# demonstra uma classe Stroke personalizada que renderiza dados StylusPoint como um traço 3-D.
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;
}
}
}
}
Implementando um InkCanvas personalizado
A maneira mais fácil de usar seu DynamicRenderer e traço personalizados é implementar uma classe que seja derivada de InkCanvas e usar essas classes. O InkCanvas possui uma propriedade DynamicRenderer que especifica como o traço é renderizado quando o usuário está desenhando-o.
Para renderizar traços de modo personalizado em um InkCanvas faça o seguinte:
Crie uma classe que seja derivada de InkCanvas.
Atribua seu DynamicRenderer personalizado à propriedade InkCanvas.DynamicRenderer.
Substitua o método OnStrokeCollected. Nesse método, remova o traço original que foi adicionado ao InkCanvas. Em seguida, crie um traço personalizado, adicione-o à propriedade Strokes, e chame a classe base com um novo InkCanvasStrokeCollectedEventArgs que contém o traço personalizado.
O seguinte código C# demonstra uma classe InkCanvas personalizada que usa um DynamicRenderer personalizado e coleta traços personalizados.
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);
}
}
Um InkCanvas pode ter mais de um DynamicRenderer. Você pode adicionar vários objetos DynamicRenderer ao InkCanvas adicionando-os à propriedade StylusPlugIns.
Conclusão
Você pode personalizar a aparência de tinta criando suas próprias classes DynamicRenderer, Stroke e InkCanvas. Juntas, essas classes garantem que a aparência do traço seja consistente quando o usuário desenha o traço e depois que ele é coletado.