ScaleTransform.ScaleY UIElement without keeping space reserved

167 views Asked by At

I have a ListView whose ItemTemplate is a custom control (acts as expander) that has a toggle that is always visible and border content bellow that expands as needed.

<ControlTemplate TargetType="local:ExpanderControl">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="ExpandStateGroup">
                                <VisualState x:Name="Collapsed">
                                    <!--<Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="PART_ExpandableContent" 
                                                         Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
                                                         To="0.0"
                                                         Duration="0:0:0.2"
                                                         AutoReverse="False"
                                                         EnableDependentAnimation="True"></DoubleAnimation>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Expanded">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="PART_ExpandableContent" 
                                                         Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
                                                         To="1.0"
                                                         Duration="0:0:0.2"
                                                         AutoReverse="False"
                                                         EnableDependentAnimation="True"></DoubleAnimation>
                                    </Storyboard>-->
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="PART_ExpandableContent" 
                                                         Storyboard.TargetProperty="Height"
                                                         To="0.0"
                                                         Duration="0:0:0.2"
                                                         AutoReverse="False"
                                                         EnableDependentAnimation="True"></DoubleAnimation>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Expanded">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="PART_ExpandableContent" 
                                                         Storyboard.TargetProperty="Height"
                                                         To="100.0"
                                                         Duration="0:0:0.2"
                                                         AutoReverse="False"
                                                         EnableDependentAnimation="True"></DoubleAnimation>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <ToggleButton x:Name="PART_expanderButton" Foreground="{TemplateBinding Foreground}"
                                      Style="{StaticResource ExpanderButtonStyle}" Background="{TemplateBinding Background}"
                                      BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
                                      IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsExpanded , Mode=TwoWay}">
                            <ContentPresenter VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                              ContentTemplate="{TemplateBinding HeaderContentTemplate}" Content="{TemplateBinding Header}"
                                              Foreground="{TemplateBinding Foreground}" FontSize="{TemplateBinding FontSize}"
                                              FontWeight="{TemplateBinding FontWeight}" FontStyle="{TemplateBinding FontStyle}"
                                              Margin="{TemplateBinding Padding}"/>
                        </ToggleButton>
                        <Border Grid.Row="1" x:Name="PART_ExpandableContent" Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
                                Height="0">
                            <!--<Border.RenderTransform>
                                <ScaleTransform ScaleY="0.0" />
                            </Border.RenderTransform>-->
                            <ContentPresenter x:Name="PART_ExpandableContentPresenter" Content="{TemplateBinding Content}"
                                    ContentTemplate="{TemplateBinding ContentTemplate}">
                            </ContentPresenter>
                        </Border>
                    </Grid>
                </ControlTemplate>

I've been playing around with VisualState trying to achieve a simple animation: when clicked the item expands and when clicked again it collapses.

Using Visibility on the "expandable" control would be an option except I want a "growing" animation, having the Height increase until it's full height, instead of the snapping effect that the Visibility provides.

I also messed around with the ScaleY effect and it is almost what I want, except the parent reserves the Height for the expandable control even when this is with ScaleY = 0 leaving a big unwanted space between every element on the list and it makes sense.

Now the working solution as demonstrated above is having a set Height value on the control and varying between this one and 0. But I would like to achieve a more reusable solution without having to hardcode the height.

Any help would be appreciated. Thanks!

1

There are 1 answers

0
SWilko On

Ive recently built a Custom Control that does something similar to the behaviour you are after. Here is a test version to show what I mean as you maybe able to adapt for your project.

Two key points are the control the items you wish to move are in a StackPanel and the Height property is manipulated rather than using Transform.ScaleY

MainPage.xaml

<Grid Background="Black">
    <StackPanel Orientation="Vertical" Margin="20"
                VerticalAlignment="Top">
        <Button x:Name="ExpandButton" Width="200" Height="75" Content="expand"
                Foreground="White" BorderBrush="White"
                Click="ExpandButton_Click" />

        <Rectangle x:Name="Red" Width="200" Height="200" Fill="Red" />
        <Rectangle x:Name="Green" Width="200" Height="200" Fill="Green" />
        <Rectangle x:Name="Blue" Width="200" Height="200" Fill="Blue" />
    </StackPanel>
</Grid>

MainPage.xaml.cs

public sealed partial class MainPage : Page
{
    private double panelHeight = 0.0;
    private bool contentOpen = false;

    public MainPage()
    {
        this.InitializeComponent();

        //this is the Green rectangles height we want to expand on Button Click
        panelHeight = this.Green.ActualHeight;

        //expandable item should be 0 height is Flag is false
        if (!contentOpen)
            Green.Height = 0.0;
    }

    private void ExpandButton_Click(object sender, RoutedEventArgs e)
    {            
        OpenCloseAnimation animate;

        //Create animation based on Flag.
        if (contentOpen)
        {
            //pass in zero height as we are closing
            animate = new OpenCloseAnimation(Green, 0);
            contentOpen = false;
            this.ExpandButton.Content = "expand";
        }
        else
        {
            //pass in Green rectangles actual height as we are opening
            animate = new OpenCloseAnimation(Green, panelHeight);
            contentOpen = true;
            this.ExpandButton.Content = "close";
        }

        //start the animation
        animate.sb.Begin();          
    }
}

OpenCloseAnimation.cs

public class OpenCloseAnimation
{
    public Storyboard sb { get; set; }
    private double _height;
    private FrameworkElement _element;

    public OpenCloseAnimation(FrameworkElement element, double height)
    {
        _element = element;
        _height = height;
        CreateStoryboard();
    }

    public void CreateStoryboard()
    {
        sb = new Storyboard();

        var animation = new DoubleAnimationUsingKeyFrames { EnableDependentAnimation = true };
        var easing = new EasingDoubleKeyFrame { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(200)), Value = _height };
        animation.KeyFrames.Add(easing);

        Storyboard.SetTargetProperty(animation, "(FrameworkElement.Height)");
        Storyboard.SetTarget(animation, _element);
        sb.Children.Add(animation);
    }
}

Hope it might help