WEBSITE

Silverlight Recipes : Controls - Applying Custom Templates to a DataGrid Cell

3/4/2013 6:23:30 PM

1. Problem

You need a customized way of viewing and editing data that is not supported out of the box by any of the typed DataGridColumns like DataGridTextColumn or DataGridCheckBoxColumn. For example, say you want to view a color value rendered as a color stripe instead of the color name string literal.

2. Solution

Use the CellTemplate and the CellEditingTemplate properties of the DataGridTemplateColumn to apply custom viewing and editing templates.

3. How It Works

The various DataGridColumn types like DataGridTextColumn and DataGridCheckBoxColumn are designed to support binding to specific CLR data types, such as String and Boolean, or to types that can be automatically converted to these types. The way that the data is viewed and edited in cells of these specific column types is predetermined by the framework. However, the need for custom UIs for viewing and editing data was well anticipated; the DataGridTemplateColumn is supplied for exactly that purpose.

The DataGridTemplateColumn exposes two properties, CellTemplate and CellEditingTemplate, both of which accept data templates. When the column is data bound, the DataGrid binds the cell data item to the data template specified in CellTemplate to display the data in view mode. When the cell enters edit mode, the DataGrid switches to the CellEditingTemplate.

4. The Code

In this sample, you use a DataGrid bound to product data fetched from the AdventureWorks WCF service. The Product class exposes a Color property, which is defined as a String on the class. You bind the Color property to one of the DataGrid columns and thereby create a more intuitive interface where the user can actually view the color itself for both viewing and editing the Color value, as compared to the default string editing experience exposed by the DataGridTextColumn. Listing 1 shows the XAML.

Listing 1. XAML for the MainPage Used to Demonstrate Custom DataGrid Column Templates
<UserControl x:Class="Recipe5_6.MainPage"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:data=
 "clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
 xmlns:local="clr-namespace:Recipe5_6"
 Width="800" Height="400"

>

					  

<UserControl.Resources>
    <local:ColorNameToBrushConverter x:Key="REF_ColorNameToBrushConverter"/>
    <DataTemplate x:Key="dtColorViewTemplate">
      <Border CornerRadius="5,5,5,5" BorderBrush="Black"
              BorderThickness="1,1,1,1" VerticalAlignment="Stretch"
              HorizontalAlignment="Stretch" Margin="1,1,1,1"
              Background="{Binding Color,
                Converter={StaticResource REF_ColorNameToBrushConverter}}"/>
    </DataTemplate>
    <DataTemplate x:Key="dtColorEditingTemplate">
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto" />
          <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ListBox Grid.Row="0" VerticalAlignment="Stretch"
                 HorizontalAlignment="Stretch"
                 ItemsSource="{Binding ColorList}"
                 SelectedItem="{Binding Color, Mode=TwoWay}"
                 Height="200">
          <ListBox.ItemTemplate>
            <DataTemplate>
              <Border CornerRadius="5,5,5,5" BorderBrush="Black"
                      BorderThickness="1,1,1,1" Height="25" Width="70"
                      Margin="2,5,2,5"
                      Background=
                      "{Binding Converter=
                          {StaticResource REF_ColorNameToBrushConverter}}"/>
            </DataTemplate>
          </ListBox.ItemTemplate>
        </ListBox>
      </Grid>
    </DataTemplate>
  </UserControl.Resources>
  <Grid x:Name="LayoutRoot" Background="White">
    <data:DataGrid x:Name="dgProducts" AutoGenerateColumns="False">
      <data:DataGrid.Columns>
        <data:DataGridTextColumn Binding="{Binding ProductID}"
                                 Header="ID" />
        <data:DataGridTextColumn Binding="{Binding Name}"
                                 Header="Name" />
        <data:DataGridTemplateColumn
          CellTemplate="{StaticResource dtColorViewTemplate}"
          CellEditingTemplate="{StaticResource dtColorEditingTemplate}"
          Header="Color" Width="100"/>

					  

</data:DataGrid.Columns>
    </data:DataGrid>
  </Grid>
</UserControl>

In the DataGrid declaration named dgProducts, you use a DataGridTemplateColumn to bind to Product.Color. To get the custom UI for viewing and editing the Color property, you define two data templates, dtColorTemplate and dtColorEditingTemplate, and use them to set the CellTemplate and the CellEditingTemplate properties.

In view mode, where the bound DataGridTemplateColumn uses the CellTemplate to bind the data, you bind the Color value to the Background property of a Border, as shown in the dtColorViewTemplate template. In edit mode, where CellEditingTemplate is used, dtColorEditingTemplate uses a ListBox to display the list of available colors. The ListBox.SelectedItem is bound to Product.Color to represent the currently selected color. The binding mode is set to TwoWay so that any changes made by the user updates the Product instance and is reflected in the DataGrid when the cell moves out of edit mode.

Listing 2 shows the codebehind for the page.

Listing 2. Codebehind for the MainPage Demonstrating Custom DataGrid Column Templates
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using Recipe5_6.AdvWorks;

namespace Recipe5_6
{
  public partial class MainPage : UserControl
  {
    AdvWorksDataServiceClient client =
      new AdvWorksDataServiceClient();
    bool EditingColor = false;
    public MainPage()
    {
      InitializeComponent();
      GetData();
    }

    private void GetData()
    {
      client.GetProductsCompleted +=
        new EventHandler<GetProductsCompletedEventArgs>(
          delegate(object sender, GetProductsCompletedEventArgs e)

					  

{
            dgProducts.ItemsSource = e.Result;
          });

      client.GetProductsAsync();
    }
  }


