Dustin Horne

Developing for fun...

UI Updates With Databinding and INotifyPropertyChanged

Today I'm going to show you how easy it is to leverage the power of property binding in Silverlight.  I'm going to provide a very simplistic example that, while not very useful in itself, I hope will spark your imagination and show you how easy it is.  In my example I'm going to show you how to capture the position of the mouse over a canvas and keep the live updated mouse position in a separate class.  In addition, I'll show you how to update your UI directly (moving a box with the mouse and using labels to show the current mouse position) without directly wiring a single event to those controls in your page codebehind!

Before the MVVM pattern folks start foaming at the mouth over this article, I'd like to point out that this is meant to be a very simple demonstration and is not meant to be an MVVM application.  However, I will introduce you to a few concepts along the way that you will leverage in the MVVM design pattern.  I'm going to briefly introduce you to the DataContext object and I'll be storing the mouse data in a separate class but I will not create a complete Model-View-ViewModel separation for this example.  I will, however be encouraging a separation of UI and logic that will serve as a good starting place when you decide to dive into the world of MVVM.  Now, on with the show...

 

What the Heck is INotifyPropertyChanged?

INotifyPropertyChanged is an interface provided in Silverlight and WPF.  For the purposes of this article, we will concentrate solely on Silverlight.  Traditional ASP .NET applications rely on an event based model.  Since the web is really a stateless demon, ASP .NET pages rely on the state of controls being preserved on the page and passed back to the application.  While implementing INotifyPropertyChanged still requires an event hook, it does so in a stateful manner by allowing your user interface to interact with a connected data model.  So let's take a second to step back and talk about what exactly MVVM means.  This will help me keep the usefullness of property binding in context. 

MVVM stands for Model-View-ViewModel.  While the pattern seems daunting at first, it's really quite simplistic.  It provides a means of creating separation between your user interface, your business logic, and your data model.  This allows for a very nice separation of design and development processes.  The Model is simply a class that provides access to your data.  The Model can get it's data from a variety of sources, whether it be static or dynamic such as pulling data from a database, XML, or some other arbitrary location.  The Model is in charge of exposing that data, but not directly to the View.  This allows also for easily switching your data store.  Maybe you've started with XML but as demand grows you want to migrate to a database and pull the data from a web service.  This can be done by simply switching out the model without a concern of how it will affect your UI.

The View is just as it states, a view.  It's a "page" of sorts that holds your controls.  Really it is a user control itself, but it is a container for the other controls.  The View is responsible only for controlling the display of your controls and instancing the ViewModel for the controls to bind to.  The ViewModel is a bridge between the View and the Model.  The ViewModel exposes the data to the View and handles any business logic such as performing actions based on changes to the View.  It can be a one way or two way bridge, but it is a live connection.  If data in the Model is changed, that data can move through the ViewModel and update the content of the controls on the View, and if a user changes data on the View, that data can move through the ViewModel and back into the Model, ultimately being updated in your actual data store if necessary.  The details of MVVM are really beyond the scope of this article, but understanding the basics will help you understand the importance and usefulness of property binding.

 

Setting Up The Data For Property Binding

Ok, so we want to bind some data to some control properties but we need data to bind.  For the purposes of this demo we are going to create a class called "MouseMonitor".  The MouseMonitor class will implement INotifyPropertyChanged and will expose the position of the mouse to the controls on the page.  Why would I want to expose them through a class instead of just setting the properties directly you ask?  Well, if we were only using the data for a single property on a single control, you could easily update it from within your view if you're not worried about losing flexibility.  However, what happens if you want to bind the same data to multiple controls, or even add controls later on without updating your code?  I'm going to show you how to define your DataContext for your "View" and expose that data.  This allows you to drop new controls into your XAML and bind to the DataContext without having to add code to update those controls.  In a future article I'll show you how to bind that data programmatically which is even more useful as you may want to dynamically add objects to the page and not in your XAML.

What we need to do first is define our base class that will hold the mouse data that we'll be exposing.  This class will implement INotifyPropertyChanged, which means it will have to contain a PropertyChanged event.  The class is very simple.  It contains an OnPropertyChanged subroutine which raises the PropertyChanged event, an OnMouseMove subroutine which we'll use as an entry point to hook our UI mouse event, and the MouseX and MouseY properties which will expose the current mouse position to the controls that are bound.  The setter on each property will call our OnPropertyChanged method.  I've also marked each setter as "Private" since we don't want the values changed outside of our class.

