1

I've a collection of items inside an ObservableCollection, each item have a specific nation name (that's only a string). This is my collection:

private ObservableCollection<League> _leagues = new ObservableCollection<League>();
    public ObservableCollection<League> Leagues
    {
        get
        {
            return _leagues;
        }
        set
        {
            _leagues = value;
            OnPropertyChanged();
        }
    }

the League model have only a Name and a NationName properties. The Xaml looks like this:

<Controls:DropDownButton Content="Leagues" x:Name="LeagueMenu"
                             ItemsSource="{Binding Leagues}"
                                     ItemTemplate="{StaticResource CombinedTemplate}" >
        <Controls:DropDownButton.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding NationName}" />
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </Controls:DropDownButton.GroupStyle>
</Controls:DropDownButton>

but I doesn't get any header for the NationName property, the items inside the DropDown are organized without header but as list, so without organization. I'm trying to get this predisposition.

What am I doing wrong?

Community
  • 1
  • 1
Unchained
  • 187
  • 8

2 Answers2

1

If you check out the other post you've linked to, the answer has it all - in particular you need to bind to a CollectionView, rather than directly to the collection. Then you can set up grouping on the CollectionView.

So, in your case, define the property:

public ICollectionView LeaguesView { get; private set; }

and then after you've created your Leagues Collection, attach the View to your collection, and while you're at it set up the grouping on the view:

LeaguesView = (ListCollectionView)CollectionViewSource.GetDefaultView(Leagues);
LeaguesView.GroupDesriptions.Add(new PropertyGroupDescription("NationName"));

Then, bind your DropDownButton ItemSource to LeaguesView, and change your HeaderTemplate to bind to "Name" - which is the the name of the group:

            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" />
                </DataTemplate>
            </GroupStyle.HeaderTemplate>

You can also use the ItemCount property in there if you want to show how many items there are in the group.

Ian of Oz
  • 116
  • 7
  • I don't know, but I get no item displayed in the DropDownButton, I've created a paste with all code, maybe could you take a look? http://pastebin.com/BWVsjEFd – Unchained Oct 09 '16 at 14:45
  • I can't see anything obvious, but a few points: - you only need to create the View once, not each time you add a nation. - you've also added a layer of complexity above the initial question but using a CheckedListItem, maybe get it working without that first - try adding IsSynchronizedWithCurrentItem="true" to your DropDownButton xaml. - OnPropertyChanged on LeaguesView might help too – Ian of Oz Oct 09 '16 at 23:22
1

Preliminaries

Grouping items in an ItemsControl in WPF (which DropDownButton derives from) is fairly simple, and is accomplished in two steps. First you need to set up the items source by tweaking an ICollectionView associated with the source collection. Then you need to populate the ItemsControl.GroupStyle collection with at least one GroupStyle item - otherwise the items are presented in a plain (non-grouped) manner.

Diagnosis

The main issue you're facing is getting the drop-down to present the items in a grouped manner. Unfortunately, unlike setting up the items source, it is not something that is easily accomplished in case of the DropDownButton control. The reason for that stems from the way the control (or, more precisely, its template) is designed - the drop-down is presented inside a ContextMenu attached to a Button which is part of the template (see MahApps.Metro source code). Now ContextMenu also derives from ItemsControl, and most of its properties are bound to corresponding properties of the templated DropDownButton. That is however not the case for its GroupStyle property, because it's a read-only non-dependency property, and cannot be bound or event styled. That means that even if you add items to DropDownButton.GroupStyle collection, the ContextMenu.GroupStyle collection remains empty, hence the items are presented in non-grouped manner.

Solution (workaround)

The most reliable, yet most cumbersome solution would be to re-template the control and add GroupStyle items directly to the ContextMenu.GroupStyle collection. But I can offer you a much more concise workaround.

First of all, let's deal with the first step - setting up the items source. The easiest way (in my opinion) is to use CollectionViewSource in XAML. In your case it would boil down to something along these lines:

