views:

180

answers:

1

I am doing some transition animation using WPF and Blend 3. I have two outer usercontrols which is what I want the transition effect to happen on. Using Blend I can make this effect. The problem is however, that I dont know how to trigger this animation inside one of those usercontrols. The storyboard is only in scope for the window holding the two outer usercontrols.

Do I have to do some code behind or is there some easy way to do this?

+1  A: 

Bobjink,

Hello, I think I understand your question, but if I've not, please let me know where I've gone wrong, and I will modify my answer as needed. From what I understand, you want to have two custom user controls that are added to a window. You then want to animate them using a story board that is contained within your window. You want this animation triggered by an event from within the window. If this is what you're wanting, I've come up with the following solution:

Storyboards can be triggered by a number of different items. If you're wanting to stay fully XAML and not wanting to do any code behind animations, you can actually have the storyboards started by a control's events. For my example, I created two controls (MyAwesomeControl and MyAwesomerControl). These two controls are very simple, in that they are both grids that take up 100% width and height of what they are given. That is, the controls will be 50 x 50 if they are contained within a control that has it's height set to 50 x 50. Within the control's grids, I put a simple label to allow them to be distinguished. I also changed the background color of each control to show clear separation of each. Here is the definition code for my controls:

MyAwesomeControl.xaml

<UserControl x:Class="MyAwesomeControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
    <Grid Background="LightBlue">
  <TextBlock Text="Awesome!" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</UserControl>

MyAwesomerControl.xaml

<UserControl x:Class="MyAwesomerControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
 <Grid Background="LightGreen">
  <TextBlock Text="Awesomer!" FontSize="22" HorizontalAlignment="Center" VerticalAlignment="Center" />
 </Grid>
</UserControl>

Next, I added these to my window. So that I could clearly see them in the window, I added a column definition (one column for each control). That is shown here:

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 Title="Window1" Height="300" Width="300">
 <Grid>
  <Grid.ColumnDefinitions>
   <ColumnDefinition />
   <ColumnDefinition />
  </Grid.ColumnDefinitions>



 </Grid>
</Window>

