Search code examples
wpflistviewexpander

Listview in Listview + Expander spilling out of outer container


So I have an expander containing a listview within a listview, here is a schema:

enter image description here

Xaml:

<UserControl x:Class="Sesam.Resources.CommonControls.FilterPanelView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:Sesam.Resources.CommonControls"
         xmlns:filters="clr-namespace:Sesam.Filters"
         mc:Ignorable="d" 
         d:DataContext="{d:DesignInstance local:FilterPanelViewModel}">
<UserControl.Resources>
    <ControlTemplate x:Key="NoScroll">
        <ItemsPresenter></ItemsPresenter>
    </ControlTemplate>


</UserControl.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition x:Name="ListViewGridRowDefinition" Height="*"/>
    </Grid.RowDefinitions>
    <Border Grid.Row="0" Background="Transparent" BorderThickness="0,0,0,1" BorderBrush="{StaticResource myLightGrey}">
        <DockPanel VerticalAlignment="Center">
            <Label Content="Filtres" DockPanel.Dock="Left" Foreground="{StaticResource myDarkBlue}" FontSize="14" FontWeight="SemiBold"/>
            <!-- future reset button -->
        </DockPanel>
    </Border>
    <Grid Grid.Row="1">
        <ListView BorderThickness="1" ItemsSource="{Binding FilterCollection}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Disabled"  >
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
                    <Setter Property="OverridesDefaultStyle" Value="True"/>
                    <Setter Property="SnapsToDevicePixels" Value="True"/>
                    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                    <Setter Property="VerticalContentAlignment" Value="Stretch"/>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                <Grid Background="Transparent">
                                    <Expander BorderThickness="2"  Style="{StaticResource SesamExpanderFiltres}" Header="{Binding Title}" Foreground="White">
                                        <ListView   BorderThickness="0" ItemsSource="{Binding Filters}" SelectedItem="{Binding SelectedFilter}"  SelectedIndex="{Binding SelectedIndex}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Visible" SelectionChanged="Selector_OnSelectionChanged" VirtualizingPanel.ScrollUnit="Pixel" >
                                            <ListView.ItemContainerStyle>
                                                <Style TargetType="ListViewItem">
                                                    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
                                                    <Setter Property="OverridesDefaultStyle" Value="True"/>
                                                    <Setter Property="SnapsToDevicePixels" Value="True"/>
                                                    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                                                    <Setter Property="VerticalContentAlignment" Value="Stretch"/>
                                                    <Setter Property="Template">
                                                        <Setter.Value>
                                                            <ControlTemplate TargetType="{x:Type ListViewItem}">
                                                                <Border Height="Auto" Name="ContentBorder"  BorderBrush="{StaticResource myLightGrey}" BorderThickness="0,0,0,1" Visibility="{Binding IsVisible, Converter={StaticResource BoolToCollapsed}}" >
                                                                    <Grid>
                                                                        <Grid.ColumnDefinitions>
                                                                            <ColumnDefinition Width="15" />
                                                                            <ColumnDefinition Width="*"   />
                                                                        </Grid.ColumnDefinitions>
                                                                        <Grid Name="selectCol" Grid.Column="0" Background="White" />
                                                                        <Label Grid.Column="1" Foreground="{StaticResource myDarkBlue}" Content="{Binding Name}" />
                                                                    </Grid>
                                                                </Border>
                                                                <ControlTemplate.Triggers>
                                                                    <Trigger Property="IsMouseOver" Value="True">
                                                                        <Setter Property="Background" TargetName="selectCol"  Value="{StaticResource myDarkBlue}" />
                                                                        <Setter Property="BorderBrush" TargetName="ContentBorder" Value="{StaticResource myDarkBlue}" />
                                                                    </Trigger>
                                                                </ControlTemplate.Triggers>
                                                            </ControlTemplate>
                                                        </Setter.Value>
                                                    </Setter>
                                                </Style>
                                            </ListView.ItemContainerStyle>
                                        </ListView>
                                    </Expander>
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListView.ItemContainerStyle>
        </ListView>
    </Grid>
</Grid>

My goal is to have the "expanders" and lists contained within, to share the space in the container grid (I left the first listview border on to see that it is in fact taking up the correct amount of space):

enter image description here

But when I expand one of the expanders it spills out of the first listview as you can see here, the second expander is overflowing and of course the scroll bars are not functioning:

enter image description here

I would like the expanders to stack at the bottom so they remain visible and for the expanded expander/list to takeup the remaining space and have the user use the inner scrollbars to scroll through the list contained within the expander.

Expected Result:

enter image description here

I have seen how to do it in a previous post with fixed grid heights but my list of expanders is bound to a collection so that solution does not work for me. I have been fighting for hours to get this to work, wondering if an outside observer will see the error I am making.


Solution

  • Actually since the expander header height never varies (I can set it to a fixed height) and i can get the grid.row "actualheight" AND i needed for all other expanders to be collapsed when one is open I did this in the code behind:

        private void Expander_Expanded(object sender, RoutedEventArgs e)
        {
    
            var exp = sender as Expander;
            if (exp != null)
            {
                exp.MaxHeight = ListViewGridRowDefinition.ActualHeight -  (ContainerListView.Items.Count*31) + 28;
            }
        }
    
    
        private void Expander_Collapsed(object sender, RoutedEventArgs e)
        {
            var exp = sender as Expander;
            if (exp != null)
            {
                exp.MaxHeight = 31;
            }
        }
    

    And this in the xaml:

    <Expander Height="Auto" IsExpanded="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}".....
    

    Also to fix a side effect that came out of using this solution (container scrolling still enabled) I applied a template to the container listview to remove scroll behabiour:

        <UserControl.Resources>
        <ControlTemplate x:Key="NoScroll">
            <ItemsPresenter></ItemsPresenter>
        </ControlTemplate>
    </UserControl.Resources>
    

    ...

        <ListView x:Name="ContainerListView" Template="{StaticResource NoScroll}"
    

    I am using the mvvm pattern but I consider this pure interface interaction so model has no need to know about this, it is fine in codebehind. And it will work for all further uses in MY application. For "allround" reuse a custom control was a good solution, but too much work for my use IMO.