为什么我的装饰者在应用它的元素发生变化时不会重新渲染?

时间:2010-03-15 21:28:56

标签: wpf adorner

在我正在构建的用户界面中,只要面板中的某个控件具有焦点,我就会想要装饰面板。因此,我处理IsKeyboardFocusWithinChanged事件,并在元素获得焦点时向元素添加元素,并在焦点失去焦点时移除元素。这似乎工作正常。

我遇到的问题是,如果装饰元素的边界发生变化,则不会重新渲染装饰器。例如,在这个简单的例子中:

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

WrapPanel收到焦点时,装饰器会正确地装饰TextBox的边界,但当我输入文字时,TextBox会在装饰边缘下方展开。当然,只要我做任何迫使装饰者渲染的东西,例如ALT-TAB从应用程序中移出或给予另一个小组焦点,它就会自行纠正。但是,当装饰元素的边界发生变化时,如何让它重新渲染?

2 个答案:

答案 0 :(得分:58)

WPF有一个内置机制,可以在相应的Adorners更改大小,位置或转换时重新测量,重新排列和重新呈现所有AdornedElement。这种机制要求您在对装饰者进行编码时遵循某些规则,而不是所有这些都按照应有的方式进行记录。

我将首先回答你的标题问题为什么你的装饰师不会一致地重新渲染,然后解释解决它的最佳方法。

为什么装饰者不会重新渲染

每当AdornerLayer收到LayoutChanged通知时,它会扫描每个Adorner以查看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 :(得分:1)

您需要在面板上调用调度程序。向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