XAML 101: More Data Binding

FacebookTwitterGoogle+Share

Sample code for this post on GitHub: https://github.com/billreiss/xamlnative/tree/master/Xaml101/016_MoreDataBinding

In the last post there was a brief introduction to data binding in Windows 10 apps. Let’s build upon this a bit. Previously we defined a string field and a string property in the MainPage.xaml.cs code behind file then used the x:Bind markup extension in the XAML to bind to these values.  This works fine in this case, but it is generally considered a best practice to separate your views from your data. Let’s add a new class to the last sample and call it MainViewModel.cs. Here is the body of that class:

class MainViewModel
{
    public string FieldBindingText = "Here is a Field Binding";

    public MainViewModel()
    {
        this.PropertyBindingText = "This is a Property Binding";
    }

    public string PropertyBindingText
    {
        get;
        set;
    }
}

Then in the MainPage.xaml.cs file, we can create an instance of this model class:

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
    }

    MainViewModel Model = new MainViewModel();
}

And then finally in the MainPage.xaml we can bind the values like this:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel>
        <TextBlock Text="{x:Bind Model.FieldBindingText}"/>
        <TextBlock Text="{x:Bind Model.PropertyBindingText}"/>
    </StackPanel>
</Grid>

Notice the small difference from the last sample, where now we are binding to Model.FieldBindingText instead of just FieldBindingText. This is because we have a field named Model in the MainPage.xaml.cs code behind and then we can reference sub-elements using dot notation.

Running this gives us the same result as before:

image

Now after the TextBlock in the XAML, let’s add a Button we can click:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel>
        <TextBlock Text="{x:Bind Model.FieldBindingText}"/>
        <TextBlock Text="{x:Bind Model.PropertyBindingText}"/>
        <Button Content="Click Me!" Click="Button_Click"/>
    </StackPanel>
</Grid>

Notice the Button has a Content property and not a Text property like a TextBlock has. This is because the button content can be just about anything. More about that in future posts. There is a click event specified here, so we need to implement this in the MainPage.xaml.cs file:

private void Button_Click(object sender, RoutedEventArgs e)
{
    Model.PropertyBindingText = "Clicked!";
}

Notice that the button’s click even changes the value of Model.PropertyBindingText. If we run the app, we get this:

image

And if we click the button, what will happen? You might expect the text of the second TextBlock to change. Actually what happens is nothing. There are a couple of reasons for this. The first is that there are a few data binding modes available, and the default is OneTime. One time binding means that the value is evaluated when the page is loaded and never again. This is the default because it is the most efficient and fast, and so unless you need a different mode for some reason XAML tries to help you write fast apps.

Other options are OneWay and TwoWay. A one way binding only goes from the source to the target, and  any changes to the target aren’t reflected in the source. With a two way binding, data flows in both directions.

NOTE: In other XAML platforms like WPF, the default is OneWay or in some cases TwoWay depending on the control.

Let’s change the TextBlock binding to a OneWay binding.

<TextBlock Text="{x:Bind Model.PropertyBindingText, Mode=OneWay}"/>

This however is still not enough. It’s not enough for the source value to change. We also need to notify XAML that the value has changed. This is done through the INotifyPropertyChanged interface. The INotifyPropertyChanged interface has a single event defined in it, an event we need to fire when the property changes. Here is a refactored MainViewModel.cs that implements INotifyPropertyChanged:

class MainViewModel : INotifyPropertyChanged
{
    public string FieldBindingText = "Here is a Field Binding";

    public MainViewModel()
    {
        this.PropertyBindingText = "This is a Property Binding";
    }

    string propertyBindingText;
    public string PropertyBindingText
    {
        get
        {
            return propertyBindingText;
        }
        set
        {
            propertyBindingText = value;
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("PropertyBindingText"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Now if you run it again and click the button you should see this:

image

It is so common to have to raise the property changed event that most helper frameworks like the great MVVM Light framework created by Laurent Bugnion provide a base class for this with a simple method call to raise the property changed event. In our MainViewModel class we can add the following method:

private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
} 

Notice the [CallerMemberName] attribute. This takes the name of the caller as the property name if it’s not specified. This keeps us from having to use hard coded strings when calling the notify property changed method. Hard coded strings can lead to problems when renaming and refactoring, and can also be easily mistyped. Then to use this method, the PropertyBindingText property can be changed to look like this:

public string PropertyBindingText
{
    get
    {
        return propertyBindingText;
    }
    set
    {
        propertyBindingText = value;
        NotifyPropertyChanged();
    }
}

And if you run the app again you should see the same result as before.

So we’re doing a good job of separating the view from the data and linking them together with data binding, but there is one thing that isn’t in the model and that’s the click event handler. It’s generally not considered good design to call into the model directly from the view, but in some cases it makes sense to do it. Here we can easily fix this with xBind by moving the click handler to the model as well.

Remove the event handler from MainPage.xaml.cs and then add this to MainViewModel.cs:

public void ClickHandler(object sender, RoutedEventArgs e)
{
    PropertyBindingText = "Clicked!";
}

And the XAML changes to this:

<Button Content="Click Me!" Click="{x:Bind Model.ClickHandler}"/>

Running this gives us the same result, but the view has nothing special in it except a declaration and instantiation of the MainViewModel class, and all of our data and logic is in one place. If you don’t need the sender and the args in the method that handles the event, you can make it a method without arguments like this and it will work fine:

public void ClickHandler()
{
    PropertyBindingText = "Clicked!";
}

Note that you may need to rebuild all on the solution for it to regenerate the bindings. Next time we’ll dig even deeper into data binding.