Encre de rendu personnalisé
Mise à jour : novembre 2007
La propriété DrawingAttributes d'un trait vous permet de spécifier son apparence (taille, couleur et forme) mais il se peut que vous ayez envie de personnaliser l'apparence au delà de ce que les DrawingAttributes permettent. Vous souhaitez peut-être personnaliser l'apparence de l'encre en lui donnant l'aspect d'un aérographe, d'une peinture à l'huile ou autre. Windows Presentation Foundation (WPF) vous permet de personnaliser l'encre de rendu en implémentant un objet DynamicRenderer et Stroke personnalisé.
Cette rubrique contient les sous-sections suivantes :
Architecture
Implementing a Dynamic Renderer
Implementing a Custom Stroke
Implementing a Custom InkCanvas
Conclusion
Architecture
Le rendu de l'encre a lieu deux fois : lorsqu'un utilisateur écrit sur une surface d'encrage, et après l'ajout du trait à la surface prenant en charge l'écriture manuscrite. Le DynamicRenderer restitue l'encre lorsque l'utilisateur déplace le stylet sur le digitaliseur et le Stroke est restitué une fois qu'il est ajouté à un élément.
Il convient d'implémenter trois classes lors du rendu dynamique de l'encre.
DynamicRenderer : implémente une classe qui dérive de DynamicRenderer. Cette classe est un StylusPlugIn spécialisé qui restitue le trait comme il est dessiné. Le DynamicRenderer effectue le rendu sur un thread séparé ; la surface d'encrage semble donc collecter de l'encre même lorsque le thread de l'interface utilisateur de l'application est bloqué. Pour plus d'informations sur le modèle de thread, consultez Modèle de thread de l'encre. Pour personnaliser le rendu dynamique d'un trait, substituez la méthode OnDraw.
Stroke : implémente une classe qui dérive de Stroke. Cette classe est responsable du rendu statique des données StylusPoint après leur conversion en objet Stroke. Substituez la méthode DrawCore pour vous assurer que le rendu statique du trait est cohérent avec le rendu dynamique.
InkCanvas : implémente une classe qui dérive de InkCanvas. Assignez le DynamicRenderer personnalisé à la propriété DynamicRenderer. Substituez la méthode OnStrokeCollected et ajoutez un trait personnalisé à la propriété Strokes. Cela garantit la cohérence de l'apparence de l'encre.
Implémentation d'un convertisseur dynamique
Bien que la classe DynamicRenderer soit un composant standard de WPF, pour exécuter un rendu plus spécialisé, vous devez créer un convertisseur dynamique personnalisé qui dérive de DynamicRenderer et substituer la méthode OnDraw.
L'exemple suivant montre un DynamicRenderer personnalisé qui dessine de l'encre avec un effet de pinceau à dégradé linéaire.
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;
}
}
}
}
Implémentation de traits personnalisés
Implémentez une classe qui dérive de Stroke ; Cette classe est responsable du rendu des données StylusPoint après leur conversion en objet Stroke. Substituez la classe DrawCore pour réaliser le dessin proprement dit.
Votre classe Stroke peut également stocker des données personnalisées à l'aide de la méthode AddPropertyData. Ces données sont stockées avec les données du trait lorsqu'elles sont persistantes.
La classe Stroke peut également effectuer un test de recherche. Vous pouvez également implémenter votre propre algorithme de test de recherche en substituant la méthode HitTest dans la classe en cours.
Le code C# suivant montre une classe Stroke personnalisée qui restitue des données StylusPoint en tant que trait 3D.
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;
}
}
}
}
Implémentation d'un InkCanvas personnalisé
Le meilleur moyen d'utiliser votre DynamicRenderer et votre trait personnalisés est d'implémenter une classe que dérive de InkCanvas et qui utilise ces classes. InkCanvas a une propriété DynamicRenderer qui spécifie la manière dont le trait est restitué lorsque l'utilisateur le dessine.
Pour personnaliser le rendu des traits sur un InkCanvas, procédez comme suit :
Créez une classe qui dérive de InkCanvas.
Assignez votre DynamicRenderer personnalisé à la propriété InkCanvas.DynamicRenderer.
Substituez la méthode OnStrokeCollected. Dans cette méthode, supprimez le trait d'origine ajouté à InkCanvas. Ensuite, créez un trait personnalisé, ajoutez-le à la propriété Strokes et appelez la classe de base avec un nouveau InkCanvasStrokeCollectedEventArgs qui contient le trait personnalisé.
Le code C# suivant montre une classe InkCanvas personnalisée qui utilise un DynamicRenderer personnalisé et collecte des traits personnalisés.
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);
}
}
Un InkCanvas peut avoir plusieurs DynamicRenderer. Vous pouvez ajouter plusieurs objets DynamicRenderer à InkCanvas en les ajoutant à la propriété StylusPlugIns.
Conclusion
Vous pouvez personnaliser l'apparence de l'encre en dérivant vos propres classes DynamicRenderer, Stroke et InkCanvas. Ensemble, ces classes garantissent la cohérence de l'apparence du trait lorsque l'utilisateur dessine le trait et après sa collecte.