Tools and Toys Update

I’ve moved the source code for the WPF Media Player to CodePlex: http://codeplex.com/WPFMediaPlayer.  Currently the Player sits at version 0.1, but I’ve been working on a version 0.2 for a while now.  Ok, I was working on it earlier this year, and it’s been sitting dormant for a while now.  Ahem.  That should be changing in another week or so.  I’m wrapping up a personal project of mine, and then I focus on a handful of programming projects that I’ve been putting off for a while – this being one of them.

The WPF Media Player is actually the second project I’ve moved to CodePlex – the first being NAntRunner, now hosted at http://codeplex.com/nantrunner.  I have an update in mind for NAntRunner as well, which would bring it up to version 0.3.  Hopefully that will be posted in the next month or so.

Now that I’ve moved these to CodePlex, I’ve been thinking about the other items still hosted on my Tools and Toys page:

  • The SSMS Scripter seems like a natural to post there, so that one will probably be next to move.  I have an update in mind for that one as well, but it’s not clear to me how to go about making it.  I’ll have to give it some more thought first.
  • In the four months since I wrote and started using String Cleaning, I’ve found it to be eminently useful, but the core of the application is the one line of source described in this post.  I can’t really see it changing all that much, so I don’t think that’s worth setting up a new project for.  I have considered porting it to a PowerShell script (which would hopefully decrease the cold-start time), but that’s pretty low on the priority list at the moment.
  • One-Three Back Solitaire hasn’t changed in years, and it was really my learning application for Test-Driven Development.  At this point I don’t think that will move over either.  If I ever decided to add on to it (write a web-based leader board using WCF, or something like that) I’d probably move it over first, just to simplify things.

There you have it.  I’ll keep you posted on progress on these projects, and I’d love to hear your feedback on what you find useful, and what you’d like to see in future releases.

Advertisement

Brian Noyes / WPF Update on .NET Rocks

Brian Noyes delivered an update on the state of WPF on .NET Rocks a couple of months ago, and the podcast aired on February 12.  (Ok, so I’m a little behind on my podcast consumption).  Brian made a couple of good points about WPF – things that he’d like to see changed or improved in later versions of the technology.

The first point he made was on WPF animations, and seeing as I just did a series on those last month (WPF Animations Part 1, Part 2, and Part 3) I think it’s good to put the two side by side.  Brian made the comment that XAML actually takes us backward in some respects.  For example, XAML gives us the ability to define and fire animations in the markup directly.  However, there is no way to automate a test for an animation defined like this.  Fair point, but how would you automate a test for an animation defined and triggered in code?  Would you trigger it in a unit test, and then examine the final position of the object being animated?  Don’t get me wrong – I am a firm believer in the value of automated testing, but I also think 100% test-case coverage is not realistic.  My gut says that the 80-20 rule applies here (80% of the test cases will require 20% of the time to automate the tests for them), and automating tests for animations falls into the 20% bucket.

The second point deals with inheritance.  You can’t inherit the declarative part of a WPF control, meaning the XAML markup.  While I haven’t hit this roadblock yet, I can the truth in it and see why it will be a problem.

LIke Brian, I am also curious and slightly anxious to see where Microsoft and the development community takes this technology.  Perhaps there is an elegant solution to these two issues waiting to be found.

UPDATE: Less than an hour after I posted this, I came across a blog post by Scott Guthrie where he describes a feature that shipped with Silverlight 2.0 Beta 1 – a cross-platform, cross-browser unit testing harness for Silverlight.  While Scott doesn’t explicitly mention testing animations, you might just be able to do with this.  For the last week I’ve been checking out Selenium from the team at ThoughtWorks for browser-based UI testing.  If this Silverlight test harness can test something other than just Silverlight apps, that might be a second product to consider.

Dude, where’s my .CancelButton?

One of the minor road bumps I encountered was trying to build the WPF Media Player, and especially the Songs popup list, was how to denote the Close button’s Click event handler to fire when the user hit the Escape key.

SongsList

In Winforms, the CancelButton property of the form did this trick nicely.  There was also an AcceptButton property that determined which button would be “clicked” when the user hit the Enter key.  These two properties were invaluable when creating Winforms utilities.  When you’re opening and closing the applications over and over (you know, like when you’re debugging the utility initially), being able to slap the Esc or Enter buttons to test something saves a lot of time.

In WPF, however, things have changed.

These two properties have moved from the form to the buttons themselves.  There are now button properties for IsCancel and IsAccept.  As the names suggest, these take Boolean values and define which button will be “clicked” when the user hits Esc or Enter, respectively.

I can’t say that I’m entirely pleased with this move.  With a Winforms form, you could only have at most one button defined as the Cancel button, and at most one as the Accept button (these two could theoretically map to the same button, but doing that would confuse the user, to whom “Enter” means “come on, do something, I don’t have all day!” and “Esc” means “no, wait, stop, I didn’t want that!”).

But with the WPF forms, and the IsCancel property (for example) attached to the buttons, what would happen if I had more than one button defined with “IsCancel=True”?

Would they both fire when the user hit “Esc”?  That could get a little messy, not to mention confusing for the user.

Would the first button in the tab order fire, and the others be ignored?  That could be confusing for the maintenance programmer.

As it turns out, having multiple buttons defined with “IsCancel=True” does neither of these things.  In fact, hitting Esc repeatedly simply tabs between the two (or more) buttons defined in this way.  The Click events never fire; the focus merely jumps around the screen.

The lesson here is that WPF makes it easier to shoot (or is that click?) yourself in the foot.  Take care that when you define a Cancel or Accept button for your form you only define at most one of each.

Ultimately I decided not to use the IsCancel property in the 0.1 release to implement the Esc-key functionality.  However, I’m glad I know where it’s moved to, even if it is a little more quirky than before.

Debugging XAML

One of the questions that was raised during the WPF presentation I made at the most recent Microsoft Developers of Southwest Michigan users group meeting was: “Can you debug the XAML?”  It was a very good question, but one that I didn’t have an answer for.

Well, doing a simple search for “debug XAML” turned up quite a few posts about debugging XAML issues.  While I haven’t seen anything as sophisticated as being able to step through the animations as they’re running, but there are a few ways to get more information back about exceptions being thrown.  Here is a small collection:

  1. Get line numbers of XAML issues (Peter Himschoot): http://www.u2u.info/Blogs/Peter/Lists/Posts/Post.aspx?ID=183
  2. More descriptive XAML exception details (Rob Relyea): http://rrelyea.spaces.live.com/Blog/cns!167AD7A5AB58D5FE!1625.entry
  3. More on XamlParseException (Laurent Bugnion): http://geekswithblogs.net/lbugnion/archive/2007/03/14/108728.aspx
  4. Xaml Runtime Parsing Exceptions (c/o Erno de Weerd): http://blogs.infosupport.com/ernow/archive/2006/02/22/3899.aspx
  5. “Debugging Dynamic XAML in Silverlight” (Julia Lerman): http://blogs.devsource.com/devlife/content/silverlight/debugging_dynamic_xaml_in_silverlight_1.html

So, the simple answer is yes, you can debug problems with XAML.  The longer, more qualified answer is that while it’s possible, the support isn’t terribly robust yet but should improve with newer dev environments (Visual Studio, Expression Blend, etc.).

WPF Media Player – Alternate Features

Getting the WPF Media Player to version 0.1 wasn’t all smooth sailing.  In fact the norm was rocky shoals, rocky shoals, The Flying Dutchman attacking, fog, more rocky shoals, and THEN a clear channel.

 

One of the rocky shoals I hit was with the SlideUpAnimation that I talked about previously.  Here is the definition that I eventually went with:

SlideUpAnimation = New Animation.ThicknessAnimation(New Thickness(e.JewelCaseBottomLeftCorner.X, e.JewelCaseBottomLeftCorner.Y – 20, 0, 0), _

     New Thickness(e.JewelCaseBottomLeftCorner.X, e.JewelCaseBottomLeftCorner.Y + 5, 0, 0), _

     New Duration(New TimeSpan(0, 0, 0, 0, 200)))

As I mentioned before, the ThicknessAnimation constructor takes three parameters – the beginning Thickness, the target Thickness, and the Duration in which to adjust the Margin from the former to the latter.  However, my original attempt at getting this to work was the following:

SlideUpAnimation = New Animation.ThicknessAnimation(New Thickness(e.JewelCaseBottomLeftCorner.X, e.JewelCaseBottomLeftCorner.Y + 5, 0, 0), _

     New Duration(New TimeSpan(0, 0, 0, 0, 200)))

