4

I want to show a ComboBox with OPTGROUP style header gruopings in Silverlight. Every website I find (including questions on SO) that sovle this link to an outdated link and, handily, show no code snippets for me to work from.

E.g.:

enter image description here

So how do I do this?

McGarnagle
  • 101,349
  • 31
  • 229
  • 260
Chris
  • 26,744
  • 48
  • 193
  • 345
  • Are the items static or like a collection binding? – Chris W. Oct 31 '13 at 18:03
  • They would be a collection binding – Chris Nov 01 '13 at 09:37
  • I could swear I've done this in the past by placing a HeaderedItemsControl in as the ComboBox.ItemTemplate for collection instances, I'll see if I can't track down the source to provide an example but you get the concept. – Chris W. Nov 01 '13 at 14:30
  • @ChrisW. that would be pretty sweet. I added my flawed approach below, which avoids CollectionViewSource (of which I've found the Silverlight version to be more trouble than it's worth). – McGarnagle Mar 13 '14 at 23:57
  • Related: http://stackoverflow.com/q/2062720/1001985 – McGarnagle Mar 13 '14 at 23:58

3 Answers3

1

See my similar question: How to show group header for items in Silverlight combobox? I put dummy entries in collection source to indicate the group header and then modified DataTemplate to show the group headers in different way and normal entries in separate way.

Community
  • 1
  • 1
Nitesh
  • 2,681
  • 4
  • 27
  • 45
1

Here is one generalized approach. It's not bad, and should be flexible enough -- although it has the weakness of requiring an extended collection class (cannot make use of CollectionViewSource).

Grouped ComboBox screenshot

Step 1 ComboBoxGroupHeader object

This plays the role of "dummy entry" mentioned by @NiteshChordiya.

public class ComboBoxGroupHeader
{
    public ComboBoxGroupHeader(object header)
    {
        Header = header;
    }

    public object Header { get; protected set; }
}

Step 2 Extended ComboBox

This overrides PrepareContainerForItemOverride, in order to tinker with the dummy items' containers. It also provides an (optional) "HeaderTemplate".

public class GroupedComboBox : ComboBox
{
    public DataTemplate HeaderTemplate
    {
        get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
        set { SetValue(HeaderTemplateProperty, value); }
    }
    public static readonly DependencyProperty HeaderTemplateProperty =
        DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(GroupedComboBox), new PropertyMetadata(null));

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);
        var container = element as ComboBoxItem;
        if (container != null && item is ComboBoxGroupHeader)
        {
            // prevent selection
            container.IsHitTestVisible = false;

            // adjust the container to display the header content
            container.ContentTemplate = HeaderTemplate
            container.Content = ((ComboBoxGroupHeader)item).Header;
        }
    }
}

Step 3 Extended collection class

This does the grouping, and adds the dummy "ComboBoxGroupHeader" entries. This implementation is essentially read-only (groupings would break if you tried to add new items), but it would be straight-forward to support operations like "Add", "Insert", etc.

public class GroupedCollection<T, TGroup> : ObservableCollection<object>
{
    private Func<T, TGroup> _grouping;

    public IEnumerable<T> BaseItems
    {
        get { return base.Items.OfType<T>(); }
    }

    public GroupedCollection(IEnumerable<T> initial, Func<T, TGroup> grouping)
        : base(GetGroupedItems(initial, grouping))
    {
        _grouping = grouping;
    }

    private static IEnumerable<object> GetGroupedItems(IEnumerable<T> items, Func<T, TGroup> grouping)
    {
        return items
            .GroupBy(grouping)
            .SelectMany(grp => 
                new object[] { new ComboBoxGroupHeader(grp.Key) } 
                    .Union(grp.OfType<object>())
            );
    }
}

Usage

It works like a normal ComboBox, with the optional HeaderTemplate:

<local:GroupedComboBox ItemsSource="{Binding Source}">
    <local:GroupedComboBox.HeaderTemplate>
        <TextBlock FontSize="9" TextDecorations="Underline" Foreground="DarkGray"
                   Text="{Binding}" />
    </local:GroupedComboBox.HeaderTemplate>
</local:GroupedComboBox>

For the grouped to take effect, the source must use the "GroupedCollection" above, so as to include the dummy header items. Example:

public class Item
{
    public string Name { get; set; }
    public string Category { get; set; }

    public Item(string name, string category)
    {
        Name = name;
        Category = category;
    }
}

public class Items : GroupedCollection<Item, string>
{
    public Items(IEnumerable<Item> items)
        : base(items, item => item.Category) { }
}

public class ViewModel
{
    public IEnumerable<Item> Source { get; private set; }

    public ViewModel()
    {
        Source = new Items(new Item[] {
            new Item("Apples", "Fruits"),
            new Item("Carrots", "Vegetables"),
            new Item("Bananas", "Fruits"),
            new Item("Lettuce", "Vegetables"),
            new Item("Oranges", "Fruits")
        });
    }
}
McGarnagle
  • 101,349
  • 31
  • 229
  • 260
  • 1
    Please be aware: although the control sets `IsHitTestVisible` to false, the header items can still be selected via keyboard. You need to override `OnKeyDown` and check for arrow keys and page up \ down as well - either disable them or implement custom index updating logic. – Impworks Jun 28 '16 at 15:22
0

Group header is not supported in ComboBox. Alternate way for to implement it with DataGrid or use TreeView to show grouped data.

However, you can try something like this,

Silverlight Custom ComboBox

McGarnagle
  • 101,349
  • 31
  • 229
  • 260
Sajeetharan
  • 216,225
  • 63
  • 350
  • 396