datagrid (part 2) -- Show me some code.

In part 1, I walked through some of the features in datagrid.
The source for the series is here.

In this part, we will create a UI that looks like this:

I will mostly highlight the interesting parts [instead of going on a step by step].
The source is available and it would be repetitive if I go step-by-step. 
One thing to note is that (approximately) 90% of the work to customize it is in XAML.  Some of it I did in code just to illustrate a point.

DataGridTextColumn bindings were the simplest ones. 
You can assign a DataFieldBinding -- which is an actual Binding to the data you want. I liked the approach of passing the full binding [instead of a property name] because it allowed me to pass parameters like StringFormat (see below) and converters and other similar binding features.  Nice!

<dg:DataGridTextColumnDataFieldBinding="{BindingQuantity}"Header="Quantity" /> 
<dg:DataGridTextColumnDataFieldBinding="{BindingQuote,StringFormat={}{0:C}}"Header="Quote" />   

From above, notice I mostly passed strings into the header property.  This was my choice; I could have more complex objects since Headers are Content controls and have a HeaderTemplate, but I did not need it here.  

The symbol column  is a DataGridHyperlinkColumn; I did nothing to customize it. If you compare it to DataGridTextColumn you will see an extra property. On the DataGridHyperlinkColumn,  DataFieldBinding -- looks or expects a Uri.  and the ContentBinding looks for the text that the UI will display.


 <dg:DataGridHyperlinkColumn DataFieldBinding="{Binding SymbolUri}"  
ContentBinding="{Binding Symbol}" Header="Symbol" SortMemberPath="Symbol"/>

DataFieldBinding  - is where the data (Uri) is coming from.  
ContentBinding - is the 'text' that is displayed on the hyperlink. 
SortMemberPath - is the data used for sorting. The datagrid will look at the property this path points to and if it implements IComparer will automatically handle the sorting. [In this app, most of the columns sort and I implemented no sorting logic at all :)]  

If you run the app, you can also see the "edit' behavior for DataGridHyperlinkColumn. You can edit the Uri, but not change the actual text (ContentBinding). You can manipulate it programmatically, but not from the editing experience.

Today's change column,   I implemented as a DataGridTemplateColumn.

 <dg:DataGridTemplateColumn CellTemplate="{StaticResource DailyPerformance}" 
Header="Today's Change" SortMemberPath="DailyDifference" />

A DatagridTemplateColumn is one where I can apply a CellTemplate so that it generates the UI.  I first chose it for this column because I wanted to implement the behavior of highlighting gains ( >0 ) with Green and losses ( <0) as Red using a DataTemplate.Trigger, but that did not work so I ended up using a Converter. Hind-sight this is likely a better solution [more performant] any way.

 <DataTemplate x:Key="DailyPerformance">
<TextBlock   Text="{Binding DailyDifference, StringFormat={}{0:C}}" 
   Foreground="{Binding '', Converter={StaticResource StockToBrushConverter}, 
   ConverterParameter=IsDailyPositive }">

Notice that I was still able to use a SortMemberPath on the DataGridTemplateColumn. This is really nice because regardless of what my UI looks like I can still sort the data.Total Gain column uses the same technique than Today's change.  

Rating column is a little gaudy on purpose .  

CellTemplateSelector="{StaticResource StarsTemplateSelector}" 
Header="Rating" SortMemberPath="Rating"/>

Here I used a TemplateSelector just for illustration purposes.

The selector is trivial. All it does is look for a template in a resource dictionary for the datagrid.

 public class StarsTemplateSelector : DataTemplateSelector 
       public override System.Windows.DataTemplate 
           SelectTemplate(object item, 
           System.Windows.DependencyObject container)
           StockXAction sac = item as StockXAction;
           FrameworkElement  fe = container as FrameworkElement; 

           if (sac != null && fe != null )

               string s = sac.Stars.ToString() + "StarsTemplate";
               DataTemplate ret =  fe.FindResource(s) as DataTemplate;
               return ret; 
           return base.SelectTemplate(item, container);

From the XAML, you can also notice the SortMemberPath again. The UI now has Star ratings on it, yet I can still sort and did not have to write any code !!

Separator Columns are empty 'dummy' columns I added just make empty space to separate the colums from autogenerated ones. See Autogenerated Columns below for why.

Autogenerated columns I wanted to leave AutoGenerateColumns="true" so you could see how the 'raw' data turns into the view.  It is also nice because you get to see some of the Column types I did not use for example the ComboBoxColum -- you can see it on the Autogenerated rating column. It is an enum, and it turns into a ComboBoxColumn.

Default data for new rows If you scroll to the bottom and a new row [functionality that comes out of box]. You will see this:


The NaN is a problem. What happens is here is it is trying to calculate Gain, but data has not been initialized.

The workaround is to handle the DataGrid's InitializeNewItem. This will be called as a new record is initalized.

 this.BigKahuna.InitializingNewItem += 
new InitializingNewItemEventHandler(BigKahuna_InitializingNewItem);

void  BigKahuna_InitializingNewItem(objectsender,

 InitializingNewItemEventArgs e)
            //cast e.NewItem to our type 
            StockXAction sa = e.NewItem as StockXAction;
            if (sa != null)
                sa.Symbol = "New data"; 
                sa.Quantity = 0;
                sa.Quote = 0; 
                sa.PurchasePrice = 0.0001; 

Copying data on DataGridTemplateColumns

Another issue you would notice is that if you do a Copy (Ctrl-C) or right click into Context menu which I added, the TemplatedColumns are not copied by default.  What I needed to handle in order for copying to work is to pass a binding to ClipboardContentBinding.  So we can tweak the template we had earlier and I will be tricky and pass the enumerator (Stars).

 <dg:DataGridTemplateColumn CellTemplateSelector="{StaticResource StarsTemplateSelector}" 
Header="Rating" SortMemberPath="Rating" 
ClipboardContentBinding="{Binding Stars }"  />

Now when I copy paste, I do get the value generated from ToString() on the enumerator.

One more thing to mention around Copying is that Data*Column has a CopyingCellClipboardContent event. This is good for overriding the value if I did not have a binding; what I noticed on this build is that if there is no binding set on ClipboardContentBinding, the event is not firing.  This will be fixed by RTM, interim just pass any binding (like {Binding}) and when the event fires you can override the value that will be cut & pasted from code.

OK, that covers most of the functionality. In part 3 we can take care of the styling.