I was using a different constructor for ThicknessAnimation here, one that only requires the target Thickness and the Duration.  In all of the other animations defined up to this point, I didn’t need to specify the starting point because I wanted the new animation to start working on the object from wherever it was already.  I thought I would following the same steps here.

What I didn’t take into account on this particular animation is that the JewelCaseExpand control would be capable of moving around the screen, and more so than simply the sliding effect that I was trying to implement.  The JewelCaseExpand control is tied to the location of the JewelCase on the shelf.

So, when I implemented the version without specifying starting location, I found the JewelCaseExpand control actually following my mouse horizontally.  The best effect was when I moused over a JewelCase on the far left of the Shelf, moused away, and then (without touching any JewelCase in between) moused over a JewelCase on the right right.  The JewelCaseExpand control would appear on the left, and go zooming over to the right, and slide down at the same time.

Basically, because I hadn’t changed where the JewelCaseExpand control was left when it was hidden and hadn’t specified the starting point explicitly, the rendering engine remembered where it was and used that as my starting point.  This is a cool feature, but not what I wanted.

 

One of the patches of fog that I encountered was with how I programmatically attached the JewelCaseExpand control to the main form.  As I’ve mentioned previously, there is at most one instance of this control attached to the form.  Most of the form uses the CurrentJewelCaseExpand property to access it, and that property takes care of instantiating it and hooking it up.

Private ReadOnly Property CurrentJewelCaseExpand() As JewelCaseExpand
    Get
        Dim AlreadyExists As Boolean

        AlreadyExists = Not IsNothing(Me._CurrentJewelCaseExpand)
        If (Not AlreadyExists) Then
            Me._CurrentJewelCaseExpand = New JewelCaseExpand()
            Me._CurrentJewelCaseExpand.Name = “MyJewelCaseExpand”
            Me.MainGrid.Children.Add(Me._CurrentJewelCaseExpand)

            ‘ For fun, comment the first and uncomment the second.  Then try mousing over an album.
            Me.RegisterName(Me._CurrentJewelCaseExpand.Name, Me._CurrentJewelCaseExpand)
            ‘Me.RegisterName(Me._CurrentJewelCaseExpand.Name, Me.MainGrid)
       End If

       Return Me._CurrentJewelCaseExpand
    End Get
End Property

Originally, if the object didn’t already exist, I would instantiate it, give it a name, and add it to the Main form’s Children collection.  Apparently that wasn’t enough because I got the following error: “ ‘MyJewelCaseExpand’ name cannot be found in the name scope of ‘WPFMediaPlayer.Main’ ”.  I did some digging, and came across the topic of NameScopes.

Based on the error message, and the articles I was finding online, I pieced together that you have to formally register the name of the control so that the rendering engine will recognize it.  When you create a control in XAML, this is done implicitly, but because I am defining the control in code, I have to do it explicitly.

Conveniently, the System.Windows.Window class provides a method called “RegisterName” (huh, go figure) to do just this.  The method takes two parameters – the name that you’re trying to register, and the scoped element that you’re registering it to.  My first thought was that the “scoped element” was the Grid .  I added the control to the Children collection of the grid, so I should register the name of that control with the grid, right?

Wrong.  I’ve left my original call in the code for your amusement.  Swapping out these two calls to RegisterName gets past the exception, but it was most assuredly NOT the effect I was going for.  As near as I can tell, when you register the name with the Grid, and not the JewelCaseExpand control itself, the grid becomes the target of the animations that we’ve defined for the JewelCaseExpand control.  Here is what the grid looks like with this change, but before I’ve moused over any of the JewelCases on the Shelf:

Initial

And here is after touching the first JewelCase:

First Touch

And the second:

Second Touch

And the third:

Third Touch

You get the idea.  This might be a useful feature in some cases, but not here.  I am still working to get my head wrapped around the concept of NameScopes, so the use of RegisterName is still something of an exercise in code roulette.

 

There you have it.  A couple of interesting features that might be useful in some applications.

 

 

WPF Animations (3 of 3)

In my previous two posts in this series, I’ve gone through defining and triggering animations in markup, and defining animations in markup but triggering them in code.  Now, we’ll look at animations that are both defined and triggered in code.  All of this code comes from the WPF Media Player sample application.

