WEBSITE

Silverlight Recipes : Controls - Displaying Row Details in a DataGrid

3/4/2013 6:21:41 PM

1. Problem

You need to display additional detail information about a bound row in a DataGrid on demand so that the details portion is displayed in place within the DataGrid.

2. Solution

Use the RowDetailsTemplate property of the DataGrid to associate a data template that can be used to display additional data on demand.

3. How It Works

The DataGrid.RowDetailsTemplate property accepts a data template  that can be used to display additional data in place, associated with a bound row. This feature comes handy in many scenarios—to provide master-detail data where multiple detail records need to be displayed for a top-level row or where additional information, not otherwise bound to top-level columns, needs to be displayed adjacent to a row.

The DataGrid.RowDetailsVisibilityMode property controls the visibility of the row details information at DataGrid scope. That is, setting it to Visible keeps it always visible for every bound row, whereas setting it to VisibleWhenSelected makes the details portion of a row visible when the row is selected and collapsed back when selection moves off to another row. To control row details' visibility in code, set this property to Collapsed, which hides row details for every row, and instead use the DataGridRow.DetailsVisibility property on the individual row.

The DataGrid also exposes two useful events: LoadingRowDetails and UnloadingRowDetails. LoadingRowDetails is raised when the DataGrid initially applies the RowDetailsTemplate to the row. This is especially useful if you want to load the data for the row details in a delayed fashion—placing the code to load the data in the handler for LoadingRowDetails ensures that the data is only loaded when the user first expands the row details and is never executed again, unless the row details are explicitly unloaded. UnloadingRowDetails is raised when the rows are unloaded, such as when a method like DataGrid.ClearRows() is invoked.

The DataGrid also raises another event called RowDetailsVisibilityChanged every time a row detail is either made visible or is collapsed.

NOTE

The DataGrid control is found in the System.Windows.Controls.Data assembly in the System.Windows.Controls namespace.

1. The Code

For this code sample, you bind a DataGrid to product data sourced from the AdventureWorks WCF service. Each row, in addition to the bound columns, also displays some row details data, including an image of the product, inventory information, a product description, and another DataGrid displaying the cost history records of the product demonstrating a master-detail arrangement. Figure 5-19 shows the DataGrid with the row details of a row expanded.

Figure 5-19. Bound DataGrid using a RowDetailstemplate

Listing 1 shows the XAML for the page.

Listing 1. XAML for the page hosting the DataGrid with row details
<UserControl x:Class="Recipe5_5.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"
  Width="900" Height="600" >
  <UserControl.Resources>
    <DataTemplate x:Key="dtProductRowDetails">
      <Grid Height="350" Width="646">
        <Grid.RowDefinitions>
          <RowDefinition Height="0.127*"/>
          <RowDefinition Height="0.391*"/>
          <RowDefinition Height="0.482*"/>
        </Grid.RowDefinitions>
        <Grid.Background>
          <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="#FF7D7A7A"/>
            <GradientStop Color="#FFFFFFFF" Offset="1"/>
          </LinearGradientBrush>
        </Grid.Background>
        <Grid.ColumnDefinitions>

					  

