希望我的问题能够帮助我
我有两个按钮和一个矩形,我想做的是: 1.将矩形移至按钮的方向(RightButton将其向右移动,LeftButton则向左移动)(工作正常) 2.停用最后一次按下以移动矩形的按钮(矩形为右,禁用了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.的重置行为?
编辑:我能够找出问题的根源:在步骤3中,按按钮,不要将IsChecked-Property设置为true,在步骤4中,IsChecked设置为true。问题是,为什么在第3步中未将其设置为true,或者我怎么做才能实现呢?
答案 0 :(得分:1)
代码中的基本问题是评估触发器的顺序以及触发器之间的交互方式。
程序启动时,未选中任何按钮,因此没有触发器适用。每个按钮均以默认状态显示。
当用户单击切换按钮时,首先发生的是切换按钮的状态。由于按钮以默认值false
开始,因此其新值变为true
。到那时,您的触发器开始生效。
例如,单击“右”按钮会将其自己的IsChecked
值切换为true
,并使“左”按钮的触发器将其IsChecked
值设置为{{1} }。到目前为止,一切都很好。这可以实现您想要的:单击时有一个红色按钮,希望用户单击下一步时有一个绿色按钮。这是因为激活了“向右”按钮的false
触发器(将其禁用并将其着色为红色),以及为“向左”按钮的IsChecked
触发器已被激活(使其启用并为绿色着色) )。
但是现在单击“左”按钮时,出现了问题。 “左”按钮转换为已检查状态(RightButton.IsChecked
变为IsChecked
)。这将触发引用“左”按钮的“右”按钮的数据触发器,从而导致“右”按钮再次变为未选中状态。但是请回想一下,因为触发器引用了“右”按钮(在上一步中),所以“左”按钮最初只是被涂成绿色。现在不再选择“右键”按钮,该触发器将不再适用。 “左”按钮恢复为默认的无色状态!
更糟糕的是,当触发器停用时,它将使触发器集的所有属性返回到触发器被激活时的先前状态。因此,“左”按钮现在也将其true
属性设置回了IsChecked
(这是在激活触发器之前设置的属性)。现在,“右键”按钮的false
触发器也不再适用,并且该按钮返回其默认状态。
Ph!你遵循了所有这些吗?这有点令人费解,但是如果您跟踪与我上面描述的步骤相同的步骤(在执行过程中记下各种属性状态,以帮助跟踪情况),我认为它将变得很清楚。
现在,该怎么办?好吧,我觉得从根本上来说,问题是您滥用触发机制来尝试在XAML中执行程序操作。循环依赖会导致一些非常奇怪且难以调试的问题。
XAML并不是真的非常适合于程序任务。如果仅以声明方式使用它,则效果最佳。但是在一定程度上支持过程性任务的情况下,WPF中的动画功能才真正做到了这一点。因此,一种解决方案是将所需的行为转移到您已有的动画中:
LeftButton.IsChecked
此操作使用关键帧动画将<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
值)。
我之所以陷入这种困境,是因为使用这种基于动画的方法,仅基于bool
属性值进行简单的样式触发器以更新按钮的视觉状态就更有意义了。在您已经拥有的样式资源中,我添加了以下内容:
IsChecked
这样,您只需声明性地指定按钮在每种状态下的外观,而不是尝试响应 other 按钮状态的变化。简单得多。
(忽略额外的<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>
XML名称空间...我只是用它来解决Stack Overflow对XAML格式的错误处理,当它在XML中看到“样式”并正在处理时,会感到困惑使用XML。)
请注意,如果您不介意将切换按钮的状态初始化为p:
或true
,以便应用其中一个或另一个触发器,则可以使用{{ 1}}元素,因为false
属性将始终为非null值。但是,为了保留您的行为,即在按下按钮之前,按钮具有默认的无色状态,我将每个按钮初始化为BooleanAnimationUsingKeyFrames
的{{1}}值:
IsChecked
请注意,由于您现在可以为两个按钮使用相同的样式,因此无需为每个按钮声明自定义样式。每个按钮都可以引用您已经创建的样式资源,并在其中添加了按钮视觉状态的设置器。
现在,所有这些都说明了:我在C#中比XAML舒服得多,而且我发现XAML不能很好地表达程序性内容,因此我不想在大多数时间用于此目的。如果我尝试实现类似您在此处的行为,那么我将在视图模型中完成所有过程方面的工作,然后让XAML处理 only 的视觉状态:当然,移动矩形,还要移动按钮对象的特定格式,即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}"/>
对象触发按钮的IsChecked
属性。然后,该注释将通过绑定到按钮的自身属性来处理IsChecked
属性的切换。
但是,如果您想将所有内容都放在XAML中,那么我上面描述的方法将适用于您的示例。