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!

CustomRichTextBoxSample.zip