Почему я должен использовать UIElement.UpdateLayout?
У нас довольно большое бизнес-приложение WPF, и я работаю над переоснащением существующего отчета WPF FixedPage/FixedDocument.
Это несколько загруженная экосистема. У нас есть встроенный генератор форм с множеством различных элементов управления, которые вы можете надеть (например, мини-встроенная визуальная студия). Все это прекрасно работает. Вы заполняете форму на экране, а затем можете распечатать (в XPS) идентичную копию на стандартной бумаге размером 8,5x11.
В коде мы разбиваем этот отчет на вертикальные куски. Скажем, каждый кусок будет на дюйм или два на печатном листе бумаги. Вот как мы справляемся с нумерацией страниц. Если следующий блок слишком велик для страницы, мы делаем NewPage() и повторяем. Как я уже говорил, это работало нормально.
У WPF огромная кривая обучения, и я возвращался к старому коду и рефакторингу, а также успешно работал с DataTemplates, строго типизированными ViewModels и универсальными ContentControls, чтобы уменьшить размер нашего кода. Генератор экранных форм все еще работает, но отчет FixedDocument стал странным.
Возвращаясь к этим вертикальным фрагментам, мы печатаем пользовательские формы на бумаге в виде отдельных элементов управления Grid. Ничего фантастического. Каждая сетка (как я упоминал выше) может иметь высоту в один или два дюйма и содержать произвольную смесь флажков, радиокнопок, текстовых блоков и т. Д.
Когда сетки содержали эти стандартные (стандартные) элементы управления MS WPF, я мог делать это целый день:
System.Windows.Controls.Grid g = .....
g.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
g.Arrange(new Rect(g.DesiredSize));
И получить обратно правильные размеры, т.е. 100 х 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 подберет эту самую нижнюю табличку данных. Этот шаблон, в свою очередь, будет использовать меньший шаблон выше.
Модный конвертер просто устанавливает запас. Это может показаться странным, но все это правильно отображается на экране в родительском пользовательском контроле. Это все правильный размер и обоснование и все такое.
На стороне печатного отчета (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 дорог и его следует избегать, и я бы предпочел не вызывать его в каждом разделе сетки, прежде чем добавить его в свою FixedPage отчета FixedDocument. Там может быть десятки или даже сотни итераций. И, опять же, если в Grid есть регулярные элементы управления WPF, без каких-либо ContentControls и необычного поиска и поиска шаблонов данных, измерение работает нормально без вызова UpdateLayout.
Любой совет? Спасибо!
Я просто не понимаю, почему возникла необходимость начать называть его, как только я начал использовать движок Xaml. Такое ощущение, что меня наказывают за использование расширенных функций.
2 ответа
Это сложно объяснить, но позвольте мне попробовать использовать простые слова... В wpf все работает с диспетчером. Кроме того, как вы, возможно, уже знаете, диспетчер имеет дело с задачами, упорядоченными по приоритету.
Например, сначала инициализируется элемент управления, затем запускается привязка, затем обновляются значения, в конце - все, что измеряется... и т. Д. И т. Д.
То, что вам удалось как-то, установив все эти contentcontrol внутри содержимого contentcontrol, вы испортили этот порядок
Вызов UpdateLayout в основном заставляет диспетчера завершить свою работу в макете, ожидающую выполнения, чтобы вы могли впоследствии работать с чистым макетом
Винт с диспетчером довольно распространен в wpf, так как некоторые элементы управления или значения могут быть добавлены позже, что заканчивается переизмериванием.
В вашем случае вы, кажется, создаете все сразу в одном вызове метода, не позволяя диспетчеру перевести дух. Поэтому вам нужен метод UpdateLayout для нормализации очереди диспетчера.
Я надеюсь, это поможет вам. Вы также можете решить свою проблему с помощью Dispatcher.BeginInvoke.
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));
Я нашел еще одну статью об этом подходе.