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

发布于 2024-08-25 18:34:13 字数 616 浏览 2 评论 0原文

在我正在构建的用户界面中,每当面板中的某个控件获得焦点时,我想装饰面板。因此,我处理 IsKeyboardFocusWithinChanged 事件,并在元素获得焦点时向其添加装饰器,并在元素失去焦点时删除装饰器。这似乎工作正常。

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

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

TextBox 接收焦点时,装饰器正确装饰 WrapPanel 的边界,但当我输入文本时,TextBox 在装饰器边缘下方展开。当然,一旦我做了任何强制装饰器渲染的事情,比如 ALT-TAB 退出应用程序或给另一个面板焦点,它就会自行纠正。但是当装饰元素的边界发生变化时,如何让它重新渲染呢?

In a UI I'm building, I want to adorn a panel whenever one of the controls in the panel has the focus. So I handle the IsKeyboardFocusWithinChanged event, and add an adorner to the element when it gains the focus and remove the adorner when it loses focus. This seems to work OK.

The problem I'm having is that the adorner isn't getting re-rendered if the bounds of the adorned element changes. For instance, in this simple case:

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

the adorner correctly decorates the bounds of the WrapPanel when the TextBox receives the focus, but as I type in text, the TextBox expands underneath the edge of the adorner. Of course as soon as I do anything that forces the adorner to render, like ALT-TAB out of the application or give another panel the focus, it corrects itself. But how can I get it to re-render when the bounds of the adorned element change?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

辞旧 2024-09-01 18:34:13

WPF 有一个内置机制,每当相应的 AdornedElement 更改大小、位置或变换时,都会重新测量、重新排列和重新渲染所有 Adorners。这种机制要求您在对装饰器进行编码时遵循某些规则,但并非所有规则都应有的清晰记录。

我将首先回答您的标题问题,即为什么您的装饰器不能一致地重新渲染,然后解释修复它的最佳方法。

为什么装饰器不重新渲染

每当 AdornerLayer 收到 LayoutChanged 通知时,它都会扫描其每个装饰器,以查看 AdornedElement 的大小、位置或变换是否已更改。如果是这样,它会设置标志来强制Adorner再次测量、排列和渲染——大致相当于InvalidateMeasure(); InvaliateArrange(); InvalidateVisual();。

在这种情况下通常会发生的情况是,首先测量控件,然后排列,然后渲染。事实上,WPF 试图使这种情况成为最常见的情况,因为它是最有效的序列。然而,在许多情况下,控件最终可能在重新测量之前被重新排列和/或重新渲染。这是 WPF 中事件的合法顺序(以允许灵活的布局技术),但它并不常见,因此通常未经过测试。

正确实现的 Adorner 或其他 UIElement 将在渲染可能受到影响的任何时候小心调用 InvalidateVisual() 除非仅更改了 AffectsRender 依赖属性。

就您而言,装饰器的大小显然会影响渲染。大小属性不是 AffectsRender 依赖属性,因此当它们发生变化时需要手动调用 InvalidateVisual()。如果不这样做,WPF 可能永远不知道重新渲染您的装饰器。

在您的情况下发生的情况可能是这样的:

  • 布局完成并且 LayoutChanged 事件触发
  • 上的大小变化
  • AdornerLayer 发现 AdornedElement AdornerLayer 安排您的装饰器进行重新测量、重新布局和重新渲染。
  • 某些因素会导致调用 Arrange(),从而导致重新布局和重新渲染在重新调整之前发生。 -措施。这会导致 WPF 认为装饰器不再需要重新布局或重新渲染。
  • 布局引擎检测到装饰器需要测量并调用 Measure
  • 装饰器的 MeasureOverride 重新计算所需的大小但不会告诉 WPF 装饰器需要重新渲染< /em>
  • 布局引擎决定没有什么可做的,因此装饰器永远不会重新渲染

你可以采取什么措施来修复它

解决方案当然是修复 <每当重新测量控件时,通过调用 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() 不会执行任何操作。

WPF has a built-in mechanism to cause all Adorners to be remeasured, rearranged, and rerendered whenever the corresponding AdornedElement changes size, position, or transform. This mechanism requires you to follow certain rules when coding your adorner, not all of which are documented as clearly as they ought to be.

I will first answer your title question of why your adorner doesn't consistenty re-render, then explain the best way to fix it.

Why the adorner doesn't re-render

Whenever an AdornerLayer receives a LayoutChanged notification it scans each of its Adorners to see if the AdornedElement has changed in size, position or transform. If so, it sets flags to force the Adorner to measure, arrange, and render again -- roughly equivalent to InvalidateMeasure(); InvaliateArrange(); InvalidateVisual();.

What normally happens in this situation is that the control is first measured, then arranged, then rendered. In fact, WPF tries to make this the most common case because it is the most efficient sequence. However there are many situations where a control can end up being rearranged and/or rerendered before it is remeasured. This is a legitimate order of events in WPF (to allow flexible layout techniques), but it is not common so it is often not tested.

A correctly implemented Adorner or other UIElement will be careful to call InvalidateVisual() any time the rendering may be affected unless only AffectsRender dependency properties were changed.

In your case, your adorner's size clearly affect rendering. The size properties are not AffectsRender dependency properties, so it is necessary to manualy call InvalidateVisual() when they change. If you don't, WPF may never know to re-render your adorner.

What is happening in your situation is probably this:

  • Layout completes and the LayoutChanged event fires
  • AdornerLayer discovers the size change on your AdornedElement
  • AdornerLayer schedules your adorner for re-measure, re-layout, and re-render
  • Something causes Arrange() to be called which causes the re-layout and re-render to happen before the re-measure. This causes WPF to think the adorner no longer needs a re-layout or re-render.
  • The layout engine detects that the adorner needs measuring and calls Measure
  • The adorner's MeasureOverride recomputes the desired size but does nothing to tell WPF the adorner needs to re-render
  • The layout engine decides there is nothing more to be done and so the adorner never re-renders

What you can do to fix it

The solution is, of course, to fix the bug in the Adorner by calling InvalidateVisual() whenever the control is re-measured, like this:

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

Doing this will cause your Adorner to consistently obey all the rules of WPF, so it will work as expected in all situations. This is also the most efficient solution, since InvalidateVisual() will do nothing at all except in those cases where it is really needed.

三五鸿雁 2024-09-01 18:34:13

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

这基本上来自这篇文章: 链接

You need to invoke the dispatcher on the panel. Add a handler to the TextBox SizeChanged event:

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

This basically comes from this post: Link

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文