实现“下滑” WPF 中的动画

发布于 2024-12-17 11:34:26 字数 4519 浏览 1 评论 0原文

我正在尝试为 Expander 控件创建自己的模板。当控件展开时,我希望内容慢慢地向下滑动。

内容所需的高度在编译时未知。

我以为我们可以将向下滑动定义为动画:

<Storyboard x:Key="ExpandContent">

    <DoubleAnimation 
        Storyboard.TargetName="_expanderContent" 
        Storyboard.TargetProperty="Height" 
        From="0.0" 
        To="{Binding ElementName=_expanderContent,Path=DesiredHeight}"
        Duration="0:0:1.0" />
</Storyboard>

但不幸的是不是。我们得到一个错误

无法冻结此情节提要时间线树以供跨线程使用。

看来我们在定义动画参数时不能使用绑定。 (也在这个问题中讨论过.)

有谁对我如何解决这个问题有任何想法吗?我对使用 LayoutTransform.ScaleY 持谨慎态度,因为这会产生扭曲的图像。

这类似于 这个问题,但是这个问题的答案涉及编写隐藏代码,我认为这在控件模板中是不可能的。 我想知道是否可以实现基于 XAML 的解决方案。


For what it's worth, here is the current state of my control template.

<ControlTemplate x:Key="ExpanderControlTemplate"  TargetType="{x:Type Expander}">
    <ControlTemplate.Resources>
            <!-- Here are the storyboards which don't work -->
            <Storyboard x:Key="ExpandContent">

                <DoubleAnimation 
                    Storyboard.TargetName="_expanderContent" 
                    Storyboard.TargetProperty="Height" 
                    From="0.0" 
                    To="{Binding ElementName=_expanderContent,Path=DesiredHeight}"
                    Duration="0:0:1.0" />
            </Storyboard>
            <Storyboard x:Key="ContractContent">

                <DoubleAnimation 
                    Storyboard.TargetName="_expanderContent" 
                    Storyboard.TargetProperty="Height" 
                    From="{Binding ElementName=_expanderContent,Path=DesiredHeight}"
                    To="0.0"
                    Duration="0:0:1.0" />

            </Storyboard>
        </ControlTemplate.Resources>
    <Grid Name="MainGrid" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Name="ContentRow" Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Border>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <ContentPresenter ContentSource="Header" />
                <ToggleButton Template="{StaticResource ProductButtonExpand}"
                              Grid.Column="1"
                              IsChecked="{Binding Path=IsExpanded,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}" 
                              />
                <Rectangle Grid.ColumnSpan="2" Fill="#FFDADADA" Height="1" Margin="8,0,8,2" VerticalAlignment="Bottom"/>

            </Grid>
        </Border>

            <ContentPresenter Grid.Row="1" HorizontalAlignment="Stretch" Name="_expanderContent">

            </ContentPresenter>

    </Grid>
    <ControlTemplate.Triggers>
        <Trigger Property="IsExpanded" Value="True">
            <Setter TargetName="_expanderContent" Property="Height" Value="{Binding ElementName=_expanderContent,Path=DesiredHeight}" />

                <!-- Here is where I would activate the storyboard if they did work -->
                <Trigger.EnterActions>
                <!--<BeginStoryboard Storyboard="{StaticResource ExpandContent}"/>-->
            </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <!--<BeginStoryboard x:Name="ContractContent_BeginStoryboard" Storyboard="{StaticResource ContractContent}"/>-->
                </Trigger.ExitActions>
        </Trigger>
            <Trigger Property="IsExpanded" Value="False">

                <Setter TargetName="_expanderContent" Property="Height" Value="0" />
            </Trigger>
        </ControlTemplate.Triggers>

</ControlTemplate>

I am attempting to create my own template for an Expander control. When the control is expanded, I want the content to slide down slowly.

The desired height of the content is not known at compile time.

I thought we could define the slide down as an animation:

<Storyboard x:Key="ExpandContent">

    <DoubleAnimation 
        Storyboard.TargetName="_expanderContent" 
        Storyboard.TargetProperty="Height" 
        From="0.0" 
        To="{Binding ElementName=_expanderContent,Path=DesiredHeight}"
        Duration="0:0:1.0" />
