IsChecked устанавливается на второй, а не на первый щелчок (ToggleButton (XAML)) - PullRequest
1 голос
/ 19 сентября 2019

надеюсь, что вы сможете мне помочь с моей проблемой:

У меня есть две кнопки и прямоугольник, что я хочу сделать: 1. переместить прямоугольник в направлении кнопки (правая кнопка перемещаетсяэто вправо, LeftButton влево) (Это работает нормально) 2. Деактивируйте кнопку, которая была нажата последней, чтобы переместить прямоугольник (Rectangle вправо, RightButton деактивирована), кроме того, кнопка окрашена в красный цвет.3. Активируйте кнопку, которая была деактивирована ранее (прямоугольник находится в правильном положении, левая кнопка активна), кроме того, кнопка имеет зеленый цвет.

шаблон кнопки

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="{x:Type ToggleButton}" x:Key="DefaultToggleButtonTemplate" x:Name="DefaultToggleButtonTemplate">
        <Style.Setters>
            <Setter Property="FontSize" Value="16"/>
            <Setter Property="FontFamily" Value="Arial"/>
            <Setter Property="Width" Value="100"/>
            <Setter Property="Height" Value="30"/>
            <Setter Property="HorizontalContentAlignment" Value="Center"/>
            <Setter Property="VerticalContentAlignment" Value="Top"/>


            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ToggleButton}" >
                        <Grid Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" ClipToBounds="True">
                            <Rectangle x:Name="buttonFrame" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
                                   Stroke="{TemplateBinding Background}" RadiusX="5" RadiusY="5" StrokeThickness="1" Fill="Transparent"/>
                            <Rectangle x:Name="buttonBody" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                                   Stroke="Transparent" RadiusX="5" RadiusY="5" StrokeThickness="1" Fill="{TemplateBinding Background}"/>
                            <TextBlock x:Name="buttonText" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}"/>
                        </Grid>

                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style.Setters>
    </Style>
</ResourceDictionary>
    <Window x:Class="StyleResourceDictionariesDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>

    </Window.Resources>
    <StackPanel>
        <Button Style="{StaticResource DefaultButtonTemplate}" Content="ok"/>
        <Button Style="{StaticResource DarkDefaultButtonTemplate}" Content="cancel"/>
        <Button Style="{StaticResource FreakShowButtonTemplate}" Content="FREak"/>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="2*"/>
                <RowDefinition Height="*"/>

            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <Grid.Triggers>
                <EventTrigger SourceName="RightButton" RoutedEvent="Button.Click">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <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>
                            <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>
            </Grid.Triggers>
            <ToggleButton Content="Right" Name="RightButton" Grid.Column="0" Grid.Row="0" >
                <ToggleButton.Style>
                    <Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource DefaultToggleButtonTemplate}">
                        <Style.Triggers>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsChecked" Value="True"/>
                                </MultiTrigger.Conditions>
                                <MultiTrigger.Setters>
                                    <Setter Property="IsEnabled" Value="False"/>
                                    <Setter Property="Background" Value="Red"/>
                                </MultiTrigger.Setters>
                            </MultiTrigger>
                            <MultiDataTrigger>
                                <MultiDataTrigger.Conditions>
                                    <Condition Binding="{Binding ElementName=LeftButton, Path=IsChecked, NotifyOnSourceUpdated=True}" Value="True"/>
                                </MultiDataTrigger.Conditions>
                                <MultiDataTrigger.Setters>
                                    <Setter Property="IsChecked" Value="False"/>
                                    <Setter Property="IsEnabled" Value="True"/>
                                    <Setter Property="Background" Value="Green"/>
                                </MultiDataTrigger.Setters>
                            </MultiDataTrigger>

                        </Style.Triggers>
                    </Style>
                </ToggleButton.Style>
            </ToggleButton>
            <ToggleButton Content="Left" Name="LeftButton" Grid.Column="1" Grid.Row="0" Height="30" Width="100">
                <ToggleButton.Style>
                    <Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource DefaultToggleButtonTemplate}">
                        <Style.Triggers>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsChecked" Value="True"/>
                                </MultiTrigger.Conditions>
                                <MultiTrigger.Setters>
                                    <Setter Property="IsEnabled" Value="False"/>
                                    <Setter Property="Background" Value="Red"/>
                                </MultiTrigger.Setters>
                            </MultiTrigger>
                            <MultiDataTrigger>
                                <MultiDataTrigger.Conditions>
                                    <Condition Binding="{Binding ElementName=RightButton, Path=IsChecked, NotifyOnSourceUpdated=True}" Value="True"/>
                                </MultiDataTrigger.Conditions>
                                <MultiDataTrigger.Setters>
                                    <Setter Property="IsChecked" Value="False"/>
                                    <Setter Property="IsEnabled" Value="True"/>
                                    <Setter Property="Background" Value="Green"/>
                                </MultiDataTrigger.Setters>
                            </MultiDataTrigger>
                        </Style.Triggers>
                    </Style>
                </ToggleButton.Style>
            </ToggleButton>



            <Rectangle x:Name="RectangleToMove" Grid.Column="0" Grid.Row="1" Height="100" Width="20"  Fill="Black" Stroke="Black">
                <Rectangle.RenderTransform>
                    <MatrixTransform x:Name="RectMatrixTransform"/>
                </Rectangle.RenderTransform>
            </Rectangle>



        </Grid>
    </StackPanel>
</Window>

текущее поведение: 1. Программа запускается, кнопки серые, прямоугольник находится в левом положении, обе кнопки активны 2. Нажатие одной кнопки приводит к перемещению прямоугольника в направлении, окрашивает нажатую кнопку в красный цвет (другой зеленый) и деактивируетсам (активирует другой).3. Нажав сейчас другую кнопку, сбросьте цвета кнопок (обе), переместите прямоугольник в другую позицию (обе кнопки все еще активны) (вот в чем проблема) 4. Нажатие этой же кнопки приведет к ожидаемому поведению 2.

Так как же заблокировать поведение сброса 3.?

edit: я смог изолировать источник проблемы: на шаге 3 нажатие кнопки НЕ устанавливает IsChecked-Property в значение true, на шаге 4 IsChecked устанавливается в значение true, как и ожидалось.Вопрос в том, почему он не установлен в true на шаге 3 или что я могу сделать, чтобы это реализовать?

1 Ответ

1 голос
/ 23 сентября 2019

Основной проблемой в вашем коде является порядок, в котором оцениваются триггеры, и то, как они взаимодействуют друг с другом.

Когда программа запускается, ни одна кнопка не проверяется, поэтому ни один из триггеров не применяется.Каждая кнопка отображается в состоянии по умолчанию.

Когда пользователь нажимает кнопку переключения, в первую очередь происходит переключение состояния кнопки.Поскольку кнопка запускается со значением по умолчанию 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, описанный выше подход будет работать в вашем примере.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...