Search code examples
uwprendertransform

UWP grid rowheight not updating after scale transform of content


I have a control which contains 2 rows (header and content). The content has a ScaleY transform on it when the pointer enters or exists, but it seems the row which has height Auto does not collapse the height when the content is scaled to 0.

<Page
x:Class="ScaleTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ScaleTest"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <local:TestControl VerticalAlignment="Bottom"/>

</Grid>

    <Style TargetType="local:TestControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:TestControl">
                <Grid VerticalAlignment="{TemplateBinding VerticalAlignment}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="DisplayStates">
                            <VisualState x:Name="Minimized">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="contentTransForm" 
                                                     Storyboard.TargetProperty="ScaleY" 
                                                     Duration="0:0:0.2" 
                                                     To="0"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Maximized">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="contentTransForm" 
                                                     Storyboard.TargetProperty="ScaleY" 
                                                     Duration="0:0:0.2" 
                                                     To="1"/>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Grid Background="Red">
                        <TextBlock Text="Header"/>
                    </Grid>
                    <Grid Grid.Row="1" Background="Orange">
                        <Grid.RenderTransform>
                            <CompositeTransform x:Name="contentTransForm" ScaleY="0" ScaleX="1"/>
                        </Grid.RenderTransform>
                        <TextBlock Text="Content"/>
                    </Grid>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

I made a small sample app to show the problem. sample

Why the grid is not updating.