</Storyboard>

But Unfortunately not. We get an error

Cannot freeze this Storyboard timeline tree for use across threads.

It appears that we cannot use binding when defining animation parameters. (Discussed also in this question.)

Does anyone have any ideas on how I can approach this? I'm wary of using LayoutTransform.ScaleY, because that would create a distorted image.

This is similar to this question, but this question has an answer involved writing code-behind, which I don't think is possible in a control template.
I'm wondering if a XAML-based solution is achievable.


For what it's worth, here is the current state of my control template.

<ControlTemplate x:Key="ExpanderControlTemplate"  TargetType="{x:Type Expander}">
    <ControlTemplate.Resources>
            <!-- Here are the storyboards which don't work -->
            <Storyboard x:Key="ExpandContent">

                <DoubleAnimation 
                    Storyboard.TargetName="_expanderContent" 
                    Storyboard.TargetProperty="Height" 
                    From="0.0" 
                    To="{Binding ElementName=_expanderContent,Path=DesiredHeight}"
                    Duration="0:0:1.0" />
            </Storyboard>
            <Storyboard x:Key="ContractContent">

                <DoubleAnimation 
                    Storyboard.TargetName="_expanderContent" 
                    Storyboard.TargetProperty="Height" 
                    From="{Binding ElementName=_expanderContent,Path=DesiredHeight}"
                    To="0.0"
                    Duration="0:0:1.0" />

            </Storyboard>
        </ControlTemplate.Resources>
    <Grid Name="MainGrid" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Name="ContentRow" Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Border>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <ContentPresenter ContentSource="Header" />
                <ToggleButton Template="{StaticResource ProductButtonExpand}"
                              Grid.Column="1"
                              IsChecked="{Binding Path=IsExpanded,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}" 
                              />
                <Rectangle Grid.ColumnSpan="2" Fill="#FFDADADA" Height="1" Margin="8,0,8,2" VerticalAlignment="Bottom"/>

            </Grid>
        </Border>

            <ContentPresenter Grid.Row="1" HorizontalAlignment="Stretch" Name="_expanderContent">

            </ContentPresenter>

    </Grid>
    <ControlTemplate.Triggers>
        <Trigger Property="IsExpanded" Value="True">
            <Setter TargetName="_expanderContent" Property="Height" Value="{Binding ElementName=_expanderContent,Path=DesiredHeight}" />

                <!-- Here is where I would activate the storyboard if they did work -->
                <Trigger.EnterActions>
                <!--<BeginStoryboard Storyboard="{StaticResource ExpandContent}"/>-->
            </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <!--<BeginStoryboard x:Name="ContractContent_BeginStoryboard" Storyboard="{StaticResource ContractContent}"/>-->
                </Trigger.ExitActions>
        </Trigger>
            <Trigger Property="IsExpanded" Value="False">

                <Setter TargetName="_expanderContent" Property="Height" Value="0" />
            </Trigger>
        </ControlTemplate.Triggers>

</ControlTemplate>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

庆幸我还是我 2024-12-24 11:34:26

这是一个老问题,但我今天遇到了问题,所以我想发布我的解决方案是值得的:

我必须对网格行的 Height 属性进行动画处理(上下滑动),但需要动态绑定,以便该行将再次滑动到与之前相同的位置。

我发现这个答案非常有帮助(在与 XAML 进行无果的斗争之后):
http://go4answers.webhost4life.com/Question/ found-solution-work-protected-override-190845.aspx

有时,在代码隐藏中执行操作会更简单

        Storyboard sb = new Storyboard();

        var animation = new GridLengthAnimation
        {
                Duration = new Duration(500.Milliseconds()),
                From = this.myGridRow.Height,
                To = new GridLength(IsGridRowVisible ? GridRowPreviousHeight : 0, GridUnitType.Pixel)
        };

        // Set the target of the animation
        Storyboard.SetTarget(animation, this.myGridRow);
        Storyboard.SetTargetProperty(animation, new PropertyPath("Height"));
        
        // Kick the animation off
        sb.Children.Add(animation);
        sb.Begin();

: GridLengthAnimation 类可以在这里找到:
http://social.msdn.microsoft.com/forums/en-US/wpf/thread/da47a4b8-4d39-4d6e-a570-7dbe51a842e4/

/// <summary>
    /// Animates a grid length value just like the DoubleAnimation animates a double value
    /// </summary>
    public class GridLengthAnimation : AnimationTimeline
    {
        /// <summary>
        /// Returns the type of object to animate
        /// </summary>
        public override Type TargetPropertyType
        {
            get
            {
                return typeof(GridLength);
            }
        }

        /// <summary>
        /// Creates an instance of the animation object
        /// </summary>
        /// <returns>Returns the instance of the GridLengthAnimation</returns>
        protected override System.Windows.Freezable CreateInstanceCore()
        {
            return new GridLengthAnimation();
        }

        /// <summary>
        /// Dependency property for the From property
        /// </summary>
        public static readonly DependencyProperty FromProperty = DependencyProperty.Register("From", typeof(GridLength),
                typeof(GridLengthAnimation));

        /// <summary>
        /// CLR Wrapper for the From depenendency property
        /// </summary>
        public GridLength From
        {
            get
            {
                return (GridLength)GetValue(GridLengthAnimation.FromProperty);
            }
            set
            {
                SetValue(GridLengthAnimation.FromProperty, value);
            }
        }

        /// <summary>
        /// Dependency property for the To property
        /// </summary>
        public static readonly DependencyProperty ToProperty = DependencyProperty.Register("To", typeof(GridLength),
                typeof(GridLengthAnimation));

        /// <summary>
        /// CLR Wrapper for the To property
        /// </summary>
        public GridLength To
        {
            get
            {
                return (GridLength)GetValue(GridLengthAnimation.ToProperty);
            }
            set
            {
                SetValue(GridLengthAnimation.ToProperty, value);
            }
        }

        /// <summary>
        /// Animates the grid let set
        /// </summary>
        /// <param name="defaultOriginValue">The original value to animate</param>
        /// <param name="defaultDestinationValue">The final value</param>
        /// <param name="animationClock">The animation clock (timer)</param>
        /// <returns>Returns the new grid length to set</returns>
        public override object GetCurrentValue(object defaultOriginValue,
            object defaultDestinationValue, AnimationClock animationClock)
        {
            double fromVal = ((GridLength)GetValue(GridLengthAnimation.FromProperty)).Value;
            //check that from was set from the caller
            if (fromVal == 1)
                //set the from as the actual value
                fromVal = ((GridLength)defaultDestinationValue).Value;

            double toVal = ((GridLength)GetValue(GridLengthAnimation.ToProperty)).Value;

            if (fromVal > toVal)
                return new GridLength((1 - animationClock.CurrentProgress.Value) * (fromVal - toVal) + toVal, GridUnitType.Star);
            else
                return new GridLength(animationClock.CurrentProgress.Value * (toVal - fromVal) + fromVal, GridUnitType.Star);
        }
    }

This is kind of an old question but I had problems with this today, so I guess posting my solution would be worth it:

I had to animate the Height property of a grid row (sliding up and down), but needed dynamic binding so that the row would slide again to the same position as before.

I found this answer to be very helpful (after fruitlessly battling XAML):
http://go4answers.webhost4life.com/Question/found-solution-work-protected-override-190845.aspx

Sometimes doing things in the code-behind is just simpler:

        Storyboard sb = new Storyboard();

        var animation = new GridLengthAnimation
        {
                Duration = new Duration(500.Milliseconds()),
                From = this.myGridRow.Height,
                To = new GridLength(IsGridRowVisible ? GridRowPreviousHeight : 0, GridUnitType.Pixel)
        };

        // Set the target of the animation
        Storyboard.SetTarget(animation, this.myGridRow);
        Storyboard.SetTargetProperty(animation, new PropertyPath("Height"));
        
        // Kick the animation off
        sb.Children.Add(animation);
        sb.Begin();

The GridLengthAnimation class can be found here:
http://social.msdn.microsoft.com/forums/en-US/wpf/thread/da47a4b8-4d39-4d6e-a570-7dbe51a842e4/

