很遗憾,您必须调用InvalidateVisual,它在内部调用InvalidateArrange。 OnRender 方法作为排列阶段的一部分被调用,因此您需要告诉 WPF 重新排列控件(InvalidateArrange 执行此操作)并且它需要重绘(InvalidateVisual 执行此操作)。
FrameworkPropertyMetadata.AffectsRender 选项只是告诉 WPF 在关联属性更改时调用 InvalidateVisual。
如果您有一个覆盖OnRender 并包含多个后代控件的控件(我们称之为MainControl),那么调用InvalidateVisual 可能需要重新排列甚至重新测量后代控件。但我相信 WPF 已经进行了优化,以防止在其可用空间不变的情况下重新排列后代控件。
您可以通过将渲染逻辑移至单独的控件(例如 NestedControl)来解决此问题,该控件将是 MainControl 的可视子控件。 MainControl 可以自动将其添加为可视子项或作为其 ControlTemplate 的一部分,但它必须是 z 顺序中最低的子项。然后,您可以在 MainControl 上公开一个 InvalidateNestedControl 类型的方法,该方法将在 NestedControl 上调用 InvalidateVisual。
这就是我所做的。为了测试这一点,我创建了这个子类...
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);
}
}
...我这样布置(注意它们是嵌套的):
<Button Content="Test" Click="Button_Click" DockPanel.Dock="Top" HorizontalAlignment="Left" />
<l:TestPanel x:Name="InnerPanel" Background="Red" Margin="16" />
当我调整窗口大小时,我得到了这个......
为 MainTestPanel 调用了MeasureOverride。
MeasureOverride 调用了 InnerPanel。
为 MainTestPanel 调用了 ArrangeOverride。
为 InnerPanel 调用了 ArrangeOverride。
OnRender 调用了 InnerPanel。
OnRender 调用 MainTestPanel。
但是当我在“MainTestPanel”上(在按钮的“Click”事件中)调用 InvalidateVisual 时,我得到了这个......
为 MainTestPanel 调用了 ArrangeOverride。
OnRender 调用 MainTestPanel。
注意没有调用任何测量覆盖,只调用了外部控件的 ArrangeOverride。
这并不完美,好像您在子类的 ArrangeOverride 中进行了非常繁重的计算(不幸的是,我们这样做了)仍然会被(重新)执行,但至少孩子们不会落入同样的命运。
但是,如果您知道没有一个子控件具有设置了 AffectsParentArrange 位的属性(同样,我们这样做了),您可以更好地使用 Nullable Size 作为标志来抑制 ArrangeOverride 逻辑重新除非需要,否则进入,就像这样......
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);
}
}
现在,除非有什么特别需要重新执行排列逻辑(就像调用 MeasureOverride 那样),否则您只会获得 OnRender,如果您想显式强制排列逻辑,只需将大小清零,调用 InvalidateVisual。