My example here is the ShowAlbumFlyout method of the Main window.  The first couple of lines of this method trigger the control to be made visible (as we discussed in my previous post), but the rest of the method controls the animation involved in showing the JewelCaseExpand user control sliding down, as if it is being taken down off the shelf.

Before we can talk about the animation code, however, I need to point out a trick that I’m employing.  The JewelCase is placed on the form and hidden using the Main.CurrentJewelCaseExpand property.  This property looks at the _CurrentJewelCaseExpand variable on the form, and if it isn’t instantiated already, instantiates it.  When I do this, by default, the control gets dropped onto the very center of the form.

I tried several different ways to make the control move around using (X,Y) coordinates, but the control just doesn’t have support for .Location, .Left, .Top, etc..  I ended up getting it to move via some sleight of hand.  I found that I could adjust the alignment using the control’s HorizontalAlignment and VerticalAlignment properties.  I also found that I could fake moving it by adjusting the control’s Margin thickness.  With these two facts, I can effectively move the JewelCaseExpand control to wherever I need it:

Me.CurrentJewelCaseExpand.HorizontalAlignment = Windows.HorizontalAlignment.Left

Me.CurrentJewelCaseExpand.VerticalAlignment = Windows.VerticalAlignment.Top

Me.CurrentJewelCaseExpand.Margin = New Thickness(e.JewelCaseBottomLeftCorner.X, e.JewelCaseBottomLeftCorner.Y – 20, 0, 0)

This shoves the control into the upper left corner, and then adjusts the Margin’s left thickness to align with the JewelCases’s lower left corner (flush left, and higher by 20 pixels).

Manipulating the Margin like this also sets up how the animation works.  If I want it to appear as though it was sliding down, I just create an animation that adjusts the Margin of the control.  I begin by defining the “SlideUpAnimation”:

SlideUpAnimation = New Animation.ThicknessAnimation(New Thickness(e.JewelCaseBottomLeftCorner.X, e.JewelCaseBottomLeftCorner.Y – 20, 0, 0), _

     New Thickness(e.JewelCaseBottomLeftCorner.X, e.JewelCaseBottomLeftCorner.Y + 5, 0, 0), _

     New Duration(New TimeSpan(0, 0, 0, 0, 200)))

The ThicknessAnimation constructor that I’m using here takes three parameters – the beginning Thickness, the ending Thickness, and the Duration in which to adjust the Margin from the former to the latter.

Next, just like in the markup examples, an Animation is a child element of a Storyboard, so we need to define that here and add the SlideUpAnimation to it:

MyStoryBoard = New Storyboard

MyStoryBoard.Children.Add(SlideUpAnimation)

Next, I need to configure the Storyboard to affect the correct property of the correct object (again, just like in markup):

Storyboard.SetTargetName(SlideUpAnimation, Me.CurrentJewelCaseExpand.Name)

Storyboard.SetTargetProperty(SlideUpAnimation, New PropertyPath(JewelCaseExpand.MarginProperty))

Finally, I start the animation:

MyStoryBoard.Begin(Me.MainGrid)

 

Why did I need to define this and trigger this animation in code?  The JewelCaseExpand control needs to appear relative to the JewelCase that the user mouses over, and that information is not known at design time.

We’ve now looked at what I think are three major buckets for defining and triggering animations in WPF.  The WPF Media Player has several other notable aspects (especially surrounding the use of LINQ to XML), and we’ll look at those in later posts.

 

(On an amusing note, while writing this post I discovered that the name of the Animation object, SlideUpAnimation, is misleading.  Originally I had planned on the animation coming up from the bottom, but I’ve since changed it to drop down, but neglected to update the variable name to match.  I’ll correct that in the next release.)

 

 

WPF Animations (2 of 3)

In my last post on WPF Animations, I covered the basics of defining and using an animation purely in the XAML markup.  Today I’ll be going through the process or and reasons for defining animations in markup, but firing them from code.

I’ll be looking at the FadeIn/FadeOut and ExpandOut/ShrinkDown animation pairs defined in the JewelCaseExpand.xaml markup.  This control is what is displayed when you mouse over a jewel case on the shelf.  I wanted to give the user the rough illusion of the jewel case being pull down off of the shelf, and being turned so they can see the full album jacket.  In version 0.1 I do this with something of a cheat.  Instead of rotating the object so you start by looking at the spine, and end with looking at the front panel, I simply start the jewel case off squashed and then expand it to full size.