/// <summary>
    /// Animates a grid length value just like the DoubleAnimation animates a double value
    /// </summary>
    public class GridLengthAnimation : AnimationTimeline
    {
        /// <summary>
        /// Returns the type of object to animate
        /// </summary>
        public override Type TargetPropertyType
        {
            get
            {
                return typeof(GridLength);
            }
        }

        /// <summary>
        /// Creates an instance of the animation object
        /// </summary>
        /// <returns>Returns the instance of the GridLengthAnimation</returns>
        protected override System.Windows.Freezable CreateInstanceCore()
        {
            return new GridLengthAnimation();
        }

        /// <summary>
        /// Dependency property for the From property
        /// </summary>
        public static readonly DependencyProperty FromProperty = DependencyProperty.Register("From", typeof(GridLength),
                typeof(GridLengthAnimation));

        /// <summary>
        /// CLR Wrapper for the From depenendency property
        /// </summary>
        public GridLength From
        {
            get
            {
                return (GridLength)GetValue(GridLengthAnimation.FromProperty);
            }
            set
            {
                SetValue(GridLengthAnimation.FromProperty, value);
            }
        }

        /// <summary>
        /// Dependency property for the To property
        /// </summary>
        public static readonly DependencyProperty ToProperty = DependencyProperty.Register("To", typeof(GridLength),
                typeof(GridLengthAnimation));

        /// <summary>
        /// CLR Wrapper for the To property
        /// </summary>
        public GridLength To
        {
            get
            {
                return (GridLength)GetValue(GridLengthAnimation.ToProperty);
            }
            set
            {
                SetValue(GridLengthAnimation.ToProperty, value);
            }
        }

        /// <summary>
        /// Animates the grid let set
        /// </summary>
        /// <param name="defaultOriginValue">The original value to animate</param>
        /// <param name="defaultDestinationValue">The final value</param>
        /// <param name="animationClock">The animation clock (timer)</param>
        /// <returns>Returns the new grid length to set</returns>
        public override object GetCurrentValue(object defaultOriginValue,
            object defaultDestinationValue, AnimationClock animationClock)
        {
            double fromVal = ((GridLength)GetValue(GridLengthAnimation.FromProperty)).Value;
            //check that from was set from the caller
            if (fromVal == 1)
                //set the from as the actual value
                fromVal = ((GridLength)defaultDestinationValue).Value;

            double toVal = ((GridLength)GetValue(GridLengthAnimation.ToProperty)).Value;

            if (fromVal > toVal)
                return new GridLength((1 - animationClock.CurrentProgress.Value) * (fromVal - toVal) + toVal, GridUnitType.Star);
            else
                return new GridLength(animationClock.CurrentProgress.Value * (toVal - fromVal) + fromVal, GridUnitType.Star);
        }
    }
只想待在家 2024-12-24 11:34:26

如果您可以将InteractionsFluidLayout一起使用(Blend 4 SDK)你很幸运,它对于那些精美的动画非常有用。

首先将内容 CP 的高度设置为 0:

<ContentPresenter Grid.Row="1"
    HorizontalAlignment="Stretch"
    x:Name="_expanderContent"
    Height="0"/>

要对其进行动画处理,只需在表示展开的 VisualState 中将 Height 动画设置为 NaN状态(非离散动画不允许您使用 NaN):

xmlns:is="http://schemas.microsoft.com/expression/2010/interactions"
<Grid x:Name="MainGrid" Background="White">
    <VisualStateManager.CustomVisualStateManager>
        <is:ExtendedVisualStateManager/>
    </VisualStateManager.CustomVisualStateManager>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="ExpansionStates" is:ExtendedVisualStateManager.UseFluidLayout="True">
            <VisualStateGroup.Transitions>
                <VisualTransition GeneratedDuration="0:0:1"/>
            </VisualStateGroup.Transitions>
            <VisualState x:Name="Expanded">
                <Storyboard>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)"
                                                   Storyboard.TargetName="_expanderContent">
                        <DiscreteDoubleKeyFrame KeyTime="0" Value="NaN"/>
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
            <VisualState x:Name="Collapsed"/>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <!-- ... --->

这应该是所有必需的,流体布局将从那里为您创建过渡。


如果您有一个代码隐藏解决方案就可以了,您甚至可以在字典中使用代码隐藏,如下所示:

<!-- TestDictionary.xaml -->
<ResourceDictionary x:Class="Test.TestDictionary"
                    ...>