Solution

  • Thanks guys,

    I actually solved it by implementing a LayoutTansformer from the wpf toolkit and using a mediator it and the transformer. It gives a nice smooth animation without staying up and then suddenly collapsing.

    the LayoutTransformer:

        [TemplatePart(Name = "Presenter", Type = typeof(ContentPresenter))]
    [TemplatePart(Name = "TransformRoot", Type = typeof(Grid))]
    public sealed class LayoutTransformer : ContentControl
    {
        public static readonly DependencyProperty LayoutTransformProperty = DependencyProperty.Register("LayoutTransform", typeof(Transform), typeof(LayoutTransformer), new PropertyMetadata(new PropertyChangedCallback(LayoutTransformer.LayoutTransformChanged)));
    
        private Size _childActualSize = Size.Empty;
    
        private const string TransformRootName = "TransformRoot";
    
        private const string PresenterName = "Presenter";
    
        private const double AcceptableDelta = 0.0001;
    
        private const int DecimalsAfterRound = 4;
    
        private Panel _transformRoot;
    
        private ContentPresenter _contentPresenter;
    
        private MatrixTransform _matrixTransform;
    
        private Matrix _transformation;
    
        public Transform LayoutTransform
        {
            get => (Transform)GetValue(LayoutTransformer.LayoutTransformProperty);
            set => SetValue(LayoutTransformer.LayoutTransformProperty, (object)value);
        }
    
        private FrameworkElement Child => _contentPresenter?.Content as FrameworkElement ?? _contentPresenter;
    
        public LayoutTransformer()
        {
            DefaultStyleKey = (object)typeof(LayoutTransformer);
            IsTabStop = false;
            UseLayoutRounding = false;
        }
    
        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _transformRoot = (Panel)(GetTemplateChild(TransformRootName) as Grid);
            _contentPresenter = GetTemplateChild(PresenterName) as ContentPresenter;
            _matrixTransform = new MatrixTransform();
            if (null != _transformRoot)
                _transformRoot.RenderTransform = (Transform)_matrixTransform;
            ApplyLayoutTransform();
        }
    
        private static void LayoutTransformChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((LayoutTransformer)o).ProcessTransform((Transform)e.NewValue);
        }
    
        public void ApplyLayoutTransform()
        {
            ProcessTransform(LayoutTransform);
        }
    
        private void ProcessTransform(Transform transform)
        {
            _transformation = LayoutTransformer.RoundMatrix(GetTransformMatrix(transform), DecimalsAfterRound);
            if (null != _matrixTransform)
                _matrixTransform.Matrix = _transformation;
            InvalidateMeasure();
        }
    
        private Matrix GetTransformMatrix(Transform transform)
        {
            if (null == transform) return Matrix.Identity;
            if (transform is TransformGroup transformGroup)
            {
                var matrix1 = Matrix.Identity;
                foreach (var transform1 in (TransformCollection)transformGroup.Children)
                    matrix1 = LayoutTransformer.MatrixMultiply(matrix1, GetTransformMatrix(transform1));
                return matrix1;
            }
            if (transform is RotateTransform rotateTransform)
            {
                var num1 = 2.0 * Math.PI * rotateTransform.Angle / 360.0;
                var m12 = Math.Sin(num1);
                var num2 = Math.Cos(num1);
                return new Matrix(num2, m12, -m12, num2, 0.0, 0.0);
            }
            if (transform is ScaleTransform scaleTransform)
                return new Matrix(scaleTransform.ScaleX, 0.0, 0.0, scaleTransform.ScaleY, 0.0, 0.0);
            if (transform is SkewTransform skewTransform)
            {
                var angleX = skewTransform.AngleX;
                return new Matrix(1.0, 2.0 * Math.PI * skewTransform.AngleY / 360.0, 2.0 * Math.PI * angleX / 360.0, 1.0, 0.0, 0.0);
            }
            if (transform is MatrixTransform matrixTransform)
                return matrixTransform.Matrix;
            return Matrix.Identity;
        }
    
        protected override Size MeasureOverride(Size availableSize)
        {
            if (_transformRoot == null || null == Child)
                return Size.Empty;
            _transformRoot.Measure(!(_childActualSize == Size.Empty) ? _childActualSize : ComputeLargestTransformedSize(availableSize));
            var x = 0.0;
            var y = 0.0;
            var desiredSize = _transformRoot.DesiredSize;
            var width = desiredSize.Width;
            desiredSize = _transformRoot.DesiredSize;
            var height = desiredSize.Height;
            var rect = LayoutTransformer.RectTransform(new Rect(x, y, width, height), _transformation);
            return new Size(rect.Width, rect.Height);
        }
    
        protected override Size ArrangeOverride(Size finalSize)
        {
            var child = Child;
            if (_transformRoot == null || null == child)
                return finalSize;
            var a = ComputeLargestTransformedSize(finalSize);
            if (LayoutTransformer.IsSizeSmaller(a, _transformRoot.DesiredSize))
                a = _transformRoot.DesiredSize;
            var rect = LayoutTransformer.RectTransform(new Rect(0.0, 0.0, a.Width, a.Height), _transformation);
            _transformRoot.Arrange(new Rect(-rect.Left + (finalSize.Width - rect.Width) / 2.0, -rect.Top + (finalSize.Height - rect.Height) / 2.0, a.Width, a.Height));
            if (LayoutTransformer.IsSizeSmaller(a, child.RenderSize) && Size.Empty == _childActualSize)
            {
                _childActualSize = new Size(child.ActualWidth, child.ActualHeight);
                InvalidateMeasure();
            }
            else
                _childActualSize = Size.Empty;
            return finalSize;
        }
    
        private Size ComputeLargestTransformedSize(Size arrangeBounds)
        {
            var size = Size.Empty;
            var flag1 = double.IsInfinity(arrangeBounds.Width);
            if (flag1)
                arrangeBounds.Width = arrangeBounds.Height;
            var flag2 = double.IsInfinity(arrangeBounds.Height);
            if (flag2)
                arrangeBounds.Height = arrangeBounds.Width;
            var m11 = _transformation.M11;
            var m12 = _transformation.M12;
            var m21 = _transformation.M21;
            var m22 = _transformation.M22;
            var num1 = Math.Abs(arrangeBounds.Width / m11);
            var num2 = Math.Abs(arrangeBounds.Width / m21);
            var num3 = Math.Abs(arrangeBounds.Height / m12);
            var num4 = Math.Abs(arrangeBounds.Height / m22);
            var num5 = num1 / 2.0;
            var num6 = num2 / 2.0;
            var num7 = num3 / 2.0;
            var num8 = num4 / 2.0;
            var num9 = -(num2 / num1);
            var num10 = -(num4 / num3);
            if (0.0 == arrangeBounds.Width || 0.0 == arrangeBounds.Height)
                size = new Size(arrangeBounds.Width, arrangeBounds.Height);
            else if (flag1 && flag2)
                size = new Size(double.PositiveInfinity, double.PositiveInfinity);
            else if (!LayoutTransformer.MatrixHasInverse(_transformation))
                size = new Size(0.0, 0.0);
            else if (0.0 == m12 || 0.0 == m21)
            {
                var num11 = flag2 ? double.PositiveInfinity : num4;
                var num12 = flag1 ? double.PositiveInfinity : num1;
                if (0.0 == m12 && 0.0 == m21)
                    size = new Size(num12, num11);
                else if (0.0 == m12)
                {
                    var height = Math.Min(num6, num11);
                    size = new Size(num12 - Math.Abs(m21 * height / m11), height);
                }
                else if (0.0 == m21)
                {
                    var width = Math.Min(num7, num12);
                    size = new Size(width, num11 - Math.Abs(m12 * width / m22));
                }
            }
            else if (0.0 == m11 || 0.0 == m22)
            {
                var num11 = flag2 ? double.PositiveInfinity : num3;
                var num12 = flag1 ? double.PositiveInfinity : num2;
                if (0.0 == m11 && 0.0 == m22)
                    size = new Size(num11, num12);
                else if (0.0 == m11)
                {
                    var height = Math.Min(num8, num12);
                    size = new Size(num11 - Math.Abs(m22 * height / m12), height);
                }
                else if (0.0 == m22)
                {
                    var width = Math.Min(num5, num11);
                    size = new Size(width, num12 - Math.Abs(m11 * width / m21));
                }
            }
            else if (num6 <= num10 * num5 + num4)
                size = new Size(num5, num6);
            else if (num8 <= num9 * num7 + num2)
            {
                size = new Size(num7, num8);
            }
            else
            {
                var width = (num4 - num2) / (num9 - num10);
                size = new Size(width, num9 * width + num2);
            }
            return size;
        }
    
        private static bool IsSizeSmaller(Size a, Size b)
        {
            return a.Width + AcceptableDelta < b.Width || a.Height + AcceptableDelta < b.Height;
        }
    
        private static Matrix RoundMatrix(Matrix matrix, int decimals)
        {
            return new Matrix(Math.Round(matrix.M11, decimals), Math.Round(matrix.M12, decimals), Math.Round(matrix.M21, decimals), Math.Round(matrix.M22, decimals), matrix.OffsetX, matrix.OffsetY);
        }
    
        private static Rect RectTransform(Rect rect, Matrix matrix)
        {
            var point1 = matrix.Transform(new Point(rect.Left, rect.Top));
            var point2 = matrix.Transform(new Point(rect.Right, rect.Top));
            var point3 = matrix.Transform(new Point(rect.Left, rect.Bottom));
            var point4 = matrix.Transform(new Point(rect.Right, rect.Bottom));
            var x = Math.Min(Math.Min(point1.X, point2.X), Math.Min(point3.X, point4.X));
            var y = Math.Min(Math.Min(point1.Y, point2.Y), Math.Min(point3.Y, point4.Y));
            var num1 = Math.Max(Math.Max(point1.X, point2.X), Math.Max(point3.X, point4.X));
            var num2 = Math.Max(Math.Max(point1.Y, point2.Y), Math.Max(point3.Y, point4.Y));
            return new Rect(x, y, num1 - x, num2 - y);
        }
    
        private static Matrix MatrixMultiply(Matrix matrix1, Matrix matrix2)
        {
            return new Matrix(matrix1.M11 * matrix2.M11 + matrix1.M12 * matrix2.M21, matrix1.M11 * matrix2.M12 + matrix1.M12 * matrix2.M22, matrix1.M21 * matrix2.M11 + matrix1.M22 * matrix2.M21, matrix1.M21 * matrix2.M12 + matrix1.M22 * matrix2.M22, matrix1.OffsetX * matrix2.M11 + matrix1.OffsetY * matrix2.M21 + matrix2.OffsetX, matrix1.OffsetX * matrix2.M12 + matrix1.OffsetY * matrix2.M22 + matrix2.OffsetY);
        }
    
        private static bool MatrixHasInverse(Matrix matrix)
        {
            return 0.0 != matrix.M11 * matrix.M22 - matrix.M12 * matrix.M21;
        }
    }
    
    <Style TargetType="local:LayoutTransformer">
        <Setter Property="Foreground" Value="#FF000000" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:LayoutTransformer">
                    <Grid x:Name="TransformRoot" Background="{TemplateBinding Background}">
                        <ContentPresenter x:Name="Presenter" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    the Mediator:

        public class DoubleAnimationMediator : FrameworkElement
    {
        private string _layoutTransformerName;
    
        public LayoutTransformer LayoutTransformer { get; set; }
    
        public string LayoutTransformerName
        {
            get => _layoutTransformerName;
            set
            {
                _layoutTransformerName = value;
                LayoutTransformer = null;
            }
        }
    
        public static readonly DependencyProperty AnimationValueProperty =
            DependencyProperty.Register(
                "AnimationValue",
                typeof(double),
                typeof(DoubleAnimationMediator),
                new PropertyMetadata(0, AnimationValuePropertyChanged));
    
        private static void AnimationValuePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((DoubleAnimationMediator)o).AnimationValuePropertyChanged();
        }
    
        public double AnimationValue
        {
            get => (double)GetValue(AnimationValueProperty);
            set => SetValue(AnimationValueProperty, value);
        }
    
        private void AnimationValuePropertyChanged()
        {
            if (null == LayoutTransformer)
            {
                LayoutTransformer = FindName(LayoutTransformerName) as LayoutTransformer;
                if (null == LayoutTransformer)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                        "AnimationMediator was unable to find a LayoutTransformer named \"{0}\".",
                        LayoutTransformerName));
                }
            }
            CoreApplication.MainView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => LayoutTransformer.ApplyLayoutTransform()).AsTask().ConfigureAwait(true);
        }
    }
    

    The TestControl:

        public class TestControl : Control
    {
        public TestControl()
        {
            DefaultStyleKey = typeof(TestControl);
    
            PointerEntered += OnPointerEntered;
            PointerExited += OnPointerExited;
        }
    
        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
    
            VisualStateManager.GoToState(this, "Minimized", false);
        }
    
        private void OnPointerExited(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            VisualStateManager.GoToState(this, "Minimized", false);
        }
    
        private void OnPointerEntered(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            VisualStateManager.GoToState(this, "Maximized", false);
        }
    }
    
        <Style TargetType="local:TestControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:TestControl">
                    <Grid VerticalAlignment="{TemplateBinding VerticalAlignment}">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="DisplayStates">
                                <VisualState x:Name="Minimized">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="scaleYMediator" 
                                                         Storyboard.TargetProperty="AnimationValue" 
                                                         EnableDependentAnimation="True"
                                                         Duration="0:0:0.2" 
                                                         To="0"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Maximized">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="scaleYMediator" 
                                                         Storyboard.TargetProperty="AnimationValue" 
                                                         EnableDependentAnimation="True"
                                                         Duration="0:0:0.2" 
                                                         To="1"/>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <local:DoubleAnimationMediator x:Name="scaleYMediator"
                                                       LayoutTransformerName="layoutTransform"
                                                       AnimationValue="{Binding ScaleY, ElementName=scaleTransform, Mode=TwoWay}"/>
                        <Grid Background="Red"
                              Grid.Row="0">
                            <TextBlock Text="Header"/>
                        </Grid>
                        <local:LayoutTransformer x:Name="layoutTransform" Grid.Row="1"
                                                 Background="Orange">
                            <local:LayoutTransformer.LayoutTransform>
                                <TransformGroup>
                                    <ScaleTransform x:Name="scaleTransform" ScaleY="0"/>
                                </TransformGroup>
                            </local:LayoutTransformer.LayoutTransform>
                            <local:LayoutTransformer.Content>
                                <Grid>
                                    <TextBlock Text="Content"/>
                                </Grid>
                            </local:LayoutTransformer.Content>
                        </local:LayoutTransformer>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>