WPF将StackPanel的宽度从0设置为自动?

时间:2014-12-04 22:44:45

标签: .net wpf xaml wpf-controls wpf-4.0

我正在尝试为StackPanel设置动画,当它的可见性从0宽度增加到自动宽度时,这就是我现在所拥有的:

<Trigger Property="Visibility" Value="Visible">
    <Setter Property="Width" Value="0"></Setter>
    <Trigger.EnterActions>
        <BeginStoryboard>
            <Storyboard>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Width" Duration="0:0:1">
                    <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                            <System:Double>NaN</System:Double>
                        </DiscreteObjectKeyFrame.Value>
                    </DiscreteObjectKeyFrame>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </BeginStoryboard>
    </Trigger.EnterActions>
</Trigger>

有人可以解释我如何实现这个动画吗?这可能不是我想要的方式吗?

谢谢, 亚历克斯。

2 个答案:

答案 0 :(得分:9)

这是一个我扔在一起的快速模型项目。

在Window的Loaded事件中,我只是将stackpanel的可见性设置为Visible,它从左到右展开以适应其容器宽度......希望这符合您的需求。

有些注意事项:

  • 您必须预先定义缩放变换,否则动画将无法播放。
  • 如果在动画中省略To,则会将其设置为默认值。

以下是代码:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="600" Loaded="Window_Loaded">
    <Border HorizontalAlignment="Center" Width="300" Background="Gainsboro">
        <Border.Resources>
            <Style TargetType="StackPanel" x:Key="expand">
                <Setter Property="RenderTransform">
                    <Setter.Value>
                        <ScaleTransform ScaleX="1"/>
                    </Setter.Value>
                </Setter>
                <Style.Triggers>
                    <Trigger Property="Visibility" Value="Visible">
                        <Trigger.EnterActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX"
                                                     From="0"
                                                     Duration="0:00:01"/>
                                </Storyboard>
                            </BeginStoryboard>
                        </Trigger.EnterActions>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </Border.Resources>

        <StackPanel x:Name="stackpanel" Background="Gray" Visibility="Collapsed" Style="{StaticResource expand}"/>

    </Border>
</Window>

答案 1 :(得分:0)

所以,这是一个很老的问题,但是我认为这是一个足够普遍的情况,您需要将WidthHeight0动画到Auto(或类似内容)以证明其他答案的合理性。在这里,我将不着重于Alex的确切要求,以强调我提出的解决方案的一般性质。

这是什么:编写自己的Clipper控件,将其孩子的可见WidthHeight裁剪到其中的一部分。然后,我们可以对那些Fraction属性(0-> 1)进行动画处理以获得所需的效果。以下是Clipper的代码,其中包括所有帮助程序。

public sealed class Clipper : Decorator
{
    public static readonly DependencyProperty WidthFractionProperty = DependencyProperty.RegisterAttached("WidthFraction", typeof(double), typeof(Clipper), new PropertyMetadata(1d, OnClippingInvalidated), IsFraction);
    public static readonly DependencyProperty HeightFractionProperty = DependencyProperty.RegisterAttached("HeightFraction", typeof(double), typeof(Clipper), new PropertyMetadata(1d, OnClippingInvalidated), IsFraction);
    public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register("Background", typeof(Brush), typeof(Clipper), new FrameworkPropertyMetadata(Brushes.Transparent, FrameworkPropertyMetadataOptions.AffectsRender));
    public static readonly DependencyProperty ConstraintProperty = DependencyProperty.Register("Constraint", typeof(ConstraintSource), typeof(Clipper), new PropertyMetadata(ConstraintSource.WidthAndHeight, OnClippingInvalidated), IsValidConstraintSource);

    private Size _childSize;
    private DependencyPropertySubscriber _childVerticalAlignmentSubcriber;
    private DependencyPropertySubscriber _childHorizontalAlignmentSubscriber;

    public Clipper()
    {
        ClipToBounds = true;
    }

    public Brush Background
    {
        get { return (Brush)GetValue(BackgroundProperty); }
        set { SetValue(BackgroundProperty, value); }
    }

    public ConstraintSource Constraint
    {
        get { return (ConstraintSource)GetValue(ConstraintProperty); }
        set { SetValue(ConstraintProperty, value); }
    }

    [AttachedPropertyBrowsableForChildren]
    public static double GetWidthFraction(DependencyObject obj)
    {
        return (double)obj.GetValue(WidthFractionProperty);
    }

    public static void SetWidthFraction(DependencyObject obj, double value)
    {
        obj.SetValue(WidthFractionProperty, value);
    }

    [AttachedPropertyBrowsableForChildren]
    public static double GetHeightFraction(DependencyObject obj)
    {
        return (double)obj.GetValue(HeightFractionProperty);
    }

    public static void SetHeightFraction(DependencyObject obj, double value)
    {
        obj.SetValue(HeightFractionProperty, value);
    }

