Welcome to MSDN Blogs Sign in | Join | Help

WPF Blog Writer by Lester

This past winter, I had coded a custom control which has a RichTextBox with toolbar for various formatting commands. I had hoped to publish it as a a standalone control sample which people could reuse, but it needed some styling to make the control look prettier.

Lester has done a great job with that code and finished it up as a nice blog writer sample. Check out his post.

Posted by Prajakta Joshi | 1 Comments
Filed under:

Dave Relyea's Silverlight Layout Controls

One of the topmost questions about Silverlight has been the lack of layout controls and TextBox. Dave Relyea has put together a great set of sample layout controls for Silvelight in C#. The controls in his project are:

  • Layout framework
  • LayoutControl and LayoutContainerControl base control classes
  • Border
  • StackPanel
  • Grid
  • Label
  • Button (XAML courtesy of Mike Harsh)
  • TextBox

Check out Dave's post for more information on this. This is very cool indeed.

Back from the Silverlight cave

It is time to catch up with blogging after a long silence from my end. Earlier this year, I moved to work on WPF/e aka Silverlight team. It has been an exciting time and now that the cat is out of the bag, I am looking forward to blogging some fun Silverlight samples. Stay tuned.

For a good overview of what Silverlight can offer you, I recommend these sites.

Scott Guthrie's blog http://weblogs.asp.net/scottgu/archive/2007/05/07/silverlight.aspx
Watch the Silverlight sessions on http://sessions.visitmix.com/ 
Play with samples at http://silverlight.net/

Posted by Prajakta Joshi | 0 Comments
Filed under: ,

Printing Contents of WPF RichTextBox

I was recently playing with code to print contents of WPF RichTextBox control. The SDK has a code sample on this at http://msdn2.microsoft.com/en-us/library/aa970917.aspx

            // Print RichTextBox content

            private void PrintCommand()

            {

                PrintDialog pd = new PrintDialog();

                if ((pd.ShowDialog() == true))

                {

                    //use either one of the below     

                    pd.PrintVisual(richTB as Visual, "printing as visual");

                    pd.PrintDocument((((IDocumentPaginatorSource)richTB.Document).DocumentPaginator), "printing as paginator");

                }

            }

Using PrintDialog.PrintDocument() API as recommended in this sample does not produce good print output. The main reason being that page margins are not set correctly. The DocumentPaginator class does expose a PageSize property. But it does not have a margin property equivalent.

After little bit of poking around, I have finally found something that works. But it is very hard to discover to say the least. The below code sample shows how to use the XpsDocumentWriter class and its synchronous Write() API which accepts a DocumentPaginator object.

            // Serialize RichTextBox content into a stream in Xaml or XamlPackage format. (Note: XamlPackage format isn't supported in partial trust.)

            TextRange sourceDocument = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);

            MemoryStream stream = new MemoryStream();

            sourceDocument.Save(stream, DataFormats.Xaml);

 

            // Clone the source document's content into a new FlowDocument.

            FlowDocument flowDocumentCopy = new FlowDocument();

            TextRange copyDocumentRange = new TextRange(flowDocumentCopy.ContentStart, flowDocumentCopy.ContentEnd);

            copyDocumentRange.Load(stream, DataFormats.Xaml);

 

            // Create a XpsDocumentWriter object, open a Windows common print dialog.

            // This methods returns a ref parameter that represents information about the dimensions of the printer media.

            PrintDocumentImageableArea ia = null;

            XpsDocumentWriter docWriter = PrintQueue.CreateXpsDocumentWriter(ref ia);

 

            if (docWriter != null && ia != null)

            {

                DocumentPaginator paginator = ((IDocumentPaginatorSource)flowDocumentCopy).DocumentPaginator;

 

                // Change the PageSize and PagePadding for the document to match the CanvasSize for the printer device.

                paginator.PageSize = new Size(ia.MediaSizeWidth, ia.MediaSizeHeight);

                Thickness pagePadding = flowDocumentCopy.PagePadding;

                flowDocumentCopy.PagePadding = new Thickness(

                        Math.Max(ia.OriginWidth, pagePadding.Left),

                        Math.Max(ia.OriginHeight, pagePadding.Top),

                        Math.Max(ia.MediaSizeWidth - (ia.OriginWidth + ia.ExtentWidth), pagePadding.Right),

                        Math.Max(ia.MediaSizeHeight - (ia.OriginHeight + ia.ExtentHeight), pagePadding.Bottom));

                flowDocumentCopy.ColumnWidth = double.PositiveInfinity;

 

                // Send DocumentPaginator to the printer.

                docWriter.Write(paginator);

            }

You might wonder why I need to copy the original RichTextBox's FlowDocument and then send the cloned FlowDocument to XpsDocumentWriter. This is necessary because there is no way to support simultaneous editing and printing of the original FlowDocument in RichTextBox. Without confirming what is happening behind the scene here (so take this with a grain of salt), during the print operation XpsDocumentWriter needs to paginate through the content.  This means you need to suspend RichTextBox's layout update until the print is complete. Of course, the RichTextBox needs to be UI responsive during printing, so this is not an option.

(Note that it does not matter whether the printing is done using the synchronous Write() method or asynchronous WriteAsync() method on XpsDocumentWriter).

Posted by Prajakta Joshi | 5 Comments
Filed under:

Upcoming WPF Technical Chat

The WPF team will be hosting our first ever live online technical chat on the MSDN site next Thursday, December 21st, at 12:30pm Pacific Time (that's 3:30pm ET, 8:30pm GMT, 9:30pm CET). We'll have a collection of the WPF team on hand to answer your technical questions, hear your feedback on what we should be doing in our next release, share great tips and tricks for using WPF, and just generally connecting with one another.

I have to attend another campus event that day which I cannot miss. So unfortunately I cannot participate in this chat. Hope you can make the best use of this opportunity though, get all your WPF questions ready!

Posted by Prajakta Joshi | 0 Comments
Filed under:

Auto-detecting Hyperlinks in RichTextBox - Part II

In my previous post, we looked at code that auto-formats hyperlink strings while typing. The remaining part was to scan words when content is pasted in RichTextBox to auto-format any hyperlink strings.

The approach to do this is quite straight forward as you can imagine. Listen to DataObject.Pasting event on RichTextBox, set a flag to remember the paste operation. Later, in the handler for TextChanged event (this would be fired after paste is handled by base RTB), scan for any matching words. The code in this solution builds upon my earlier post of navigating words in RichTextBox.

The main challenge in this problem is how to track the start and end of pasted content. Since TextChanged event does not supply any information about what content changed, I use a simple trick here. In the DataObject.Pasting event handler, I remember two TextPointer positions.

        /// <summary>

        /// Event handler for DataObject.Pasting event on this RichTextBox.

        /// </summary>

        private void DataObjectPastingEventHandler(object sender, DataObjectPastingEventArgs e)

        {

            this.wordsAddedFlag = true;

            this.selectionStartPosition = this.Selection.Start;

            this.selectionEndPosition = this.Selection.IsEmpty ?

                this.Selection.End.GetPositionAtOffset(0, LogicalDirection.Forward) :

                this.Selection.End;

 

            // We don't handle the event here. Let the base RTB handle the paste operation.

            // This will raise a TextChanged event, which we handle below to scan for any matching hyperlinks.

        }

The gravities for both positions are subtly important. For non-empty selection, its start and end have backward and forward gravities respectively. But when selection is empty (you just have a caret), start and end are collapsed to a single TextPointer instance with backward gravity. In this case, we normalize end position with forward gravity, so that end TextPointer sticks to content following its position. This is important, because during paste, stuff is going to be inserted between selection start and end positions. So start and end must have backward and forward gravities respectively to cover pasted content.

OK, so much for that gravities business! This simple trick does the job. In TextChanged event handler, we scan for hyperlinks auto-formatting them.  

        /// <summary>

        /// Event handler for RichTextBox.TextChanged event.

        /// </summary>

        private void TextChangedEventHandler(object sender, TextChangedEventArgs e)

        {

            if (!this.wordsAddedFlag || this.Document == null)

            {

                return;

            }

 

            // Temporarily disable TextChanged event handler, since following code might insert Hyperlinks,

            // which will raise another TextChanged event.

            this.TextChanged -= this.TextChangedEventHandler;

 

            TextPointer navigator = this.selectionStartPosition;

            while (navigator != null && navigator.CompareTo(this.selectionEndPosition) <= 0)

            {

                TextRange wordRange = WordBreaker.GetWordRange(navigator);

                if (wordRange == null || wordRange.IsEmpty)

                {

                    // No more words in the document.

                    break;

                }

 

                string wordText = wordRange.Text;

                if (wordText == "www.microsoft.com" &&

                    !HyperlinkHelper.IsInHyperlinkScope(wordRange.Start) &&

                    !HyperlinkHelper.IsInHyperlinkScope(wordRange.End))

                {

                    Hyperlink hyperlink = new Hyperlink(wordRange.Start, wordRange.End);

                    navigator = hyperlink.ElementEnd.GetNextInsertionPosition(LogicalDirection.Forward);

                }

                else

                {

                    navigator = wordRange.End.GetNextInsertionPosition(LogicalDirection.Forward);

                }

            }

 

            this.TextChanged += this.TextChangedEventHandler;

            this.wordsAddedFlag = false;

            this.selectionStartPosition = null;

            this.selectionEndPosition = null;

        }

 

This is all it takes to detect hyperlinks on paste. I have attached full code to this post.

Replying to Messages

I received a few questions from my blog readers in email. I will try my best to answer them in this post.

Question 1: Hi there! I saw that you were posting about the use of RichTextBox control and I thought you might be able to help me on my problem: how can I get the current line number and the current character position when using the RichTextBox control? The TextBox control has some methods for this. But for the RichTextBox, I can't seem to find them. Hope you can help. Thanks.

While the TextBox's content is plain text only, the RichTextBox contains a FlowDocument that has rich flow content elements (see http://windowssdk.msdn.microsoft.com/en-gb/library/ms748443.aspx and my previous post on FlowDocument schema). RichTextBox doesn't expose API to get a character or line index, but you can construct this information using some code.

How to get character index?

You can get the current TextPointer position by using RichTextBox.GetPositionFromPoint method. This method returns a TextPointer insertion position closest to the supplied point. Once you have this position, you can keep calling TextPointer.GetNextInsertionPosition(LogicalDirection.Backward) in a loop until it returns null, counting caret positions from current character until the document start.

How to get line index?

You can keep calling TextPointer.GetLineStartPosition (-1) in a loop until you reach the first line, counting lines.

Question 2: Your implementation of the XPath Visualizer is most interesting. Does your XPath Visualizer now have support for XPath 2.0 ? Thank you.

I believe you are referring to my MSDN article at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnxmlnet/html/xpthviewer.asp. Thanks for your feedback. At this moment, I don't have any plans to update this code for XPath 2.0. (Though, that may be a very interesting WPF project!)

You should check out the XML team's current blog at http://blogs.msdn.com/xmlteam/. One recent post talks about an XPathMania plug-in to VS2005 xml editor. That should be really cool!

Question 3: I'm trying to build an Inline that draws itself like a square root with the text inside it. The thing is that I can't find the way to change the look of an inline, or anything that gets me to my purpose. Can you help? Thanks.

If you want to build something that also edits such Inlines, we don't support that in V1. If your content is read-only, you might want to try the WPF Text team's blog http://blogs.msdn.com/text/ or post your question on the WPF forum.

Posted by Prajakta Joshi | 1 Comments
Filed under:

Navigate Words in RichTextBox

A commonly asked question on the WPF forum is - how can I navigate RichTextBox's content word by word (see http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=778441&SiteID=1). In today's post, we will see how this can be done using the TextPointer API.

What we need is a helper function which takes a TextPointer position and returns us the word start and word end position. It is a good idea to do a mini-spec for this function's behavior. What happens when the position passed is between words or at a word edge?

  • If the TextPointer is within a word or at start of a word boundary, the containing word will be returned.
  • If the TextPointer is between two words, the following (next) word will be returned.
  • If the TextPointer is at trailing word boundary, the following (next) word will be returned.

It is convenient to return the word start and end positions as a TextRange object. Also, our implementation of GetWordRange will return the range covering just the word, excluding any extra trailing whitespaces.

Disclaimer: Before, you dive into the code below, keep in mind that this version is intended for en-us locale only. This assumption greatly simplifies our word-break heuristic (simply use the whitespace character). In reality, any globalized application will need to handle many more sophisticated word-break cases for Korean, Thai, Middle-East and other languages. [May be in WPF v.next, we will implement such an API supporting globalization.]

Most of the code below is well-commented. Please post comments if you have questions!

using System;

using System.Windows;

using System.Windows.Documents;

 

namespace HyperlinkAutoDetect

{

    public static class WordBreaker

    {

        /// <summary>

        /// Returns a TextRange covering a word containing or following this TextPointer.

        /// </summary>

        /// <remarks>

        /// If this TextPointer is within a word or at start of word, the containing word range is returned.

        /// If this TextPointer is between two words, the following word range is returned.

        /// If this TextPointer is at trailing word boundary, the following word range is returned.

        /// </remarks>

        public static TextRange GetWordRange(TextPointer position)

        {

            TextRange wordRange = null;

            TextPointer wordStartPosition = null;

            TextPointer wordEndPosition = null;

 

            // Go forward first, to find word end position.

            wordEndPosition = GetPositionAtWordBoundary(position, /*wordBreakDirection*/LogicalDirection.Forward);

 

            if (wordEndPosition != null)

            {

                // Then travel backwards, to find word start position.

                wordStartPosition = GetPositionAtWordBoundary(wordEndPosition, /*wordBreakDirection*/LogicalDirection.Backward);

            }

 

            if (wordStartPosition != null && wordEndPosition != null)

            {

                wordRange = new TextRange(wordStartPosition, wordEndPosition);

            }

 

            return wordRange;

        }

 

        /// <summary>

        /// 1.  When wordBreakDirection = Forward, returns a position at the end of the word,

        ///     i.e. a position with a wordBreak character (space) following it.

        /// 2.  When wordBreakDirection = Backward, returns a position at the start of the word,

        ///     i.e. a position with a wordBreak character (space) preceeding it.

        /// 3.  Returns null when there is no workbreak in the requested direction.

        /// </summary>

        private static TextPointer GetPositionAtWordBoundary(TextPointer position, LogicalDirection wordBreakDirection)

        {

            if (!position.IsAtInsertionPosition)

            {

                position = position.GetInsertionPosition(wordBreakDirection);

            }

 

            TextPointer navigator = position;

            while (navigator != null && !IsPositionNextToWordBreak(navigator, wordBreakDirection))

            {

                navigator = navigator.GetNextInsertionPosition(wordBreakDirection);

            }

 

            return navigator;

        }

 

        // Helper for GetPositionAtWordBoundary.

        // Returns true when passed TextPointer is next to a wordBreak in requested direction.

        private static bool IsPositionNextToWordBreak(TextPointer position, LogicalDirection wordBreakDirection)

        {

            bool isAtWordBoundary = false;

 

            // Skip over any formatting.

            if (position.GetPointerContext(wordBreakDirection) != TextPointerContext.Text)

            {

                position = position.GetInsertionPosition(wordBreakDirection);

            }

 

            if (position.GetPointerContext(wordBreakDirection) == TextPointerContext.Text)

            {

                LogicalDirection oppositeDirection = (wordBreakDirection == LogicalDirection.Forward) ?

                    LogicalDirection.Backward : LogicalDirection.Forward;

 

                char[] runBuffer = new char[1];

                char[] oppositeRunBuffer = new char[1];

 

                position.GetTextInRun(wordBreakDirection, runBuffer, /*startIndex*/0, /*count*/1);

                position.GetTextInRun(oppositeDirection, oppositeRunBuffer, /*startIndex*/0, /*count*/1);

 

                if (runBuffer[0] == ' ' && !(oppositeRunBuffer[0] == ' '))

                {

                    isAtWordBoundary = true;

                }

            }

            else

            {

                // If we're not adjacent to text then we always want to consider this position a "word break". 

                // In practice, we're most likely next to an embedded object or a block boundary.

                isAtWordBoundary = true;

            }

 

            return isAtWordBoundary;

        }

    }

}

Posted by Prajakta Joshi | 1 Comments
Filed under:

Auto-detecting Hyperlinks in RichTextBox - Part I

In this post, we will see a custom RichTextBox implementation that auto-detects Hyperlinks while typing. In a later post, I will demonstrate how auto-detection can be done on paste command. Both these parts together enable complete auto-detect support for hyperlinks in a RichTextBox.

For simplicity, in this demo, my code detects the string www.microsoft.com as a hyperlink. A real app would need a more sophisticated RegEx match expression. Here is a snapshot of the result.

For auto-detection while typing, there are 2 tasks. First is auto-creating hyperlink element on space and enter keys. Second is auto-deleting it on backspace key. For this, we intercept KeyDown events on the RichTextBox. Note that we dont set e.Handled to true, since we want the base RichTextBox to do the work of inserting/deleting the space character/enter break.

    /// <summary>

    /// Custom RichTextBox class that performs hyperlink auto-detection.

    /// </summary>

    public class MyRichTextBox : RichTextBox

    {

        // Ctor.

        static MyRichTextBox()

        {

            // KeyDown event.

            EventManager.RegisterClassHandler(typeof(MyRichTextBox), KeyDownEvent, new KeyEventHandler(OnKeyDown), /*handledEventsToo*/true);

        }

When user enters space or enter key, we want to check preceeding text. If it matches a hyperlink, we want to insert a hyperlink tag around it. 

            TextPointer caretPosition = myRichTextBox.Selection.Start;

            if (e.Key == Key.Space || e.Key == Key.Return)

            {

                TextPointer wordStartPosition;

                string word = GetPreceedingWordInParagraph(caretPosition, out wordStartPosition);

 

                if (word == "www.microsoft.com") // A real app would need a more sophisticated RegEx match expression for hyperlinks.

                {

                    // Insert hyperlink element at word boundaries.

                    new Hyperlink(

                        wordStartPosition.GetPositionAtOffset(0, LogicalDirection.Backward),

                        caretPosition.GetPositionAtOffset(0, LogicalDirection.Forward));

 

The toughest part of this task is finding preceeding "word" in the document.  In a simple case, a word is within a single Run element inside the document. In a complex case, when sub-parts of a word are formatted (bold, italic etc), the word can span different Inline elements. Below helper routine demonstrates how one can use TextPointer API to accomplish this. The idea is to keep traversing backwards from current position till its Paragraph's start until you find a word break (space character for simplicity).

 

        // Helper that returns a word preceeding the passed position in its paragraph,

        // wordStartPosition points to the start position of word.

        private static string GetPreceedingWordInParagraph(TextPointer position, out TextPointer wordStartPosition)

        {

            wordStartPosition = null;

            string word = String.Empty;

 

            Paragraph paragraph = position.Paragraph;

            if (paragraph != null)

            {

                TextPointer navigator = position;

                while (navigator.CompareTo(paragraph.ContentStart) > 0)

                {

                    string runText = navigator.GetTextInRun(LogicalDirection.Backward);

 

                    if (runText.Contains(" ")) // Any globalized application would need more sophisticated word break testing.

                    {

                        int index = runText.LastIndexOf(" ");

                        word = runText.Substring(index + 1, runText.Length - index - 1) + word;

                        wordStartPosition = navigator.GetPositionAtOffset(-1 * (runText.Length - index - 1));

                        break;

                    }

                    else

                    {

                        wordStartPosition = navigator;

                        word = runText + word;

                    }

                    navigator = navigator.GetNextContextPosition(LogicalDirection.Backward);

                }

            }

 

            return word;

        }

 

Now lets look at the code that deletes the hyperlink tag on backspace key. How do you detect if backspace is at the hyperlink's end boundary?

 

            else // Key.Back

            {

                TextPointer backspacePosition = caretPosition.GetNextInsertionPosition(LogicalDirection.Backward);

                Hyperlink hyperlink;

                if (backspacePosition != null && IsHyperlinkBoundaryCrossed(caretPosition, backspacePosition, out hyperlink))

                {

 

Lets look at the helper IsHyperlinkBoundaryCrossed that detects this for us. It uses a heuristic based on hyperlink ancestors of current position and backspace position. The comments in the code are self explanatory, so I will just show the code here.

 

        // Helper that returns true if passed caretPosition and backspacePosition cross a hyperlink end boundary

        // (under the assumption that caretPosition and backSpacePosition are adjacent insertion positions).

        private static bool IsHyperlinkBoundaryCrossed(TextPointer caretPosition, TextPointer backspacePosition, out Hyperlink backspacePositionHyperlink)

        {

            Hyperlink caretPositionHyperlink = GetHyperlinkAncestor(caretPosition);

            backspacePositionHyperlink = GetHyperlinkAncestor(backspacePosition);

 

            return (caretPositionHyperlink == null && backspacePositionHyperlink != null) ||

                (caretPositionHyperlink != null && backspacePositionHyperlink != null && caretPositionHyperlink != backspacePositionHyperlink);

        }

 

        // Helper that returns a hyperlink ancestor of passed position.

        private static Hyperlink GetHyperlinkAncestor(TextPointer position)

        {

            Inline parent = position.Parent as Inline;

            while (parent != null && !(parent is Hyperlink))

            {

                parent = parent.Parent as Inline;

            }

 

            return parent as Hyperlink;

        }

 

Finally, the process of deleting the hyperlink element consists of below steps.

 

                    // 1. Copy its children Inline to a temporary array.

                    InlineCollection hyperlinkChildren = hyperlink.Inlines;

                    Inline[] inlines = new Inline[hyperlinkChildren.Count];

                    hyperlinkChildren.CopyTo(inlines, 0);

 

                    // 2. Remove each child from parent hyperlink element and insert it after the hyperlink.

                    for (int i = inlines.Length - 1; i >= 0; i--)

                    {

                        hyperlinkChildren.Remove(inlines[i]);

                        hyperlink.SiblingInlines.InsertAfter(hyperlink, inlines[i]);

                    }

 

                    // 3. Apply hyperlink's local formatting properties to inlines (which are now outside hyperlink scope).

                    LocalValueEnumerator localProperties = hyperlink.GetLocalValueEnumerator();

                    TextRange inlineRange = new TextRange(inlines[0].ContentStart, inlines[inlines.Length - 1].ContentEnd);

 

                    while (localProperties.MoveNext())

                    {

                        LocalValueEntry property = localProperties.Current;

                        DependencyProperty dp = property.Property;

                        object value = property.Value;

 

                        if (!dp.ReadOnly &&

                            dp != Inline.TextDecorationsProperty && // Ignore hyperlink defaults.

                            dp != TextElement.ForegroundProperty &&

                            !IsHyperlinkProperty(dp))

                        {

                            inlineRange.ApplyPropertyValue(dp, value);

                        }

                    }

 

                    // 4. Delete the (empty) hyperlink element.

                    hyperlink.SiblingInlines.Remove(hyperlink);

 

I have attached all the code to this post. The custom RichTextBox is ~150 lines of code, enjoy!

Customize RichTextBox to allow only plain text input (with custom ContextMenu)

In this post, we will look at a custom RichTextBox implementation.

In V1, TextBox selection highlight is not extensible and TextBox schema is restricted to plain text only. Often customers want a “rich” TextBox, where they can apply formatting properties (such as bold, underline) to Runs of text. I coded a sample that demonstrates how one can achieve this by restricting RichTextBox schema to plain text input.

    /// <summary>

    /// An extension of RichTextBox that allows only plain text input.

    /// This class auto-formats words in the document using a dictionary lookup.

    /// <remarks>

    /// One of the applications of such a class can be a code editor.

    /// Syntax highlight for keywords can be implemented using this approach.

    /// </remarks>

    /// </summary>

    public class MyRichTextBox : RichTextBox

To allow plain text only input, first, I register command handlers for all formatting commands in my custom RichTextBox, thus disabling them. Second, I listen to Copy, Cut and Paste events and allow clipboard data to be loaded/saved only in UnicodeText format. (Note that this approach doesn't restrict the application from programmatic ally modifying the content of custom RichTextBox, the idea here is to just restrict input to plain text.)

 static void RegisterCommandHandlers()

        {

            // Register command handlers for all rich text formatting commands.

            // We disable all commands by returning false in OnCanExecute event handler,

            // thus making this control a "plain text only" RichTextBox.

            foreach (RoutedUICommand command in _formattingCommands)

            {

                CommandManager.RegisterClassCommandBinding(typeof(MyRichTextBox),

                    new CommandBinding(command, new ExecutedRoutedEventHandler(OnFormattingCommand),

                    new CanExecuteRoutedEventHandler(OnCanExecuteFormattingCommand)));

            }

 

            // Command handlers for Cut, Copy and Paste commands.

            // To enforce that data can be copied or pasted from the clipboard in text format only.

            CommandManager.RegisterClassCommandBinding(typeof(MyRichTextBox),

                new CommandBinding(ApplicationCommands.Copy, new ExecutedRoutedEventHandler(OnCopy),

                new CanExecuteRoutedEventHandler(OnCanExecuteCopy)));

            ...

        }

Also, I illustrate how this app can auto-format words in the document by listening to TextChanged event. I have a dictionary of hard coded email alias and names which are auto-formatted as you type. [One application of this concept can be a code editor app with syntax highlight.] Since TextChanged event doesn't supply any information about affected change, I reparse the whole document scanning for matching words from dictionary. This approach won't scale well for very large document sizes, but for the scope of this sample (and its application towards a code editor) it seems acceptable.

        /// <summary>

        /// Event handler for RichTextBox.TextChanged event.

        /// </summary>

        private void TextChangedEventHandler(object sender, TextChangedEventArgs e)

        {

            // Clear all formatting properties in the document.

            // This is necessary since a paste command could have inserted text inside or at boundaries of a keyword from dictionary.

            TextRange documentRange = new TextRange(this.Document.ContentStart, this.Document.ContentEnd);

            documentRange.ClearAllProperties();

 

            // Reparse the document to scan for matching words.

            TextPointer navigator = this.Document.ContentStart;

            while (navigator.CompareTo(this.Document.ContentEnd) < 0)

            {

                TextPointerContext context = navigator.GetPointerContext(LogicalDirection.Backward);

                if (context == TextPointerContext.ElementStart && navigator.Parent is Run)

                {

                    this.AddMatchingWordsInRun((Run)navigator.Parent); // Scans passed Run's text, for any matching words from dictionary.

                }

                navigator = navigator.GetNextContextPosition(LogicalDirection.Forward);

            }

 

            // Format words found.

            this.FormatWords();

        }

 

Another feature of this sample is a custom ContextMenu implementation. Along with clipboard menu items (cut, copy, paste), I add speller and custom dictionary menu items to my context menu.

 

    /// <summary>

    /// Custom context menu for MyRichTextBox type.

    /// </summary>

    internal class MyContextMenu : ContextMenu

    {

        public MyContextMenu(MyRichTextBox myRichTextBox) : base()

        {

            _myRichTextBox = myRichTextBox;

        }

 

        protected override void OnOpened(RoutedEventArgs e)

        {

            this.Items.Clear();

            this.AddEmailNameDictionaryMenuItems();

            this.AddSpellerMenuItems();

            this.AddClipboardMenuItems();           

        }

 ...

 

        // Helper to add dictionary menu items.

        private void AddEmailNameDictionaryMenuItems()

        {

            TextPointer mousePosition = this.GetMousePosition();

            if (mousePosition != null && mousePosition.Parent != null && mousePosition.Parent is Run)

            {

                Run run = (Run)mousePosition.Parent;

                if (_myRichTextBox.EmailNamesDictionary.ContainsKey(run.Text))

                {

                    MenuItem menuItem = new MenuItem();

                    menuItem.Header = _myRichTextBox.EmailNamesDictionary[run.Text];

                    this.Items.Add(menuItem);

                    this.AddSeperator();

                }

            }

        }

 

The zip file with all sources is attached to this post. Most of the code is self explanatory. Enjoy!

Posted by Prajakta Joshi | 3 Comments
Filed under:

Attachment(s): CustomRichTextBoxSample.zip

FlowDocument Content Model

A while ago, I made up a UML diagram to illustrate WPF FlowDocument content model. This diagram is useful to understand the inheritance hierarchy of TextElement implementations and their TextElementCollection members.

Check out the full diagram here.

The one aspect of content schema this diagram doesn't capture, is the fact that Hyperlink elements cannot be nested. Hope this helps!

Posted by Prajakta Joshi | 0 Comments
Filed under:

eLearning clinics for .NET FX 3.0 (WPF, WWF and WCF) are live and FREE for a limited time!

I wanted to spread the word on this  great opportunity for all you .NET developers out there. Enjoy!

This collection of 3 2-hour premium clinics teaches about the new capabilities provided by the .NET Framework 3.0. These clinics are for experienced Developers and Software Architects who are looking to adopt Microsoft's next generation technology within their solutions.

Topics covered within the collection include:

  • Windows Presentation Foundation
  • Windows Workflow Foundation
  • Windows Communication Foundation

Link: https://www.microsoftelearning.com/eLearning/offerDetail.aspx?offerPriceId=109340

Posted by Prajakta Joshi | 1 Comments
Filed under:

Get set, Go!

I am a developer on WPF editing team. I have been on this team for little over 1 year now. It has been an awesome year for me and I have learnt a great deal about WPF. Ofcourse, there is so much more to learn from the myriad powerful features WPF offers you.

I have found blogs by fellow WPF bloggers to be an amazing source of information. I always wondered if one day I should take the plunge and start blogging myself. This morning looked like a perfect sunny Seattle morning to do that, so here I am, blogging!

 

Posted by Prajakta Joshi | 1 Comments
Filed under:
 
Page view tracker