It is always a blast to talk at Tech Ed and this year was no different. It was great to get to meet many of you and hear how you are using DataGrid, DataForm, Validation, and RIA Services in your projects today.
As promised, here are the files for the demos that I showed in my presentations.
All demos were done on the MIX’09 Silverlight Beta bits and accompanying tools:
WUX 303: Building Data-Driven RIAs with Microsoft ASP.NET and Microsoft Silverlight
What we covered:
- Navigation, browser integration, permalinks
- Domain methods, filtering, paging, grouping, sorting
- Adding validation metadata
- Windows Authentication
- Writing a different view in ASP.Net and SEO
Watch It: (Right now it is restricted to Tech Ed attendees)
http://www.msteched.com/online/view.aspx?tid=ff4f56fc-ff74-4a68-90a8-33c69b0acecc
Files:
WUX 308: Delivering Rich User Experiences for Business Applications with Microsoft Silverlight 3
What we covered:
- Customizing the DataGrid: TemplateColumns, Row Details, events in template columns
- Charts
- Custimizing the DataForm: Using the Display attribute, the Display / Edit templates
- Writing a custom cross-field validation method
- Export to Excel
- DataGrid Copy / Paste with Excel
- Going out of browser
Watch It: (Right now it is restricted to Tech Ed attendees)
http://www.msteched.com/online/view.aspx?tid=b35f4684-54b2-4da5-9a70-663f79dd9ab7
Files:
Note: If you get a reference error for the Activity control, re-add the dll that is included in the zip.
Enjoy!
Since we released the first version of the DataGrid in October we have been listening to your feedback and have an early holiday gift to give.
Today we are releasing an updated DataGrid that contains almost 30 fixes to the top issues that we heard, including:
- Rows containing focus no longer disappear when the ItemsSource changes
- ComboBox and other controls opening a popup now work as expected when set as cell content
- DataGrid Selection is preserved when sorting
- Buttons outside the DataGrid work as expected when they are clicked while the DataGrid is in editing mode
- Improved FrozenColumn behavior
- Improved cell currency
- SelectedItem is updated before the CurrentCellChanged event is raised
- Many more...
For the full list of fixes check out this post by Brian Braeckel one of our DataGrid developers.
Breaking Changes
As a result of these fixes there are several behavioral breaking changes that you should consider before upgrading:
- FrozenColumnCount now takes hidden columns into account. Previously, only the first columns with Visibility set to Visible were included in the range of frozen columns. A column with Visibility set to Collapsed can now be considered frozen even though it isn’t visible. This is to ensure that a column’s DisplayIndex corresponds with the DataGrid’s FrozenColumnCount (i.e. if FrozenColumnCount == 3, columns with DisplayIndex < 3 are considered frozen).
- The DataGrid’s current cell is now kept in sync with an ICollectionView’s notion of currency. Moving the current position or changing the current item of an ICollectionView will now move the current cell and selected item of a DataGrid, and vice versa. For this to work, the DataGrid’s ItemsSource must be set to a collection that fully implements ICollectionView.
- A developer can now programmatically reorder a non-frozen column to the frozen column range (and vice versa) by changing a column’s DisplayIndex. The DataGrid used to throw an exception in these cases. An end-user, however, is still unable to do this through the UI by dragging column headers.
Installation Instructions
Visit the Silverlight 2 DataGrid December 2008 Release download page and follow the installation instructions carefully.
In order to use the new DataGrid you need to do all of the following:
- Close all instances of Microsoft Visual Studio.
- Delete all cached toolbox items by removing all files beginning with "toolbox" from %LocalAppData%\Microsoft\VisualStudio\9.0.
- On Microsoft Windows XP, %LocalAppData% is Documents and Settings\UserName\Local Settings\Application Data.
- On Microsoft Windows Vista, %LocalAppData% is Users\UserName\AppData\Local.
- Replace the following assemblies with the ones contained in the package in the link above:
- %ProgramFiles%\Microsoft SDKs\Silverlight\v2.0\Libraries\Client\System.Windows.Controls.Data.dll
- %ProgramFiles%\Microsoft SDKs\Silverlight\v2.0\Libraries\Client\System.Windows.Controls.Data.Design.dll
- For any existing projects, delete the reference to System.Windows.Controls.Data.dll and then re-add the reference.
We hope you enjoy the update and we look forward to bringing great new things to future versions of the DataGrid.
Freezing columns is just one of the many things you can do with the Silverlight DataGrid. Read more about the features that the Silverlight 2 DataGrid has to offer...
Freezing Columns
Have you ever been working with a large enough number of columns that you get horizontal scrolling, but you want some of the columns to not scroll? Think of a column such as Employee Name that you want to remain visible while you scroll through ten weeks of sales data. You might want to see which employee had the best sales for week 4. Since the Employee column is on the far left, any amount of scrolling causes it to scroll out of view, that is unless you freeze it. This causes it to not scroll and always be visible putting your other data in context.
To freeze columns, you simply set the FrozenColumnCount property on the DataGrid to the number of columns you want to freeze. For instance, to freeze the Employee column pictured above, you would write:
C#
employeeSalesGrid.FrozenColumnCount = 1;
VB
EmployeeSalesGrid.FrozenColumnCount = 1
IsFrozen
If you are programmatically working with a column and want to know if it is frozen or not, you can check the IsFrozen property on the Column. This is a readonly property that will let you know if the column you are working with is frozen.
As mentioned we made some changes, hopefully for the better, to the DataGrid API since Beta 2. Special thanks to our developer Yifung Lin for compiling this list:
DataGrid breaking changes
DisplayMemberBinding renamed to Binding
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
DisplayMemberBinding wasn’t an ideal name, but we used it because WPF has some precedence with it. This breaks down in scenarios where there is a separate Binding for display like in ComboBox scenarios since our Binding is actually the property field binding as opposed to the display binding.
Fix Required
Users using DataGridBoundColumn will need to change DisplayMemberBinding to Binding.
Beta 2
[Xaml]
<data:DataGridTextColumn DisplayMemberBinding="{Binding FirstName}" />
RTM
[Xaml]
<data:DataGridTextColumn Binding="{Binding FirstName}" />
DataGrid has VSM support
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
The DataGrid now supports the Visual State Manager.
Fix Required
Custom DataGrid templates need to be updated. The new templates can be found at: http://msdn.microsoft.com/library/cc278066(vs.95).aspx
IEditableObject moved to System.ComponentModel namespace
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
In the full framework IEditableObject is part of the System.ComponentModel namespace and it is located in System.dll For Silverlight, it was temporarily in the System.Windows.Controls.Data.dll under the System.Windows.Controls namespace. It now matches the full framework.
Fix Required
Users using IEditableObject need to update the namespace.
Beta 2
[c#]
using System.Windows.Controls;
RTM
[c#]
using System.ComponentModel;
SelectionChanged event changed from EventHandler to SelectionChangedEventHandler
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
Using SelectionChangedEventHandler allows the user to access OldItems and NewItems.
Fix Required
The signature for the SelectionChanged event handler needs to be updated.
Beta 2
[c#]
void SelectionChanged(object sender, EventArgs e)
{
}
RTM
[c#]
void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
DataGridHeaders enum renamed to DataGridHeadersVisibility
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
DataGridHeadersVisibility is a better indication of what the enum is.
Fix Required
DataGridHeaders -> DataGridHeadersVisibility.
Beta 2
[c#]
dataGrid.HeadersVisibility = DataGridHeaders.Column;
RTM
[c#]
dataGrid.HeadersVisibility = DataGridHeadersVisibility.Column;
DataGridAutoGeneratingColumnEventArgs change
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
The PropertyInfo of DataGridAutoGeneratingColumnEventArgs was changed to PropertyName and PropertyType.
Fix Required
Update to use PropertyName and PropertyType.
Beta 2
[c#]
string name = e.Property.Name;
Type type = e.Property.Type;
RTM
[c#]
string name = e.PropertyName;
Type type = e.PropertyType;
DataGridColumn GenerateElement and GenerateEditingElement now take in the cell
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
GenerateElement and GenerateEditingElement now provide the containing cell
Fix Required
Update signature.
Beta 2
[c#]
protected override FrameworkElement GenerateEditingElement(object dataItem)
{
}
protected override FrameworkElement GenerateElement(object dataItem)
{
}
RTM
[c#]
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
}
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
}
DataGridColumnReorderingEventArgs changed
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
The following modifications were made to the DataGridColumnReorderingEventArgs:
Old:
public object DragIndicatorContent { get; set}
public FrameworkElement DropLocationIndicator { get; set}
New:
public Control DragIndicator { get; set}
public Control DropLocationIndicator { get; set}
Fix Required
Update to use DragIndicator.
Beta 2
[c#]
object dragIndicator = e.DragIndicatorContent;
FrameworkElement dropIndicator = e.DropLocationIndicator;
RTM
[c#]
Control dragIndicator = e.DragIndicator;
Control type = e.DropLocationIndicator;
DataGrid.CancelingEdit and DataGrid.CommittingEdit events removed
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
Explicit Binding is required to implement these 2 events correctly. Since the Silverlight Binding does not support explicit Bindings, these events were removed. They will be resurrected when explicit Binding is supported.
Fix Required
The DataGrid.CancelingEdit and the DataGrid.CommittingEdit events can no longer be used. It is recommended that you use IEditableObject to track when a commit has occurred.
DataGrid.DataError event removed
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
The concept of a DataError event was taken from Winforms. This does not fit well into the WPF model so the event was removed.
Fix Required
The DataGrid.DataError event can no longer be used.
DataGridColumn.Header no longer supports visuals
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
Visuals cannot be duplicated so they cannot be used for the Header property due to column reordering.
Fix Required
To put visuals in column headers, users will need to use the header's ContentTemplate to include the visual instead of setting it as the Header.
Beta 2
[Xaml]
<data:DataGridTextColumn DisplayMemberBinding="{Binding FirstName}">
<data:DataGridTextColumn.Header>
<Button Content="hello" />
</data:DataGridTextColumn.Header>
</data:DataGridTextColumn>
RTM
[Xaml]
<data:DataGridTextColumn Binding="{Binding LastName}" Header="hello">
<data:DataGridTextColumn.HeaderStyle>
<Style TargetType="dataprimitives:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Content="{Binding}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</data:DataGridTextColumn.HeaderStyle>
</data:DataGridTextColumn>
Root element of DataGridRow changed from Grid to DataGridFrozenGrid
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
DataGridFrozenGrid derives from Grid and it contains an IsFrozen attached property. Users can use IsFrozen to specify parts of the Row that are frozen
Fix Required
Custom DataGridRow templates need to use DataGridFrozenGrid as the root element instead of Grid.
Gridline renamed to GridLine
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
Gridline would be appropriate if it was a word. MS Word says it is, but the dictionary says it’s not. WPF has precedence with GridLine so we went with that.
Fix Required
All instances of Gridline need to be renamed to GridLine.
Beta 2
[c#]
dataGrid.GridlinesVisibility = DataGridGridlinesVisibility.All;
RTM
[c#]
dataGrid.GridLinesVisibility = DataGridGridLinesVisibility.All;
Template only controls moved to primitives namespace
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
These types moved from the System.Windows.Controls namespace to the System.Windows.Controls.Primitives namespace:
- DataGridCellsPresenter
- DataGridColumnHeadersPresenter
- DataGridDetailsPresenter
- DataGridRowsPresenter
- DataGridColumnHeader
- DataGridRowHeader
Fix Required
The types above need to be referenced using the System.Windows.Controls.Primitives namespace.
DataGridCheckBoxColumn.Content was removed
Who Is Affected: Silverlight 2 Beta 2 managed applications that use the DataGrid.
Summary
The Content property was not useful for mainline scenarios so it was removed.
Fix Required
The DataGridCheckBoxColumn.Content property can no longer be used. In rare scenarios where it is needed, users can template the CheckBox through ElementStyle and EditingElementStyle
As you might have heard, we just released Silverlight 2, and with it the first version of the Silverlight DataGrid! We have been building this for a while, giving the first glimpse of it last March in Silverlight 2 Beta 1, and tweaking it ever since based on your feedback. We were able to pack a lot of great features into it, and I look forward to hearing from you what features you want next!
In celebration of its release, all of the previous walkthroughs have been updated to work with the RTW bits, and new walkthroughs are on their way.
Also, if you have never used the Silverlight DataGrid before, here is a good place to start.
What's New?
You might be wondering what has changed since Beta 2:
| Accessibility: One of the biggest features that we have added since Beta 2 is the addition of an automation peer framework which allows the DataGrid to work well with screen readers. |
| Support for the VSM: The Visual State Manager (VSM) is a tool that allows designers to customize how a control looks in various states, and the animations used to switch between these states. Read more about the Visual State Manager... |
| Performance, Performance, Performance: We have done a lot in this area to try to get the DataGrid running as fast as possible. If the last time you used it was in Beta 1 or even Beta 2, you should definitely take another look. |
| API Improvements: Based on your feedback, we took another pass through the API in an effort to simplify it more and give you more control over the DataGrid. Read about the Breaking Changes since Beta 2... |
| Great New Look: The entire framework got a visual upgrade since Beta 2, and the DataGrid is no exception. |
What's Else Can It Do?
For those of you new to the Silverlight DataGrid, here is a quick rundown of what it could already do:
| Auto-sizing: Everything in the DataGrid can be auto-sized. That includes the DataGrid itself, Columns, Rows, Headers, and Row Details meaning that you do not have to worry about the size of what you are putting into the DataGrid, it will grow to accommodate it. Read more about Auto-sizing... |
 | Sorting: Any time you bind the DataGrid to a collection that implements IList such as a List<T> you now get sorting for free. Even better, sorting isn't limited to a single column, but end users can sort by multiple columns by holding Shift and clicking on additional column headers. As an RTM improvement, you can also choose which columns you want to support sorting. Read more about Sorting... |
| Column Reordering: End users can reorder columns at runtime just by dragging a column's header, and developers can control not only the order, but the appearance of the reordering UI as well as the behavior. |
 | Column Resizing: Just like reordering, and sorting, end users can control the size of columns at runtime. |
| Frozen Columns: This is a semi-advanced feature, but once you know about it you'll be surprised at how useful it can be. A frozen column does not scroll horizontally but rather is "frozen" in place. This is the same as the Freeze Column feature in Excel. Read more about Freezing Columns... |
| Row Details: This feature is one that we are very excited about. In short, it is an area in each row that allows you to put any UI you want that spans all columns. This could be anything from form-based entry to the contents of a column that would do better if given more space. |
| Auto-Generation of Columns: A great feature to help get an application up and running quickly, auto-generation of columns creates a column for every property on the item that it is bound to. |
| Highly Customizable Visuals: The look of the Silverlight 2 DataGrid has been improved, but it doesn't mean that you can't change it. Every aspect of the DataGrid's appearance can be retemplated to create a drastically different looking DataGrid that maintains all of the great features listed here. Read more about Customizing Control Visuals... |
| Editable: The DataGrid control allows your users to edit the data that it is bound to. One benefit of the Silverlight DataGrid is that it gives you control over what control is used to display the data for each cell, and what control is used to edit the data for each cell even if they are different. The customization is easy, and it handles switching between the two when the user wants to go into edit mode. |
| Scrollable: Another benefit that the DataGrid provides is the ability to scroll through long lists of data. The DataGrid does a lot behind the scenes to make this experience as smooth as possible for your users even when you have a large number of rows. You can also scroll columns and items into view to make sure that an item you want to highlight to your user is visible on screen. |
| Multi-Selection: The DataGrid provides both single as well as extended selection modes, allowing you to choose if you want users to be able to select more than one row at a time |
| Built-in Column Types: Out of the box you can use both Text columns as well as CheckBox columns, and creating your own column types is easy. Read more about using the Built-in Column Types: |
 | Template Column: In addition to the built-in column types, the template column gives you complete control of what to show during display and edit modes. Read more about using the Template Column: |
Multi-column Sorting
One of the most requested features in Beta 1 was a way to sort the data contained in the DataGrid. In fact it was so desired that clever developers such as Matt Berseth simply created their own implementation. Fortunately, you no longer have to put much work into getting this functionality out of the box.
To get sorting functionality in your DataGrid you will need to perform the following steps:
- Make sure that the collection that you use as the DataGrid's ItemsSource implements IList. Common implementations are List<T> and ObservableCollection<T>.
- That's it, you don't need to do anything else
Why this odd requirement? Under the covers, when a DataGrid is data bound to a collection that implements IList, it creates an internal ListCollectionView to provide the sorting functionality. Since CollectionViews can handle multiple levels of sorting, the DataGrid also possesses this functionality and exposes it by allowing your users to click on multiple headers while holding the Shift key.
Here is the DataGrid showing off multi-column sorting:
Customizing Sorting
Alternatively if you want more control over how the sorting algorithm is performed, you can implement the ICollectionView interface currently found in System.Windows.Controls.Data. The DataGrid knows that when it is bound to an implementation of ICollectionView that it should delegate all sorting to it, listening to its SortDescriptions collection and visually displaying the sorting status of the CollectionView. Additionally by using an external CollectionView, you can have multiple controls share the same sort order.
Turning Off Sorting
It is easy to get sorting out of the box, but you might not want to allow your DataGrid or a particular column to be sorted by your user. To turn this off, use DataGrid's CanUserSortColumns property to turn on or off all sorting at the DataGrid level, or use DataGridColumn's CanUserSort to turn on or off sorting for a particular column.
C#
dataGrid1.CanUserSortColumns = false;
unsortableColumn.CanUserSort = false;
VB
DataGrid1.CanUserSortColumns = False
UnsortableColumn.CanUserSort = False
Choosing Which Property to Sort By
Sometimes the property that your column is bound to is not always the one that you want it to sort by. Normally a column sorts by the property that it is bound to through the Binding property. However, if you look at the Template Column there is no Binding property, meaning that the DataGrid has no idea of how to sort the data if you were to click on that column's header. To give the DataGrid that information, you can use a property found on all column types called SortMemberPath. This property allows you to specify a path to the property on the data item that you want that particular column to sort by. Now if you create a template column that uses a DatePicker to allow your user to select a date instead of the standard TextBox, you can still provide sorting for that column by setting the SortMemberPath.
C#
datePickerColumn.SortMemberPath = "Birthday";
VB
DatePickerColumn.SortMemberPath = "Birthday"
Notice that since SortMemberPath is on DataGridColumn, you can use this beyond just template columns and can actually have a column bound to one property, but sort based on another property. This is a much less common scenario, but becomes useful when working with types that normally don't support sorting such as Colors or Images.
Sorting UI Cues
In the picture above you can see that each sorted column has an arrow in its header indicating if it is sorted ascending or descending. It also has a subtle animation when switching between the two. If you want to customize how this looks or behaves, the easiest way to do this is to use the Visual State Manager in Blend. Otherwise, you can manually override the default DataGridColumnHeader template using the DataGrid.ColumnHeaderStyle property. The template contains two storyboards, one named "SortAscending" and the other named "SortDescending". These two storyboards are ran by the DataGrid when a column is sorted ascending or descending. Finally there is a third storyboard called "Unsorted" that is ran when the data is no longer being sorted by that column and can be used to clean up any UI that you used to display sort status.
Auto-sizing the DataGrid
The Silverlight 2 DataGrid is auto-sized by default. This means that if you put it in a Canvas or a StackPanel and don't specify a Height or Width, or a MaxHeight or MaxWidth, you will get a DataGrid that is sized to its contents.
Warning: This can be dangerous however if you are working with a large (1000+ item) collection as the ItemsSource. Since the DataGrid will grow to the size of its contents, and will create objects for each cell that it can show based on its height, it will literally create thousands of objects to display all of the data. As you can guess this has app performance implications so you should avoid the combination of an auto-sized DataGrid and a large amount of data to display. This isn't an issue of course if you specify a Height or MaxHeight for the DataGrid, or put it in a container such as a Grid that provides it with a size.
Since the default size for the DataGrid is auto you won't have write any code to get auto-sizing behavior, but if you do give the DataGrid a height or width, and then want to switch it back to auto-size at runtime, simply set that value to double.NaN which is WPF and Silverlight's way of having a FrameworkElement auto-size itself.
C#
dataGrid1.Height = double.NaN;
VB
DataGrid1.Height = Double.NaN
Auto-sizing Columns and Rows
In addition to the DataGrid auto-sizing itself, Columns and Rows are also auto-sized by default. Like the DataGrid, rows are auto-sized in the same manner by setting the value to double.NaN while columns on the other hand are done slightly differetly. If you have ever worked with the Grid control, you might have noticed that the Width property of its ColumnDefinitions is not of type double, but rather a struct called GridLength. The reason for this is that unlike a FrameworkElement's Width or Height properties which only have the possible values of Auto or a numeric value, a ColumnDefinition's Width can be set to a numeric value, Auto, or Star. DataGridColumn.Width follows a similar pattern where instead of being of type double, it is of type DataGridLength.
The possible values for DataGridLenth are:
- Auto: This auto-sizes the column or row to grow to the size of the largest header or cell. It is effectively a combination of the other two auto-size modes. You might notice as you scroll when a new larger cell is encountered that this value increases. It will however not decrease once that item is scrolled out of view.
- SizeToHeader: This auto-sizes the column or row the grow to the size of the header. This will ignore the contents of the cells when determining its size and will not change unless the size of the header changes.
- SizeToCells: This auto-sizes the column or row to grow to the size of the largest visible cell. It will ignore the header cell in this calculation. You might notice as you scroll when a new larger cell is encountered that this value increases. It will however not decrease once that item is scrolled out of view.
- Numeric: Unlike the others this is not an enum value, but rather simply a numeric value such as 100. This mode will behave the same way that Beta 1 did, however setting it in code behind is slightly different.
Setting these in XAML is straight forward other than that unfortunately just like GridLength, there is no Intellisense for the enum values since a number is a valid entry as well.
XAML
<data:DataGrid x:Name="dataGrid1" AutoGenerateColumns="False">
<data:DataGrid.Columns>
<data:DataGridTextColumn Binding="{Binding}"
Width="Auto" Header="Auto"/>
<data:DataGridTextColumn Binding="{Binding}"
Width="SizeToHeader" Header="Size to Header"/>
<data:DataGridTextColumn Binding="{Binding}"
Width="SizeToCells" Header="Size to Cells"/>
<data:DataGridTextColumn Binding="{Binding}"
Width="50" Header="Numeric"/>
</data:DataGrid.Columns>
</data:DataGrid>
If you were to run the code above and give it a data source it would look something like this:
As you can see, once you know the possible enum values, setting it in XAML is fairly simple, and the Beta 1 code of Width="100" just works. When you are working in code behind however things get slightly more tricky.
If you wanted to write code to do the same as above:
C#
DataGrid dataGrid1 = new DataGrid();
dataGrid1.AutoGenerateColumns = false;
DataGridTextColumn col1 = new DataGridTextColumn();
col1.Binding = new Binding();
col1.Width = DataGridLength.Auto;
col1.Header = "Auto";
dataGrid1.Columns.Add(col1);
DataGridTextColumn col2 = new DataGridTextColumn();
col2.Binding = new Binding();
col2.Width = DataGridLength.SizeToHeader;
col2.Header = "Size to Header";
dataGrid1.Columns.Add(col2);
DataGridTextColumn col3 = new DataGridTextColumn();
col3.Binding = new Binding();
col3.Width = DataGridLength.SizeToCells;
col3.Header = "Size to Cells";
dataGrid1.Columns.Add(col3);
DataGridTextColumn col4 = new DataGridTextColumn();
col4.Binding = new Binding();
col4.Width = new DataGridLength(50);
col4.Header = "Numeric";
dataGrid1.Columns.Add(col4);
dataGrid1.ItemsSource = "a b c d e f g h i j".Split();
LayoutRoot.Children.Add(dataGrid1);
VB
Dim dataGrid1 As New DataGrid
dataGrid1.AutoGenerateColumns = False
Dim col1 As New DataGridTextColumn
col1.Binding = New Binding
col1.Width = DataGridLength.Auto
col1.Header = "Auto"
dataGrid1.Columns.Add(col1)
Dim col2 As New DataGridTextColumn
col2.Binding = New Binding
col2.Width = DataGridLength.SizeToHeader
col2.Header = "Size to Header"
dataGrid1.Columns.Add(col2)
Dim col3 = New DataGridTextColumn
col3.Binding = New Binding
col3.Width = DataGridLength.SizeToCells
col3.Header = "Size to Cells"
dataGrid1.Columns.Add(col3)
Dim col4 = New DataGridTextColumn
col4.Binding = New Binding
col4.Width = New DataGridLength(50)
col4.Header = "Numeric"
dataGrid1.Columns.Add(col4)
dataGrid1.ItemsSource = "a b c d e f g h i j".Split()
LayoutRoot.Children.Add(dataGrid1)
The interesting line above is where the Numeric column's width is set to 50. There are two ways to do this. If you are working with doubles, you can create a new GridLength that takes a double in its constructor. This is the most common and the easiest way to do it. If however you are working with strings such as a value in a TextBox, you can use the ConvertFrom method on DataGridLengthConverter which takes an object and returns a GridLength.
Silverlight 2 Beta 2 was released this weekend, see what ScottGu has to say about it here. We've all been pretty busy since releasing Beta 1 at MIX, and there are a lot of great new features across the product as a result. The question that you might be asking is what has changed in the DataGrid? Check out this list of new features and their associated posts to find out.
New Features Overview
- Auto-sizing: Unlike Beta 1 when you had to specify widths and heights yourself, everything in the DataGrid now can be auto-sized. That includes the DataGrid itself, Columns, Rows, Headers, and Row Details.
- Sorting: Now any time you bind the DataGrid to a collection that implements IList such as a List<T> you now get sorting for free. Even better sorting isn't limited to a single column, but end users can sort by multiple columns by holding Ctrl and clicking on additional column headers.
- Column Reordering: In Beta 1 the order of columns could be set programmatically, but now in Beta 2 we have extended this capability to the end user, where column reordering can be performed at runtime just by dragging a column's header.
- Frozen Columns: This is a semi-advanced feature, but once you know about it you'll be surprised at how useful it can be. A frozen column does not scroll horizontally but rather is "frozen" in place. This is the same as the Freeze Column feature in Excel.
- Programmatic Scrolling: Now you can scroll columns and items into view to make sure that an item you want to highlight to your user is visible on screen.
- Performance Enhancements: One of the big points of feedback we got in Beta 1 was vertical scrolling speed needed to be faster. We've made some improvements in this space to speed things up.
- API Improvements: These are changes that we made to existing API's to try to make using the DataGrid easier. Hopefully they are all for the better, and we would love to hear your feedback if you think that something could be easier.
You might have noticed that posts have dropped off over the last month or so. The reason being that we have been pretty heads down working on some great new stuff for the DataGrid. I can't go into specifics just yet, but stay tuned...
If however you are starved for DataGrid info, check out these posts over on the Swiss MSDN blog who have been doing some great things with the DataGrid in Silverlight Beta 1:
Using WCF and LINQ to Connect a DataGrid to a SQL Database
CRUD operations using Silverlight 2 Beta 1 Part 1 / Part 2
Adding Paging to the Silverlight DataGrid
Know of any other really good Silverlight DataGrid posts? I would love to hear about them in the comments.
Now that you know the basics of the Silverlight DataGrid and how to specify the Columns in XAML, you might want to customize your DataGrid's columns at runtime.
This process is pretty straight forward, so instead of doing the usual end-to-end walk through, I'm going to provide you with a Rosetta Stone between the static XAML form and the dynamic C#/VB form for the scenario in the last post.
Defining a DataGrid
For any of these columns to be useful you are going to first need a DataGrid to add them to. The following creates a DataGrid, adds it as a child of the root layout Grid, and sets its ItemsSource to a collection called "source".
C#
DataGrid targetDataGrid = new DataGrid();
targetDataGrid.ItemsSource = source;
targetDataGrid.AutoGenerateColumns = false;
LayoutRoot.Children.Add(targetDataGrid);
VB
Dim TargetDataGrid As New DataGrid
TargetDataGrid.ItemsSource = Source
TargetDataGrid.AutoGenerateColumns = False
LayoutRoot.Children.Add(TargetDataGrid)
Defining a DataGrid Text Column
The following creates a DataGridTextColumn bound to the FirstName property with a header of "First Name", and adds it to the columns collection of the DataGrid named "targetDataGrid".
Static
<data:DataGrid x:Name="targetDataGrid">
<data:DataGrid.Columns>
<data:DataGridTextColumn Header="First Name"
Binding="{Binding FirstName}" />
</data:DataGrid.Columns>
</data:DataGrid>
Dynamic
C#
using System.Windows.Data;
...
DataGridTextColumn textColumn = new DataGridTextColumn();
textColumn.Header = "First Name";
textColumn.Binding = new Binding("FirstName");
targetDataGrid.Columns.Add(textColumn);
VB
Imports System.Windows.Data
...
Dim TextColumn As New DataGridTextColumn
TextColumn.Header = "First Name"
TextColumn.Binding = New Binding("FirstName")
TargetDataGrid.Columns.Add(TextColumn)
Defining a DataGrid CheckBox Column
The following creates a DataGridCheckColumn bound to the Available property with a header of "Available", and adds it to the columns collection of the DataGrid named "targetDataGrid".
Static
<data:DataGrid x:Name="targetDataGrid">
<data:DataGrid.Columns>
<data:DataGridCheckBoxColumn Header="Available "
Binding="{Binding Available}" />
</data:DataGrid.Columns>
</data:DataGrid>
Dynamic
C#
using System.Windows.Data;
...
DataGridCheckBoxColumn checkBoxColumn = new DataGridCheckBoxColumn();
checkBoxColumn.Header = "Available";
checkBoxColumn.Binding = new Binding("Available");
targetDataGrid.Columns.Add(checkBoxColumn);
VB
Imports System.Windows.Data
...
Dim CheckBoxColumn As New DataGridCheckBoxColumn
CheckBoxColumn.Header = "Available"
CheckBoxColumn.Binding = New Binding("Available")
TargetDataGrid.Columns.Add(CheckBoxColumn)
Defining a DataGrid Template Column
The following creates a DataGridTemplateColumn bound to the Birthday property with a header of "Birthday", and adds it to the columns collection of the DataGrid named "targetDataGrid".
Static
<UserControl.Resources>
<local:DateTimeConverter x:Key="DateConverter" />
</UserControl.Resources>
...
<data:DataGrid x:Name="targetDataGrid" AutoGenerateColumns="False" >
<data:DataGrid.Columns>
<data:DataGridTemplateColumn Header="Birthday">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Birthday,
Converter={StaticResource DateConverter}}"
Margin="4"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<basics:DatePicker
SelectedDate="{Binding Birthday, Mode=TwoWay}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid.Columns>
</data:DataGrid>
Dynamic
There are two ways to dynamically create a template column for a DataGrid. One is to load in the CellTemplate and CellEditingTemplates as DataTemplates from resources, and the other is to construct the DataTemplates on the fly using XamlReader.
1. Resources Method
This method creates the CellTemplate DataTemplate and the CellEditingTemplate DataTemplate in XAML and stores them as named resources. Then when the column is created the DataTemplates are used.
Use the below XAML to create the DataTemplates as resources to support the code for this method.
<UserControl.Resources>
<local:DateTimeConverter x:Key="DateConverter" />
<DataTemplate x:Key="myCellTemplate">
<TextBlock
Text="{Binding Birthday,
Converter={StaticResource DateConverter}}"
Margin="4"/>
</DataTemplate>
<DataTemplate x:Key="myCellEditingTemplate">
<basics:DatePicker
SelectedDate="{Binding Birthday, Mode=TwoWay}" />
</DataTemplate>
</UserControl.Resources>
C#
using System.Windows.Data;
...
DataGridTemplateColumn templateColumn = new DataGridTemplateColumn();
templateColumn.Header = "Birthday";
templateColumn.CellTemplate = (DataTemplate)Resources["myCellTemplate"];
templateColumn.CellEditingTemplate =
(DataTemplate)Resources["myCellEditingTemplate"];
targetDataGrid.Columns.Add(templateColumn);
VB
Imports System.Windows.Data
...
Dim TemplateColumn As New DataGridTemplateColumn
TemplateColumn.Header = "Birthday"
TemplateColumn.CellTemplate = Resources("myCellTemplate")
TemplateColumn.CellEditingTemplate = Resources("myCellEditingTemplate")
TargetDataGrid.Columns.Add(TemplateColumn)
2. XamlReader Method
This method creates the DataTemplate inline using the XamlReader class. This class takes a string and parses it to try to build a visual tree. In this case we are creating DataTemplates. This method is especially useful if the DataTemplate itself has to be dynamic. One example being if you wanted to modify what the element in the template was bound to.
Warning: This method is considerably more difficult than the resources method. I recommend only using this if you need to dynamically create the DataTemplate.
Some things to watch out for:
- You will need to declare any XAML namespace that is used in the data template
- Any custom XAML namespace needs to specify both the clr-namespace and the assembly
- You cannot have white space between the xmlns: and the name of your namespace
- External resources cannot be referenced, they need to be declared inline
- The entire template is a single line, so if you get a XAML Parse exception, it will always say line 1, however the character is fairly accurate if you were to concatenate all of your lines.
- When using the StringBuilder approach shown below, make sure that you have the correct white space at the end of a line so that it is correctly separated when concatenated with the next line.
Now that the warnings are out of the way, here is how you do the equivalent of the code above:
C#
using System.Windows.Data;
using System.Windows.Markup;
using System.Text;
...
DataGridTemplateColumn templateColumn = new DataGridTemplateColumn();
templateColumn.Header = "Birthday";
StringBuilder CellTemp = new StringBuilder();
CellTemp.Append("<DataTemplate ");
CellTemp.Append("xmlns='http://schemas.microsoft.com/winfx/");
CellTemp.Append("2006/xaml/presentation' ");
CellTemp.Append("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' ");
//Be sure to replace "YourNamespace" and "YourAssembly" with your app's
//actual namespace and assembly here
CellTemp.Append("xmlns:local = 'clr-namespace:YourNamespace");
CellTemp.Append(";assembly=YourAssembly'>");
CellTemp.Append("<Grid>");
CellTemp.Append("<Grid.Resources>");
CellTemp.Append("<local:DateTimeConverter x:Key='DateConverter' />");
CellTemp.Append("</Grid.Resources>");
CellTemp.Append("<TextBlock ");
CellTemp.Append("Text = '{Binding Birthday, ");
CellTemp.Append("Converter={StaticResource DateConverter}}' ");
CellTemp.Append("Margin='4'/>");
CellTemp.Append("</Grid>");
CellTemp.Append("</DataTemplate>");
StringBuilder CellETemp = new StringBuilder();
CellETemp.Append("<DataTemplate ");
CellETemp.Append("xmlns='http://schemas.microsoft.com/winfx/");
CellETemp.Append("2006/xaml/presentation' ");
CellETemp.Append("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' ");
CellETemp.Append("xmlns:basics='clr-namespace:System.Windows.Controls;");
CellETemp.Append("assembly=System.Windows.Controls' >");
CellETemp.Append("<basics:DatePicker ");
CellETemp.Append("SelectedDate='{Binding Birthday, Mode=TwoWay}' />");
CellETemp.Append("</DataTemplate>");
templateColumn.CellTemplate =
(DataTemplate)XamlReader.Load(CellTemp.ToString());
templateColumn.CellEditingTemplate =
(DataTemplate)XamlReader.Load(CellETemp.ToString());
targetDataGrid.Columns.Add(templateColumn);
VB
Imports System.Windows.Data
Imports System.Text
Imports System.Windows.Markup
...
Dim TemplateColumn As New DataGridTemplateColumn
TemplateColumn.Header = "Birthday"
Dim CellTemp As New StringBuilder
CellTemp.Append("<DataTemplate ")
CellTemp.Append("xmlns='http://schemas.microsoft.com/winfx/")
CellTemp.Append("2006/xaml/presentation' ")
CellTemp.Append("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' ")
'Be sure to replace "YourNamespace" and "YourAssembly" with your app's
'actual namespace and assembly here
CellTemp.Append("xmlns:local = 'clr-namespace:YourNamespace")
CellTemp.Append(";assembly=YourAssembly'>")
CellTemp.Append("<Grid>")
CellTemp.Append("<Grid.Resources>")
CellTemp.Append("<local:DateTimeConverter x:Key='DateConverter' />")
CellTemp.Append("</Grid.Resources>")
CellTemp.Append("<TextBlock ")
CellTemp.Append("Text = '{Binding Birthday, ")
CellTemp.Append("Converter={StaticResource DateConverter}}' ")
CellTemp.Append("Margin='4'/>")
CellTemp.Append("</Grid>")
CellTemp.Append("</DataTemplate>")
Dim CellETemp As New StringBuilder
CellETemp.Append("<DataTemplate ")
CellETemp.Append("xmlns='http://schemas.microsoft.com/winfx/")
CellETemp.Append("2006/xaml/presentation' ")
CellETemp.Append("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' ")
CellETemp.Append("xmlns:basics='clr-namespace:System.Windows.Controls;")
CellETemp.Append("assembly=System.Windows.Controls' >")
CellETemp.Append("<basics:DatePicker ")
CellETemp.Append("SelectedDate='{Binding Birthday, Mode=TwoWay}' />")
CellETemp.Append("</DataTemplate>")
TemplateColumn.CellTemplate = XamlReader.Load(CellTemp.ToString())
TemplateColumn.CellEditingTemplate = XamlReader.Load(CellETemp.ToString())
targetDataGrid.Columns.Add(TemplateColumn)
If you read my last post, you might have noticed how easy it is to get a Silverlight DataGrid up and running with the AutoGenerateColumns feature. Something else you might have noticed is that if you don't like the default column choices it is sort of hard to change them. Not to fear however since there are two ways to get more control. One way is to use the AutoGeneratingColumn event (we'll talk about this in a different post) but the easiest way, and the topic of our discussion today, is by using the Columns collection.
The Columns Collection and You
The Columns Collection is your ticket to controlling the DataGrid's column order, appearance, and even what controls they use to represent data in their cells. It enables you to have a concrete instance for each column that you define unlike auto generation that simply fills the column collection for you.
Before we dive into using the Columns collection though, it is useful to know what you can put in it.
DataGrid Column Types
- DataGridTextColumn - When you think of a standard column in a DataGrid, you probably picture something that looks a lot like a DataGridTextColumn. This column is the default and uses a TextBlock to display its data, and a TextBox to allow editing of its data.
- DataGridCheckBoxColumn - The DataGridCheckBoxColumn is the first out of box custom column type. It provides a read-only CheckBox for displaying a boolean or nullable boolean value, and a normal CheckBox to allow editing of that value. This column derives from DataGridBoundColumn and serves as a sample for building your own column types. We'll walk through the process of building your own column types in a future post.
- DataGridTemplateColumn - If the DataGrid was a movie, the DataGridTemplateColumn would be the star. It is the most versatile column but, like most prima donnas, takes the most work to get up and going. Once it is going though there is pretty much no limit to what it can do.
Using the Columns Collection
First we need to get our project back to where we ended last time. Go ahead and follow the steps 1 and 2 here.
If you followed steps 1 & 2 from the previous post, your DataGrid should look something like this:
<data:DataGrid x:Name="dg"></data:DataGrid>
(If yours looks different because you did step 3, don't worry since it won't affect this walk through. The one exception is that you need to change IsReadOnly back to False.)
Also, in the code behind file for Page.xaml you should be setting the source to a List<Data> where Data is the custom business object you created.
Now that we are all on the same page lets start playing with the Column collection.
Since auto generation is no longer needed, go ahead and set AutoGenerateColumns to False.
<data:DataGrid x:Name="dg" AutoGenerateColumns="False"></data:DataGrid>
Now add the Columns collection, and add a Text column in it bound to the "FirstName" property.
<data:DataGrid x:Name="dg" AutoGenerateColumns="False">
<data:DataGrid.Columns>
<data:DataGridTextColumn Header="First Name"
Binding="{Binding FirstName}" />
</data:DataGrid.Columns>
</data:DataGrid>
The code above is doing a few things:
- It creates the Columns collection using the <data:DataGrid.Columns> tag.
- Inside that collection it creates a new DataGridTextColumn
- The content that you see in the column header is set to "First Name" using the Header property
- The column is data bound to the FirstName property using the Binding property. Notice that since the DataContext of the DataGrid is its ItemsSource collection a Source does not need to be specified.
Side Note: Some of you might be wondering why we use Binding here instead of a string like DisplayMemberPath. The thinking behind this decision was that a binding gives you a lot more control of what ends up being shown. It allows the use of converters, and leaves the door open for the addition of validation, and formatters.
If you run this you should see something like this:
In addition to the Header and Binding properties, you can also customize the column through properties such as:
Column Properties:
- CanUserResize - Determines if this column can be resized. In regards to its relationship with the DataGrid property CanUserResizeColumns, false always wins.
- CanUserReorder - Determines if this column can be reordered with other columns. In regards to its relationship with the DataGrid property CanUserReorderColumns, false always wins.
- CanUserSort - Determines if this column can be sorted. In regards to its relationship with the DataGrid property CanUserSortColumns, false always wins.
- IsReadOnly - Determines if this column can be edited. In regards to its relationship with the DataGrid property IsReadOnly, true always wins.
- Width - Sets the width of the column. Setting this value overrides the DataGrid ColumnWidth property.
- MinWidth - Sets the minimum width for this column.
- Visibility - Hides or shows this column
- ElementStyle - The property used to style the display element for a column. (In the case of the TextBox Column the display element is the TextBlock)
- EditingElementStyle - The property used to style the editing element for a column. (In the case of the TextBox Column the display element is the TextBox)
TextBox Column Specific Properties:
- FontFamily - Sets the FontFamily property on both the TextBlock and TextBox.
- FontSize - Sets the FontSize property on both the TextBlock and TextBox.
- FontStyle - Sets the FontStyle property on both the TextBlock and TextBox.
- FontWeight - Sets the FontWeight property on both the TextBlock and TextBox.
- Foreground - Sets the Foreground property on both the TextBlock and TextBox.
CheckBox Column Specific Properties:
- IsThreeState - Sets the IsThreeState property on the CheckBoxes
Lets now finish off the columns that we used to have by adding two more DataGridTextBoxColumns and a DataGridCheckBoxColumn bound to "LastName", "Age", and "Available".
<data:DataGrid x:Name="dg" AutoGenerateColumns="False" >
<data:DataGrid.Columns>
<data:DataGridTextColumn Header="First Name"
Binding="{Binding FirstName}" />
<data:DataGridTextColumn Header="Last Name"
Binding="{Binding LastName}" />
<data:DataGridTextColumn Header="Age" Width="50"
Binding="{Binding Age}" />
<data:DataGridCheckBoxColumn Header="Available"
Binding="{Binding Available}" />
</data:DataGrid.Columns>
</data:DataGrid>
Now when you run the application you have the same output as the auto generation, except now you control the order, what the header says, and you can set properties. For instance in the code above the width of the Age column is set to 50 instead of the usual Auto.
Unleashing the Template Column
Now that we have recreated the auto generated DataGrid from before let's add some new functionality, specifically a column that can be bound to a DateTime. To do this we can either create a new column type, or just use a template column. Since we only plan on using this column once, let's use a template column.
First let's add something for this column to bind to. Going back to your Data class, add the following property:
C#
public DateTime Birthday{ get; set; }
VB
Private _birthday As DateTime
Property Birthday() As DateTime
Get
Return _birthday
End Get
Set(ByVal value As DateTime)
_birthday = value
End Set
End Property
Also we need to update the Page.xaml code behind to initialize the new property:
C#
public Page()
{
InitializeComponent();
List<Data> source = new List<Data>();
int itemsCount = 100;
for (int i = 0; i < itemsCount; i++)
{
source.Add(new Data()
{
FirstName = "First",
LastName = "Last",
Age = i,
Available = i % 2 == 0,
Birthday = DateTime.Today.AddYears(-i)
});
}
dg.ItemsSource = source;
}
VB
Public Sub New()
InitializeComponent()
Dim Source As List(Of Data) = New List(Of Data)
Dim ItemsCount As Integer = 100
For index As Integer = 1 To ItemsCount
Source.Add(New Data() With _
{ _
.FirstName = "First", _
.LastName = "Last", _
.Age = index, _
.Available = (index Mod 2 = 0), _
.Birthday = DateTime.Today.AddYears(-index) _
})
Next
dg.ItemsSource = Source
End Sub
Now that the data is there we can bind to it. Going back to the Page.xaml file add a DataGridTemplateColumn to the Columns collection right after the "Available" column.
xmlns:basics="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls"
<data:DataGrid x:Name="dg" AutoGenerateColumns="False" >
<data:DataGrid.Columns>
<data:DataGridTextColumn Header="First Name"
Binding="{Binding FirstName}" />
<data:DataGridTextColumn Header="Last Name"
Binding="{Binding LastName}" />
<data:DataGridTextColumn Header="Age" Width="50"
Binding="{Binding Age}" />
<data:DataGridCheckBoxColumn Header="Available"
Binding="{Binding Available}" />
<data:DataGridTemplateColumn Header="Birthday">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Birthday}" Margin="4"/>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<basics:DatePicker
SelectedDate="{Binding Birthday, Mode=TwoWay}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid.Columns>
</data:DataGrid>
Warning: Be sure to add the DatePicker from the ToolBox so the basics namespace and associated reference to System.Windows.Controls is added for you.
What's going on in the code above:
- A DataGridTemplateColumn is being added to the Columns collection with its header being set to "Birthday".
- The CellTemplate property, which takes a DataTemplate, is being used to define the UI that cells in the column will use to display the data.
- In this instance, the content of that DataTemplate is a TextBlock that has its Text property bound to the "Birthday" property on the Data Item. Once again notice that no binding source is necessary since the default DataContext of a Template column's contents is the data item that it will be representing.
- In addition to the CellTemplate the DataGridTemplateColumn's CellEditingTemplate property is also being set. This property is also a DataTemplate and defines the UI that cells in the column will display during edit mode.
- In this instance, the content of the CellEditingTemplate's DataTemplate is a DatePicker control that has its SelectedDate property also bound to the "Birthday" property.
- Notice that its binding has the Mode property set to TwoWay. This is crucial to make sure that any changes made to this property at runtime will be pushed back into the data source. Note that you do not need to set the Mode when you are using the DisplayMemberBinding property in column types other than DataGridTemplateColumn because the columns themselves take care of this.
When you run this you should see a new column in your DataGrid that uses a TextBlock to display a date, and then switches to a DatePicker during edit mode to provide a richer input experience.
(If your DataGrid still has all of the properties set from Step 3 of the last post you need to set IsReadOnly back to False if you want to be able to see the DatePicker in edit mode)
Using Value Converters
This column is pretty much exactly what we want, but there is a finishing touch missing. If you look at the text, it has an annoying 12:00:00 AM after each date. Since we only care about the date itself and not the time, let's reformat this string to the short date format that matches what is shown in the DatePicker.
Add a new class to your Silverlight project and name it "DateTimeConverter". Then add:
C#
using System.Windows.Data;
using System.Globalization;
public class DateTimeConverter: IValueConverter
{
public object Convert(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
DateTime date = (DateTime)value;
return date.ToShortDateString();
}
public object ConvertBack(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
string strValue = value.ToString();
DateTime resultDateTime;
if (DateTime.TryParse(strValue, out resultDateTime))
{
return resultDateTime;
}
return value;
}
}
VB
Imports System.Windows.Data
Public Class DateTimeConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, _
ByVal targetType As System.Type, _
ByVal parameter As Object, _
ByVal culture As System.Globalization.CultureInfo) _
As Object _
Implements System.Windows.Data.IValueConverter.Convert
Dim DateValue As DateTime = value
Return DateValue.ToShortDateString()
End Function
Public Function ConvertBack(ByVal value As Object, _
ByVal targetType As System.Type, _
ByVal parameter As Object, _
ByVal culture As System.Globalization.CultureInfo) _
As Object _
Implements System.Windows.Data.IValueConverter.ConvertBack
Dim StrValue As String = value.ToString()
Dim ResultDateTime As DateTime
If DateTime.TryParse(StrValue, ResultDateTime) Then
Return ResultDateTime
End If
Return value
End Function
End Class
If you look at the code, a converter is a class that implements IValueConverter and as part of that provides two methods: Convert and ConvertBack. These two methods are where you can perform your custom logic, in this case parsing a DateTime to and from a short date string format.
Now that you have your reusable converter class you can use it in data binding.
Back in your Page.xaml add a local xmlns. To do this add the following in the UserControl tag next to the other xmlns declarations:
xmlns:local="clr-namespace:_____________"
Where the _________ is the name of your Application. IntelliSense should provide the correct syntax as an option in the dropdown. Next add the converter to your UserControl as a static resource.
<UserControl.Resources>
<local:DateTimeConverter x:Key="DateConverter" />
</UserControl.Resources>
Finally use the converter in the TextBox's binding in the DataGridTemplateColumn.
<TextBlock
Text="{Binding Birthday, Converter={StaticResource DateConverter}}"
Margin="4"/>
Now when you run the application, the text will be formatted in the manner that the converter specified.

If you have ever worked on an application that displayed large amounts of data, one of the cornerstones of your application was probably a DataGrid control. We have provided four .NET DataGrids over the years, two for ASP.NET and two for Windows Forms, but until now Silverlight and WPF were left out of the party.
At MIX 2008 we shipped the first preview of the Silverlight DataGrid and a preview of it in WPF was also shown. Now that it is out there people want to know how to use it. If you are one of those people, then you have come to the right place. Here's a quick guide on how to get up and running with a Silverlight DataGrid.
Step 0: Create A Silverlight Project
Start a new Silverlight Application as outlined in my previous post. When given the option, choose the default "Add a new ASP.NET Web project" option.
Step 1: Add a DataGrid
If everything went smoothly in the previous step, your project should be loaded and opened to Page.xaml. Now just find the DataGrid on the Toolbox and drag it into the root layout Grid named "LayoutRoot".
This does a few things behind the scenes:
- It adds a reference in your Silverlight project to System.Windows.Controls.Data
- It adds an xmlns called "data" to the root UserControl that specifies that the DataGrid is in the System.Windows.Controls namespace and located in the System.Windows.Controls.Data assembly
- It adds an instance of the DataGrid as a child of "LayoutRoot"
<data:DataGrid></data:DataGrid>
If you are the type of the person who likes to see things working after each step, feel free to F5 (choose the option to allow debugging in the popup) and take in the awesome sight that is an empty DataGrid.
Not much here, so lets fill it with something.
Step 2: Set the ItemsSource and AutoGenerateColumns
The way to make a DataGrid interesting is by giving it some data. This is done through the DataGrid's ItemsSource property. This is same property that other controls in WPF and Silverlight, such as ListBox, use to specify where they will get their data. The one difference here is that you cannot place arbitrary content in it and have it create a collection for you.
Instead you need to provide it a collection of anything that implements IEnumerable such as a List or ObservableCollection.
The ItemsSource can be specified inline in XAML such as:
<data:DataGrid x:Name="dg">
<data:DataGrid.ItemsSource>
<!--Something that implements IEnumerable -->
</data:DataGrid.ItemsSource>
</data:DataGrid>
However, it is more commonly set in code behind, which is what we will do in this example.
Step 2 A: Name the DataGrid and build
Before we go to the code behind you will want to be sure to give the DataGrid a name such as "dg". Also be sure to build so that you can reference the DataGrid in code:
<my:DataGrid x:Name="dg" ></my:DataGrid>
Step 2 B: Create and Set the Items Source
Now that the DataGrid is ready to have its ItemsSource set, go to the Page's constructor located in the code behind file for Page.xaml (A handy shortcut to do this from within Page.xaml is F7) and add the following line below InitializeComponent:
C#
public Page()
{
InitializeComponent();
dg.ItemsSource = "H e l l o W o r l d !".Split();
}
VB
Public Sub New()
InitializeComponent()
dg.ItemsSource = "H e l l o W o r l d !".Split()
End Sub
(If you get the build error: "The name 'dg' does not exist in the current context" with the code above be sure to build a second time so that the name has a chance to propagate)
One of the easiest ways to generate an IEnumerable collection is String.Split. When the resulting array is set as the ItemsSource of the DataGrid a column will be automatically generated since AutoGenerateColumns is true. When you run the application, it will look like this:
This is a little better, but so far this could be done with a ListBox. Lets add some more complicated data so that we actually need to use a DataGrid.
Add a new class to your Silverlight project (not the Web project) and name it "Data".
Then add a few properties to bind to.
C#
If you are using C#, you can use the great 3.0 Automatic Properties feature.
public class Data
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public bool Available { get; set; }
}
VB
Public Class Data
Private _firstName As String
Private _lastName As String
Private _age As Integer
Private _available As Boolean
Property FirstName() As String
Get
Return _firstName
End Get
Set(ByVal value As String)
_firstName = value
End Set
End Property
Property LastName() As String
Get
Return _lastName
End Get
Set(ByVal value As String)
_lastName = value
End Set
End Property
Property Age() As Integer
Get
Return _age
End Get
Set(ByVal value As Integer)
_age = value
End Set
End Property
Property Available() As Boolean
Get
Return _available
End Get
Set(ByVal value As Boolean)
_available = value
End Set
End Property
End Class
Once the Data class is defined, it can now be used to provide data for the DataGrid. Go back to the code behind file for Page.xaml and replace the previous ItemsSource assignment with the following. A useful trick here is to use the C# and VB 3.0 Object Initializer feature to initialize the Data objects as we add them to the List.
C#
public Page()
{
InitializeComponent();
//dg.ItemsSource = "H e l l o W o r l d !".Split();
List<Data> source = new List<Data>();
int itemsCount = 100;
for (int i = 0; i < itemsCount; i++)
{
source.Add(new Data()
{
FirstName = "First",
LastName = "Last",
Age = i,
Available = (i % 2 == 0)
});
}
dg.ItemsSource = source;
}
VB
Public Sub New()
InitializeComponent()
'dg.ItemsSource = "H e l l o W o r l d !".Split()
Dim Source As List(Of Data) = New List(Of Data)
Dim ItemsCount As Integer = 100
For index As Integer = 1 To ItemsCount
Source.Add(New Data() With _
{ _
.FirstName = "First", _
.LastName = "Last", _
.Age = index, _ .Available = (index Mod 2 = 0) _
})
Next
dg.ItemsSource = Source
End Sub
When you run this you will notice that columns are created for you. This is because auto-generation takes over, using reflection to create a column for each property in Data, setting the column header to the name of the property, and choosing default column types based on the property type. For instance the Available column is a DataGridCheckBoxColumn. (If you did not want this behavior, but rather wanted to choose your own columns, you can do this by setting the DataGrid's AutoGenerateColumns property to false. For information on choosing your own columns see my post on Defining Columns for a Silverlight DataGrid)
Step 3: Simple Customization of the DataGrid
The easiest way to customize the DataGrid is through a variety of properties. The other two ways are through Styles and Templates which will be covered in future posts. Some of the most useful properties for customization are:
GridLinesVisibility & HeadersVisibility
These properties are enumerations that control what gridlines and headers are displayed.
RowBackground & AlternatingRowBackground
These properties are shortcuts to setting the background color for both rows and alternating rows.
ColumnWidth & RowHeight
These properties set the default column width and default row height.
IsReadOnly & CanUserResizeColumns
These properties control if the end user can edit the data in the grid and if the columns can be resized.
For instance if you set the following properties to the following values:
<data:DataGrid x:Name="dg" AutoGenerateColumns="True"
GridlinesVisibility="None" HeadersVisibility="All"
RowBackground="Cornsilk" AlternatingRowBackground="LemonChiffon"
ColumnWidth="85" RowHeight="30"
IsReadOnly="True" CanUserResizeColumns="False"
>
</data:DataGrid>
You would get this:
Step 4: Enjoy
Now that you have the basics, enjoy using the DataGrid. Next time I'll go into how to explicitly define and customize columns instead of using auto generation.
Also, you can read more about the features that the Silverlight 2 DataGrid has to offer...
Odds are if you are on this site you are probably interested in building Silverlight applications. Here’s the rundown of what you need to get up and running.
What You Will Need
You will need either Visual Studio 2008, or the Expression Blend 2.5 Preview.
(For now, you will need Visual Studio 2008 Standard or above. Unfortunately it looks like the Express versions cannot author Silverlight content just yet.)
What Else You Will Need
(All of these are at the very bottom of http://silverlight.net/GetStarted/)
If you encounter any problems, Bradley Bartz has some great troubleshooting tips here: http://weblogs.asp.net/bradleyb/archive/2008/03/06/installation-tips-for-sivliverlight-tools-beta-1-for-visual-studio-2008.aspx
Starting Your First Project
Once you have all of those pieces installed you should be ready to create Silverlight 2 applications. Just Open Visual Studio 2008 and choose File -> New Project. Choose the Silverlight node and choose Silverlight Application. Give it a better name than SilverlightApplication1 and hit OK.

Once you do that there is one last question to answer, do you want to add a web site to host your Silverlight control, or do you want to have this page "automagically" generated each time you run your application.

Pick the Web Site option if:
- You want to work with Web Services
- You want to perform cross domain calls
- You want to reference images that are not part of your application
Pick the Test Page option if:
- You just want to write a quick sample that only works with local data and resources
- You plan on adding this application to an existing Web Site
Now that you are up and running you can start playing around with Silverlight. If this is your first time with Silverlight and WPF, Scott Guthrie has a great introductory walk through, or if you are interested in doing things with the Silverlight DataGrid check out these posts.