【问题标题】:How do I properly animate my Border to move to the right我如何正确地为我的边框设置动画以向右移动
【发布时间】:2020-02-14 22:58:32
【问题描述】:

所以我有这种针对我的 ToggleButton 的样式,我正在尝试将名为 ThumbCircle 的边框的 HorizontalAlignment 从左更改为右,我确实读到更改该属性并不像更改值,我将不得不做某种 LayoutTransform,但这似乎不起作用,当我点击我的按钮时没有任何反应,它不会移动。

所以我的问题是,我如何让ThumbCircle 移动到它当前放置的Border 的右侧。

<Style x:Key="MyToggleButton"
       TargetType="{x:Type ToggleButton}">

        <Style.Resources>
            <Color x:Key="Color.MyBtn.PrimaryColor">#2ecc71</Color>
            <Color x:Key="Color.MyBtn.SecondaryColor">#27ae60</Color>
        </Style.Resources>


        <Setter Property="Template">

            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <Grid>
                        <Border Width="50" Height="12" Background="White" CornerRadius="6">

                        </Border>

                        <Border Width="25"
                                Background="#2ecc71"
                                CornerRadius="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"
                                HorizontalAlignment="Left"
                                x:Name="ThumbCircle">
                            <Border.Triggers>
                                <EventTrigger RoutedEvent="PreviewMouseDown">
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <!-- Dont forget easing -->
                                            <DoubleAnimation Storyboard.TargetProperty="(LayoutTransform).(ScaleTransform.ScaleX)" Storyboard.TargetName="ThumbCircle" To="10" Duration="0:0:0.5" />
                                        </Storyboard>
                                    </BeginStoryboard>

                                </EventTrigger>
                            </Border.Triggers>
                        </Border>
                    </Grid>

                </ControlTemplate>
            </Setter.Value>

        </Setter>
    </Style>

