重新加载RTF后未检测到下划线

时间:2014-08-09 10:23:01

标签: c# wpf serialization richtextbox rtf

我目前正在尝试使用基本格式的RichTextBox为我的新测试笔记软件Lilly Notes工作。关于这个问题的Brian Lagunas' article让我朝着正确的方向前进,但是我遇到了一些问题。如果单击带下划线的文本,则会按下“下划线”按钮,因此正在识别状态。但是,如果我将其序列化为RTF然后将其反序列化为RichTextBox,则不会检测到它。由于Lilly Notes中的代码在这里展示并不容易,我创建了一个SSCCE来演示这个问题。

首先,MainWindow.xaml:

<Window x:Class="WpfRichTextBoxUnderline.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Height="350"
        Width="525">
    <DockPanel LastChildFill="True">
        <Button Name="SaveAndReloadButton"
                Content="Save and Reload"
                DockPanel.Dock="Bottom"
                Click="SaveAndReloadButton_Click" />
        <ToggleButton Name="UnderlineButton"
                      DockPanel.Dock="Top"
                      Width="20"
                      Command="{x:Static EditingCommands.ToggleUnderline}"
                      CommandTarget="{Binding ElementName=RichText}">
            <ToggleButton.Content>
                <TextBlock Text="U"
                           TextDecorations="Underline" />
            </ToggleButton.Content>
        </ToggleButton>
        <RichTextBox Name="RichText"
                     SelectionChanged="RichTextBox_SelectionChanged" />
    </DockPanel>
</Window>

这就是它的样子:

enter image description here

在代码隐藏中,我有代码在选择更改时检测格式化状态,并相应地更新“下划线”按钮的状态。这与Brian Lagunas的方法没有什么不同。

private void RichTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
    if (this.RichText.Selection != null)
    {
        object currentValue = this.RichText.Selection.GetPropertyValue(Inline.TextDecorationsProperty);
        this.UnderlineButton.IsChecked = (currentValue == DependencyProperty.UnsetValue) ? false : currentValue != null && currentValue.Equals(TextDecorations.Underline);
    }
}

然后我有一个方法(和另一个辅助方法)将RTF保存到字符串然后将其应用于RichTextBox。我这样做只是为了保持简单 - 在Lilly Notes中,我将该字符串保存到数据库中,然后在应用程序再次运行时将其加载回来。

public Stream GenerateStreamFromString(string s)
{
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(s);
    writer.Flush();
    stream.Position = 0;
    return stream;
}

private async void SaveAndReloadButton_Click(object sender, RoutedEventArgs e)
{
    string data = null;
    var range = new TextRange(this.RichText.Document.ContentStart, this.RichText.Document.ContentEnd);
    using (var memoryStream = new MemoryStream())
    {
        range.Save(memoryStream, DataFormats.Rtf);
        memoryStream.Position = 0;

        using (StreamReader reader = new StreamReader(memoryStream))
        {
            data = await reader.ReadToEndAsync();
        }
    }

    // load

    var stream = GenerateStreamFromString(data);
    range = new TextRange(this.RichText.Document.ContentStart, this.RichText.Document.ContentEnd);
    range.Load(stream, DataFormats.Rtf);
}

单击“保存并重新加载”按钮后,RTF被序列化为字符串并反序列化为RichTextBox,下划线检测不再起作用,当我单击带下划线的文本时,按钮仍然像下划线一样没工作:

enter image description here

现在,当我调试这个时,我注意到了这一点:

enter image description here

最初,当您点击带下划线的文字时,会得到一个TextDecorationCollection Count为1.但保存并重新加载后,您的Count为零,这就是检测不起作用的原因。

请注意,此问题仅适用于属于WPF中TextDecorationCollection的下划线/删除线。 Bold和Italic没有表现出这个问题。

这是因为我做错了,还是这是RichTextBox的错误?

您可以在我的BitBucket存储库中找到SSCCE代码here

4 个答案:

答案 0 :(得分:5)

Inline.TextDecorations是一个集合,所以可能直接比较它并不是一个好主意。

也许这会更好:

