WPF RichTextBox选择更改性能

WPF RichTextBox选择更改性能,wpf,wpf-controls,Wpf,Wpf Controls,我正在使用WPF RichTextBox开发一个文字处理器类型的应用程序。我使用SelectionChanged事件通过以下代码计算RTB中当前所选内容的字体、字体大小、样式等: private void richTextBox_SelectionChanged(object sender, RoutedEventArgs e) { TextSelection selection = richTextBox.Selection; if (selectio

我正在使用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、SelectionBold等都是托管用户控件的从属属性,绑定模式为OneWayToSource。它们被绑定到ViewModel,而ViewModel又绑定了一个视图,该视图上有字体组合框、粗体、斜体、下划线等控件。当RTB中的选择更改时,这些控件也会更新以反映所选内容。这很有效

不幸的是,它是以牺牲性能为代价的,这在选择大量文本时会受到严重影响。选择所有内容都非常慢,然后使用Shift+箭头键等更改选择非常慢。太慢了,不能接受


我做错什么了吗?对于如何实现将RTB中所选文本的属性反映到绑定控件而不影响RTB在该过程中的性能,是否有任何建议

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

  • 调用selection.GetPropertyValue()的次数超过了需要的次数
  • 每次选择更改时都会重新计算
  • 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();
        }));
      }
    }
    
    这将调用
    handleSelectionChanged()
    方法(如上)来实际处理选择更改,但会将调用延迟到ContextIdle dispatcher priority,并且无论出现多少选择更改事件,都只对一次更新进行排队

    可能的额外加速

    上面的代码在一个DispatcherOperation中生成所有四个GetPropertyValue,这意味着您可能仍然有一个“滞后”,只要这四个调用。若要将延迟再减少4倍,请在每个DispatcherOperation中仅设置一个GetPropertyValue。因此,例如,第一次DispatcherOperation将调用GetPropertyValue(FontFamilyProperty),将结果存储在字段中,并安排下一次DispatcherOperation以获取字体权重。每个后续DispatcherOperation都将执行相同的操作

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

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

    线程化有帮助吗?


    您在评论中询问是否可以使用线程来实现这一点。答案是,您可以使用线程来编排工作,但由于您必须始终使用Dispatcher.Invoke返回主线程以调用GetPropertyValue,因此在每次GetPropertyValue调用的整个过程中,您仍然会阻塞UI线程,因此其粒度仍然是一个问题。换言之,线程并不能真正为您带来任何东西,除了避免使用状态机将您的工作分割成小块之外。

    感谢您的代码,这确实提高了您所说的速度,但当RTB中有相当数量的文本(比如15页左右)时,它仍然相当滞后。当您高亮显示所有文本并使用箭头键取消选择行/词时,它仍然滞后到相当明显的程度。这样更好了,但还是不行。像这样的东西可以放在一个线程中吗?我扩展了我的答案,让你知道进一步加速需要什么,以及线程是否有用。非常好的建议,谢谢Ray。我会更详细地研究你的建议。