Monday, March 24, 2008

In Soviet Russia, Blog Posts You

Wisdom of the ages right here, wisdom of the ages ;). Oren is like Life Ceral's Mikey for Alt.NET.

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.

Monday, March 03, 2008

WPF: Adventures in Formatting

Problem: I needed to have a really long string text in a bound WPF ListView column wrap instead of running on until the end of the text.

Solution: After doing quite a bit of searching around I was unable to find a good concrete example that worked in my particular case. The closest example I found looked something like this:


<ListView Height="Auto" Name="lsvErrorSummary2" ScrollViewer.CanContentScroll="False">

<ListView.View>

<GridView>

<GridViewColumn Header="Message" Width="200" DisplayMemberBinding="{Binding Path=Property}">

<GridViewColumn.CellTemplate>

<DataTemplate>

<TextBlock TextWrapping="Wrap" Text="{Binding}" />

</DataTemplate>

</GridViewColumn.CellTemplate>

</GridViewColumn>

</GridView>

</ListView.View>

</ListView>


Maddeningly enough this doesn't work as I spent at least an hour trying to find some magical combination of properties that would get it to work. The reason this doesn't work seems to be the fact that when we use the "DisplayMemberBinding" to bind the column WPF is using the binding for both the data and how to display the data, or at the very least WPF is just using the default for displaying the bound data. That being said I was able to solve it by making some changes.

The first thing I did was to remove the DataTemplate out into a Resources area (Window.Resources would work) and make sure to give it an x:Key="" attribute. After doing that I pulled out the GridViewColumn.CellTemplate tags as well. All that's left to do is to set the Text attribute of your DataTemplate's TextBlock equal to your real binding property (i.e. Text="{Binding Path=Message}" and point the CellTemplate attribute to the DataTemplate's x:Key (i.e. CellTemplate="{StaticResource CustomListViewItems}"). The end result should look similar to below:


<Window.Resources>
<DataTemplate x:Key="CustomListViewItems">
<TextBlock TextWrapping="Wrap" Text="{Binding Path=Message}" />
</DataTemplate>
</Window.Resources>

<ListView Height="Auto" Name="lsvErrorSummary" Width="511" ScrollViewer.CanContentScroll="False">
<ListView.View>
<GridView>
<GridViewColumn Header="Message" Width="200" CellTemplate="{StaticResource CustomListViewItems}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>

So far so good, now here's the hard part: this only works well when the column has a fixed width, but the TextWrapping setting will fail to take effect otherwise. In order to have it work with a flexible width a little bit of hacking is required. The easiest way to do this is using the ActualWidth property on the GridViewColumn you're wrapping. The following code shows doing this with two columns in the ListView:


((GridView)myListView.View).Columns[1].Width =
myListView.ActualWidth - ((GridView)myListView.View).Columns[0].ActualWidth - 5

With this code I'm using Columns[1] as the column that wraps and I am determining the width with a little padding on the end (hence the "- 5"). There may be a more effecient way of doing this, but the math is pretty straight forward and you can run this code whenever the ListView would resize in order to maintain the text wrapping.