Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In WPF how can I get the rendered size of a control before it actually renders?

I'm doing custom rendering in a Decorator subclass. Our rendering requires creating complex geometries which only neded to be re-created when the actual rendered size changes. As such, I have moved the geometry creation into its own function called UpdateGeometry which creates, then freezes the geometry for use in OnRender. This new function only needs to be called in response to a change in ActualWidth or ActualHeight.

Even better, it looks like we should be able to simply override OnRenderSizeChanged, which according to the documentation states...

"When overridden in a derived class, participates in rendering operations that are directed by the layout system. This method is invoked after layout update, and before rendering, if the element's RenderSize has changed as a result of layout update."

However, regardless if I'm using the override or listening to the property change notifications of ActualWidth and ActualHeight, my logging consistently shows OnRender as happening first! Um... Wha??

To be sure it wasn't something I was doing in my code, I created a bare-bones test decorator subclass and added logging there, both on entry and exit to the overrides. Here is the entire class...

using System;
using System.Windows.Controls;

public class TestControl : Decorator
{

    protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
    {
        Console.WriteLine("OnRender Entered");

        base.OnRender(drawingContext);

        Console.WriteLine("OnRender Exited");
    }

    protected override void OnRenderSizeChanged(System.Windows.SizeChangedInfo sizeInfo)
    {
        Console.WriteLine("OnRenderSizeChanged Entered");

        base.OnRenderSizeChanged(sizeInfo);

        Console.WriteLine("OnRenderSizeChanged Exited");
    }

}

And as I feared... here is the output...

OnRender Entered
OnRender Exited
OnRenderSizeChanged Entered
OnRenderSizeChanged Exited

So what am I missing here?

More importantly, how can I get the ActualWidth and ActualHeight values after the layout subsystem has done its job, but before the control is rendered so I can create the geometry before it's needed in the OnRender override?

My latest implementation overrides ArrangeOverride as the value that's passed in there is a size containing what the ActualWidth and ActualHeight values should be after the core layout system takes into consideration HorizontalAlignment and VerticalAlignment with values of 'Stretch', minimums and maximums, etc, but what they actually are depends on the value that's returned from that override so it's a little more complex than that.

Either way, I'm still wondering why the OnRenderSizeChanged call doesn't happen when it's supposed to. Thoughts?

Mark

like image 249
Mark A. Donohoe Avatar asked Apr 13 '11 16:04

Mark A. Donohoe


1 Answers

In general, you should be able to get the correct size from ArrangeOverride. This doesn't include things like Margin, but that probably shouldn't be taken into account. You could either use the size passed as a parameter as your "render" size or use the return value of the base.ArrangeOverride call.

EDIT:

The OnRender method is called from the Arrange method, after OnArrangeOverride is ultimately called. The OnRenderSizeChanged on the other hand is called from UpdateLayout, which is effectively dispatched to be executed all at once for a given section of the visual tree. This is why the OnRenderSizeChanged is called after the OnRender.

The documentation may refer to the "rendering" as in actually rendered to the screen, not when OnRender is called. WPF can cache the rendering instructions for a given element and execute them when needed. So the fact that OnRender is called before OnRenderSizeChanged, doesn't mean it's actual rendering instructions are committed to the screen at that time.

You can modify your OnRenderSizeChanged to force OnRender to be called again using:

protected override void OnRenderSizeChanged(System.Windows.SizeChangedInfo sizeInfo)
{
    Console.WriteLine("OnRenderSizeChanged Entered");

    base.OnRenderSizeChanged(sizeInfo);
    this.InvalidateVisual();

    Console.WriteLine("OnRenderSizeChanged Exited");
}

You may also want to skip your OnRender code if RenderSize is "0,0".

like image 192
CodeNaked Avatar answered Sep 21 '22 18:09

CodeNaked