wpf 为什么我必须使用 UIElement.UpdateLayout?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27894477/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why do I have have to use UIElement.UpdateLayout?
提问by John Doh
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. As I mentioned, this was working fine.
在代码中,我们将此报告分解为垂直块。假设每个块在打印的纸上有一两英寸高。这就是我们处理分页的方式。如果下一个块对于页面来说太高,我们执行 NewPage() 并重复。正如我提到的,这工作正常。
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.
WPF 有一个巨大的学习曲线,我一直在回顾旧代码并重构东西,并愉快地使用 DataTemplates、强类型 ViewModel 和通用 ContentControls 以减少我们的代码大小。屏幕上的表单生成器仍然有效,但 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.
回到那些垂直切片,我们将用户的表单作为单独的 Grid 控件打印到纸上。没有什么花哨。每个网格(正如我上面提到的)可能是一英寸或两英寸高,包含复选框、单选按钮、文本块等的任意混合。
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, i.e. 100 x 67.
并取回适当的尺寸,即 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.
现在,有时网格只有一个控件 - 如果您愿意,可以使用标题(即“本月的日程表”)。添加到该网格的唯一子控件是 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. Here, I'll show that:
资源字典中有两个 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>
The ContentControl will pick up that bottom-most datatemplate. That template will then in turn use the smaller one above.
ContentControl 将选取最底层的数据模板。然后该模板将依次使用上面较小的模板。
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)
在打印报告方面 (XPS),我必须在代码中创建这些控件并测量它们以查看它们是否适合当前的 FixedPage。当我去做这一步时:(在包含这个 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...
我找回 0,0 大小。例如,即使它应该像 730x27。同样,在屏幕上,托管在 UserControl 中,这一切正常。只是试图实例化它并纯粹在代码中测量它失败了。我已经确认控件已添加到网格中,设置了行和列,已添加到 Children 集合中,等等...
If I prepend those two statements with an UpdateLayoutcall, 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. 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.
我一直在读到 UpdateLayout 很昂贵并且应该避免,并且在将它添加到我的 FixedDocument 报告的 FixedPage 之前,我宁愿不在每个网格部分上调用它。可能有几十甚至几百次迭代。而且,如果 Grid 中有常规的 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. It almost feels like I'm being punished for using the advanced features.
我只是不明白为什么在我开始使用 Xaml 引擎后就必须开始调用它。几乎感觉就像我因使用高级功能而受到惩罚。
回答by dev hedgehog
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.
解释起来很复杂,但让我尝试使用简单的词...在 wpf 中,一切都适用于调度员。此外,您可能已经知道调度程序处理按优先级排序的任务。
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 东西中设置所有这些 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 解决您的问题。
回答by Der_Meister
UpdateLayoutdoes not work in my case. I had to wait until dispatcher finishes processing of the layout tasks.
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));
I found another articleabout this approach.

