Wednesday 29 September 2010

A Basic Guide to WPF Databinding using Visual Studio and C#

Databinding is one of the most powerful tools the WPF framework provides. It allows a developer to abstract the user interface from the underlying mechanics of the system, removing the need for repetitive and boring assignment calls, type conversions and validation code on every control. The coder links a control's property to a field in an object or to a property on another control and everything else, all the assignments and most conversions are handled for you.

Read past the break for a simple guide to setting up a couple of bindings in Microsoft's Visual Studio and C#.

To begin, create a new WPF application. We're going to create a basic form for inputting user details. Create some textboxes and a button at the bottom, go wild - make it beautiful, or if you're bored or in a rush, make it something like mine.

A beautifully designed user interface with 5 textboxes and one button. Nothing stupid here, honest.
Beautiful and sensible.

Next, create a new class; let's call it "Person" because imagination is the spice of life. Our Person class will hold our users' data. Go ahead and add some fields that correspond to your interface. Mine looks like this:

class Person
    {
        string firstName;
        string surName;
        int age;
        bool mollusc;
        char favLetter;
    }


It's almost time to make the magic happen, but first we need to do some messy bits. You see, databinding works by waiting until the source (one of our fields) updates before changing the linked interface element. At the moment any binding we were to set up would never know when the source had changed and nothing would happen. Let's rectify this by editing our Person class. First, our class needs to implement the INotifyPropertyChanged interface, which is part of the System.ComponentModel package.

Next, we create a public PropertyChangedEventHandler delegate; in fact let's make it an event too as it's only going to be called on property changes. We'll leave that there for now, shrouded in mystery and dripping in suspense, to write a function called updateTarget (or whatever you want really, it's a free country, unless you're reading this from inside a dictatorship, in which case you should probably name it updateTarget). This function, whatever you've called it, takes a string for its parameter. This string will be the name of the property that we're updating. What our function must first do is check that our PropertyChangedEventHandler thingy isn't null, because otherwise we're in for a whole heap of exceptions. If it isn't null, which is the way things should be, then we should call it, passing in this as the sender and a new PropertyChangedEventArgs, which gets our propertyName parameter as its parameter, our method being a kind and generous method. If you've been paying attention your code should now look something like this:

