Как работает отрисовка в фреймворках на основе XAML

от автора

Довольно часто мне приходится видеть вопросы, причиной которого либо непонимание как работают под капотом фреймоворки на базе XAML (я работал с WPF и AvaloniaUI, но имею все основания считать, что эта информация применима и к другим их родственникам).

Сегодня хочу осветить один из таких аспектов работы, а именно то, как выполняется отрисовка разных данных и почему это вообще работает. В иллюстрирующих примерах буду использовать фреймворк AvaloniaUI.

Давайте начнём с одного простого примера, а потом будем развивать мысль дальше:

<Button>Hello world</Button>  <Button>   <StackPanel Orientation="Horizontal">     <Image Source="/Assets/Help.png" />     <AccessText>_Help</AccessText>   </StackPanel> </Button>
Как это выглядит

Как это выглядит

Здесь две кнопки, на одной просто текст, на другой — некоторое содержимое, содержащее элементы самого фреймворка. И то и другое работает как ожидается, но по сути здесь работает два разных механизма (на самом деле всё ещё интереснее, но и до этого доберёмся).

Хочу сразу обратить внимание на один важный момент: свойство Content у кнопки, унаследованный от ContentControl имеет тип object — это важно для дальнейшего повествования.

Давайте попробуем объяснить текущее поведение: если содержимое — строка, то она отображается как текст. Если содержимое — часть фреймворка, то оно знает как себя рисовать, и поэтому отрисовка ему и делегируется. Но могут быть и иные варианты, верно?

Сделаем простенькую ViewModel со свойством некоторого нашего типа и попробуем его расположить в кнопке:

public class MainViewModel {     public ButtonContent ButtonContent { get; } = new() { Action = "Do something" }; }  public class ButtonContent {     public required string Action { get; init; } }
<Button Content="{Binding ButtonContent}" />

И что же мы видим?

Видим вызванный ToString

Видим вызванный ToString

Попробуем добавить шаблон данных:

<Button Content="{Binding ButtonContent}" >   <Button.DataTemplates>     <DataTemplate DataType="vm:ButtonContent">       <TextBlock Text="{Binding Action}" FontWeight="Bold" />     </DataTemplate>   </Button.DataTemplates> </Button>
Шаблон работает

Шаблон работает

А что, если мы сделаем шаблон для строки?

<Button Content="Hello world" >   <Button.DataTemplates>     <DataTemplate DataType="system:String">       <TextBlock>         <Run Text="“" />         <Run Text="{Binding }" TextDecorations="Underline" />       </TextBlock>     </DataTemplate>   </Button.DataTemplates> </Button>
Забавно, но это тоже работает!

Забавно, но это тоже работает!

Итак, выводим общее правило:

  1. если объект является частью фреймворка, то он сам себя отрисовывает

  2. в противном случае для него ведётся поиск шаблона, и если он найден, то для отрисовки используется этот шаблон

  3. если подходящего шаблона не нашлось, то на объекте вызывается ToString, он заворачивается в текстовый элемент (TextBlock или AccessText), и уже он рисуется.

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

Как понять, когда такие правила применимы? Очень просто: если свойство, отвечающее за содержимое, имеет тип object, или есть коллекция объектов, или коллекция не generic, то почти наверняка это правило работает.


Работаете с графическими фреймворками и всё ещё не присоединились к чату в телеграме? Надо срочно исправить эту недоработку: https://t.me/AvaloniaRU

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Используете ли Вы фреймвоки, основанные на XAML?

44.44% Да, регулярно4
33.33% Иногда3
0% Нет, просто любопытствую0
22.22% Нет2

Проголосовали 9 пользователей. Воздержался 1 пользователь.

ссылка на оригинал статьи https://habr.com/ru/articles/821795/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *