Customized RichTextBox

One of the devs from the Editing side Prajakta has just started blogging and the latest post has some nice code on customizing the RichTextBox.

The customized RichTextBox has the following behavior:
1> Plain text input just like the textbox
2> autoformat specific words (like email aliases)
3> custom context menus (changes when opened over formatted text)

To begin with, it would be necessary to remove all the formatting commands to get the Textbox input :)

private static readonly RoutedUICommand[] _formattingCommands = new RoutedUICommand[]
{
EditingCommands.ToggleBold,
EditingCommands.ToggleItalic,
EditingCommands.ToggleUnderline,
EditingCommands.ToggleSubscript,
EditingCommands.ToggleSuperscript,
EditingCommands.IncreaseFontSize,
EditingCommands.DecreaseFontSize,
};

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, null, new CanExecuteRoutedEventHandler(OnCanExecuteFalse)));
}
// 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 add Cut and Paste handlers
}

/// CanExecute event handler that returns false.
private static void OnCanExecuteFalse(object target, CanExecuteRoutedEventArgs args)
{
args.CanExecute = false;
}

/// <summary>/// CanExecute event handler for ApplicationCommands.Copy./// </summary>
private static void OnCanExecuteCopy(object target, CanExecuteRoutedEventArgs args)
{
MyRichTextBox myRichTextBox = (MyRichTextBox)target;
args.CanExecute = myRichTextBox.IsEnabled && !myRichTextBox.Selection.IsEmpty;
}

/// We want to enforce that data can be set on the clipboard
/// only in plain text format from this RichTextBox.
private static void OnCopy(object sender, ExecutedRoutedEventArgs e)
{
MyRichTextBox myRichTextBox = (MyRichTextBox)sender;
string selectionText = myRichTextBox.Selection.Text;
Clipboard.SetText(selectionText);
e.Handled = true;
}

This gets us to acheive the first goal of plain input in RichTextBox. Next we would need to identify words so that specific text can get formatted. This is handled in the TextChangeEvent

 /// Event handler for RichTextBox.TextChanged event.
       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);                    //this function checks if the Run’s text is the same as the set of words 
                   (email aliases)initially set and adds the Run to a List of words with the 
                   start and end pointer of the word

                }
                navigator = navigator.GetNextContextPosition(LogicalDirection.Forward);
            }
            // Format words found.
            this.FormatWords();                        //this function goes through the list of words and applies foreground property 
            to the specific words in the list
        }

 

Now for the 3rd part which is having a custom context menu... the context menu should change based on what text is clicked. Hence, the best way would be to populate the menu in the OnOpened handler.

 
protected override void OnOpened(RoutedEventArgs e)
{
 this.Items.Clear();
  this.AddClipboardMenuItems();
    this.AddEmailNameDictionaryMenuItems();
}

// 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];
          //emaildictionary is a Dictionary containing the email alias and the email
          this.AddSeperator();
         this.Items.Add(menuItem);
       }
    }
}

// Helper to add menu items for Cut, Copy and Paste commands.
private void AddClipboardMenuItems()
{
    MenuItem menuItem;
  menuItem = new MenuItem();
   menuItem.Header = ApplicationCommands.Cut.Text;
  menuItem.CommandTarget = _myRichTextBox;
 menuItem.Command = ApplicationCommands.Cut;
   this.Items.Add(menuItem);
 .........
}

As for the uses, this can be used for some Code editor which has syntax highlighting but does not need all the bells and whistles that comes along with the RichTextBox :)...

More detailed explanation can be found on Praj's blog. Complete project is also attached.

 

ClipboardSample.zip