Как сохранить состояние IsExpanded в заголовках групп списка
У меня довольно сложная проблема:
Я использую элемент управления ListView с элементом ItemsSource, установленным в CollectionViewSource, включая PropertyGroupDescription для группировки элементов ListView. CollectionViewSource выглядит следующим образом:
<CollectionViewSource x:Key="ListViewObjects">
<CollectionViewSource.Source>
<Binding Path="CurrentListViewData"/>
</CollectionViewSource.Source>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="ObjectType" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
В ListView я использую настройки заголовков групп следующим образом:
<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>
Как вы можете видеть, свойство IsExpanded для Expander имеет значение true. Это означает, что всякий раз, когда ListView обновляется, все элементы Expander расширяются.
Однако я хочу сохранить последнее состояние каждого Expander. Я не смог найти способ сохранить список состояний Expander в ObjectType. Я экспериментировал со связанным HashTable и конвертером, но мне не удалось предоставить ObjectType в качестве ConverterParameter, потому что он всегда передавался как строка. Но это может быть не решение в любом случае.
Может кто-нибудь дать мне подсказку или идею для решения, пожалуйста?:)
Ответы
Ответ 1
Вы можете создать новый класс с помощью словаря (скажем, ObjectType в качестве ключа к значениям bool) и дать ему индекс:
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;
}
}
Затем создайте экземпляр в ResourceDictionary и привяжите IsExpanded к нему следующим образом:
<Expander IsExpanded="{Binding Source={StaticResource myExpMgr}, Path=[Items[0].ObjectType]}">
Это может сделать это: хороший способ заставить WPF вызывать ваш код и передавать параметр только тогда, когда вам это нужно. (Что WPF позволяет помещать подвыражения в индексаторы в путь привязки, было для меня новостью - хорошо, хотя это не так!)
Ответ 2
Принятый ответ неверен, как объясняется в комментариях. Я написал следующее поведение, которое обеспечивает желаемую функциональность:
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
}
Он прикрепляет словарь к содержащему ItemsControl
, который сохраняет расширенное состояние для каждого элемента группы. Это будет необходимо, даже если элементы в элементе управления будут изменены.
Применение:
<Expander>
<i:Interaction.Behaviors>
<behaviors:PersistGroupExpandedStateBehavior GroupName="{Binding Name}" />
</i:Interaction.Behaviors>
...
</Expander>
Ответ 3
Отмеченный ответ Не работает в .Net 4.5.
Простым решением для этого является добавление
private bool _isExpanded = true;
public bool IsExpanded
{
get { return _isExpanded; }
set { _isExpanded = value; }
}
для вашего ViewModel (в этом случае любые объекты CurrentListViewData
)
а затем выполните:
<Expander IsExpanded="{Binding Items[0].IsExpanded}">
на вашем шаблоне.
Простой и эффективный, как WPF, должен быть