What is the best way to detect if a WPF RichTextBox/FlowDocument is empty?
The following works if only text is present in the document. Not if it contains UIElement\'s
H.B.'s answer isn't useful if you need to distinguish between images and whitespace. You can use something like this answer to check for images.
bool IsEmpty(Document document)
{
string text = new TextRange(Document.ContentStart, Document.ContentEnd).Text;
if (string.IsNullOrWhiteSpace(text) == false)
return false;
else
{
if (document.Blocks.OfType<BlockUIContainer>()
.Select(c => c.Child).OfType<Image>()
.Any())
return false;
}
return true;
}
This seems laborious, and still probably isn't correct for all scenarios. But I couldn't find any better way.
The answer above works if you don't put anything into the RTB. However, if you simply delete the contents, the RTB tends to return a single, empty paragraph, not a completely empty string. So, this is more reliable in such cases:
string text = new TextRange(Document.ContentStart, Document.ContentEnd).Text;
return !String.IsNullOrWhiteSpace(text);
This only applies to textual contents, of course.
Here's an extension of H.B.'s idea that works with both text and images.
I found that difference is always >4 whenever the RTB has text. However, if you only paste an image it is 3. To combat this i look at the string length of the raw rtf string.
var start = Document.ContentStart;
var end = Document.ContentEnd;
var difference = start.GetOffsetToPosition(end);
HasText = difference > 4 || GetRtfText().Length > 350;
public string GetRtfText()
{
var tr = new TextRange(Document.ContentStart, Document.ContentEnd);
using (var ms = new MemoryStream())
{
tr.Save(ms, DataFormats.Rtf);
return Encoding.Default.GetString(ms.ToArray());
}
}
Through my testing i found that an empty box with no chars has a length of 270. If i even paste in an image that's only 1 pixel in size it balloons to 406.
I played with toggling on various formatting options without typing any letters and haven't gotten close to 300, so I went with 350 for the baseline.
The length check could be expensive if there are no textual characters, but they pasted in a massive image.
First - thank you to McGarnagle - their answer got me going in the right direction. However for whatever reason their image check didn't work for me. This is what I ended up doing:
Private Function RichTextBoxIsEmpty(BYVAL rtb As RichTextBox) As Boolean
Dim ReturnCode As Boolean = True
Dim text As String = New TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd).Text
If String.IsNullOrWhiteSpace(text) Then
For Each block As Block In rtb.Document.Blocks
'check for an image
If TypeOf block Is Paragraph Then
Dim paragraph As Paragraph = DirectCast(block, Paragraph)
For Each inline As Inline In paragraph.Inlines
If TypeOf inline Is InlineUIContainer Then
Dim uiContainer As InlineUIContainer = DirectCast(inline, InlineUIContainer)
If TypeOf uiContainer.Child Is Image Then
ReturnCode = False
Exit For
End If
End If
Next
End If
' Check for a table
If TypeOf block Is Table Then
ReturnCode = False
Exit For
End If
Next
Else
ReturnCode = False
End If
Return ReturnCode
End Function
there may be other checks to do, but this at least covers text, images and tables.
You could compare the pointers, which is not all too reliable:
var start = rtb.Document.ContentStart;
var end = rtb.Document.ContentEnd;
int difference = start.GetOffsetToPosition(end);
This evaluates to 2
if the RTB is loaded, and 4
if content has been entered and removed again.
If the RTB is completely cleared out e.g. via select all -> delete
the value will be 0
.
In the Silverlight reference on MSDN another method is found which can be adapted and improved to:
public bool IsRichTextBoxEmpty(RichTextBox rtb)
{
if (rtb.Document.Blocks.Count == 0) return true;
TextPointer startPointer = rtb.Document.ContentStart.GetNextInsertionPosition(LogicalDirection.Forward);
TextPointer endPointer = rtb.Document.ContentEnd.GetNextInsertionPosition(LogicalDirection.Backward);
return startPointer.CompareTo(endPointer) == 0;
}