简体   繁体   English

为什么我必须使用UIElement.UpdateLayout?

[英]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. 我们有一个相当大的WPF业务应用程序,我正在研究现有的WPF FixedPage / FixedDocument报告的重组。

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. 您在屏幕上填写表格,然后您可以打印(到XPS)相同的副本到标准8.5x11纸张。

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. 如果下一个块对于页面太高,我们执行NewPage()并重复。 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. WPF有一个巨大的学习曲线,我一直在回顾旧代码和重构事物,并愉快地使用DataTemplates,强类型ViewModels和通用ContentControls来减少代码的大小。 The on-screen forms generator still works, but the FixedDocument report has gotten weird. 屏幕上的表单生成器仍然有效,但FixedDocument报告已经变得奇怪了。

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: 当网格包含这些库存(标准)MS WPF控件时,我可以整天这样做:

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

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

And get back proper sizes, ie 100 x 67. 并获得适当的尺寸,即100 x 67。

Now, sometimes the grids have just one control - a header if you will (ie "This Month's Schedule). The only child control added to that grid is a ContentControl. 现在,有时网格只有一个控件 - 如果你愿意的话就是一个标题(即“本月的时间表”)。添加到该网格的唯一子控件是ContentControl。

The ContentControl is simply bound to a ViewModel: ContentControl只是绑定到ViewModel:

<ContentControl Content="{Binding}" />

There's then two DataTemplates in the resource dictionary that picks up this binding. 然后在资源字典中有两个DataTemplates来获取这个绑定。 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. ContentControl将获取最底部的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. 在打印报告方面(XPS),我必须在代码中创建这些控件并测量它们以查看它们是否适合当前的FixedPage。 When I go to do this step: (on the grid containing this ContentControl) 当我去做这一步时:(在包含此ContentControl的网格上)

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

I get back 0,0 size. 我回到0,0大小。 Even though it should be like 730x27 for instance. 即使它应该像730x27一样。 Again, on the screen, hosted in a UserControl, this all works fine. 再次,在屏幕上,托管在UserControl中,这一切都很好。 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... 我已经确认控件已添加到网格中,其行和列集已添加到Children集合中,等等...

If I prepend those two statements with an UpdateLayout call, like this, then it works: 如果我使用UpdateLayout调用添加这两个语句,就像这样,那么它的工作原理如下:

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. 我一直在阅读UpdateLayout很昂贵而且要避免,在将其添加到FixedDocument报告的FixedPage之前,我宁愿不在每个网格部分调用它。 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. 而且,再次,如果网格中有常规的WPF控件,没有任何ContentControls和花哨的查找和查找数据窗口,测量工作正常,没有UpdateLayout调用。

Any advice? 有什么建议? Thank you! 谢谢!

I just don't understand why it became necessary to start calling it once I started utilizing the Xaml engine. 我只是不明白为什么有必要在我开始使用Xaml引擎后开始调用它。 It almost feels like I'm being punished for using the advanced features. 几乎感觉我因使用高级功能而受到惩罚。

Its complicated to explain that but let me try using plain words... In wpf everything works with dispatcher. 很难解释,但让我尝试使用简单的单词...在wpf中,一切都与调度员一起工作。 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 你以某种方式管理的是通过在contentcontrol内部设置所有那些内容控件,你搞砸了那个订单

Calling UpdateLayout basically forces dispatcher to finish its pending work in layout so you can work with clean layout afterwards 调用UpdateLayout基本上会强制调度程序在布局中完成其挂起的工作,以便您可以在之后使用干净的布局

Screwing with dispatcher is quite common in wpf since some controls or values may be added later which ends in remeasuring things. 使用调度程序进行拧紧在wpf中非常常见,因为某些控件或值可能会在以后添加,最终会重新测量。

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. 因此,您需要UpdateLayout方法来规范化调度程序队列。

I hope this helps you. 我希望这可以帮助你。 You can also solve your issue by using Dispatcher.BeginInvoke. 您还可以使用Dispatcher.BeginInvoke解决您的问题。

UpdateLayout does not work in my case. UpdateLayout在我的情况下不起作用。 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. 我找到了另一篇关于这种方法的文章

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM