The secret life of a Xaml Page

Disclaimer: Post describes Xaml Pages used in Xamarin Forms

If you are like me the first time when confronted with a Xaml page they look sort of really alien to an old school programmer who has never did dirty web stuff before.
After reading some articles about using them you finally grasp what you can do with them but still there is a sort of magic around them or at least if feels like a strange sort of animal that you have learned how to ride following all the examples but still you are not really sure what it really is and how does it work and if you can trust it.

Xaml Pages are just classes

OK not completely true but at least partial (pun intended). They are one part of a page class, the other one is normally declared in your xxx.xaml.cs file. Visual Studio only knows that they belong together and displays them linked together so that they look like some special thing.

If you have a Xaml Page like

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="FormsTest.Pages.BasicPage">
  <Label Text="Hello Forms" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>

with it’s counterpart

using Xamarin.Forms;

namespace FormsTest.Pages
{
    public partial class BasicPage : ContentPage
    {
        public BasicPage()
        {
            InitializeComponent();
        }
    }
}

it is basiscally the same as if you write in code:

class BasicCodePage : ContentPage
{
    public BasicCodePage()
    {
        Content = new Label() {Text = "Hello Forms", HorizontalOptions = LayoutOptions.Center,
                                VerticalOptions = LayoutOptions.Center};
    }
}

The most important part in the Xaml header x:Class="FormsTest.Pages.BasicPage" tells the compiler to which partial c# class this Xaml belongs.

The magical part

How does this work? Basically your Xaml definitions get included in your assembly as embedded resource. When creating an instance of the page Forms parses the Xaml and instantiates the there defined classes like <Label>, <Grid>, <ScrollView> any set attributes get assigned to the same named properties of this classes.

You can see it yourself checking the objfolder of you project where you find temporary generated files of your pages like:

    public partial class BasicPage : global::Xamarin.Forms.ContentPage {

        [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "0.0.0.0")]
        private void InitializeComponent() {
            this.LoadFromXaml(typeof(BasicPage));
        }
    }

InitializeComponent();

If you look at the constructor of the partial class you find a generated call of InitializeComponent. This call instantiates and initializes all of the objects defined in the XAML file, connects them together in parent-child relationships, attaches event handlers defined in code to events set in the Xaml file, and sets the resultant tree of objects as the content of the page.
Taking this into account it now get’s clear why you NEVER EVER SHOULD TRY TO ACCESS ANY CONTROL of your page before this call because they just don’t exist at this point and you will get an exception.

Quite often you will get compiler errors that tells you that InitializeComponentdoes not exist. Looking at your partial C# class we have to admit that indeed there is no such method. So it must be defined somewhere in the from Forms generated second half of our page class. So if you get this kind of error in most cases the link between the code-behind file (how your partial class is often been called) and the Xaml definition is broken. You first stop is therefore checking the x:classdefinition in your Xaml header if the name there matches the class name of your c# class.
Another reason can be that VS or XS did not set the correct build tool for the Xaml page. Checking the Xaml files properties Custom Tool should be set to MSBuild:UpdateDesignTimeXaml and Build Action to `Embedded Resource.

Named Controls

In a lot of tutorials you see that the code is accessing the controls of the page just by there name which was set with the x:name attribute on the Xaml control.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:pages="clr-namespace:FormsTest.Pages;assembly=FormsTest"
             x:Class="FormsTest.Pages.BasicPage">

  <Label x:Name="MyLabel" Text="Hello Forms" VerticalOptions="Start" HorizontalOptions="Center" />
</ContentPage>

How does this work? The answer is that the Xamarin compiler will create a field for every named element in your Xaml definition inside your Page class. This is the code the compiler generates for the Xaml page:

    public partial class BasicPage : global::Xamarin.Forms.ContentPage {

        [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "0.0.0.0")]
        private global::Xamarin.Forms.Label MyLabel;

        [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "0.0.0.0")]
        private void InitializeComponent() {
            this.LoadFromXaml(typeof(BasicPage));
            MyLabel = this.FindByName<global::Xamarin.Forms.Label>("MyLabel");
        }
    }

If you want to dig deeper into this please check out: Anatomy_of_a_XAML_Class

Custom Controls

Recalling that

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="FormsTest.Pages.BasicPage">
  <Label Text="Hello Forms" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>

The <Label> directive instantiates an Object of the class Label you could get the idea if it would be possible to use any class inside Xaml. And actually you can. Ok, not any class everywhere but you can use any from `VisualElement derived class as controls. So if you need a custom controls, you can just derive your class from an existing control and use it on your page.

E.g. if we want to have an control that capitalizes every input we could do this like so:

class CapitalEntry : Entry
{
    public CapitalEntry()
    {
        TextChanged += CapitalEntry_TextChanged;   
    }

    private void CapitalEntry_TextChanged(object sender, TextChangedEventArgs e)
    {
        Text =   e.NewTextValue.ToUpper();
    }
}

This might be a pretty naive solution but works for this example.

and use it in our Page just like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:pages="clr-namespace:FormsTest.Pages;assembly=FormsTest"
             x:Class="FormsTest.Pages.BasicPage">
  <StackLayout>

    <Label Text="Hello Forms" VerticalOptions="Start" HorizontalOptions="Center" />

    <pages:CapitalEntry VerticalOptions= "Start"/>

  </StackLayout>
</ContentPage>

The only thing to be able to use our own classes is to tell forms in which assembly they are to find. That’s why we need this line

 xmlns:pages="clr-namespace:FormsTest.Pages;assembly=FormsTest"

It defines Xaml namespace so that we now can reference classes from external assemblies. You only need “assembly=XXXX” if the control is in a different assembly to the page you’re referencing it from.

This also means that you can easily reuse parts of your page by defining them as separate classes and use that in your pages.
When doing this and define your “page parts” as ContentView or ViewCell derived classes you can define them in Xaml too and get even more flexibility because you can let it handle their own events in their own code-behind files to create more complex self contained custom controls.

Now I moved the content of the previous Page to a custom View class:

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:pages="clr-namespace:FormsTest.Pages;assembly=FormsTest"
             x:Class="FormsTest.Pages.PagePartView">
  <ContentView.Content>
    <StackLayout>

      <Label Text="Hello Forms" VerticalOptions="Start" HorizontalOptions="Center" />

      <pages:CapitalEntry x:Name="capitalEntry" VerticalOptions= "Start" TextChanged="Entry_OnTextChanged"/>

    </StackLayout> 
  </ContentView.Content>
</ContentView>

with it’s own code-behind file:

namespace FormsTest.Pages
{
    public partial class PagePartView : ContentView
    {
        public int MaxLength { get; set; } //Will be accessible as Xaml Attribute

        public PagePartView()
        {
            InitializeComponent();
        }

        //Limit the entry to MaxLength characters 
        private void Entry_OnTextChanged(object sender, TextChangedEventArgs e)
        {
            if (e.NewTextValue.Length > MaxLength)
                capitalEntry.Text = e.OldTextValue;
        }
    }
}

Then we can use it in our page:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:pages="clr-namespace:FormsTest.Pages;assembly=FormsTest"
             x:Class="FormsTest.Pages.BasicPage">

  <pages:PagePartView MaxLength="10"/>

</ContentPage>

As you see we can pass values to properties of our custom view as Xaml attributes.

Deriving Pages

You can derive Pages but with some limitations.

  • It’s possible to derive a Xaml Page from a Page defined in code that does not create any controls but only provides other functionality.
  • You can derive a code defined Page from a Xaml defined page that can access the Xaml defined controls of it’s parent page.
  • You cannot derive a Xaml defined Page from another Xaml defined page (unless they only define resources and no content)

If you want to derive a Xaml page from some outside defined base page the definition looks a bit strange as you have to use the base classes namespace before it is even declared:

Here an example that derives from ReactiveContentPage defines in the ReactiveUI library package.

<?xml version="1.0" encoding="utf-8" ?>
<rxui:ReactiveContentPage x:TypeArguments="pageModels:SettingsFilerPageModel" xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:rxui="clr-namespace:ReactiveUI.XamForms;assembly=ReactiveUI.XamForms"
             xmlns:pageModels="clr-namespace:FormsTest.PageModels;assembly=FormsTest"
             x:Class="FormsTest.SettingsFilterPage" BackgroundColor="Black">

This example is even more interesting because it shows that it’s possible to derive a Xaml Page from a generic base class. The generic type parameter is passed by using the x:TypeArgumentsdeclaration.

Why can’t I bind my Xaml Controls to Properties of my Page class?

A lot of developers when starting using MVVM as design principle try to bind attributes or command of their controls in the Xaml definition to properties defined in the code-behind file of the page and are surprised that this doesn’t work.

Besides that it makes no sense at all to bind to elements of your code-behind file, the reason is that the {Binding}
markup extension tries to make link between the Attribute it is set with an object assigned to the BindingContextproperty of the Page class and not to the Page itself.
Now you could get the idea and assign this.BindingContext = this inside the constructor of the page, but it really makes no sense if you recall that the compiler fuses the Xaml Definition together with the code-behind file to one single class. Binding inside one class does not make any sense at all.
If you want want access your pages controls just give them names with the x:name attribute and access them directly. And if you want to handle events in the code behind, don’t try to use commands, just use the Xaml exposed events.

Should I use the code-behind file at all?

Many MVVM purists will tell you that your code-behind files should only contain the InitializeComponentcall and nothing else.
I’m not that dogmatic. Actually if you are not using the Xamarin Forms bindings you might need to define bindings manually inside the constructor.
Second if we are talking about custom views it might not make sense to bind that to a ViewModel if it acts as a self-contained control. Think of a complex DateView that only exposes a property selected date but handles user interaction inside. This could be completly handled in the code-behind file. Another example is if you have to change some components dimensions based on the physical screen size. It makes much more sense to do this after the ‘InitializeComponent` call in the pages constructor than with a binding from the ViewModel because screen dimensions are something inherently connected to the View and not to the ViewModel.
As so often the answer is it depends 🙂

XamlCompile

You have seen before that Xaml pages are added as Resources and parsed at runtime, so many of you might have thought if this isn’t a bit slow. And indeed it is. Luckily there is a way out of it you can tell Xamarin to compile your Xaml classes directly into IL by simply setting an attribute on your class or on your whole assembly. As I don’t want to just copy the offical documentation, you can find here how to use Xaml Compilation

Contact me:

2 thoughts on “The secret life of a Xaml Page

  1. PJ says:

    Read all of your posts. and impressed by each and every point made by you. I’m just starting out with XF and have been reading a lot. I was wondering if you can share a sample project with all of the best practices for XAML, MVVM, data binding etc. that you are talking about. It can be good starting point for freshers knowing that we are following the best.

    • admin says:

      Thanks a lot for your kind words. Creating a full Demo App is a lot of work that’s why I didn’t do it yet 😉 I’m preparing a blog series that will cover s lot of this topics in a sample App. But that will take some time.

Leave a Reply

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