Adding additional power to RadGridView for Silverlight with attached behaviors

by XAML Team | Comments 29

PART III [example: Column Groups  A.K.A. Merged Column Headers]

 

Recently we have had a lots of requests for a feature referred as column groups / merged column headers / common headers for two or more columns - something like :

 common_header

Here we have three ordinary columns ( L, W, H)  with regular column headers . There is also an additional common header – ‘Dimensions’ which spans over the three regular headers.

Unfortunately RadGridView for Silverlight and WPF does not support this internally (yet).

 

The good news is that RadGridView is a flexible and easily extendable control so we can easily attach a new column grouping behavior. You can see it in action here:

(live & clickable  example

We set up the common headers codeless  with a few lines of XAML:

   1: <telerik:RadGridView x:Name="RadGridView1" >
   2: <i:Interaction.Behaviors>
   3: <local:ColumnGroupsBehavior>
   4: <local:ColumnGroupsBehavior.CommonHeaders>
   5: <local:CommonHeader StartColumnIndex="0" Caption="Vehicle Info" ColumnSpan="2" />
   6: <local:CommonHeader StartColumnIndex="2" Caption="Dimensions (mm)" ColumnSpan="3" />
   7: <local:CommonHeader StartColumnIndex="5" Caption="Total Price" ColumnSpan="1" />
   8: </local:ColumnGroupsBehavior.CommonHeaders>
   9: </local:ColumnGroupsBehavior>
  10: </i:Interaction.Behaviors>
  11: </telerik:RadGridView>

As you can see, when adding a common header we need to set three properties:

StartColumnIndex – defines the first column of the column group

ColumnSpan – tells the header how many columns to unite

Caption – defines the text content of the common header.

 

*We are not limited to text content here. Since the common header is an ordinary user control with its own XAML you can customize it to add any UI elements , images etc.

* This approach has some limitations e.g. column freezing, column reordering  are explicitly disabled by the behavior. We are working on providing this as an internal feature to RadGridView for our  future versions when these limitations will be solved.

 

For your copy/paste needs you can download a 

To have this working in your own project you will have to grab the following :

 

ColumnGroupsBehavior.cs  - the attached behavior itself.

 

CommonHeader.xaml/.cs  - the common header user control. You can modify the XAML within to provide more complex content.

 

SecondaryHeader.xaml/.cs –this is the placeholder for the common headers ( the secondary header row rendered above the ordinary RadGridView header row) . It provides the scrolling & resizing  of the common headers.

 

Additionally you will need a reference to the System.Windows.Interactivity.dll

29 Comments

BHASKAR
HI Pavel,

Nice Post.I want to Change the color of the group headers. For example  i want to give Red Color for TotalPrice Header.How can i do this.We got a sample from u before in that we are placing group header names in TextBlock,so we are not able to change the colors of the headings.Please suggest ..

Thanks,
BHASKAR.
cherrythompson
XAML is used extensively in .NET Framework 3.0 technologies, particularly Windows Presentation Foundation (WPF), Silverlight, and Windows Workflow Foundation (WF). In WPF, XAML is used as a user interface markup language  to define UI elements, data binding, eventing, and other features. In WF, workflows  can be defined using XAML.Extensible Application Markup Language
cherrythompson
XAML is used extensively in .NET Framework 3.0 technologies, particularly Windows Presentation Foundation (WPF), Silverlight, and Windows Workflow Foundation (WF). In WPF, XAML is used as a user interface markup language  to define UI elements, data binding, eventing, and other features. In WF, workflows  can be defined using XAML.Extensible Application Markup Language
Dave
Does this work for wpf as well?
Pavel
Yes, I believe the same approach may be used in WPF as well
Tobias
Hi Pavel,
Thanks for making this possible, but how would one go about adding a bit of dynamic to this? The datagrid in my case is far from static and the columns changes as the user interacts with the application. Is there any way to get a reference to and access the CommonHeaders runtime to change the layout or would I have to set up different grid's for each scenario?
VeeraMalleswaraRao.Gali
Hi Pavel,
What you are giving is ok, but i need to add text like  row number,etc... in RowHeader of RadGridView.
Please give me solution.
Marco
Hi Pavel,
Thanks for this post. I'm using this feature in a RadTreeListView with dynamic and expandable columns and I found something interesting. Every time that a node is expanded it will create a new SecondaryHeader. I added a simple condition before adding SecondaryHeader to the grid "HierrarchyBackground".

private void PlaceSecondaryHeader() 
        { 
            SecondaryHeader secondaryHeader = new SecondaryHeader(); 
            secondaryHeader.ParentTreeListView = this.AssociatedObject; 
 
            var grid = this.AssociatedObject.ChildrenOfType<Grid>().Where(c => c.Name == "HierrarchyBackground").FirstOrDefault(); 
            if (grid.ChildrenOfType<SecondaryHeader>().Count > 0) 
                return
 
            grid.Children.Add(secondaryHeader); 
            secondaryHeader.SetValue(Grid.RowProperty, 1); 
            secondaryHeader.CommonHeaders = this.CommonHeaders; 
        } 

I know it was made for RadGridView but now, since RadTreeListView inherits from RadGridView, I thought someone would have the same problem I did.

Thanks again Pavel and sorry about my terrible English.
Marco.
Pavel
Hi , Marco
Thanks for sharing this . I am glad you have found a solution.
In fact I have never tested this one with the TreeListView. In case you need any additional assistance , please post in our forum or open a support ticket as these are more frequently monitored and  a timely response is guaranteed.
vinod
Hi,

Thanks for this article.

Is Freezing columns  when secondary Header is added bug resolved by Telerik. or do u have any solution to overcome it.

Thanks,
Vin Sa.
Vinod Sa

Hi,

I could achieve Freezing functionality using your code/approach. just by twiking some code. Please provide your comments.
I am pasting code here
:

Page.XAML:
======================================================================

 

<

 

UserControl x:Class="ColumnGroups.Page"

 

 

 

 

 

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

 

 

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

 

 

Width="400" Height="300"

 

 

 

 

xmlns

 

:telerik="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.GridView"

 

 

 

 

 

>

 

 

 

 

 

 

<Grid x:Name="LayoutRoot" Background="White">

 

 

 

 

 

 

<telerik:RadGridView x:Name="RadGridView1" />

 

 

 

 

 

 

 

</Grid>

 

</

 

UserControl>

 

 

 

===============================================================
Page.Xaml.cs
=============================================================

 

using

 

System;

 

 

 

using

 

System.Collections.Generic;

 

 

 

using

 

System.Linq;

 

 

 

using

 

System.Net;

 

 

 

using

 

System.Windows;

 

 

 

using

 

System.Windows.Controls;

 

 

 

using

 

System.Windows.Documents;

 

 

 

using

 

System.Windows.Input;

 

 

 

using

 

System.Windows.Media;

 

 

 

using

 

System.Windows.Media.Animation;

 

 

 

using

 

System.Windows.Shapes;

 

 

 

using

 

Telerik.Windows.Controls.GridView;

 

 

 

using

 

Telerik.Windows.Controls;

 

 

 

namespace

 

ColumnGroups

 

{

 

public partial class Page : UserControl

 

 

 

 

{

 

public Page()

 

{

InitializeComponent();

 

this.RadGridView1.ItemsSource = Person.GetSampleListOfPersons();

 

 

this.RadGridView1.RowLoaded += new EventHandler<Telerik.Windows.Controls.GridView.RowLoadedEventArgs>(RadGridView1_RowLoaded);

 

 

//this.RadGridView1.FrozenColumnCount = 2;

 

 

 

 

 

}

 

 

 

private void InitializeUI()

 

{

 

this.RadGridView1.Columns.Clear();

 

 

//Create Columns

 

 

 

 

 

GridViewColumn gvc = new GridViewColumn();

 

gvc.UniqueName =

"Age";

 

gvc.HeaderText =

"Age";

 

 

GridViewColumn gvc1 = new GridViewColumn();

 

gvc1.HeaderText =

"First Name";

 

gvc1.UniqueName =

"FirstName";

 

 

GridViewColumn gvc2 = new GridViewColumn();

 

gvc2.HeaderText =

"Last Name";

 

gvc2.UniqueName =

"LastName";

 

 

GridViewColumn gvc3 = new GridViewColumn();

 

gvc3.HeaderText =

"Middle Name";

 

gvc3.UniqueName =

"MiddleName";

 

 

 

this.RadGridView1.Columns.Add(gvc);

 

 

this.RadGridView1.Columns.Add(gvc1);

 

 

this.RadGridView1.Columns.Add(gvc2);

 

 

this.RadGridView1.Columns.Add(gvc3);

 

 

this.RadGridView1.FrozenColumnCount = 2;

 

 

this.RadGridView1.ItemsSource = Person.GetSampleListOfPersons();

 

 

if (this.RadGridView1.ChildrenOfType<GridViewHeaderRow>() != null)

 

{

 

var headerRow = this.RadGridView1.ChildrenOfType<GridViewHeaderRow>().First();

 

 

this.RadGridView1.FrozenColumnCount = 2;

 

 

ColumnGroup colGroup = new ColumnGroup(this.RadGridView1.Columns[3], this.RadGridView1.Columns[3], "Column group 1", false);

 

 

GridViewCell cell;

 

headerRow.ChildrenOfType<

Grid>()[0].Height = 50;

 

headerRow.ChildrenOfType<

Grid>()[0].Children.Add(colGroup);

 

 

ColumnGroup colGroup1 = new ColumnGroup(this.RadGridView1.Columns[0], this.RadGridView1.Columns[2], "Basic group 1", true);

 

 

//colGroup1.Width = this.RadGridView1.Columns[0].ActualWidth + this.RadGridView1.Columns[1].ActualWidth;

 

 

 

 

headerRow.ChildrenOfType<

Grid>()[0].Children.Add(colGroup1);

 

}

 

}

 

void RadGridView1_RowLoaded(object sender, Telerik.Windows.Controls.GridView.RowLoadedEventArgs e)

 

{

 

if (e.Row is GridViewHeaderRow)

 

{

 

this.RadGridView1.FrozenColumnCount = 2;

 

 

ColumnGroup colGroup2 = new ColumnGroup(this.RadGridView1.Columns[3], this.RadGridView1.Columns[3], "Column group 2", false);

 

e.Row.ChildrenOfType<

Grid>()[0].Children.Add(colGroup2);

 

 

 

ColumnGroup colGroup = new ColumnGroup(this.RadGridView1.Columns[2], this.RadGridView1.Columns[2], "Column group 1", false);

 

 

GridViewCell cell;

 

e.Row.ChildrenOfType<

Grid>()[0].Height = 50;

 

e.Row.ChildrenOfType<

Grid>()[0].Children.Add(colGroup);

 

 

ColumnGroup colGroup1 = new ColumnGroup(this.RadGridView1.Columns[0], this.RadGridView1.Columns[1], "Basic group 1", true);

 

 

//colGroup1.Width = this.RadGridView1.Columns[0].ActualWidth + this.RadGridView1.Columns[1].ActualWidth;

 

 

 

 

e.Row.ChildrenOfType<

Grid>()[0].Children.Add(colGroup1);

 

}

 

else

 

 

 

 

{

}

 

}

}

 

public class Person

 

 

 

 

{

 

public Person(int age, string firstName, string lastName, string middleName)

 

{

 

this.Age = age;

 

 

this.FirstName = firstName;

 

 

this.LastName = lastName;

 

 

this.MiddleName = middleName;

 

}

 

public int Age { get; set; }

 

 

public string FirstName { get; set; }

 

 

public string LastName { get; set; }

 

 

public string MiddleName { get; set; }

 

 

public static List<Person> GetSampleListOfPersons()

 

{

 

var persons = new List<Person>

 

{

 

new Person(30, "John", "Smith","A"),

 

 

new Person(29, "Jane", "Smith","A"),

 

 

new Person(30, "Peter", "Smith","A")

 

};

 

return persons;

 

}

}

}

 

==========================================================
ColumnGroup.xaml
===========================================================

 

<

 

UserControl x:Class="ColumnGroups.ColumnGroup"

 

 

 

 

 

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

 

 

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

 

 

Width="100" Height="15">

 

 

 

 

 

 

<Grid x:Name="LayoutRoot" Background="White">

 

 

 

 

 

 

<TextBlock x:Name="CaptionTextBlock" Text="{Binding }" />

 

 

 

 

 

 

</Grid>

 

</

 

UserControl>

 

 

 

===============================================================
ColumnGroup.Xaml.cs
============================================================

 

using

 

System;

 

 

 

using

 

System.Collections.Generic;

 

 

 

using

 

System.Linq;

 

 

 

using

 

System.Net;

 

 

 

using

 

System.Windows;

 

 

 

using

 

System.Windows.Controls;

 

 

 

using

 

System.Windows.Documents;

 

 

 

using

 

System.Windows.Input;

 

 

 

using

 

System.Windows.Media;

 

 

 

using

 

System.Windows.Media.Animation;

 

 

 

using

 

System.Windows.Shapes;

 

 

 

using

 

Telerik.Windows.Controls;

 

 

 

namespace

 

ColumnGroups

 

{

 

public partial class ColumnGroup : UserControl

 

 

 

 

{

 

public ColumnGroup(GridViewColumn firstColumn, GridViewColumn lastColumn, string caption, bool isFrozen)

 

{

InitializeComponent();

 

this.FirstColumn = firstColumn;

 

 

this.LastColumn = lastColumn;

 

 

this.Caption = caption;

 

 

this.LastColumn = lastColumn;

 

 

this.Caption = caption;

 

 

this.HorizontalAlignment = HorizontalAlignment.Left;

 

 

this.VerticalAlignment = VerticalAlignment.Top;

 

 

this.Loaded += new RoutedEventHandler(ColumnGroup_Loaded);

 

 

this.Indent = 5;

 

 

this.IsFrozen = isFrozen;

 

 

this.SetValue(Grid.ColumnProperty, 2);

 

}

 

 

 

public static readonly DependencyProperty CaptionProperty =

 

 

DependencyProperty.Register("Caption", typeof(string), typeof(ColumnGroup), new PropertyMetadata(String.Empty, CaptionChanged));

 

 

 

public string Caption

 

{

 

get

 

 

 

 

{

 

return (string) this.GetValue(CaptionProperty);

 

}

 

set

 

 

 

 

{

 

this.SetValue(CaptionProperty, value);

 

}

}

 

 

 

 

public double Indent { get; set; }

 

 

private RadGridView parentGrid;

 

 

void ColumnGroup_Loaded(object sender, RoutedEventArgs e)

 

{

 

this.parentGrid = this.ParentOfType<RadGridView>();

 

 

 

 

this.parentGrid.ItemsControl.VirtualizingPanel.ScrollOwner.HorizontalScrollChanged += new EventHandler<System.Windows.Controls.Primitives.ScrollEventArgs>(ScrollOwner_HorizontalScrollChanged);

 

 

foreach (GridViewColumn column in this.parentGrid.Columns)

 

{

column.PropertyChanged +=

new System.ComponentModel.PropertyChangedEventHandler(column_PropertyChanged);

 

}

AlignToColumns();

 

}

 

double horizontalScrollOffset;

 

 

void ScrollOwner_HorizontalScrollChanged(object sender, System.Windows.Controls.Primitives.ScrollEventArgs e)

 

{

 

this.horizontalScrollOffset = e.NewValue;

 

 

this.AlignToColumns();

 

 

//AlignToColumnsDuringScroll();

 

 

 

 

}

 

static void CaptionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)

 

{

((

ColumnGroup)sender).CaptionTextBlock.Text = (string) args.NewValue;

 

}

 

void column_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)

 

{

 

if (e.PropertyName == "ActualWidth")

 

 

this.AlignToColumns();

 

}

 

private void AlignToColumns()

 

{

 

if (!this.IsFrozen)

 

{

 

double startPosition = 0;

 

 

double endPosition = 0;

 

 

double offset = 0;

 

 

foreach (GridViewColumn column in this.parentGrid.Columns)

 

{

 

if (column == this.FirstColumn)

 

{

startPosition = offset;

}

 

 

offset += column.ActualWidth;

 

if (column == this.LastColumn)

 

endPosition = offset;

}

 

this.Margin = new Thickness(startPosition + Indent - horizontalScrollOffset, 0, 0, 0);

 

 

this.Width = endPosition - startPosition;

 

}

 

else

 

 

 

 

{

 

double startPosition = 0;

 

 

double endPosition = 0;

 

 

double offset = 0;

 

 

for (int i = 0; i < this.parentGrid.FrozenColumnCount; i++)

 

{

offset +=

this.parentGrid.Columns[i].ActualWidth;

 

endPosition = offset;

}

 

this.Margin = new Thickness(0, 0, 0, 0);

 

 

this.Width = endPosition - startPosition;

 

 

}

}

 

public GridViewColumn FirstColumn { get; set; }

 

 

public GridViewColumn LastColumn { get; set; }

 

 

public bool IsFrozen { get; set; }

 

}

}

 

