0

Seems I always get stumped by what should be the simplest of things...
I have a UserControl with a custom bool DependencyProperty 'GpoModule'.

The default is set to false.

I have a FadeIn animation triggered when 'GpoModule' = true and a FadeOut animation triggered when 'GpoModule' = false.

My problem is, When the control is loaded, the FadeOut animation is triggered, I want the animation to be triggered only when the property is changed (or at least not on the default value when loaded). I suspect I may have to define an event trigger of some kind, but I'm hoping to avoid that.

DependencyProperty:

    public bool GpoModule {
        get { return (bool)GetValue(GpoModuleProperty); }
        set { SetValue(GpoModuleProperty, value); }
    }
    public static readonly DependencyProperty GpoModuleProperty =
        DependencyProperty.Register("GpoModule", typeof(bool),
            typeof(ModuleIndicator), new UIPropertyMetadata(false));

Animation:

 <UserControl.Resources>
    <Duration x:Key="D">0:0:0.6</Duration>
    <Storyboard x:Key="SbFadeOut">
        <DoubleAnimation Storyboard.TargetProperty="(FrameworkElement.Width)"
                         From="38" To="0"
                         Duration="{StaticResource D}">
            <DoubleAnimation.EasingFunction>
                <QuadraticEase EasingMode="EaseInOut" />
            </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
        <DoubleAnimation Storyboard.TargetProperty="(FrameworkElement.Height)"
                         From="38"
                         To="0"
                         Duration="{StaticResource D}">
            <DoubleAnimation.EasingFunction>
                <QuadraticEase EasingMode="EaseInOut" />
            </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
        <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)"
                         From="1"
                         To="0"
                         Duration="{StaticResource D}">
            <DoubleAnimation.EasingFunction>
                <QuadraticEase EasingMode="EaseInOut" />
            </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
    </Storyboard>
    <Storyboard x:Key="SbFadeIn">
        <DoubleAnimation Storyboard.TargetProperty="(FrameworkElement.Width)"
                         From="0" To="38"
                         Duration="{StaticResource D}">
            <DoubleAnimation.EasingFunction>
                <QuadraticEase EasingMode="EaseInOut" />
            </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
        <DoubleAnimation Storyboard.TargetProperty="(FrameworkElement.Height)"
                         From="0"
                         To="38"
                         Duration="{StaticResource D}">
            <DoubleAnimation.EasingFunction>
                <QuadraticEase EasingMode="EaseInOut" />
            </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
        <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)"
                         From="0"
                         To="1"
                         Duration="{StaticResource D}">
            <DoubleAnimation.EasingFunction>
                <QuadraticEase EasingMode="EaseInOut" />
            </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
    </Storyboard>        
</UserControl.Resources>

Animated Element:

   <Viewbox x:Name="GpoIndicator"
            VerticalAlignment="Center" HorizontalAlignment="Center"
                              Stretch="Fill" Width="0" Height="0">
        <Grid Height="38" Width="38">
            <Ellipse Height="38"
                    Fill="{DynamicResource App.Primary.Light}"
                    Stroke="{DynamicResource App.Border}" />
            <TextBlock Width="38"
                    Height="38"
                    Margin="0"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Top"
                    FontFamily="{DynamicResource AppFont}"
                    FontSize="30"
                    FontWeight="Bold"
                    Foreground="{DynamicResource ResourceKey=App.Accent.Secondary}"
                    Padding="0,2,0,0"
                    Text="G"
                    TextAlignment="Center"
                    TextWrapping="Wrap"/>
        </Grid>
        <Viewbox.Style>
            <Style TargetType="Viewbox">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding GpoModule, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ModuleIndicator}}}"
                             Value="True">
                        <DataTrigger.EnterActions>
                            <BeginStoryboard Name="GpoIn">
                                <Storyboard>
                                    <StaticResource ResourceKey="SbFadeIn" />
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.EnterActions>
                        <DataTrigger.ExitActions>
                            <RemoveStoryboard BeginStoryboardName="GpoIn" />
                        </DataTrigger.ExitActions>                            
                    </DataTrigger>
                    <DataTrigger Binding="{Binding GpoModule, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ModuleIndicator}}}"
                                 Value="False">
                        <DataTrigger.EnterActions>
                            <BeginStoryboard Name="GpoOut">
                                <Storyboard>
                                    <StaticResource ResourceKey="SbFadeOut" />
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.EnterActions>
                        <DataTrigger.ExitActions>
                            <RemoveStoryboard BeginStoryboardName="GpoOut" />
                        </DataTrigger.ExitActions>
                    </DataTrigger>                       
                </Style.Triggers>
            </Style>
        </Viewbox.Style>
    </Viewbox>

Thanks!

Edit: I should have mentioned there are two other 'Indicator' objects / children - 'RedesignIndicator' and 'NovellIndicator'. Each with respective bool DependencyProperties.
I tried using multiple conditions before asking the question. My first attempt was setting a condition to 'IsLoaded' which had no effect.
My second attempt was to use another Dependency property 'AnimationLocked', with default set to true, and tried two variations on this.
One: set AnimationLocked to false on the property changed callback (this had the side effect of triggering the FadeOut animation on one of the other child objects and
Two: set AnimationLocked to false on the UserControl.Loaded event (which had no effect)

  • Possible duplicate of [How can I provide multiple conditions for data trigger in WPF?](http://stackoverflow.com/questions/905932/how-can-i-provide-multiple-conditions-for-data-trigger-in-wpf), you have to add another condition (another dependency property `IsLoaded` or maybe there is already related property to check). – Sinatr May 17 '16 at 11:33
  • The load form always causes an initial triggering of event. You can set a property to false in the constructor and then set true at end of form load. Then all your methods can ignore events until property is set true. – jdweng May 17 '16 at 11:44
  • @Sinatr & jdweng: I tried both of these. I tried using a MultiDataTrigger and adding a condition. Once for 'IsLoaded'and once for another custom DependencyProperty 'AnimationLocked' which would only set to false after the GpoModule property was changed. Both had the side effect of disabling or enabling the animation at unwanted times. – Rob Blinzler May 17 '16 at 12:32
  • @jdweng, I tried your suggestion, using the UserControl.Loaded event, but it had no effect. – Rob Blinzler May 17 '16 at 12:44
  • Show what you have tried (by editing question). It's kinda hard to judge, it should work. Perhaps you don't understand the idea of multidata triggers: you shouldn't use `ExitActions` anymore (or, well, it will keep triggering, killing the idea). Create 2 data triggers, which only run animation in `EnterActions`, both triggers should use `IsLoaded` value which you set in control `Loaded` event. – Sinatr May 17 '16 at 12:59
  • Put a break point on the first time event is called. The look at Call Stack to see where it is being called from. – jdweng May 17 '16 at 13:00
  • @Sinatr, why shouldn't I use ExitActions anymore? I would ask you to submit your suggestions as an answer, that way I can also accept it if it works – Rob Blinzler May 17 '16 at 13:26

2 Answers2

0

You can use multidata trigger:

<Viewbox.Style>
    <Style TargetType="Viewbox">
        <Style.Triggers>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding IsLoaded}" Value="True" />
                    <Condition Binding="{Binding GpoModule}" Value="True" />
                </MultiDataTrigger.Conditions>
                <MultiDataTrigger.EnterAction>
                    ... fade in animation
                </MultiDataTrigger.EnterAction>
            </MultiDataTrigger>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding IsLoaded}" Value="True" />
                    <Condition Binding="{Binding GpoModule}" Value="False" />
                </MultiDataTrigger.Conditions>
                <MultiDataTrigger.EnterAction>
                    ... fade out animation
                </MultiDataTrigger.EnterAction>
            </MultiDataTrigger>
        </Style.Triggers>
    </Style>
</Viewbox.Style>

IsLoaded is another dependency property which is set to true in control Loaded event handler.

This way animation will only occurs when IsLoaded == true.

