How rendering works in XAML-based frameworks

Quite often I see questions that are caused by either a lack of understanding of how XAML-based frameworks work under the hood (I worked with WPF and AvaloniaUI, but I have every reason to believe that this information applies to their other relatives).

Today I want to highlight one of these aspects of the work, namely, how different data is rendered and why it works at all. In illustrative examples I will use the AvaloniaUI framework.

Let's start with one simple example and then develop the idea further:

<Button>Hello world</Button>

<Button>
  <StackPanel Orientation="Horizontal">
    <Image Source="/Assets/Help.png" />
    <AccessText>_Help</AccessText>
  </StackPanel>
</Button>
What does it look like

What does it look like

There are two buttons, one is just text, the other is some content containing elements of the framework itself. Both work as expected, but essentially there are two different mechanisms at work here (in fact, it’s even more interesting, but we’ll get to that).

I would like to immediately draw your attention to one important point: the Content property of the button, inherited from ContentControl, is of type object – this is important for further narration.

Let's try to explain the current behavior: if the content is a string, then it is displayed as text. If the content is part of the framework, then it knows how to draw itself, and therefore the drawing is delegated to it. But there may be other options, right?

Let's make a simple ViewModel with a property of some of our type and try to place it in a button:

public class MainViewModel
{
    public ButtonContent ButtonContent { get; } = new() { Action = "Do something" };
}

public class ButtonContent
{
    public required string Action { get; init; }
}
<Button Content="{Binding ButtonContent}" />

So what do we see?

We see the called ToString

We see the called ToString

Let's try to add a data template:

<Button Content="{Binding ButtonContent}" >
  <Button.DataTemplates>
    <DataTemplate DataType="vm:ButtonContent">
      <TextBlock Text="{Binding Action}" FontWeight="Bold" />
    </DataTemplate>
  </Button.DataTemplates>
</Button>
The template works

The template works

What if we made a template for a string?

<Button Content="Hello world" >
  <Button.DataTemplates>
    <DataTemplate DataType="system:String">
      <TextBlock>
        <Run Text="“" />
        <Run Text="{Binding }" TextDecorations="Underline" />
      </TextBlock>
    </DataTemplate>
  </Button.DataTemplates>
</Button>
It's funny, but it works too!

It's funny, but it works too!

So, we derive a general rule:

  1. if the object is part of the framework, then it draws itself

  2. otherwise, a template is searched for it, and if one is found, then this template is used for rendering

  3. if a suitable template is not found, then ToString is called on the object, it is wrapped in a text element (TextBlock or AccessText), and it is already drawn.

Well, to complete the picture: if we are talking about a list element, then similar rules apply for each element of the list.

How do you know when such rules apply? It’s very simple: if the property responsible for the content is of type object, or there is a collection of objects, or the collection is not generic, then this rule almost certainly works.


Do you work with graphic frameworks and still haven’t joined the telegram chat? We urgently need to fix this flaw: https://t.me/AvaloniaRU

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *