Detect if a RichTextBox is empty

2019-04-04 18:49发布

问题:

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

new TextRange(Document.ContentStart, Document.ContentEnd).IsEmpty

回答1:

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;
}


回答2:

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.



回答3:

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.



回答4:

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.



回答5:

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.