Take a deep breath.  That seemed like a lot of steps but let's put it all together with a quick workflow description and some sample code.  Here's how it breaks down:

  1. Mouse movement fires the OnMouseMove subroutine in our class.
  2. The OnMouseMove Subroutine updates the MouseX and MouseY properties.
  3. The MouseX and MouseY properties fire the PropertyChanged event through OnPropertyChanged when updated.
  4. The PropertyChanged event notifies bound controls that the properties have changed and they should update accordingly.

Now on with the source code:

Imports System.ComponentModel

Public Class MouseMonitor
     Implements INotifyPropertyChanged 

     Private _mouseX As Double 
     Private _mouseY As Double 

     Public Property MouseX As Double 
          Get 
               Return _mouseX 
          End Get  

          Private Set(ByVal value As Double) 
               If Not _mouseX = value Then 
                    _mouseX = value 
                    OnPropertyChanged(Me, "MouseX") 
               End If 
          End Set 
     End Property

     Public Property MouseY As Double
          Get
               Return _mouseY
          End Get

          Private Set(ByVal value As Double
               If Not _mouseY = value Then
                    _mouseY = value
                    OnPropertyChanged(Me, "MouseY")
               End If
          End Set
     End Property

     Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

     Private Sub OnPropertyChanged(ByVal sender As Object, ByVal propertyName As String)
          RaiseEvent PropertyChanged(sender, New PropertyChangedEventArgs(propertyName))
     End Sub

     Public Sub OnMouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
          Me.MouseX = e.GetPosition(sender).X
          Me.MouseY = e.GetPosition(sender).Y
     End Sub
End Class

I'm Gonna Tell You Where to Stick It

That's right, I'm just going to come right out and tell you where to stick it.  It being our class and where being our friend DataContext.  Getting back to the basics of MVVM, the user interface (view) really just defines how your application will look.  The business logic, including that which reacts to events from your UI resides in the ViewModel, in this case our simple MouseMonitor class.  While DataContext seems like a handy place to dump objects, it is really designed for a specific purpose, to expose your business logic and data to your view and provide a context for which the controls in your view can access that data.

For my sample application I wanted something simple, so I got rid of the default Grid and replaced it with a Canvas that is now my LayoutRoot object.  I'll give you the XAML later when I tie it all together.  For now, all you need to know is that everything in the view is contained within a Canvas with a name of "LayoutRoot".  What we want to do now is detect mouse movement over the LayoutRoot UI element and handle it.  This is done using the MouseMove event.  The catch is that we will be handling the event with our MouseMonitor class (this is the reason for the OnMouseMove event in the code above) and updating the properties to expose the mouse location to the bound controls.

The first step is to put into DataContext an instance of our class.  DataContext is of type Object and is created automatically an already there for us to use.  We'll create the instance of the MouseMonitor class in the default constructor for our view.  In this case I've left it as MainPage.xaml.vb.  To do this we merely have to say:  DataContext = New MouseMonitor and we brush off our hands and call it a day, well almost.  We still have to hook our event to our MouseMonitor class.  To do this, we simple add an event handler to our LayoutRoot and use the instance in DataContext like so:
AddHandler LayoutRoot.MouseMove, AddressOf CType(DataContext, MouseMonitor).OnMouseMove

NOTE: Make sure you're hooking your event after InitializeComponent() is called, otherwise you'll receive a null reference exception because the LayoutRoot object has not yet been initialized.

And that's all there is to it.  In a few simple lines of code we've exposed our MouseMonitor class to the view and added a handler to track mouse movement over the LayoutRoot canvas.  Going back to the MouseMonitor class code above you'll notice the following lines in the OnMouseMove routine:
     Me.MouseX = e.GetPosition(sender).X
     Me.MouseY = e.GetPosition(sender).Y

These lines get the position of the mouse from the supplied event arguments.  Furthermore, the GetPosition() routine takes an object reference.  We've supplied "sender" here so it gets the mouse position relative to the object that fired the event which is the LayoutRoot.  Here is all of the code that is required in our View codebehind (MainPage.xaml.vb):

Partial Public Class MainPage
    Inherits UserControl 
    Public Sub New()
        InitializeComponent()

        DataContext = New MouseMonitor
        AddHandler LayoutRoot.MouseMove, AddressOf CType(DataContext, MouseMonitor).OnMouseMove
    End Sub

End Class

And that's it.  We are now ready to bind the MouseX and MouseY properties of our MouseMonitor class to any suitable control property we choose.  We can also do this by purely modifying our XAML code which I'll show you next.

 

Binding Through XAML

Now that our code is prepared and we've made our data available through DataContext, we're ready to bind it to our controls.  I'm going to start with a simple example of a Rectangle.  The Rectangle is contained within the LayoutRoot canvas, so we can bind it's Canvas.Left and Canvas.Top properties to our MouseX and MouseY MouseMonitor properties.  This will cause the upper left corner of the Rectangle to follow the mouse pointer around our canvas.  You can drag and drop in the IDE of your choice, but the XAML is as follows:

<UserControl x:Class="DustinHorne.INPCDemo.MainPage"
    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"
    mc:Ignorable="d"
    d:DesignHeight="500" d:DesignWidth="500" xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input">
    <Canvas x:Name="LayoutRoot" Background="Yellow" Width="500" Height="500">
        <Rectangle Height="84" HorizontalAlignment="Left" Stroke="Black"
         Canvas.Left="{Binding Path=MouseX}" Canvas.Top="{Binding Path=MouseY}" Width="102" Fill="#FF190000" />
    </Canvas>
</UserControl>

 

Why So Much Work To Move One Object?

This is an easy question to answer.  As I've previously mentioned, the purpose of this property binding demonstration is to show you how easy it is to make simple changes without a rewrite of your application.  You'll see in the above XAML we have a Rectangle with its Canvas.Left and Canvas.Top properties bound.  Imagine if this was a client project.  The client now says, "Hey this is great!  The black square follows the mouse around on the gawdy yellow background just like we wanted!  But, wouldn't it be nice if we could show the user the location of the mouse cursor?"

At this point you slap your forehead, grit your teeth and (hopefully) ask the client for more money since we know that your requirements were perfectly documented and this new feature wasn't accounted for, right?  Let's just say for the sake of keeping the client happy, you want to honor the request.  Since we've implemented binding, we can do this with a simple addition to the XAML.  This is crucial if you're a shop that splits the responsibilities between UI and Business Logic developers (and even data modellers).

So let's throw a few label controls in our XAML.  We're going to include 4 labels.  Two of these labels are not databound.  They are simply identifiers to tell the user where you are displaying the X: location of the mouse and the Y: location of the mouse.  The other two labels will have their Content property bound in the exact same way as the Rectangle's Canvas.Left and Canvas.Top properties.  By slapping in 4 labels, dragging them where you want, and telling them what you want to bind to, you've honored the client's request with little to no effort.  Our revised XAML now looks like this:

<UserControl x:Class="DustinHorne.INPCDemo.MainPage"
    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"
    mc:Ignorable="d"
    d:DesignHeight="500" d:DesignWidth="500" xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input">
    <Canvas x:Name="LayoutRoot" Background="Yellow" Width="500" Height="500">
        <Rectangle Height="84" HorizontalAlignment="Left" Stroke="Black" 
               Canvas.Left="{Binding Path=MouseX}" Canvas.Top="{Binding Path=MouseY}" Width="102" Fill="#FF190000" />
        <dataInput:Label Canvas.Left="12" Canvas.Top="450" Height="17" Width="18" Content="x:" />
        <dataInput:Label Canvas.Left="27" Canvas.Top="449" Height="22" Width="46" Content="{Binding Path=MouseX}" />
        <dataInput:Label Canvas.Left="12" Canvas.Top="470" Height="18" Width="19" Content="y:" />
        <dataInput:Label Canvas.Left="27" Canvas.Top="468" Height="23" Width="46" Content="{Binding Path=MouseY}" />

    </Canvas>
</UserControl>

 

Closing Notes / Demo

And that's it folks.  Property binding in Silverilght is a breeze.  While this demo doesn't serve any practicle purpose, I hope it serves as a base for you to come up with your own creative ideas.  Have a look below for a demo of this code in action.  I've compiled it against Silverlight 3.

 

 

Quick Note:  Testing for this project went well however I did run into an issue when deploying.  Make sure you add a reference to the System.ComponentModel namespace in your project properties to ensure that it is correctly included with the project.  If anyone is interested I can upload the entire project and provide a download link.