class Person
    {
        string firstName;
        string surName;
        int age;
        bool mollusc;
        char favLetter;

        #region Stuff We Don't Need To See All The Time

        public event PropertyChangedEventHandler PropertyChanged;
        void updateTarget(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }


I've also gone ahead and stuck that new code inside a region, this way we can hide it and keep it out of our way while the real code gets down to bid'niss.

"So," I hear you say, "We have some code that does absolutely nothing, our fields don't send updates and our UI just sits there, staring out of the window like a forlorn child."
"Shut up." I respond, "We're getting there, and I think you might have some repressed memories you should probably see a shrink about."

*Ahem*

What we really need to do now is tell our code to call our fancy new method whenever it's updated. To do this we create public versions of all of the fields, and during the set routine, we make a call to updateTarget. Simple, right? I also explicitly stated that my original fields were private, mainly for neatness. Here's my code now:


class Person
    {
        private string firstName;
        private string surName;
        private int age;
        private bool mollusc;
        private char favLetter;

        #region Stuff We Don't Need To See All The Time

        public event PropertyChangedEventHandler PropertyChanged;
        void updateTarget(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(thisnew PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

        public string FirstName
        {
            get {return firstName;}
            set
            {
                firstName = value;
                updateTarget("FirstName");
            }
        }

        public string SurName
        {
            get { return surName; }
            set
            {
                surName = value;
                updateTarget("SurName");
            }
        }

        public int Age
        {
            get { return age; }
            set
            {
                age = value;
                updateTarget("Age");
            }
        }

        public bool IsMollusc
        {
            get { return mollusc; }
            set
            {
                mollusc = value;
                updateTarget("IsMollusc");
            }
        }

        public char FavouriteLetter
        {
            get { return favLetter; }
            set
            {
                favLetter = value;
                updateTarget("FavouriteLetter");
            }
        }
    }


Aaaaaand that's all we need to do for our Person class, all done. Isn't that a relief, right? We retreat back into the warm and loving embrace of XAML, where finally we're going to set up these sexy bindings.

Well, maybe not yet, but nearly.

First we're going to add a little bit of XAML to help us. Y'see, our XAML has absolutely no idea our Person class even exists at the moment, it's snubbing our poor little Person like a snooty art critic in a black turtle-neck and half-moon sunglasses, sipping dry white wine and looking disgusted by everything it sees. This actually won't be an issue for us because once we've set our created Person object as the DataContext of the application (more on that later), it will know what to bind to, however we won't get any help from visual studio and we'll have to hope we didn't make any spelling mistakes.

To start with, we need to show XAML what namespace we're using so it can be all impressed and go "ooh!" At the top of our XAML you'll see a load of lines that look like this:

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"


These are the namespace declarations. The x namespace points to some XAML-specific functions. Well, we simply add another line underneath them, this time looking something like this:

xmlns:data="clr-namespace:tut"


Where "tut" happens to be the namespace my Person class resides in, and "data" is the label I've given it. I don't recommend changing the rest as it's kind of important, you know?

Nearly there! What with XAML being a declarative language and all, we should probably go ahead and declare our Person class. We do this in the DataContext, which is what the binding system will look to when it wants to know just what the hell you think you're talking about. Add the following code, just under the Window element at the top of your XAML:

<Window.DataContext>
            <
data:Person x:Name="PersonData"/>
</
Window.DataContext>


This declares a Person, from the namespace known by the tag of "data" and gives it a name (PersonData) to XAML, so it can be referenced elsewhere. Go ahead and add x:Name="mainWindow" to the main Window element's attributes too, that way we can refer to it in code.

DataContexts are inherited by child elements, unless those elements have their own DataContexts. This means every element in our window has access to our Person class, which is probably a bit of overkill. On a small test like this it doesn't really matter, but if you're making a Roaring Resource Raptor Reeling from Roid Rage™ you'll need all the spare cycles you can muster so it's best to set the DataContext as close to the elements that are using it as possible. If a binding is looking for an object with a field called Awesome, and you've neglected to set a DataContext, but there's a parent with a DataContext object that has that field, then that object will be bound to. It very well could be the wrong object, and bugs will ensue.

Guess what! It's time for a Binding party! Now we can do this the easy way, or the hard way, but I'll show you both because I've heard you internet folk can be greedy. We'll start with the hard way, which is coding by hand. Find one of the UI controls in your XAML (I'm going with the textbox next to First Name:). Now work out which property you want to bind (I'm binding the Text property, but you can bind almost any property on any control). Then, add that property as an attribute to the XAML tag. E.g. my Textbox becomes:

<TextBox Text=/>


This is, of course, invalid XAML, as we still need to tell the TextBox what to put in its Text property. After the equals sign, we add the following:

"{Binding Path=FirstName}"


What this does, is it tells the XAML to create a Binding. Now a Binding always assumes that the object you're binding to is the DataContext, unless you specify otherwise by using the Source field, which we'll cover some other time. The Path then tells it which field on this object you want to bind to, in my case, it's the FirstName field.

Let's try it out! If you've jumped the gun and ran the code already you'll have noticed that absolutely nothing happened. Shows what you get for skipping ahead, dummy. That's because we can't see the object, so let's go ahead and change the value of our object's property in code, then our control will update automatic-like. Magic.

Double click on the button you made earlier. Not made a button? Make one, and for future reference I will be obeyed. It will have automagically made you an event handler. In there we're going to put some simple code:

Person person = new Person();
            person.Age = 18;
            person.FavouriteLetter = 'C';
            person.FirstName = "Lars";
            person.SurName = "Rodriguez";
            person.IsMollusc = true;
            mainWindow.DataContext = person;


Simple really, we create a new Person object, fill in the properties and swap out the old DataContext (our declared PersonData) with it.

Now if you run it you should find that when you click on the button the "First Name:" TextBox automatically updates to "Lars" or whatever name you chose. Maybe you used a celebrity's name, or maybe you used your own, you unimaginative halfwit.

For the easy way, we'll bind the Age control. Select the TextBox next to the Age: label and then peruse the properties on the right hand side of the screen, look for the Text property. Have you found it yet? No? Keep looking! Ok, I'm bored of that. Next to the word "Text" in the property screen there'll be a little nondescript box, hiding in plain sight. It will look a little something like this:

Seriously Microsoft, how did you expect us to see that?
If you click that a menu will pop up, allowing you to put special things in your properties, and what we want to add is, of course, a data binding so click "Apply Data Binding..."

A new menu will come up and, thanks to our declaring the PersonData DataContext before, the Source will have automatically been set to PersonData and the Path window will be expanded. Here you will see a list of the available fields we can bind to. Double click on "Age" and you're done. You might notice that what this has actually done is written the XAML for us, so both methods are guaranteed to perform exactly the same. Build it and have a go!

One final thing you really should know before venturing out alone in the brave new world of binding is that binding errors are silent assassins of CPU cycles. If a property has failed to bind, it will attempt to convert any bound field it has that matches the path. That takes time, but if it's unsuccessful it will then try to use a fallback. If there's no fallback it will create an exception, which we all know is a slow process, it will then trap the exception it created and report it to the Output log. The tricky thing about WPF exceptions is that they only appear in the Output. They do not halt the program when one is thrown, but they do slow everything down a lot.

You might have seen a few more options on that binding screen, but we'll save them for a more advanced binding tutorial, this is just the basics after all. You've now learnt two useful ways to bind data and all the necessary preparation required beforehand. Luckily most of this stuff is quite easy to remember after a couple of practises and soon becomes a much quicker way to build and maintain programs. Particularly when you factor in styled validation, converters, relative paths and template binding. But I'll save those for another day, nefarious tease that I am.

1 comment:

  1. The best introduction to WPF DataBinding I've ever seen.

    ReplyDelete

Like what I've written? Dislike what I've written? Spotted an error? Go ahead and tell me about it!