Tor Thorbergsen
Tor Thorbergsen

Reputation: 693

How to make a listboxitem expand on selection?

I have a situation where I want the user to make choices in a certain order (first I want the user to select the database, then I want him to tell me his credentials).

To do this I have challenged myself with the task of making a listbox that expands the item on selection. To make it expand is not difficult (something like

Visibility="{Binding Path=IsSelected
             , RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}
             , Converter={StaticResource VisibilityOfBool}
             , ConverterParameter=False}"

will do the trick).

The problem is that the transition is instantaneous; it is hard for the user to see what happened. What I should like to do is an animated expansion of the hidden panel...

Here is a page to demonstrate what I mean:

<Page 
      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:system="clr-namespace:System;assembly=mscorlib"
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"
    Title="SlidingExpansionOnSelected">
    <!--x:Class="TorsSandBox.Pages.SlidingExpansionOnSelected"-->

    <Page.Resources>
        <DataTemplate x:Key="daItemTemplate">
            <StackPanel Margin="10">
                <StackPanel.Triggers>
                        <EventTrigger RoutedEvent="ListBoxItem.Selected">
                            <EventTrigger.Actions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation
                                        Storyboard.TargetName="daTransform"
                                        Storyboard.TargetProperty="ScaleY"
                                        To="1.0" Duration="0:0:1"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger.Actions>
                        </EventTrigger>
                        <EventTrigger RoutedEvent="ListBoxItem.Unselected">
                            <EventTrigger.Actions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation
                                        Storyboard.TargetName="daTransform"
                                        Storyboard.TargetProperty="ScaleY"
                                        To="0" Duration="0:0:1"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger.Actions>
                        </EventTrigger>
                </StackPanel.Triggers>
                <TextBlock x:Name="Header" Text="{Binding}"/>
                <Border x:Name="daBorder"
                    BorderThickness="3" BorderBrush="DarkOrange" CornerRadius="5"
                    HorizontalAlignment="Stretch"
                    Margin="20 10 10 10"
                    MinHeight="100"
                    >
                    <Border.LayoutTransform>
                        <ScaleTransform x:Name="daTransform" ScaleX="1" ScaleY="0"/>
                    </Border.LayoutTransform>
                    <TextBlock Text="Hide this until selected" 
                        HorizontalAlignment="Center" VerticalAlignment="Center" />
                </Border>
            </StackPanel>
        </DataTemplate>
    </Page.Resources>

    <ListBox
        HorizontalContentAlignment="Stretch"
        ItemTemplate="{StaticResource daItemTemplate}"
        >
        <system:String>First row</system:String>
        <system:String>Second row</system:String>
        <system:String>Third row</system:String>
        <system:String>Last row</system:String>
    </ListBox>
</Page>

The triggers doesn't work, but that's because I can't make them fire...Anybody knows how to make this happen?

Regards Tor Thorbergsen

Upvotes: 0

Views: 4036

Answers (3)

ColinE
ColinE

Reputation: 70142

You should not need the ListBoxItem prefix on your events, just use "Selected" and "Unselected".

Another alternative to try is a property trigger:

https://web.archive.org/web/20120225232943/http://en.csharp-online.net/WPF_Styles_and_Control_Templates%E2%80%94Property_Triggers

Upvotes: 0

JobaDiniz
JobaDiniz

Reputation: 1013

EDIT:

Ok, I've figure it out. I've created an attached property for this (instead of using Tag).

public static class ListBoxHelper
{
    public static readonly DependencyProperty ScaleYAnimationProperty =
        DependencyProperty.RegisterAttached("ScaleYAnimation", typeof(double), typeof(ListBoxHelper), new FrameworkPropertyMetadata(0d));

    public static double GetScaleYAnimation(UIElement element)
    {
        return (double)element.GetValue(ScaleYAnimationProperty);
    }

    public static void SetScaleYAnimation(UIElement element, double value)
    {
        element.SetValue(ScaleYAnimationProperty, value);
    }
}

 <ListBox ItemsSource="{Binding Contacts}" HorizontalContentAlignment="Stretch">
        <ListBox.Resources>
            <Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
                <Setter Property="controls:ListBoxHelper.ScaleYAnimation" Value="0"/>
                <Setter Property="ContentTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <StackPanel>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="40"/>
                                        <ColumnDefinition Width="*"/>
                                        <ColumnDefinition Width="Auto"/>
                                        <ColumnDefinition Width="100"/>
                                    </Grid.ColumnDefinitions>

                                    <controls:CircleContentControl Grid.Column="0" Width="40" Height="40">
                                        <Image Source="{Binding Image}"/>
                                    </controls:CircleContentControl>
                                    <TextBlock Text="{Binding FullName}" Grid.Column="1" Margin="10,0,5,0" VerticalAlignment="Center"/>
                                    <TextBlock Text="{Binding PhoneNumber}" Grid.Column="2" VerticalAlignment="Center" FontStyle="Italic">
                                        <TextBlock.LayoutTransform>
                                            <ScaleTransform ScaleX="0.7" ScaleY="0.7"/>
                                        </TextBlock.LayoutTransform>
                                    </TextBlock>
                                    <StackPanel Orientation="Horizontal" Grid.Column="3" VerticalAlignment="Center" HorizontalAlignment="Center">
                                        <Button cal:Message.Attach="Call($dataContext)" Width="30" Height="30" Style="{StaticResource ContactDialButtonStyle}">
                                            <Rectangle Width="10" Height="10" Fill="{Binding Path=Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}">
                                                <Rectangle.OpacityMask>
                                                    <VisualBrush Stretch="Fill" Visual="{DynamicResource appbar_phone}" />
                                                </Rectangle.OpacityMask>
                                            </Rectangle>
                                        </Button>
                                    </StackPanel>
                                </Grid>
                                <Border x:Name="daBorder"
                                BorderThickness="3" BorderBrush="DarkOrange" CornerRadius="5"
                                HorizontalAlignment="Stretch"
                                Margin="20 10 10 10"
                                MinHeight="100">
                                    <Border.LayoutTransform>
                                        <ScaleTransform ScaleX="1" ScaleY="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=(controls:ListBoxHelper.ScaleYAnimation)}"/>
                                    </Border.LayoutTransform>
                                    <TextBlock Text="Hide this until selected"  HorizontalAlignment="Center" VerticalAlignment="Center" />
                                </Border>
                            </StackPanel>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
                <Style.Triggers>
                    <EventTrigger RoutedEvent="ListBoxItem.Selected">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation
                                Storyboard.TargetProperty="(controls:ListBoxHelper.ScaleYAnimation)"
                                Storyboard.TargetName="{x:Null}"
                                To="1.0" Duration="0:0:1"/>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                    <EventTrigger RoutedEvent="ListBoxItem.Unselected">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation
                                Storyboard.TargetProperty="(controls:ListBoxHelper.ScaleYAnimation)"
                                Storyboard.TargetName="{x:Null}"
                                To="0.0" Duration="0:0:1"/>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                </Style.Triggers>
            </Style>
        </ListBox.Resources>
    </ListBox> 

