This topic explains adornments and the process of creating custom adornments.
The following topics are prerequisites to understanding this topic:
This topic contains the following sections:
Adornments are visual elements displayed in the xamSyntaxEditor ™ control’s text editing area that display visual user cues. Adornments are very flexible and may be rendered in either their own custom rendering layer or in any of the predefined rendering layers. The xamSyntaxEditor control provides an extensible API that allows you to create your own adornments and adornment layers. These are the same APIs used internally by the xamSyntaxEditor for implementing several xamSyntaxEditor’s features - Caret, Selection and Error Reporting.
By following the steps below you will create a custom adornment, which marks all whitespaces in the current document.
The following screenshot is a preview of the result. The spaces are marked with dots and tabs are marked with arrows.
To complete the procedure, you need to know how to add xamSyntaxEditor to your page.
Following is a conceptual overview of the process:
1. Creating an AdornmentGeneratorProvider
class derived from adornment generator provider. This class is responsible for creating instances of your adornment generator class wheneverthe control requests one.
2. Registering your derived AdornmentGeneratorProvider
with the ServicesManager
,exposed on the TextDocument’s Language.
3. Creating a derived SyntaxEditorAdornmentGeneratorBase
class from adornment generator. This class is responsible for rendering the actual adornments into a specific document view. Instances of this class will be automatically created by the AdornmentGeneratorProvider
you registered in step 2.
The following steps demonstrate how to create a custom adornment.
Create a class extending from the AdornmentGeneratorProvider and implements the CreateAdornmentGenerator method, which, in turn, instantiates and returns an instance of your SyntaxEditorAdornmentGeneratorBase derived class. When the xamSyntaxEditor invokes this method, it supplies the related DocumentViewBase. It is a good idea to pass it along to the adornment, because the DocumentViewBase
contains all the context the adornment needs to access view and control information.
Create a page and add the xamSyntaxEditor control. Use the ServicesManager’s RegisterService method to register the adornment generator provider.
Create an adornment generator class extending from the SyntaxEditorAdornmentGeneratorBase
class. When invoking the base constructor you can associate the adornment generator with one of the predefined adornment layers or create a new layer for your adornment and specify its z-order position with respect to the other layers.
Use the AdornmentLayer
property to access the adornment layer’s AddAdornment method to add the elements associated with your adornment generator.
In most cases, you will also add a handler to the document view’s LayoutChanged event to update the adornments whenever the visible lines change.
Override the OnRefreshAdornments
method, invoked by the xamSyntaxEditor whenever its settings change.
Override the OnUnloaded
method to remove any added adornment elements and free up any other resources in use.
The following table lists the code examples included in this topic.
This code example shows how to create a custom adornment generator provider.
In C#:
public class WhiteSpaceAdornmentProvider : AdornmentGeneratorProvider
{
public override SyntaxEditorAdornmentGeneratorBase CreateAdornmentGenerator(DocumentViewBase documentView)
{
WhiteSpaceAdornment adornment = new WhiteSpaceAdornment(documentView);
return adornment;
}
}
In Visual Basic:
Public Class WhiteSpaceAdornmentProvider
Inherits AdornmentGeneratorProvider
Public Overrides Function CreateAdornmentGenerator(documentView As DocumentViewBase) As SyntaxEditorAdornmentGeneratorBase
Dim adornment As New WhiteSpaceAdornment(documentView)
Return adornment
End Function
End Class
This code example shows how to create a custom adornment generator that replaces all spaces and tabs in the current document with symbols.
In C#:
// this adornment will draw symbols to indicate tabs and spaces
public class WhiteSpaceAdornment : SyntaxEditorAdornmentGeneratorBase
{
private AdornmentInfo adornmentInfo;
private Canvas adornmentCanvas;
private bool _adornmentsInitialized;
// The adornment will draw symbols in its own layer defined between
// the Caret layer and the Text Foreground layer
public WhiteSpaceAdornment(DocumentViewBase dv) :
base(dv, new AdornmentLayerInfo("WhiteSpaceLayer",
new string[] { AdornmentLayerKeys.CaretLayer },
new string[] { AdornmentLayerKeys.TextForegroundLayer }))
{
// listen for layout changed so that the whitespace marks will be
// redrawn when scrolling the document
this.DocumentView.LayoutChanged += UpdateWhiteSpaces;
InitializeAdornments();
}
private void InitializeAdornments()
{
if (this._adornmentsInitialized || this.AdornmentLayer == null) return;
// create a canvas for showing the whitespace marks
this.adornmentCanvas = new Canvas();
this.adornmentCanvas.Width = this.DocumentView.TextAreaBounds.Width;
this.adornmentCanvas.Height = this.DocumentView.TextAreaBounds.Height;
// add the adornment and position the canvas at 0,0 with respect to the editing area
this.adornmentInfo =
this.AdornmentLayer.AddAdornment(this.adornmentCanvas, new Point(0, 0), null);
this._adornmentsInitialized = true;
}
protected override void OnTextAreaInitialized()
{
base.OnTextAreaInitialized();
// initialize the adornment after the text area of the editor is initialized
this.InitializeAdornments();
}
// create new geometries to update the whitespace marks
private void UpdateWhiteSpaces(object sender, EventArgs e)
{
// obtain all visible lines
DocumentViewLineCollection visLines = this.DocumentView.VisibleLines;
// clear old geometry
this.adornmentCanvas.Children.Clear();
// iterate over all visible lines
foreach (DocumentViewLine visLine in visLines)
{
SnapshotLineInfo sli = visLine.SnapshotLineInfo;
// iterate over the characters in a single line
for (int charIndex = 0; charIndex < sli.Length; charIndex++)
{
char ch = sli.GetCharacter(charIndex);
if (ch.Equals('\t'))
{
// if the adornment encounter a tab - create the tab mark
Rect bounds = GetCharBounds(charIndex, visLine, sli);
Path path = CreateTabMarker(Colors.Blue, Colors.Blue, bounds);
this.adornmentCanvas.Children.Add(path);
}
else if (ch.Equals(' '))
{
// if the adornment encounter a space - create the space mark
Rect bounds = GetCharBounds(charIndex, visLine, sli);
Path path = CreateSpaceMarker(Colors.Black, Colors.Black, bounds);
this.adornmentCanvas.Children.Add(path);
}
}
}
// force repaint of the canvas
this.adornmentCanvas.InvalidateMeasure();
}
// calculate the bounds of a given character
private Rect GetCharBounds(int charIndex, DocumentViewLine visLine, SnapshotLineInfo sli)
{
Rect result = new Rect();
Point startPoint = visLine.PointFromCharacterIndex(charIndex);
result.X = startPoint.X;
result.Y = startPoint.Y;
Point endPoint;
if (charIndex == sli.Length - 1)
{
// last line character
endPoint = new Point(visLine.Bounds.Right, visLine.Bounds.Bottom);
}
else
{
// not last line character
endPoint = visLine.PointFromCharacterIndex(charIndex + 1);
endPoint.X--;
endPoint.Y = visLine.Bounds.Bottom;
}
result.Width = endPoint.X - startPoint.X + 1;
result.Height = endPoint.Y - startPoint.Y + 1;
return result;
}
// create the geometries for a tab mark
private Path CreateTabMarker(Color stroke, Color fill, Rect bounds)
{
PathGeometry geo = new PathGeometry();
GeometryGroup geoGroup = new GeometryGroup();
// Draw the center line
LineGeometry line = new LineGeometry();
line.StartPoint = new Point(bounds.Left + 3, bounds.Top + bounds.Height / 2);
line.EndPoint = new Point(bounds.Right, bounds.Top + bounds.Height / 2);
geoGroup.Children.Add(line);
// Draw the upper part of the arrow tip.
line = new LineGeometry();
line.StartPoint = new Point(bounds.Right, bounds.Top + bounds.Height / 2);
line.EndPoint = new Point(bounds.Right - 4, bounds.Top + (bounds.Height / 2) - 3);
geoGroup.Children.Add(line);
// Draw the lower part of the arrow tip.
line = new LineGeometry();
line.StartPoint = new Point(bounds.Right, bounds.Top + bounds.Height / 2);
line.EndPoint = new Point(bounds.Right - 4, bounds.Top + (bounds.Height / 2) + 3);
geoGroup.Children.Add(line);
Path path = new Path();
path.Fill = new SolidColorBrush(fill);
path.Stroke = new SolidColorBrush(stroke);
path.Data = geoGroup;
return path;
}
// create the geometries for a space mark
private Path CreateSpaceMarker(Color stroke, Color fill, Rect bounds)
{
EllipseGeometry geo = new EllipseGeometry();
geo.Center = new Point(bounds.Left + bounds.Width / 2, bounds.Top + bounds.Height / 2);
geo.RadiusX = .5;
geo.RadiusY = .5;
Path path = new Path();
path.Fill = new SolidColorBrush(fill);
path.Stroke = new SolidColorBrush(stroke);
path.Data = geo;
return path;
}
// invoked from the Syntax Editor, when there are changes and update is needed
protected override void OnRefreshAdornments()
{
UpdateWhiteSpaces(null, null);
}
// unregister event handlers on unload
protected override void OnUnloaded()
{
this.DocumentView.LayoutChanged -= UpdateWhiteSpaces;
if (this._adornmentsInitialized)
{
bool removed = this.AdornmentLayer.RemoveAdornment(this.adornmentInfo);
this._adornmentsInitialized = false;
}
}
}
In Visual Basic:
' this adornment will draw symbols to indicate tabs and spaces
Public Class WhiteSpaceAdornment
Inherits SyntaxEditorAdornmentGeneratorBase
Private adornmentInfo As AdornmentInfo
Private adornmentCanvas As Canvas
Private _adornmentsInitialized As Boolean
' the adornment will draw symbols in its own layer defined between
' the Caret layer and the Text Foreground layer
Public Sub New(dv As DocumentViewBase)
MyBase.New(dv, New AdornmentLayerInfo("WhiteSpaceLayer", New String() {AdornmentLayerKeys.CaretLayer}, New String() {AdornmentLayerKeys.TextForegroundLayer}))
' listen for layout changed so that the whitespace marks will be
' redrawn when scrolling the document
AddHandler Me.DocumentView.LayoutChanged, AddressOf UpdateWhiteSpaces
InitializeAdornments()
End Sub
Private Sub InitializeAdornments()
If Me._adornmentsInitialized OrElse Me.AdornmentLayer Is Nothing Then
Return
End If
' create a canvas for showing the whitespace marks
Me.adornmentCanvas = New Canvas()
Me.adornmentCanvas.Width = Me.DocumentView.TextAreaBounds.Width
Me.adornmentCanvas.Height = Me.DocumentView.TextAreaBounds.Height
' add the adornment and position the canvas at 0,0 with respect to the editing area
Me.adornmentInfo = _
Me.AdornmentLayer.AddAdornment(Me.adornmentCanvas, New Point(0, 0), Nothing)
Me._adornmentsInitialized = True
End Sub
Protected Overrides Sub OnTextAreaInitialized()
MyBase.OnTextAreaInitialized()
' initialize the adornment after the text area of the editor is initialized
Me.InitializeAdornments()
End Sub
' create new geometries to update the whitespace marks
Private Sub UpdateWhiteSpaces(sender As Object, e As EventArgs)
' obtain all visible lines
Dim visLines As DocumentViewLineCollection = Me.DocumentView.VisibleLines
' clear old geometry
Me.adornmentCanvas.Children.Clear()
' iterate over all visible lines
For Each visLine As DocumentViewLine In visLines
Dim sli As SnapshotLineInfo = visLine.SnapshotLineInfo
' iterate over the characters in a single line
For charIndex As Integer = 0 To sli.Length - 1
Dim ch As Char = sli.GetCharacter(charIndex)
If ch.Equals(ControlChars.Tab) Then
' if the adornment encounter a tab - create the tab mark
Dim bounds As Rect = GetCharBounds(charIndex, visLine, sli)
Dim path As Path = CreateTabMarker(Colors.Blue, Colors.Blue, bounds)
Me.adornmentCanvas.Children.Add(path)
ElseIf ch.Equals(" "C) Then
' if the adornment encounter a space - create the space mark
Dim bounds As Rect = GetCharBounds(charIndex, visLine, sli)
Dim path As Path = CreateSpaceMarker(Colors.Black, Colors.Black, bounds)
Me.adornmentCanvas.Children.Add(path)
End If
Next
Next
' force repaint of the canvas
Me.adornmentCanvas.InvalidateMeasure()
End Sub
' calculate the bounds of a given character
Private Function GetCharBounds(charIndex As Integer, visLine As DocumentViewLine, sli As SnapshotLineInfo) As Rect
Dim result As New Rect()
Dim startPoint As Point = visLine.PointFromCharacterIndex(charIndex)
result.X = startPoint.X
result.Y = startPoint.Y
Dim endPoint As Point
If charIndex Is sli.Length - 1 Then
' last line character
endPoint = New Point(visLine.Bounds.Right, visLine.Bounds.Bottom)
Else
' not last line character
endPoint = visLine.PointFromCharacterIndex(charIndex + 1)
endPoint.X -= 1
endPoint.Y = visLine.Bounds.Bottom
End If
result.Width = endPoint.X - startPoint.X + 1
result.Height = endPoint.Y - startPoint.Y + 1
Return result
End Function
' create the geometries for a tab mark
Private Function CreateTabMarker(stroke As Color, fill As Color, bounds As Rect) As Path
Dim geo As New PathGeometry()
Dim geoGroup As New GeometryGroup()
' Draw the center line
Dim line As New LineGeometry()
line.StartPoint = New Point(bounds.Left + 3, bounds.Top + bounds.Height / 2)
line.EndPoint = New Point(bounds.Right, bounds.Top + bounds.Height / 2)
geoGroup.Children.Add(line)
' Draw the upper part of the arrow tip.
line = New LineGeometry()
line.StartPoint = New Point(bounds.Right, bounds.Top + bounds.Height / 2)
line.EndPoint = New Point(bounds.Right - 4, bounds.Top + (bounds.Height / 2) - 3)
geoGroup.Children.Add(line)
' Draw the lower part of the arrow tip.
line = New LineGeometry()
line.StartPoint = New Point(bounds.Right, bounds.Top + bounds.Height / 2)
line.EndPoint = New Point(bounds.Right - 4, bounds.Top + (bounds.Height / 2) + 3)
geoGroup.Children.Add(line)
Dim path As New Path()
path.Fill = New SolidColorBrush(fill)
path.Stroke = New SolidColorBrush(stroke)
path.Data = geoGroup
Return path
End Function
' create the geometries for a space mark
Private Function CreateSpaceMarker(stroke As Color, fill As Color, bounds As Rect) As Path
Dim geo As New EllipseGeometry()
geo.Center = New Point(bounds.Left + bounds.Width / 2, bounds.Top + bounds.Height / 2)
geo.RadiusX = 0.5
geo.RadiusY = 0.5
Dim path As New Path()
path.Fill = New SolidColorBrush(fill)
path.Stroke = New SolidColorBrush(stroke)
path.Data = geo
Return path
End Function
' invoked from the Syntax Editor, when there are changes and update is needed
Protected Overrides Sub OnRefreshAdornments()
UpdateWhiteSpaces(Nothing, Nothing)
End Sub
' unregister event handlers on unload
Protected Overrides Sub OnUnloaded()
RemoveHandler Me.DocumentView.LayoutChanged, AddressOf UpdateWhiteSpaces
If Me._adornmentsInitialized Then
Dim removed As Boolean = Me.AdornmentLayer.RemoveAdornment(Me.adornmentInfo)
Me._adornmentsInitialized = False
End If
End Sub
End Class
This code example demonstrates registering your custom adornment generator provider in the service manager, provided by the TextDocument’s language.
In C#:
this.xamSyntaxEditor1.Document.Language.ServicesManager.RegisterService(
"WhiteSpaceAdornment",
new WhiteSpaceAdornmentProvider());
In Visual Basic:
Me.xamSyntaxEditor1.Document.Language.ServicesManager.RegisterService( _
"WhiteSpaceAdornment", New WhiteSpaceAdornmentProvider())
The following topics provide additional information related to this topic.