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

时间:2015-01-12 02:18:28

标签: c# wpf visual-studio wpf-controls wpf-4.0

我们有一个相当大的WPF业务应用程序,我正在研究现有的WPF FixedPage / FixedDocument报告的重组。

这是一个有点繁忙的生态系统。我们有一个内置的表单生成器,你可以使用许多不同的控件(就像一个迷你内置的可视化工作室)。一切正常。您在屏幕上填写表格,然后您可以打印(到XPS)相同的副本到标准的8.5x11纸张。

在代码中,我们将此报告分解为垂直块。假设每张纸块在打印的纸上都是一英寸或两英寸高。这就是我们处理分页的方式。如果下一个块对于页面太高,我们执行NewPage()并重复。正如我所提到的,这很好。

WPF有一个巨大的学习曲线,我一直在回顾旧代码和重构事物,并愉快地使用DataTemplates,强类型ViewModels和通用ContentControls来减少代码的大小。屏幕上的表单生成器仍然有效,但FixedDocument报告已经变得很奇怪。

回到那些垂直切片,我们将用户的表单打印为纸张作为单独的网格控件。没有什么花哨。每个网格(如上所述)可能是一英寸或两英寸高,包含复选框,单选按钮,文本块等随机混合。

当网格包含这些库存(标准)MS WPF控件时,我可以整天这样做:

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

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

恢复正确尺寸,即100 x 67。

现在,有时网格只有一个控件 - 如果你愿意的话就是一个标题(即“本月的时间表”)。添加到该网格的唯一子控件是ContentControl。

ContentControl只绑定到ViewModel:

<ContentControl Content="{Binding}" />

然后资源字典中有两个DataTemplates来获取此绑定。在这里,我将展示:

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

ContentControl将获取最底部的datatemplate。然后,该模板将使用上面较小的模板。

花式转换器只是设置了一个余量。阅读可能很难看,但这一切都在父用户控件的屏幕上正确显示。这是所有正确的尺寸和理由以及所有这些。

在打印报告方面(XPS),我必须在代码中创建这些控件并测量它们以查看它们是否适合当前的FixedPage。当我去做这一步时:(在包含此ContentControl的网格上)

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

我回到0,0大小。即使它应该像730x27一样。再次,在屏幕上,托管在UserControl中,这一切都很好。只是尝试实例化它并纯粹在代码中测量它失败了。我已经确认控件已添加到网格中,其行和列集已添加到Children集合中等等...

如果我使用 UpdateLayout 调用添加这两个语句,就像这样,那么它可以工作:

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

我一直在阅读UpdateLayout很昂贵且要避免,在将其添加到FixedDocument报告的FixedPage之前,我宁愿不在每个网格部分调用它。可能有几十甚至几百次迭代。而且,再次,如果网格中有常规的WPF控件,没有任何ContentControls和花哨的查找并查找数据窗口,那么测量工作正常,没有UpdateLayout调用。

有什么建议吗?谢谢!

我只是不明白为什么有必要在我开始使用Xaml引擎后开始调用它。几乎感觉我因使用高级功能而受到惩罚。

2 个答案:

答案 0 :(得分:5)

很难解释,但让我尝试使用简单的单词......在wpf中,一切都与调度员一起工作。此外,您可能已经知道调度员处理按优先级排序的任务。

例如,首先控制器正在初始化,然后触发绑定,然后更新值,最后测量所有内容等等。

你以某种方式管理的是通过在contentcontrol中设置所有内容控件,你搞砸了那个订单

调用UpdateLayout基本上会强制调度程序在布局中完成挂起的工作,以便您可以在之后使用干净的布局

在wpf中使用调度程序很常见,因为某些控件或值可能会在以后添加,最终会重新测量。

在您的情况下,您似乎在一次方法调用中一次创建所有内容而不让调度员屏住呼吸。因此,您需要UpdateLayout方法来规范化调度程序队列。

我希望这会对你有所帮助。您也可以使用Dispatcher.BeginInvoke来解决您的问题。

答案 1 :(得分:1)

UpdateLayout在我的情况下不起作用。我不得不等到调度员完成布局任务的处理。

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

我发现了another article这种方法。