TextDecorationCollection currentValue = this.RichText.Selection.GetPropertyValue(Inline.TextDecorationsProperty) as TextDecorationCollection;
this.UnderlineButton.IsChecked = (currentValue == DependencyProperty.UnsetValue) ? false : currentValue != null && currentValue.Contains(TextDecorations.Underline);

修改

在完成提供的代码后,我发现了可能的原因:

before

此图像在保存并重新加载为RTF之前完成。

在上图中,请注意该段落的内嵌为Run,插入符的内容也为Run且两者都有TextDecorations

现在让我们保存并重新加载!

after

在上图中,请注意段落的内联现在是Span,而插入符的父级是Run。但奇怪的是,SpanTextDecoration,但父Run中没有TextDecoration

<强>解决方案

这是一个可能的解决方案,或者更好地说一个解决方法:

    private void RichTextBox_SelectionChanged(object sender, RoutedEventArgs e)
    {
        var caret = RichText.CaretPosition;
        Paragraph paragraph = RichText.Document.Blocks.FirstOrDefault(x => x.ContentStart.CompareTo(caret) == -1 && x.ContentEnd.CompareTo(caret) == 1) as Paragraph;

        if (paragraph != null)
        {
            Inline inline = paragraph.Inlines.FirstOrDefault(x => x.ContentStart.CompareTo(caret) == -1 && x.ContentEnd.CompareTo(caret) == 1) as Inline;
            if (inline != null)
            {
                TextDecorationCollection decorations = inline.TextDecorations;
                this.UnderlineButton.IsChecked = (decorations == DependencyProperty.UnsetValue) ? false : decorations != null && decorations.Contains(TextDecorations.Underline[0]);
            }
        }
    }

在上面的解决方案中,我试图通过使用当前插入位置来获取基础Run或Span。其余的仍然相似。

答案 1 :(得分:1)

RichTextBox.Selection.GetPropertyValue正在使用GetCharacterValueFromPosition中的错误。看看这篇文章:link

当该方法获取TextDecorationsProperty时,它向上走逻辑树以查找具有该属性的非null值的元素。当它找到这样的值时,它返回它。问题是它应该检查空值和空TextDecorationCollection。

如果您使用建议的实现,它将修复切换按钮上的已检查状态问题。但是,仍有问题是它没有正确设置/取消设置下划线。

答案 2 :(得分:0)

尝试将您的选择事件处理程序更改为以下内容:

private void RichTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
    if (this.RichText.Selection != null)
    {
        var currentValue = new TextRange(this.RichText.Selection.Start, this.RichText.Selection.End);
        if (currentValue.GetPropertyValue(Inline.TextDecorationsProperty) == TextDecorations.Underline)
        {
            this.UnderlineButton.IsChecked = true;
        }
        else
        {
            this.UnderlineButton.IsChecked = false;
        }
}

答案 3 :(得分:0)

我知道这是一个老问题,并且已经有一个已接受的答案,但不是完全正确的答案,并且遇到同样的问题,我为未找到的解决方案的用户写道。在VB ......

    Dim TextRange = New TextRange(richTxtEditor.Selection.Start, richTxtEditor.Selection.End)
Dim textDecor As TextDecorationCollection
Dim DecorationFound as Boolean = false
If TextRange.IsEmpty Then
    textDecor = txtRange.GetPropertyValue(Inline.TextDecorationsProperty)

    If textDecor.Equals(TextDecorations.Underline) Then
        MsgBox("Is Underline, and it only works on a new document, not a document loaded.")
        DecorationFound = true
    End If

    If textDecor.Equals(TextDecorations.Strikethrough) Then
        MsgBox("Is Strikethrough, and it only works on a new document, not a document loaded.")
        DecorationFound = true
    End If

    ' ### START # From here starts the solution to the problem !!! ###

    If NOT DecorationFound Then
        For i = 0 To textDecor.Count - 1
            If textDecor.Item(i).Location = TextDecorationLocation.Underline Then
                MsgBox("Is Underline, and it works on a loaded document.")
            ElseIf textDecor.Item(i).Location = TextDecorationLocation.Strikethrough Then
                MsgBox("Is Strikethrough, and it works on a loaded document.") 
            End If
        Next
    End If

    ' ### END SOLUTION ###

End If