none
GroupStyle and Expander: saving IsExpanded state in Data Model RRS feed

  • Question

  • Hello!

    I'm doing some research but I am not able to find a solution to my problem.

    I have a ViewModel with this property

     public ICollectionView Inputs
    {
        get
            {
    
                    m_CollectionViewSource.Source = m_InputsCollection;
                    m_ICollectionView = m_CollectionViewSource.View;
                    m_ICollectionView.GroupDescriptions.Add(new PropertyGroupDescription("GroupingCategoryName"));
                    m_ICollectionView.GroupDescriptions.Add(new PropertyGroupDescription("SubGroupingCategoryName"));
                    
    
                    return m_ICollectionView;
            }
         }
    }

    m_InputsCollection is an ObservableCollection of view models that have a property "IsExpanded"

    m_ICollectionView is of type ICollectionView

    m_CollectionViewSource is of type CollectionViewSource

    I have a UserControl in which I bind to the Inputs

    <Grid DataContext="{Binding Path=Inputs}">
            <!--right side of the page with active content-->
            <Grid>
                <ScrollViewer>
                     <ContentControl Content="{Binding  Path=Inputs}" ContentTemplate="{StaticResource InputsTemplate}"/>
                </ScrollViewer>    
            </Grid>
    </Grid>

    Then I have all my styles and templates in a ResourceDictionary:

    1)Template for the Parent container

    <DataTemplate x:Key="InputsTemplate" x:Name="InputsTemplate">
            <Grid>
                <ItemsControl
                    ItemsSource="{Binding}"
                     ItemTemplate="{StaticResource ViewModelChildTemplate}">
                    <ItemsControl.GroupStyle>
                        <GroupStyle ContainerStyle="{StaticResource ContainerStyle}"/>
                    </ItemsControl.GroupStyle>
                </ItemsControl>
            </Grid>
        </DataTemplate>

    2)The Group style:

    <Style x:Key="ContainerStyle" TargetType="{x:Type GroupItem}">
         <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                     <Expander Header="{Binding Name}" IsExpanded="{Binding Items[0].IsExpanded, diag:PresentationTraceSources.TraceLevel=High}">
                            <ItemsPresenter />
                      </Expander>
                  </ControlTemplate>
            </Setter.Value>
         </Setter>
    </Style>

    3) Style for the Expander:

        <Style TargetType="{x:Type Expander}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Expander}">
                        <!--Grid containing the expander and the content to be expanded-->
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="{StaticResource ExpanderRowHeight}" />
                                <RowDefinition x:Name="ContentRow" Height="0" />
                            </Grid.RowDefinitions>
                            <!-- Border for the header group-->
                            <Border x:Name="Border"
                                  Grid.Row="0"
                                  BorderThickness="1"
                                  Margin="{StaticResource HeaderGroupMargin}"
                                  CornerRadius="2,2,0,0">
    
                                <Grid>
                                    <!-- toggle button expander-->
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="*" />
                                        
                                    </Grid.ColumnDefinitions>
                                    <ToggleButton VerticalAlignment="Center"
                                                  HorizontalAlignment="Stretch"
                                                  HorizontalContentAlignment="Stretch"
                                                  Margin="{StaticResource HeaderToggleButtonMargin}"
                                                  OverridesDefaultStyle="True"
                                                  Template="{StaticResource ExpanderToggleButton}"
                                                  IsChecked="{Binding IsExpanded, Mode=TwoWay, 
                                                  RelativeSource={RelativeSource TemplatedParent}}">
                                        <ToggleButton.Background>
                                            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                                <GradientStop Color="{DynamicResource ControlLightColor}" Offset="0" />
                                                <GradientStop Color="{DynamicResource ControlMediumColor}" Offset="1" />
                                            </LinearGradientBrush>
                                        </ToggleButton.Background>
                                    </ToggleButton>
                                </Grid>
                            </Border>
                            <!-- Border for the content (the children) -->
                            <Border x:Name="Content"
                                    Grid.Row="1"
                                    BorderThickness="1,0,1,1"
                                    CornerRadius="0,0,2,2">
                                <ContentPresenter Margin="4" />
                            </Border>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsExpanded" Value="True">
                                <Setter TargetName="ContentRow"
                                        Property="Height"
                                        Value="{Binding Height, ElementName=Content}" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

    The result I have is a grouping on two levels:

    The only problem I have now is preserve the state of the expander.

    As I said, m_InputsCollection is an ObservableCollection of view models  that have a property "IsExpanded"

    If I bind the expander to Items[0].IsExpanded I can see (by enabling binding trace) that the bind works for the inner items

    that are in the subgroup (that is "Filters" "Domain" "Advanced" in the picture above) but for the "Force" I got a binding error:

    BindingExpression path error: 'IsExpanded' property not found on 'object' ''CollectionViewGroupInternal' (HashCode=19753106)'. BindingExpression:Path=Items[0].IsExpanded; DataItem='CollectionViewGroupInternal' (HashCode=53740895); target element is 'Expander' (Name=''); target property is 'IsExpanded' (type 'Boolean')

    I don't understand what's going on :/


    The full trace of the binding is here:

    https://pastebin.com/raw/TKEXq26a


    Thanks for your time.

    Gianpaolo


    EDIT: I know that probably the binding to Items[0].IsExpanded is not correct,

    I was just trying to understand what's happen with the trace enabled







    Tom Waits :)



    • Edited by gianpaolo72 Tuesday, May 22, 2018 2:43 PM chenge title
    Tuesday, May 22, 2018 9:07 AM

All replies

  • Hi gianpaolo72,

    >>that are in the subgroup (that is "Filters" "Domain" "Advanced" in the picture above) but for the "Force" I got a binding error:

    The reason for the success of the binding on sub group is the XAML binding can find the fist item with your custom IsExpanded property in the data context. So for the expander of the first root group("Force" in your case), it will find the IsExpanded property in the data context of the fist sub group("Filters" in your case), but, please notice that there is no such property inside its data context, only you can use is the Expander.IsExpanded control property.

    So how to fix this issue? We can use value converter in MVVM design mode.

    I wrote a value converter to traverse all sub-items, for example, if there is any sub group which has been expanded, the current expander should also be expanded:

    public class CheckExpandConverter : IValueConverter
        {
            private bool calcIsExpandedFromItems(CollectionViewGroup cvg)
            {
                var isExp = false;
                if (cvg != null && cvg.Items.Count > 0)
                {
                    foreach(var item in cvg.Items)
                    {
                        if (item is TestClass)
                        {
                            isExp |= (item as TestClass).IsExpanded;
                        }
                    }
                }
                return isExp;
            }
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (value is ReadOnlyObservableCollection<Object>)
                {
                    var isExp = false;
                    var oc = value as ReadOnlyObservableCollection<Object>;
                    foreach(var t in oc)
                    {
                        if (t is TestClass)
                            isExp |= (t as TestClass).IsExpanded;
    
                        if (t is CollectionViewGroup)
                            isExp |= calcIsExpandedFromItems(t as CollectionViewGroup);
                    }
                    return isExp;
                }
                    
                return false;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }

    We need to change the GroupItem syle as below:

    <cv:CheckExpandConverter x:Key="CheckExpandConverter" />
    
     <Style x:Key="ContainerStyle" TargetType="{x:Type GroupItem}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <Expander Header="{Binding Name}" IsExpanded="{Binding Items, Converter={StaticResource CheckExpandConverter}, Mode=OneWay}">
                                <ItemsPresenter />
                            </Expander>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>

    Screenshot:


    Here is my model:

    public class TestClass
        {
            public string Name { get; set; }
            public bool IsExpanded { get; set; }
            public string GroupingCategoryName { get; set; }
            public string SubGroupingCategoryName { get; set; }
        }

    Test collection:

    m_InputsCollection = new ObservableCollection<TestClass>() {
                    new TestClass(){ Name="Items1", IsExpanded=true, GroupingCategoryName="G1", SubGroupingCategoryName = "G1-S1"},
                    new TestClass(){ Name="Items2", IsExpanded=false, GroupingCategoryName="G1", SubGroupingCategoryName = "G1-S1"},
                    new TestClass(){ Name="Items3", IsExpanded=false, GroupingCategoryName="G1", SubGroupingCategoryName = "G1-S2"},
                    new TestClass(){ Name="Items4", IsExpanded=false, GroupingCategoryName="G1", SubGroupingCategoryName = "G1-S2"},
    
                    new TestClass(){ Name="Items5", IsExpanded=false, GroupingCategoryName="G2", SubGroupingCategoryName = "G2-S1"},
                    new TestClass(){ Name="Items6", IsExpanded=false, GroupingCategoryName="G2", SubGroupingCategoryName = "G2-S1"},
                    new TestClass(){ Name="Items7", IsExpanded=true, GroupingCategoryName="G2", SubGroupingCategoryName = "G2-S2"},
                    new TestClass(){ Name="Items8", IsExpanded=false, GroupingCategoryName="G2", SubGroupingCategoryName = "G2-S2"},
    
                    new TestClass(){ Name="Items9", IsExpanded=false, GroupingCategoryName="G3", SubGroupingCategoryName = "G3-S1"},
                    new TestClass(){ Name="Items10", IsExpanded=false, GroupingCategoryName="G3", SubGroupingCategoryName = "G3-S1"},
                    new TestClass(){ Name="Items11", IsExpanded=true, GroupingCategoryName="G3", SubGroupingCategoryName = "G3-S2"},
                    new TestClass(){ Name="Items12", IsExpanded=false, GroupingCategoryName="G3", SubGroupingCategoryName = "G3-S2"},
                };


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Wednesday, May 23, 2018 4:52 AM
    Moderator
  • Hi Chen!

    Thanks for your reply!

    Binding is working now but how can I persist IsExpanded value on the data model (in yr case would be TestClass) since Items is ReadOnly property?

    Gianpaolo


    Tom Waits :)


    • Edited by gianpaolo72 Wednesday, May 23, 2018 9:13 AM
    Wednesday, May 23, 2018 9:12 AM
  • Hi Chen!

    Thanks for your reply!

    Binding is working now but how can I persist IsExpanded value on the data model (in yr case would be TestClass) since Items is ReadOnly property?

    Gianpaolo


    Tom Waits :)


    >>but how can I persist IsExpanded value on the data model (in yr case would be TestClass) since Items is ReadOnly property

    You have set the IsExpanded property's value in your viewModel, the reason you get ReadOnly property is because of One-way binding, you don't need to persist anything in this solution. 


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Wednesday, May 23, 2018 9:16 AM
    Moderator
  • hi again

    I need to be able to update/set the IsExpanded state down till the data model, so that when the view model is re-created, the user will see the collapsed/expanded status as it was previously, that's why I wrote in the title  "saving IsExpandedState in the data model" but I did not explicitly mention it in the body of the question. Is that feasible in some way?

    Gianpaolo


    Tom Waits :)

    Wednesday, May 23, 2018 9:37 AM
  • hi again

    I need to be able to update/set the IsExpanded state down till the data model, so that when the view model is re-created, the user will see the collapsed/expanded status as it was previously, that's why I wrote in the title  "saving IsExpandedState in the data model" but I did not explicitly mention it in the body of the question. Is that feasible in some way?

    Gianpaolo


    Tom Waits :)

    The question about store the property value, I don't think there is a good way in MVVM pattern. Basically, if IsExpanded value is changed by user's operation, you have to record all changes and reset by traversing the live visual tree, check out VisualTreeHelper Class

    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Wednesday, May 23, 2018 9:44 AM
    Moderator