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

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

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

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

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

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

回到那些垂直切片,我们将用户的表单作为单独的网格控件打印到纸上。没有什么花哨。每个网格(正如我上面提到的)可能有一两英寸高,包含复选框、单选按钮、文本 block 等的任意组合。

当网格包含这些常用(标准)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 简单地绑定(bind)到 ViewModel:

<ContentControl Content="{Binding}" />

然后在资源字典中有两个 DataTemplates 来获取这个绑定(bind)。在这里,我将证明:

<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 将选取最底部的数据模板。然后该模板将依次使用上面较小的模板。

花哨的转换器只是设置了一个边距。阅读起来可能很麻烦,但这一切都在父用户控件的屏幕上正确显示。所有这些都是正确的大小和理由。

在打印报表端 (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 之前,我不想在每个网格部分调用它。可能有数十次甚至数百次迭代。而且,如果 Grid 中有常规 WPF 控件,没有任何 ContentControls 和花哨的查找和查找数据模板,则测量工作正常而无需 UpdateLayout 调用。

有什么建议吗?谢谢!

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

最佳答案

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

例如,首先初始化一个控件,然后触发绑定(bind),然后更新值,最后测量所有内容......等等

你以某种方式管理的是通过在 contentcontrol 东西中设置所有那些 contentcontrol,你搞砸了那个顺序

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

在 wpf 中使用调度程序是很常见的,因为稍后可能会添加一些控件或值,这最终会导致重新测量。

在您的情况下,您似乎是在一次方法调用中同时创建所有内容,而没有让调度员喘口气。因此,您需要 UpdateLayout 方法来标准化调度程序队列。

希望对您有所帮助。您还可以使用 Dispatcher.BeginInvoke 解决您的问题。

关于c# - 为什么我必须使用 UIElement.UpdateLayout?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27894477/

相关文章:

c# - 第二次调用 HttpWebRequest.GetRequestStream() 抛出超时异常

c# - 无法使用异步/等待连接到异步服务器

c# - 绑定(bind)到 List<> 依赖属性的 WPF 上下文菜单

c# - 如何从datagrid的数据导出PDF?

c# - 将对象转换为 JSON,然后将该 JSON 下载为文本文件 [Asp.net core]

c# - 如何获取指定的消息标签?

c# - INotifyPropertyChanged 和计算属性

visual-studio - 当前上下文中不存在“TeleportClassicalMessage”

c# - 如何设置表单可见区域的大小,减去标题和边框?

visual-studio - 连接到甲骨文