2010-08-12 13 views
5

Estoy trabajando en una aplicación de procesador de textos utilizando el WPF RichTextBox. Estoy usando el evento SelectionChanged para averiguar cuál es la fuente, el peso de la fuente, el estilo, etc., es de la selección actual en el RTB con el siguiente código:WPF RichTextBox SelectionChanged Rendimiento

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., son cada una DependencyProperty en el UserControl de alojamiento con un modo de enlace de OneWayToSource. Están vinculados a un ViewModel, que a su vez tiene una vista vinculada a él que tiene el cuadro combinado Fuente, negrita, cursiva, subrayado, etc. controla en él. Cuando la selección en el RTB cambia, esos controles también se actualizan para reflejar lo que se ha seleccionado. Esto funciona genial

Desafortunadamente, funciona a expensas del rendimiento, que se ve seriamente afectado al seleccionar grandes cantidades de texto. Seleccionar todo es notablemente lento, y luego usar algo como Mayús + teclas de flecha para cambiar la selección es muy lento. Demasiado lento para ser aceptable.

¿Estoy haciendo algo mal? ¿Hay alguna sugerencia sobre cómo lograr reflejar los atributos del texto seleccionado en el RTB a los controles vinculados sin matar el rendimiento del RTB en el proceso?

Respuesta

9

Sus dos principales causas de los problemas de rendimiento son:

  1. Usted llama selection.GetPropertyValue() más veces de lo necesario
  2. Se vuelve a calcular cada vez que la selección cambia

El getPropertyValue() El método debe explorar internamente cada elemento del documento, lo que lo hace lento. Así que en lugar de llamarlo varias veces con el mismo argumento, almacenar los valores de retorno:

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

este período es de 3 veces más rápido, sino para hacer que se sienta muy ágil para el usuario final, no actualizar los ajustes al instante en cada cambio. En su lugar, la actualización de ContextIdle:

bool _queuedChange; 

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

Esto requiere la HandleSelctionChanged() método (arriba) para manejar realmente el cambio de selección, pero retrasa la llamada hasta que la prioridad despachador ContextIdle y también pone en cola sólo una actualización, no importa cuántas selección vienen eventos de cambio en.

aceleraciones adicionales posibles

El código anterior hace que los cuatro getPropertyValue en un solo DispatcherOperation, lo que significa que es posible que tenga un "retraso", siempre y cuando los cuatro llamadas. Para reducir el retraso un 4x adicional, haga solo un GetPropertyValue por DispatcherOperation. Por lo tanto, por ejemplo, el primer DispatcherOperation llamará a GetPropertyValue (FontFamilyProperty), almacenará el resultado en un campo y programará el próximo DispatcherOperation para obtener el peso de la fuente. Cada DispatcherOperation posterior hará lo mismo.

Si esta aceleración adicional aún no es suficiente, el siguiente paso sería dividir la selección en piezas más pequeñas, llamar a GetPropertyValue en cada pieza en un DispatcherOperation por separado, luego combine los resultados que obtenga.

Para obtener la máxima suavidad absoluta, puede implementar su propio código para GetPropertyValue (simplemente itere los elementos de contenido en la selección) que funciona de forma incremental y lo devuelve después de comprobar, por ejemplo, 100 elementos.La próxima vez que lo llame, continuará donde lo dejó. Esto garantizaría su capacidad para evitar cualquier retraso discernible variando la cantidad de trabajo realizado por DispatcherOperation.

¿Ayudaría el roscado?

Usted pregunta en los comentarios si esto es posible hacerlo usando el enhebrado. La respuesta es que puede usar un hilo para orquestar el trabajo, pero dado que siempre debe Dispatcher.Invocar de nuevo al hilo principal para llamar a GetPropertyValue, aún bloqueará su subproceso de UI durante toda la duración de cada llamada a GetPropertyValue, por lo que su granularidad sigue siendo un problema. En otras palabras, el enhebrado realmente no le compra nada, excepto tal vez la capacidad de evitar el uso de una máquina de estado para dividir su trabajo en trozos del tamaño de un bocado.

+0

Gracias por su código, esto de hecho aumentó la velocidad como usted indicó, pero todavía es bastante lento cuando tiene una buena cantidad de texto en el RTB (digamos 15 páginas más o menos). Cuando resaltas todo el texto y utilizas las teclas de flecha para anular la selección de líneas/palabras, todavía queda lo suficientemente marcado como para que se note. Entonces es mejor, pero aún no está allí. ¿Se puede poner algo así en un hilo? – Scott

+0

He extendido mi respuesta para darle una idea de lo que se necesitaría para una mayor aceleración, y si un hilo sería o no útil. –

+0

Excelente consejo, gracias Ray. Voy a ver sus sugerencias con más detalle. – Scott

Cuestiones relacionadas