Довольно часто мне приходится видеть вопросы, причиной которого либо непонимание как работают под капотом фреймоворки на базе XAML (я работал с WPF и AvaloniaUI, но имею все основания считать, что эта информация применима и к другим их родственникам).
Сегодня хочу осветить один из таких аспектов работы, а именно то, как выполняется отрисовка разных данных и почему это вообще работает. В иллюстрирующих примерах буду использовать фреймворк AvaloniaUI.
Давайте начнём с одного простого примера, а потом будем развивать мысль дальше:
<Button>Hello world</Button>
<Button>
<StackPanel Orientation="Horizontal">
<Image Source="/Assets/Help.png" />
<AccessText>_Help</AccessText>
</StackPanel>
</Button>
![Как это выглядит Как это выглядит](https://habrastorage.org/getpro/habr/upload_files/35b/b2b/12f/35bb2b12f48b7127bb42c5823cdce3b0.png)
Здесь две кнопки, на одной просто текст, на другой - некоторое содержимое, содержащее элементы самого фреймворка. И то и другое работает как ожидается, но по сути здесь работает два разных механизма (на самом деле всё ещё интереснее, но и до этого доберёмся).
Хочу сразу обратить внимание на один важный момент: свойство 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](https://habrastorage.org/getpro/habr/upload_files/7ab/b86/fb5/7abb86fb50a1f80f2e922e9cbcf1ef06.png)
Попробуем добавить шаблон данных:
<Button Content="{Binding ButtonContent}" >
<Button.DataTemplates>
<DataTemplate DataType="vm:ButtonContent">
<TextBlock Text="{Binding Action}" FontWeight="Bold" />
</DataTemplate>
</Button.DataTemplates>
</Button>
![Шаблон работает Шаблон работает](https://habrastorage.org/getpro/habr/upload_files/cb9/f81/ef8/cb9f81ef8383f2654acffa1a6bf7715c.png)
А что, если мы сделаем шаблон для строки?
<Button Content="Hello world" >
<Button.DataTemplates>
<DataTemplate DataType="system:String">
<TextBlock>
<Run Text="“" />
<Run Text="{Binding }" TextDecorations="Underline" />
</TextBlock>
</DataTemplate>
</Button.DataTemplates>
</Button>
![Забавно, но это тоже работает! Забавно, но это тоже работает!](https://habrastorage.org/getpro/habr/upload_files/954/864/03d/95486403dc2dd8e4bdefae5b3f36d369.png)
Итак, выводим общее правило:
если объект является частью фреймворка, то он сам себя отрисовывает
в противном случае для него ведётся поиск шаблона, и если он найден, то для отрисовки используется этот шаблон
если подходящего шаблона не нашлось, то на объекте вызывается ToString, он заворачивается в текстовый элемент (TextBlock или AccessText), и уже он рисуется.
Ну и для полноты картины: если речь про списочный элемент, то аналогичные правила применяются для каждого элемента списка.
Как понять, когда такие правила применимы? Очень просто: если свойство, отвечающее за содержимое, имеет тип object, или есть коллекция объектов, или коллекция не generic, то почти наверняка это правило работает.
Работаете с графическими фреймворками и всё ещё не присоединились к чату в телеграме? Надо срочно исправить эту недоработку: https://t.me/AvaloniaRU