wpf 当应用装饰器的元素发生更改时,为什么装饰器不重新呈现?

kkih6yb8  于 2023-04-22  发布在  其他
关注(0)|答案(2)|浏览(180)

在我正在构建的UI中,每当面板中的一个控件具有焦点时,我希望装饰面板。因此,我处理IsKeyboardFocusWithinChanged事件,并在元素获得焦点时向元素添加装饰器,并在失去焦点时删除装饰器。这似乎可以正常工作。
我遇到的问题是,如果被装饰元素的边界发生变化,装饰器不会被重新呈现。例如,在这个简单的例子中:

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

TextBox接收到焦点时,装饰器正确地装饰了WrapPanel的边界,但是当我输入文本时,TextBox在装饰器的边缘下方展开。当然,只要我做任何迫使装饰器渲染的事情,比如ALT-TAB退出应用程序或给予另一个面板焦点,但是当被修饰的元素的边界改变时,我怎样才能让它重新呈现呢?

balp4ylt

balp4ylt1#

WPF有一个内置的机制,当相应的AdornedElement改变大小、位置或转换时,会导致所有Adorners被重新测量、重新排列和重新呈现。这种机制要求您在编写装饰器时遵循某些规则,但并非所有规则都有明确的文档说明。
我将首先回答你的标题问题为什么你的装饰器不一致重新渲染,然后解释最好的方法来解决它。

为什么装饰器不重新渲染

每当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装饰器需要重新呈现 *
  • 布局引擎决定没有更多的事情要做,所以装饰器永远不会重新呈现
    你能做些什么来弥补

当然,解决方案是通过在每次重新测量控件时调用InvalidateVisual()来修复Adorner中的错误,如下所示:

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

这样做会使你的Adorner始终遵守WPF的所有规则,所以它在所有情况下都能按预期工作。这也是最有效的解决方案,因为InvalidateVisual()除了在真正需要它的情况下,什么也不做。

jecbmhm3

jecbmhm32#

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

这基本上来自这篇文章:Link

相关问题