Introduction
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.
Building the Sample
Just download, unblock, unzip, open and run!
Description
This project has three examples included.
The first shows just the power of the Converter to group the Expanders.
The third example shows how you can generate the expanders from a collection, in a ListBox, just like the Accordian.
Example 1 - ValueConverter
The trick is to set the
IsExpanded property of each
Expander control from a code-behind property.
C#
Edit|Remove
csharp
string _CurrentExpanded;
public string CurrentExpanded
{
get
{
return _CurrentExpanded;
}
set
{
if (_CurrentExpanded != value)
{
_CurrentExpanded = value;
RaisePropertyChanged("CurrentExpanded");
}
}
}
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)
XAML
Edit|Remove
xaml
<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>
<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.
C#
Edit|Remove
csharp
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;
}
}
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 - Animation
I've added the second example of animated Expanders just to pad out the project and give you something extra. This project is mainly to teach about Grouping Expanders with a Value Converter, but the next natural step is to want to animate the opeining
and closing. So I grabbed one of the better examples for that from Matt Serbinski, thanks to him for that.
Example 3 - 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.
XAML
Edit|Remove
xaml
<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>
<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>
Notice the Expander Content is a property is also passed in. This is a property of the bound ExpanderItem class:
C#
Edit|Remove
csharp
public class ExpanderItem
{
public string Header { get; set; }
public string ItemId { get; set; }
public FrameworkElement Content { get; set; }
}
public class ExpanderItem
{
public string Header { get; set; }
public string ItemId { get; set; }
public FrameworkElement Content { get; set; }
}
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.
Source Code Files
- MainWindow.xaml - The startup window
- MainWindow.xaml.cs - Where the CurrentExpander property is for the example
- App.xaml - The Animated Expander style is in App.Resources
- Converters.cs - The ValueConverter and MultiValueConverter
- ExpanderItem.cs - The class used in Example 4
