Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EventTrigger Not Working When Declared in Window.Resources in WPF

I am new to WPF, so I may be missing something essential, but I have experimented and tried to come up with an explanation for the following phenomenon, to no avail. Basically, the following code works (displays animation):

    <Window.Resources>
    <Storyboard x:Key="LoadStoryBoard"
AutoReverse="True"
RepeatBehavior="Forever">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="button1" 
                Storyboard.TargetProperty="(Button.Opacity)">
            <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.4" />
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Window.Resources>
...
<Button x:Name="button1" Grid.Column="0" Grid.Row="1" Style="{StaticResource Load}">
<Button.Triggers>
    <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard Storyboard="{StaticResource LoadStoryBoard}" />
            </EventTrigger>
</Button.Triggers>
</Button>

However, when I try to put the eventrigger in the Load Style in the following, the animation ceases to appear:

    <Window.Resources>
    <Storyboard x:Key="LoadStoryBoard"
AutoReverse="True"
RepeatBehavior="Forever">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="button1" 
                Storyboard.TargetProperty="(Button.Opacity)">
            <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.4" />
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Window.Resources>
...
<Style x:Key="Load" TargetType="Button">
...
<Style.Triggers>
    ...
    <EventTrigger RoutedEvent="Loaded">
                <BeginStoryboard Storyboard="{StaticResource LoadStoryBoard}" />
            </EventTrigger>
</Style.Triggers>
</Style>
like image 862
Clement Hoang Avatar asked Jul 23 '13 04:07

Clement Hoang


2 Answers

In the Style of triggers can not use objects with TargetName, such animation. To do this, they are placed in triggers template <ControlTemplate.Triggers>. Quote from link:

TargetName is not intended for use within the Triggers collection of a Style. A style does not have a namescope, so it does not make sense to refer to elements by name there. But a template (either DataTemplate or ControlTemplate) does have a namescope.

The following works:

<Window.Resources>
    <Storyboard x:Key="LoadStoryBoard" AutoReverse="True" RepeatBehavior="Forever">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="button1" Storyboard.TargetProperty="(Button.Opacity)">
            <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.4" />
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>       

    <Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
        <Setter Property="Background" Value="Green" />
        <Setter Property="Foreground" Value="White" />
        <Setter Property="FontSize" Value="14" />
        <Setter Property="FocusVisualStyle" Value="{x:Null}" />
        <Setter Property="SnapsToDevicePixels" Value="True" />

        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Border x:Name="button1" CornerRadius="0" Background="{TemplateBinding Background}">
                        <Grid>
                            <ContentPresenter x:Name="MyContentPresenter" Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" />                                
                        </Grid>
                    </Border>

                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="Orange" />
                        </Trigger>

                        <EventTrigger RoutedEvent="Button.Loaded">
                            <BeginStoryboard Storyboard="{StaticResource LoadStoryBoard}" />
                        </EventTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

<Grid>
    <Button Name="TestButton" Style="{StaticResource ButtonStyle}" Width="100" Height="30" Content="Test" Grid.Column="0" Grid.Row="1" />
</Grid>

Notice that now TargetName in the template specified in the Border: <Border x:Name="button1" .../>.

Note: Or, you can just remove the Storyboard.TargetName, since it triggers the style is not supported.

like image 160
Anatoliy Nikolaev Avatar answered Oct 01 '22 18:10

Anatoliy Nikolaev


You are correct that the EventTrigger is not working, but it is not because it was declared in the Resources section. To see this, you can move your style directly into the Button declaration where it still does not work:

<Button x:Name="button1" Grid.Column="0" Grid.Row="1">
    <Button.Style>
        <Style TargetType="Button">
            <Style.Triggers>
                <EventTrigger RoutedEvent="Button.Loaded">
                    <BeginStoryboard Storyboard="{StaticResource LoadStoryBoard}" />
                </EventTrigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
</Button>

However, if we move the declaration of the Animation from the Resources section, it works again:

