Почему я должен использовать 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));

Я нашел еще одну статью об этом подходе.

Другие вопросы по тегам