Почему ничего не появляется
Причина, по которой вы не видите ничего движущегося, заключается в том, что вы нацеливаетесь на свойство (LayoutTransform).(ScaleTransform.ScaleX)
вашего ThumbCircle
, но это не такдля него не установлено значение 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>
А затем анимируем X
свойство TranslateTransform
из 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" />
Но этого достичь невозможно, так как невозможноустановите Binding
для любого свойства Animation
при использовании подобным образом, потому что WPF делает некоторые оптимизации, которые предотвращают это , как описано здесь .
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
для выполнения некоторого базового арОперации на вашем Binding
s:
Один для умножения всех предоставленных значений в 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
, мы должны запускать анимацию слева.
Это возможно путем добавления некоторых Trigger
объектов в коллекцию ControlTemplate.Triggers
. 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
, такую оригинальную коллекцию Border.Triggers
поместить в Trigger
невозможно, вы можете прочитать подробнее об этом здесь .