Wednesday, March 05, 2008

WPF: Data-bind This!

I've been struggling to learn WPF while writing a small log reporting app and one of the hardest things for me to figure out so far has been the data binding logic. Unlike plain-old WinForms, the many ways to hook-up object data to your controls follow a vague syntax, are not designer drive, and seem to come together magically at runtime; the last part being very similar to the Web Site project in VS2005. Since I'm extremely new to WPF and unfamiliar with the binding syntax (though I'm catching up) it would have been nigh impossible for me to figure it out how get anything done without using some serious Google-fu. Unfortunately even the Google-fu failed me and I had to rely on a group of experts to point me in the right direction. After a few more hours of struggling I was able to figure most of it out and I figured it was a good idea to help somone else's Google-fu out on my blog.

What do you mean your Google-fu failed?

Well in this case almost all of the examples I was able to find out there dealt with binding to a pre-done static list of items created and populated within the XAML itself, binding to a list of objects populated in the default constructor of a container class that would be instantiated within the XAML itself (very confusing), binding a custom list class that was once again instantiated via a default constructor within the XAML, and binding to a DataSet. None of these use cases really hit what I would consider the basic use case (and the one forum question I did find asking about this basic case went unanswered): You have a non-static list of objects conforming to a known type or interface generated from within your domain and exposed via a controller/presenter/service class. So looking for this out there was no-dice, and the MSDN documentation was nearly impossible to read and pretty much covers the worst of the above examples. Eventually, and with much trial and error, I was able to figure it out.

So how does it all work?

Good question! I'm still trying to tease out all the details, on how this works, but in general I've got it. In the back-end domain we're still generating and exposing our objects as normal, none of this has really changed, but once we get to the code-behind it's a little different, and the XAML is entirely different. The first thing we have to know in this space is that we can only bind to controls that support dependence properties, and these are the properties we will actually be binding to data such as the Text="..." attribute of a TextBlock (i.e. myTextBlock.Text). Without these properties being available on our control we're out of luck when it comes to binding. Next we'll have to determine whether or not we're binding to a single object or a collection of objects (myTextBlock.Text to a string or myComboBox to a collection). If we're using a collection we'll also decide if we want to customize our view into the collection (more on this later). I should also mention that at this point I'm only talking about One-Way binding (read-only into the data), I haven't quite gotten Two-Way binding down pat yet, so that'll be the topic of another post.

Now once we've figured out what control we want to bind to what data, we'll need to take a moment in order to figure out where the best place to expose this data is. In the case of WPF's XAML implementation there are a couple of places we can do this. The first is using the DataContext, a sort of "catch-all" that most of these dependency objects (what I'll call the objects with dependence parameters) have for data. This can be bound to pretty much anything as it takes in an "object" type, so the simplest way to set the DataContext in code is by saying myControl.DataContext = myData. You can also set the DataContext as an attribute on the tag for your particular object. One nice but dangerous thing about the DataContext is that it is inherited down the XAML tree so it's possible to set the DataContext property/tag on any object that contains your object and have the object that you want to bind take advantage of it. Beatriz has a good post on the subject here.

Another option for exposing the data is to directly set the binding "source" for the property you want to bind your particular WPF control. This is a little confusing to do as it requires you to deal with WPF's binding syntax. Using the binding syntax without the "source" looks something like this:

<TextBlock Text="{Binding Path=MyProperty}"/>

As you can see we have the Text attribute set to a string containing the keyword "Binding" in curly braces, which identifies that you want to bind the Attribute/Property to something. The Path= (notice the lack of quotes) statement is where you tell WPF which property on your data you want to bind to. It's possible to write this statement as "{Binding}" or "{Binding .}" to say that you want to bind to everything on some inherited DataContext, but remember that depending on the property you set this way it may just call .ToString() when it gets to binding, so be careful. Now when you use the binding syntax there's also a Source keyword. The binding syntax that uses the Source keyword looks something like this:

<TextBlock Text="{Binding Source={StaticResource myDataObject}, Path=MyProperty}"/>

This will tell the binding to find the Path property using the given data object within the source declaration. Again myDataObject can be declared either in your XAML or within your code-behind, however it's important to reference your code-behind clr-namespace at the top of your XAML file (see here (MSDN link, Sorry!). If you do this you would reference namespaceAlias:myDataObject, where namespaceAlias is whatever alias you've setup at the top of the XAML file. Using the source property is useful in some cases including debugging (again see Beatriz's article), but I'm not sure about all the details that surround the StaticResource keyword. Any comments to clarify that keyword would be appreciated.

You can also directly set the binding on a given object via code, however I've had some trouble getting the source to change as it kept its reference to the original object, but again it will be the topic for another post.

The final way to expose data to your binding is particular to binding to collections. If your control supports binding to a collection (Grid, ListView, ListBox, ComboBox, TreeView, etc.) then you can bind its ItemsSource Property/Attribute (I'm going to start calling them "propttributes" soon!) to your data, which will then allow the attributes and children of any template items (used in a DataTemplate or a HierarchicalDataTemplate, which you can find further information on here (MSDN link, sorry!)) to bind directly to properties on object instances within your collection.

This is all the "setup" we need to understand getting our controls bound to data (finally!) so all that's left to do is actually wiring up our binding to our attributes. Again this uses the binding syntax above, and I think I've covered how to write those, however feel free to ask if you have any questions or get confused. Anyways I think I've at least covered the basics, however I'll have to write some future posts on collections and the CollectionView for grouping, sorting, and formatting as well as getting data to refresh.

1 comment:

Tanner said...

I'm in the middle of this mess right now but most of my current troubles pertain to the datagrid and the way it implements rows compared to a winforms datagrid.

For example,

I have a child datagrid inside the rowdetailstemplate of another datagrid. If I set up the bindings on the child datagrid I get no visual display of data even though in the DataRowView Container surrounding the template I can see the data during debug. What's even more frustrating is that you can remove the datagrid and replace with stackpanels/labels and use the same binding syntax and it works fine.

A lot of what I see in articles is not what helps me most of the time. Static data items created manually in the constructor doesn't help when you use a different data access technology. Showing demos of master/detail that relies on a back end JOIN between two tables is nice but if you are weird like me and use a fully de-normalized data structure then you have to create your joins on the fly and having to traverse the visual tree backwards to get row index and cell index and value really eats up your overhead.

Debugging also is a pain.

If learning WPF is like climbing a mountain then currently I feel like i'm out there with a novelty carabiner and dry-rotting rope.