我正在使用WPF RichTextBox处理一个文字处理器类型的应用程序。我正在使用SelectionChanged事件使用以下代码确定RTB中当前选择的字体,字体粗细,样式等:
private void richTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
TextSelection selection = richTextBox.Selection;
if (selection.GetPropertyValue(FontFamilyProperty) != DependencyProperty.UnsetValue)
{
//we have a single font in the selection
SelectionFontFamily = (FontFamily)selection.GetPropertyValue(FontFamilyProperty);
}
else
{
SelectionFontFamily = null;
}
if (selection.GetPropertyValue(FontWeightProperty) == DependencyProperty.UnsetValue)
{
SelectionIsBold = false;
}
else
{
SelectionIsBold = (FontWeights.Bold == ((FontWeight)selection.GetPropertyValue(FontWeightProperty)));
}
if (selection.GetPropertyValue(FontStyleProperty) == DependencyProperty.UnsetValue)
{
SelectionIsItalic = false;
}
else
{
SelectionIsItalic = (FontStyles.Italic == ((FontStyle)selection.GetPropertyValue(FontStyleProperty)));
}
if (selection.GetPropertyValue(Paragraph.TextAlignmentProperty) != DependencyProperty.UnsetValue)
{
SelectionIsLeftAligned = (TextAlignment)selection.GetPropertyValue(Paragraph.TextAlignmentProperty) == TextAlignment.Left;
SelectionIsCenterAligned = (TextAlignment)selection.GetPropertyValue(Paragraph.TextAlignmentProperty) == TextAlignment.Center;
SelectionIsRightAligned = (TextAlignment)selection.GetPropertyValue(Paragraph.TextAlignmentProperty) == TextAlignment.Right;
SelectionIsJustified = (TextAlignment)selection.GetPropertyValue(Paragraph.TextAlignmentProperty) == TextAlignment.Justify;
}
}
SelectionFontFamily,SelectionIsBold等都是主机UserControl上的DependencyProperty,具有OneWayToSource的绑定模式。它们绑定到ViewModel,而ViewModel又绑定了一个View,它上面有Font组合框,粗体,斜体,下划线等控件。当RTB中的选择发生变化时,这些控件也会更新以反映已选择的内容。这很有效。
不幸的是,它以牺牲性能为代价,在选择大量文本时会严重影响性能。选择一切都非常慢,然后使用Shift +箭头键来更改选择非常慢。太慢是不可接受的。
我做错了吗?是否有任何关于如何实现将RTB中所选文本的属性反映到绑定控件而不会在此过程中破坏RTB性能的建议?
答案 0 :(得分:9)
性能问题的两个主要原因是:
GetPropertyValue()方法必须在内部扫描文档中的每个元素,这会使其变慢。因此,不是使用相同的参数多次调用它,而是存储返回值:
private void HandleSelectionChange()
{
var family = selection.GetPropertyValue(FontFamilyProperty);
var weight = selection.GetPropertyValue(FontWeightProperty);
var style = selection.GetPropertyValue(FontStyleProperty);
var align = selection.GetPropertyValue(Paragraph.TextAlignmentProperty);
var unset = DependencyProperty.UnsetValue;
SelectionFontFamily = family!=unset ? (FontFamily)family : null;
SelectionIsBold = weight!=unset && (FontWeight)weight == FontWeight.Bold;
SelectionIsItalic = style!=unset && (FontStyle)style == FontStyle.Italic;
SelectionIsLeftAligned = align!=unset && (TextAlignment)align == TextAlignment.Left;
SelectionIsCenterAligned = align!=unset && (TextAlignment)align == TextAlignment.Center;
SelectionIsRightAligned = align!=unset && (TextAlignment)align == TextAlignment.Right;
SelectionIsJustified = align!=unset && (TextAlignment)align == TextAlignment.Justify;
}
这将大约快3倍,但为了让最终用户感觉非常活泼,请不要在每次更改时立即更新设置。而是在ContextIdle上更新:
bool _queuedChange;
private void richTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
if(!_queuedChange)
{
_queuedChange = true;
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, (Action)(() =>
{
_queuedChange = false;
HandleSelectionChange();
}));
}
}
这会调用HandleSelctionChanged()
方法(上面)来实际处理选择更改,但会将调用延迟到ContextIdle调度程序优先级,并且无论有多少选择更改事件进入,也只会对一个更新进行排队。
可能的额外加速
上面的代码在一个DispatcherOperation中生成了所有四个GetPropertyValue,这意味着只要四个调用你仍然可能有“滞后”。要将延迟减少4倍,每个DispatcherOperation只需要一个GetPropertyValue。因此,例如,第一个DispatcherOperation将调用GetPropertyValue(FontFamilyProperty),将结果存储在字段中,并安排下一个DispatcherOperation以获取字体粗细。每个后续DispatcherOperation都将执行相同的操作。
如果这个额外的加速比仍然不够,下一步就是将选择分成更小的部分,在单独的DispatcherOperation中调用每个部分的GetPropertyValue,然后合并你得到的结果。
要获得绝对最大平滑度,您可以为GetPropertyValue(只是迭代选择中的ContentElements)实现自己的代码,该代码以增量方式工作,并在检查100个元素后返回。下次你打电话时,它会从它停止的地方开始。这可以保证您通过改变每个DispatcherOperation完成的工作量来防止任何可辨别的延迟。
线程会有帮助吗?
您在评论中询问是否可以使用线程。答案是您可以使用线程来协调工作,但由于您必须始终将Dispatcher.Invoke返回到主线程以调用GetPropertyValue,因此您仍将在每个GetPropertyValue调用的整个持续时间内阻止UI线程,因此其粒度仍然是一个问题。换句话说,线程并不能真正为你买任何东西,除非是能够避免使用状态机将你的工作分成一口大小的块。