<ColumnDefinition Width="0.245*"/>
          <ColumnDefinition Width="0.755*"/>
        </Grid.ColumnDefinitions>
        <Border HorizontalAlignment="Stretch" Margin="5,5,5,5"
              VerticalAlignment="Stretch" Grid.RowSpan="2"
              BorderThickness="4,4,4,4">
          <Border.BorderBrush>
            <LinearGradientBrush
            EndPoint="1.02499997615814,0.448000013828278"
            StartPoint="−0.0130000002682209,0.448000013828278">
              <GradientStop Color="#FF000000"/>
              <GradientStop Color="#FF6C6C6C" Offset="1"/>
            </LinearGradientBrush>
          </Border.BorderBrush>
          <Image  MinHeight="50" MinWidth="50"
                Source="{Binding ProductPhoto.LargePhotoPNG}"
                Stretch="Fill"
                VerticalAlignment="Stretch"
                HorizontalAlignment="Stretch"/>
        </Border>
        <Grid HorizontalAlignment="Stretch" Margin="8,8,8,0"
            VerticalAlignment="Stretch"
            Grid.Column="1" Grid.RowSpan="1">
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.25*"/>
            <ColumnDefinition Width="0.3*"/>
            <ColumnDefinition Width="0.05*"/>
            <ColumnDefinition Width="0.4*"/>
          </Grid.ColumnDefinitions>
          <StackPanel HorizontalAlignment="Stretch" Grid.Column="0"
                      Orientation="Horizontal" Margin="1,0,1,0">
            <Ellipse Height="15" Width="15"
                     Fill="{Binding InventoryLevelBrush}" Margin="0,0,2,0" />
            <TextBlock Text="{Binding InventoryLevelMessage}" FontSize="12"
                    FontWeight="Bold"
                    VerticalAlignment="Center" Margin="2,0,0,0"/>
          </StackPanel>
          <TextBlock HorizontalAlignment="Stretch"
                   VerticalAlignment="Center"
                   Grid.ColumnSpan="1"
                   Text="{Binding ProductCategory.Name}"
                   TextAlignment="Right" TextWrapping="Wrap"
                   Grid.Column="1" FontSize="13"/>
          <TextBlock HorizontalAlignment="Stretch"
                   VerticalAlignment="Center"
                   Grid.Column="2" Text="/"

					  

TextWrapping="Wrap" TextAlignment="Center"
                   FontSize="13" />
          <TextBlock HorizontalAlignment="Stretch"
                   VerticalAlignment="Center"
                   Grid.Column="3" Grid.ColumnSpan="1"
                   Text="{Binding ProductSubCategory.Name}"
                   TextWrapping="Wrap" TextAlignment="Left"
                   FontSize="13"/>
        </Grid>
        <StackPanel Orientation="Vertical"
                  HorizontalAlignment="Stretch"
                  VerticalAlignment="Stretch"
                  Margin="8,8,8,8"
                  Grid.ColumnSpan="2"
                  Grid.Row="2" Grid.RowSpan="1" >
          <TextBlock Height="Auto" Width="Auto"
                   FontSize="12" FontWeight="Bold"
                   Text="Cost History" Margin="0,0,0,10"/>
          <data:DataGrid AutoGenerateColumns="False"
                       ItemsSource="{Binding ProductCostHistories}">
            <data:DataGrid.Columns>
              <data:DataGridTextColumn Binding="{Binding StartDate}"
                                     Header="Start"/>
              <data:DataGridTextColumn Binding="{Binding EndDate}"
                                     Header="End"/>
              <data:DataGridTextColumn
                Binding="{Binding StandardCost}"
                Header="Cost"/>
            </data:DataGrid.Columns>
          </data:DataGrid>
        </StackPanel>
        <Border HorizontalAlignment="Stretch"
              Margin="8,8,8,8"
              VerticalAlignment="Stretch"
              Grid.Column="1"
              Grid.Row="1"
              Grid.RowSpan="1"
              BorderBrush="#FF000000"
              BorderThickness="1,1,1,1">
          <TextBox Height="Auto" Width="Auto"
                 FontSize="12"
                 FontWeight="Bold"
                 Text="{Binding ProductDescription.Description,Mode=TwoWay}"
                 TextWrapping="Wrap"/>
        </Border>

					  

</Grid>
    </DataTemplate>
  </UserControl.Resources>

  <Grid x:Name="LayoutRoot" Background="White">
    <data:DataGrid x:Name="dgProducts" AutoGenerateColumns="False"
             RowDetailsTemplate="{StaticResource dtProductRowDetails}"
             RowDetailsVisibilityMode="Collapsed">
      <data:DataGrid.Columns>
        <data:DataGridTextColumn
          Binding="{Binding ProductID}" Header="ID" />
        <data:DataGridTextColumn
          Binding="{Binding Name}" Header="Name" />
        <data:DataGridTextColumn
          Binding="{Binding ProductNumber}" Header="Number"/>
        <data:DataGridTextColumn
          Binding="{Binding ListPrice}" Header="List Price"/>
        <data:DataGridTextColumn
          Binding="{Binding Style}" Header="Style"/>
        <data:DataGridTextColumn
          Binding="{Binding Color}" Header="Color"/>
        <data:DataGridTemplateColumn>
          <data:DataGridTemplateColumn.CellTemplate>
            <DataTemplate x:Key="dtShowDetailTemplate">
              <Button Content="..." x:Name="ShowDetails"
                      Click="ShowDetails_Click" />
            </DataTemplate>
          </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
      </data:DataGrid.Columns>
    </data:DataGrid>
  </Grid>
</UserControl>

					  

The data template used for the RowDetailsTemplate is named dtProductRowDetails and contains fields bound to several properties in the Product data class plus some nested classes. It displays an image of the product along with category and inventory information.

To use the data template in the DataGrid named dgProducts, set the DataGrid.RowDetailsVisibilityMode to Collapsed so that all rows have their detail information hidden to start with. To allow users to display row details on demand, an extra column of type DataGridTemplateColumn is added to the DataGrid with a specific CellTemplate containing a Button. (You will learn more about column templates in the next recipe.) The CellTemplate causes a Button to be displayed in the last column of each bound row, and you use the Button's click handler ShowDetails_Click() to allow the user to toggle the Visibility of the row detail information, as shown the codebehind for the page in Listing 2.

Listing 2. Codebehind for the MainPage hosting the DataGrid
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Recipe5_5.AdvWorks;

namespace Recipe5_5
{
  public partial class MainPage : UserControl
  {
    AdvWorksDataServiceClient client =
      new AdvWorksDataServiceClient();


    public MainPage()
    {
      InitializeComponent();
      //async completion callbacks for the web service calls to get data
      client.GetPhotosCompleted +=
        new EventHandler<GetPhotosCompletedEventArgs>(
          delegate(object s1, GetPhotosCompletedEventArgs e1)
          {
            (e1.UserState as Product).ProductPhoto = e1.Result;
          });
      client.GetInventoryCompleted +=
        new EventHandler<GetInventoryCompletedEventArgs>(
          delegate(object s2, GetInventoryCompletedEventArgs e2)
          {
            (e2.UserState as Product).ProductInventories = e2.Result;
            (e2.UserState as Product).InventoryLevelBrush = null;
            (e2.UserState as Product).InventoryLevelMessage = null;
          });
      client.GetCategoryCompleted +=
        new EventHandler<GetCategoryCompletedEventArgs>(
         delegate(object s3, GetCategoryCompletedEventArgs e3)
         {
           (e3.UserState as Product).ProductCategory = e3.Result;
         });
      client.GetSubcategoryCompleted +=
        new EventHandler<GetSubcategoryCompletedEventArgs>(
          delegate(object s4, GetSubcategoryCompletedEventArgs e4)

					  

{
            (e4.UserState as Product).ProductSubCategory = e4.Result;
          });
      client.GetDescriptionCompleted +=
        new EventHandler<GetDescriptionCompletedEventArgs>(
          delegate(object s5, GetDescriptionCompletedEventArgs e5)
          {
            (e5.UserState as Product).ProductDescription = e5.Result;
          });
      client.GetProductCostHistoryCompleted +=
        new EventHandler<GetProductCostHistoryCompletedEventArgs>(
          delegate(object s6, GetProductCostHistoryCompletedEventArgs e6)
          {
            (e6.UserState as Product).ProductCostHistories = e6.Result;
          });

      //LoadingRowDetails handler - here we make the calls to load
      //row details data on demand
      dgProducts.LoadingRowDetails +=
        new EventHandler<DataGridRowDetailsEventArgs>(
          delegate(object sender, DataGridRowDetailsEventArgs e)
          {
            Product prod = e.Row.DataContext as Product;
            if (prod.ProductInventories == null)
              client.GetInventoryAsync(prod, prod);
            if (prod.ProductCategory == null && prod.ProductSubcategoryID != null)
              client.GetCategoryAsync(prod, prod);
            if (prod.ProductSubCategory == null &&
              prod.ProductSubcategoryID != null)
              client.GetSubcategoryAsync(prod, prod);
            if (prod.ProductDescription == null)
              client.GetDescriptionAsync(prod, prod);
            if (prod.ProductPhoto == null)
              client.GetPhotosAsync(prod, prod);
            if (prod.ProductCostHistories == null)
              client.GetProductCostHistoryAsync(prod, prod);
          });
      GetData();
    }

    private void GetData()
    {
      //get the top level product data
      client.GetProductsCompleted +=
        new EventHandler<GetProductsCompletedEventArgs>(

					  
delegate(object sender, GetProductsCompletedEventArgs e)
          {
            dgProducts.ItemsSource = e.Result;
          });
      client.GetProductsAsync();
    }


    private void ShowDetails_Click(object sender, RoutedEventArgs e)
    {
      DataGridRow row = DataGridRow.GetRowContainingElement(sender as Button);
      row.DetailsVisibility =
        (row.DetailsVisibility == Visibility.Collapsed ?
        Visibility.Visible : Visibility.Collapsed);
    }
  }
}

namespace Recipe5_5.AdvWorks
{
  public partial class ProductPhoto
  {
    private BitmapImage _LargePhotoPNG;

    public BitmapImage LargePhotoPNG
    {
      get
      {
        BitmapImage bim = new BitmapImage();
        MemoryStream ms = new MemoryStream(this.LargePhoto.Bytes);
        bim.SetSource(ms);
        ms.Close();
        return bim;
      }
      set
      {
        RaisePropertyChanged("LargePhotoPNG");
      }
    }
  }


public partial class Product
  {
    private SolidColorBrush _InventoryLevelBrush;

					  

public SolidColorBrush InventoryLevelBrush
    {
      get
      {
        return (this.ProductInventories == null
          || this.ProductInventories.Count == 0) ?
          new SolidColorBrush(Colors.Gray) :
            (this.ProductInventories[0].Quantity > this.SafetyStockLevel ?
              new SolidColorBrush(Colors.Green) :
                (this.ProductInventories[0].Quantity > this.ReorderPoint ?
                   new SolidColorBrush(Colors.Yellow) :
                    new SolidColorBrush(Colors.Red)));
      }
      set
      {
        //no actual value set here - just property change raised
        RaisePropertyChanged("InventoryLevelBrush");
      }

    }
    private string _InventoryLevelMessage;
    public string InventoryLevelMessage
    {
      get
      {
        return (this.ProductInventories == null
          || this.ProductInventories.Count == 0) ?
          "Stock Level Unknown" :
            (this.ProductInventories[0].Quantity > this.SafetyStockLevel ?
            "In Stock" :
              (this.ProductInventories[0].Quantity > this.ReorderPoint ?
                "Low Stock" : "Reorder Now"));
      }
      set
      {
        //no actual value set here - just property change raised
        RaisePropertyChanged("InventoryLevelMessage");
      }
    }
    private ProductSubcategory _productSubCategory;
    public ProductSubcategory ProductSubCategory
    {
      get { return _productSubCategory; }
      set
      {

					  

_productSubCategory = value;
        RaisePropertyChanged("ProductSubCategory");
      }
    }
    private ProductCategory _productCategory;
    public ProductCategory ProductCategory
    {
      get { return _productCategory; }
      set { _productCategory = value; RaisePropertyChanged("ProductCategory"); }
    }
    private ProductDescription _productDescription;
    public ProductDescription ProductDescription
    {
      get { return _productDescription; }
      set
      {
        _productDescription = value;
        RaisePropertyChanged("ProductDescription");
      }
    }
    private ProductReview _productReview;
    public ProductReview ProductReview
    {
      get { return _productReview; }
      set { _productReview = value; RaisePropertyChanged("ProductReview"); }
    }
    private ProductPhoto _productPhoto;
    public ProductPhoto ProductPhoto
    {
      get { return _productPhoto; }
      set { _productPhoto = value; RaisePropertyChanged("ProductPhoto"); }
    }
  }
}

					  

Once again, the data is acquired by calling the AdventureWorks WCF service. The GetData() method loads the initial product data into the rows to which the DataGrid is bound. You set the DataGrid's ItemsSource in the completion handler for the GetProductsAsync() web service call. When the user toggles the visibility of a row's details for the first time, the LoadingRowDetails event is raised, and the row detail data is fetched from the web service in that handler, defined using an anonymous delegate.

The row detail data, once fetched, is bound to various parts of the UI by setting appropriate properties in the already bound Product instance, which, in turn, uses property change notification to update the UI. Just as in the previous recipes, you extend the Product partial class, as generated by the WCF service proxy, to include the additional property definitions.

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