I have been trying to consolidate. Most of my work is here, in this blog, and for this site, but I also have a Blog for O’Reilly and a political Blog, and a page where I list the books I’ve written, and a support site and…
About a year ago, I migrated away from my business web site and set up a “portal” to all my on-line “presences.” which, frankly, I slapped together. It is ugly, but its concept is to show an image that is a link to each site or blog.
Since I’m about to tape a series of videos on Templates and the Visual State Manager to coincide with releasing a tutorial on the subject, I thought I’d “play” with the idea of replacing the static images with buttons. Each image could be hovered over and would grow (and identify itself), could be clicked on, etc.
It turned out to be a pretty easy exercise (like many things, a couple hours to figure out, but the next time will take 5 minutes) and will both make a good exercise for the videos and will make my site nicer (once I get around to (a) perfecting the buttons and then (b) switching the rest of the images over!)
For now, you are welcome to take a look, (if you do, be sure to hover over a button, and even more fun, click in the control but not on a button and then hit tab, changing focus).
Just Xaml
The point of this blog post, however, is that a buddy (who is a killer javascript programmer and the best high school math teacher on the planet) wrote to me and asked “how did you make them spin” and in explaining (briefly) i realized that it might be interesting and surprising that there was no non-declarative code; everything was done in Xaml.
What I’m about to write will make a lot more sense after you read the tutorial and see the videos but I’m going to capture it today as I have it in my head, so forgive me for putting the cart of details before the horse of introduction.
Image rotation, and everything else (making it larger, etc.) is just a transition based on the “state” (mouse over, etc.) . There isn’t one line of code in there; it is all declarative Xaml.
<vsm:VisualState x:Name="Focused"> <Storyboard> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="thePanel" Storyboard.TargetProperty= "(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)"> <SplineDoubleKeyFrame KeyTime="00:00:00" Value="360"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </vsm:VisualState>
This is an excerpt of the Visual state group that shows the Visual state for when the panel has the focus.
A storyboard is an animation. Here we’re using a DoubleAnimationUsingkeyFrames which is to say we’re going to go from one discrete state to another (keyframe animation) by changing the value of a double (the angle). The name of the target is “thePanel” which is a stackpanel and the TargetProperty is held in a collection, but is the angle of rotation (all this is built using a tool).
The actual set of keyframes can be many for making a nice animation, but I just spin it so I only need one; at time zero I set the value to 360. Since my duration is set to a fraction of a section, it spins fast.
Here’s the Xaml for when the mouse moves over the button…
<vsm:VisualState x:Name="MouseOver"> <Storyboard> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="thePanel" Storyboard.TargetProperty= "(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"> <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1.75"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="thePanel" Storyboard.TargetProperty= "(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"> <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1.75"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="TagText" Storyboard.TargetProperty="(UIElement.Opacity)"> <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/> <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0.2"/> <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="0.4"/> <SplineDoubleKeyFrame KeyTime="00:00:00.8000000" Value="0.6"/> <SplineDoubleKeyFrame KeyTime="00:00:01" Value="0.8"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </vsm:VisualState>
This time, when the mouse is passed over, the story board does a few things. First, it scales up on the x and y by 1.75 over a short period (not instant cause it looks goofy). It then also tells the TextBlock (which dynamically loads the name of the button from the tag value) to change its opacity from 0 through 0.2, 0.4, 0.6 to 0.8 at the time intervals shown (0.3 seconds, 0.5 seconds 0.8 seconds 1 second, respectively), thus the name fades in to 80% opacity.
The three buttons are all one template button used three times, the declaration of each is in the page. Here’s the declaration of the first:
<Button x:Name="blogButton" VerticalAlignment="Center" HorizontalAlignment="Center" Template="{StaticResource PortalButton}" Grid.Row="1"
Tag="My Blog"> <Button.Content> <Image x:Name="BlogImage" Source="SilverlightBlog.jpg" Width="Auto" Height="Auto" /> </Button.Content> </Button>
The Template statement takes a normal button and tells it to use my custom template
The Tag attribute is used by the template to set the text in the TextBlock that fades in when you hover over the control
The Button.Content attribute fills the templated button with whatever you put in it, in this case the image. This is put in the page rather than the template so that each instance can have its own image.
A second button would look the same but have a different tag and different image, and of course would sit in a different placement in its page.
<Button x:Name="SilverlightButton" VerticalAlignment="Center" HorizontalAlignment="Center" Template="{StaticResource PortalButton}" Grid.Row="1" Tag="Silverlight.Net"> <Button.Content> <Image x:Name="SilverlightImage" Source="SilverlightNet.jpg"
Width="Auto" Height="Auto" /> </Button.Content> </Button>
Event Handlers
I lied about there being zero code ( I told you not to believe anything I say!) . When you click the buttons there is an event handler on the click event in the page that holds the buttons,
void button_handler( object sender, RoutedEventArgs e ) { HtmlWindow win = HtmlPage.Window; string url = string.Empty; Button pb = e.Source as Button; if ( pb != null ) { switch (pb.Name.ToString().ToUpper()) { case "SILVERLIGHTBUTTON": url = "http://silverlight.net"; break; case "BLOGBUTTON": url = "http://silverlight.net/blogs/jesseliberty"; break; case "MICROBLOGBUTTON": url="http://www.twitter.com/SLMicroBlog"; break; } System.Uri theURI = new Uri(url); win.Navigate(theURI,"new"); } }
Separating Code From UI
Of course, none of this code affects any of the effects of displaying, spinning, or animating the buttons; in fact, the event handler has nothing to do with the template; it is just a plain old button.click – part of the principle that Templating affects the UI but not the logic of the control.
More Soon
More soon once the tutorial and videos are out; and again, I apologize for blogging before the fact, but my enthusiasm got carried away and I had to answer Seth’s question.