Next I added a reference to my controls (if the controls are in the same application, you only have to add the reference via a namespace declaration like this:

xmlns:me="clr-namespace:MyWpfApplication"

Next, I added the controls to my window, placing one in each column of the grid. I also gave them distinguishable names. Here are the lines of code that I added to the grid:

I then got to the fun part, Animation! I decided to start my animation whenever anyone pushed down any mouse buttons. Then I figured out what my storyboard should do, and added this to the windows triggers. Here is that code:

Now, to explain this a bit further, here is what is happening:

We are adding an EventTrigger to the Window's triggers. This trigger will be initiated by the RoutedEvent property. The event that I chose was the MouseDown event (on the Window control). To this event, I added an action. The action that I added was the BeginStoryboard action. Within this I added my Storyboard which contained my animations. The animations I chose to start were the DoubleAnimations for setting the Width and Height of my controls. The Storyboard.TargetName property sets the named control instance to act upon. The Storyboard.TargetProperty property is the dependency property of the control to act upon. The From and To properties are from where the animation is going from and to. The Duration property tells how long the animation will take from start to finish. The AutoReverse property is setting the animation to return the values to the From value when done.

This all means that when a user clicks on the window, my two controls will at the same time (over the course of 1/2 second) shrink and grow in height or width. The Awesome control will shrink in width and the Awesomer control will shrink in height. When these animations reach the To value, they will reverse and got back to their From value.

For completeness, here is my full Window file:

Window1.xmal

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:me="clr-namespace:MyWpfApplication"
    Title="Window1" Height="300" Width="300">
 <Window.Triggers>
  <EventTrigger RoutedEvent="Window.MouseDown">
   <EventTrigger.Actions>
    <BeginStoryboard>
     <Storyboard>
      <DoubleAnimation Storyboard.TargetName="Awesome" Storyboard.TargetProperty="Width" From="150" To="50" Duration="0:0:0.5" AutoReverse="True"/>
      <DoubleAnimation Storyboard.TargetName="Awesomer" Storyboard.TargetProperty="Height" From="300" To="50" Duration="0:0:0.5" AutoReverse="True"/>
     </Storyboard>
    </BeginStoryboard>
   </EventTrigger.Actions>
  </EventTrigger>
 </Window.Triggers>
 <Grid>
  <Grid.ColumnDefinitions>
   <ColumnDefinition />
   <ColumnDefinition />
  </Grid.ColumnDefinitions>

  <me:MyAwesomeControl x:Name="Awesome" Grid.Column="0" />
  <me:MyAwesomerControl x:Name="Awesomer" Grid.Column="1" />

 </Grid>
</Window>

Here is my final result:

Final Result Collapsed Result

I've added the code to my Google Code project for your download. I've also zipped up the source code and it is available at the following address:

http://stackoverflow-answers-by-scott.googlecode.com/files/1784500.zip

Also, here is a very good reference to WPF animations:

http://dotnetslackers.com/articles/wpf/IntroductionToWPFAnimations.aspx

I hope this helps,

Thanks!


EDIT: Ok, got it figured out. Heres what you do... If the 'Awesomer' control has a button that needs to start a storyboard that is contained in the container (Window1 in my case) this is what you do:

Add the event to your UserControl (MyAwesomerControl) to your code-behind and add the click event for your button which raises your internal event (MyAwesomerControlClick), like so:

Partial Public Class MyAwesomerControl

    Private Sub Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
     RaiseEvent MyAwesomerControlClick(sender, e)
    End Sub

    Public Shared ReadOnly MyAwesomerControlClickEvent As RoutedEvent = EventManager.RegisterRoutedEvent("MyAwesomerControlClick", RoutingStrategy.Tunnel, GetType(RoutedEventHandler), GetType(MyAwesomerControl))

    Public Custom Event MyAwesomerControlClick As RoutedEventHandler
     AddHandler(ByVal value As RoutedEventHandler)
      [AddHandler](MyAwesomerControlClickEvent, value)
     End AddHandler

     RemoveHandler(ByVal value As RoutedEventHandler)
      [RemoveHandler](MyAwesomerControlClickEvent, value)
     End RemoveHandler

     RaiseEvent(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs)
      Dim newEventArgs As RoutedEventArgs = New RoutedEventArgs(MyAwesomerControl.MyAwesomerControlClickEvent)
      [RaiseEvent](newEventArgs)
     End RaiseEvent
    End Event

End Class

Finally, in your Window code, change your EventTrigger code to listen for your event from above, like this:

<EventTrigger SourceName="Awesomer" RoutedEvent="me:MyAwesomerControl.MyAwesomerControlClick">

Notice the EventTrigger now has a SourceName property that points to our 'Awesomer' control. The RoutedEvent changes also, in that it has a 'me:' in front of the event. This isn't required previously as we were listening for a Microsoft routed event, instead of a custom routed event.

I also updated the Google Code project for your download. I've also zipped up the source code and it is available at the following address:

http://stackoverflow-answers-by-scott.googlecode.com/files/1784500.zip

I hope this helps,

Thanks!


EDIT: I updated the code on the Google Code project for your download. I've also zipped up the source code and it is available at the following address:

http://stackoverflow-answers-by-scott.googlecode.com/files/1784500.zip

This now contains a C# project and a VB project.

Hope this helps,

Thanks!


EDIT: In order to get your inner control to send events all the way up the visual tree, you need to change the event type from Tunnel to Bubble. This will mean that any events that fire in children walk up the tree to any parent element that handles their event. Thus, in your scenario, you have a window which contains a storyboard with the animations. You then have a control contained within the window, which contains another control. This inner most control requires the button click to 'Bubble' up to the Window, and start the animation. This should be relativity simple.

I updated the code on the Google Code project for your download. I've also zipped up the source code and it is available at the following address:

http://stackoverflow-answers-by-scott.googlecode.com/files/1784500.zip

hope this helps,

Thanks!

Scott
Not quite what I meant but I like your effort and approach of explaining :) Imagine that your 'Awesomer' usercontrol had a button and only when that button was pushed the same animation would begin. Now since the animation is in window1 but the button is in the usercontrol 'awesomer' you have a problem.
bobjink
I understand the issue and will work on it shortly. This should be possible. Check back soonish.
Scott
I figured it out and updated my post above. I hope this helps, thanks!
Scott
Yes! This is exatly what I want :) If you get the time it would be really helpful if the code was converted to C#. Currently I am having difficulty understanding whats going on in the VB code and trying converting it.
bobjink
I have updated the Google Code project for your download. I've also zipped up the source code and it is available at the following address:http://stackoverflow-answers-by-scott.googlecode.com/files/1784500.zip
Scott
This is great! Was not aware of the add/remove keywords in C# :) Just tested it and it worked. However in my own application my button is actually nested in one more control. So what is the approach to this? Your solution seems to take advantage of that we have the Awsomer control in scope(only one nesting. sourceName="Awsomer.NextInnerUsercontrol" does not work). Hope I dont sound too spoiled. Your last answer has been really helpfull :)
bobjink
Ok, I've got your solution. Please see my edited answer above.
Scott
Thanks once again! It works :) I wonder though if I am on a wrong path since I havent seen much response to this 'problem'? But it is nice finally get it to work.
bobjink
I personally go for creating custom controls rather than User Controls. That, I think, is the more accepted way of doing the custom control type of project. Basically, instead of creating a few different user controls with their own styles, etc. You can create a custom control which goes to the Themes\Generic.xaml file for the styles, etc. This way you have them all in one place, and you can granularly modify the controls and their individual actions. Have a look at this page at msdn: http://msdn.microsoft.com/en-us/library/ms745683(VS.100).aspx
Scott
Thanks :) Will look into it some time
bobjink