2010-08-12 1 views
5

Ich arbeite an einer Textverarbeitung App mit der WPF RichTextBox. Ich bin mit dem Selection Ereignisse, um herauszufinden, was die Schriftart, Schriftstärke, Stil, etc. ist die aktuellen Auswahl in der RTB mit dem folgenden Code:WPF RichTextBox SelectionChanged Leistung

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 usw. sind jeweils ein DependencyProperty auf dem Hosting-Benutzersteuerelement mit einem Bindungsmodus von OneWayToSource. Sie sind an ein ViewModel gebunden, an das wiederum eine View gebunden ist, die über das Kombinationsfeld Font, fett, kursiv, unterstrichen usw. verfügt. Wenn sich die Auswahl in der RTB ändert, werden diese Steuerelemente ebenfalls aktualisiert, um anzuzeigen, was ausgewählt wurde. Das funktioniert großartig.

Leider funktioniert es auf Kosten der Leistung, die bei der Auswahl großer Textmengen stark beeinträchtigt wird. Alles auswählen ist merklich langsam, und dann mit etwas wie Umschalt + Pfeiltasten, um die Auswahl zu ändern ist sehr langsam. Zu langsam, um akzeptabel zu sein.

Mache ich etwas falsch? Gibt es Vorschläge, wie die Attribute des ausgewählten Textes in der RTB zu gebundenen Steuerelementen umgesetzt werden können, ohne dabei die Leistung der RTB zu beeinträchtigen?

Antwort

9

Ihre zwei Hauptursachen von Performance-Problemen sind:

  1. Sie rufen selection.GetPropertyValue() mehr Zeit als nötig
  2. Sie jedes Mal die Auswahl

Die GetPropertyValue ändert neu berechnen() Methode muss intern jedes Element im Dokument scannen, wodurch es langsam wird. Also anstatt es mehrere Male mit dem gleichen Argument aufrufen, speichern Sie die Rückgabewerte:

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

Dies wird etwa 3x schneller, aber es ist wirklich bissig Gefühl an den Endbenutzer, nicht aktualisieren Sie die Einstellungen sofort bei jeder Änderung. Stattdessen aktualisiert auf ContextIdle:

bool _queuedChange; 

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

Dies ruft die HandleSelctionChanged() Methode (siehe oben), um tatsächlich die Änderung der Auswahl handhaben, aber verzögert den Anruf bis ContextIdle Dispatcher Priorität und reiht auch nur ein Update, egal wie viele Auswahländerungsereignisse kommen in.

Zusätzliche speedups möglich

Der obige Code macht alle vier GetPropertyValue in einem einzigen DispatcherOperation, was bedeutet, dass Sie immer noch eine „Verzögerung“, solange die vier Anrufe haben. Um die Verzögerung um ein weiteres 4x zu reduzieren, machen Sie nur einen GetPropertyValue pro DispatcherOperation. So ruft beispielsweise die erste DispatcherOperation GetPropertyValue (FontFamilyProperty) auf, speichert das Ergebnis in einem Feld und plant die nächste DispatcherOperation, um die Schriftgröße zu ermitteln. Jede nachfolgende DispatcherOperation wird dasselbe tun.

Wenn diese zusätzliche Beschleunigung immer noch nicht ausreicht, besteht der nächste Schritt darin, die Auswahl in kleinere Teile aufzuteilen, GetPropertyValue für jedes Teil in einer separaten DispatcherOperation aufzurufen und dann die Ergebnisse zu kombinieren.

Um die absolute maximale Glätte zu erhalten, könnten Sie Ihren eigenen Code für GetPropertyValue (Iterieren Sie einfach die ContentElements in der Auswahl) implementieren, der inkrementell arbeitet und nach Prüfung von beispielsweise 100 Elementen zurückkehrt.Wenn du es das nächste Mal anrufst, würde es dort weitermachen, wo es aufgehört hat. Dies würde garantieren, dass Sie jede erkennbare Verzögerung vermeiden können, indem Sie den Umfang der Arbeit pro DispatcherOperation variieren.

Würde Threading helfen?

Sie fragen in den Kommentaren, ob dies mit Threading möglich ist. Die Antwort ist, dass Sie einen Thread verwenden können, um die Arbeit zu koordinieren, aber da Sie immer Dispatcher.Invoke zurück zum Hauptthread rufen müssen, um GetPropertyValue aufzurufen, blockieren Sie weiterhin Ihren UI-Thread für die gesamte Dauer jedes GetPropertyValue-Aufrufs, also seine Granularität ist immer noch ein Problem. Mit anderen Worten, Threading kauft dir wirklich nichts, außer vielleicht die Fähigkeit, die Verwendung einer Zustandsmaschine zu vermeiden, um deine Arbeit in mundgerechte Stücke aufzuteilen.

+0

Vielen Dank für Ihren Code, dies hat zwar die Geschwindigkeit erhöht, wie Sie gesagt haben, aber es ist immer noch ziemlich verzögert, wenn Sie eine anständige Menge an Text in der RTB haben (sagen 15 Seiten oder so). Wenn Sie den gesamten Text markieren und die Pfeiltasten verwenden, um die Auswahl von Zeilen/Wörtern aufzuheben, ist die Verzögerung noch so groß, dass sie ziemlich auffällt. Also ist es besser, aber immer noch nicht da. Kann so etwas in einen Thread eingefügt werden? – Scott

+0

Ich habe meine Antwort erweitert, um Ihnen eine Idee zu geben, was für eine weitere Beschleunigung erforderlich wäre und ob ein Thread hilfreich wäre. –

+0

Ausgezeichneter Rat, danke Ray. Ich werde Ihre Vorschläge genauer untersuchen. – Scott