1

I have complicated animation, which has to run at startup and every time when mouse leaves buttons (I have many buttons)

<Window.Triggers>
    <EventTrigger RoutedEvent="Loaded">
        <BeginStoryboard x:Name="storyboard">
            <Storyboard>
                ...
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>

    <!-- I have to do this for every button -->
    <EventTrigger RoutedEvent="MouseEnter" SourceName="button1">
        <SeekStoryboard BeginStoryboardName="storyboard" Offset="0"/>
        <PauseStoryboard BeginStoryboardName="storyboard"/>
    </EventTrigger>
    <EventTrigger RoutedEvent="MouseLeave" SourceName="button1">
        <ResumeStoryboard BeginStoryboardName="storyboard"/>
    </EventTrigger>
</Window.Triggers>

Instead of creating new animations I reuse just one, which was played in Loaded, this is why here Seek.., Pause.. and ResumeStoryboard.

Everything works fine, but there is a lot of duplicated code.

I started thinking of making a style for buttons to avoid WET xaml coding. At first, I tried this (simply to see if idea is good)

<Button x:Name="button1">
    <Button.Triggers>
        <EventTrigger RoutedEvent="MouseEnter">
            <SeekStoryboard BeginStoryboardName="storyboard" Offset="0"/>
            ...

Not very surprisingly I got an exception

System.Windows.Media.Animation Warning: 6 : Unable to perform action because the specified Storyboard was never applied to this object for interactive control.; Action='Seek'; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='56868664'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; TargetElement='System.Windows.Controls.Button'; TargetElement.HashCode='23765798'; TargetElement.Type='System.Windows.Controls.Button'

I have feeling it should be something to do with Storyboard.Target..., but trying to give window name produces same error

<Window x:Name="window"
    ...
    <SeekStoryboard BeginStoryboardName="storyboard" Offset="0" Storyboard.TargetName="window"/>

What should I do? My aim is to have button style (defined in window resources), but I have problem to target storyboard within window triggers from child control event trigger.

Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • I don't see any effort showing that you were trying to put the code in Button's Style. In Button's Style, how could you set the `Triggers` property by this ``? it should be `...` – King King Oct 20 '14 at 12:32
  • Targeting storyboard doesn't works, so it won't work in the style either. [SeekStoryboard](http://msdn.microsoft.com/en-us/library/system.windows.media.animation.seekstoryboard.aspx) uses `SourceName` approach (which works, but required to type same peace of xaml 100 times) and I fail to find anything anywhere of how to target storyboard from `SeekStoryboard` call in child control event. After hour of unsuccessful searching I made this post. Ideally it should be fixed by style (I think). But I am unsure. And to continue I have to fight over said issue first. – Sinatr Oct 20 '14 at 12:58

1 Answers1

1

I understand that you have many buttons. So the Storyboard in Window.Triggers should be used for all the buttons (animate once when the window is loaded). However the Storyboard requires a specific target, how could you set it for all the buttons? We can only duplicate the code and target each one by its name. That's the first problem.

The second problem appears while attempting to solve the first problem. When placing all the SeekStoryboard, PauseStoryboard, ... inside a Button's Style, it cannot find the Storyboard's name in the Style scope. That means the only way to fix this is place the Storyboard right inside the Button's Style. But then we cannot access this Storyboard easily inside Window.Triggers. However to solve the first problem we should also not place the Storyboard inside Window.Triggers scope. Why don't we trigger the Storyboard when the Button's Loaded instead? Then we can place all inside a Button's Style. The following code should solve all your problems:

<Window.Resources>
    <Storyboard x:Key="sb">
      <!-- more code here ... -->
    </Storyboard>       
    <Style TargetType="Button">
        <Style.Triggers>
           <DataTrigger Binding="{Binding Visibility,  
                             RelativeSource={RelativeSource AncestorType=Window},
                             Mode=OneTime}" Value="Visible">
               <DataTrigger.EnterActions>
                 <StaticResource ResourceKey="beginSb"/>
               </DataTrigger.EnterActions>
           </DataTrigger>
           <EventTrigger RoutedEvent="MouseEnter">
               <SeekStoryboard BeginStoryboardName="storyboard" Offset="0"/>
               <PauseStoryboard BeginStoryboardName="storyboard"/>
           </EventTrigger>
           <EventTrigger RoutedEvent="MouseLeave">
               <ResumeStoryboard BeginStoryboardName="storyboard"/>
           </EventTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
King King
  • 61,710
  • 16
  • 105
  • 130
  • 1
    Great idea, I was stumbled with that `Loaded` event. It makes me think what I must define it in the first place - on windows loading. Let me check if it works without further problems ;) – Sinatr Oct 20 '14 at 14:41
  • You remember I said *complicated animation*? It contains many animations for different elements, including buttons. And it looks like using `Storyboard.TargetName` inside style triggers is not allowed (getting compile error). Also, this complicated animation has to run *once*, and **not for each button** (I am a bit tired already, didn't think about this in previous comment). Do you think your solution is still apply with those *new* conditions? – Sinatr Oct 20 '14 at 14:56
  • @Sinatr I supposed the animation is used only for Buttons. You can try placing the `Storyboard` (inside EventTrigger for `Loaded`) in some such as `Window.Resources`, there the `Storyboard.TargetName` can find the all names in the window. – King King Oct 20 '14 at 15:01
  • @Sinatr see my updated code for details. I also use a `DataTrigger` to listen to `Visibility` of window with OneTime binding. It mimics listening to `IsLoaded`, however `IsLoaded` cannot be used because it's not notified when changed. I would like to hear some response from you to know if the problem is fixed. – King King Oct 20 '14 at 15:38
  • Right now code is still the same as it was when I asked question. Binding to visibility is a nice trick, i will definitely check it tomorrow (too busy for today, sorry). – Sinatr Oct 21 '14 at 08:33
  • Still problem with `TargetName` (this time in run-time): *A Storyboard tree in a Style cannot specify a TargetName. Remove TargetName 'someTarget'*. I found [this](http://stackoverflow.com/a/24774823/1997232) answer, which say I can only use `TargetName` in templates. But I have not clue how to deal with it. Button is already templated (I am simply adding `BasedOn` in your style definition), redefining template again completely is not an option. Maybe `DataTemplate` will do? – Sinatr Oct 22 '14 at 08:21
  • @Sinatr saying `TargetName` can only be used in templates is wrong. We can still use `TargetName` in other cases such as when using `Storyboard` directly inside some `EventTrigger` which is added directly to `Triggers` property of the element. I'd knew about that wrong saying before and that's why I was surprised when seeing a guy use it directly inside an `EventTrigger` (added directly to `Triggers` property of some element). The correct rule should be **TargetName cannot be used in Style scope** and usually used in Template scope. – King King Oct 22 '14 at 09:09
  • For your problem, I managed to use `{x:Reference}` to reference some named element and set to `Storyboard.Target` instead. It works but it requires all the named elements ***to be declared before any Button*** in your XAML code. So unless you use Grid layout (or Canvas, ..) so that we can place the elements where we want no matter the order of the elements declaration. If using templates, all the named elements of course should be inside the template scope, otherwise we cannot still use `TargetName`. – King King Oct 22 '14 at 09:11
  • 1
    Thanks for all your effort. Having a bit of repeatable code looks much better than all these over-complications and conventionalities ;). I will have to support that code and don't want to break my brains in a few years trying to understand why I can't move buttons away from `Grid`. – Sinatr Oct 22 '14 at 09:17