为什么什么都没有显示
您看不到任何移动的原因是因为您的目标是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>
然后将TranslateTransform 的X 属性从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。