26

我们正在控件子类的OnRender. 此绘图代码基于外部触发器和数据。因此,每当触发器触发时,我们都需要根据该数据重新渲染控件。我们正在尝试做的是找出如何强制控件重新渲染但不经过整个布局传递。

如上所述,我看到的大多数答案都围绕着使Visual布局无效,这会强制执行新的措施并安排非常昂贵的通道,尤其是对于像我们这样非常复杂的视觉树。但同样,布局没有改变,VisualTree 也没有改变。唯一能做的就是以不同方式呈现的外部数据。因此,这严格来说是一个纯粹的渲染问题。

同样,我们只是在寻找一种简单的方法来告诉控件它需要重新执行OnRender。我见过一个“hack”,您可以在其中创建一个新的DependencyProperty并将其注册为“AffectsRender”,当您想刷新控件时,您只需将其设置为某个值,但我对默认实现内部发生的事情更感兴趣这些属性:它们调用来影响该行为的东西。


更新:

好吧,看起来没有任何这样的调用,因为即使AffectsRender标志仍然在内部导致 Arrange 传递(根据下面 CodeNaked 的回答),但我发布了第二个答案,显示了内置行为以及工作 -以禁止您的布局密码以简单的可为空大小作为标志运行。见下文。

4

4 回答 4

20

不幸的是,您必须调用InvalidateVisual,它在内部调用InvalidateArrange。该OnRender方法作为排列阶段的一部分被调用,因此您需要告诉 WPF 重新排列控件(InvalidateArrange 执行此操作)并且它需要重绘(InvalidateVisual 执行此操作)。

FrameworkPropertyMetadata.AffectsRender选项只是告诉 WPFInvalidateVisual在关联属性更改时调用。

如果您有一个覆盖 OnRender 并包含多个后代控件的控件(我们称之为 MainControl),那么调用 InvalidateVisual 可能需要重新排列甚至重新测量后代控件。但我相信 WPF 已经进行了优化,以防止后代控件在其可用空间不变的情况下被重新排列。

您可以通过将渲染逻辑移动到单独的控件(例如 NestedControl)来解决此问题,该控件将是 MainControl 的可视子控件。MainControl 可以自动将其添加为可视子项或作为其 ControlTemplate 的一部分,但它必须是 z 顺序中最低的子项。然后,您可以在 MainControl 上公开一个InvalidateNestedControl类型方法,该方法将在 NestedControl 上调用 InvalidateVisual。

于 2011-10-18T06:09:35.207 回答
12

好的,我回答这个问题是为了向人们展示为什么 CodeNaked 的答案是正确的,但如果你愿意,可以加上星号,并提供一种解决方法。但是在良好的 SO-citizenship 中,我仍然将他标记为已回答,因为他的回答将我带到了这里。

更新:我已将接受的答案移至此处,原因有两个。一,我想让人们知道有一个解决方案(大多数人只阅读接受的答案并继续前进),二,考虑到他有 25K 的代表,我认为他不会介意我收回它!:)

这就是我所做的。为了测试这一点,我创建了这个子类......

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

}

...我这样布置(注意它们是嵌套的):

<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>

当我调整窗口大小时,我得到了这个......

MeasureOverride called for MainTestPanel.
MeasureOverride called for InnerPanel.
ArrangeOverride called for MainTestPanel.
ArrangeOverride called for InnerPanel.
OnRender called for InnerPanel.
OnRender called for MainTestPanel.

但是当我调用InvalidateVisual'MainTestPanel'(在按钮的'Click'事件中)时,我得到了这个......

ArrangeOverride called for MainTestPanel.
OnRender called for MainTestPanel.

注意没有调用任何测量覆盖,只调用了外部控件的 ArrangeOverride。

这并不完美,好像您ArrangeOverride的子类中有一个非常繁重的计算(不幸的是我们这样做)仍然会被(重新)执行,但至少孩子们不会落入同样的命运。

但是,如果您知道没有一个子控件具有设置了 AffectsParentArrange 位的属性(同样,我们这样做了),您可以更好地使用 NullableSize作为标志来禁止重新输入 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 和 Bob 是你的叔叔!:)

希望这可以帮助!

于 2011-10-18T07:43:05.150 回答
5

除非控件的大小发生变化,否则您不应该调用InvalidateVisual(),即使那样,还有其他方法可以导致重新布局。

在不改变控件大小的情况下有效地更新控件的视觉效果。使用DrawingGroup. 您创建 DrawingGroup 并将其放入DrawingContext期间OnRender(),然后您可以随时更改Open()DrawingGroup可视绘图命令,WPF 将自动有效地重新呈现 UI 的该部分。RenderTargetBitmap(如果您希望使用可以进行增量更改而不是每次都重绘的位图,也可以使用此技术)

这是它的样子:

DrawingGroup backingStore = new DrawingGroup();

protected override void OnRender(DrawingContext drawingContext) {      
    base.OnRender(drawingContext);            

    Render(); // put content into our backingStore
    drawingContext.DrawDrawing(backingStore);
}

// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {            
    var drawingContext = backingStore.Open();
    Render(drawingContext);
    drawingContext.Close();            
}

private void Render(DrawingContext drawingContext) {
    // put your render code here
}
于 2017-06-08T02:23:02.707 回答
1

这是另一个黑客: http: //geekswithblogs.net/NewThingsILearned/archive/2008/08/25/refresh--update-wpf-controls.aspx

简而言之,您调用优先级 DispatcherPriority.Render 调用一些虚拟委托,这将导致任何具有该优先级或更高优先级的东西也被调用,从而导致重新渲染。

于 2011-10-18T08:11:51.643 回答