Like our animations before, these are defined in the JewelCaseExpand UserControl.Resources tag:

<Storyboard x:Key=”FadeIn”>

    <DoubleAnimationUsingKeyFrames BeginTime=”00:00:00″ Storyboard.TargetName=”MainImage” Storyboard.TargetProperty=”(FrameworkElement.Opacity)”>
        <SplineDoubleKeyFrame KeyTime=”00:00:00.4000000″ Value=”1″/>
    </DoubleAnimationUsingKeyFrames>

</Storyboard>

<Storyboard x:Key=”FadeOut”>

    <DoubleAnimationUsingKeyFrames BeginTime=”00:00:00″ Storyboard.TargetName=”MainImage” Storyboard.TargetProperty=”(FrameworkElement.Opacity)”>
        <SplineDoubleKeyFrame KeyTime=”00:00:00.2000000″ Value=”0″/>
    </DoubleAnimationUsingKeyFrames>

</Storyboard>

<Storyboard x:Key=”ExpandOut”>

    <DoubleAnimationUsingKeyFrames BeginTime=”00:00:00″ Storyboard.TargetName=”MainImage” Storyboard.TargetProperty=”(FrameworkElement.Width)”>
        <SplineDoubleKeyFrame KeyTime=”00:00:00.2000000″ Value=”75″/>
    </DoubleAnimationUsingKeyFrames>

</Storyboard>

<Storyboard x:Key=”ShrinkDown”>

    <DoubleAnimationUsingKeyFrames BeginTime=”00:00:00″ Storyboard.TargetName=”MainImage” Storyboard.TargetProperty=”(FrameworkElement.Width)”>
        <SplineDoubleKeyFrame KeyTime=”00:00:00.2000000″ Value=”15″/>
    </DoubleAnimationUsingKeyFrames>

</Storyboard>

As the names suggest, these are used to fade the control in and out, and expand/shrink it.

The trigger for these animations is not something that can be defined on the JewelCaseExpand control itself because the user will be mousing over a completely separate control (a JewelCase on the Shelf) to activate them.

I also can’t define a trigger in the JewelCase XAML to fire these, again because they aren’t in the same control.  Even if I could cross-reference control resources like this, I wouldn’t want to because that would couple these two user controls together too tightly.

As a result, what I’m left with is having the Main form detect when the JewelCaseExpand control should be made visible, and firing its animations programmatically.

First, let’s detect when the JewelCaseExpand control should be made visible – when the user mouses over a JewelCase object on the Shelf.  While it’s important to determined WHEN the user mouses over a JewelCase, it’s equally important to know WHICH JewelCase they touched.

Each JewelCase object sits on a Shelf object (a Shelf can hold one or more JewelCases).  In the 0.1 release of the Media Player, there is only Shelf shown at a time on the Main form (eventually the application will allow for one or more).  What this leads to the following hierarchy:    Main –> Shelf –> JewelCase

I want each object to respect the boundaries of the other objects up and down the hierarchy, so that something at the top level (like the Main form) isn’t directly manipulating or watching something more than one level away (like the JewelCase).  To do that, I decided to have the lower-level objects throw custom Events to let the higher levels know when something happened.

In the JewelCase control, I have handled the MouseEnter and MouseLeave events.  These throw custom JewelCase.Spotlight and JewelCase.UnSpotlighted events (programmers have the most baddest grammer, don’t we?).  The Shelf object catches these, and rethrows them as Shelf.AlbumSpotlighted and Shelf.AlbumUnSpotlighted.  The Main form catches the Shelf events, and handles them with the Main.ShowAlbumFlyout and Main.HideAlbumFlyout methods.

For the purposes of today’s post, we’re only really concerned about the parts of these two methods that reference the VisibleWithAnimation property of the _CurrentJewelCaseExpand variable.  I’ll discuss how this variable is managed in a later post, but for now understand that this variable points to the one and only JewelCaseExpand user control available to the Main window.  The additional information that is sent back by the Spotlight events is also a topic for later.

