WPF RichTextBox SelectionChanged性能

时间:2010-08-12 22:51:27

标签: 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;
        }            
    }

SelectionFontFamily,SelectionIsBold等都是主机UserControl上的DependencyProperty,具有OneWayToSource的绑定模式。它们绑定到ViewModel,而ViewModel又绑定了一个View,它上面有Font组合框,粗体,斜体,下划线等控件。当RTB中的选择发生变化时,这些控件也会更新以反映已选择的内容。这很有效。

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

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

1 个答案:

答案 0 :(得分:9)

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

  1. 您需要多次调用selection.GetPropertyValue()
  2. 每次选择更改时都会重新计算
  3. 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线程,因此其粒度仍然是一个问题。换句话说,线程并不能真正为你买任何东西,除非是能够避免使用状态机将你的工作分成一口大小的块。