【问题讨论】:

    标签: c# .net wpf xaml mvvm


    【解决方案1】:

    为什么什么都没有显示

    您看不到任何移动的原因是因为您的目标是ThumbCircle(LayoutTransform).(ScaleTransform.ScaleX) 属性,但它的LayoutTransform 属性没有设置任何值。

    如果您将此添加到您的ThumbCircle Border

        <Border.LayoutTransform>
            <ScaleTransform/>
        </Border.LayoutTransform>
    

    然后你会看到一些事情发生。但是您会看到缩放,而不是平移!你想要的是从一侧翻译到另一侧。

    直观的修复不起作用...

    最简单的方法是首先将LayoutTransform 替换为RenderTransform,并将ScaleTransform 替换为TranslateTransform,如下所示:

        <Border.RenderTransform>
            <TranslateTransform x:Name="MyTranslate"/>
        </Border.LayoutTransform>
    

    然后像这样为您的Grid 命名:

    <Grid x:Name="MyGrid">
        ...
    </Grid>
    

    然后将TranslateTransformX 属性从0 设置为Grid.ActualWidth,如下所示:

    <!-- This won't run -->
    <DoubleAnimation Storyboard.TargetProperty="X" Storyboard.TargetName="MyTranslate" To="{Binding ElementName=MyGrid, Path=ActualWidth}" Duration="0:0:0.5" />
    

    但是不可能实现这一点,因为当像这样使用时,无法在 Animation 的任何属性上设置 Binding,因为 WPF 进行了一些优化来阻止 as explained here

    一种 XAML 密集型方式

    因此,一种方法是定义代理元素,我们将其属性从0 动画化到1,然后在另一个位置与MyGrid.ActualWidth 相乘。

    所以你的整个 XAML 风格变成了:

    <Style x:Key="MyToggleButton" TargetType="{x:Type ToggleButton}">
    
        <Style.Resources>
            <Color x:Key="Color.MyBtn.PrimaryColor">#2ecc71</Color>
            <Color x:Key="Color.MyBtn.SecondaryColor">#27ae60</Color>
            <!-- Aded some converters here -->
            <views:MultiMultiplierConverter x:Key="MultiMultiplierConverter"></views:MultiMultiplierConverter>
            <views:OppositeConverter x:Key="OppositeConverter"></views:OppositeConverter>
        </Style.Resources>
    
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <Grid x:Name="ContainerGrid">
                        <Border Width="50" Height="12" Background="Red" CornerRadius="6">
    
                        </Border>
                        <Border Width="25"
                            Background="#2ecc71"
                            CornerRadius="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"
                            HorizontalAlignment="Left"
                            x:Name="ThumbCircle">
                            <Border.Resources>
                                <!-- Proxy object whose X property gets animated from 0 to 1. -->
                                <!-- Could be any DependencyObject with a property of type double. -->
                                <TranslateTransform x:Key="unusedKey" x:Name="Proxy"></TranslateTransform>
                            </Border.Resources>
                            <Border.RenderTransform>
                                <TransformGroup>
                                    <!-- Main translation to move from one side of the grid to the other -->
                                    <TranslateTransform>
                                        <TranslateTransform.X>
                                            <MultiBinding Converter="{StaticResource MultiMultiplierConverter}" ConverterParameter="2">
                                                <Binding ElementName="Proxy" Path="X"></Binding>
                                                <Binding ElementName="ContainerGrid" Path="ActualWidth"></Binding>
                                            </MultiBinding>
                                        </TranslateTransform.X>
                                    </TranslateTransform>
    
                                    <!-- Secondary translation to adjust to the actual width of the object to translate -->
                                    <TranslateTransform>
                                        <TranslateTransform.X>
                                            <MultiBinding Converter="{StaticResource MultiMultiplierConverter}" ConverterParameter="2">
                                                <Binding ElementName="Proxy" Path="X"></Binding>
                                                <Binding ElementName="ThumbCircle" Path="ActualWidth" Converter="{StaticResource OppositeConverter}"></Binding>
                                            </MultiBinding>
                                        </TranslateTransform.X>
                                    </TranslateTransform>
                                </TransformGroup>
                            </Border.RenderTransform>
                            <Border.Triggers>
                                <EventTrigger RoutedEvent="MouseDown">
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <!-- Dont forget easing -->
                                            <DoubleAnimation Storyboard.TargetProperty="X" Storyboard.TargetName="Proxy" To="1" Duration="0:0:0.5" />
                                        </Storyboard>
                                    </BeginStoryboard>
                                </EventTrigger>
                            </Border.Triggers>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    您需要定义两个IValueConverter 来对您的Bindings 执行一些基本的算术运算:

    一个用于将MultiBinding中所有提供的值相乘:

    /// <summary>
    /// Defines a converter which multiplies all provided values.
    /// The given parameter indicates number of arguments to multiply.
    /// </summary>
    public class MultiMultiplierConverter : IMultiValueConverter {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
            double result = 1;
            int count = int.Parse((string)parameter);
            for (int i = 0; i < count; i++) {
                result *= (double)values[i];
            }
            return result;
        }
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) {
            throw new NotSupportedException("Cannot convert back");
        }
    }
    

    还有一个将输入乘以-1

    /// <summary>
    /// Defines a converter which multiplies the provided value by -1
    /// </summary>
    public class OppositeConverter : IValueConverter {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
            return (dynamic)value * -1;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
            throw new NotSupportedException("Cannot convert back");
        }
    }
    

    这不是一种优雅的方式,但它确实有效!

    来回动画如何实现?

    到目前为止,我们设法在点击时将拇指动画向右移动。但这不是重点,不是吗?

    我们正在模板化的是ToggleButton:每次点击时,都应该触发对面的动画。更准确地说,每当IsChecked 属性变为True 时,我们应该触发向右的动画,而每当IsChecked 属性获得False 时,我们应该触发向左的动画。

    这可以通过在ControlTemplate.Triggers 集合中添加一些Trigger 对象来实现。 Trigger 应该与 IsChecked 属性(我们无法控制)挂钩并监听它的变化。我们可以指定一个EnterAction,它是我们在右边的动画,一个ExitAction,它是我们左边的动画。

    完整的Style变成:

    <Style x:Key="MyToggleButton" TargetType="{x:Type ToggleButton}">
    
        <Style.Resources>
            <Color x:Key="Color.MyBtn.PrimaryColor">#2ecc71</Color>
            <Color x:Key="Color.MyBtn.SecondaryColor">#27ae60</Color>
            <!-- Aded some converters here -->
            <views:MultiMultiplierConverter x:Key="MultiMultiplierConverter"></views:MultiMultiplierConverter>
            <views:OppositeConverter x:Key="OppositeConverter"></views:OppositeConverter>
        </Style.Resources>
    
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsChecked" Value="True">
                            <!-- Animation to the right -->
                            <Trigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <!-- Dont forget easing -->
                                        <DoubleAnimation Storyboard.TargetProperty="X" Storyboard.TargetName="Proxy" To="1" Duration="0:0:0.5" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
    
                            <!-- Animation to the left -->
                            <Trigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <!-- Dont forget easing -->
                                        <DoubleAnimation Storyboard.TargetProperty="X" Storyboard.TargetName="Proxy" To="0" Duration="0:0:0.5" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                    <Grid x:Name="ContainerGrid">
                        <Border Width="50" Height="12" Background="Red" CornerRadius="6">
    
                        </Border>
                        <Border Width="25"
                    Background="#2ecc71"
                    CornerRadius="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"
                    HorizontalAlignment="Left"
                    x:Name="ThumbCircle">
                            <Border.Resources>
                                <!-- Proxy object whose X property gets animated from 0 to 1. -->
                                <!-- Could be any DependencyObject with a property of type double. -->
                                <TranslateTransform x:Key="unusedKey" x:Name="Proxy"></TranslateTransform>
                            </Border.Resources>
                            <Border.RenderTransform>
                                <TransformGroup>
                                    <!-- Main translation to move from one side of the grid to the other -->
                                    <TranslateTransform>
                                        <TranslateTransform.X>
                                            <MultiBinding Converter="{StaticResource MultiMultiplierConverter}" ConverterParameter="2">
                                                <Binding ElementName="Proxy" Path="X"></Binding>
                                                <Binding ElementName="ContainerGrid" Path="ActualWidth"></Binding>
                                            </MultiBinding>
                                        </TranslateTransform.X>
                                    </TranslateTransform>
    
                                    <!-- Secondary translation to adjust to the actual width of the object to translate -->
                                    <TranslateTransform>
                                        <TranslateTransform.X>
                                            <MultiBinding Converter="{StaticResource MultiMultiplierConverter}" ConverterParameter="2">
                                                <Binding ElementName="Proxy" Path="X"></Binding>
                                                <Binding ElementName="ThumbCircle" Path="ActualWidth" Converter="{StaticResource OppositeConverter}"></Binding>
                                            </MultiBinding>
                                        </TranslateTransform.X>
                                    </TranslateTransform>
                                </TransformGroup>
                            </Border.RenderTransform>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    请注意,这个Trigger 只在ControlTemplate.Triggers 集合中被接受,不可能将这样的Trigger 放在原来的Border.Triggers 集合中,您可以阅读更多关于它的信息here

    【讨论】:

    • 这是一个很好的答案!当 IsChecked 为 false 时,是否可以使其反转动画?
    • 我编辑了 :) 老实说,这是 WPF 最令人沮丧的问题之一,必须找到一种方法来使事情正常运行,这是有道理的......
    • 这真是令人印象深刻!我真的很喜欢你的方法!然而,我确实认为微软应该允许在 Storyboards 中绑定而不会出现冻结问题。太感谢了!我从中学到了很多东西!
    • IsChecked为真时是否可以改变“红色”边框的背景?
    • 是的。如果你自己找不到,你肯定应该在 SO 上问另一个问题 :) 但我相信这个问题已经有很多答案了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-12
    • 1970-01-01
    • 1970-01-01
    • 2020-10-06
    • 2021-12-22
    • 1970-01-01
    • 2020-09-10
    相关资源
    最近更新 更多