Jaime Rodriguez On Windows Store apps, Windows Phone, HTML and XAML
On Monday, the WPF team released the CTP of their new datagrid control. You can download it from here. [Note that it requires .NET 3.5 SP1, released monday too]
I have been playing with it so I created this 3 part series.
The source for this sample is here.
Getting Started was trivial.
<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit" Title="Window1" Height="300" Width="300" >
I had some 'dummy' data simulating financial transactions so I took advantage of AutoGenerateColumns feature in the grid to get a quick 'fix':
The results were quite rewarding for a line of code. I could Reorder the columns, Resize the Columns, Sort by Column, Add New Rows , Edit the data, Select Rows , and Copy to Clipboard. I then moved on quickly to styling it a little bit.. Like every thing else WPF, the datagrid is incredibly flexible on customization by using styles and templates. In a few minutes, I hand-wrote this
<Window.Resources> <SolidColorBrush x:Key="DataGrid_Style0_Header" Color="#FF4F81BD"/> <SolidColorBrush x:Key="DataGrid_Style0_Alt0" Color="#FFD0D8E8"/> <SolidColorBrush x:Key="DataGrid_Style0_Alt1" Color="#FFE9EDF4"/> <Style x:Key="ColumnHeaderStyle" TargetType="{x:Type dg:DataGridColumnHeader}"> <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Header}" /> <Setter Property="Foreground" Value="White" /> </Style> <Style x:Key="RowStyle" TargetType="dg:DataGridRow" > <Style.Triggers> <Trigger Property="AlternationIndex" Value="1" > <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Alt1}" /> </Trigger> <Trigger Property="AlternationIndex" Value="0" > <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Alt0}" /> </Trigger> </Style.Triggers> </Style> </Window.Resources> and got this:
<Window.Resources> <SolidColorBrush x:Key="DataGrid_Style0_Header" Color="#FF4F81BD"/> <SolidColorBrush x:Key="DataGrid_Style0_Alt0" Color="#FFD0D8E8"/> <SolidColorBrush x:Key="DataGrid_Style0_Alt1" Color="#FFE9EDF4"/> <Style x:Key="ColumnHeaderStyle" TargetType="{x:Type dg:DataGridColumnHeader}"> <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Header}" /> <Setter Property="Foreground" Value="White" /> </Style> <Style x:Key="RowStyle" TargetType="dg:DataGridRow" > <Style.Triggers> <Trigger Property="AlternationIndex" Value="1" > <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Alt1}" /> </Trigger> <Trigger Property="AlternationIndex" Value="0" > <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Alt0}" /> </Trigger> </Style.Triggers> </Style> </Window.Resources>
and got this:
[We will cover styling in part3, let's first walk through all features]:
Selection Unit: Under the hood with the grid, you are really selecting cells, but the grid has a couple nice modes to make it easier for developer to use the concept of selected rows:
o SelectedCells has all the selected cells, including all the cells in a selected row when a row is selected through the RowHeader
o SelectedItems has the rows which are selected through the RowHeader, or is empty if only cells are selected (i.e., highlighting all the cells in a row will not add that row to SelectedItems)
Selection Mode: In Single mode, I can choose a single unit (see above for unit) and in Extended Mode I can select multiple units. The usual keyboard navigation short-cuts apply (Shift takes you to the end of current row, Ctrl preserves previous selection, etc.)
GridLines
Using GridLinesVisibility property, I can choose which gridlines are visible: All, Horizontal, Vertical, and None. I can also choose the color for the horizontal and vertical gridlines.
Headers (Row & Columns)
By tweaking the HeaderVisibility property, I can choose which headers are visible: All, Column, Row, None. The headers for each column can be customized/styled using a HeaderTemplate.
Column operations
Autogeneration of columns works quite well. The default mappings are:
*the ComboBoxColumn is created only if the field is writeable ( which happens when the property is not read only). For other types (e.g. DateTime or objects) the DataGridTextColumn will be the default with a ToString() on the object. You can customize AutoGeneration of columns by handling the AutoGeneratingColumn event. You will see this in part2.
ReadOnly columns is missing from the CTP, but that is not a huge problem, you can easily accomplish ReadOnly behavior by using DataGridTemplateColumns and replacing the templates with read-only controls ( like TextBlocks).
Column Resizing and Reordering is implemented out of the box and is toggled on/off via the CanUserReorderColumns and CanUserResizeColumns respectively. If you want to control reorder per column, there is a CanUserReorder property on the column itself.
Frozen columns. A frozen column is one that does not scroll out of view when the user scrolls in the horizontal direction. When a column is frozen every column displayed to its left are also frozen. Frozen columns is supported out of the box. You can control it by setting the IsFrozen property on a column.
You can see frozen columns in the demo I created by right clicking and showing the ContextMenu.
Row operations
Adding new rows is supported. You can enable it via CanUserAddRows. Deleting rows is supported too, controlled via CanUserDeleteRows.
For alternating rows, the datagrid has an AlternationCount property for controlling AlternateRows. The way it works is you set AlternationCount to the total number of styles/colors to be used. Usually this is two colors, truly alternating, but it could be more colors if needed [and you like to get funky] Once AlternationCount has been set, on your RowStyle you can create a trigger that checks AlternationIndex (which should be 0 to AlternationCount-1) and set the style there.
Editing Cells
Before getting into editing, I have to comment on entering Edit Mode. The Datagrid requires you to have focus in the cell in order to get into edit mode. To get focus, you can click on a cell, or tab into it. Once you have focus, the most common gestures to get into edit mode are supported:
For programmatically manipulating the cell with focus or during edit mode, the datagrid has a property of CurrentCell and each DataGridCell instance has an IsEditing property
You can customize the Editing experience for any column by providing a CellEditingTemplate. [If you used one of the stock columns listed above, those automatically provide a template]. Editing a cell has three commands that are fired as you get in and out of edit mode:
Other features Copy to Clipboard is implemented. Ctrl-C works fine. The data format appears to be tab delimited. Which is nice as it works seamlessly with excel. Keyboard Navigation [by using arrows and tab] works out of the box.
Some missing features already announced: The big one is RowDetails. I hear it is already in later builds, so the expectation is that it will be in by RTM. ReadOnly columns, and support for hidden columns. [Though I am thinking for those there is workarounds today]. Bugs along the way and known issues.
The only one I ran into is that DataTemplate.Triggers is not working on this build. I hear it will be working on later builds. Show me the code (or demo). Playing with all the features above is easy. The sample app I Created has a little bit of UI data bound to the datagrid that lets you manipulate the grid to see most of the features above.
In Part2, we start using these features to build a more 'insightful' view of the data.
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="{BindingDescription}"Header="Description"/> <dg:DataGridTextColumnDataFieldBinding="{BindingQuantity}"Header="Quantity" /> <dg:DataGridTextColumnDataFieldBinding="{BindingQuote,StringFormat={}{0:C}}"Header="Quote" />
<dg:DataGridHyperlinkColumn DataFieldBinding="{Binding SymbolUri}" ContentBinding="{Binding Symbol}" Header="Symbol" SortMemberPath="Symbol"/>
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 }"> </TextBlock> </DataTemplate>
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.
<dg:DataGridTemplateColumn 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) { //initialize 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.
In this third and final part of the datagrid series ( part1, part 2) we get into styling the datagrid a little bit. This part is not an all comprehensive tutorial on styling datagrids, I will just touch on what we did for my sample and share a few tips & tricks.
Here is the final look.
Here is the declaration for the whole data grid.
<dg:DataGrid ItemsSource="{Binding Data}" Margin="20" AlternationCount="2" RowStyle="{StaticResource RowStyle}" AutoGenerateColumns="true" Grid.RowSpan="1" x:Name="BigKahuna">
If you see above, I templated the Header. All I wanted was a blue background with white foreground.
<Style x:Key="ColumnHeaderStyle" TargetType="{x:Type dg:DataGridColumnHeader}" BasedOn="{StaticResource {x:Type dg:DataGridColumnHeader}}"> <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Header}" /> <Setter Property="Foreground" Value="White" /> <Setter Property="HorizontalContentAlignment" Value="Center" /></Style>
<Style x:Key="RowStyle" TargetType="dg:DataGridRow" > <Style.Triggers> <Trigger Property="AlternationIndex" Value="1" > <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Alt1}" /> </Trigger> <Trigger Property="AlternationIndex" Value="0" > <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Alt0}" /> </Trigger> </Style.Triggers> </Style>
One customization that I did not do in the demo, but is probably common is tweaking the selection. By default DataGrid does a blue highlight, imagine you want to change that color to a green; you would just need to override the Template for DataCell.
<Style x:Key="CellStyle" TargetType="{x:Type dg:DataGridCell}"> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="Background" Value="#FF3FC642" /> </Trigger> </Style.Triggers> </Style>
The rest is tips & tricks for designers.
If you look in the obvious place ( Edit Template) Blend does not have a lot of design-time support for all the pieces in the datagrid because the parts to customize are not quite parts of the ControlTemplate, but for the most part a few of these are DataTemplates, so you can workaround by creating any ContentControl, and editing the ContentTemplate there. To edit the parts of the DataGrid's controlTemplate, you can actually drop and individual parts (like DataGridHeader) of the template in a regular window and edit their style there.
The only 'tricky' one you might run into is DataGridHeaderBorder (because it does not expose a template). My advise there is to go ahead and select Edit Style -> Create Empty. Treat it as you would a border. Window4.xaml in the sample code has a small example of editing the pieces. [it is not complete by any means. It is also not pretty].
That is it in terms of getting my little portfolio data styled and it closes the DataGrid series. I hope it is useful to some one reading it. It was fun playing with the DataGrid. You don't realize how big this thing is until you play with it and see all the small, yet meaningful options it has.
For feedback, best place is the codeplex discussion . This build is a CTP, but it is quite functional and near completion. The control will ship out of band so don't wait too long; it will be shipping soon.
As I mentioned on the SP1 cheat sheet, client profile is an exciting new deployment feature in SP1.. Troy Martez has an intro paper and a deployment guide for it. Those docs and his coming blog are the ultimate reference on client profile, but I wanted to share the whole context and my 2c on the subject [because I have seen a lot of questions on the feature]. [For most of you readers half of this is old news, feel free to skip to the highlighted sentences].
The motivation for client profile:
The .NET run-time has gotten big over time. The growth was positive- we got WPF, WCF, Card Space, LINQ, etc- but the trade-off was increased download size and increased install time . The bad rep on download size is compounded by how we package the off-line installers. For example, the off-line 3.5 SP1 installer, is ~230 MB. The reason for it is because we ship x86, x64, and ia64 bundled together. The reality is that if you used the 3.5 SP1 boot-strapper and installed online, you would get 1/3 of that size, with a ~3 MB bootstrapper (instead of the whoopy 200mb).
Introducing client profile
The idea is simple: 1) package the subset of the framework that is most commonly used by client apps. 2) install that subset. 3) later (on the background preferably), upgrade the subset installed to a full .NET 3.5 SP1. This package in #1 above is the client profile SKU. Client profile includes WPF, Windows Forms, WCF, the BCL, data access, etc. (for a full manifest, check Justin's post). The net result is that client profile gives you a .NET run-time with an initial download of ~28 MB and install time much shorter than full .NET framework.
The details on the implementation If you read #1 above, client profile is a subset of the framework. The more accurate explanation is that it is a subset of .NET 1.1x + subset of 2.0 + subset of 3.x so unfortunately we can not install client profile on a machine that already has a .NET framework installed.
If you see above again, step #3 is upgrading the framework from client profile to full .NET 3.5 SP1.. how does it happen?
The temporary gotcha .NET 3.5 SP1 shipped last week, and client profile is available for download but Windows Update does not start updating machines to .NET 3.5 SP1 until they complete their testing - 4 to 6 weeks from now, we hope-. So if you installed client profile today you would not be upgraded automatically like we planned. Because of this reason, we are labeling the Client Profile release as "Preview" until Windows update begins upgrading systems to 3.5 SP1.
This does not mean the run-time with change; the run-time is frozen. We have the preview out for people to start testing their app against client profile and planning the deployment; we just recommend that you wait and release a client profile app only after Windows Update begins upgrading systems to 3.5 SP1.
Other FAQs: A point of confusion is the packaging. Client profile is ~28 MB, but if you go to download it, you will see two options available:
I am sure there are a lot more questions you will have (like how do I create an app that targets client profile). I will come back to client profile later, or at least point you to Troy's blog since he is working on explaining all of these. For today, I just wanted to explain the platforms and scenarios that client profile aims to address, its relationship to Windows Update, and the explanation on the off-line installer's size (this was causing confusion, at least for me). For the record a benefit I did not tout today is that client profile lets you customize the UI for the install experience; I will have to come back to that one since it is neat-o.
.NET 3.5 SP1 buzz peaked very early at the beta. At the time I was immersed in Silverlight, so I am now having to catch up; which is a bit of work since the release is packed with lots of new features. Below is my cheat sheet to date; I tried to group them on what I saw were the "core" investments.
The official release notes on 3.5 SP1 is at windowsclient.net/wpf. Tim Sneath has a great post that puts the enhancements into context both on size ( # of changes) and on impact ( based on customer feedback ) .
Deployment
Performance:
Graphics:
AppModel:
Tools/Other:
Data:
The list above is not all comprehensive, but it can help you catch up. Please let me know what I missed [I am sure there is lots of that]...
Tim is OOF and his automated response forwards WPF requests to me.. want to know the most FAQ was last week ? Where is the hands-on-lab for building the Outlook UI using WPF?..
Answer: It is here. Give me a few days to look through it and ask Ronnie –the author – if we should post it on windowsclient.net too..
There was 6 requests for it last week, which is great because it confirms some thing we are thinking today: we need more WPF reference applications.
We do have Family.Show, but are thinking of a new one. Should we??
If so, what is the scenario? should it be a LOB or a consumer scenario? high-end graphics? Do you really need step-by-step HOL?? Or would a slightly higher level write-up explaining all the trade-offs and best practices do?? [we are leaning for the latter]..
Let me know via comments or email…
Thanks!!
PS – if we move it or add it to windowsclient.net I will put it in the comments for this post to avoid an extra post… I tried to do that on Tim’s original post but new comments were disabled..
Last week I recorded a PDC CountDown video about the Pre-conference seminars at PDC2008.
Going into the recording, I knew I would have to keep it very high level (since the format for the whole recording is < 10 mins and there are important PDC announcements first, so I was couting on 8 minutes, and it takes me 3 to introduce each speaker, these folks have too many accomplishments). Now that I have no time constraints, here is a detailed introduction to the PDC pre-cons. The top part of this writing is a bit of high-level reasons why you should attend the pre-cons? Underneath that section, you can find the "inside the pre-con" planning, goals, processes, and raw session details as I see them (not as the polished abstracts describe).
<marketing> What is a pre-con? An all day training the day before PDC.
What should you expect if you attend a pre-con?
More info? Official abstracts and bios for the speakers at the PDC pre-con website.
</marketing>
<themeat> Now the inside story to the Pre-cons..
The goals: There were three key tenets at the core of pre-con planning:
Selecting the sessions I started with ~50 topics. I was all over the map, XNA, HPC, WPF, WF, Sharepoint, Live, F#, LINQ, etc. Then I mapped them to PDC tracks (sorry, afaik I can't share these but I can say there were four), for pre-con we added an extra track called "industry/fun" track; which aimed at doing some thing different from what you expect at PDC. We quickly narrowed it from 50 to 25 by applying a rigorous quality criteria:
When I was down to 20-25 sessions, we shared them with other teams at Microsoft and with an "Advisory board"; the advisory board is mostly Regional Directors; all of them trainers/influentials and community leaders. It was great to hear what they thought the community needed; it was quite different from what I was expecting, but they provided great info to back up their arguments, so we listened!
After the prioritization, we went after great presenters for each topic. If we did not find an absolute rock-star (inside or outside Microsoft ) we cut the topic.
In the end, there were still a few painful cuts. ASPX/MVC stuff, XNA, new MFC/Win32 stuff, and a few others. There was a an important goal to achieve: variety! . Some people inside Microsoft didn't get that it did not matter not to have the 10 most popular, but we needed to have some thing for everyone! I could agree that ASPX would have had more attendees than say "advanced debugging", but I needed to offer some thing to the C++ developers, and the web developers already had Silverlight, you get the drill. "Something for everyone" was maybe a secondary tenet.
The sessions themselves and the dynamics: Why the session, why the speaker(s)? I aimed to add context to each session, without repeating the abstracts and speaker bios. Please do read those before reading the ramblings below; my thoughts below are meant to add context on why the speaker or topic, but I did not repeat the outline or objectives for each session.
</themeat> Registering for pre-cons. Now that you have seen the line-up; I hope you do consider joining. It is going to be fun and enlightening. I promise! I have to close with an FAQ that has come up several times already: if you already registered for the conference and need to change your registration so you can attend the pre-con, it is possible to do it, just email PDC2008@ustechs.com and they will help you.
Feedback/Suggestions: If you have a suggestion for improving the pre-cons feel free to email me via the blog. We welcome all suggestions!
That is it (for now)! C U at PDC Pre-cons!