none
preserving Expander's expanded property after a custom sort? RRS feed

  • Question

  • I have a listview that uses Expanders in a ListView groupstyle and also has a custom sort that will need to be applied dynamically.  Right now whenever I re-sort the expanders return to their default expanded state.  I want the individual Expanders to retain their IsExpanded property they had before sorting.  I assume I'm going to have to do some sort of nasty Binding magic?

    <Window x:Class="Grouping.ExpandGroupsWithSort" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:cc="clr-namespace:Grouping" 
        Title="ExpandGroupsWithSort" Height="300" Width="300">  
        <StackPanel> 
            <Button Click="Button_Click">Sort</Button> 
            <ListView x:Name="listView" ItemsSource="{Binding}">  
                <ListView.View> 
                    <GridView> 
                        <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>  
                        <GridViewColumn Header="Class" DisplayMemberBinding="{Binding Class}"/>  
                    </GridView> 
                </ListView.View> 
                <ListView.GroupStyle> 
                    <GroupStyle> 
                        <GroupStyle.ContainerStyle> 
                            <Style TargetType="{x:Type GroupItem}">  
                                <Setter Property="Template">  
                                    <Setter.Value> 
                                        <ControlTemplate TargetType="{x:Type GroupItem}">  
                                            <Expander> 
                                                <Expander.Header> 
                                                    <StackPanel Orientation="Horizontal">  
                                                        <TextBlock Text="{Binding Name}" Margin="3,0,0,0"/>  
                                                    </StackPanel> 
                                                </Expander.Header> 
                                                <Expander.Content> 
                                                    <ItemsPresenter Margin="0,0,0,0"/>  
                                                </Expander.Content> 
                                            </Expander> 
                                        </ControlTemplate> 
                                    </Setter.Value> 
                                </Setter> 
                            </Style> 
                        </GroupStyle.ContainerStyle> 
                    </GroupStyle> 
                </ListView.GroupStyle> 
            </ListView> 
        </StackPanel> 
    </Window> 

     

    using System;  
    using System.Collections.Generic;  
    using System.Linq;  
    using System.Text;  
    using System.Windows;  
    using System.Windows.Controls;  
    using System.Windows.Data;  
    using System.Windows.Documents;  
    using System.Windows.Input;  
    using System.Windows.Media;  
    using System.Windows.Media.Imaging;  
    using System.Windows.Navigation;  
    using System.Windows.Shapes;  
    using System.Globalization;  
    using System.Collections.ObjectModel;  
     
    namespace Grouping  
    {  
       public partial class ExpandGroupsWithSort : Window  
       {  
          CollectionViewSource source = new CollectionViewSource();  
          public ExpandGroupsWithSort ()  
          {  
             InitializeComponent();  
             List<DataEntry> data = new List<DataEntry>()   
            {  
                new DataEntry(){Name="Entry1", Class="A"},  
                new DataEntry(){Name="Entry2", Class="A"},  
                new DataEntry(){Name="Entry3", Class="C"},  
                new DataEntry(){Name="Entry4", Class="B"},  
                new DataEntry(){Name="Entry5", Class="B"},  
            };            
             source.Source = data;  
             source.GroupDescriptions.Add( new PropertyGroupDescription( "Class" ) );  
             this.DataContext = source;  
          }  
     
          private void Button_Click ( object sender, RoutedEventArgs e )  
          {  
             source.View.Refresh();  
             ListCollectionView lcv = source.View as ListCollectionView;  
             if ( lcv != null )  
             {  
                if ( lcv.CustomSort == null )  
                   lcv.CustomSort = new DataSorter();  
             }  
     
          }  
       }  
     
       public class DataEntry  
       {  
          public string Name { getset; }  
     
          public string Class { getset; }  
       }  
     
       class DataSorter : System.Collections.IComparer  
       {
          #region IComparer Members  
     
          public int Compare ( object x, object y )  
          {  
             DataEntry lhs = x as DataEntry;  
             DataEntry rhs = y as DataEntry;  
     
             if ( lhs == null || rhs == null )  
                return 0;  
     
             return lhs.Class.CompareTo( rhs.Class );  
       
          }
          #endregion  
       }  
    }  
     


     

    • Edited by kbeal2k Tuesday, October 14, 2008 8:37 PM
    Tuesday, October 14, 2008 8:22 PM