I'm using @H.B solution. It works the first time the list is loaded. However, if I expand one listboxitem, switch to another tab and go back to the tab where listbox is, an exception is thrown:

System.Windows.Data Error: 23 : Cannot convert '<null>' from type '<null>' to type 'System.Double' for 'en-US' culture with default conversions; consider using Converter property of Binding. NotSupportedException:'System.NotSupportedException: DoubleConverter não pode ser convertido de (nulo).
   em System.ComponentModel.TypeConverter.GetConvertFromException(Object value)
   em System.ComponentModel.TypeConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
   em System.ComponentModel.BaseNumberConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
   em MS.Internal.Data.DefaultValueConverter.ConvertHelper(Object o, Type destinationType, DependencyObject targetElement, CultureInfo culture, Boolean isForward)'

System.Windows.Data Error: 6 : 'ObjectSourceConverter' converter failed to convert value '<null>' (type '<null>'); fallback value will be used, if available. BindingExpression:Path=Tag; DataItem='ListBoxItem' (Name=''); target element is 'ScaleTransform' (HashCode=48000142); target property is 'ScaleY' (type 'Double') NotSupportedException:'System.NotSupportedException: DoubleConverter não pode ser convertido de (nulo).
   em MS.Internal.Data.DefaultValueConverter.ConvertHelper(Object o, Type destinationType, DependencyObject targetElement, CultureInfo culture, Boolean isForward)
   em MS.Internal.Data.ObjectSourceConverter.Convert(Object o, Type type, Object parameter, CultureInfo culture)
   em System.Windows.Data.BindingExpression.ConvertHelper(IValueConverter converter, Object value, Type targetType, Object parameter, CultureInfo culture)'

Anyone having this too?

Upvotes: 0

brunnerh
brunnerh

Reputation: 184947

This stuff is way too complicated...
What's wrong with your approach is that the animation only affects elements lower on the VisualTree, that means the container is not affected as far as i know.
Specifying the property path in animations is a major pain, i say. I could not access the border inside the StackPanel because its Children property is not a dependency property and the whole syntax is rather unusual.

Anyway, here's a hacky solution, that works:

        <Style TargetType="ListBoxItem">
            <Style.Resources>
                <Storyboard x:Key="OnSelected1"/>
            </Style.Resources>
            <Setter Property="Tag">
                <Setter.Value>
                    <sys:Double>0</sys:Double>
                </Setter.Value>
            </Setter>
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <StackPanel x:Name="stackPanel" Margin="10">
                            <TextBlock x:Name="Header" Text="{Binding}"/>
                            <Border x:Name="daBorder"
                                    BorderThickness="3" BorderBrush="DarkOrange" CornerRadius="5"
                                    HorizontalAlignment="Stretch"
                                    Margin="20 10 10 10"
                                    MinHeight="100">
                                <Border.LayoutTransform>
                                    <ScaleTransform ScaleX="1" ScaleY="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=Tag}"/>
                                </Border.LayoutTransform>
                                <TextBlock Text="Hide this until selected"  HorizontalAlignment="Center" VerticalAlignment="Center" />
                            </Border>
                        </StackPanel>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <EventTrigger RoutedEvent="ListBoxItem.Selected">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="(ListBoxItem.Tag)"
                                    Storyboard.TargetName="{x:Null}"
                                    To="1.0" Duration="0:0:1"/>
                            </Storyboard>
                        </BeginStoryboard>
                        <BeginStoryboard Storyboard="{StaticResource OnSelected1}"/>
                    </EventTrigger.Actions>
                </EventTrigger>
                <EventTrigger RoutedEvent="ListBoxItem.Unselected">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    Storyboard.TargetProperty="(ListBoxItem.Tag)"
                                    Storyboard.TargetName="{x:Null}"
                                    To="0" Duration="0:0:1"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Style.Triggers>
        </Style>

I've tried to extract the ScaleTransform and reference it both in the animation and in the Border but that did not work for whatever reason.

Upvotes: 3

Related Questions