WPF RichTextBox SelectionChanged性能

Sco*_*ott 5 wpf wpf-controls

我正在使用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;
        }            
    }
Run Code Online (Sandbox Code Playgroud)

SelectionFontFamily,SelectionIsBold等都是对用户控件托管每一个DependencyProperty与OneWayToSource的结合模式.他们被绑定到视图模型,这反过来又绑定了一个观点是,字体的组合框,粗体,斜体,下划线等就可以控制.当RTB中的选择发生变化时,这些控件也会更新以反映已选择的内容.这非常有效.

不幸的是,它以牺牲性能为代价,在选择大量文本时会严重影响性能.选择一切都非常慢,然后使用Shift +箭头键来更改选择非常慢.太慢是不可接受的.

难道我做错了什么?是否有任何关于如何实现将RTB中所选文本的属性反映到绑定控件而不会在此过程中破坏RTB性能的建议?

Ray*_*rns 9

导致性能问题的两个主要原因是:

  1. 你调用selection.GetPropertyValue()的次数超过必要的次数
  2. 每次选择更改时,您都会重新计算

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;
}
Run Code Online (Sandbox Code Playgroud)

这将大约快3倍,但为了让最终用户感觉非常活泼,不要在每次更改时立即更新设置.而是在ContextIdle上更新:

bool _queuedChange;

private void richTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
  if(!_queuedChange)
  {
    _queuedChange = true;
    Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, (Action)(() =>
    {
      _queuedChange = false;
      HandleSelectionChange();
    }));
  }
}
Run Code Online (Sandbox Code Playgroud)

这会调用HandleSelctionChanged()方法(上面)来实际处理选择更改,但会将调用延迟到ContextIdle调度程序优先级,并且无论有多少选择更改事件进入,也只会对一个更新进行排队.

额外的加速可能

上面的代码在一个DispatcherOperation中生成所有四个GetPropertyValue,这意味着只要四次调用你仍然可能有"滞后".要将延迟减少4倍,每个DispatcherOperation只需要一个GetPropertyValue.因此,例如,第一个DispatcherOperation将调用GetPropertyValue(FontFamilyProperty),将结果存储在字段中,并安排下一个DispatcherOperation以获取字体粗细.每个后续DispatcherOperation都将执行相同的操作.

如果这个额外的加速仍然不够,下一步就是将选择分成更小的部分,在单独的DispatcherOperation中对每个部分调用GetPropertyValue,然后合并你得到的结果.

为了获得绝对最大的平滑度,您可以为GetPropertyValue(只是迭代选择中的ContentElements)实现自己的代码,该代码以增量方式工作,并在检查100个元素后返回.下次你打电话时它会从中断的地方继续.这可以保证您通过改变每个DispatcherOperation完成的工作量来防止任何可辨别的延迟.

线程会有帮助吗?

您在评论中询问是否可以使用线程.答案是您可以使用线程来协调工作,但由于您必须始终将Dispatcher.Invoke返回到主线程以调用GetPropertyValue,因此您仍将在每个GetPropertyValue调用的整个持续时间内阻止UI线程,因此其粒度仍然是一个问题.换句话说,线程并没有真正为你买任何东西,除了可能避免使用状态机将你的工作分成一口大小的块.