Я пытаюсь создать кнопки, аналогичные кнопкам выбора сервера Discords. При этом у них должна быть анимация при наведении (изменение CornerRadius при наведении), но если вы нажмете RadioButton, CornerRadius должен остаться прежним, даже если вы больше не наводите курсор. Здесь я столкнулся с несколькими проблемами, когда анимация при наведении больше не работает после того, как кнопка отмечена и снята. Что я делаю не так?
Я попытался добавить ExitAction к триггеру «IsChecked», но это просто добавляет дополнительную анимацию, а анимация наведения по-прежнему не работает, когда RadioButton был отмечен и снят.
Это визуализация моей проблемы. После того, как RadioButtons были проверены до того, как анимация при наведении перестанет работать: https://imgur.com/a/gJp1Ldd
Вот мой текущий Style.Triggers
<Style.Triggers>
<Trigger Property = "IsMouseOver" Value = "True">
<Setter Property = "Cursor" Value = "Hand"/>
</Trigger>
<Trigger Property = "IsPressed" Value = "True">
<Setter Property = "VerticalAlignment" Value = "Bottom"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property = "IsMouseOver" Value = "True"/>
<Condition Property = "IsChecked" Value = "False"/>
</MultiTrigger.Conditions>
<MultiTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty = "(Border.CornerRadius)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime = "0:0:0">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "40" BottomRight = "40" TopLeft = "40" TopRight = "40"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.025">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "37" BottomRight = "37" TopLeft = "37" TopRight = "37"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.05">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "34" BottomRight = "34" TopLeft = "34" TopRight = "34"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.075">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "31" BottomRight = "31" TopLeft = "31" TopRight = "31"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.1">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "28" BottomRight = "28" TopLeft = "28" TopRight = "28"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.EnterActions>
<MultiTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty = "(Border.CornerRadius)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime = "0:0:0">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "28" BottomRight = "28" TopLeft = "28" TopRight = "28"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.025">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "31" BottomRight = "31" TopLeft = "31" TopRight = "31"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.05">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "34" BottomRight = "34" TopLeft = "34" TopRight = "34"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.075">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "37" BottomRight = "37" TopLeft = "37" TopRight = "37"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.1">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "40" BottomRight = "40" TopLeft = "40" TopRight = "40"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.ExitActions>
</MultiTrigger>
<Trigger Property = "IsChecked" Value = "True">
<Setter Property = "BorderThickness" Value = "3"/>
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty = "(Border.CornerRadius)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.1">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "28" BottomRight = "28" TopLeft = "28" TopRight = "28"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty = "(Border.CornerRadius)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime = "0:0:0">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "28" BottomRight = "28" TopLeft = "28" TopRight = "28"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.025">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "31" BottomRight = "31" TopLeft = "31" TopRight = "31"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.05">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "34" BottomRight = "34" TopLeft = "34" TopRight = "34"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.075">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "37" BottomRight = "37" TopLeft = "37" TopRight = "37"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.15">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "40" BottomRight = "40" TopLeft = "40" TopRight = "40"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
Ваша логика триггера или условия, если быть точнее, неверны. Вы пропустили некоторые важные условия, чтобы правильно настроить анимацию.
При определении Trigger
или DataTrigger
вы всегда будете запускать состояние объекта.
Если триггер просто изменяет свойства, которые изначально имели допустимое значение в контексте старого состояния, в целом все в порядке: после перехода нового состояния обратно в предыдущее состояние XAML неявно применит предыдущие значения старого состояния.
Например, если ваш Style
настраивает элемент для «включенного» состояния (IsEnabled == true
), то определения Trigger
/DataTrigger
для «отключенного» состояния (IsEnabled == false
) будет достаточно, поскольку предыдущее состояние «включено» хорошо определено, а XAML двигатель просто вернется в предыдущее состояние «включено» после того, как состояние «отключено» закончится.
Но если состояние «включено» не определено должным образом (например, некоторые значения, измененные триггером, недействительны или не определены), состояние «отключено» не может перейти обратно в предыдущее и неопределенное состояние. В этом случае вам нужно будет явно определить дополнительный «включенный» Trigger
/DataTrigger
, чтобы покрыть этот случай и предоставить допустимые значения, чтобы можно было выполнить переход от «отключенного» обратно к «включенному». Установка анимированных значений по умолчанию с помощью Style.Setter
также является альтернативой, позволяющей избежать явного триггера.
Кроме того, когда вы используете анимацию, вы всегда должны помнить, что анимация не просто останавливается, когда достигается конечное значение анимации. Если для Storyboard.FillBehavior
явно не установлено значение FillBehavior.Stop
, анимация продолжается, чтобы сохранить значение. В противном случае, если анимация остановится здесь, анимированное свойство будет возвращено к исходному значению.
Это означает, что в вашем случае анимация триггера состояния «Проверено» все еще активна — одна из причин заключается в том, что вы решили определить ExitAction
. Анимация ExitAction
остается активной, хотя состояние триггера больше не действует. В этом весь смысл ExitAction
. Другая причина заключается в том, что не определен триггер состояния, который явно противодействует состоянию «Проверено» Trigger
, чтобы неявно остановить его.
Другими словами, состояние «IsChecked» Trigger
не соответствует состоянию «!IsChecked/IsMouseOver» MultiTrigger
.
Обычно я рекомендую использовать VisualStateManager
для управления анимацией состояний управления. Это гарантирует, что анимации правильно следуют друг за другом.
VisualStateManager
Это улучшенная версия вашего оригинала Style
.
<Style x:Key = "ToggleButtonStyle"
TargetType = "ToggleButton">
<Setter Property = "BorderThickness"
Value = "1" />
<Setter Property = "Template">
<Setter.Value>
<ControlTemplate TargetType = "ToggleButton">
<Border x:Name = "Border"
Background = "{TemplateBinding Background}"
BorderBrush = "{TemplateBinding BorderBrush}"
BorderThickness = "{TemplateBinding BorderThickness}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name = "CommonStates">
<VisualState x:Name = "Normal" />
<VisualState x:Name = "MouseOver" />
<VisualState x:Name = "Pressed" />
<VisualState x:Name = "Disabled" />
</VisualStateGroup>
<VisualStateGroup x:Name = "CheckStates">
<VisualState x:Name = "Checked">
<Storyboard>
<ThicknessAnimation Storyboard.TargetName = "Border"
Storyboard.TargetProperty = "BorderThickness"
To = "3" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetName = "Border"
Storyboard.TargetProperty = "(Border.CornerRadius)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.1">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "28"
BottomRight = "28"
TopLeft = "28"
TopRight = "28" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name = "Unchecked" />
<VisualState x:Name = "Indeterminate" />
<VisualStateGroup.Transitions>
<VisualTransition From = "Checked"
To = "Unchecked">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty = "(Border.CornerRadius)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime = "0:0:0">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "28"
BottomRight = "28"
TopLeft = "28"
TopRight = "28" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.025">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "31"
BottomRight = "31"
TopLeft = "31"
TopRight = "31" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.05">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "34"
BottomRight = "34"
TopLeft = "34"
TopRight = "34" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.075">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "37"
BottomRight = "37"
TopLeft = "37"
TopRight = "37" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.15">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "40"
BottomRight = "40"
TopLeft = "40"
TopRight = "40" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property = "IsMouseOver"
Value = "True">
<Setter Property = "Cursor"
Value = "Hand" />
</Trigger>
<Trigger Property = "IsPressed"
Value = "True">
<Setter Property = "VerticalAlignment"
Value = "Bottom" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property = "IsMouseOver"
Value = "True" />
<Condition Property = "IsChecked"
Value = "False" />
</MultiTrigger.Conditions>
<MultiTrigger.EnterActions>
<BeginStoryboard>
<Storyboard BeginTime = "0:0:0">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName = "Border"
Storyboard.TargetProperty = "(Border.CornerRadius)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime = "0:0:0">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "40"
BottomRight = "40"
TopLeft = "40"
TopRight = "40" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.025">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "37"
BottomRight = "37"
TopLeft = "37"
TopRight = "37" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.05">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "34"
BottomRight = "34"
TopLeft = "34"
TopRight = "34" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.075">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "31"
BottomRight = "31"
TopLeft = "31"
TopRight = "31" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.1">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "28"
BottomRight = "28"
TopLeft = "28"
TopRight = "28" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.EnterActions>
<MultiTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName = "Border"
Storyboard.TargetProperty = "(Border.CornerRadius)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime = "0:0:0">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "28"
BottomRight = "28"
TopLeft = "28"
TopRight = "28" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.025">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "31"
BottomRight = "31"
TopLeft = "31"
TopRight = "31" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.05">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "34"
BottomRight = "34"
TopLeft = "34"
TopRight = "34" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.075">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "37"
BottomRight = "37"
TopLeft = "37"
TopRight = "37" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime = "0:0:0.1">
<DiscreteObjectKeyFrame.Value>
<CornerRadius BottomLeft = "40"
BottomRight = "40"
TopLeft = "40"
TopRight = "40" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.ExitActions>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Это решение явно останавливает анимацию состояния IsChecked
, чтобы позволить анимации MultiTrigger
анимировать Border
. Он не слишком сильно изменяет первоначально опубликованный Style
, поскольку добавляет только управление Storyboard
.
<Style x:Key = "ToggleButtonStyle"
TargetType = "ToggleButton">
<Setter Property = "BorderThickness"
Value = "1" />
<Setter Property = "Template">
<Setter.Value>
<ControlTemplate TargetType = "ToggleButton">
<Border x:Name = "Border"
Background = "{TemplateBinding Background}"
BorderBrush = "{TemplateBinding BorderBrush}"
BorderThickness = "{TemplateBinding BorderThickness}">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property = "IsMouseOver"
Value = "True">
<Setter Property = "Cursor"
Value = "Hand" />
</Trigger>
<Trigger Property = "IsPressed"
Value = "True">
<Setter Property = "VerticalAlignment"
Value = "Bottom" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property = "IsMouseOver"
Value = "True" />
<Condition Property = "IsChecked"
Value = "False" />
</MultiTrigger.Conditions>
<MultiTrigger.EnterActions>
<!-- Stop previous animation that block the targeted property from being animated -->
<StopStoryboard BeginStoryboardName = "EnterIsCheckedStoryboard" />
<StopStoryboard BeginStoryboardName = "ExitIsCheckedStoryboard" />
<BeginStoryboard>
<Storyboard BeginTime = "0:0:0">
...
</Storyboard>
</BeginStoryboard>
</MultiTrigger.EnterActions>
<MultiTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
...
</Storyboard>
</BeginStoryboard>
</MultiTrigger.ExitActions>
</MultiTrigger>
<Trigger Property = "IsChecked"
Value = "True">
<Setter Property = "BorderThickness"
Value = "3" />
<Trigger.EnterActions>
<BeginStoryboard x:Name = "EnterIsCheckedStoryboard">
<Storyboard>
...
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard x:Name = "ExitIsCheckedStoryboard">
<Storyboard>
...
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
У меня были трудности с реализацией первого предложения в качестве RadioButton, где предыдущая ошибка все еще возникала, но только в определенных ситуациях (не могла сознательно воспроизвести). Однако альтернативное решение работает как шарм. Спасибо за подробный ответ, теперь я лучше понимаю, как работают анимации и визуальные состояния.
Не могли бы вы показать минимальный образец, чтобы воспроизвести проблему?