<mah:DropDownButton>
    <mah:DropDownButton.Resources>
        <CollectionViewSource x:Key="LeaguesViewSource" Source="{Binding Leagues}">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="NationName" />
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </mah:DropDownButton.Resources>
    <mah:DropDownButton.ItemsSource>
        <Binding Source="{StaticResource LeaguesViewSource}" />
    </mah:DropDownButton.ItemsSource>
</mah:DropDownButton>

Now for the main part - the idea is that we'll create a helper class that will contain one attached dependency property that will assign an owner DropDownButton control to the ContextMenu responsible for presenting its items. Upon changing the owner we'll observe its DropDownButton.GroupStyle collection and use ContextMenu.GroupStyleSelector to feed the ContextMenu with items coming from its owner's collection. Here's the code:

public static class DropDownButtonHelper
{
    public static readonly DependencyProperty OwnerProperty =
        DependencyProperty.RegisterAttached("Owner", typeof(DropDownButton), typeof(DropDownButtonHelper), new PropertyMetadata(OwnerChanged));

    public static DropDownButton GetOwner(ContextMenu menu)
    {
        return (DropDownButton)menu.GetValue(OwnerProperty);
    }

    public static void SetOwner(ContextMenu menu, DropDownButton value)
    {
        menu.SetValue(OwnerProperty, value);
    }

    private static void OwnerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var menu = (ContextMenu)d;
        if (e.OldValue != null)
            //unsubscribe from the old owner
            ((DropDownButton)e.OldValue).GroupStyle.CollectionChanged -= menu.OwnerGroupStyleChanged;
        if (e.NewValue != null)
        {
            var button = (DropDownButton)e.NewValue;
            //subscribe to new owner
            button.GroupStyle.CollectionChanged += menu.OwnerGroupStyleChanged;
            menu.GroupStyleSelector = button.SelectGroupStyle;
        }
        else
            menu.GroupStyleSelector = null;
    }

    private static void OwnerGroupStyleChanged(this ContextMenu menu, object sender, NotifyCollectionChangedEventArgs e)
    {
        //this method is invoked whenever owners GroupStyle collection is modified,
        //so we need to update the GroupStyleSelector
        menu.GroupStyleSelector = GetOwner(menu).SelectGroupStyle;
    }

    private static GroupStyle SelectGroupStyle(this DropDownButton button, CollectionViewGroup group, int level)
    {
        //we select a proper GroupStyle from the owner's GroupStyle collection
        var index = Math.Min(level, button.GroupStyle.Count - 1);
        return button.GroupStyle.Any() ? button.GroupStyle[index] : null;
    }
}

In order to complete the second step we need to bind the Owner property for the ContextMenu (we'll use DropDownButton.MenuStyle to do that) and add some GroupStyle items to the DropDownButton:

<mah:DropDownButton>
    <mah:DropDownButton.MenuStyle>
        <Style TargetType="ContextMenu" BasedOn="{StaticResource {x:Type ContextMenu}}">
            <Setter Property="local:DropDownButtonHelper.Owner" Value="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
        </Style>
    </mah:DropDownButton.MenuStyle>
    <mah:DropDownButton.GroupStyle>
        <GroupStyle />
    </mah:DropDownButton.GroupStyle>
</mah:DropDownButton>

This I think should be enough to achieve your goal.

Community
  • 1
  • 1
Grx70
  • 10,041
  • 1
  • 40
  • 55
  • I get this result: http://imgur.com/a/hOi6e maybe cause I'm using CheckedListBox class? http://pastebin.com/gJA2zWzz – Unchained Oct 11 '16 at 13:45
  • I've only posted the relevant parts of the code regarding the setup of the `DropDownButton` control - you still need to supply either `DisplayMemberPath`, `ItemTemplate` or `ItemTemplateSelector` yourself - you have `ItemTemplate="{StaticResource CombinedTemplate}"` in your question, so I'll start with using that. If you still get this result, it means that there's some error in your template, in which case you should post both the `CheckedListItem` class code as well as the template XAML. – Grx70 Oct 11 '16 at 14:01