Основной проблемой в вашем коде является порядок, в котором оцениваются триггеры, и то, как они взаимодействуют друг с другом.
Когда программа запускается, ни одна кнопка не проверяется, поэтому ни один из триггеров не применяется.Каждая кнопка отображается в состоянии по умолчанию.
Когда пользователь нажимает кнопку переключения, в первую очередь происходит переключение состояния кнопки.Поскольку кнопка запускается со значением по умолчанию false
, ее новое значение становится true
.В этот момент ваши триггеры начинают вступать в силу.
Нажатие, например, правой кнопки, переключает собственное значение IsChecked
на true
и заставляет триггер левой кнопки установить свою IsChecked
значение до false
.Все идет нормально.Это делает то, что вы хотите: вы получаете красную кнопку, где вы нажали, и зеленую кнопку, где вы хотите, чтобы пользователь нажимал дальше.Это связано с тем, что триггер IsChecked
для «правой» кнопки активирован (отключение и окрашивание в красный цвет), а также триггер RightButton.IsChecked
для «левой» кнопки (включение и окрашивание в зеленый цвет).
Но когда теперь нажата «левая» кнопка, все идет не так.Кнопка «влево» переходит в проверенное состояние (IsChecked
становится true
).Это запускает триггер данных «правой» кнопки, который ссылается на «левую» кнопку, в результате чего «правая» кнопка снова становится непроверенной.Но помните, что кнопка «влево» была только окрашена в зеленый цвет в первую очередь из-за триггера, ссылающегося на кнопку «вправо» (на предыдущем шаге).Теперь, когда «правая» кнопка больше не проверена, этот триггер больше не применяется.Кнопка «влево» возвращается в свое неокрашенное состояние по умолчанию!
Хуже того, когда триггер деактивируется, он возвращает все свойства, которые устанавливают триггеры, в их прежние состояния, которые они имели при активации триггера.Таким образом, «левой» кнопке теперь также присваивается свойство IsChecked
, равное false
(это было установлено до активации триггера).Теперь LeftButton.IsChecked
триггер «правой» кнопки также больше не применяется, и эта кнопка возвращается в состояние по умолчанию.
Фу!Вы следили за всем этим?Это немного запутанно, но если вы проследите те же шаги, которые я описал выше (запишите различные состояния свойств по ходу работы, чтобы помочь отслеживать вещи), я думаю, это станет ясным.
Теперь,Что с этим делать?Ну, я чувствую, что принципиальная проблема здесь в том, что вы неправильно используете механику триггера, чтобы попытаться выполнить процедурные действия в XAML.Круговые зависимости могут привести к некоторым очень странным и трудным для отладки проблемам.
XAML на самом деле не совсем подходит для процедурных задач вообще.Это работает лучше всего, когда оно используется только декларативным способом.Но в той мере, в которой поддерживаются процедурные задачи, именно функции анимации в WPF действительно делают это.Таким образом, одним из решений было бы перенести желаемое поведение в анимации, которые у вас уже есть:
<EventTrigger SourceName="RightButton" RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LeftButton"
Storyboard.TargetProperty="IsChecked"
Duration="0"
FillBehavior="HoldEnd">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<s:Boolean>False</s:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RightButton"
Storyboard.TargetProperty="IsChecked"
Duration="0"
FillBehavior="HoldEnd">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<s:Boolean>True</s:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<MatrixAnimationUsingPath Storyboard.TargetName="RectMatrixTransform" Storyboard.TargetProperty="Matrix" DoesRotateWithTangent="False" Duration="0:0:0.100">
<MatrixAnimationUsingPath.PathGeometry>
<PathGeometry>
<PathFigure StartPoint="0,0">
<LineSegment Point="100,0"/>
</PathFigure>
</PathGeometry>
</MatrixAnimationUsingPath.PathGeometry>
</MatrixAnimationUsingPath>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
<EventTrigger SourceName="LeftButton" RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="RightButton"
Storyboard.TargetProperty="IsChecked"
Duration="0"
FillBehavior="HoldEnd">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<s:Boolean>False</s:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LeftButton"
Storyboard.TargetProperty="IsChecked"
Duration="0"
FillBehavior="HoldEnd">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<s:Boolean>True</s:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<MatrixAnimationUsingPath Storyboard.TargetName="RectMatrixTransform" Storyboard.TargetProperty="Matrix" DoesRotateWithTangent="False" Duration="0:0:0.100">
<MatrixAnimationUsingPath.PathGeometry>
<PathGeometry>
<PathFigure StartPoint="100,0">
<LineSegment Point="0,0"/>
</PathFigure>
</PathGeometry>
</MatrixAnimationUsingPath.PathGeometry>
</MatrixAnimationUsingPath>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
При этом используется анимация по ключевому кадру для принудительного присвоения свойству IsChecked
соответствующего значения для каждого элемента управления в ответ на щелчок.События.
Вы можете заметить, что я использую ObjectAnimationUsingKeyFrames
вместо BooleanAnimationUsingKeyFrames
.Если бы вы могли убедиться, что ваши кнопки-переключатели всегда имели ненулевые значения для IsChecked
, вы могли бы использовать более строго типизированную анимацию.Но, чтобы воспроизвести ваше точное поведение, упрощая аспекты визуального состояния кнопки, я счел необходимым по умолчанию установить начальное состояние IsChecked
кнопки на null
, что несовместимо с элементом BooleanAnimationUsingKeyFrames
(поскольку он поддерживает толькоnullable bool
values).
И причина, по которой я оказался в этом затруднительном положении, заключается в том, что при таком подходе, основанном на анимации, имеет смысл просто создавать простые триггеры стиля на основе значения свойства IsChecked
, чтобыобновить визуальное состояние кнопки.В ресурсе стиля, который у вас уже есть, я добавил это:
<p:Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="Background" Value="Red"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="IsEnabled" Value="True"/>
<Setter Property="Background" Value="Green"/>
</Trigger>
</p:Style.Triggers>
Таким образом, вы просто декларативно указываете, каким именно должен быть внешний вид кнопки в каждом состоянии, а не пытаетесь реагировать на изменения в состоянии другого кнопки.Гораздо проще.
(Не обращайте внимания на дополнительное p:
пространство имен XML ... Я просто использую это, чтобы обойти ошибочную обработку Stack Overflow форматирования XAML, где он запутывается, когда видит «стиль» в XML и «забывает "он имеет дело с XML.)
Обратите внимание, что если вы не возражаете инициализировать состояния кнопок переключения либо на true
, либо на false
, так, чтобы применялся один или другой из этих триггеров,тогда вы можете использовать элемент BooleanAnimationUsingKeyFrames
, потому что свойство IsChecked
всегда будет ненулевым значением.Но, чтобы сохранить ваше поведение, при котором кнопки имели по умолчанию неокрашенное состояние перед нажатием любой из них, я инициализировал для каждой значение null
для IsChecked
:
<ToggleButton Content="Right" Name="RightButton" Grid.Column="0" Grid.Row="0"
IsChecked="{x:Null}"
Style="{StaticResource DefaultToggleButtonTemplate}"/>
<ToggleButton Content="Left" Name="LeftButton" Grid.Column="1" Grid.Row="0" Height="30" Width="100"
IsChecked="{x:Null}"
Style="{StaticResource DefaultToggleButtonTemplate}"/>
Примечание, конечночто поскольку теперь вы можете использовать один и тот же стиль для обеих кнопок, нет необходимости объявлять собственный стиль для каждой из них.Каждая кнопка может ссылаться на ресурс стиля, который вы уже создали, и к которому я добавил сеттеры для визуального состояния кнопки.
Теперь все, что говорит: мне гораздо удобнее в C #, чем в XAMLи я считаю, что XAML недостаточно хорошо выражает процедурные вещи, чтобы я мог использовать его для этой цели большую часть времени.Если бы я попытался реализовать такое поведение, как у вас, я бы выполнил все процедурные аспекты в модели представления и оставил бы XAML для обработки только с визуальным состоянием: анимация для перемещенияпрямоугольник, конечно, но также и специфическое форматирование объектов кнопки, то есть триггеры для состояния IsChecked
.
Затем вместо добавления раскадровок для обновления состояний IsChecked
на основе кнопкищелкнув, я бы поместил всю эту логику в модель представления, вызванную объектом ICommand
, связанным со свойством кнопки Command
.Затем этот комментарий будет обрабатывать переключение свойства IsChecked
через его собственные свойства, связанные с кнопками.
Но если вы хотите поместить все в XAML, описанный выше подход будет работать в вашем примере.