All replies

  • -> I assume I'm going to have to do some sort of nasty Binding magic?

    Nope, define a property called IsExpanded at the data object class, and have it data bind to the IsExpanded property of the grouping Expander.

    Thanks
    Friday, October 17, 2008 5:19 AM
  • I think our definition of nasty Binding magic is different :)

    Nevertheless I tried implementing what I think you are suggesting and get the following error:

    System.Windows.Data Error: 39 : BindingExpression path error: 'IsExpanded' property not found on 'object' ''CollectionViewGroupInternal' (HashCode=5822459)'. BindingExpression:Path=IsExpanded; DataItem='CollectionViewGroupInternal' (HashCode=5822459); target element is 'Expander' (Name=''); target property is 'IsExpanded' (type 'Boolean')

    Here is what I changed:

    <Expander IsExpanded="{Binding IsExpanded, Mode=TwoWay}"

    And then changed "source" to be a new class ExpandableCVS:
       public class ExpandableCVS : CollectionViewSource, INotifyPropertyChanged  
       {  
          private bool _isExpanded;  
     
          public bool IsExpanded  
          {  
             get { return _isExpanded; }  
             set 
             {  
                _isExpanded = value;  
                OnPropertyChanged( "IsExpanded" );  
             }  
          }
          #region INotifyPropertyChanged Members  
     
          public event PropertyChangedEventHandler PropertyChanged;  
     
          protected void OnPropertyChanged ( string propertyName )  
          {  
             if ( this.PropertyChanged != null )  
                PropertyChanged( thisnew PropertyChangedEventArgs( propertyName ) );  
          }
          #endregion  
       } 
    Monday, October 20, 2008 3:08 PM
  • I wrote up a little sample on how to implement this type of feature using my naive View+ViewModel+Model pattern:

    http://cid-e9ffaf5298e5a301.skydrive.live.com/self.aspx/Documents/WPF/CodeSamples/ListViewGroupingStateDemo.zip

    Hope this helps
    • Marked as answer by Marco Zhou Tuesday, October 21, 2008 10:51 AM
    • Unmarked as answer by kbeal2k Friday, October 24, 2008 7:11 PM
    Tuesday, October 21, 2008 2:59 AM
  •  IsExpanded="{Binding Path=Items[0].IsExpanded}" was the key I was looking for, thanks.
    Tuesday, October 21, 2008 8:37 PM
  • I've run into a strange problem.  I've altered the sample posted to change what group an entry belongs to upon clicking on it (in this case, "OR").    Ocassionally an expanded group will collapse after clicking an entry and hitting the refresh button.  This doesn't happen every time but it happens often and I can't figure out what is causing it.

    Changed code is as follows:

        public partial class PlacesView : UserControl  
        {  
            public PlacesView()  
            {  
                InitializeComponent();  
                button.Click += delegate 
                {  
                    //lb.Items.Refresh();  
                   CollectionViewSource view = (CollectionViewSource)FindResource( "cvs" );  
                   view.View.Refresh();  
     
                };  
            }  
     
            void ListBoxItem_OnPreviewMouseItem ( object sender, RoutedEventArgs args )  
            {  
               ListBoxItem item = sender as ListBoxItem;  
     
               ExpandableViewModel<Place> vm = item.Content as ExpandableViewModel<Place>;  
     
               vm.DataModel.State = "OR";  
     
            }  
        } 


    Added a ListBox.ItemContainerStyle as follows

    <UserControl x:Class="ListViewGroupingStateDemo.PlacesView" 
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase" 
        xmlns:src="clr-namespace:ListViewGroupingStateDemo" 
        xmlns:dat="clr-namespace:System.Windows.Data;assembly=PresentationFramework">  
      <UserControl.Resources> 
        <src:PlacesViewModel x:Key="places"/>  
        <CollectionViewSource Source="{StaticResource places}" x:Key="cvs">  
          <CollectionViewSource.SortDescriptions> 
            <scm:SortDescription PropertyName="DataModel.CityName"/>  
          </CollectionViewSource.SortDescriptions> 
          <CollectionViewSource.GroupDescriptions> 
            <dat:PropertyGroupDescription PropertyName="DataModel.State"/>  
          </CollectionViewSource.GroupDescriptions> 
        </CollectionViewSource> 
      </UserControl.Resources> 
      <DockPanel> 
        <Button Width="120" Height="30" Content="Refresh" x:Name="button" DockPanel.Dock="Top"/>  
        <ListBox ItemsSource="{Binding Source={StaticResource cvs}}" DisplayMemberPath="DataModel.CityName" Name="lb">  
          <ListBox.GroupStyle> 
            <GroupStyle> 
              <GroupStyle.ContainerStyle> 
                <Style TargetType="{x:Type GroupItem}">  
                  <Setter Property="Margin" Value="0,0,0,5"/>  
                  <Setter Property="Template">  
                    <Setter.Value> 
                      <ControlTemplate TargetType="{x:Type GroupItem}">  
                        <!--Bind the IsExpanded property of Expander to the data object's IsExpanded property--> 
                        <Expander IsExpanded="{Binding Path=Items[0].IsExpanded}" 
                                      BorderBrush="#FFA4B97F" 
                                      BorderThickness="0,0,0,1">  
                          <Expander.Header> 
                            <DockPanel> 
                              <TextBlock FontWeight="Bold" Text="{Binding Path=DataModel.Name}" 
                                         Margin="5,0,0,0" Width="100"/>  
                              <TextBlock FontWeight="Bold" 
                                         Text="{Binding Path=ItemCount}"/>  
                            </DockPanel> 
                          </Expander.Header> 
                          <Expander.Content> 
                            <ItemsPresenter /> 
                          </Expander.Content> 
                        </Expander> 
                      </ControlTemplate> 
                    </Setter.Value> 
                  </Setter> 
                </Style> 
              </GroupStyle.ContainerStyle> 
            </GroupStyle> 
          </ListBox.GroupStyle> 
                <ListBox.ItemContainerStyle> 
                    <Style TargetType="{x:Type ListBoxItem}">  
                        <EventSetter Event="PreviewMouseDown" Handler="ListBoxItem_OnPreviewMouseItem"/>  
                    </Style> 
                </ListBox.ItemContainerStyle> 
            </ListBox> 
      </DockPanel> 
    </UserControl> 
     
    Friday, October 24, 2008 7:16 PM
  • When you move the item into another group, this item's expansion state will be propagated to the new containing group which might not be what you need, so basically speaking, the solution I posted above doesn't support the scenario of moving items to groups as you want to implement.

    Hope this clears things up a little bit.
    Monday, October 27, 2008 8:13 AM
  • If the expansion state is being propagated, then why do groups sometimes collapse after I have expanded all three groups?

    If that isn't solution, then does anyone have one?
    • Edited by kbeal2k Monday, October 27, 2008 2:30 PM added
    Monday, October 27, 2008 2:29 PM
  • Idea behind your answer is great - to have items inside group to be able to manipulate group expander state. Though the idea may not seem very clean, but it provides a working solution with minimum modifications.

    Great answer!

    Wednesday, March 27, 2019 5:41 PM