How To: Display Hierarchical Data with Row Details (RadGridView for Silverlight)

Tuesday, August 04, 2009 by Rossen Hristov | Comments 13

The main goal of the Row Details feature is to let you present additional information about a row. This makes row details the perfect candidate for presenting hierarchical data.

My sample uses two kinds of business objects – a football club and its players. As you might expect each club has some players. The master grid will show our clubs and when the user wants to see some additional information about a club he will be presented with the row details. Our first goal is to define what will these details look like. We would like to see some general information about the club and its current squad (or part of it for the sake of the example).

To do that we will create a UserControl containing an Image and another RadGridView. As you might have guessed, this last grid will be showing the club’s squad. The Image will show the logo of the club. Here is what the control looks like:

   1: <UserControl x:Class="ToggleRowDetails.ClubDetailsControl"
   2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4: xmlns:controls="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls"
   5: xmlns:telerik="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.GridView">
   6: <Grid x:Name="LayoutRoot">
   7: <Grid.RowDefinitions>
   8: <RowDefinition Height="Auto"/>
   9: <RowDefinition Height="Auto"/>
  10: </Grid.RowDefinitions>
  11: <StackPanel HorizontalAlignment="Left">
  12: <Image Name="logoImage" Source="{Binding LogoPath}" Stretch="None" Margin="8"/>
  13: <TextBlock Text="Players" FontSize="14" FontWeight="Light" Margin="8,0" />
  14: </StackPanel>
  15: <telerik:RadGridView Name="playersGrid" Grid.Row="1" Margin="8,2,8,8" Width="652" controls:StyleManager.Theme="Office_Blue"
  16: HorizontalAlignment="Left"
  17: ColumnsWidthMode="Fill"
  18: ItemsSource="{Binding Players}"
  19: AutoGenerateColumns="False"
  20: ShowGroupPanel="False"
  21: IsReadOnly="True"
  22: Background="White">
  23: <telerik:RadGridView.Columns>
  24: <telerik:GridViewDataColumn Header="Name" DataMemberBinding="{Binding Name}"/>
  25: <telerik:GridViewDataColumn Header="Number" DataMemberBinding="{Binding Number}"/>
  26: <telerik:GridViewDataColumn Header="Position" DataMemberBinding="{Binding Position}"/>
  27: <telerik:GridViewDataColumn Header="Country" DataMemberBinding="{Binding Country}"/>
  28: </telerik:RadGridView.Columns>
  29: </telerik:RadGridView>
  30: </Grid>
  31: </UserControl>

Next we need to tell the grid to use this control to display its row details. Simply set its RowDetailsTemplate like this:

   1: ...
   2: <Grid.Resources>
   3: <DataTemplate x:Key="ClubDetailsTemplate">
   4: <local:ClubDetailsControl/>
   5: </DataTemplate>
   6: </Grid.Resources>
   7: <telerik:RadGridView RowDetailsTemplate="{StaticResource ClubDetailsTemplate}"
   8:                      ...
   9: />
  10: ...
  11:  

Now we are almost ready. Typically, you would want the row details to show up when a user selects a row. This is exactly what the RowDetailsVisibilityMode property of the grid takes care of. The GridViewRowDetailsVisibilityMode enumeration has three values – Collapsed, Visible and VisibleWhenSelected which do exactly what their names suggest.

We, however, would like to go a step further. If you are not content with the grid governing the row details behavior of all rows you can redefine this for each and every row. Each GridViewRow has a property called DetailsVisibility which overrides the behavior imposed by the grid. The mechanism is simple – if this property is null then the row obeys its parent grid about showing and hiding row details. Otherwise it is what it is. We will use this to create a custom column with a toggle button that will show / hide the row details.

To do that we will inherit from GridViewColumn and override the CreateCellElement method. There we will return our “special” toggle button that knows about its parent GridViewRow and controls the DetailsVisibility:

   1: public class DetailsToggleColumn : GridViewColumn
   2: {
   3: public override FrameworkElement CreateCellElement(GridViewCell cell, object dataItem)
   4:     {
   5:         var parentRow = cell.ParentRow as GridViewRow;
   6: if (parentRow != null)
   7:         {
   8: return new DetailsToggleButton(parentRow);
   9:         }
  10:  
  11: return null;
  12:     }
  13:  
  14:     ...
  15: }

The button is very simple as well. It has its IsChecked property bound to the DetailsVisibility property of its parent row with the help of the almighty BooleanToVisibilityConverter:

   1: public partial class DetailsToggleButton : UserControl
   2: {
   3: private readonly GridViewRow parentRow;
   4: 
   5: public DetailsToggleButton(GridViewRow parentRow)
   6:     {
   7: this.InitializeComponent();
   8: 
   9: this.parentRow = parentRow;
  10: 
  11: this.Init();
  12:     }
  13:  
  14: private void Init()
  15:     {
  16:         var b1 = new Binding("IsChecked")
  17:         {
  18:             Source = this.toggleButton,
  19:             Mode = BindingMode.TwoWay,
  20:             Converter = new BooleanToVisibilityConverter()
  21:         };
  22:  
  23: this.parentRow.SetBinding(GridViewRow.DetailsVisibilityProperty, b1);
  24:     }
  25: }

Well, I lied. The designer mate sitting next to me saw the toggle button and styled it in Blend to give it this finished look, but you can examine this in the source code bundle.

And that’s all about it. Here is our grid with a “toggle hierarchy row details”:

ToggleHierarchyRowDetails

Here is the Source Code

Enjoy!

13 Comments

  • Rajan 05 Aug 2009
    Hi Hristov,
                    Thanks for the post. I have implemented heirarchy grid like that, but how can I make it On Demand ?  It appears that the child details are loaded at once . In My case there are around 8k records in parent grid and total 25k childrens. I need to load it on Demand as currently it freezes the screen even if I use the DataLoadMode="Asynchronous" .
             Is there any way that then I click the details toggle button only then I fetches the data and then loads it ?
                
  • Krlos 06 Aug 2009
    Take a look at this post
     http://www.telerik.com/community/forums/silverlight/gridview/load-on-demand-gridview.aspx

    On ClubDetailsControl -> palyersGrid -> add Loaded Event
    The first time you expand the row, it will be called. Here is where you can fecht the apropiate data and add it to you ViewModel Collection to reflect changes.
  • Rossen Hristov 07 Aug 2009
    I guess that you should leave the child grid unbound, attach to the LoadingRowDetails event, and pull the data in the event handler. The LoadingRowDetails event is fired once for each row right after the DataTemplate you specified is read and the respective FrameworkElement is loaded. I hope this helps.

  • Krlos 20 Aug 2009
    If you really need to load on demand, try fetching data on gridview RowDetailsVisibilityChanged event handler. You need to implement some logic to fetch the data only the first time you open it. What i did, was to attach a boolean property to every row with false value. When fetching the data the first time I change it to true, so I can compare the next time the handler get fired.
  • Rossen Hristov 21 Aug 2009
    I have some good news.

    In the next "Latest Internal Build" that we will release (hopefully today) RowDetails will behave like this by default. The DataTemplate will be loaded only when Visibility.Visible is requested for the first time. This means that the LoadingRowDetails event will be fired late, only when you want to see the row details for the first time. From then on only the Visibility will be changed if you select-deselect the row, so you will be receiving the RowDetailsVisibilityChanged event. Therefore, RowDetails will be "lazy" by default and you can do the data retrieving in the LoadingRowDetails event handler. Here is a little time scale:

    [Grid is created] -> [Idle] -> [RowDetails Visibility.Visible is requested in some way, for example selecting a row] -> [LoadingRowDetails event: do your data job here] -> [RowDetailsVisibilityChanged event: Visible] -> and so on...

    Another improvement is that the FrameworkElement that you get from the EventArgs of the LoadingRowDetails event will have the correct DataContext already set (the data item of the parent GridViewRow) and you can use it to fetch master-details data and place it inside the FrameworkElement.
  • Krlos 14 Oct 2009
    Thanks Rossen, that was incredibles news.
  • Paresh 23 Oct 2009
    Hi,

    using the above example a toggle button is working great.
    on clicking + converts to - and details are shown.

    can you please point how to make it work for collapsing as well on cancel/submit of the details presenter. As of now - sign changes to + only on clicking at it.

    Thanks,
    Paresh
  • Rossen Hristov 02 Nov 2009
    Hello Paresh,

    In one of the latest internal releases we have introduced a built-in GridViewToggleRowDetails column. You can simply add it as the first column of your grid and it will hook up to the row details automatically.

    From then on, if you programmatically change the DetailsVisibility of the parent GridViewRow, the details will collapse and the toggle button will update its sign automatically.

    There is a sample project attached in this forum post.
  • Rossen Hristov 02 Nov 2009
    Hello Paresh,

    In one of the latest internal releases we have introduced a built-in GridViewToggleRowDetails column. You can simply add it as the first column of your grid and it will hook up to the row details automatically.

    From then on, if you programmatically change the DetailsVisibility of the parent GridViewRow, the details will collapse and the toggle button will update its sign automatically.

    There is a sample project attached in this forum post.
  • Ganesh Jagdale 05 Jan 2010
    Hi Rossen's,
          I want Grid that shows mulitlevel hirarchy. means
        
    FirstName || LastName || Email; ---> Grid Columns
    + Manager 
         ----> M1
                         ----> M11
    +Manager2
       ------>M2
                   ----->M22
      ------->M21
                   ----->M212
         
      Ur Post is grear but there is only one level .. I want nested level like TreeView in RadGridView

    My details problem is here

      http://www.telerik.com/community/forums/silverlight/gridview/newer-version-issue.aspx


    pls ,, Help  me Out

    Thanks
    Ganesh
  • Rossen Hristov 06 Jan 2010
    Hello Ganesh,

    You can achieve any level of nesting. Simply place a RadGridView in the RowDetailsTemplate of the top-most RadGridView. Define RowDetailsTemplate for the nested RadGridView an so on. You can go on like that to infinity. Let me know if I could not explain this clearly.
  • Darryl 02 Jun 2010
    Hi all,

    Say, in your example, the Club class has a child class called Team and the Team class has a list of Players. How would you bind the players grid to the Club.Team.Players in that instance?

    Thanks,
    Darryl
  • Darryl 03 Jun 2010
    Found it! All I had to do was set ItemsSource="{Binding Team.Players}" for the child grid.

Add comment

  1. Formatting options
       
     
     
     
     
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)