    protected override Size MeasureOverride(Size constraint)
    {
        if (Child is null)
        {
            return Size.Empty;
        }

        switch (Constraint)
        {
            case ConstraintSource.WidthAndHeight:
                Child.Measure(constraint);
                break;

            case ConstraintSource.Width:
                Child.Measure(new Size(constraint.Width, double.PositiveInfinity));
                break;

            case ConstraintSource.Height:
                Child.Measure(new Size(double.PositiveInfinity, constraint.Height));
                break;

            case ConstraintSource.Nothing:
                Child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                break;
        }

        var finalSize = Child.DesiredSize;
        if (Child is FrameworkElement childElement)
        {
            if (childElement.HorizontalAlignment == HorizontalAlignment.Stretch && constraint.Width > finalSize.Width && !double.IsInfinity(constraint.Width))
            {
                finalSize.Width = constraint.Width;
            }

            if (childElement.VerticalAlignment == VerticalAlignment.Stretch && constraint.Height > finalSize.Height && !double.IsInfinity(constraint.Height))
            {
                finalSize.Height = constraint.Height;
            }
        }

        _childSize = finalSize;

        finalSize.Width *= GetWidthFraction(Child);
        finalSize.Height *= GetHeightFraction(Child);

        return finalSize;
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        if (Child is null)
        {
            return Size.Empty;
        }

        var childSize = _childSize;
        var clipperSize = new Size(Math.Min(arrangeSize.Width, childSize.Width * GetWidthFraction(Child)),
                                   Math.Min(arrangeSize.Height, childSize.Height * GetHeightFraction(Child)));
        var offsetX = 0d;
        var offsetY = 0d;

        if (Child is FrameworkElement childElement)
        {
            if (childSize.Width > clipperSize.Width)
            {
                switch (childElement.HorizontalAlignment)
                {
                    case HorizontalAlignment.Right:
                        offsetX = -(childSize.Width - clipperSize.Width);
                        break;

                    case HorizontalAlignment.Center:
                        offsetX = -(childSize.Width - clipperSize.Width) / 2;
                        break;
                }
            }

            if (childSize.Height > clipperSize.Height)
            {
                switch (childElement.VerticalAlignment)
                {
                    case VerticalAlignment.Bottom:
                        offsetY = -(childSize.Height - clipperSize.Height);
                        break;

                    case VerticalAlignment.Center:
                        offsetY = -(childSize.Height - clipperSize.Height) / 2;
                        break;
                }
            }
        }

        Child.Arrange(new Rect(new Point(offsetX, offsetY), childSize));

        return clipperSize;
    }

    protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
    {
        void UpdateLayout(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue.Equals(HorizontalAlignment.Stretch) || e.NewValue.Equals(VerticalAlignment.Stretch))
            {
                InvalidateMeasure();
            }
            else
            {
                InvalidateArrange();
            }
        }

        _childHorizontalAlignmentSubscriber?.Unsubscribe();
        _childVerticalAlignmentSubcriber?.Unsubscribe();

        if (visualAdded is FrameworkElement childElement)
        {
            _childHorizontalAlignmentSubscriber = new DependencyPropertySubscriber(childElement, HorizontalAlignmentProperty, UpdateLayout);
            _childVerticalAlignmentSubcriber = new DependencyPropertySubscriber(childElement, VerticalAlignmentProperty, UpdateLayout);
        }
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);
        drawingContext.DrawRectangle(Background, null, new Rect(RenderSize));
    }

    private static bool IsFraction(object value)
    {
        var numericValue = (double)value;
        return numericValue >= 0d && numericValue <= 1d;
    }

    private static void OnClippingInvalidated(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is UIElement element && VisualTreeHelper.GetParent(element) is Clipper translator)
        {
            translator.InvalidateMeasure();
        }
    }

    private static bool IsValidConstraintSource(object value)
    {
        return Enum.IsDefined(typeof(ConstraintSource), value);
    }
}

public enum ConstraintSource
{
    WidthAndHeight,
    Width,
    Height,
    Nothing
}

public class DependencyPropertySubscriber : DependencyObject
{    
    private static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(DependencyPropertySubscriber), new PropertyMetadata(null, ValueChanged));

    private readonly PropertyChangedCallback _handler;

    public DependencyPropertySubscriber(DependencyObject dependencyObject, DependencyProperty dependencyProperty, PropertyChangedCallback handler)
    {
        if (dependencyObject is null)
        {
            throw new ArgumentNullException(nameof(dependencyObject));
        }

        if (dependencyProperty is null)
        {
            throw new ArgumentNullException(nameof(dependencyProperty));
        }

        _handler = handler ?? throw new ArgumentNullException(nameof(handler));

        var binding = new Binding() { Path = new PropertyPath(dependencyProperty), Source = dependencyObject, Mode = BindingMode.OneWay };
        BindingOperations.SetBinding(this, ValueProperty, binding);
    }

    public void Unsubscribe()
    {
        BindingOperations.ClearBinding(this, ValueProperty);
    }

    private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((DependencyPropertySubscriber)d)._handler(d, e);
    }
}

用法如下:

<Clipper Constraint="WidthAndHeight">
    <Control Clipper.HeightFraction="0.5"
             Clipper.WidthFraction="0.5" />
</Clipper>

请注意Constraint属性:它确定子控件认为“自动”尺寸的内容。例如,如果您的控件是静态的(已明确设置HeightWidth),则应将Constraint设置为Nothing来裁剪整个元素的一部分。如果您的控件是WrapPanel,并且Orientation设置为Horizontal,则Constraint应该设置为Width,依此类推。如果截取错误,请尝试一下不同的约束。还请注意,Clipper尊重您控件的对齐方式,该对齐方式可以在动画中使用(例如,将HeightFraction0动画到1VerticalAlignment.Bottom表示控件“向下滑动”,VerticalAlignment.Center-“打开”)。