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.