I was excited at the prospect of building an editor around the new WPF RichTextBox, which Microsoft lists as the functional equivalent of the WinForms RichTextBox (see http://msdn2.microsoft.com/en-us/library/ms750559.aspx). This RichTextBox should finally permit insertion of dazzling media elements and flexible layouts...
However, there are some implementation snags. For example, many of the formatting functions that were previously available by a simple call to a (WinForms) RichEdit-based RichTextBox property, now require implmentation of an equivalent property. For example a SelColor property that changes the text color:
Public Property SelColor() As Color
Dim oCurrentColor As SolidColorBrush
Get
' Get current foreground text color
Try
oCurrentColor = WPFRichTextBox.Selection.GetPropertyValue(Inline.ForegroundProperty)
Catch
oCurrentColor = WPFRichTextBox.Foreground
End Try
SelColor = oCurrentColor.Color
End Get
Set(ByVal value As Color)
' Get current fore color and convert to System.Drawing.Color
WPFRichTextBox.Selection.ApplyPropertyValue(Inline.ForegroundProperty, New SolidColorBrush(Color.FromRgb(value.R, value.G, value.B)))
End Set
End Property
This is a relatively simple property to implement, but others require further code for error and text context checking.
Also, although the WPF RichTextBox supports RTF text, it seems to be a subset of that supported by the RichEdit-based RichTextBox. For example the \protect (for protected text) and \v (for hidden text) are ignored. There are some workarounds for this (for example on could uniquely color-code the protected text and use a practically-invisible fontsize for the hidden text), but these require additional event handlers (to prevent edit when cursor is on protected text, to skip over the "invisible" characters, and convert the XAML to/from corrected RTF as needed). It would be great to learn that these standard RichEdit RTF features will just be available in future version of the WPF RichTextBox.
In addition, although Spell-checking is available as a built-in feature, it is currently limited to Western languages, without support for custom dictionaries. It has been suggested that Microsoft will allow for custom dictionaries and expanded language support in the (hopefull very) near future.
The WPF RichTextBox poses a another challenge in that the underlying XAML of selected content is not readily availalable with all content intact--any UI elements added are converted to a textual equivalent (e.g., <InlineUIContainer>..</InlineUIContainer> elements that contain UI elements convert to a <run></run>). This poses a difficulty when one expects to support drag/drop or cut/paste with the enhanced UI elements as well as text. So far, a workaround has been to handle the drag/drop (via WPFRichTextBox_PreviewMouseMove and WPFRichTextBox_PreviewDrop events respectively) or cut/paste operations (via WPFRichTextBox_PreviewKeyDown event) so that any replaced UI elements can be restored to the original content prior to completion of the Drop or Paste. For now, it seems this can be accompished using the XAMLWriter with code similar to the following...
Private Function GetTextRangeXAML(ByVal objTextRange As TextRange) As String
' Traverse elements convered by current range and construct XAML
Dim objParagraph As Paragraph
Dim objTextElement As TextElement
Dim objInline As Inline
Dim strXAML As String
Dim strSectionStart As String
Dim objSection As New Section
Dim booStart As Boolean
Dim objSelectionText As String
Dim objTR As TextRange
' Get paragraph of start position
objParagraph = objTextRange.Start.Paragraph
booStart = True
objTextElement = objTextRange.Start.Parent
If objTextElement.GetType.Name = "Paragraph" Then
' Started at paragraph, so set 1st Inline to 1st Inline of Paragraph
objInline = objParagraph.Inlines.FirstInline
Else
objInline = objTextElement
End If
Do While Not objParagraph Is Nothing
strXAML = strXAML & "<Paragraph>"
' handle inline elements within the paragraph
Do While Not objInline Is Nothing
Dim objMemoryStream As New MemoryStream()
Dim objStreamReader As New StreamReader(objMemoryStream)
Select Case objInline.GetType.Name
Case "Run"
If booStart Then
' Get content in run offset from start position
booStart = False
objTR = New TextRange(objTextRange.Start, objInline.ElementEnd)
objTR.Save(objMemoryStream, DataFormats.Xaml)
objMemoryStream.Position = 0
objSelectionText = objStreamReader.ReadToEnd()
'objSelectionText = "<Run>" & objTR.Text & "</Run>"
strXAML = strXAML & objSelectionText
Else
' Get full run xaml
objTR = New TextRange(objInline.ElementStart, objInline.ElementEnd)
objTR.Save(objMemoryStream, DataFormats.Xaml)
objMemoryStream.Position = 0
objSelectionText = objStreamReader.ReadToEnd()
'objSelectionText = "<Run>" & objTR.Text & "</Run>"
strXAML = strXAML & objSelectionText
End If
Case "InlineUIContainer"
' Get XAML for object
strXAML = strXAML & XamlWriter.Save(objInline)
Case Else
End Select
objInline = objInline.NextInline
Loop
' Next paragraph
strXAML = strXAML & "</Paragraph>"
objParagraph = objParagraph.NextBlock
If Not objParagraph Is Nothing Then
objInline = objParagraph.Inlines.FirstInline
End If
Loop
strSectionStart = XamlWriter.Save(objSection)
strSectionStart = strSectionStart.Substring(0, strSectionStart.IndexOf("/>")) & ">"
strXAML = strSectionStart & strXAML & "</Section>"
Return strXAML
End Function