2010-08-12 82 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