Почему я должен использовать 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 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 выберет самый нижний шаблон данных. Затем этот шаблон, в свою очередь, будет использовать меньший из приведенных выше.

Причудливый преобразователь просто устанавливает маржу. Это может быть неудобно читать, но все это правильно отображается на экране в родительском пользовательском элементе управления. Это все правильный размер и обоснование и все такое.

Что касается печатного отчета (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. Итераций может быть несколько десятков, а то и сотен. И, опять же, если в сетке есть обычные элементы управления WPF, без каких-либо ContentControls и причудливого поиска и поиска шаблонов данных, измерение работает нормально без вызова UpdateLayout.

Любой совет? Спасибо!

Я просто не понимаю, почему возникла необходимость вызывать его, как только я начал использовать движок Xaml. Такое ощущение, что меня наказывают за использование расширенных функций.


person John Doh    schedule 12.01.2015    source источник


Ответы (2)


Это сложно объяснить, но давайте я попробую простыми словами... В wpf все работает с диспетчером. Кроме того, вы, возможно, уже знаете, что диспетчер работает с задачами, упорядоченными по приоритету.

Например, сначала инициализируется элемент управления, затем запускается привязка, затем обновляются значения, в конце все, что измеряется.. и т. д. и т. д.

Что вам удалось каким-то образом, так это установить все эти элементы управления контентом внутри элементов управления контентом, вы напортачили с этим порядком.

Вызов UpdateLayout в основном заставляет диспетчера завершить свою ожидающую работу в макете, чтобы впоследствии вы могли работать с чистым макетом.

Накрутка с диспетчером довольно распространена в wpf, поскольку некоторые элементы управления или значения могут быть добавлены позже, что приводит к повторному измерению вещей.

В вашем случае вы, кажется, создаете все сразу в одном вызове метода, не позволяя диспетчеру перевести дух. Поэтому вам нужен метод UpdateLayout для нормализации очереди диспетчеров.

Я надеюсь, это поможет вам. Вы также можете решить свою проблему с помощью Dispatcher.BeginInvoke.

person dev hedgehog    schedule 12.01.2015
comment
Я думаю, что вы на что-то здесь. Если я обойду измерительный код и просто добавлю сетку в свой отчет (текущая FixedPage FixedDocument), произойдет нечто очень интересное. Сначала сетка измеряется, а затем упорядочивается. Я прошел через это. У него та же проблема, что и уrangeBounds, который равен 0,0. Затем по какой-то причине он возвращается и повторяет измерение. Однако во второй раз он выдает фактические/истинные размеры сетки. Хотел бы я знать, почему это повторяется. Это может дать мне лучшее понимание того, что мне не хватает - person John Doh; 12.01.2015
comment
Мне трудно сказать, я не знаю вашего кода, но да, вот как все работает :) - person dev hedgehog; 12.01.2015
comment
На втором проходе я посмотрел внешний код в стеке вызовов. Второй проход — это когда он фактически получает правильные размеры элемента управления. И я заметил, что механизм WPF делает это сам, вызывая UpdateLayout. О первом прошлом, когда его нет. Очень интересно. Ну, по крайней мере, я знаю, как получить свои измерения, я все еще хотел бы понять, почему это работает в первый раз, когда там есть только обычные элементы управления, которые не являются шаблонными или ContentControls. Вероятно, по указанным вами причинам. Если я найду что-нибудь еще, я обновлю. еще раз спасибо - person John Doh; 12.01.2015
comment
Как вы, возможно, уже знаете, Microsoft обнародовала свой исходный код. Вот ссылка: referencesource.microsoft.com Если вы внимательно посмотрите туда, то увидите, что Я говорил об этом в своем ответе. Мне потребовалась целая вечность, чтобы понять измерения в wpf, но эта ссылка — подходящее место для начала поиска объяснений. - person dev hedgehog; 13.01.2015

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

Я нашел другая статья об этом подходе.

person Der_Meister    schedule 19.01.2018