【问题标题】:How to save the IsExpanded state in group headers of a listview如何在列表视图的组标题中保存 IsExpanded 状态
【发布时间】:2011-02-18 00:32:51
【问题描述】:

我有一个相当棘手的问题:

我正在使用 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>

您可以看到 Expander 的 IsExpanded 属性设置为 true。这意味着每当刷新 ListView 时,所有 Expander 控件都会展开。

不过,我确实想保存每个 Expander 的最后一个状态。我还没有找到一种方法来保存每个 ObjectType 的 Expander 状态列表。我正在尝试绑定 HashTable 和 Converter,但未能将 ObjectType 作为 ConverterParameter 提供,因为它始终作为字符串传递。但无论如何,这可能不是解决方案。

有人可以给我一个提示或解决方案的想法吗? :)

【问题讨论】:

  • 你确定你真正的问题不是你当前的设计强迫你刷新列表视图吗?
  • ListView 通过调用 NotifyPropertyChanged("CurrentListViewData") 进行刷新。我不知道该怎么做。

标签: c# wpf data-binding xaml


【解决方案1】:

接受的答案是错误的,如 cmets 中所述。我编写了以下实现所需功能的行为:

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>

【讨论】:

【解决方案2】:

您可以使用 Dictionary 创建一个新类(例如,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 允许您将子表达式放在绑定路径中的索引器中,这对我来说是个新闻——虽然不是很好!)

【讨论】:

  • 我还应该补充一点,您可以绑定到实现 INotifyCollectionChanged (msdn.microsoft.com/en-us/library/…) 的对象,或者使用 ObservableCollection,而不是使用 NotifyPropertyChanged()。不确定它不会导致同样的问题!
  • 酱汁真棒!喜欢这个!谢谢!我将把它用于同一件事的 TreeView。最好的部分是您可以存储的不仅仅是 IsExpanded。我实际上将使用它来存储有关基于分组的自动创建节点的 Viewmodel 信息。
  • 如果这行得通就好了,但不幸的是,据我所知,它没有。至少,不在 .NET 4.5 中。索引器中的子表达式作为字符串传递给索引器,而不是求值。
  • 投票者中是否有人真正尝试过此代码?这是行不通的。正如@Mark 已经提到的,这会将 [...] 中的所有内容作为字符串传递给索引器。索引器中的子表达式在 WPF 中是不可能的。另请参阅此问题:stackoverflow.com/questions/4511619/wpf-bind-to-indexer
  • 在 2 个单独的项目中为我工作。它可能是 .NET 4.5 的东西?
【解决方案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 一样简单有效

【讨论】:

  • 我理解正确吗,您想将属于列表本身的“IsExpanded”属性保存到列表中的第一项?如果列表为空或第一项被删除怎么办?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多