Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I have have to use UIElement.UpdateLayout?

We have a rather large WPF business application and I am working on a retool of an existing WPF FixedPage/FixedDocument report.

It's a somewhat busy ecosystem. We have a built-in forms generator, with lots of different controls you can put on (think like a mini built-in visual studio). All that works fine. You fill in the form on the screen, and then you can print out (to XPS) the identical copy to standard 8.5x11 paper.

In the code, we break out this report into vertical chunks. Say each chunk would be an inch or two tall on a printed piece of paper. This is how we handle pagination. If the next chunk is too tall for the page, we do a NewPage() and repeat. As I mentioned, this was working fine.

WPF has an enormous learning curve and I've been going back over old code and refactoring things and happily working with DataTemplates, strongly typed ViewModels, and generic ContentControls in order to reduce the size of our code. The on-screen forms generator still works, but the FixedDocument report has gotten weird.

Going back to those vertical slices, we print the user's forms to paper as individual Grid controls. Nothing fancy. Each grid (as I mentioned above) may be an inch or two high, containing any random mixture of checkboxes, radiobuttons, textblocks, and so on.

When the grids contained these stock (standard) MS WPF controls, I could do this all day long:

System.Windows.Controls.Grid g = .....

g.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
g.Arrange(new Rect(g.DesiredSize));

And get back proper sizes, i.e. 100 x 67.

Now, sometimes the grids have just one control - a header if you will (i.e. "This Month's Schedule). The only child control added to that grid is a ContentControl.

The ContentControl is simply bound to a ViewModel:

<ContentControl Content="{Binding}" />

There's then two DataTemplates in the resource dictionary that picks up this binding. Here, I'll show that:

<UserControl.Resources>

    <w:MarginConverter x:Key="boilerMargin" />

    <DataTemplate DataType="{x:Type render:BoilerViewModel}">
        <render:RtfViewer
            Width="{Binding Path=Width}"
            TextRTF="{Binding Path=Rtf}"/>
    </DataTemplate>

    <DataTemplate DataType="{x:Type render:Qst2NodeViewModel}">
        <ContentControl Content="{Binding Path=BoilerVm}">
            <ContentControl.Margin>
                <MultiBinding Converter="{StaticResource boilerMargin}">
                    <Binding Path="NodeCaptionVm.Height" />
                    <Binding Path="NodeLeft" />
                </MultiBinding>
            </ContentControl.Margin>
        </ContentControl>
    </DataTemplate>
</UserControl.Resources>

The ContentControl will pick up that bottom-most datatemplate. That template will then in turn use the smaller one above.

The fancy converter just sets a margin. It may be fugly to read, but this all displays correctly on the screen within the parent usercontrol. It's all the right size and justification and all that.

On the printed report side (XPS), I have to create these controls in code and measure them to see if they'll fit on the current FixedPage. When I go to do this step: (on the grid containing this ContentControl)

g.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
g.Arrange(new Rect(g.DesiredSize));

I get back 0,0 size. Even though it should be like 730x27 for instance. Again, on the screen, hosted in a UserControl, this all works fine. Just trying to instantiate it and measure it purely in code fails. I've confirmed that the control is added to the grid, has its row and col set, has been added to the Children collection, etc...

If I prepend those two statements with an UpdateLayout call, like this, then it works:

g.UpdateLayout();  //this fixes it
g.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
g.Arrange(new Rect(g.DesiredSize));

I've been reading that UpdateLayout is expensive and to be avoided, and I'd rather not be calling this on each grid section before I add it to my FixedPage of the FixedDocument report. There could be dozens or even hundreds of iterations. And, again, if the Grid has regular WPF controls in it, without any ContentControls and fancy finding and looking up datatemplates, the measuring works fine without the UpdateLayout call.

Any advice? Thank you!

I just don't understand why it became necessary to start calling it once I started utilizing the Xaml engine. It almost feels like I'm being punished for using the advanced features.

like image 797
John Doh Avatar asked Jan 12 '15 02:01

John Doh


2 Answers

Its complicated to explain that but let me try using plain words... In wpf everything works with dispatcher. Futhermore like you may already know dispatcher deals with tasks ordered by priority.

For example first a control is being initalized, then binding is being triggered, then values are updated, in the end all that is being measured.. etc etc

What you managed somehow is by setting all those contentcontrol inside contentcontrol stuff, you screwed up that order

Calling UpdateLayout basically forces dispatcher to finish its pending work in layout so you can work with clean layout afterwards

Screwing with dispatcher is quite common in wpf since some controls or values may be added later which ends in remeasuring things.

In your case you seem to be creating all at once in one method call without letting dispatcher take a breath. Therefore you need UpdateLayout method to normalize dispatchers queue.

I hope this helps you. You can also solve your issue by using Dispatcher.BeginInvoke.

like image 112
dev hedgehog Avatar answered Nov 03 '22 15:11

dev hedgehog


UpdateLayout does not work in my case. I had to wait until dispatcher finishes processing of the layout tasks.

toPrint.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Application.Current.Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle);
toPrint.Arrange(new Rect(new Point(0, 0), toPrint.DesiredSize));

I found another article about this approach.

like image 2
Der_Meister Avatar answered Nov 03 '22 13:11

Der_Meister