Blend Trigger vs. WPF Trigger
Asked Answered
R

3

7

It seems to me that there is a major difference between the Blend-style Triggers found in the Interactivity namespace, and the classic Triggers available via Styles, ControlTemplates, etc, in WPF (I supposed this probably applies to SilverLight as well).

In WPF, when you set a Trigger with a Setter, you get two behaviours: if the trigger condition is met, the Setter is applied. However, once a Trigger is no longer satisfied, the previous value is restored. This is tremendously useful when programming UI.

I've tried to code a DataTrigger similarly using the Blend way: I applied ChangePropertyAction to my control, and have it trigger off of Interactivity DataTrigger using a Binding to my ViewModel.

<Rectangle x:Name="SecondaryHighlightBackground" Fill="#FF505050">
            <i:Interaction.Triggers>
                <ei:DataTrigger Binding="{Binding IsHighlighted}" Value="Value">
                    <ei:ChangePropertyAction PropertyName="Opacity" Value="0.5"/>
                </ei:DataTrigger>
            </i:Interaction.Triggers>
...
</Rectangle>

So this worked as expected... except I was stunned to discover that when I flip the IsHighlighted value to false, that the original Opacity of 0% is not restored (I set that lower down). To double check this, I wrote this example to verify:

<Window x:Class="TestWpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Name="Window"
        Title="MainWindow"
        Width="640"
        Height="480">

    <Grid x:Name="LayoutRoot">
        <Button Name="button1"
                Width="173"
                Height="57"
                Margin="10,10,0,0"
                HorizontalAlignment="Left"
                VerticalAlignment="Top">
            <Button.Style>
                <Style TargetType="Button">
                    <Setter Property="Content" Value="foo" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding IsMouseOver, ElementName=button1}" Value="True">
                            <Setter Property="Content" Value="bar" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Button.Style>
        </Button>
    </Grid>
</Window>

The expected behaviour is that if you if mouse over the button, the content changes to 'bar'. Moving the mouse away restores content to 'Foo', as specified by the Style. The style sets the Content to avoid the property precedence issue.

My question is: Is two-way triggering really just missing in the Blend extensions, or is there a way to enable this somehow?

Am I missing something? I thought that was the very point of triggers, that they have this two-way functionality. I would like to continue using the Blend interactivity actions because I find them useful and intuitive, but this kind of thing forces me back into coding XAML by hand.

Redmon answered 6/3, 2013 at 21:13 Comment(0)
C
2

As stated in other answers there seems to be no automatic reversal of the value set by an Interaction trigger, but you can explicitly unset the value set by the trigger using DependencyProperty.UnsetValue and hence have the dependency property current value revert to the previous value like this:

<i:Interaction.Triggers>
  <ei:DataTrigger Binding="{Binding IsHighlighted}" Value="True">
    <ei:ChangePropertyAction PropertyName="Opacity" Value="0.5"/>
  </ei:DataTrigger>
  <ei:DataTrigger Binding="{Binding IsHighlighted}" Value="False">
    <ei:ChangePropertyAction PropertyName="Opacity" 
                             Value="{x:Static DependencyProperty.UnsetValue}"/>
  </ei:DataTrigger>
</i:Interaction.Triggers>

Disclaimer: I have actually only tested this on .NET Core 3.1 using the new open source packaging of xaml behaviors. Kept the classic i/ei namespaces for consistency with the original question.

Calculate answered 4/1, 2020 at 10:5 Comment(0)
Q
1

If I understand correctly you just have to provide the consideration for the other value in your trigger as well since it won't do it by default as you've taken control of that condition. Something like;

<i:Interaction.Triggers>
   <ei:DataTrigger Binding="{Binding IsHighlighted}" Value="True">
         <ei:ChangePropertyAction PropertyName="Opacity" Value="0.5"/>
   </ei:DataTrigger>
   <ei:DataTrigger Binding="{Binding IsHighlighted}" Value="False">
         <ei:ChangePropertyAction PropertyName="Opacity" Value="0"/>
   </ei:DataTrigger>
</i:Interaction.Triggers>
Quatrain answered 6/3, 2013 at 21:26 Comment(9)
That's my issue though - I don't want to hardcode the return value, I want it to assume the instance value from before the trigger was triggered. This is how normal WPF/Silverlight triggers work.Redmon
I think you're maybe talking in the sense of using a VisualStateManager wherein a default State is specified so it knows it has a particular instance to revert back to. However when you're using Triggers as such be it in WPF or SL I'm pretty sure you'll always have to control the revert to another state since there isn't a default available as there is when using States instead of Triggers since you're manually changing its properties except in the event of an immutable object where you couldnt change it anyway.Quatrain
No, that's really not what I mean. I added a trivial example above to illustrate normal trigger behaviour.Redmon
Ah you're referring to the expected behavior of a Style Trigger, not a Data Trigger. Therein lies your issue. A Style Trigger has an instance to fall back to via the original Style Template where essentially the same thing is happening because it has a default way of handling a default set already. DataTrigger operates differently.Quatrain
Triggers are triggers. I replaced the Trigger above with a DataTrigger to further illustrate the point. The reason the trigger is in a style is because the definition of a normal Control only allows EventTriggers in the Trigger member.Redmon
If that were the case, why would it not work the way you expect it to? Your new example just shows further what I'm talking about. You've embedded it in a .Style still, so it still has a default Style Template in the background handling the default it switches back to. Where as your issue is on a Rectangle that doesn't have a Style template handling it in the background, hence why you have to specify what it returns too in the instance...Quatrain
I'm not sure that the Style here is relevant. The opacity is set to 0 from the get-go (just trust me on that one). The expression DataTrigger can override the opacity one way (when triggered), but it doesn't restore the previous value or allow WPF to infer the value through regular precedence rules. The only reason I put the trigger in a style in my other example is because WPF doesn't allow regular property triggers (Data or otherwise) in the Triggers section of a control. The bottom-line though: Do blend triggers allow for a revert to default value. I think the answer is No.Redmon
@Redmon i think your right , i'm also looking for a way to make it fallback to the default value , i'm gonna try embedding i:Interaction.Triggers inside a style with a setter before it, maybe this way WPF would know what to revert back to .Gelid
can't seem to add Triggers in a Style.Gelid
U
1

Yes, you have to put both conditions with this Blend triggers, there is no return to default. On the other side with Blend Triggers you can CallMethodAction and that can be very useful with animations. Also with Blend triggers you have ControlStoryBoard action, and much more, but in your described case you are better with "normal" triggers.

Upper answered 12/10, 2018 at 22:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.