Почему мой рекламодатель не выполняет повторную визуализацию, когда элемент, к которому он применяется, изменяется? - PullRequest
15 голосов
/ 16 марта 2010

В пользовательском интерфейсе, который я создаю, я хочу украсить панель всякий раз, когда один из элементов управления на панели имеет фокус. Поэтому я обрабатываю событие IsKeyboardFocusWithinChanged и добавляю рекламный элемент к элементу, когда он получает фокус, и удаляю рекламный элемент, когда он теряет фокус. Кажется, это работает хорошо.

Проблема, с которой я сталкиваюсь, заключается в том, что рекламодатель не будет перерисован, если границы украшенного элемента изменятся. Например, в этом простом случае:

<WrapPanel Orientation="Horizontal"
           IsKeyboardFocusChanged="Panel_IsKeyboardFocusChanged">
   <Label>Caption</Label>
   <TextBox>Data</TextBox>
</WrapPanel>

Автор правильно украшает границы WrapPanel, когда TextBox получает фокус, но когда я набираю текст, TextBox расширяется под краем украшения. Конечно, как только я делаю что-либо, что вынуждает рекламодателя отрисовывать, например, ALT-TAB из приложения или дает фокус другой панели, это исправляет себя. Но как я могу заставить его повторно визуализировать, когда изменяются границы украшенного элемента?

Ответы [ 2 ]

58 голосов
/ 26 марта 2010

WPF имеет встроенный механизм, который позволяет переоценивать, переупорядочивать и перерисовывать все Adorners всякий раз, когда соответствующий AdornedElement изменяет размер, положение или преобразование. Этот механизм требует от вас соблюдения определенных правил при кодировании вашего рекламодателя, не все из которых документированы так четко, как следовало бы.

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

Почему рекламодатель не рендерит заново

Всякий раз, когда AdornerLayer получает уведомление LayoutChanged, он сканирует каждого из своих Adorners, чтобы увидеть, изменился ли AdornedElement в размере, положении или преобразовании. Если это так, он устанавливает флаги, чтобы заставить Adorner измерить, упорядочить и снова отобразить - примерно эквивалентно InvalidateMeasure(); InvaliateArrange(); InvalidateVisual();.

Что обычно происходит в этой ситуации, так это то, что управление сначала измеряется, затем организуется, а затем воспроизводится. Фактически, WPF пытается сделать это наиболее распространенным случаем, потому что это наиболее эффективная последовательность. Однако существует много ситуаций, когда элемент управления может в конечном итоге переставляться и / или перерисовываться перед его повторным измерением. Это законный порядок событий в WPF (для обеспечения гибких методов компоновки), но он не распространен, поэтому его часто не тестируют.

Правильно реализованный Adorner или другой UIElement будет осторожно вызывать InvalidateVisual() каждый раз, когда на рендеринг может повлиять , если только не были изменены AffectsRender свойства зависимости.

В вашем случае размер вашего рекламодателя явно влияет на рендеринг. Свойства размера не являются AffectsRender свойствами зависимостей, поэтому необходимо вручную вызвать InvalidateVisual(), когда они изменятся. Если вы этого не сделаете, WPF, возможно, никогда не узнает, как сделать ваш рекламодатель заново.

То, что происходит в вашей ситуации, вероятно, таково:

  • Макет завершен, и событие LayoutChanged запускается
  • AdornerLayer обнаруживает изменение размера вашего AdornedElement
  • AdornerLayer планирует вашего рекламодателя для повторного измерения, изменения макета и повторной визуализации
  • Что-то вызывает вызов Arrange(), что приводит к изменению макета и повторному рендерингу перед повторным измерением. Это заставляет WPF думать, что рекламодателю больше не нужно переделывать или перерисовывать.
  • Механизм макета обнаруживает, что рекламодатель нуждается в измерении, и звонит Measure
  • Авторский MeasureOverride пересчитывает желаемый размер , но ничего не делает для того, чтобы сообщить WPF, что рекламодатель должен повторно визуализировать
  • Механизм верстки решает, что больше ничего не нужно делать, и поэтому рекламодатель никогда не перерисовывает

Что вы можете сделать, чтобы исправить это

Решение, конечно, состоит в том, чтобы исправить ошибку в Adorner, вызывая InvalidateVisual() всякий раз, когда элемент управления повторно измеряется, например:

protected override Size MeasureOverride(Size constraint)
{
  var result = base.MeasureOverride(constraint);
  // ... add custom measure code here if desired ...
  InvalidateVisual();
  return result;
}

Выполнение этого приведет к тому, что ваш Adorner будет постоянно соблюдать все правила WPF, поэтому он будет работать, как и ожидалось, во всех ситуациях. Это также наиболее эффективное решение, поскольку InvalidateVisual() вообще ничего не сделает, кроме тех случаев, когда это действительно необходимо.

1 голос
/ 24 марта 2010

Вам нужно вызвать диспетчера на панели. Добавьте обработчик к событию TextBox SizeChanged:

    private void myTextBox_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        panel.Dispatcher.Invoke((Action)(() => 
        {
            if (panel.IsKeyboardFocusWithin)
            {
                // remove and add adorner to reset
                myAdornerLayer.Remove(myAdorner);
                myAdornerLayer.Add(myAdorner);
            }
        }), DispatcherPriority.Render, null);
    }

Это в основном происходит из этого поста: http://geekswithblogs.net/NewThingsILearned/archive/2008/08/25/refresh--update-wpf-controls.aspx

...