Pages

Monday, 2 January 2012

WPF - Change the color of combo item dynamically

Hi everyone!

Today we are going to see how we can change the color of particular Combo box item.

We will need to controls for this sample:
  • Combo box
  • Color picker control
We will use WPF toolkit for color picker control. WPF toolkit can be downloaded from here. Once downloaded, unzip the file - it will extract 'WPFToolkit.Extended.dll'. Add reference to this DLL in our application.

Now, add both the controls to application. To use color picker control, we need to refer the downloaded DLL. Here is code snippet for adding these controls (with addition text block for usability perspective):
<Window x:Class="ComboboxWithColoredItems.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:toolkit="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit.Extended"
        Title="Colored rows in ComboBox" Height="150" Width="400">
    <Grid>
  <Grid.RowDefinitions>
   <RowDefinition Height="45"></RowDefinition>
   <RowDefinition Height="auto"></RowDefinition>
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
   <ColumnDefinition Width="*"></ColumnDefinition>
   <ColumnDefinition Width="60"></ColumnDefinition>
  </Grid.ColumnDefinitions>
  <TextBlock Text="Please select the item in combo box, and select the color for this item from colorpicker."
       Grid.ColumnSpan="2" TextWrapping="Wrap"></TextBlock>
  <ComboBox Grid.Row="1" HorizontalAlignment="Left" Width="320" 
      Grid.Column="0" x:Name="SampleCombo">
  </ComboBox>
  <toolkit:ColorPicker ShowAdvancedButton="False" Grid.Row="1" Grid.Column="1"
        Width="50" HorizontalAlignment="Left" Margin="5,0,0,0"></toolkit:ColorPicker>
 </Grid>
</Window>
Now, we need to bind some data with this combo box. Let's create one data class - which will have two fields:
  • Name - text to be displayed as combo item
  • MyBackColor - Solid color brush - the color for this combo item
Here is the MyData class:
 public class MyData : INotifyPropertyChanged
 {
  public MyData()
  {
   // Make the item transparent by default.
   MyBackColor = new SolidColorBrush(Colors.Transparent);
  }
  public string Name { get; set; }
  private SolidColorBrush _backColor;
  public SolidColorBrush MyBackColor
  {
   get
   {
    return _backColor;
   }
   set
   {
    _backColor = value;
    OnPropertyChanged("MyBackColor");
   }
  }
  /// 
  /// Called when [property changed].
  /// 
  /// 
Name of the property.  void OnPropertyChanged(string propertyName)
  {
   if (!string.IsNullOrEmpty(propertyName) && PropertyChanged != null)
   {
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
   }
  }
  public event PropertyChangedEventHandler PropertyChanged;
 }
Observe that we have implemented INotifyPropertyChanged interface, which we will be using to notify that some value in referenced object is changed.

Now, we will be adding one List of MyData objects to xaml.cs file, which we will use as items source of the combo box. Also, we will initialize this list in constructor of xaml:
public partial class MainWindow : Window, INotifyPropertyChanged
 {
  public MainWindow()
  {
   InitializeComponent();
   this.DataContext = this;
   InitializeData();
  }

  private void InitializeData()
  {
   _dataList = new List();
   _dataList.Add(new MyData { Name = "Mr. Abc" });
   _dataList.Add(new MyData { Name = "Mr. Def" });
   _dataList.Add(new MyData { Name = "Mr. Ghi" });
   _dataList.Add(new MyData { Name = "Mr. Jkl", MyBackColor = new SolidColorBrush(Colors.SeaShell) });
   _dataList.Add(new MyData { Name = "Mr. Mno" });
   _dataList.Add(new MyData { Name = "Mr. Pqr" });
  }

  private List _dataList;

  public List DataList
  {
   get
   {
    return _dataList;
   }
   set
   {
    _dataList = value;
    OnPropertyChanged("DataList");
   }
  }

  private MyData _selectedData = new MyData();
  public MyData SelectedData 
  {
   get
   {
    return _selectedData;
   }
   set
   {
    _selectedData = value;
    OnPropertyChanged("SelectedData");
   }
  }

  void OnPropertyChanged(string propertyName)
  {
   if (!string.IsNullOrEmpty(propertyName) && PropertyChanged != null)
   {
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
   }
  }
  public event PropertyChangedEventHandler PropertyChanged;
 }