============================================================

Dave
Did you put this functionality into 2010Q2?  We can really use a secondary header solution that works with column freezing.
mansoo
i did put this functionality into 2010Q2, but when Freezing columns  set, exists a bug.
plz. send me sample program(sliverligth4)
swathi
hi, 

I am making some of columns invisible in runtime, so i am resetting StartColumnIndex and COlumnSPan in xaml.cs, I even chnged dependency properties  behavior, but it is not working for me, Any help 
Rabeek Ahamed
In the given example, can you give me the below modifications.
1. Set RowIndicatorVisibility="Collapsed" , the column merging is still consider this column and my header is not grouped as expected.
2. If the columnspan=1 then I dont want to give a seperate header caption for that column. It should take the caption from secondary column and center aligned.
Farhan Asad
Can the Common Headers be bound to a collection instead of being hard coded under the behaviours. I want a dynamic behavior for my stacked headers whereby I can add them on run time.

PS: Does October 2010 release have stacked header support yet?

Thanks
Tomasz
Hi there,
I've noticed that ColumnGroupsBehavior crashes for GridView placed in DataTemplate.
It seems this.AssociatedObject.ChildrenOfType<Grid>() does not work in such case.
This makes it useless in HierarchyChildTemplate scenario.

Can you see any workaround?
Thanks
Isabelle
Hi,
I was wondering if there is a way to had a trird layer of headers, like this:

|---------------------------------Title1----------------------------|
|--------------Title2a--------------| |--------------Title2b---------|
|------- -title3a-------||--------Title3b-------||------Title3c -------|

and if so, how?

thanks
Raghupathi Kamuni
Hi there,
I've noticed that ColumnGroupsBehavior crashes for RadGridView placed in

<

 

 

telerik:RadTabItem.Content>

 

</

 

 

telerik:RadTabItem.Content>

 

It seems this.AssociatedObject.ChildrenOfType<Grid>() does not work in such case.

Can you see any workaround?
Thanks
Raghupathi K
Satyajit Kadam
Hi,
I am working in wpf application.but I have faced problem at

RadGridView1_RowLoaded

 

event.And the line of code is

 

e.Row.ChildrenOfType<

 

Grid>()[0].Children.Add(colGroup);

This line of code is not working in wpf application

 

harika
how can i do dynamic column headers merging in radgrid in silverlight? can any one give me total working solution for this? i saw this example but i want dynamic column grouping
harika
Hi,
I am working in silverlight application.but I have faced same problem at

RadGridView1_RowLoaded

 

event.And the line of code is

 

e.Row.ChildrenOfType<

 

 

Grid>()[0].Children.Add(colGroup);
This line of code is not working in silverlight application. any help appreciated

Guti
Hi, Pavel
how Add CommonHeader from codebehind?
Guti
Hi, Pavel
how Add CommonHeader from codebehind?
Pavel Pavlov
Hi guys, I have some good news . This behavior will be included as internal feature for RadGridView for Q3 2011. The beta will be available in a few days.
Pavel Pavlov
Hi guys, I have some good news . This behavior will be included as internal feature for RadGridView for Q3 2011. The beta will be available in a few days.
Guti
Ok, ya pude hacer dinamicamente las columnas, lo tome como un reto y pues a esperar la nueva funsionalidad. Gracias
Guti
Ok, ya pude hacer dinamicamente las columnas, lo tome como un reto y pues a esperar la nueva funsionalidad. Gracias
Anthea
Hi....
Is it possible to export these additional headers to excel as well?
Thanks
Anthea

Comments

  1.    
      
      
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)
Read more articles by XAML Team - or - read latest articles in Developer Tools