By the time the code execution makes it to the ShowAlbumFlyout method, we know that the user has mouse over a JewelCase, and we know which one (similarly when the execution reaches HideAlbumFlyout, we know that the user has moused away from the JewelCase).

Now, on to the second part of our task – firing the animations.  This is handled by the custom JewelCaseExpand property called “VisibleWithAnimation”:

Public Property VisibleWithAnimation() As Boolean
    Get
        Return (Me.Visibility = Windows.Visibility.Visible)
    End Get
    Set(ByVal value As Boolean)
        If (value) Then

            Me.Visibility = Windows.Visibility.Visible
            Me.BeginStoryboard(CType(FindResource(“FadeIn”), System.Windows.Media.Animation.Storyboard))
            Me.BeginStoryboard(CType(FindResource(“ExpandOut”), System.Windows.Media.Animation.Storyboard))

        Else

            Me.BeginStoryboard(CType(FindResource(“FadeOut”), System.Windows.Media.Animation.Storyboard))
            Me.BeginStoryboard(CType(FindResource(“ShrinkDown”), System.Windows.Media.Animation.Storyboard))
            Me.Visibility = Windows.Visibility.Hidden

        End If
    End Set
End Property

 

This property wraps the Visibility property, but it also fires the animations that were defined in the XAML.  The JewelCaseExpand control has a built in method called “BeginStoryboard”, to which I pass the resource that defines the storyboard to run.  When the control should be made visible, I fire the FadeIn and ExpandOut animations (explicity cast to the StoryBoard type).  When the control should be hidden, I fire the FadeOut and ShrinkDown storyboards.

It is important to notice that while the FadeIn and FadeOut animations change the Opacity of the control, it isn’t the same as setting the Visibility to Visible and Hidden.  Visually, the effect is the same – you either see the control or you don’t.  However, settings a control’s Visibility to Hidden allows mouse clicks to register for the controls that it was previously covering.  Setting the Opacity to 0 doesn’t allow this; you can click all you want in the 75×75 pixel area represented by a completely opaque JewelCaseExpand control, and the mouse clicks will not register.

So, I have to not only run the animations, but set the Visibility as well.  This leads to a different problem, and one that still exists in the code above (and the version 0.1 release).  The FadeOut and ShrinkDown animations are designed to run to completion in 2/10 of a second.  The last line of the Property Set clause sets the Visibility to Hidden.  Unfortunately, the two lines above it are executed in a lot less than 2/10 of a second (the animations run asynchronously, so they are STARTED and then execution continues).  What this means is that while the animations do run to completion you never see most of it because the entire control is Hidden well before it can finish.

To get around this, I could handle the Storyboard “Completed” event, which as the name suggests fires when the Storyboard completes.  If I did this, I could put the Visibility assignment there, allowing the animations to run to completion.  I haven’t tried this yet, but that’s the theory and it’s on my TODO: list.

 

We looked at the reasons why the animations needed to be fired within code, but why did I continue to define them in the markup?  Why not put everything in code?  It comes down to how WPF can be used by a real programming team on a real project.

By leaving the animations defined in the markup, we let the designer create and refine them in their XAML tool of choice.  We also allow the designer to change them after the fact, and as long as the names remain the same, the programmer doesn’t have to get back involved.  We de-couple the designer experience from the programmer experience.

 

Next time we’ll go one step further and not only fire the animations in code, but define them there as well.  We’ll also look at the scenarios where this is necessary.

WPF Animations (Part 1 of 3)

I’ve looked back at my posts for the last year, and came to the startling (and rather embarrassing) realization that I haven’t had a truly technical, code-heavy post in 10 months.  Well, that hiatus has now officially come to an end.

I’ve spent the last 3 months working on a WPF Media Player, and I’ll be spending the next several posts illustrating how various pieces of it work.  I’ll start with the animations, move on to the use of LINQ to XML, and then touch on a few miscellaneous points.  The full source code for the initial release (v0.1) can be found on it’s own page off of my blog – WPF Media Player.

I’ve found three main ways for animations to exist in a WPF application.  These might sound rather arbitrary, but I think the differences are significant enough to warrant placing them into separate buckets (significant in how they might be used in a full-blown development shop with designers and developers working side by side, but that is a post for another time).

We’ll start with what I think is the simplest type – animations that are both defined and fired in XAML.  In the Media Player, mousing over an album causes the album on the shelf to fade out slightly.

