WPF RichTextBox SelectionChanged Performance - PullRequest
       8

WPF RichTextBox SelectionChanged Performance

5 голосов
/ 13 августа 2010

Я работаю над приложением типа текстового процессора с использованием WPF RichTextBox. Я использую событие SelectionChanged, чтобы выяснить, какой шрифт, вес шрифта, стиль и т. Д. Текущего выделения в RTB, используя следующий код:

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, которая, в свою очередь, имеет привязанный к ней View, в котором есть поле со списком Font, полужирный, курсив, подчеркивание и т. Д. Когда выбор в RTB изменяется, эти элементы управления также обновляются, чтобы отразить то, что было выбрано. Это прекрасно работает.

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

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

1 Ответ

9 голосов
/ 13 августа 2010

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

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

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

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

Помогло бы многопоточность?

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

...