//TestDictionary.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace Test
{
    partial class TestDictionary : ResourceDictionary
    {
        //Handlers and such here
    }
}

If you can use Interactions with FluidLayout (Blend 4 SDK) you are in luck, it's really useful for those fancy animation things.

First set the content CP's Height to 0:

<ContentPresenter Grid.Row="1"
    HorizontalAlignment="Stretch"
    x:Name="_expanderContent"
    Height="0"/>

To animate this, the Height just needs to be animated to NaN in the VisualState that represents the expanded state (non-discrete animations would not let you use NaN):

xmlns:is="http://schemas.microsoft.com/expression/2010/interactions"
<Grid x:Name="MainGrid" Background="White">
    <VisualStateManager.CustomVisualStateManager>
        <is:ExtendedVisualStateManager/>
    </VisualStateManager.CustomVisualStateManager>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="ExpansionStates" is:ExtendedVisualStateManager.UseFluidLayout="True">
            <VisualStateGroup.Transitions>
                <VisualTransition GeneratedDuration="0:0:1"/>
            </VisualStateGroup.Transitions>
            <VisualState x:Name="Expanded">
                <Storyboard>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)"
                                                   Storyboard.TargetName="_expanderContent">
                        <DiscreteDoubleKeyFrame KeyTime="0" Value="NaN"/>
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
            <VisualState x:Name="Collapsed"/>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <!-- ... --->

That should be all that is necessary, the fluid layout will create the transition for you from there.


If you have a code-behind solution that would be fine, you can even use code-behind in dictionaries like this:

<!-- TestDictionary.xaml -->
<ResourceDictionary x:Class="Test.TestDictionary"
                    ...>
//TestDictionary.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace Test
{
    partial class TestDictionary : ResourceDictionary
    {
        //Handlers and such here
    }
}
千年*琉璃梦 2024-12-24 11:34:26

CodeProject 上有一个现成的纯 XAML 解决方案:

样式:

    <local:MultiplyConverter x:Key="MultiplyConverter" />
    <Style TargetType="Expander" x:Key="VerticalSlidingEmptyExpander">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Expander}">
                    <ScrollViewer x:Name="ExpanderContentScrollView"
                  HorizontalScrollBarVisibility="Hidden"
                  VerticalScrollBarVisibility="Hidden"
                  HorizontalContentAlignment="Stretch"
                  VerticalContentAlignment="Top"
                  >
                        <ScrollViewer.Tag>
                            <system:Double>0.0</system:Double>
                        </ScrollViewer.Tag>
                        <ScrollViewer.Height>
                            <MultiBinding Converter="{StaticResource MultiplyConverter}">
                                <Binding Path="ActualHeight" ElementName="ExpanderContent"/>
                                <Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
                            </MultiBinding>
                        </ScrollViewer.Height>
                        <ContentPresenter x:Name="ExpanderContent" ContentSource="Content"/>
                    </ScrollViewer>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation 
                       Storyboard.TargetName="ExpanderContentScrollView"
                       Storyboard.TargetProperty="Tag"
                       To="1"
                       Duration="0:0:0.2"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation 
                         Storyboard.TargetName="ExpanderContentScrollView"
                         Storyboard.TargetProperty="Tag"
                         To="0"
                         Duration="0:0:0.2"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style TargetType="Expander" x:Key="HorizontalSlidingEmptyExpander">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Expander}">
                    <ScrollViewer x:Name="ExpanderContentScrollView"
                  HorizontalScrollBarVisibility="Hidden"
                  VerticalScrollBarVisibility="Hidden"
                  HorizontalContentAlignment="Left"
                  VerticalContentAlignment="Stretch"
                  >
                        <ScrollViewer.Tag>
                            <system:Double>0.0</system:Double>
                        </ScrollViewer.Tag>
                        <ScrollViewer.Width>
                            <MultiBinding Converter="{StaticResource MultiplyConverter}">
                                <Binding Path="ActualWidth" ElementName="ExpanderContent"/>
                                <Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
                            </MultiBinding>
                        </ScrollViewer.Width>
                        <ContentPresenter x:Name="ExpanderContent" ContentSource="Content"/>
                    </ScrollViewer>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation 
                       Storyboard.TargetName="ExpanderContentScrollView"
                       Storyboard.TargetProperty="Tag"
                       To="1"
                       Duration="0:0:0.2"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation 
                         Storyboard.TargetName="ExpanderContentScrollView"
                         Storyboard.TargetProperty="Tag"
                         To="0"
                         Duration="0:0:0.2"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