  public class ColorNameToBrushConverter : IValueConverter
  {
    //convert from a string Color name to a SolidColorBrush
    public object Convert(object value, Type targetType,
      object parameter, System.Globalization.CultureInfo culture)
    {
      //substitute a null with Transparent
      if (value == null)
        value = "Transparent";
      //make sure the right types are being converted
      if (targetType != typeof(Brush) || value.GetType() != typeof(string))
        throw new NotSupportedException(
          string.Format("{0} to {1} is not supported by {2}",
          value.GetType().Name,
          targetType.Name,
          this.GetType().Name));

      SolidColorBrush scb = null;
      try
      {
        //get all the static Color properties defined in
        //System.Windows.Media.Colors
        List<PropertyInfo> ColorProps = typeof(Colors).
          GetProperties(BindingFlags.Public | BindingFlags.Static).ToList();
        //use LINQ to find the property whose name equates
        //to the string literal we are trying to convert
        List<PropertyInfo> piTarget = (from pi in ColorProps
                                       where pi.Name == (string)value
                                       select pi).ToList();
        //create a SolidColorBrush using the found Color property.
        //If none was found i.e. the string literal being converted
        //did not match any of the defined Color properties in Colors
        //use Transparent
        scb = new SolidColorBrush(piTarget.Count == 0 ?
          Colors.Transparent : (Color)(piTarget[0].GetValue(null, null)));
      }
      catch

					  

{
        //on exception, use Transparent
        scb = new SolidColorBrush(Colors.Transparent);
      }
      return scb;


    }
    //convert from a SolidColorBrush to a string Color name
    public object ConvertBack(object value, Type targetType,
      object parameter, System.Globalization.CultureInfo culture)
    {
      //make sure the right types are being converted
      if (targetType != typeof(string) || value.GetType() != typeof(Brush))
        throw new NotSupportedException(
          string.Format("{0} to {1} is not supported by {2}",
          value.GetType().Name,
          targetType.Name,
          this.GetType().Name));

      string ColorName = null;
      try
      {
        //get all the static Color properties defined
        //in System.Windows.Media.Colors
        List<PropertyInfo> ColorProps = typeof(Colors).
          GetProperties(BindingFlags.Public | BindingFlags.Static).ToList();
        //use LINQ to find the property whose value equates to the
        //Color on the Brush we are trying to convert
        List<PropertyInfo> piTarget = (from pi in ColorProps
                                       where (Color)pi.GetValue(null, null)
                                       == ((SolidColorBrush)value).Color
                                       select pi).ToList();
        //If a match is found get the Property name, if not use "Transparent"
        ColorName = (piTarget.Count == 0 ? "Transparent" : piTarget[0].Name);
      }
      catch
      {
        //on exception use Transparent
        ColorName = "Transparent";
      }
      return ColorName;
    }
  }
}

					  

namespace Recipe5_6.AdvWorks
{
  public partial class Product
  {
    private ObservableCollection<string> _ColorList;
    //color literals defined in System.Windows.Media.Colors
    public ObservableCollection<string> ColorList
    {
      get
      {
        return new ObservableCollection<string> {
        "Black",
        "Blue",
        "Brown",
        "Cyan",
        "DarkGray",
        "Gray",
        "Green",
        "LightGray",
        "Magenta",
        "Orange",
        "Purple",
        "Red",
        "Transparent",
        "White",
        "Yellow" };
      }
    }
  }
}

					  

The ListBox.ItemsSource is bound to the Product.ColorList property, defined in a partial extension of the Product proxy data type, which returns a collection of string literals representing names of the Color properties, as defined in the System.Windows.Media.Colors type. To display each item, an ItemTemplate similar to that in the view mode is used, where a Border is used to display the color choice by binding the Color value to Border.Background.

Figure 1 compares the DataGrid Color column in view mode and edit mode.

Figure 1. The Color column in view mode (left) and edit mode (right)

Also note in Listing 2 the use of a value converter of type ColorNameToBrushConverter. Since the Border.Background property is of type SolidColorBrush and Product.Color is a string literal, you need to facilitate an appropriate value conversion. Listing 5-11 shows the value converter code as well in the ColorNameToBrushConverter class. 

In both the conversion functions in the value converter implementation, you use reflection to enumerate the list of static properties of type Color defined on the type System.Windows.Media.Colors, each of which are named for the Color they represent. In Convert(), while trying to convert a Color name string to a SolidColorBrush, you find the matching Color property in the enumerated list of properties and use that to create and return the brush. In ConvertBack(), while trying to convert a SolidColorBrush to a color name string, you find the property with a Color value matching the SolidColorBrush.Color and use the property name as the color name string. If no matches are found or if exceptions occur, it falls back to Colors.Transparent as the default value.

Other  
 
Top 10
Review : Sigma 24mm f/1.4 DG HSM Art
Review : Canon EF11-24mm f/4L USM
Review : Creative Sound Blaster Roar 2
Review : Philips Fidelio M2L
Review : Alienware 17 - Dell's Alienware laptops
Review Smartwatch : Wellograph
Review : Xiaomi Redmi 2
Extending LINQ to Objects : Writing a Single Element Operator (part 2) - Building the RandomElement Operator
Extending LINQ to Objects : Writing a Single Element Operator (part 1) - Building Our Own Last Operator
3 Tips for Maintaining Your Cell Phone Battery (part 2) - Discharge Smart, Use Smart
REVIEW
- First look: Apple Watch

- 3 Tips for Maintaining Your Cell Phone Battery (part 1)

- 3 Tips for Maintaining Your Cell Phone Battery (part 2)
VIDEO TUTORIAL
- How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 1)

- How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 2)

- How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010 (Part 3)
Popular Tags
Video Tutorail Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Exchange Server Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe Photoshop CorelDRAW X5 CorelDraw 10 windows Phone 7 windows Phone 8 Iphone