MouseOut   MouseOver

The effect was intended to allow the user’s eye to be drawn to the jacket that pops up below. To achieve this, I added two basic things to the JewelCase UserControl class that is used to define each object on the shelf.  First, I needed to define the animation itself in the JewelCase.xaml:

<UserControl.Resources>

        <Storyboard x:Key=”DimSelected”>

            <DoubleAnimationUsingKeyFrames BeginTime=”00:00:00″ Storyboard.TargetName=”MainImage” Storyboard.TargetProperty=”(FrameworkElement.Opacity)”>

                <SplineDoubleKeyFrame KeyTime=”00:00:00.2000000″ Value=”.3″/>

            </DoubleAnimationUsingKeyFrames>

        </Storyboard>

 

        <Storyboard x:Key=”UnDimSelected”>

            <DoubleAnimationUsingKeyFrames BeginTime=”00:00:00″ Storyboard.TargetName=”MainImage” Storyboard.TargetProperty=”(FrameworkElement.Opacity)”>

                <SplineDoubleKeyFrame KeyTime=”00:00:00.2000000″ Value=”1″/>

            </DoubleAnimationUsingKeyFrames>

        </Storyboard>

</UserControl.Resources>

 

The “DimSelected” animation modifies the image Opacity, and changes it from 1 (fully visible) to 0.3 (mostly dimmed out) over two-tenths of a second.  The UnDimSelected changes the Opacity back to fully visible in two-tenths of a second.

Next, I needed to wire these animations up to the JewelCase itself.  I did this by adding a UserControl.Triggers section to JewelCase.xaml:

<UserControl.Triggers>

    <EventTrigger RoutedEvent=”Mouse.MouseEnter”>

        <BeginStoryboard Storyboard=”{StaticResource DimSelected}”/>

    </EventTrigger>

    <EventTrigger RoutedEvent=”Mouse.MouseLeave”>

        <BeginStoryboard Storyboard=”{StaticResource UnDimSelected}”/>

    </EventTrigger>

</UserControl.Triggers>

 

The DimSelected animation is run when the user mouses over the image, and the UnDimSelected is run when they mouse away.

A slightly more complex example can be found in Main.xaml.  There, I’m animating two “flyout” menus that contain the Play Controls (upper right corner), and the Play List management controls (lower right corner).

Play Controls   Play List Controls

In this case, the animations ExpandToFullSize and ShrinkToHide are defined once in the Window.Resources section, but then are applied to both DockPanel controls containing these two sets of controls.

The other interesting thing to notice is that the ExpandToFullSize and ShrinkToHide storyboards contain multiple animations that are timed to run one after the other.  The EventTriggers in this case still only contain a reference to a single storyboard (Mouse.MouseEnter fires ExpandToFullSize and Mouse.MouseLeave fires ShrinkToHide), but it’s not hard to imagine the triggers themselves firing multiple storyboards.

Next we’ll look at defining animations in XAML, but firing them from the code-behind.

WPF Media Player Released

I’ve posted Version 0.1 of the Windows Presentation Foundation (WPF) Media Player.  The version number “0.1” understates the capabilities of the player a bit, but I’ve decided to keep with my standard of numbering the first released version of any incomplete piece of software as 0.1.

All of the source code for the player has been posted, and there is a Readme.txt file included with the archive that explains what currently works, and what is coming in a later release.

I wanted to get this version out there now (instead of waiting until it was “complete”) because I am planning a 2-part series for the Microsoft Developers of Southwest Michigan user’s group on the player, the first of which is this Thursday on WPF animations.  The second part will be on LINQ to XML (since the underlying data store for the player is XML currently), and has yet to be scheduled.

I am also planning several blog posts discussing various aspects of the player for the next few weeks, so check back frequently.

As always, please let me know what you think.

WPF Media Player Teaser

This will be like the 15-second movie teasers where you just see some interesting eye candy, maybe a character utters a cool line or two, the credits screen flashes past, and now you’re on to the jeans commercial.

(Movie-guy voice) Next week… something so unbelievable, you won’t, well, believe it.

Coming to an MDSM meeting near you, it’s "WPF Media Player".

(dum, dum, duuuuuum)

The power of Windows Presentation Foundation and LINQ to XML explodes!.  Are you ready for it?

Powered by Qumana