P.S.: not sure if you have to set IsLoaded = false in control Unloaded event handler.

Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • thanks for the effort, this is more or less what I already tried. Just to be sure, I tried several variations - and what I found was that IsLoaded is called before the Data / MultiData Triggers get set, so in effect, it is always true. – Rob Blinzler May 18 '16 at 14:06
  • Still curious why you think I shouldn't use ..Trigger.ExitAction? – Rob Blinzler May 18 '16 at 14:28
0

The solution I finally settled on was adding an 'AnimationLocked' bool DependencyProperty for each of the three children 'Module Indicators' on my control. 'AnimationLocked' has a default of true and is set to false in the 'Module' bool DependencyProperty callback, which makes sure the animation is blocked until the property is manually changed by the first user interaction. I was hoping for some built in way that would prevent a trigger from 'triggering' on the default value. This seems very inelegant and I'm still open to other suggestions.

UserControl code-behind:

 public bool GpoAnimationLocked
    {
        get { return (bool)GetValue(GpoAnimationLockedProperty); }
        set { SetValue(GpoAnimationLockedProperty, value); }
    }
    public static readonly DependencyProperty GpoAnimationLockedProperty =
        DependencyProperty.Register("GpoAnimationLocked", typeof(bool),
            typeof(ModuleIndicator), new UIPropertyMetadata(true));

    public bool GpoModule {
        get { return (bool)GetValue(GpoModuleProperty); }
        set { SetValue(GpoModuleProperty, value); }
    }
    public static readonly DependencyProperty GpoModuleProperty =
        DependencyProperty.Register("GpoModule", typeof(bool),
            typeof(ModuleIndicator), new UIPropertyMetadata(false, GpoSelectionPropertyChanged));

    protected virtual void GpoSelectionChanged(DependencyPropertyChangedEventArgs e) {
        GpoAnimationLocked = false;
    }
    private static void GpoSelectionPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
        ((ModuleIndicator)sender).GpoSelectionChanged(e);
    }

UserControl xaml (the child 'indicator' that is animated:

    <Viewbox x:Name="GpoIndicator"
             VerticalAlignment="Center"
             HorizontalAlignment="Center"
             Stretch="Fill"
             Width="0"
             Height="0">
        <Grid Height="38"
              Width="38">
            <Ellipse Height="38"
                     Fill="{DynamicResource App.Primary.Light}"
                     Stroke="{DynamicResource App.Border}" />
            <TextBlock Width="38"
                       Height="38"
                       Margin="0"
                       HorizontalAlignment="Left"
                       VerticalAlignment="Top"
                       FontFamily="{DynamicResource AppFont}"
                       FontSize="30"
                       FontWeight="Bold"
                       Foreground="{DynamicResource ResourceKey=App.Accent.Secondary}"
                       Padding="0,2,0,0"
                       Text="G"
                       TextAlignment="Center"
                       TextWrapping="Wrap" />
        </Grid>
        <Viewbox.Style>
            <Style TargetType="Viewbox">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding GpoModule, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ModuleIndicator}}}"
                                 Value="True">
                        <DataTrigger.EnterActions>
                            <BeginStoryboard Name="In">
                                <Storyboard>
                                    <StaticResource ResourceKey="SbFadeIn" />
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.EnterActions>
                        <DataTrigger.ExitActions>
                            <RemoveStoryboard BeginStoryboardName="In" />
                        </DataTrigger.ExitActions>
                    </DataTrigger>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding GpoAnimationLocked, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ModuleIndicator}}}"
                                       Value="False" />
                            <Condition Binding="{Binding GpoModule, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ModuleIndicator}}}"
                                       Value="False" />
                        </MultiDataTrigger.Conditions>
                        <MultiDataTrigger.EnterActions>
                            <BeginStoryboard Name="Out">
                                <StaticResource ResourceKey="SbFadeOut" />
                            </BeginStoryboard>
                        </MultiDataTrigger.EnterActions>
                        <MultiDataTrigger.ExitActions>
                            <RemoveStoryboard BeginStoryboardName="Out" />
                        </MultiDataTrigger.ExitActions>
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
        </Viewbox.Style>
    </Viewbox>