Pull to refresh

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

Level of difficultyMedium
Reading time2 min
Views2.4K

Довольно часто мне приходится видеть вопросы, причиной которого либо непонимание как работают под капотом фреймоворки на базе 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

Only registered users can participate in poll. Log in, please.
Используете ли Вы фреймвоки, основанные на XAML?
43.9% Да, регулярно18
21.95% Иногда9
21.95% Нет, просто любопытствую9
12.2% Нет5
41 users voted. 8 users abstained.
Tags:
Hubs:
-6
Comments1

Articles