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
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.