2010-08-12 5 views
5

Je travaille sur une application de type traitement de texte en utilisant le WPT RichTextBox. J'utilise l'événement SelectionChanged pour comprendre ce que la police, le poids police, le style, etc. est de la sélection dans le RTB en utilisant le code suivant:WPF RichTextBox PerformanceChanged Performance

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, etc. sont chacun un DependencyProperty sur l'hôte UserControl avec un mode de liaison de OneWayToSource. Ils sont liés à un ViewModel, qui à son tour a une vue liée à elle qui a la zone de liste déroulante Police, gras, italique, souligné, etc. contrôles sur elle. Lorsque la sélection dans le RTB change, ces contrôles sont également mis à jour pour refléter ce qui a été sélectionné. Cela fonctionne très bien. Malheureusement, cela fonctionne au détriment des performances, ce qui est sérieusement affecté lors de la sélection de grandes quantités de texte. Sélectionner tout est très lent, puis utiliser quelque chose comme Shift + touches fléchées pour changer la sélection est très lent. Trop lent pour être acceptable.

Est-ce que je fais quelque chose de mal? Existe-t-il des suggestions sur la façon de parvenir à refléter les attributs du texte sélectionné dans le RTB pour limiter les contrôles sans détruire les performances du RTB dans le processus?

Répondre

9

Vos deux principales causes des problèmes de performance sont:

  1. Vous appelez selection.GetPropertyValue() fois plus que nécessaire
  2. Vous recalculez chaque fois que la sélection change

Le GetPropertyValue() méthode doit analyser en interne à travers chaque élément du document, ce qui le rend lent. Ainsi, au lieu de l'appeler plusieurs fois avec le même argument, stocker les valeurs de retour:

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

Ce sera environ 3 fois plus rapide, mais pour en faire sentir vraiment accrocheurs à l'utilisateur final, ne met pas à jour les paramètres instantanément à chaque changement. Au lieu de cela, mettre à jour ContextIdle:

bool _queuedChange; 

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

Ceci appelle la méthode HandleSelctionChanged() (ci-dessus) pour gérer réellement le changement de sélection, mais retarde l'appel jusqu'à ce que ContextIdle priorité répartiteur et files d'attente également qu'une mise à jour, peu importe le nombre d'événements de changement de sélection viennent dans. possible

speedups supplémentaires Le code ci-dessus fait tous les quatre GetPropertyValue en un seul DispatcherOperation, ce qui signifie que vous pouvez toujours avoir un « retard » tant que les quatre appels. Pour réduire le retard de 4x, ne faites qu'un seul GetPropertyValue par DispatcherOperation. Ainsi, par exemple, le premier DispatcherOperation appelle GetPropertyValue (FontFamilyProperty), stocke le résultat dans un champ et planifie le DispatcherOperation suivant pour obtenir le poids de la police. Chaque DispatcherOperation suivant fera de même. Si cette accélération supplémentaire n'est toujours pas suffisante, la prochaine étape consistera à diviser la sélection en plus petites parties, appelez GetPropertyValue sur chaque élément dans un DispatcherOperation séparé, puis combinez les résultats obtenus. Pour obtenir le lissage absolu maximal, vous pouvez implémenter votre propre code pour GetPropertyValue (il suffit d'itérer les ContentElements dans la sélection) qui fonctionne de manière incrémentielle et retourne après avoir vérifié, disons, 100 éléments.La prochaine fois que vous l'appelez, il reprendrait là où il s'était arrêté. Cela garantirait votre capacité à éviter tout décalage perceptible en faisant varier la quantité de travail effectuée par DispatcherOperation.

L'aide au filetage serait-elle utile?

Vous demandez dans les commentaires si cela est possible en utilisant l'enfilage. La réponse est que vous pouvez utiliser un thread pour orchestrer le travail, mais puisque vous devez toujours Dispatcher.Invoke retour au thread principal pour appeler GetPropertyValue, vous allez toujours bloquer votre thread UI pour la durée entière de chaque appel GetPropertyValue, sa granularité est toujours un problème. En d'autres termes, le threading ne vous achètera vraiment rien sauf peut-être la possibilité d'éviter l'utilisation d'une machine d'état pour diviser votre travail en morceaux de petite taille.

+0

Merci pour votre code, cela a effectivement augmenté la vitesse comme vous l'avez dit, mais c'est quand même assez décalé quand vous avez une bonne quantité de texte dans le RTB (disons 15 pages environ). Lorsque vous mettez en surbrillance tout le texte et que vous utilisez les touches fléchées pour désélectionner les lignes/mots, il reste assez en retrait pour être assez visible. Donc c'est mieux, mais toujours pas là. Est-ce que quelque chose comme ça peut être mis dans un fil? – Scott

+0

J'ai étendu ma réponse pour vous donner une idée de ce qui serait nécessaire pour accélérer encore, et si un thread serait utile ou non. –

+0

Excellent conseil, merci Ray. Je vais examiner vos suggestions plus en détail. – Scott