Many people have found the Accordian control not flexible enough or a little buggy, or they don't want to add the WPF Toolkit to their project. However the Accordian is essentially just a grouped bunch of Expanders with animated open and close. This is easy to recreate with a ValueConverter on IsExpanded. This gives developers much more flexability over layout and design.

I have two examples of how to group existing Expanders or generate a dynamic set of grouped Expanders.

There is a link below to a demo project of all this. 

The project also includes a third example, using animated open/close of the expanders, which I borrowed from Matt Serbinski's blog, thanks to him for that.

Example 1 - ValueConverter


The trick is to set the IsExpanded property of each Expander control from a code-behind property.

string _CurrentExpanded;
public string CurrentExpanded
{
    get
    {
        return _CurrentExpanded;
    }
    set
    {
        if (_CurrentExpanded != value)
        {
            _CurrentExpanded = value;
            RaisePropertyChanged("CurrentExpanded");
        }
    }
}

The property is just a string representing the NAME of the currently expanded control.
 
The IsExpanded binding used a ValueConverter, passing in the CurrentExpanded property and setting the ConverterParameter to that control's given name (in my example just a number 1-3)

<StackPanel Margin="20">
    <Expander Header="Expander one" IsExpanded="{Binding CurrentExpanded, Converter={StaticResource ExpandedConverter}, ConverterParameter=1}">
        <TextBlock Text="ONE"/>
    </Expander>
    <Expander Header="Expander two" IsExpanded="{Binding CurrentExpanded, Converter={StaticResource ExpandedConverter}, ConverterParameter=2}">
        <TextBlock Text="TWO"/>
    </Expander>
    <Expander Header="Expander three" IsExpanded="{Binding CurrentExpanded, Converter={StaticResource ExpandedConverter}, ConverterParameter=3}">
        <TextBlock Text="THREE"/>
    </Expander>
</StackPanel>

And finally, here is the ValueConverter that decides whether to return IsExpanded true or false, depending whether the control name is the same as the bound value.

public class ExpandedConverter : IValueConverter
{
    public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return ((string)value == (string)parameter);
    }
 
    public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return parameter;
    }
}

Notice the ConvertBack method is used in this example, as the binding is TwoWay. When an expander is manually expanded, the parameter (control name) is passed back to the code-behind property. Because I implement INotifyPropertyChanged, the PropertyChanged event causes all the other expanders to update their binding and close.
 
 

Example 2 - Programattically Generating Grouped Expanders


The final example is for those that want to generate a dynamic number of Expanders, based on a collection. This uses a Datatemplate to define the Expanders.

<DataTemplate x:Key="DataTemplate1">
    <Expander Header="{Binding Header}" Content="{Binding Content}">
        <Expander.Resources>
            <local:ExpandedMultiConverter x:Key="ExpandedMultiConverter"/>
        </Expander.Resources>
        <Expander.IsExpanded>
            <MultiBinding Converter="{StaticResource ExpandedMultiConverter}">
                <Binding Path="CurrentExpanded3" Mode="TwoWay" ElementName="Window"/>
                <Binding Path="ItemId" Mode="OneWay" />
            </MultiBinding>
        </Expander.IsExpanded>
    </Expander>
</DataTemplate>

It uses a MultiBinding because the ConverterParameter is not a DependancyProperty, so you can bind and pass in a dynamic name to each Expander for the conversion.
 
Notice the Expander Content is a property is also passed in. This is a property of the bound ExpanderItem class:

public class ExpanderItem
{
    public string Header { get; set; }
    public string ItemId { get; set; }
    public FrameworkElement Content { get; set; }
}

This can be used as follows:

Expanders = new ObservableCollection<ExpanderItem>
{
    new ExpanderItem { Header="Expander 1", ItemId="1", Content = new TextBlock { Text="Hello"} },
    new ExpanderItem { Header="Expander 2", ItemId="2", Content = new Grid { Width=200, Height=30, Background=Brushes.Yellow } },
    new ExpanderItem { Header="Expander 3", ItemId="3", Content = new Label { Content="World"} },
};

Finally, here is the ListBox I use to generate the Expanders:

<ListBox ItemsSource="{Binding Expanders}" ItemTemplate="{DynamicResource DataTemplate1}" Margin="20" Background="Transparent" BorderThickness="0" />


A collection of FrameworkElements could therefore be passed in and converted to this class. Then the collection of ExpanderItem generates the grouped Expanders automatically from the ItemTemplate above.


Don't waste time cutting and pasting, just grab the project from the link below and save yourself some time :)



This available in a demo project here.

This small article is part of a series of WPF "How To" articles, in response to real user questions on the MSDN WPF Forum.