Posted by: Zeeshan Amjad | February 4, 2010

Apply group style conditionally


Now we are going to study how can we apply group style conditionally. We have already discussed multi level group style. In that program we display the same style for state and county level grouping. Now we are going to enhance our program and display different style for state and county. Let’s first define two data template one for State and another for county. Here is piece of XAML code to define two data templates.

  1: <DataTemplate x:Key="StateTemplate">
  2: 	<Border Margin="2" BorderBrush="Brown" BorderThickness="1" CornerRadius="5">
  3: 		<TextBlock Margin="2" FontSize="16" FontWeight="Bold" Foreground="DarkBlue" Text="{Binding Path=Name}"/>
  4: 	</Border>
  5: </DataTemplate>
  6: <DataTemplate x:Key="CountyTemplate">
  7: 	<Border Margin="2" BorderBrush="Brown" Background="AliceBlue" BorderThickness="1" CornerRadius="5">
  8: 		<TextBlock Margin="2" FontSize="12" Foreground="Blue" Text="{Binding Path=Name}"/>
  9: 	</Border>
 10: </DataTemplate>
 11: 

Now we are going to inherit class from DataTemplateSelector and override SelecteTemplate method. Here is our first attempt to make such class.

  1: public class MyTemplateSelector : DataTemplateSelector
  2: {
  3: 	public DataTemplate StateTemplate
  4: 	{ get; set; }
  5: 
  6: 	public DataTemplate CountyTemplate
  7: 	{ get; set; }
  8: 
  9: 	public override DataTemplate SelectTemplate(object item, DependencyObject container)
 10: 	{
 11: 		ContentPresenter cp = container as ContentPresenter;
 12: 
 13: 		if (cp != null)
 14: 		{
 15: 			/* TODO */
 16: 		}
 17: 
 18: 		return base.SelectTemplate(item, container);
 19: 	}
 20: }
 21: 

But now we have a problem. Here container is in fact CollectionViewGroupInternal class, which is inherited by CollectionViewGroup class. And we can’t access that class directly from our code, at least not easily. We already discuss one limitation of WPF extensibility and this is one more limitation of WPF extensibility.

But we know that our grouping is only two level depth therefore we can apply little trick here. If it is a first level grouping then all of items in this group is also a type of CollectionViewGroupInternal type. On the other hand if it is second level of grouping then its items type is StateInfo class. We are going to use this information and update our overridden method. Here is our new overridden method.

  1: public class MyTemplateSelector : DataTemplateSelector
  2: {
  3: 	public DataTemplate StateTemplate
  4: 	{ get; set; }
  5: 
  6: 	public DataTemplate CountyTemplate
  7: 	{ get; set; }
  8: 
  9: 	public override DataTemplate SelectTemplate(object item, DependencyObject container)
 10: 	{
 11: 		ContentPresenter cp = container as ContentPresenter;
 12: 
 13: 		if (cp != null)
 14: 		{
 15: 			CollectionViewGroup cvg = cp.Content as CollectionViewGroup;
 16: 
 17: 			if (cvg.Items.Count > 0)
 18: 			{
 19: 				StateInfo stinfo = cvg.Items[0] as StateInfo;
 20: 
 21: 				if (stinfo != null)
 22: 					return CountyTemplate;
 23: 				else
 24: 					return StateTemplate;
 25: 			}
 26: 		}
 27: 
 28: 		return base.SelectTemplate(item, container);
 29: 	}
 30: }
 31: 

 

Now it works in this case. But of course there is limitation, we can’t use the same technique if the grouping is more than two level depth. Here is a complete XAML code of our program.

  1: <Window x:Class="WpfGroupStyle.Window1"
  2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4:     xmlns:local="clr-namespace:WpfGroupStyle"
  5:     Title="Multi Level Group Style" Height="400" Width="400">
  6:     <Window.Resources>
  7:         <DataTemplate x:Key="StateTemplate">
  8:             <Border Margin="2" BorderBrush="Brown" BorderThickness="1" CornerRadius="5">
  9:                 <TextBlock Margin="2" FontSize="16" FontWeight="Bold" Foreground="DarkBlue" Text="{Binding Path=Name}"/>
 10:             </Border>
 11:         </DataTemplate>
 12:         <DataTemplate x:Key="CountyTemplate">
 13:             <Border Margin="2" BorderBrush="Brown" Background="AliceBlue" BorderThickness="1" CornerRadius="5">
 14:                 <TextBlock Margin="2" FontSize="12" Foreground="Blue" Text="{Binding Path=Name}"/>
 15:             </Border>
 16:         </DataTemplate>
 17:         <local:MyTemplateSelector x:Key="MyTemplateSelectorObj"
 18:                       CountyTemplate="{StaticResource CountyTemplate}"
 19:                       StateTemplate="{StaticResource StateTemplate}"/>
 20:     </Window.Resources>
 21:     <Grid>
 22:         <ListBox Margin="5" Name="list" HorizontalContentAlignment="Stretch">
 23:             <ListBox.ItemTemplate>
 24:                 <DataTemplate>
 25:                     <Border Margin="2" CornerRadius="5" Background="Wheat">
 26:                         <StackPanel Margin="2">
 27:                             <TextBlock Margin="2" Foreground="Black" Text="{Binding Path=City}"/>
 28:                             <TextBlock Margin="2" Foreground="Black" Text="{Binding Path=Population}"/>
 29:                         </StackPanel>
 30:                     </Border>
 31:                 </DataTemplate>
 32:             </ListBox.ItemTemplate>
 33: 
 34:             <ListBox.GroupStyle>
 35:                 <GroupStyle HeaderTemplateSelector="{StaticResource MyTemplateSelectorObj}">
 36:                 </GroupStyle>
 37:             </ListBox.GroupStyle>
 38:         </ListBox>
 39:     </Grid>
 40: </Window>
 41: 
 42: 

 

Here is complete C# code of our program.

 

  1: using System;
  2: using System.Collections.Generic;
  3: using System.Linq;
  4: using System.Text;
  5: using System.Windows;
  6: using System.Windows.Controls;
  7: using System.Windows.Data;
  8: using System.Windows.Documents;
  9: using System.Windows.Input;
 10: using System.Windows.Media;
 11: using System.Windows.Media.Imaging;
 12: using System.Windows.Navigation;
 13: using System.Windows.Shapes;
 14: 
 15: namespace WpfGroupStyle
 16: {
 17:     /// <summary>
 18:     /// Interaction logic for Window1.xaml
 19:     /// </summary>
 20:     public partial class Window1 : Window
 21:     {
 22:         public Window1()
 23:         {
 24:             InitializeComponent();
 25: 
 26:             List<StateInfo> stateList = new List<StateInfo>();
 27: 
 28:             stateList.Add(new StateInfo("Maryland", "Frederick", "Frederick", 52767));
 29:             stateList.Add(new StateInfo("Maryland", "Frederick", "Brunswick", 4894));
 30:             stateList.Add(new StateInfo("Maryland", "Frederick", "New Market", 427));
 31:             stateList.Add(new StateInfo("Maryland", "Montgomery", "Rockville", 60734));
 32:             stateList.Add(new StateInfo("Maryland", "Montgomery", "Gaithersburg", 52613));
 33:             stateList.Add(new StateInfo("Taxes", "Harris", "Deer Park", 28520));
 34:             stateList.Add(new StateInfo("Taxes", "Harris", "Jersey Village", 6880));
 35:             stateList.Add(new StateInfo("Taxes", "Dallas", "Irving", 201927));
 36:             stateList.Add(new StateInfo("Taxes", "Dallas", "DeSoto", 37646));
 37:             stateList.Add(new StateInfo("Taxes", "Bexar", "Leon Valley", 9239));
 38:             stateList.Add(new StateInfo("California", "Los Angeles", "Burbank", 100316));
 39:             stateList.Add(new StateInfo("California", "Los Angeles", "Azusa", 44712));
 40:             stateList.Add(new StateInfo("California", "Los Angeles", "Culver City", 38816));
 41:             stateList.Add(new StateInfo("California", "Los Angeles", "Glendale", 194973));
 42:             stateList.Add(new StateInfo("California", "Sacramento", "Citrus Heights", 85071));
 43:             stateList.Add(new StateInfo("California", "Sacramento", "Elk Grove", 59984));
 44: 
 45:             list.ItemsSource = stateList;
 46: 
 47:             CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(list.ItemsSource);
 48: 
 49:             PropertyGroupDescription group1 = new PropertyGroupDescription("State");
 50:             PropertyGroupDescription group2 = new PropertyGroupDescription("County");
 51:             view.GroupDescriptions.Add(group1);
 52:             view.GroupDescriptions.Add(group2);
 53:         }
 54:     }
 55: 
 56:     public class StateInfo
 57:     {
 58:         public StateInfo(String state, String county, String city, int population)
 59:         {
 60:             State = state;
 61:             County = county;
 62:             City = city;
 63:             Population = population;
 64:         }
 65: 
 66:         public String State
 67:         { get; set; }
 68: 
 69:         public String County
 70:         { get; set; }
 71: 
 72:         public String City
 73:         { get; set; }
 74: 
 75:         public int Population
 76:         { get; set; }
 77:     }
 78: 
 79:     public class MyTemplateSelector : DataTemplateSelector
 80:     {
 81:         public DataTemplate StateTemplate
 82:         { get; set; }
 83: 
 84:         public DataTemplate CountyTemplate
 85:         { get; set; }
 86: 
 87:         public override DataTemplate SelectTemplate(object item, DependencyObject container)
 88:         {
 89:             ContentPresenter cp = container as ContentPresenter;
 90: 
 91:             if (cp != null)
 92:             {
 93:                 CollectionViewGroup cvg = cp.Content as CollectionViewGroup;
 94: 
 95:                 if (cvg.Items.Count > 0)
 96:                 {
 97:                     StateInfo stinfo = cvg.Items[0] as StateInfo;
 98: 
 99:                     if (stinfo != null)
100:                         return CountyTemplate;
101:                     else
102:                         return StateTemplate;
103:                 }
104:             }
105: 
106:             return base.SelectTemplate(item, container);
107:         }
108:     }
109: }
110: 

 

