简体   繁体   中英

IsChecked is set on second and not on first click (ToggleButton (XAML))

hope you are able to help me, with my Problem:

I have two Buttons and a Rectangle, what i want to do is: 1. moving the rectangle to the direction of the button (RightButton moves it to the right, LeftButton to the Left) (That works fine) 2. Deactivate the Button which was last pressed to move the rectangle (Rectangle is right, RightButton is deactivated), in addition the button is colored red. 3. Activate the Button which was deactivated before (Rectangle is in right position, LeftButton is active), in addition the button is colored green.

the Button's template

<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>

the current behavior is: 1. Program starts, buttons are gray, rectangle is on left position, both buttons are active 2. pressing one button leads to move the rectangle in the direction, color the pressed button red (the other green) and deactivates itself (activates the other). 3. Pressing now the other button, resets the buttons colors (both), move the rectangle to the other position (both buttons still active) (thats the problem) 4. Pressing the same button leads to the expected behavior of 2.

So how to block the resetting behavior of 3.?

edit: i was able to isolate the source of the problem: In step 3 pressing the button do NOT set the IsChecked-Property to true, in step 4 IsChecked is set to true as expected. The Question is, why is it not set to true in step 3 or what can i do to realize it?

The fundamental issue in your code is the order in which the triggers are evaluated and how they interact with each other.

When the program starts, neither button is checked, so none of the triggers apply. Each button is displayed in its default state.

When the user clicks a toggle button, the first thing that happens is that the button's state is toggled. Since the button starts out with the default value of false , its new value becomes true . At that point, your triggers start to take effect.

Clicking the "right" button, for example, toggles its own IsChecked value to true and causes the "left" button's trigger to set its IsChecked value to false . So far, so good. This does what you want: you get a red button where you clicked, and a green button where you want the user to click next. This is because the IsChecked trigger for the "right" button is activated (disabling it and coloring it red), as well as the RightButton.IsChecked trigger for the "left" button (enabling it and coloring it green).

But when the "left" button is now clicked, things go wrong. The "left" button transitions into the checked state ( IsChecked becomes true ). This triggers the "right" button's data trigger that references the "left" button, causing the "right" button to become unchecked again. But recall, the "left" button was only colored green in the first place because of the trigger referencing the "right" button (in the previous step). Now that the "right" button is no longer checked, that trigger no longer applies. The "left" button reverts back to its default, uncolored state!

Worse, when a trigger deactivates, it returns all of the properties that trigger sets to their previous states they had when the trigger was activated. So the "left" button now also has its IsChecked property set back to false (that's what it was set to before the trigger activated). Now the "right" button's LeftButton.IsChecked trigger also no longer applies, and that button goes back to its default state.

Phew? Did you follow all that, It's a bit convoluted, but if you trace the same steps as I describe above (write the various property states down as you go, to help keep track of things). I think it will become clear.

Now, what to do about it? Well, I feel that fundamentally the issue here is that you're misusing the trigger mechanic to try to perform procedural actions in the XAML. Circular dependencies can make for some very odd and hard-to-debug problems.

XAML is not really all that well-suited for procedural tasks at all. It works best when it's used only in a declarative way. But to the extent that procedural tasks are supported, it is the animation features in WPF that really do this. So, one solution would be to move your desired behavior into the animations you already have:

<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>

This uses key frame animation to force the IsChecked property to the appropriate value for each control in response to click events.

You may notice that I use ObjectAnimationUsingKeyFrames instead of BooleanAnimationUsingKeyFrames . If you could ensure that your toggle buttons always had non-null values for IsChecked , you could use the more-strongly-typed animation. But, to reproduce your exact behavior, while simplifying the button visual state aspects, I found it necessary to default the button's initial IsChecked state to null , which is incompatible with the BooleanAnimationUsingKeyFrames element (because it only supports non-nullable bool values).

And the reason I found myself in that predicament is that with this animation-based approach, it makes more sense to just make simple style triggers based on the IsChecked property value to update the button's visual state. In the style resource you already have, I added this:

<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>

This way, you just declaratively specify exactly what the appearance of the button should be in each state, rather than trying to respond to changes in the other button's state. Much simpler.

(Ignore the extra p: XML namespace...I just use that to workaround Stack Overflow's buggy handling of XAML formatting, where it get confused when it sees "style" in the XML and "forgets" it's dealing with XML.)

Note that if you don't mind initializing the toggle buttons' states to either true or false , such that one or the other of those triggers would apply, then you could use the BooleanAnimationUsingKeyFrames element, because the IsChecked property would always be a non-null value. But, in order to preserve your behavior in which the buttons had a default, uncolored state before either is pressed, I initialized each to have a null value for 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}"/>

Note of course that since you can now use the same style for both buttons, there's no need to declare a custom style for each. Each button can just reference the style resource you'd already created, and to which I added the setters for the button's visual state.

Now, all that said: I'm a lot more comfortable in C# than XAML, and I find XAML doesn't express procedural things well enough for me to want to use it for that purpose most of the time. Were I to try to implement a behavior like what you've got here, I would have done all of the procedural aspects in a view model, and left XAML to deal only with visual state: the animation to move the rectangle, of course, but also the specific formatting of the button objects, ie the triggers for the IsChecked state.

Then, rather than adding the storyboards to update the IsChecked states based on the button's click, I would've put all that logic into the view model, triggered by an ICommand object bound to the button's Command property. That comment would then handle the toggling of the IsChecked property, via its own properties bound to the buttons.

But, if you want to put everything in XAML, the approach I describe above will work in your example.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM