2010-08-12 2 views
5

Я работаю над текстовым приложением, используя WPF RichTextBox. Я использую SelectionChanged событие, чтобы выяснить, какой шрифт, шрифт вес, стиль и т.д. текущего выбора в RTB, используя следующий код:WPF RichTextBox SelectionChanged 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 и т.д. являются каждый DependencyProperty на хостинге UserControl с режимом привязки OneWayToSource. Они привязаны к ViewModel, который, в свою очередь, имеет привязку к нему, которая имеет на нем поле со списком Font, жирный шрифт, курсив, подчеркивание и т. Д. Когда выбор в RTB изменяется, эти элементы управления также обновляются, чтобы отобразить выбранное. Это отлично работает.

К сожалению, это работает за счет высоких результатов, что серьезно сказывается при выборе большого количества текста. Выбор всего заметно медленный, а затем использование чего-то вроде Shift + Arrow Keys для изменения выбора происходит очень медленно. Слишком медленно, чтобы быть приемлемым.

Я что-то не так? Есть ли какие-либо предложения о том, как добиться отражения атрибутов выбранного текста в RTB для привязки элементов управления, не убивая производительность RTB в этом процессе?

ответ

9

Ваши две основные причины проблем производительности являются:

  1. Вы называете selection.GetPropertyValue() несколько раз, чем необходимо
  2. Вы пересчитывать каждый раз, когда выбор изменяется

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

Это будет примерно 3x быстрее, но, чтобы сделать его чувствовать себя действительно мгновенным к конечному пользователю, не обновлять настройки мгновенно при каждом изменении. Вместо это обновление на 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 раза, сделайте только один GetPropertyValue для DispatcherOperation. Так, например, первый DispatcherOperation вызовет GetPropertyValue (FontFamilyProperty), сохранит результат в поле и заплатит следующий DispatcherOperation, чтобы получить вес шрифта. Каждое последующее DispatcherOperation будет делать то же самое.

Если этого дополнительного ускорения все еще недостаточно, следующим шагом будет разделить выделение на более мелкие куски, вызвать GetPropertyValue для каждой части в отдельном диспетчере, а затем объединить полученные результаты.

Чтобы получить абсолютную максимальную гладкость, вы можете реализовать свой собственный код для GetPropertyValue (просто повторите выбор ContentElements в выборе), который работает постепенно и возвращается после проверки, скажем, 100 элементов.В следующий раз, когда вы его назовете, он поднимет, где он остановился. Это гарантировало бы вашу способность предотвращать любые заметные задержки, меняя количество выполненных работ в DispatcherOperation.

Может ли резьба помочь?

В комментариях вы спрашиваете, возможно ли это, используя потоки. Ответ заключается в том, что вы можете использовать поток для организации работы, но так как вы всегда должны Dispatcher.Invoke обратно в основной поток, чтобы вызвать GetPropertyValue, вы по-прежнему будете блокировать свой поток пользовательского интерфейса на весь период каждого вызова GetPropertyValue, поэтому его гранулярность все еще проблема. Другими словами, потоковая передача на самом деле не покупает вам ничего, кроме, возможно, возможности избежать использования конечного автомата для разделения вашей работы на куски размером с укусом.

+0

Спасибо за ваш код, это действительно увеличило скорость, как вы заявили, но она по-прежнему довольно лаги, когда у вас есть приличное количество текста в RTB (скажем, 15 страниц или около того). Когда вы выделяете весь текст и используете клавиши со стрелками, чтобы отменить выбор строк/слов, он все еще достаточно медлителен, что это довольно заметно. Так что лучше, но все равно не там. Может ли что-то подобное быть помещено в поток? – Scott

+0

Я распространил свой ответ, чтобы дать вам представление о том, что потребуется для дальнейшего ускорения, и будет ли поток полезен. –

+0

Отличный совет, спасибо Рэй. Я рассмотрю ваши предложения более подробно. – Scott