Here is the output of our program.

MultiLevelGroupOutput

Advertisements

Responses

  1. Hi Zeeshan,
    here is me solution without your two-level limitation:
    1. Define GroupStyles for each group in the resources of the window and use the already defined HeaderTemplates in it.

    2. Implement a GroupStyleSelector method like this(I put it into the MainWindow class in your sample), which uses the PropertyName of the PropertyStateDescription to decide which GroupStyle to use:
    public GroupStyle MyGroupStyleSelector(CollectionViewGroup group, int level)
    {
    if (level >= 0)
    {
    CollectionView cv = (CollectionView)CollectionViewSource.GetDefaultView(list.ItemsSource);
    if (cv.GroupDescriptions.Count > level)
    {
    if ((cv.GroupDescriptions[level] as PropertyGroupDescription).PropertyName == “State”)
    {
    return (GroupStyle)this.FindResource(“StateGroupStyle”);
    }
    else
    {
    return (GroupStyle)this.FindResource(“CountyGroupStyle”);
    }
    }
    else
    {
    return null;
    }
    }
    else
    {
    return null;
    }
    }

    3. Put the method into the delegate of the list (at the end of the window constructor):
    list.GroupStyleSelector = new GroupStyleSelector(MyGroupStyleSelector);

    4. Additionally I use a Converter with ConverterParameter in the HeaderTemplates.

    I hope you like it. Comments are welcome.
    Stefan

    • Sorry I missed to paste the Style in for # 1. Here they are:

      • Xaml is not displayed. I try it with pseudo notation:
        GroupStyle x:Key=”StateGroupStyle” HeaderTemplate=”{StaticResource StateTemplate}”
        GroupStyle x:Key=”CountyGroupStyle” HeaderTemplate=”{StaticResource CountyTemplate}”

  2. Thanks for this trick. I like this idea. Thanks for sharing this.

    • Now I found an alternative solution for the level limitation.
      With the CollectionView.GroupDescription.Count and the number of levels to the bottom I can calculate the level, get the according PropertyGroupDescriptions PropertyName and then decide which template to use. I think it is better because more simple: I don’t need to set the GroupStyle Selector which can only be done in code. Here is my code (I use a DataGrid instead of a ListBox):
      public override DataTemplate SelectTemplate(object item, DependencyObject container)
      {
      ContentPresenter cp = container as ContentPresenter;
      if (cp != null)
      {
      DataGrid dg = GetDataGrid(cp);
      CollectionView cv = (CollectionView)CollectionViewSource.GetDefaultView(dg.ItemsSource);
      // get count of groupDescriptions
      int groupDescCount = cv.GroupDescriptions.Count;
      int level;
      CollectionViewGroup cvg = cp.Content as CollectionViewGroup;

      // detect level from depth to bottom
      level = GetLevel(cvg, groupDescCount-1);
      // get PropertyName from PropertyGroupDescription of level and select Template to PropertyName
      string propertyName = (cv.GroupDescriptions[level] as PropertyGroupDescription).PropertyName;
      DataTemplate result = null;
      switch(propertyName)
      {
      case “Country”:
      result = CountyTemplate;
      break;
      case “State”:
      result = StateTemplate;
      break;
      }
      return result;
      }
      return base.SelectTemplate(item, container);
      }

      private static DataGrid GetDataGrid(DependencyObject parent)
      {
      if (parent != null)
      {
      if (parent.GetType() == typeof(DataGrid))
      {
      return (DataGrid)parent;
      }
      return GetDataGrid(VisualTreeHelper.GetParent(parent));
      }
      else
      {
      return null;
      }
      }

      private static int GetLevel(CollectionViewGroup cvg, int inLevel)
      {
      if (cvg != null)
      {
      if (cvg.IsBottomLevel)
      {
      return inLevel;
      }
      else
      {
      return GetLevel((CollectionViewGroup)cvg.Items[0], inLevel – 1);
      }
      }
      else
      {
      return inLevel;
      }
      }

  3. […] already few examples of using GroupStyle here, here and here. This time we are going to apply the group style in listview not in listbox. In our first step here […]

  4. […] already saw an example of applying GroupStyle with ListBox here and with ListView here.  We can apply the same technique with DatGrid. In first step we define […]

  5. thanks a lot .. you made my day..

    • I am glad that i solved your problem. Thanks to visit my blog.

      Regards
      Zeeshan Amjad

  6. Hi – great work. Question: How do we implented a fold out mechanism that fould out when clikcing on a group. If you have many groups and items, you would what to collape the groups. How could this be done?

    • First thanks to like it. I couldn’t really understand what do you mean by fold out mechanism. If you can explain it little bit then I may be able to try out what you are looking for.

      Regards
      Zeeshan Amjad


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: