SharpDevelop Community

Get your problems solved!
Welcome to SharpDevelop Community Sign in | Join | Help
in Search

AvalonEdit Selection Formatting

Last post 10-11-2012 5:35 PM by orca989. 6 replies.
Page 1 of 1 (7 items)
Sort Posts: Previous Next
  • 09-03-2012 11:46 PM

    AvalonEdit Selection Formatting

    I'm trying to use AvalonEdit as a WPF RichTextBox replacement (for performance reasons) in an app that is, essentially, a glorified word processor.  As such, users will need to be able to maniuplate the text in a few basic ways as they would in a word processor: change the font for the entire document, change the font only for selected text, change selected text to bold/italic/underline, and change the font size.  

    After perusing the forums I've come across some interesting possibilities for allowing a user to change the FontStyle/FontWeight for their selected text, but nothing that specifically does just that or any of the other items above.  I thought about using hidden escape characters (VisualLineElement) that I place at the beginning and end of a text selection and then using a VisualLineTransformer and a VisualLineElementGenerator to find those escape characters and change the text to bold, underline, or a particular FontFamily but that doesn't seem like the best approach.

    So before I go about potentially reinventing the wheel, what would be the best way to allow a user to change selected text to be Bold, Italic, or Underlined, and/or change the FontFamily for that text?

    And if AvalonEdit is simply not the control for handling this type of task (though I doubt it) are there any recommendations for WPF RTB replacements whose performance is not terrible?

    Thanks in advance.

    Filed under: ,
  • 09-04-2012 8:44 PM In reply to

    Re: AvalonEdit Selection Formatting

    You can store the font style separately from the document; no need to use escape characters.

    For example, you could create a class derived from TextSegment that stores the font properties. Then, use a TextSegmentCollection<YourClass> to store the list of segments. The collection will automatically update the start/end offsets of the segments when text is inserted/removed in the document; and it allows efficient search for segments in a given range. (the underlying implementation is an interval tree)

    With that, you can use a VisualLineTransformer to apply the font properties to the line being generated.

    You will have to call textView.Redraw(...) to notify AvalonEdit of changes to the font styles so that the affected lines can be re-created.

  • 09-05-2012 10:14 PM In reply to

    Re: AvalonEdit Selection Formatting

    Thanks for the info Daniel.

    So I create a FontAttributesTextSegmentClass that contains a FontStyle, Weight, Size, etc.  Do I then instantiate one of those and add it to the TextSegmentCollection<MyClass> when the user clicks, say, the Bold button so that the next character they type is bold and everything after that will be bold until they deselect the Bold button?  

    I'm not entirely clear when to add a TextSegement to the collection.  I can set the TextRunProperties on the element in the elements collection in the VisualLineTransformer class, I get that part, I'm just a bit confused how the TextSegementCollection works in conjunction with a VLT?

  • 09-05-2012 10:37 PM In reply to

    Re: AvalonEdit Selection Formatting

    The TextSegmentCollection class just takes care of adjusting the start/end offsets, nothing more.

    Your VLT can use TextSegmentCollection.FindOverlappingSegments to retrieve all segments that are in the visible portion of the editor (or rather: within the line being transformed) and then copy the font style from your segment into the TextRunProperties.

    As for your typing scenario, that's not how the TextSegmentCollection works, it only adjusts the length of the segment when typing within the segment; so it never enlarges zero-length segments. However you could add your own TextDocument.Changed event handler to handle such insertions: just find all segments ending exactly at the insertion offset (you can use FindSegmentsContaining so that you don't have to search the whole collection), and increase their length. That way text segments get extended when typing at their end, which should produce the behavior expected in a rich text box.

    Edit: actually that wouldn't work with deselecting the bold button. But your use case isn't how Word behaves - the bold button there applies to the current selection, and to the current word if nothing is selected. For your case, you'd need an additional 'current format' state that determines the format of newly inserted text. And then insertions work differently as well.

    Thinking some more about it, maybe you could simply use an array to stores the FontAttributes for each character. (think List<FontAttributes>). When the user inserts text, you can insert new entries with the current font attributes. When the user removes text, just remove entries from the list. You might want to use a CompressingTreeList<T> in this case - it behaves mostly the same as a normal List<T>, but internally stores the data with a sort of run-length compression - so that many characters with identical formatting after each other don't take too much memory. It also has the GetStartOfRun()/GetEndOfRun() methods which are handy for determining the boundaries of an identically-formatted span - you can use this in the VLT so that you can use a single ChangeLinePart() call for each span instead of using one per character (a single call will be much more efficient).

  • 09-20-2012 11:04 PM In reply to

    Re: AvalonEdit Selection Formatting

    Sorry, but I've been away from this project for a bit.  Anyway, I got back to it last week, read through your comments in detail, started hacking away at what I thought you meant and at some other ideas I had, and have gotten exactly no where.  I have to admit that I had trouble following your thought processes regarding this, which is no fault of yours but a lack of understanding the architecture on my part, something I'm trying hard to rectify.

    For instance, how do I get TextSegments into the TextSegmentCollection?  I've been looking at the code folding code, which uses a TextSegmentCollection, and it looks as if those segments are being manually created in a DocumentChanged event.  Is that the route I should be exploring?  I've tried creating the TSC by passing in a Document but I never get any TextSegments in the collection.  And once I have them, I'm not entirely clear what that gets me.  I've seen the TextRunProperties that allow for setting of Font, FontWeight, etc. but I'm not sure how to get fome TextSegments, or some other aspect of the architecture, to being able to set the TextRunProperties.  This doesn't seem like it should be that hard but I'll be darned if I'm seeing how to do it.  I also wasn't clear on how you meant to use a CompressingTreeList<FontAttributes> with a VLT in order to achieve this effect.

    And just to clear up my use cases:

    1. User is typing and stops.  User clicks the Bold button and starts typing again.  All subsequent text should be FontWeight.Bold.  User stops typing, clicks Bold button.  All subsequent text is back to FontWeight.Normal.

    2. User highlights a selection and clicks the Bold button.  The selection should be changed to FontWeight.Bold.

    3. User places the caret over a word and clicks the Bold button.  The word the caret is over should change to FontWeight.Bold.

    The same use cases would apply to Font and FontStyle.

    Thanks a ton in advance, I appreciate any time you're willing to spend on this.  If I can get this working it'll be a huge benefit to the project, the WPF RTB's performance is just brutal.

  • 09-21-2012 12:37 AM In reply to

    Re: AvalonEdit Selection Formatting

    AvalonEdit is a code editor, it only keeps track of the raw document text, nothing else.

    So if you want to store additional formatting, you need to do so yourself, in your own data structures. I was merely suggesting AvalonEdit collection classes that you might use for that purpose. AvalonEdit won't add anything to those collections, and it also won't read from them - all the access to the formattings needs to be done by your code.

    The idea with the CompressionTreeList was that you store the font for each character. Index 0 in the list is for the first character in the document, index 1 for the second character, etc. CompressingTreeList acts exactly like the normal .NET List<T>, it just is more performant in cases where lots of successive elements are equal.

    Here is a simple visual line transformer that shows how to highlight a word:

    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
    }   }   }

    In your case, you'll need to read the font properties from your own data structure (e.g. the CompressingTreeList) and apply it to the visual line elements.
  • 10-11-2012 5:35 PM In reply to

    Re: AvalonEdit Selection Formatting

    Just wanted to let you know that I got the editor working quite well as a word processor, giving the user full control of the text appearance including Bold, Italic, Underline, Font and Font Size, all within the same line.  It works quite well, though once I get everything in place I plan to revisit my implementation as there's room for improvement -- perf drops a bit when the amount of text in the editor gets up over 1500 words, but I have an idea as to the cause.

    For the curious among you, I essentially implemented the suggestion by Daniel in his fourth post, using an array to store attribute information for each character and then using a DocumentColorizingTransformer to read that array and apply the attributes to the each element touched by the Transformer.  It works well and is far more performant than the RichTextBox control, though, as I said, my solution definitely needs some refinement.  Regardless, AvalonEdit is a viable replacement for the RTB.

    Thanks, Daniel, for the control and the pointers.

Page 1 of 1 (7 items)
Powered by Community Server (Commercial Edition), by Telligent Systems
Don't contact us via this (fleischfalle@alphasierrapapa.com) email address.