为什么我的装饰器在应用于的元素发生变化时不重新渲染?
在我正在构建的用户界面中,每当面板中的某个控件获得焦点时,我想装饰面板。因此,我处理 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
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,如下所示:这样做将使您的 Adorner 始终遵守 WPF 的所有规则,因此它将像预期在所有情况下。这也是最有效的解决方案,因为除了真正需要的情况外,InvalidateVisual() 不会执行任何操作。
WPF has a built-in mechanism to cause all
Adorners
to be remeasured, rearranged, and rerendered whenever the correspondingAdornedElement
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 theAdorner
to measure, arrange, and render again -- roughly equivalent toInvalidateMeasure(); 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 otherUIElement
will be careful to callInvalidateVisual()
any time the rendering may be affected unless onlyAffectsRender
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 callInvalidateVisual()
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:
LayoutChanged
event firesAdornerLayer
discovers the size change on yourAdornedElement
AdornerLayer
schedules your adorner for re-measure, re-layout, and re-renderArrange()
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.Measure
MeasureOverride
recomputes the desired size but does nothing to tell WPF the adorner needs to re-renderWhat you can do to fix it
The solution is, of course, to fix the bug in the
Adorner
by callingInvalidateVisual()
whenever the control is re-measured, like this: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.您需要调用面板上的调度程序。向 TextBox SizeChanged 事件添加处理程序:
这基本上来自这篇文章: 链接
You need to invoke the dispatcher on the panel. Add a handler to the TextBox SizeChanged event:
This basically comes from this post: Link