This document describes how the TextView class renders the text, and how you can extend the text rendering process to add new features to the text editor.
The ICSharpCode.AvalonEdit.Rendering..::..TextView class is the heart of AvalonEdit. It takes care of getting the document onto the screen.
To do this in an extensible way, the TextView uses its own kind of model: the VisualLine. Visual lines are created only for the visible part of the document.
The rendering process looks like this:
VisualLines are only created for the visible part of the document. Lots of actions can trigger their creation, but most commonly the creation will be caused by the MeasureOverride method of TextView. When the TextView is measured, it uses the height tree to determine the first document line in the visible region. Then, it constructs and measures a VisualLine for that first line, and repeats that with the following lines until the visible region is filled.
The TextView caches VisualLines - when the user scrolls down, only the VisualLines coming into view are created, the rest is reused. The VisualLine cache can be manually invalidated using the Redraw method family; moreover, lots of actions cause automatic invalidation:
- any change in the document will invalidate the affected VisualLines
- changing the width of the TextView invalidates all VisualLines if word-wrap is enabled
- changing any text editor settings (word-wrap, font size, etc.) will invalidate all VisualLines
- VisualLines leaving the visible area after scrolling will be disposed
If external data used by your text editor extension changes, call TextView..::..Redraw()()()() to invalidate the VisualLine.
Invalidating VisualLines does not cause immediate recreation of the lines! Rather, the VisualLines are recreated when the text view is next re-measured. While measurement in WPF normally happens with DispatcherPriority.Render, the TextView also supports redrawing with a lower priority than that. For example, normal text insertion causes a redraw at background priority, so that in case the user types faster than the text view can redraw, there will be only one redraw for multiple input actions.
The TextView will never return invalid lines to you, but you may run into the case that the valid visual lines are not available.
What happens in this case depends on the method you are calling - the new visual line might get created automatically, null could be returned, or you may get a VisualLinesInvalidException.
You can call TextView..::..EnsureVisualLines()()()() to make the text view create all VisualLines in the visible region.
As a first step, the VisualLineElementGenerators are used to produce elements. The room in between the elements returned from the generators is filled with text elements. Then, the VisualLine assigns the VisualColumn and RelativeTextOffset properties of the line elements.
For example, a line contains the text "Hello, World". The user has enabled "ShowSpaces", so the text editor should show a little dot instead of the space. In this case, the SingleCharacterElementGenerator, which is responsible for ShowSpaces, will produce a "SpaceTextElement" for the space character. Because no other generators are interested in the line, the remaining strings "Hello," and "World" will be represented by VisualLineText elements.
After that, the IVisualLineTransformers are used to modify the produced line elements. Transformers must not add elements, but they may split existing elements, e.g. to colorize only parts of an element. When splitting elements (or somehow modifying the elements collection), care must be taken that the VisualColumn,VisualLine,RelativeTextOffset and DocumentLength properties stay correct.
The ColorizingTransformer base class provides helper methods for splitting, so the derived class can simply say "color this section in that color".
The DocumentColorizingTransformer extends the ColorizingTransformer and additionally allows highlighting on per DocumentLine, coloring text segments (instead of directly working with visual line elements).
After building the visual line elements, the TextLines for the visual line are constructed. A visual line may result in multiple text lines when word wrapping is active or when special visual line elements force a line break. Building text lines: The text line construction is done by a WPF TextFormatter. The VisualLineTextSource will take the visual line elements and build WPF TextRuns from it, while the WPF TextFormatter takes care of word wrapping etc. VisualLineElements are requested to produce TextRuns for their full or a partial length. The TextView will take care to measure any inline UI elements in the visual lines.
After the visible region is filled, the TextView updates the heights stored in the document lines to the measured heights. This way, scrolling takes account for word-wrapping. The constructed text lines are stored for the arrange and render steps. Now, finally, the measure step is complete.
The WPF arrange step doesn't have much work to do: It just arranges inline UI elements at their position inside the text.
The actual rendering does not happen directly in the TextView, but in its various layers.
These are the predefined layers:
- Background layer: renders the background colors associated with the visual elements
- Selection layer: renders the background of the selection
- Text layer: renders the TextLines that were constructed during the Measure step. Starting with AvalonEdit 4.1, the TextLayer uses child elements to draw the text: one DrawingVisual for each VisualLine.
- Immediately after the text layer, any inline UI elements are placed as if they were separate layers.
- Caret layer: renders a blinking caret