MultiplyConverter:

public class MultiplyConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType,
           object parameter, CultureInfo culture)
    {
        double result = 1.0;
        for (int i = 0; i < values.Length; i++)
        {
            if (values[i] is double)
                result *= (double)values[i];
        }

        return result;
    }

    public object[] ConvertBack(object value, Type[] targetTypes,
           object parameter, CultureInfo culture)
    {
        throw new Exception("Not implemented");
    }
}

我复制了样式以具有水平和垂直版本,并省略了切换按钮,但您可以轻松地从原始帖子中获取它。

There is a ready-to-use and XAML-only solution on CodeProject:

The Styles:

    <local:MultiplyConverter x:Key="MultiplyConverter" />
    <Style TargetType="Expander" x:Key="VerticalSlidingEmptyExpander">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Expander}">
                    <ScrollViewer x:Name="ExpanderContentScrollView"
                  HorizontalScrollBarVisibility="Hidden"
                  VerticalScrollBarVisibility="Hidden"
                  HorizontalContentAlignment="Stretch"
                  VerticalContentAlignment="Top"
                  >
                        <ScrollViewer.Tag>
                            <system:Double>0.0</system:Double>
                        </ScrollViewer.Tag>
                        <ScrollViewer.Height>
                            <MultiBinding Converter="{StaticResource MultiplyConverter}">
                                <Binding Path="ActualHeight" ElementName="ExpanderContent"/>
                                <Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
                            </MultiBinding>
                        </ScrollViewer.Height>
                        <ContentPresenter x:Name="ExpanderContent" ContentSource="Content"/>
                    </ScrollViewer>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation 
                       Storyboard.TargetName="ExpanderContentScrollView"
                       Storyboard.TargetProperty="Tag"
                       To="1"
                       Duration="0:0:0.2"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation 
                         Storyboard.TargetName="ExpanderContentScrollView"
                         Storyboard.TargetProperty="Tag"
                         To="0"
                         Duration="0:0:0.2"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style TargetType="Expander" x:Key="HorizontalSlidingEmptyExpander">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Expander}">
                    <ScrollViewer x:Name="ExpanderContentScrollView"
                  HorizontalScrollBarVisibility="Hidden"
                  VerticalScrollBarVisibility="Hidden"
                  HorizontalContentAlignment="Left"
                  VerticalContentAlignment="Stretch"
                  >
                        <ScrollViewer.Tag>
                            <system:Double>0.0</system:Double>
                        </ScrollViewer.Tag>
                        <ScrollViewer.Width>
                            <MultiBinding Converter="{StaticResource MultiplyConverter}">
                                <Binding Path="ActualWidth" ElementName="ExpanderContent"/>
                                <Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
                            </MultiBinding>
                        </ScrollViewer.Width>
                        <ContentPresenter x:Name="ExpanderContent" ContentSource="Content"/>
                    </ScrollViewer>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation 
                       Storyboard.TargetName="ExpanderContentScrollView"
                       Storyboard.TargetProperty="Tag"
                       To="1"
                       Duration="0:0:0.2"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation 
                         Storyboard.TargetName="ExpanderContentScrollView"
                         Storyboard.TargetProperty="Tag"
                         To="0"
                         Duration="0:0:0.2"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

MultiplyConverter:

public class MultiplyConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType,
           object parameter, CultureInfo culture)
    {
        double result = 1.0;
        for (int i = 0; i < values.Length; i++)
        {
            if (values[i] is double)
                result *= (double)values[i];
        }

        return result;
    }

    public object[] ConvertBack(object value, Type[] targetTypes,
           object parameter, CultureInfo culture)
    {
        throw new Exception("Not implemented");
    }
}

I duplicated the Style to have a horizontal and vertical version and omitted the ToggleButtons, but you can easily get that from the original post.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文