We are now ready with our sample data and required controls. Now, we need to specify that the selected combo item's background color should be inline with the associated MyData object's MyBackColor property. For this, we will define the data template for combo box item:
 <Window.Resources>
  <DataTemplate x:Key="ColoredTemplate">
   <Grid Background="{Binding Path=MyBackColor}" Width="310" >
    <TextBlock Text="{Binding Name}" Margin="10,0,0,0"/>
   </Grid>
  </DataTemplate>
 </Window.Resources>
And change the combo box to refer this style, and the data list as items source:
<ComboBox Grid.Row="1" HorizontalAlignment="Left" Width="320" Grid.Column="0" x:Name="SampleCombo"
   ItemTemplate="{StaticResource ColoredTemplate}"
   SelectedItem="{Binding Path=SelectedData, Mode=TwoWay}"
   ItemsSource="{Binding Path=DataList}">
</ComboBox> 

Now, what we need to do is - when user changes the color in color picket, we will change the MyBackColor  property of selected item's associated MyData object using this new color. For this, let's use SelectedColorChanged event of color picker in xaml.cs file:
private void ColorPicker_SelectedColorChanged(object sender, RoutedPropertyChangedEventArgs e)
  {
   // Prepare new brush and assign it to the currently selected items's back color property.
   ((SampleCombo.SelectedItem) as MyData).MyBackColor = new SolidColorBrush(e.NewValue);
  }

And specify this event in Xaml also:
  <toolkit:ColorPicker SelectedColorChanged="ColorPicker_SelectedColorChanged" 
        ShowAdvancedButton="False" Grid.Row="1" Grid.Column="1"
        Width="50" HorizontalAlignment="Left" Margin="5,0,0,0"></toolkit:ColorPicker>

Yup, we are done now. Whenever we change the color in color picker, the background color of selected combo item will also be changed:

And, all the rows can have their own color:

Enjoy!

WPF - Single running instance of application

Welcome!

As a win application developer, it is possible that we required to develop the singleton application - the application having only one running instance and executed without allowing user to open multiple instances.

Let's do this today in simple WPF application. Create new WPF application project. I would name it: 'SingletonApplication'. Now, lets create our 'SingletonManager' class which would contain the logic maintain singleton instance of our application.

Let's add reference to 'Microsoft.VisualBasic' dll. Now, we need to derive this class from 'WindowsFormsApplicationBase' class, which will allow us to override two important methods:
  • OnStartup - which would be called when the application's first instance is started.
  • OnStartupNextInstance - which would be called when the application's second instance started.

Now, let's add 'Main()' method to this class as this class would be our start up location for project. See the below code:
 public class SingletonManager : WindowsFormsApplicationBase
 {
  [STAThread]
  public static void Main(string[] args)
  {
   new SingletonManager().Run(args);
  }

Now, in the constructor of this class, we need to set 'IsSingleInstance' property of the parent class to 'true' - to indicate that this application is singleton application:
public SingletonManager()
{
IsSingleInstance = true;
}

It's time to handle the 'OnStartup' and 'OnStartupNextInstance' methods now. In 'OnStartup' method, we will create new instance of Application and store it in class variable for further reference. Also, we will be maintaining the start up window object - so this object can be activated when next instance is ready to run. Here is how we need to modify this method:
  public App application { get; private set; }
  MainWindow window;
  protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
  {
   application = new App();
   window = new MainWindow();
   application.Run(window);
   return false;
  }
Next, lets override the OnStartupNextInstance method:
protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
  {
   base.OnStartupNextInstance(eventArgs);
   // if the window is currently minimized, then restore it.
   if (window.WindowState == WindowState.Minimized)
   {
    window.WindowState = WindowState.Normal;
   }
   window.Activate();
  }

Hmm, we are almost done now. Let's compile our project and the result should be 2 errors - stating that application has more than one entry points defined. We need to our 'SingletonManager' class as start up location for our application. Go to solution explorer, right click on our project, and click on Properties. Observe the options available under start up location:

Here, we have two start up objects: One is default App object, and the other is one which we have defined with 'Main()' method. The first one would auto-generate the code with 'Main()' method and so leading to errors. Let's select SingletonManager object as our start up object. Save the properties and compile - application should be compiles successfully now.

Go to the output directory, and run the application - first instance would run. Now, any number of time we try to run the other instance by double clicking the .exe, it would always highlight the first instance. If it's minimized, then it would be restored to normal state and then would be highlighted.

Here the full code of SingletonManager class:
using System;
using System.Windows;
using Microsoft.VisualBasic.ApplicationServices;

namespace SingletonApplication
{
 public class SingletonManager : WindowsFormsApplicationBase
 {
  [STAThread]
  public static void Main(string[] args)
  {
   new SingletonManager().Run(args);
  }

  public SingletonManager()
  {
   IsSingleInstance = true;
  }

  public App application { get; private set; }
  MainWindow window;
  protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
  {
   application = new App();
   window = new MainWindow();
   application.Run(window);
   return false;
  }
  protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
  {
   base.OnStartupNextInstance(eventArgs);
   // if the window is currently minimized, then restore it.
   if (window.WindowState == WindowState.Minimized)
   {
    window.WindowState = WindowState.Normal;
   }
   window.Activate();
  }
 }
}

Enjoy!

Sunday, 1 January 2012

WPF - Custom combo box with grouping

Happy new year to everyone!

I am sure many of us had a time when we require to create grouped items in the combo box - same way I required. I needed to create the combo box with look like the address bar of Internet explorer - where there are three groups of links:
  • Group without any title showing the just visited URL
  • History group showing URL used in past
  • Favourite group showing the favourite URLs

Let's create such combo box step by step - which can be named as Custom Combo box, Combo box with grouped items or Combo box with Internet Explorer Address bar style - which will look like:


I have used Visual Studio 2010 for this example. Create a new WPF Application in Visual Studio and give it any suitable name. I have used 'ComboBoxWithGroupedItems'.

It will add Xaml page with default Grid added to your window. Simply add new combo box to this window:
<stackpanel>
 <combobox height="30" width="300">
 </combobox>
</stackpanel>


Now, its time to create the informative class for our URL information. Add new class to your project - named - 'URLDetails' with following members:
public class URLDetails
 {
  public URLDetails()
  {
  }

  public string Link { get; set; }
  public string GroupName { get; set; }
 }

Link property will hold Link to be opened and GroupName property will hold the name of the group under which the item should be displayed. Let's define one property of type ObservableCollection in the .cs file of our Xaml as shown below. To use the ObservableCollection, you will have to add 'using System.Collections.ObjectModel;' at the top of your xaml.cs file.
private ObservableCollection&lt;URLDetails> _URLs;
public ObservableCollection&lt;URLDetails> URLs
{
 get { return _URLs; }
 set { _URLs = value; }
}

Now, we have property ready to store the details or URLs. We will now fill dummy data to this property. Let's create private method to do this for us, and call this method from the constructor of xaml.cs file as shown below:
const string MRUGroupName = "Most Recently Used";
  const string HistoryGroupName = "History";
  const string FavouritesGroupName = "Favourites";
  public MainWindow()
  {
   InitializeComponent();
   FillDummyData();
  }

  private void FillDummyData()
  {
   _URLs = new ObservableCollection&lt;URLDetails>();

   // Add MRU link
   _URLs.Add(new URLDetails { GroupName = MRUGroupName, Link = "www.msdn.com" });

   // Add History links
   _URLs.Add(new URLDetails { GroupName = HistoryGroupName, Link = "www.google.co.in"  });
   _URLs.Add(new URLDetails { GroupName = HistoryGroupName, Link = "www.yahoo.com" } );
   _URLs.Add(new URLDetails { GroupName = HistoryGroupName, Link = "www.msn.com" } );
   _URLs.Add(new URLDetails { GroupName = HistoryGroupName, Link = "https://mail.google.com" } );
   _URLs.Add(new URLDetails { GroupName = HistoryGroupName, Link = "www.msdn.com" } );
   _URLs.Add(new URLDetails { GroupName = HistoryGroupName, Link = "www.wikipedia.com" } );

   // Add Favorite links
   _URLs.Add(new URLDetails { GroupName = FavouritesGroupName, Link = "www.mymail.com" } );
   _URLs.Add(new URLDetails { GroupName = FavouritesGroupName, Link= "www.codeproject.com" } );
   _URLs.Add(new URLDetails { GroupName = FavouritesGroupName, Link = "www.songs.in" } );
  }

OK, now we have data ready to bind with combo box. But before doing this, we want to have some grouping applied there. So, lets first define the CollectionViewSource with Grouping in Xaml file as shown below. Don't forget to refer to xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase" as shown in below code.
<window height="400" title="My Grouped Combo" width="350" x:class="ComboBoxWithGroupedItems.MainWindow" xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
 <window.resources>
  <collectionviewsource source="{Binding URLs}" x:key="GroupedData">
   <collectionviewsource.groupdescriptions>
    <propertygroupdescription propertyname="GroupName">
    </propertygroupdescription>
   </CollectionViewSource.GroupDescriptions>
 </window.resources>
</window>

Now, lets add template for group header and group item description in Window.Resources only. Here, Link template says that value of Link property should be displayed as item.
<DataTemplate x:Key="GroupHeader">
   <TextBlock Text="{Binding Name}" Margin="10,0,0,0" Foreground="#989791" />
  <:/DataTemplate>
  <DataTemplate x:Key="URLTemplate">
   <TextBlock Text="{Binding Link}" Margin="10,0,0,0"/>
  </DataTemplate>

Now, we need to define style for combo box group item, to tell compiler to add Expander for each group item - so grouped items can be expanded and collapsed. Add below style in Window.Resources:

OK, now we are ready to bind this data source with combo-box:
<StackPanel>
  <ComboBox Width="300" Grid.Row="0" Height="30" 
      ItemsSource="{Binding Source={StaticResource GroupedData}}"
      ItemTemplate="{StaticResource URLTemplate}">
   <ComboBox.GroupStyle>
    <GroupStyle ContainerStyle="{StaticResource containerStyle}"
     HeaderTemplate="{StaticResource GroupHeader}">
    </GroupStyle>
   </ComboBox.GroupStyle>
  </ComboBox>
 </StackPanel>
And, here it is - how it will look when you run the application:
Hurray! We have got something similar what we want to achieve. But observe that,
  • The expander button is still having default arrow style, its on the left side of header text (which we want on right most side of group header), 
  • There is no line present in the header text, and 
  • When the group is collapsed, no item is being shown (which we want to show some configured item at least when its collapsed).
For this, we will have to modify the control template of Combo box. It would lead to a lot of Xaml code. The control template of any control can be easily found on Internet or Microsoft Expression Blend can be used to get/modify the template of any control. Any control's template will include number of colour brushed it has used for styling, and lot of Xaml code which should be placed in Windows.Resources. If you want to get rid of hundreds of lines in your Xaml, then you can shift your styles into other Xaml and then can refer those styles using Resource dictionary concept in your Xaml. First, we need to add one property in Xaml.cs for desired number items to be displayed while group is collapsed:
public int NumberOfItems
  {
   get
   {
    return 3;
   }
  }  
Also, for calculating the minimum number of items to be displayed while the group is collapsed, we need to add converter for calculation of height:
public class HeightConverter : IMultiValueConverter
 {
  /// 





There will be two values here - first one will be the collection group bound - one call per group in combo box.
  /// The second will be the desired items to be displayed when the group is not expanded.  /// 





  /// 





  /// 





  /// 
  public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
   double returnValue;
   // Find out the total number of items in the current group i.e. 4 items in Most Recently Used group.
   int itemCount = ((System.Windows.Data.CollectionViewGroup)(((System.Windows.FrameworkElement)(values[0])).DataContext)).ItemCount;
   // This is the number of items to be displayed when the group is not expanded.
   int configuredItems = (int)values[1];
   // Now, if there are less items available in particular group than configured, then we will display all the items available in group
   // else we will display the configured number of items only. This means, if configured item number is 5 but there are only 3 items in 
   // any group, then we will consider 3 items and will return height to display exact 3 items only.
   if (configuredItems >= itemCount)
   {
    // SystemParameters.SmallIconHeight - return the required height for one row - this is required because
    // if you configured it hard coded, then when user will change the resolution, the number of items displayed will not be
    // what you wish i.e. if resolution is incresed, then instead of 3 - it will display 4 items and vice versa.
    returnValue = itemCount * SystemParameters.SmallIconHeight;
   }
   else
   {
    returnValue = configuredItems * SystemParameters.SmallIconHeight;
   }
   // This 2 is added just to ensure some space is left at the end of combo box.
   return returnValue + 2;
  }

  public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
  {
   throw new NotImplementedException();
  }
 }
