I have quite a tricky problem:
I am using a ListView control with the ItemsSource set to a CollectionViewSource including a PropertyGroupDescription to group the ListView elements. The CollectionViewSource looks like this:
<CollectionViewSource x:Key="ListViewObjects">
<CollectionViewSource.Source>
<Binding Path="CurrentListViewData"/>
</CollectionViewSource.Source>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ObjectType" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
In the ListView I use customize the group headers like this:
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<DockPanel>
<TextBlock Text="{Binding Path=Items[0].ObjectType />
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
As you can see the IsExpanded property of the Expander is set to true. This means that whenever the ListView is refreshed, all Expander controls are expanded.
I do however want to save the last state of every Expander. I haven't been able to figure out a way to save a list of Expander states per ObjectType. I was experimenting with a bound HashTable and a Converter, but I failed at providing the ObjectType as a ConverterParameter, because it was always passed as a string. But that may not be the solution anyways.
Can somebody give me a hint or an idea for a solution, please? :)
The accepted answer is wrong as explained in the comments. I wrote the following behavior which achieves the desired functionality:
public class PersistGroupExpandedStateBehavior : Behavior<Expander>
{
#region Static Fields
public static readonly DependencyProperty GroupNameProperty = DependencyProperty.Register(
"GroupName",
typeof(object),
typeof(PersistGroupExpandedStateBehavior),
new PropertyMetadata(default(object)));
private static readonly DependencyProperty ExpandedStateStoreProperty =
DependencyProperty.RegisterAttached(
"ExpandedStateStore",
typeof(IDictionary<object, bool>),
typeof(PersistGroupExpandedStateBehavior),
new PropertyMetadata(default(IDictionary<object, bool>)));
#endregion
#region Public Properties
public object GroupName
{
get
{
return (object)this.GetValue(GroupNameProperty);
}
set
{
this.SetValue(GroupNameProperty, value);
}
}
#endregion
#region Methods
protected override void OnAttached()
{
base.OnAttached();
bool? expanded = this.GetExpandedState();
if (expanded != null)
{
this.AssociatedObject.IsExpanded = expanded.Value;
}
this.AssociatedObject.Expanded += this.OnExpanded;
this.AssociatedObject.Collapsed += this.OnCollapsed;
}
protected override void OnDetaching()
{
this.AssociatedObject.Expanded -= this.OnExpanded;
this.AssociatedObject.Collapsed -= this.OnCollapsed;
base.OnDetaching();
}
private ItemsControl FindItemsControl()
{
DependencyObject current = this.AssociatedObject;
while (current != null && !(current is ItemsControl))
{
current = VisualTreeHelper.GetParent(current);
}
if (current == null)
{
return null;
}
return current as ItemsControl;
}
private bool? GetExpandedState()
{
var dict = this.GetExpandedStateStore();
if (!dict.ContainsKey(this.GroupName))
{
return null;
}
return dict[this.GroupName];
}
private IDictionary<object, bool> GetExpandedStateStore()
{
ItemsControl itemsControl = this.FindItemsControl();
if (itemsControl == null)
{
throw new Exception(
"Behavior needs to be attached to an Expander that is contained inside an ItemsControl");
}
var dict = (IDictionary<object, bool>)itemsControl.GetValue(ExpandedStateStoreProperty);
if (dict == null)
{
dict = new Dictionary<object, bool>();
itemsControl.SetValue(ExpandedStateStoreProperty, dict);
}
return dict;
}
private void OnCollapsed(object sender, RoutedEventArgs e)
{
this.SetExpanded(false);
}
private void OnExpanded(object sender, RoutedEventArgs e)
{
this.SetExpanded(true);
}
private void SetExpanded(bool expanded)
{
var dict = this.GetExpandedStateStore();
dict[this.GroupName] = expanded;
}
#endregion
}
It attaches a dictionary to the containing ItemsControl
which saves the expanded state for every group item. This will be peristent, even if the items in the control changes.
Usage:
<Expander>
<i:Interaction.Behaviors>
<behaviors:PersistGroupExpandedStateBehavior GroupName="{Binding Name}" />
</i:Interaction.Behaviors>
...
</Expander>
You could create a new class with a Dictionary (say, ObjectType as a key to bool values), and give it an indexer:
Dictionary<ObjectType, bool> expandStates = new Dictionary<ObjectType, bool>();
public bool this[ObjectType key]
{
get
{
if (!expandStates.ContainsKey(key)) return false;
return expandStates[key];
}
set
{
expandStates[key] = value;
}
}
Then, instantiate it in a ResourceDictionary somewhere and bind the IsExpanded to it like this:
<Expander IsExpanded="{Binding Source={StaticResource myExpMgr}, Path=[Items[0].ObjectType]}">
That might well do it: a nice way of getting WPF to call your code and pass a parameter just when you need it. (That WPF lets you put subexpressions in indexers in a binding path was news to me - good though isn't it!)
The marked Answer Doesn't work in .Net 4.5.
A simple solution for this is to add a
private bool _isExpanded = true;
public bool IsExpanded
{
get { return _isExpanded; }
set { _isExpanded = value; }
}
property to your ViewModel (In this case Whatever objects CurrentListViewData
holds)
and then do:
<Expander IsExpanded="{Binding Items[0].IsExpanded}">
on your template.
Simple and effective just like WPF should be
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With