我目前正在尝试使用基本格式的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>
这就是它的样子:
在代码隐藏中,我有代码在选择更改时检测格式化状态,并相应地更新“下划线”按钮的状态。这与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,下划线检测不再起作用,当我单击带下划线的文本时,按钮仍然像下划线一样没工作:
现在,当我调试这个时,我注意到了这一点:
最初,当您点击带下划线的文字时,会得到一个TextDecorationCollection
Count
为1.但保存并重新加载后,您的Count
为零,这就是检测不起作用的原因。
请注意,此问题仅适用于属于WPF中TextDecorationCollection
的下划线/删除线。 Bold和Italic没有表现出这个问题。
这是因为我做错了,还是这是RichTextBox的错误?
您可以在我的BitBucket存储库中找到SSCCE代码here。
答案 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);
修改强>
在完成提供的代码后,我发现了可能的原因:
此图像在保存并重新加载为RTF之前完成。
在上图中,请注意该段落的内嵌为Run
,插入符的内容也为Run
且两者都有TextDecorations
。
现在让我们保存并重新加载!
在上图中,请注意段落的内联现在是Span
,而插入符的父级是Run
。但奇怪的是,Span
有TextDecoration
,但父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