WPF:DataTemplate内GoToStateAction触发器无法正常工作
我创建了一个包含Button的DataTemplate,Button内部定义了VisualState,ControlTemplate是包含ContentPresenter的Grid,Button也设置了对应内容。我期望绑定的数据变化时切换VisualState,但GoToStateAction触发器没生效;不过用普通DataTrigger直接修改视觉属性能正常工作。
业务场景里用VisualState更便捷,但不确定问题出在VisualState定义(虽通过Blend添加)、Interaction.Triggers还是GoToStateAction本身?
附相关XAML代码:
<DataTemplate x:Key="UpgradeElementDataTemplate" DataType="{x:Type commonData:UpgradeItemViewModel}" > <Button x:Name="MainButton" Style="{StaticResource ButtonWithSoundStyle}" Command="{Binding CommandToInvoke}" CommandParameter="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="BoughtState"> <VisualState x:Name="Bought"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.Background)"> <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{StaticResource HDC-Yellow}"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Available"/> <VisualState x:Name="NotAvailable"> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.Opacity)"> <EasingDoubleKeyFrame KeyTime="00:00:00" Value="0.5"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <b:Interaction.Triggers> <b:DataTrigger Binding="{Binding IsBought}" Value="True"> <b:GoToStateAction StateName="Bought"/> </b:DataTrigger> <b:DataTrigger Binding="{Binding CanBeBought}" Value="True"> <b:GoToStateAction StateName="Available"/> </b:DataTrigger> <b:DataTrigger Binding="{Binding IsUnavailable}" Value="True"> <b:GoToStateAction StateName="NotAvailable"/> </b:DataTrigger> </b:Interaction.Triggers> <Button.Template> <ControlTemplate TargetType="{x:Type Button}"> <Grid> <ContentPresenter /> </Grid> </ControlTemplate> </Button.Template> <Canvas> <Grid ClipToBounds="False" Canvas.Bottom="0" Canvas.Right="0"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="80"/> </Grid.RowDefinitions> <Border x:Name="border" Background="{DynamicResource Typo-Darker}" CornerRadius="10" Height="80" Width="117" Grid.RowSpan="2" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Right"> <Image x:Name="image" Source="{Binding ImagePath}" Opacity="1"/> </Border> </Grid> </Canvas> </Button> <DataTemplate.Triggers> <!-- this data trigger works properly and the color gets changed, so the value of IsBought is properly changing in the view model. --> <DataTrigger Binding="{Binding IsBought}" Value="True"> <Setter TargetName="border" Property="Background" Value="{DynamicResource Yellow}" /> </DataTrigger> </DataTemplate.Triggers> </DataTemplate>
注:DataTemplate的Triggers中绑定IsBought的DataTrigger可正常修改border背景色,说明ViewModel中的IsBought值确实在变化。
核心问题
- VisualState放置位置错误:你把
VisualStateManager.VisualStateGroups直接放在Button的内容层级里,但Button的ControlTemplate已替换为仅含ContentPresenter的Grid,VisualState未作用在正确的可视化树上——border和image属于Button的Content内容,而VisualState定义在Button本身,Storyboard无法找到目标元素(目标元素在ContentPresenter内部,不在Button的ControlTemplate可视化树中)。 - GoToStateAction目标对象不明确:默认
GoToStateAction作用于触发元素(这里是Button),但Button的ControlTemplate里没有VisualState需要修改的元素,状态切换自然无法生效。
修复步骤
步骤1:将VisualStateGroups移到Button的ControlTemplate内部
把VisualState定义放到Button的ControlTemplate中,同时将Button的Content内容整合到ControlTemplate里,确保Storyboard能找到目标元素:
<Button.Template> <ControlTemplate TargetType="{x:Type Button}"> <Grid> <!-- 原Button的Content内容移到此处 --> <Canvas> <Grid ClipToBounds="False" Canvas.Bottom="0" Canvas.Right="0"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="80"/> </Grid.RowDefinitions> <Border x:Name="border" Background="{DynamicResource Typo-Darker}" CornerRadius="10" Height="80" Width="117" Grid.RowSpan="2" Grid.Row="1" VerticalAlignment="Bottom" HorizontalAlignment="Right"> <Image x:Name="image" Source="{Binding ImagePath}" Opacity="1"/> </Border> </Grid> </Canvas> <!-- VisualStateGroups移到ControlTemplate根元素下 --> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="BoughtState"> <VisualState x:Name="Bought"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="(Border.Background)"> <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{StaticResource HDC-Yellow}"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Available"/> <VisualState x:Name="NotAvailable"> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="image" Storyboard.TargetProperty="(UIElement.Opacity)"> <EasingDoubleKeyFrame KeyTime="00:00:00" Value="0.5"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> </ControlTemplate> </Button.Template>
步骤2:明确GoToStateAction的TargetObject
在GoToStateAction中指定目标为Button,确保状态切换作用于包含VisualState的ControlTemplate所在元素:
<b:Interaction.Triggers> <b:DataTrigger Binding="{Binding IsBought}" Value="True"> <b:GoToStateAction StateName="Bought" TargetObject="{Binding ElementName=MainButton}"/> </b:DataTrigger> <b:DataTrigger Binding="{Binding CanBeBought}" Value="True"> <b:GoToStateAction StateName="Available" TargetObject="{Binding ElementName=MainButton}"/> </b:DataTrigger> <b:DataTrigger Binding="{Binding IsUnavailable}" Value="True"> <b:GoToStateAction StateName="NotAvailable" TargetObject="{Binding ElementName=MainButton}"/> </b:DataTrigger> </b:Interaction.Triggers>
步骤3:移除Button的Content内容
因为Canvas内容已移到ControlTemplate里,删除Button原有的<Canvas>...</Canvas>内容,避免重复渲染。
步骤4:处理状态互斥(可选)
若三个状态为互斥关系(同一时间仅能处于一个状态),建议调整DataTrigger逻辑,比如使用MultiDataTrigger或设置状态切换优先级,避免多个状态同时触发导致冲突。
额外说明
- VisualState必须定义在ControlTemplate的根元素下,或包含目标元素的可视化树根节点,否则Storyboard无法找到对应TargetName的元素。
- DataTemplate的Triggers能生效是因为它直接作用于DataTemplate内的元素,而VisualState的Storyboard基于ControlTemplate的可视化树查找元素,两者作用范围不同。
内容的提问来源于stack exchange,提问作者user2396632

