Building a XAML UserControl for WinUI, UWP, WPF or Xamarin.Forms (.NET MAUI)

One of the powerful aspects of any XAML platform is the ability to define your own controls. In this post we’re going to look at building a user control that allows you to reuse chunks of your user experience. It doesn’t matter whether you’re using UWP, WinUI, Xamarin.Forms (.NET Maui) or the Uno Platform, the walk through in this post will show you how to create a user control and how you can use it throughout your application.

Before we jump into creating a user control it’s worth discussing what a user control is and how it compares to either a custom control or a content control. You can think of these three types of controls as different layers of abstraction.

Custom control: A custom control, which inherits directly from the Control base class, is useful if you want complete control over how the control looks and behaves. Whilst you can control the look of the control in code, it is more common to define a ControlTemplate that controls the layout of the control.

Content control: A content control inherits from the ContentControl base class (which derives from Control). The ContentControl class exposes Content and ContentTemplate properties which allow the user to specify the content that’s to be displayed and the template to use in order to display it, all of which without altering the functionality of the control – this is fundamental to the concept of lookless controls. The Button is a good example of a ContentControl – you can either specify XAML as the Content, or you can set the Content to data and then provide a ContentTemplate to define how the data should be displayed.

User Control: A user control inherits from the UserControl base class (which derives from Control). The UserControl class exposes only a Content property which is a UIElement. Unlike the ContentControl which relies on a ContentTemplate to define how the Content should be displayed (both of which can be overwritten by the user of the control), the Content property of the UserControl is typically predefined by the developer of the control. This is the simplest way to create a control and is useful for reusing chunks of XAML throughout an application.

We’ll cover custom and content controls in future posts but for now, let’s focus on building a user control.

As I mentioned above, one of the things that user controls are great for is reusing chunks of XAML. For example, say I want to provide an interface where the user can enter the first and last name for someone. This same interface may appear on a number of different pages within the app, so rather than simply copying the XAML, I’ve decided to create a user control called Profile (with a view that down the line I’m going to add other profile information such as date of birth and perhaps some contact information).

Here’s the XAML that we’re going to start with prior to creating the user control. Both TextBoxes are bound to the same DataContext as the rest of the page, and then the Text property is two way bound to the FirstName and LastName properties of the data context respectively.

<TextBox Text="{Binding FirstName, Mode=TwoWay}" />
<TextBox Text="{Binding LastName, Mode=TwoWay}" />

To create the user control, we start by right-clicking on the project and selecting Add -> New Item -> User Control. Note that in the case of Xamarin.Forms you need to add a ContentView instead of a User Control. After the user control is created, Visual Studio should automatically open the XAML for the control, which we’ll update to the following:

<UserControl x:Class="WinUIControl.Profile"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             x:Name="Root"
             mc:Ignorable="d">
    <StackPanel>
        <TextBox Text="{Binding FirstName, ElementName=Root, Mode=TwoWay}" />
        <TextBox Text="{Binding LastName, ElementName=Root, Mode=TwoWay}" />
    </StackPanel>
</UserControl>

The XAML for Xamarin.Forms is slightly different, with the main differences being that the root element is ContentView instead of UserControl, StackLayout replaces StackPanel and Entry replaces TextBox.

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Name="Root"
             x:Class="XFApp.Profile">
    <ContentView.Content>
        <StackLayout BindingContext="{x:Reference Root}">
            <Entry Text="{Binding FirstName, Mode=TwoWay}" />
            <Entry Text="{Binding LastName, Mode=TwoWay}" />
        </StackLayout>
    </ContentView.Content>
</ContentView>

What’s worth noting about the XAML is that the TextBox (and Entry for XF) is data bound to properties on the root element, in other words properties on the control itself. Now you might be wondering why we need to do this – surely when the user control is used, the data context will flow into the user control and thus each of the child elements can continue to data bind to the appropriate property on the data context. Whilst this is true, it requires the user of the control to know what properties are being used inside the user control.

To avoid the user of the control having to know about the inner workings of the user control, we’re going to expose two properties on the user control called FirstName and LastName. As DependencyProperty (BindingProperty for XF) properties these are accessible both to the user of the control but also internally within the control, such as from the XAML as we saw in the XAML above.

public sealed partial class Profile : UserControl
{
    public string FirstName
    {
        get { return (string)GetValue(FirstNameProperty); }
        set { SetValue(FirstNameProperty, value); }
    }
    public static readonly DependencyProperty FirstNameProperty =
        DependencyProperty.Register(
            "FirstName", 
            typeof(string), 
            typeof(Profile), 
            new PropertyMetadata(null));
        
    public string LastName
    {
        get { return (string)GetValue(LastNameProperty); }
        set { SetValue(LastNameProperty, value); }
    }

    public static readonly DependencyProperty LastNameProperty =
        DependencyProperty.Register(
            "LastName", 
            typeof(string), 
            typeof(Profile), 
            new PropertyMetadata(null));

    public Profile()
    {
        this.InitializeComponent();
    }
}

Now all we need to do is consume the Profile user control within an app.

<local:Profile 
    FirstName="{Binding First, Mode=TwoWay}" 
    LastName="{Binding Last, Mode=TwoWay}"/>

To prove the point, the data context for the page, the MainViewModel, doesn’t have FistName and LastName properties. Instead, they’ve been shortened to just First and Last (not recommended and only done to illustrate the point that these properties are independent of the properties on the Profile control).

public class MainViewModel : ObservableObject
{
    private string firstName;
    private string lastName;

    public string First{ get => firstName; set => SetProperty(ref firstName, value); }

    public string Last{ get => lastName; set => SetProperty(ref lastName ,value); }
}

The MainViewModel inherits from ObservableObject that comes from the CommunityToolkit.Mvvm package which provides a number of mvvm helper functions and base classes.

The important thing to note here is that any changes to the First and Last properties of the MainViewModel will bubble up the OnPropertyChanged event. This event will trigger the FirstName and LastName properties on the Profile control to be updated. Subsequently the changes to these properties will result in the Text property on both TextBox elements within the Profile control updating. Similarly in reverse – changes to the Text property on the TextBox elements will result in the FirstName and LastName properties on the Profile control being updated, which will flow on to updating the First and Last properties on the MainViewModel.

The great thing about building user controls is that they provide an easy way to reuse XAML without having to write very much code. Data binding in XAML goes a long way to defining in a declarative way how the user control should behave.

You can of course extend the functionality of the user control. For example you may want to execute some validation when the FirstName or LastName properties change. For this, you can attach an event handler to the declaration of the DependencyPropety (or BindingProperty in the case of XF),

public static readonly DependencyProperty FirstNameProperty =
    DependencyProperty.Register(
        "FirstName", 
        typeof(string), 
        typeof(Profile), 
        new PropertyMetadata(null, OnFirstNameChanged));

private static void OnFirstNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    throw new NotImplementedException();
}

I hope this gets you started building your own user controls and stay tuned for future posts on building custom and content controls.

6 thoughts on “Building a XAML UserControl for WinUI, UWP, WPF or Xamarin.Forms (.NET MAUI)”

  1. When you introduce the MainViewModel code, that doesn’t really mean anything to me. Where does it go, what do I do with it? Should you have called it ProfileViewModel maybe? Because there is no main anything. Main implies to me that it’s part of the core application logic, but it’s clearly not main to anything, it’s very particular to the Profile control. In my test project, there is no main view model.

    And is there a way to do this without the community toolkit? Is this not basic functionality of WinUI?

    But besides that, adding the package is fine if that’s the only way to do it. But then what?You don’t explain how the ViewModel and the xaml are wired up. So there is no connection there. I put in a simple test event…
    “`cs
    private void Profile_PointerEntered(object sender, PointerRoutedEventArgs e)
    {
    Debug.WriteLine(“: ” + FirstName + ” ” + LastName);
    }
    “`
    And FirstName and LastName are not updating when I enter text into the fields. Nor does the control update if I programmatically set First and Last. There is something missing in this explanation.

    Reply
  2. I’m concerned about this…

    In the first instances of binding that I got working, they had something like this…
    ItemsSource=”{x:Bind DataRepository.Data}”
    or this…
    Text=”{x:Bind ViewModel.DefaultRecording.OneLineSummary}”

    These were referencing actual classes in code. Your Profile xaml does not reference anything. And there is no x:Bind.

    Reply
  3. I somewhat got it working by completely removing your MainViewModel code, it just doesn’t do anything. I also noticed that I hadn’t given Profile.xaml a name, so I named it “Root”.

    I also gave the Profile.xaml a name, which is need to access it in code. So…

    Now I can set the fields in code with…
    ProfileForm.FirstName = “James”;
    ProfileForm.LastName = “Bond”;
    And change the values in the fields. Although, the backing value doesn’t update automatically, not even enter will update the code, you need to change focus, so maybe some additional code to sort that out would be helpful to assure that it seems to work. At first I just wrote it off as not working at all.

    Reply

Leave a comment