The above class will return the desired height to display the defined number of items for particular combo box group. Now, final step to use the modified style for the combo box. The below is the style you need to add to Window.Resources. This is final mark-up for our Xaml:
<Window x:Class="ComboBoxWithGroupedItems.MainWindow"
  xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:local="clr-namespace:ComboBoxWithGroupedItems"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="My Grouped Combo" Height="400" Width="350" x:Name="MyWindow">
 <Window.Resources>
  <!-- Fill Brushes, which are required by the control template of combo box - so we have copied them. -->
  <local:HeightConverter x:Key="MyHeightConverter" />
  <LinearGradientBrush x:Key="NormalBrush" StartPoint="0,0" EndPoint="0,1">
   <LinearGradientBrush.GradientStops>
    <GradientStopCollection>
     <GradientStop Color="#FFF" Offset="0.0"/>
     <GradientStop Color="#CCC" Offset="1.0"/>
    </GradientStopCollection>
   </LinearGradientBrush.GradientStops>
  </LinearGradientBrush>
  <LinearGradientBrush x:Key="HorizontalNormalBrush" StartPoint="0,0" EndPoint="1,0">
   <LinearGradientBrush.GradientStops>
    <GradientStopCollection>
     <GradientStop Color="#FFF" Offset="0.0"/>
     <GradientStop Color="#CCC" Offset="1.0"/>
    </GradientStopCollection>
   </LinearGradientBrush.GradientStops>
  </LinearGradientBrush>
  <LinearGradientBrush x:Key="LightBrush" StartPoint="0,0" EndPoint="0,1">
   <LinearGradientBrush.GradientStops>
    <GradientStopCollection>
     <GradientStop Color="#FFF" Offset="0.0"/>
     <GradientStop Color="#EEE" Offset="1.0"/>
    </GradientStopCollection>
   </LinearGradientBrush.GradientStops>
  </LinearGradientBrush>
  <LinearGradientBrush x:Key="HorizontalLightBrush" StartPoint="0,0" EndPoint="1,0">
   <LinearGradientBrush.GradientStops>
    <GradientStopCollection>
     <GradientStop Color="#FFF" Offset="0.0"/>
     <GradientStop Color="#EEE" Offset="1.0"/>
    </GradientStopCollection>
   </LinearGradientBrush.GradientStops>
  </LinearGradientBrush>
  <LinearGradientBrush x:Key="DarkBrush" StartPoint="0,0" EndPoint="0,1">
   <LinearGradientBrush.GradientStops>
    <GradientStopCollection>
     <GradientStop Color="#FFF" Offset="0.0"/>
     <GradientStop Color="#AAA" Offset="1.0"/>
    </GradientStopCollection>
   </LinearGradientBrush.GradientStops>
  </LinearGradientBrush>
  <LinearGradientBrush x:Key="PressedBrush" StartPoint="0,0" EndPoint="0,1">
   <LinearGradientBrush.GradientStops>
    <GradientStopCollection>
     <GradientStop Color="#BBB" Offset="0.0"/>
     <GradientStop Color="#EEE" Offset="0.1"/>
     <GradientStop Color="#EEE" Offset="0.9"/>
     <GradientStop Color="#FFF" Offset="1.0"/>
    </GradientStopCollection>
   </LinearGradientBrush.GradientStops>
  </LinearGradientBrush>
  <SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />
  <SolidColorBrush x:Key="DisabledBackgroundBrush" Color="#EEE" />
  <SolidColorBrush x:Key="WindowBackgroundBrush" Color="#FFF" />
  <SolidColorBrush x:Key="SelectedBackgroundBrush" Color="#DDD" />
  <!-- Border Brushes -->
  <LinearGradientBrush x:Key="NormalBorderBrush" StartPoint="0,0" EndPoint="0,1">
   <LinearGradientBrush.GradientStops>
    <GradientStopCollection>
     <GradientStop Color="#CCC" Offset="0.0"/>
     <GradientStop Color="#444" Offset="1.0"/>
    </GradientStopCollection>
   </LinearGradientBrush.GradientStops>
  </LinearGradientBrush>
  <LinearGradientBrush x:Key="HorizontalNormalBorderBrush" StartPoint="0,0" EndPoint="1,0">
   <LinearGradientBrush.GradientStops>
    <GradientStopCollection>
     <GradientStop Color="#CCC" Offset="0.0"/>
     <GradientStop Color="#444" Offset="1.0"/>
    </GradientStopCollection>
   </LinearGradientBrush.GradientStops>
  </LinearGradientBrush>
  <LinearGradientBrush x:Key="DefaultedBorderBrush" StartPoint="0,0" EndPoint="0,1">
   <LinearGradientBrush.GradientStops>
    <GradientStopCollection>
     <GradientStop Color="#777" Offset="0.0"/>
     <GradientStop Color="#000" Offset="1.0"/>
    </GradientStopCollection>
   </LinearGradientBrush.GradientStops>
  </LinearGradientBrush>
  <LinearGradientBrush x:Key="PressedBorderBrush" StartPoint="0,0" EndPoint="0,1">
   <LinearGradientBrush.GradientStops>
    <GradientStopCollection>
     <GradientStop Color="#444" Offset="0.0"/>
     <GradientStop Color="#888" Offset="1.0"/>
    </GradientStopCollection>
   </LinearGradientBrush.GradientStops>
  </LinearGradientBrush>
  <SolidColorBrush x:Key="DisabledBorderBrush" Color="#AAA" />
  <SolidColorBrush x:Key="SolidBorderBrush" Color="#888" />
  <SolidColorBrush x:Key="LightBorderBrush" Color="#AAA" />
  <!-- Miscellaneous Brushes -->
  <SolidColorBrush x:Key="GlyphBrush" Color="#444" />
  <SolidColorBrush x:Key="LightColorBrush" Color="#DDD" />
  <!--This is the default toggle button style - we have not played it with it, just leave it as it is.
  If you wish, then you can change the expanded and collapsed image of this button too. -->
  <ControlTemplate x:Key="ExpanderToggleButton" TargetType="{x:Type ToggleButton}">
   <Border x:Name="Border" CornerRadius="2,0,0,0" Background="Transparent" BorderBrush="{StaticResource NormalBorderBrush}"
    BorderThickness="0,0,0,0">
    <Path x:Name="Arrow" Fill="{StaticResource GlyphBrush}" HorizontalAlignment="Center" VerticalAlignment="Center"
      Data="M 0 0 L 4 4 L 8 0 Z"/>
   </Border>
   <ControlTemplate.Triggers>
    <Trigger Property="IsMouseOver" Value="true">
     <Setter TargetName="Border" Property="Background"
              Value="{StaticResource DarkBrush}" />
    </Trigger>
    <Trigger Property="IsPressed" Value="true">
     <Setter TargetName="Border" Property="Background"
              Value="{StaticResource PressedBrush}" />
    </Trigger>
    <Trigger Property="IsChecked" Value="true">
     <Setter TargetName="Arrow" Property="Data"
              Value="M 0 4 L 4 0 L 8 4 Z" />
    </Trigger>
    <Trigger Property="IsEnabled" Value="False">
     <Setter TargetName="Border" Property="Background"
              Value="{StaticResource DisabledBackgroundBrush}" />
     <Setter TargetName="Border" Property="BorderBrush"
              Value="{StaticResource DisabledBorderBrush}" />
     <Setter Property="Foreground"
              Value="{StaticResource DisabledForegroundBrush}"/>
     <Setter TargetName="Arrow" Property="Fill"
              Value="{StaticResource DisabledForegroundBrush}" />
    </Trigger>
   </ControlTemplate.Triggers>
  </ControlTemplate>
  <Style TargetType="{x:Type Expander}">
   <Setter Property="Template">
    <Setter.Value>
     <ControlTemplate TargetType="{x:Type Expander}">
      <Grid>
       <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition x:Name="ContentRow"/>
       </Grid.RowDefinitions>
       <Border 
        x:Name="Border" Grid.Row="0" Background="{StaticResource WindowBackgroundBrush}"
        BorderBrush="{StaticResource NormalBorderBrush}" BorderThickness="0,0,0,0" 
        CornerRadius="2,2,0,0" >
        <Grid>
         <Grid.ColumnDefinitions>
          <ColumnDefinition Width="*" />
          <ColumnDefinition Width="20" />
         </Grid.ColumnDefinitions>
         <!--Here, we have played a bit, to move the toggle button to expand and collapse the group
         to the right corner of the group header, so we placed this button in column 1 instead of column 0.-->
         <ToggleButton
          IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
          OverridesDefaultStyle="True" Grid.Column="1" Template="{StaticResource ExpanderToggleButton}" 
          Background="{StaticResource NormalBrush}" />
         <Grid Grid.Column="0">
          <!--We have modified this grid to have Group name i.e. ContentPresenter and than 
          a line upto the expand/collapse button.-->
          <Grid.ColumnDefinitions>
           <ColumnDefinition Width="auto"/>
           <ColumnDefinition Width="*"/>
          </Grid.ColumnDefinitions>
          <!--This content presenter will have value of 'Header' which in our case is group name.-->
          <ContentPresenter Margin="2" ContentSource="Header" RecognizesAccessKey="True" Grid.Column="0" />
          <Line X1="0" Y1="0" X2="1" Y2="0" Stretch="Fill" Stroke="DimGray" Grid.Column="1" />
         </Grid>
        </Grid>
       </Border>
       <Border x:Name="Content" Grid.Row="1" Background="{StaticResource WindowBackgroundBrush}"
        BorderBrush="{StaticResource SolidBorderBrush}" BorderThickness="0,0,0,0" 
        CornerRadius="0,0,2,2" >
        <!--This is border, in which the entire content is placed, and we need to play with height of this border
        in order to dispaly only desired number of items when group is collapsed. We have defined height converter class
        in our project. We are passing the Content and NumberOfItems to this converter using multibinding.-->
        <Border.Height>
         <MultiBinding Converter="{StaticResource MyHeightConverter}">
          <Binding ElementName="ComboExpander" Path="Content" />
          <Binding ElementName="MyWindow" Path="NumberOfItems"/>
         </MultiBinding>
        </Border.Height>
        <ContentPresenter Margin="10,2,0,0" />
       </Border>
      </Grid>
      <!--This is regular expand button and we have left it as it was.-->
      <ControlTemplate.Triggers>
       <Trigger Property="IsExpanded" Value="True">
        <Setter TargetName="Content" Property="Height" Value="{Binding DesiredHeight, ElementName=Content}"/>
       </Trigger>
       <Trigger Property="IsEnabled" Value="False">
        <Setter TargetName="Border" Property="Background"
        Value="{StaticResource DisabledBackgroundBrush}" />
        <Setter TargetName="Border" Property="BorderBrush" Value="{StaticResource DisabledBorderBrush}" />
        <Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
       </Trigger>
      </ControlTemplate.Triggers>
     </ControlTemplate>
    </Setter.Value>
   </Setter>
  </Style>
  <CollectionViewSource x:Key="GroupedData" Source="{Binding URLs}">
   <!--Here we are specifying on which field of collection item should be grouped-->
   <CollectionViewSource.GroupDescriptions>
    <PropertyGroupDescription PropertyName="GroupName"/>
   </CollectionViewSource.GroupDescriptions>
  </CollectionViewSource>
  <!--Here we tell that the Header of each group should be displaying the group name-->
  <DataTemplate x:Key="GroupHeader">
   <TextBlock Text="{Binding Name}" Margin="10,0,0,0" Foreground="#989791" />
  </DataTemplate>
  <!--Here, we tell that the URL's description should be displayed as item text.-->
  <DataTemplate x:Key="URLTemplate">
   <TextBlock Text="{Binding Link}" Margin="10,0,0,0"/>
  </DataTemplate>
  <!-- Container Style for 1st level of grouping -->
  <Style x:Key="containerStyle"  TargetType="{x:Type GroupItem}">
   <Setter Property="Template">
    <Setter.Value>
     <ControlTemplate TargetType="{x:Type GroupItem}">
      <!--Here, we tell that each group of item will be placed under Expander control, 
      and this expander control will by default will have style we defined in above code.-->
      <Expander IsExpanded="False" x:Name="ComboExpander" Header="{TemplateBinding Content}" 
       HeaderTemplate="{TemplateBinding ContentTemplate}">
       <ItemsPresenter />
      </Expander>
     </ControlTemplate>
    </Setter.Value>
   </Setter>
  </Style>
 </Window.Resources>
 <StackPanel>
  <ComboBox Width="300" Grid.Row="0" Height="30" 
      ItemsSource="{Binding Source={StaticResource GroupedData}}"
      ItemTemplate="{StaticResource URLTemplate}">
   <ComboBox.GroupStyle>
    <GroupStyle ContainerStyle="{StaticResource containerStyle}"
     HeaderTemplate="{StaticResource GroupHeader}">
    </GroupStyle>
   </ComboBox.GroupStyle>
  </ComboBox>
 </StackPanel>
</Window>
And here we are done. It would be showing the desired look and behaviour. Here is the sample solution uploaded: Download

Enjoy!