Ok, I'm answering this to show people why CodeNaked's answer is correct, but with an asterisk if you will, and also to provide a work-around. But in good SO-citizenship, I'm still marking his as answered since his answer led me here.
Update: I've since moved the accepted answer to here for two reasons. One, I want people to know there is a solution to this (most people only read the accepted answer and move on) and two, considering he has a rep of 25K, I don't think he'd mind if I took it back! :)
Here's what I did. To test this, I created this subclass...
public class TestPanel : DockPanel
{
protected override Size MeasureOverride(Size constraint)
{
System.Console.WriteLine("MeasureOverride called for " + this.Name + ".");
return base.MeasureOverride(constraint);
}
protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
{
System.Console.WriteLine("ArrangeOverride called for " + this.Name + ".");
return base.ArrangeOverride(arrangeSize);
}
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
System.Console.WriteLine("OnRender called for " + this.Name + ".");
base.OnRender(dc);
}
}
...which I laid out like this (note that they are nested):
<l:TestPanel x:Name="MainTestPanel" Background="Yellow">
<Button Content="Test" Click="Button_Click" DockPanel.Dock="Top" HorizontalAlignment="Left" />
<l:TestPanel x:Name="InnerPanel" Background="Red" Margin="16" />
</l:TestPanel>
When I resized the window, I got this...
MeasureOverride called for MainTestPanel.
MeasureOverride called for InnerPanel.
ArrangeOverride called for MainTestPanel.
ArrangeOverride called for InnerPanel.
OnRender called for InnerPanel.
OnRender called for MainTestPanel.
but when I called InvalidateVisual
on 'MainTestPanel' (in the button's 'Click' event), I got this instead...
ArrangeOverride called for MainTestPanel.
OnRender called for MainTestPanel.
Note how none of the measuring overrides were called, and only the ArrangeOverride for the outer control was called.
It's not perfect as if you have a very heavy calculation inside ArrangeOverride
in your subclass (which unfortunately we do) that still gets (re)executed, but at least the children don't fall to the same fate.
However, if you know none of the child controls have a property with the AffectsParentArrange bit set (again, which we do), you can go one better and use a Nullable Size
as a flag to suppress the ArrangeOverride logic from re-entry except when needed, like so...
public class TestPanel : DockPanel
{
Size? arrangeResult;
protected override Size MeasureOverride(Size constraint)
{
arrangeResult = null;
System.Console.WriteLine("MeasureOverride called for " + this.Name + ".");
return base.MeasureOverride(constraint);
}
protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
{
if(!arrangeResult.HasValue)
{
System.Console.WriteLine("ArrangeOverride called for " + this.Name + ".");
// Do your arrange work here
arrangeResult = base.ArrangeOverride(arrangeSize);
}
return arrangeResult.Value;
}
protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
System.Console.WriteLine("OnRender called for " + this.Name + ".");
base.OnRender(dc);
}
}
Now unless something specifically needs to re-execute the arrange logic (as a call to MeasureOverride does) you only get OnRender, and if you want to explicitly force the Arrange logic, simply null out the size, call InvalidateVisual and Bob's your uncle! :)
Hope this helps!