Browse Source
git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5045 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61shortcuts
5 changed files with 561 additions and 293 deletions
After Width: | Height: | Size: 31 KiB |
@ -0,0 +1,242 @@
@@ -0,0 +1,242 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> |
||||
<!------------------------------------------------------------> |
||||
<!-- INTRODUCTION |
||||
|
||||
The Code Project article submission template (HTML version) |
||||
|
||||
Using this template will help us post your article sooner. To use, just |
||||
follow the 3 easy steps below: |
||||
|
||||
1. Fill in the article description details |
||||
2. Add links to your images and downloads |
||||
3. Include the main article text |
||||
|
||||
That's all there is to it! All formatting will be done by our submission |
||||
scripts and style sheets. |
||||
|
||||
--> |
||||
<!------------------------------------------------------------> |
||||
<!-- IGNORE THIS SECTION --> |
||||
<html> |
||||
<head> |
||||
<title>Document</title> |
||||
<Style> |
||||
BODY, P, TD { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt } |
||||
H2,H3,H4,H5 { color: #ff9900; font-weight: bold; } |
||||
H2 { font-size: 13pt; } |
||||
H3 { font-size: 12pt; } |
||||
H4 { font-size: 10pt; color: black; } |
||||
PRE { BACKGROUND-COLOR: #FBEDBB; FONT-FAMILY: "Courier New", Courier, mono; WHITE-SPACE: pre; } |
||||
CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; } |
||||
</style> |
||||
<link rel="stylesheet" type="text/css" href="http://www.codeproject.com/App_Themes/NetCommunity/CodeProject.css"> |
||||
</head> |
||||
<body bgcolor="#FFFFFF" color=#000000> |
||||
<div style="width:600px; margin-left: 24px;"> |
||||
<!------------------------------------------------------------> |
||||
|
||||
|
||||
<!------------------------------------------------------------> |
||||
<!-- Fill in the details (CodeProject will reformat this section for you) --> |
||||
|
||||
|
||||
<!------------------------------------------------------------> |
||||
<!-- Include download and sample image information. --> |
||||
|
||||
For the sample application and source code download, please see the main article: |
||||
<a href="http://www.codeproject.com/KB/edit/AvalonEdit.aspx">Using AvalonEdit (WPF Text Editor)</a> |
||||
|
||||
<p><img src="Article.gif" alt="Sample Image - maximum width is 600 pixels" width=400 height=200></p> |
||||
|
||||
|
||||
<!------------------------------------------------------------> |
||||
|
||||
<!-- Add the article text. Please use simple formatting (<h2>, <p> etc) --> |
||||
|
||||
<h2>Introduction</h2> |
||||
|
||||
|
||||
|
||||
<h2>Using the Code</h2> |
||||
|
||||
|
||||
<!------------------------------------------------------------> |
||||
<h2>Document (The Text Model)</h2> |
||||
|
||||
<p>So, what is the model of a text editor that has support for complex features like syntax highlighting and folding?<br> |
||||
Would you expect to be able to access collapsed text using the document model, given that the text is folded away?<br> |
||||
Is the syntax highlighting part of the model? |
||||
|
||||
<p>In my quest for a good representation of the model, I decided on a radical strategy: |
||||
<b>if it's not a <code>char</code>, it's not in the model</b>! |
||||
|
||||
<p>The main class of the model is <code>ICSharpCode.AvalonEdit.Document.TextDocument</code>. |
||||
Basically, the document is a <code>StringBuilder</code> with events. |
||||
However, the <code>Document</code> namespace also contains several features that are useful to applications working with the text editor. |
||||
|
||||
<p>In the text editor, all three controls (<code>TextEditor</code>, <code>TextArea</code>, <code>TextView</code>) have a <code>Document</code> property pointing to the <code>TextDocument</code> instance. |
||||
You can change the <code>Document</code> property to bind the editor to another document; but please only do so on the outermost control (usually <code>TextEditor</code>), it will inform its child controls about that change. |
||||
Changing the document only on a child control would leave the outer controls confused. |
||||
|
||||
<p><i>Simplified</i> definition of <code>TextDocument</code>: |
||||
<pre lang="cs">public sealed class TextDocument : ITextSource |
||||
{ |
||||
public event EventHandler UpdateStarted; |
||||
public event EventHandler<DocumentChangeEventArgs> Changing; |
||||
public event EventHandler<DocumentChangeEventArgs> Changed; |
||||
public event EventHandler TextChanged; |
||||
public event EventHandler UpdateFinished; |
||||
|
||||
public TextAnchor CreateAnchor(int offset); |
||||
public ITextSource CreateSnapshot(); |
||||
|
||||
public IList<DocumentLine> Lines { get; } |
||||
public DocumentLine GetLineByNumber(int number); |
||||
public DocumentLine GetLineByOffset(int offset); |
||||
public TextLocation GetLocation(int offset); |
||||
public int GetOffset(int line, int column); |
||||
|
||||
public char GetCharAt(int offset); |
||||
public string GetText(int offset, int length); |
||||
|
||||
public void BeginUpdate(); |
||||
public bool IsInUpdate { get; } |
||||
public void EndUpdate(); |
||||
|
||||
public void Insert(int offset, string text); |
||||
public void Remove(int offset, int length); |
||||
public void Replace(int offset, int length, string text); |
||||
|
||||
public string Text { get; set; } |
||||
public int LineCount { get; } |
||||
public int TextLength { get; } |
||||
public UndoStack UndoStack { get; } |
||||
}</pre> |
||||
|
||||
<h3>Offsets</h3> |
||||
In AvalonEdit, an index into the document is called an <b>offset</b>. |
||||
|
||||
<p>Offsets usually represent the position between two characters. |
||||
The first offset at the start of the document is 0; the offset after the first <code>char</code> in the document is 1. |
||||
The last valid offset is <code>document.TextLength</code>, representing the end of the document. |
||||
|
||||
<p>This is exactly the same as the 'index' parameter used by methods in the .NET <code>String</code> or <code>StringBuilder</code> classes. |
||||
Offsets are used because they are dead simple. To all text between offset 10 and offset 30, |
||||
simply call <code>document.GetText(10, 20)</code> – just like <code>String.Substring</code>, AvalonEdit usually uses <code>Offset / Length</code> pairs to refer to text segments. |
||||
|
||||
<p>To easily pass such segments around, AvalonEdit defines the <code>ISegment</code> interface: |
||||
<pre lang="cs">public interface ISegment |
||||
{ |
||||
int Offset { get; } |
||||
int Length { get; } // must be non-negative |
||||
int EndOffset { get; } // must return Offset+Length |
||||
}</pre> |
||||
All <code>TextDocument</code> methods taking Offset/Length parameters also have an overload taking an <code>ISegment</code> instance – I have just removed those from the code listing above to make it easier to read. |
||||
|
||||
<h3>Lines</h3> |
||||
Offsets are easy to use, but sometimes you need Line / Column pairs instead. |
||||
AvalonEdit defines a <code>struct</code> called <code>TextLocation</code> for those. |
||||
|
||||
<p>The document provides the methods <code>GetLocation</code> and <code>GetOffset</code> to convert between offsets and <code>TextLocation</code>s. |
||||
Those are convenience methods built on top of the <code>DocumentLine</code> class. |
||||
|
||||
<p>The <code>TextDocument.Lines</code> collection contains one <code>DocumentLine</code> instance for every line in the document. |
||||
This collection is read-only to user code and is automatically updated to always<sup><small>*</small></sup> reflect the current document content. |
||||
|
||||
<p>Internally, the <code>DocumentLine</code> instances are arranged in a binary tree that allows for both efficient updates and lookup. |
||||
Converting between offset and line number is possible in O(lg N) time, and the data structure also updates all offsets in O(lg N) whenever text is inserted/removed. |
||||
|
||||
|
||||
<p><small>* tiny exception: it is possible to see the line collection in an inconsistent state inside <code>ILineTracker</code> callbacks. Don't use <code>ILineTracker</code> |
||||
unless you know what you are doing!</small> |
||||
|
||||
<h3>Change Events</h3> |
||||
|
||||
Here is the order in which events are raised during a document update: |
||||
|
||||
<p><b>BeginUpdate()</b> |
||||
<ul><li><code>UpdateStarted</code> event is raised</li></ul> |
||||
|
||||
<p><b>Insert() / Remove() / Replace()</b> |
||||
<ul> |
||||
<li><code>Changing</code> event is raised</li> |
||||
<li>The document is changed</li> |
||||
<li><code>TextAnchor.Deleted</code> events are raised if anchors were in the deleted text portion</li> |
||||
<li><code>Changed</code> event is raised</li> |
||||
</ul> |
||||
|
||||
<p><b>EndUpdate()</b> |
||||
<ul><li><code>TextChanged</code> event is raised</li> |
||||
<li><code>TextLengthChanged</code> event is raised</li> |
||||
<li><code>LineCountChanged</code> event is raised</li> |
||||
<li><code>UpdateFinished</code> event is raised</li></ul> |
||||
|
||||
<p>If the insert/remove/replace methods are called without a call to <code>BeginUpdate()</code>, they will call |
||||
<code>BeginUpdate()</code> and <code>EndUpdate()</code> to ensure no change happens outside of <code>UpdateStarted</code>/<code>UpdateFinished</code>. |
||||
|
||||
<p>There can be multiple document changes between the <code>BeginUpdate()</code> and <code>EndUpdate()</code> calls. |
||||
In this case, the events associated with <code>EndUpdate</code> will be raised only once after the whole document update is done. |
||||
|
||||
<p>The <code>UndoStack</code> listens to the <code>UpdateStarted</code> and <code>UpdateFinished</code> events to group |
||||
all changes into a single undo step. |
||||
|
||||
<h3>TextAnchor</h3> |
||||
If you are working with the text editor, you will likely run into the problem that you need to store an offset, but want it to adjust |
||||
automatically whenever text is inserted prior to that offset. |
||||
|
||||
<p>Sure, you could listen to the <code>TextDocument.Changed</code> event and call <code>GetNewOffset</code> on the <code>DocumentChangeEventArgs</code> to translate |
||||
the offset, but that gets tedious; especially when your object is short-lived and you have to deal with deregistering the event handler at the correct point of time.<br> |
||||
|
||||
<p>A much simpler solution is to use the <code>TextAnchor</code> class. Usage: |
||||
<pre lang="cs">TextAnchor anchor = document.CreateAnchor(offset); |
||||
ChangeMyDocument(); |
||||
int newOffset = anchor.Offset;</pre> |
||||
|
||||
<p>The document will automatically update all text anchors; and because it uses weak references to do so, the GC can simply collect the anchor object when you don't need it anymore. |
||||
|
||||
<p>Moreover, the document is able to efficiently update a large number of anchors without having to look at each anchor object individually. Updating the offsets of all anchors |
||||
usually only takes time logarithmic to the number of anchors. Retrieving the <code>TextAnchor.Offset</code> property also runs in O(lg N). |
||||
|
||||
<p>When a piece of text containing an anchor is removed; that anchor will be deleted. First, the <code>TextAnchor.IsDeleted</code> property is set to true on all deleted anchors, then the |
||||
<code>TextAnchor.Deleted</code> events are raised. You cannot retrieve the offset from an anchor that has been deleted. |
||||
|
||||
<p>This deletion behavior might be useful when using anchors for building a bookmark feature, but in other cases you want to still be able to use the anchor. For those cases, set <code>TextAnchor.SurviveDeletion = true</code>. |
||||
|
||||
<p>Note that anchor movement is ambiguous if text is inserted exactly at the anchor's location. Does the anchor stay before the inserted text, or does it move after it? |
||||
The property <code>TextAnchor.MovementType</code> will be used to determine which of these two options the anchor will choose. The default value is <code>AnchorMovementType.BeforeInsertion</code>. |
||||
|
||||
<p>If you want to track a segment, you can use the <code>AnchorSegment</code> class which implements <code>ISegment</code> using two text anchors. |
||||
|
||||
<h3>TextSegmentCollection</h3> |
||||
<p>Sometimes it is useful to store a list of segments and be able to efficiently find all segments overlapping with some other segment.<br> |
||||
Example: you might want to store a large number of compiler warnings and render squiggly underlines only for those that are in the visible region of the document. |
||||
|
||||
<p>The <code>TextSegmentCollection</code> serves this purpose. Connected to a document, it will automatically update the offsets of all <code>TextSegment</code> instances inside the collection; |
||||
but it also has the useful methods <code>FindOverlappingSegments</code> and <code>FindFirstSegmentWithStartAfter</code>. |
||||
The underlying data structure is a hybrid between the one used for text anchors and an <a href="http://en.wikipedia.org/wiki/Interval_tree#Augmented_tree">interval tree</a>, so it is able to do both jobs quite fast. |
||||
|
||||
<h3>Thread Safety</h3> |
||||
<p>The <code>TextDocument</code> class is not thread-safe. It expects to have a single owner thread and will throw an <code>InvalidOperationException</code> when accessed from another thread. |
||||
|
||||
<p>However, there is a single method that is thread-safe: <code>CreateSnapshot()</code><br> |
||||
It returns an immutable snapshot of the document, and may be safely called even when the owner thread is concurrently modifying the document. |
||||
This is very useful for features like a background parser that is running on its own thread. |
||||
The overload <code>CreateSnapshot(out ChangeTrackingCheckpoint)</code> also returns a <code>ChangeTrackingCheckpoint</code> for the document snapshot. |
||||
Once you have two checkpoints, you can call <code>GetChangesTo</code> to retrieve the complete list of document changes that happened between those versions of the document. |
||||
|
||||
|
||||
<h2>Points of Interest</h2> |
||||
|
||||
<p>Did you learn anything interesting/fun/annoying while writing the code? Did you |
||||
do anything particularly clever or wild or zany? |
||||
|
||||
<h2>History</h2> |
||||
|
||||
<p>Keep a running update of any changes or improvements you've made here. |
||||
|
||||
<p><b>Note: although my sample code is provided under the MIT license, ICSharpCode.AvalonEdit itself is provided under the terms of the GNU LGPL.</b> |
||||
|
||||
<!------------------------------- That's it! ---------------------------> |
||||
</div></body> |
||||
|
||||
</html> |
@ -0,0 +1,301 @@
@@ -0,0 +1,301 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> |
||||
<!------------------------------------------------------------> |
||||
<!-- INTRODUCTION |
||||
|
||||
The Code Project article submission template (HTML version) |
||||
|
||||
Using this template will help us post your article sooner. To use, just |
||||
follow the 3 easy steps below: |
||||
|
||||
1. Fill in the article description details |
||||
2. Add links to your images and downloads |
||||
3. Include the main article text |
||||
|
||||
That's all there is to it! All formatting will be done by our submission |
||||
scripts and style sheets. |
||||
|
||||
--> |
||||
<!------------------------------------------------------------> |
||||
<!-- IGNORE THIS SECTION --> |
||||
<html> |
||||
<head> |
||||
<title>Rendering</title> |
||||
<Style> |
||||
BODY, P, TD { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt } |
||||
H2,H3,H4,H5 { color: #ff9900; font-weight: bold; } |
||||
H2 { font-size: 13pt; } |
||||
H3 { font-size: 12pt; } |
||||
H4 { font-size: 10pt; color: black; } |
||||
PRE { BACKGROUND-COLOR: #FBEDBB; FONT-FAMILY: "Courier New", Courier, mono; WHITE-SPACE: pre; } |
||||
CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; } |
||||
</style> |
||||
<link rel="stylesheet" type="text/css" href="http://www.codeproject.com/App_Themes/NetCommunity/CodeProject.css"> |
||||
</head> |
||||
<body bgcolor="#FFFFFF" color=#000000> |
||||
<div style="width:600px; margin-left: 24px;"> |
||||
<!------------------------------------------------------------> |
||||
|
||||
|
||||
<!------------------------------------------------------------> |
||||
<!-- Fill in the details (CodeProject will reformat this section for you) --> |
||||
|
||||
|
||||
<!------------------------------------------------------------> |
||||
<!-- Include download and sample image information. --> |
||||
|
||||
For the sample application and source code download, please see the main article: |
||||
<a href="http://www.codeproject.com/KB/edit/AvalonEdit.aspx">Using AvalonEdit (WPF Text Editor)</a> |
||||
|
||||
<p><img src="Article.gif" alt="Sample Image - maximum width is 600 pixels" width=400 height=200></p> |
||||
|
||||
|
||||
<!------------------------------------------------------------> |
||||
|
||||
<!-- Add the article text. Please use simple formatting (<h2>, <p> etc) --> |
||||
|
||||
<h2>Introduction</h2> |
||||
|
||||
<h2>Using the Code</h2> |
||||
|
||||
|
||||
<!------------------------------------------------------------> |
||||
<h2>Rendering</h2> |
||||
|
||||
Noticed how through the whole 'Document' section, there was no mention of extensibility? |
||||
The text rendering infrastructure now has to compensate for that by being completely extensible. |
||||
|
||||
<p>The <code>ICSharpCode.AvalonEdit.Rendering.TextView</code> class is the heart of AvalonEdit. |
||||
It takes care of getting the document onto the screen. |
||||
|
||||
<p>To do this in an extensible way, the <code>TextView</code> uses its own kind of model: the <code>VisualLine</code>. |
||||
Visual lines are created only for the visible part of the document. |
||||
<p>The rendering process looks like this:<br> |
||||
<img src="AvalonEdit/renderingPipeline.png" alt="rendering pipeline"><br> |
||||
The last step in the pipeline is the conversion to one or more <code>System.Windows.Media.TextFormatting.TextLine</code> instances. WPF then takes care of the actual text rendering. |
||||
|
||||
<h3>Lifetime of visual lines</h3> |
||||
When the <code>TextView</code> needs to construct visual lines (usually before rendering), it first |
||||
determines which <code>DocumentLine</code> is the top-most visible line in the currently viewed region. |
||||
From there, it starts to build visual lines and also immediately does the conversion to <code>TextLine</code> (word-wrapping). |
||||
The process stops once the viewed document region is filled. |
||||
<p> |
||||
The resulting visual lines (and <code>TextLine</code>s) will be cached and reused in future rendering passes. |
||||
When the user scrolls down, only the visual lines coming into view are created, the rest is reused. |
||||
<p> |
||||
The <code>TextView.Redraw</code> methods are used to remove visual lines from the cache. |
||||
AvalonEdit will redraw automatically on the affected lines when the document is changed; and will invalidate the whole cache |
||||
when any editor options are changed. You will only have to call <code>Redraw</code> manually if you write extensions to the visual line creation process |
||||
that maintain their own data source. For example, the <code>FoldingManager</code> invokes <code>Redraw</code> whenever text sections are expanded or collapsed. |
||||
<p> |
||||
Calling <code>Redraw</code> does not cause immediate recreation of the lines. |
||||
They are just removed from the cache so that the next rendering step will recreate them. |
||||
All redraw methods will enqueue a new rendering step, using the WPF Dispatcher with a low priority. |
||||
|
||||
<h3>Elements inside visual line</h3> |
||||
A visual line consists of a series of elements. These have both a <code>DocumentLength</code> measured in characters as well as a logical length called <code>VisualLength</code>. |
||||
For normal text elements, the two lengths are identical; but some elements like fold markers may have a huge document length, yet a logical length of 1. |
||||
On the other hand, some elements that are simply inserted by element generators may have a document length of 0, but still need a logical length of at least 1 to allow |
||||
addressing elements inside the visual line. |
||||
<p> |
||||
The <code>VisualColumn</code> is a position inside a visual line as measured by the logical length of elements. It is counted starting from 0 at the begin of the visual line.<br> |
||||
Also, inside visual lines, instead of normal offsets to the text document; relative offsets are used.<br> |
||||
<code>Absolute offset = relative offset + VisualLine.FirstDocumentLine.Offset</code><br> |
||||
This means that offsets inside the visual line do not have to be adjusted when text is inserted or removed in front of the visual line; we simply rely on the document |
||||
automatically updating <code>DocumentLine.Offset</code>. |
||||
<p> |
||||
The main job of a visual line element is to implement the <code>CreateTextRun</code> method. |
||||
This method should return a <code>System.Windows.Media.TextFormatting.TextRun</code> instance that can be rendered using the <code>TextLine</code> class. |
||||
<p> |
||||
Visual line elements can also handle mouse clicks and control how the caret should move. The mouse click handling might suffice as a light-weight alternative |
||||
to embedding inline <code>UIElement</code>s in the visual lines. |
||||
|
||||
<h3>Element Generators</h3> |
||||
|
||||
You can extend the text view by registering a custom class deriving from <code>VisualLineElementGenerator</code> in the <code>TextView.ElementGenerators</code> collection. |
||||
This allows you to add custom <code>VisualLineElements</code>. |
||||
Using the <code>InlineObjectElement</code> class, you can even put interactive WPF controls (anything derived from <code>UIElement</code>) into the text document. |
||||
<p> |
||||
For all document text not consumed by element generators, AvalonEdit will create <code>VisualLineText</code> elements. |
||||
<p> |
||||
Usually, the construction of the visual line will stop at the end of the <code>DocumentLine</code>. However, if some <code>VisualLineElementGenerator</code> |
||||
creates an element that's longer than the rest of the line, construction of the visual line may resume in another <code>DocumentLine</code>. |
||||
Currently, only the <code>FoldingElementGenerator</code> can cause one visual line to span multiple <code>DocumentLine</code>s. |
||||
<p> |
||||
<img src="AvalonEdit/folding.png" alt="Screenshot Folding and ImageElementGenerator"> |
||||
<p> |
||||
Here is the full source code for a class that implements embedding images into AvalonEdit: |
||||
<pre lang="cs">public class ImageElementGenerator : VisualLineElementGenerator |
||||
{ |
||||
readonly static Regex imageRegex = new Regex(@"<img src=""([\.\/\w\d]+)""/?>", |
||||
RegexOptions.IgnoreCase); |
||||
readonly string basePath; |
||||
|
||||
public ImageElementGenerator(string basePath) |
||||
{ |
||||
if (basePath == null) |
||||
throw new ArgumentNullException("basePath"); |
||||
this.basePath = basePath; |
||||
} |
||||
|
||||
Match FindMatch(int startOffset) |
||||
{ |
||||
// fetch the end offset of the VisualLine being generated |
||||
int endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; |
||||
TextDocument document = CurrentContext.Document; |
||||
string relevantText = document.GetText(startOffset, endOffset - startOffset); |
||||
return imageRegex.Match(relevantText); |
||||
} |
||||
|
||||
/// Gets the first offset >= startOffset where the generator wants to construct |
||||
/// an element. |
||||
/// Return -1 to signal no interest. |
||||
public override int GetFirstInterestedOffset(int startOffset) |
||||
{ |
||||
Match m = FindMatch(startOffset); |
||||
return m.Success ? (startOffset + m.Index) : -1; |
||||
} |
||||
|
||||
/// Constructs an element at the specified offset. |
||||
/// May return null if no element should be constructed. |
||||
public override VisualLineElement ConstructElement(int offset) |
||||
{ |
||||
Match m = FindMatch(offset); |
||||
// check whether there's a match exactly at offset |
||||
if (m.Success && m.Index == 0) { |
||||
BitmapImage bitmap = LoadBitmap(m.Groups[1].Value); |
||||
if (bitmap != null) { |
||||
Image image = new Image(); |
||||
image.Source = bitmap; |
||||
image.Width = bitmap.PixelWidth; |
||||
image.Height = bitmap.PixelHeight; |
||||
// Pass the length of the match to the 'documentLength' parameter |
||||
// of InlineObjectElement. |
||||
return new InlineObjectElement(m.Length, image); |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
BitmapImage LoadBitmap(string fileName) |
||||
{ |
||||
// TODO: add some kind of cache to avoid reloading the image whenever the |
||||
// VisualLine is reconstructed |
||||
try { |
||||
string fullFileName = Path.Combine(basePath, fileName); |
||||
if (File.Exists(fullFileName)) { |
||||
BitmapImage bitmap = new BitmapImage(new Uri(fullFileName)); |
||||
bitmap.Freeze(); |
||||
return bitmap; |
||||
} |
||||
} catch (ArgumentException) { |
||||
// invalid filename syntax |
||||
} catch (IOException) { |
||||
// other IO error |
||||
} |
||||
return null; |
||||
} |
||||
}</pre> |
||||
|
||||
<h3>Line Transformers</h3> |
||||
|
||||
Line transformers can modify the visual lines after they have been generated. The main usage of this is to colorize the text, |
||||
as done both by syntax highlighting and the selection. |
||||
<p> |
||||
The base classes <code>ColorizingTransformer</code> and <code>DocumentColorizingTransformer</code> help with this task |
||||
by providing helper methods for colorizing that split up visual line elements where necessary. The difference between |
||||
the two classes is that one works using visual columns whereas the other one uses offsets into the document. |
||||
<p> |
||||
Here is an example <code>DocumentColorizingTransformer</code> that highlights the word 'AvalonEdit' using bold font: |
||||
<pre lang="cs">public class ColorizeAvalonEdit : DocumentColorizingTransformer |
||||
{ |
||||
protected override void ColorizeLine(DocumentLine line) |
||||
{ |
||||
int lineStartOffset = line.Offset; |
||||
string text = CurrentContext.Document.GetText(line); |
||||
int start = 0; |
||||
int index; |
||||
while ((index = text.IndexOf("AvalonEdit", start)) >= 0) { |
||||
base.ChangeLinePart( |
||||
lineStartOffset + index, // startOffset |
||||
lineStartOffset + index + 10, // endOffset |
||||
(VisualLineElement element) => { |
||||
// This lambda gets called once for every VisualLineElement |
||||
// between the specified offsets. |
||||
Typeface tf = element.TextRunProperties.Typeface; |
||||
// Replace the typeface with a modified version of |
||||
// the same typeface |
||||
element.TextRunProperties.SetTypeface(new Typeface( |
||||
tf.FontFamily, |
||||
FontStyles.Italic, |
||||
FontWeights.Bold, |
||||
tf.Stretch |
||||
)); |
||||
}); |
||||
start = index + 1; // search for next occurrence |
||||
} } }</pre> |
||||
|
||||
<h3>Background renderers</h3> |
||||
|
||||
Background renderers are simple objects that allow you to draw anything in the text view. |
||||
They can be used to draw nice-looking backgrounds behind the text. |
||||
<p> |
||||
AvalonEdit contains the class <code>BackgroundGeometryBuilder</code> that helps with this task. |
||||
You can use the static <code>BackgroundGeometryBuilder.GetRectsForSegment</code> to fetch a list of rectangles that |
||||
contain text from the specified segment (you will get one rectangle per <code>TextLine</code>); |
||||
or you can use the instance methods to build a <code>PathGeometry</code> for the text's outline. |
||||
AvalonEdit also internally uses this geometry builder to create the selection with the rounded corners. |
||||
<p> |
||||
Inside SharpDevelop, the first option (getting list of rectangles) is used to render the squiggly red line that for compiler errors, |
||||
while the second option is used to produce nice-looking breakpoint markers. |
||||
|
||||
<h2>Editing</h2> |
||||
|
||||
The <code>TextArea</code> class is handling user input and executing the appropriate actions. |
||||
Both the caret and the selection are controlled by the <code>TextArea</code>. |
||||
<p> |
||||
You can customize the text area by modifying the <code>TextArea.DefaultInputHandler</code> by adding new or replacing existing |
||||
WPF input bindings in it. You can also set <code>TextArea.ActiveInputHandler</code> to something different than the default |
||||
to switch the text area into another mode. You could use this to implement an "incremental search" feature, or even a VI emulator. |
||||
<p> |
||||
The text area has the useful <code>LeftMargins</code> property - use it to add controls to the left of the text view that look like |
||||
they're inside the scroll viewer, but don't actually scroll. The <code>AbstractMargin</code> base class contains some useful code |
||||
to detect when the margin is attached/detaching from a text view; or when the active document changes. However, you're not forced to use it; |
||||
any <code>UIElement</code> can be used as margin. |
||||
|
||||
<h2>Folding</h2> |
||||
Folding (code collapsing) could be implemented as an extension to the editor without having to modify the AvalonEdit code. |
||||
A <code>VisualLineElementGenerator</code> takes care of the collapsed sections in the text document; and a custom margin draws the plus and minus |
||||
buttons. |
||||
<p> |
||||
That's exactly how folding is implemented in AvalonEdit. However, to make it a bit easier to use; the static <code>FoldingManager.Install</code> |
||||
method will create and register the necessary parts automatically. |
||||
<p> |
||||
All that's left for you is to regularly call <code>FoldingManager.UpdateFoldings</code> with the list of foldings you want to provide. |
||||
<p> |
||||
Here is the full code required to enable folding: |
||||
<pre lang="cs">foldingManager = FoldingManager.Install(textEditor.TextArea); |
||||
foldingStrategy = new XmlFoldingStrategy(); |
||||
foldingStrategy.UpdateFoldings(foldingManager, textEditor.Document);</pre> |
||||
If you want the folding markers to update when the text is changed, you have to repeat the <code>foldingStrategy.UpdateFoldings</code> call regularly. |
||||
<p> |
||||
The sample application to this article also contains the <code>BraceFoldingStrategy</code> that folds using { and }. |
||||
However, it is a very simple implementation and does not handle { and } inside strings or comments correctly. |
||||
|
||||
<h2>Syntax highlighting</h2> |
||||
TODO: write this section |
||||
|
||||
<h2>Points of Interest</h2> |
||||
|
||||
<p>Did you learn anything interesting/fun/annoying while writing the code? Did you |
||||
do anything particularly clever or wild or zany? |
||||
|
||||
<h2>History</h2> |
||||
|
||||
<p>Keep a running update of any changes or improvements you've made here. |
||||
|
||||
<p><b>Note: although my sample code is provided under the MIT license, ICSharpCode.AvalonEdit itself is provided under the terms of the GNU LGPL.</b> |
||||
|
||||
<!------------------------------- That's it! ---------------------------> |
||||
</div></body> |
||||
|
||||
</html> |
Loading…
Reference in new issue