Stumbling Through

Join me as I stumble, bumble and fumble my way through some new developer technologies. We'll laugh, we'll cry, there may be a mouse tossed through a monitor, but in the end we will all hopefully learn something.
in

Stumbling Through: WPF (Databinding Part II)

We left off previously with a listbox bound to a custom collection of People, showing their first name in the list.  What I want to do now is drop two textboxes onto the window that display the first and last name of the person selected in the listbox.  If the user manipulates the first name in the text box, the change should be reflected in the listbox as well.  First things first, lets drag out two text boxes from the toolbox and place them next to the list box on the window.  We're not going to worry about aesthetics here, just get them out there anywhere and name them, oh I dunno, txtFirstName and txtLastName.  For each of these textboxes, we want to bind their 'Text' property to the corresponding property of the PersonCollection dictionary.  Here is the line that will do it for FirstName, it can be copied and pasted for LastName as well (just change the path):

 

Text="{Binding Source={StaticResource _personCollection},Path=FirstName}"

 

Similar to how we bound the listbox itemsource, this line is telling the textbox to get its 'Text' value from the dictionary object with a key of _personCollection.  We additionally get the option to specify the binding property name (Path) all in the same line.  This syntax feels a bit ugly to me, and still suffers from one of the biggest drawbacks that databinding has always exhibited, that is, if the property name being bound to ever changes, the code will still compile but fail at run time because the bound property name is essentially hard-coded, and not tied in any way contextually to the binding source.  I'm told by local experts that using expression blend will help alleviate this problem, so I'm not going to make too big of a deal of it right now, but I will re-visit the syntax later and see if we can make it neater.  As it is, when we run the app and select a person from the list, we can see the text boxes reflecting the person's first and last names:

 

image

 

If we change the first name via the first name text box and tab off of it, we see that the first name in the listbox is automatically updated:

 

image

 

Great!  Next order of business is data validation combined with data binding, and hierarchical databinding too.  We'll make it so that first and last name cannot be specified as blank in the text box, or the textbox will glow red.  We will also link a Person to a collection of addresses, so another listbox will appear that displays the addresses of the selected person.

 

That is a beautiful thing, and I believe it comes from using the 'ObservableCollection' as our base collection class.  One very very important item I need to note here is that, if you are using my 'HotTrackListBoxStyle' for the listbox, you'll recall that I set a property of the listbox IsSynchronizedWithCurrentItem to true.  If you do not set this property, then the bindings to the textboxes WILL NOT WORK!  I can't tell you how many hours I wasted trying to figure out what was wrong with my bindings, that is why I put that property setter in the style so I will never forget to set it again (assuming I always use that ListBox style).

 

Now I'd like to revisit the syntax for our databinding... it seemed a little redundant to me to specify the object and path for every control, when the object is always the same.  This problem would only be magnified as we add more controls, so lets address it now with a little thing called 'DataContext'.  This property is available on all container controls (such as grids and stackpanels) and allows you to specify root bindings for all controls in said container.  In our little example, we know that all controls in the grid (the listbox and two textboxes) are all sourced from the _personCollection dictionary, so if we were to set the DataContext of the grid, then we should be able to clean up the databinding syntax of the controls.  Set up the main Grid tag to look like this:

 

<Grid DataContext="{Binding Source={StaticResource _personCollection}}">

 

Now, each of our controls in that grid no longer have to specify the _personCollection binding source, simply stating 'Binding' implies the same binding of the container's data context.  So now, our ItemSource for the listbox can look like this:

 

ItemsSource="{Binding}"

 

And the Text binding for the textboxes can look like this:

 

Text="{Binding Path=FirstName}"

 

I like that syntax much better, and its true power will be more evident when you are binding to many different collections on the same form, and you can group your controls based on data context to keep things nice and tidy.

 

I have one other task to attempt here that might get interesting... Let's make a new property of our Person class called 'FullName', which is read-only and concatenates the first and last name of the person.  It will look like this:

 

public String FullName
{
    get
    {
        return _firstName + " " + _lastName;
    }
}

 

Now bind the listbox's displaymember to be the full name instead of the first name:

 

image

 

Easy, right?  However, if we now change the first name via textbox, the listbox value is not updated to reflect our change.  How do we tell the listbox that the full name has changed?  Similar to WinForms databinding, we'll need our class to implement the INotifyPropertyChanged interface, which forces us to specify the 'PropertyChanged' event and OnPropertyChanged method (which simply raises the PropertyChanged event with the appropriate arguments).  When we have these items defined, then we can call OnPropertyChanged when the first or last name is changed, telling it that 'FullName' has also changed.  The following is the full definition of the Person object that will do just that:

 

namespace StumblingThroughWPFPartI
{
    public class Person : System.ComponentModel.INotifyPropertyChanged
    {
        String _firstName;
        String _lastName;

        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
        {
            PropertyChanged(this, e);           
        }

        public Person(String firstName, String lastName)
        {
            _firstName = firstName;
            _lastName = lastName;
        }

        public String FirstName
        {
            get
            {
                return _firstName;
            }
            set
            {
                _firstName = value;
                this.OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("FullName"));
            }
        }

        public String LastName
        {
            get
            {
                return _lastName;
            }
            set
            {
                _lastName = value;
                this.OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("FullName"));
            }
        }

        public String FullName
        {
            get
            {
                return _firstName + " " + _lastName;
            }
        }

    }
}

 

Now when we run the application and change either the first or last name, the full name displayed in the listbox is automatically updated:

 

image

Leave a Comment

(required) 

(required) 

(optional)

(required)