1

I have some items that I want to show to the user in a WPF window, but I want to hide the details of each item until the user selects/expands the item. I'd like to achieve Windows-Explorer-esque functionality, where the item Header is always displayed, and the item Details (as an ItemsControl) are displayed when the user clicks on the item's arrow.

Is there an obvious way to do this? Or am I going to have to whip out a custom control?

Joseph
  • 903
  • 1
  • 10
  • 25

3 Answers3

0

This can be done with the standard WPF Treeview. Josh Smith's article on Simplifying the WPF TreeView includes a sample showing how to provide lazy-loading of the data for the subitems within each TreeViewItem.

The basic approach is to make a "dummy" child of each item in the ViewModel, and track the expanded state of each TreeViewItem. As a TreeViewItem is expanded, the dummy child is removed and replaced with the real data.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Thank you, that is the appearance I want. The children, however, are actually `TreeViewItem`, which means they are individually selectable. How do I get the entire package - parent and details - to be one selectable unit? – Joseph Jul 12 '13 at 14:58
  • 1
    @Joseph You can make them non-hierarchical, and just use a ListView instead of a TreeView, in that case. Each ListView can just be handled via an expander, and fill in it's children.. – Reed Copsey Jul 12 '13 at 16:51
  • Yeah, I would rather it look/function like the `TreeView` (click on little arrow to expand Details), but I might be just being picky. It's unfortunate that `Expander` is so difficult to [customize](http://stackoverflow.com/questions/250788/how-to-modify-expander-button-background-only-wpf). – Joseph Jul 12 '13 at 17:22
0

If you are just looking to automatically expand/collapse items then you probably want a trigger. For example you can use an expander and automatically Expand it when the mouse is over it.

                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter Property="IsExpanded" Value="True"/>
                                    </Trigger>
                                </ControlTemplate.Triggers>

And of course this can be repeated for other triggers that you want to set it to be open

        <Expander Header="Hello" IsExpanded="False">
            <Border Background="Red" Height="32"/>
            <Expander.Style>
                <Style TargetType="Expander">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="Expander">
                                <StackPanel>
                                    <ContentPresenter Content="{TemplateBinding Header}"/>
                                    <ContentPresenter x:Name="expander" Content="{TemplateBinding Content}" Visibility="Collapsed"/>
                                </StackPanel>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter TargetName="expander" Property="Visibility" Value="Visible"/>
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>                                
                </Style>
            </Expander.Style>
        </Expander>

If you don't need a full tree like UI then it would probably be sufficient to just have a list of these, but I'm pretty sure you can also use the same trick on a treeviewitem too.

AlSki
  • 6,868
  • 1
  • 26
  • 39
  • Apologies this is a really poor example and not making any use of the expander, but it does show the trigger idea. I'll update in the morning to something a little better. – AlSki Jul 12 '13 at 00:29
  • This is a really neat idea, but it might throw the user off if the items are, for example, stacked vertically and the expand/collapse feature is also vertical. If they're trying to move down the list, it would be pretty easy to expand many items they didn't want to. – Joseph Jul 12 '13 at 12:43
  • Actually, since the mouse can only ever be over one control at a time, you'll end up with the same action as the AJAX accordion control. :-) – AlSki Jul 14 '13 at 09:33
0

I took the ListBox of Expanders path, and I am pretty pleased with the results.

Here's how my final code ended up:

Control Template for Expander Button

        <ControlTemplate x:Key="TreeViewToggleButton" TargetType="{x:Type ToggleButton}">
            <Border x:Name="ToggleButtonBorder"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    Padding="{TemplateBinding Padding}">
                <Grid>
                    <Rectangle Fill="Transparent"/>

                    <Path x:Name="Arrow"
                          Height="10" Width="10"
                          Stroke="Black"
                          Data="m 2 1 v 8 l 4 -3.75 Z">
                    </Path>
                </Grid>
            </Border>

            <ControlTemplate.Triggers>
                <Trigger Property="IsChecked" Value="True">
                    <Setter TargetName="Arrow" Property="Data" Value="m 2 9 h 5 v -5 Z"/>
                    <Setter TargetName="Arrow" Property="Fill" Value="Black"/>
                </Trigger>

                <Trigger Property="IsMouseOver" Value="True">
                    <Setter TargetName="Arrow" Property="Stroke" Value="#00A7C2"/>
                </Trigger>

                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="IsMouseOver" Value="True"/>
                        <Condition Property="IsChecked" Value="True"/>
                    </MultiTrigger.Conditions>

                    <Setter TargetName="Arrow" Property="Fill" Value="#00A7C2"/>
                </MultiTrigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

Control Template for Expander

        <ControlTemplate x:Key="TreeViewExpander" TargetType="{x:Type Expander}">
            <DockPanel>
                <Grid DockPanel.Dock="Top">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>

                    <ToggleButton x:Name="ExpanderButton"
                                  Grid.Column="0"
                                  Template="{StaticResource TreeViewToggleButton}"
                                  IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                                  OverridesDefaultStyle="True"
                                  Padding="2, 0" />
                    <Label Grid.Column="1" Content="{TemplateBinding Header}"
                           Padding="0, 1"/>
                </Grid>

                <ContentPresenter x:Name="ExpanderContent"
                                  Visibility="Collapsed"
                                  DockPanel.Dock="Bottom"/>
            </DockPanel>

            <ControlTemplate.Triggers>
                <Trigger Property="IsExpanded" Value="True">
                    <Setter TargetName="ExpanderContent" Property="Visibility" Value="Visible"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

Usage in Window

    <ListBox Grid.Row="1" DataContext="{Binding Inputs}" ItemsSource="{Binding}" ScrollViewer.CanContentScroll="False">
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="ContentTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <Expander Template="{StaticResource TreeViewExpander}"
                                      IsExpanded="{Binding IsExpanded}">
                                <Expander.Header>
                                    <TextBlock Text="{Binding Timestamp, StringFormat=Time: {0}}"/>
                                </Expander.Header>
                                <ItemsControl ItemsSource="{Binding Variables}" Margin="30 0 0 0"/>
                            </Expander>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>

                <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
Joseph
  • 903
  • 1
  • 10
  • 25