2 using System
.Collections
;
3 using System
.Collections
.Generic
;
5 using System
.Windows
.Controls
;
6 using System
.Windows
.Controls
.Primitives
;
7 using System
.Windows
.Documents
;
8 using System
.Windows
.Input
;
9 using System
.Windows
.Media
;
11 namespace CustomRichTextBoxSample
14 /// An extension of RichTextBox that allows only plain text input.
15 /// This class auto-formats words in the document using a dictionary lookup.
17 /// One of the applications of such a class can be a code editor.
18 /// Syntax highlight for keywords can be implemented using this approach.
21 public class MyRichTextBox
: RichTextBox
23 //------------------------------------------------------
27 //------------------------------------------------------
30 static MyRichTextBox()
32 RegisterCommandHandlers();
34 _emailNamesDictionary
= new Dictionary
<string, string>();
35 _emailNamesDictionary
.Add("alias1", "Emailname 1");
36 _emailNamesDictionary
.Add("alias2", "Emailname 2");
37 _emailNamesDictionary
.Add("alias3", "Emailname 3");
40 static void RegisterCommandHandlers()
42 // Register command handlers for all rich text formatting commands.
43 // We disable all commands by returning false in OnCanExecute event handler,
44 // thus making this control a "plain text only" RichTextBox.
45 foreach (RoutedUICommand command
in _formattingCommands
)
47 CommandManager
.RegisterClassCommandBinding(typeof(MyRichTextBox
),
48 new CommandBinding(command
, new ExecutedRoutedEventHandler(OnFormattingCommand
),
49 new CanExecuteRoutedEventHandler(OnCanExecuteFormattingCommand
)));
52 // Command handlers for Cut, Copy and Paste commands.
53 // To enforce that data can be copied or pasted from the clipboard in text format only.
54 CommandManager
.RegisterClassCommandBinding(typeof(MyRichTextBox
),
55 new CommandBinding(ApplicationCommands
.Copy
, new ExecutedRoutedEventHandler(OnCopy
),
56 new CanExecuteRoutedEventHandler(OnCanExecuteCopy
)));
57 CommandManager
.RegisterClassCommandBinding(typeof(MyRichTextBox
),
58 new CommandBinding(ApplicationCommands
.Paste
, new ExecutedRoutedEventHandler(OnPaste
),
59 new CanExecuteRoutedEventHandler(OnCanExecutePaste
)));
60 CommandManager
.RegisterClassCommandBinding(typeof(MyRichTextBox
),
61 new CommandBinding(ApplicationCommands
.Cut
, new ExecutedRoutedEventHandler(OnCut
),
62 new CanExecuteRoutedEventHandler(OnCanExecuteCut
)));
66 public MyRichTextBox() : base()
68 this._words
= new List
<Word
>();
69 this.TextChanged
+= this.TextChangedEventHandler
;
71 MyContextMenu myContextMenu
= new MyContextMenu(this);
72 myContextMenu
.Placement
= PlacementMode
.RelativePoint
;
73 myContextMenu
.PlacementTarget
= this;
75 this.ContextMenu
= myContextMenu
;
78 //------------------------------------------------------
82 //------------------------------------------------------
84 #region Public Properties
87 /// Dictionary of email names which are auto-formatted by this RichTextBox.
89 public Dictionary
<string, string> EmailNamesDictionary
93 return _emailNamesDictionary
;
99 //------------------------------------------------------
103 //------------------------------------------------------
105 #region Event Handlers
108 /// Event handler for all formatting commands.
110 private static void OnFormattingCommand(object sender
, ExecutedRoutedEventArgs e
)
112 // Do nothing, and set command handled to true.
117 /// Event handler for ApplicationCommands.Copy command.
119 /// We want to enforce that data can be set on the clipboard
120 /// only in plain text format from this RichTextBox.
123 private static void OnCopy(object sender
, ExecutedRoutedEventArgs e
)
125 MyRichTextBox myRichTextBox
= (MyRichTextBox
)sender
;
126 string selectionText
= myRichTextBox
.Selection
.Text
;
127 Clipboard
.SetText(selectionText
);
132 /// Event handler for ApplicationCommands.Cut command.
134 /// We want to enforce that data can be set on the clipboard
135 /// only in plain text format from this RichTextBox.
138 private static void OnCut(object sender
, ExecutedRoutedEventArgs e
)
140 MyRichTextBox myRichTextBox
= (MyRichTextBox
)sender
;
141 string selectionText
= myRichTextBox
.Selection
.Text
;
142 myRichTextBox
.Selection
.Text
= String
.Empty
;
143 Clipboard
.SetText(selectionText
);
148 /// Event handler for ApplicationCommands.Paste command.
150 /// We want to allow paste only in plain text format.
153 private static void OnPaste(object sender
, ExecutedRoutedEventArgs e
)
155 MyRichTextBox myRichTextBox
= (MyRichTextBox
)sender
;
157 // Handle paste only if clipboard supports text format.
158 if (Clipboard
.ContainsText())
160 myRichTextBox
.Selection
.Text
= Clipboard
.GetText();
166 /// CanExecute event handler.
168 private static void OnCanExecuteFormattingCommand(object target
, CanExecuteRoutedEventArgs args
)
170 args
.CanExecute
= true;
174 /// CanExecute event handler for ApplicationCommands.Copy.
176 private static void OnCanExecuteCopy(object target
, CanExecuteRoutedEventArgs args
)
178 MyRichTextBox myRichTextBox
= (MyRichTextBox
)target
;
179 args
.CanExecute
= myRichTextBox
.IsEnabled
&& !myRichTextBox
.Selection
.IsEmpty
;
183 /// CanExecute event handler for ApplicationCommands.Cut.
185 private static void OnCanExecuteCut(object target
, CanExecuteRoutedEventArgs args
)
187 MyRichTextBox myRichTextBox
= (MyRichTextBox
)target
;
188 args
.CanExecute
= myRichTextBox
.IsEnabled
&& !myRichTextBox
.IsReadOnly
&& !myRichTextBox
.Selection
.IsEmpty
;
192 /// CanExecute event handler for ApplicationCommand.Paste.
194 private static void OnCanExecutePaste(object target
, CanExecuteRoutedEventArgs args
)
196 MyRichTextBox myRichTextBox
= (MyRichTextBox
)target
;
197 args
.CanExecute
= myRichTextBox
.IsEnabled
&& !myRichTextBox
.IsReadOnly
&& Clipboard
.ContainsText();
201 /// Event handler for RichTextBox.TextChanged event.
203 private void TextChangedEventHandler(object sender
, TextChangedEventArgs e
)
205 // Clear all formatting properties in the document.
206 // This is necessary since a paste command could have inserted text inside or at boundaries of a keyword from dictionary.
207 TextRange documentRange
= new TextRange(this.Document
.ContentStart
, this.Document
.ContentEnd
);
208 documentRange
.ClearAllProperties();
210 // Reparse the document to scan for matching words.
211 TextPointer navigator
= this.Document
.ContentStart
;
212 while (navigator
.CompareTo(this.Document
.ContentEnd
) < 0)
214 TextPointerContext context
= navigator
.GetPointerContext(LogicalDirection
.Backward
);
215 if (context
== TextPointerContext
.ElementStart
&& navigator
.Parent
is Run
)
217 this.AddMatchingWordsInRun((Run
)navigator
.Parent
);
219 navigator
= navigator
.GetNextContextPosition(LogicalDirection
.Forward
);
222 // Format words found.
228 //------------------------------------------------------
232 //------------------------------------------------------
234 #region Private Methods
237 /// Helper to apply formatting properties to matching words in the document.
239 private void FormatWords()
241 // Applying formatting properties, triggers another TextChangedEvent. Remove event handler temporarily.
242 this.TextChanged
-= this.TextChangedEventHandler
;
244 // Add formatting for matching words.
245 foreach (Word word
in _words
)
247 TextRange range
= new TextRange(word
.Start
, word
.End
);
248 range
.ApplyPropertyValue(TextElement
.ForegroundProperty
, new SolidColorBrush(Colors
.Blue
));
249 range
.ApplyPropertyValue(TextElement
.FontWeightProperty
, FontWeights
.Bold
);
253 // Add TextChanged handler back.
254 this.TextChanged
+= this.TextChangedEventHandler
;
258 /// Scans passed Run's text, for any matching words from dictionary.
260 private void AddMatchingWordsInRun(Run run
)
262 string runText
= run
.Text
;
264 int wordStartIndex
= 0;
265 int wordEndIndex
= 0;
266 for (int i
= 0; i
< runText
.Length
; i
++)
268 if (Char
.IsWhiteSpace(runText
[i
]))
270 if (i
> 0 && !Char
.IsWhiteSpace(runText
[i
- 1]))
272 wordEndIndex
= i
- 1;
273 string wordInRun
= runText
.Substring(wordStartIndex
, wordEndIndex
- wordStartIndex
+ 1);
275 if (_emailNamesDictionary
.ContainsKey(wordInRun
))
277 TextPointer wordStart
= run
.ContentStart
.GetPositionAtOffset(wordStartIndex
, LogicalDirection
.Forward
);
278 TextPointer wordEnd
= run
.ContentStart
.GetPositionAtOffset(wordEndIndex
+ 1, LogicalDirection
.Backward
);
279 _words
.Add(new Word(wordStart
, wordEnd
));
282 wordStartIndex
= i
+ 1;
286 // Check if the last word in the Run is a matching word.
287 string lastWordInRun
= runText
.Substring(wordStartIndex
, runText
.Length
- wordStartIndex
);
288 if (_emailNamesDictionary
.ContainsKey(lastWordInRun
))
290 TextPointer wordStart
= run
.ContentStart
.GetPositionAtOffset(wordStartIndex
, LogicalDirection
.Forward
);
291 TextPointer wordEnd
= run
.ContentStart
.GetPositionAtOffset(runText
.Length
, LogicalDirection
.Backward
);
292 _words
.Add(new Word(wordStart
, wordEnd
));
298 //------------------------------------------------------
302 //------------------------------------------------------
304 #region Private Types
307 /// This class encapsulates a matching word by two TextPointer positions,
308 /// start and end, with forward and backward gravities respectively.
312 public Word(TextPointer wordStart
, TextPointer wordEnd
)
314 _wordStart
= wordStart
.GetPositionAtOffset(0, LogicalDirection
.Forward
);
315 _wordEnd
= wordEnd
.GetPositionAtOffset(0, LogicalDirection
.Backward
);
318 public TextPointer Start
326 public TextPointer End
334 private readonly TextPointer _wordStart
;
335 private readonly TextPointer _wordEnd
;
340 //------------------------------------------------------
344 //------------------------------------------------------
346 #region Private Members
348 // Static member for email names dictionary.
349 private static readonly Dictionary
<string, string> _emailNamesDictionary
;
351 // Static list of editing formatting commands. In the ctor we disable all these commands.
352 private static readonly RoutedUICommand
[] _formattingCommands
= new RoutedUICommand
[]
354 EditingCommands
.ToggleBold
,
355 EditingCommands
.ToggleItalic
,
356 EditingCommands
.ToggleUnderline
,
357 EditingCommands
.ToggleSubscript
,
358 EditingCommands
.ToggleSuperscript
,
359 EditingCommands
.IncreaseFontSize
,
360 EditingCommands
.DecreaseFontSize
,
361 EditingCommands
.ToggleBullets
,
362 EditingCommands
.ToggleNumbering
,
365 // List of matching words found in the document.
366 private List
<Word
> _words
;
368 #endregion Private Members