<Button x:Name="button1" Grid.Column="0" Grid.Row="1">
    <Button.Style>
        <Style TargetType="Button">
            <Style.Triggers>
                <EventTrigger RoutedEvent="Button.Loaded">
                    <BeginStoryboard>
                        <Storyboard AutoReverse="True" RepeatBehavior="Forever">
                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Button.Opacity)">
                                <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.4" />
                            </DoubleAnimationUsingKeyFrames>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Style.Triggers>
        </Style>
    </Button.Style>
</Button>

So it seems as though the problem has something to do with the Storyboard declared in the Resources section not being ready by the time the Loaded event fires. There is a similar problem noted in this post.

However, just to confuse things more, if we then put the full declaration for the Animation into the Style declared in the Resources section, then now the Style works:

<Window.Resources>
    <Style x:Key="Load" TargetType="Button">
        <Style.Triggers>
            <EventTrigger RoutedEvent="Button.Loaded">
                <BeginStoryboard>
                    <Storyboard AutoReverse="True" RepeatBehavior="Forever">
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Button.Opacity)">
                            <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="0.4" />
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<Button x:Name="button1" Grid.Column="0" Grid.Row="1" Style="{StaticResource Load}" />

I could speculate as to why this happens, but I'm guessing that there are very few WPF developers that really know why everything is the way that it is... I've learnt that if a particular declaration method works, use it and if not, try a different one.

Background

In WPF, there are four places where we can define Triggers; Style.Triggers, ControlTemplate.Triggers, DataTemplate.Triggers and FrameworkElement.Triggers (eg. Button.Triggers).

Basically, there is a huge flaw in the FrameworkElement.Triggers TriggerCollection in that it only accepts triggers of type EventTrigger. This can be seen on the FrameworkElement.Triggers Property page at MSDN where the following definition is given as to what this property can accept:

One or more defined EventTrigger elements. Each such trigger is expected to contain valid storyboard actions and references. Note that this collection can only be established on the root element of a page.

The MSDN property pages for the other trigger properties each announce that they can accept either Zero or more TriggerBase objects, or One or more TriggerBase objects.

Furthermore, there are distinct rules that different triggers follow - a unified approach would have certainly helped newcomers to WPF. From the FrameworkElement.Triggers Property page:

This property does not enable you to examine triggers that exist as part of styles in use on this element. It only reports the collection of triggers that are literally added to the collection, either in markup or code. Elements do not typically have such elements existing by default (through a template for instance); it is more common for triggers that come from control compositing to be established in styles instead.

In terms of behavior (and trying to establish which effect came from which element's declared Triggers collection), both the triggering condition and the trigger effect might be on this element, or might be on its child elements in the logical tree. Note that if you use lifetime events such as Loaded to get this collection, the child element's triggers might not yet be fully loaded, and the collection will be smaller than it would truly be at run time.

Note that the collection of triggers established on an element only supports EventTrigger, not property triggers (Trigger). If you require property triggers, you must place these within a style or template and then assign that style or template to the element either directly through the Style property, or indirectly through an implicit style reference.

From the DataTemplate.Triggers Property page at MSDN:

If you are creating triggers within a data template, the setters of the triggers should be setting properties that are within the scope of the data template. Otherwise, it may be more suitable to create triggers using a style that targets the type that contains the data. For example, if you are binding a ListBox control, the containers are ListBoxItem objects. If you are using triggers to set properties that are not within the scope of the DataTemplate, then it may be more suitable to create a ListBoxItem style and create triggers within that style.

Unfortunately, all this extra information doesn't actually answer your question as to why the animation resource does not work in the Style resource, but hopefully now, you can see that the whole Trigger area is a bit of a complicated, messy area. Not being an expert myself, I just tend to use whichever method of declaring Triggers that works.

I hope that helps in some way.

like image 34
Sheridan Avatar answered Oct 01 '22 19:10

Sheridan