2010-08-12 62 views
5

我正在使用WPF RichTextBox处理文字处理器类型的应用程序。我使用的是SelectionChanged事件找出什么字体,字体粗细,风格等是使用下面的代码在RTB当前选择的:WPF RichTextBox SelectionChanged性能

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等分别是使用OneWayToSource的绑定模式托管UserControl上的DependencyProperty。它们绑定到一个ViewModel,而ViewModel又有一个绑定到它的视图,它具有字体组合框,粗体,斜体,下划线等控件。当RTB中的选择更改时,这些控件也会更新以反映选择的内容。这很好。

不幸的是,它的工作原理是牺牲了性能,在选择大量文本时会受到严重影响。选择一切显然很慢,然后使用Shift +箭头键来改变选择非常缓慢。太慢,无法接受。

我做错了什么?对于如何在RTB中将选定文本的属性反映到绑定控件而不会在此过程中杀死RTB的性能,有什么建议吗?

回答

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; 
} 

这将是约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调度优先级,而且队列只有一个更新,不管更改事件多少选择来中。

附加的加速可能

上面的代码,使所有四个GetPropertyValue在一个单一的DispatcherOperation,这意味着你仍然可以有一个“滞后”只要四个电话。为了减少延迟4倍,每个DispatcherOperation只能创建一个GetPropertyValue。因此,例如,第一个DispatcherOperation将调用GetPropertyValue(FontFamilyProperty),将结果存储在一个字段中,并计划下一个DispatcherOperation以获取字体权重。每个后续的DispatcherOperation都会执行相同的操作。

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

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

线程帮助?

您在评论中询问是否可以使用线程。答案是可以使用线程编排工作,但由于您必须始终将Dispatcher.Invoke重新调用回主线程以调用GetPropertyValue,因此在每次GetPropertyValue调用的整个持续时间内,您仍然会阻塞UI线程,因此其粒度仍然是一个问题。换句话说,线程并不会真正为您购买任何东西,除了可能避免使用状态机将工作分解为一块大小的块。

+0

感谢你的代码,这确实提高了你所说的速度,但是当你在RTB中有大量文本时(比如说15页左右),它仍然很不稳定。如果突出显示所有文字并使用箭头键取消选择线条/文字,则它仍然滞后,非常明显。所以它更好,但仍然不存在。 可以这样的事情放在一个线程? – Scott 2010-08-19 20:43:55

+0

我已经扩展了我的答案,让您了解进一步加速需要什么,以及线程是否会有所帮助。 – 2010-08-20 04:58:16

+0

优秀的建议,谢谢雷。我会更详细地研究你的建议。 – Scott 2010-08-20 20:19:55