Reputation: 693
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
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:
Upvotes: 0
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
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