Silverlight Hover Button – XAML Style implementation with code that is dynamic and skinnable.

By dsandor at July 06, 2010 04:52
Filed Under: WPF, Silverlight, Programming

So I needed to create a hover button.  The idea is that when the mouse rolls over the button the image will flip between a normal and ‘over’ version of the button image.  This is the same thing as old school image rollover code we have all used before.  There is an image that you use for a button in the ‘normal’ or ‘off’ state and when the mouse rolls over the image the picture changes to the ‘over’ or ‘on’ state.

It is pretty easy to do this statically.  Edit the button style, slap a couple of images in the ControlTemplate and tie that to the Normal and MouseOver visual states and you are done.

But what if you want to be able to use that same style for all your buttons and you want to be able to databind the Normal and MouseOver images?

This was pretty easy with a trick here or there.

First, we need to create a HoverButton class that inherits from the standard Button.  We need to do this because we need 2 new properties for our Normal and Over image url’s.

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
 
namespace MySLApplication.Local
{
    public class HoverButton : Button
    {
 
 
        public string NormalImageUrl
        {
            get { return (string)GetValue(NormalImageUrlProperty); }
            set { SetValue(NormalImageUrlProperty, value); }
        }
 
        public static readonly DependencyProperty NormalImageUrlProperty =
            DependencyProperty.Register("NormalImageUrl", typeof(string), typeof(HoverButton), 
            new PropertyMetadata(""));
 
 
 
        public string MouseOverImageUrl
        {
            get { return (string)GetValue(MouseOverImageUrlProperty); }
            set { SetValue(MouseOverImageUrlProperty, value); }
        }
 
        public static readonly DependencyProperty MouseOverImageUrlProperty =
            DependencyProperty.Register("MouseOverImageUrl", typeof(string), typeof(HoverButton), 
            new PropertyMetadata(""));
 
 
 
    }
}

This is the button you will place on your design surface instead of the regular button.  This one gives us NormalImageUrl and the MouseOverImageUrl.  These properties we will set to the URL for the images we want to use for our normal state and mouse over state.

Next is the Style (see the zip file at the bottom of this post for the actual code).  Since the Style is so big, I will describe the important pieces to you.

First, your XAML that contains this style code needs to have a namespace reference to the HoverButton’s namespace.  I named mine local so you will see my TargetType set to local:HoverButton.

xmlns:local="clr-namespace:MySLApplication.Local"

So the style starts out like this:

<Style x:Key="HoverButtonStyle" TargetType="local:HoverButton">
            <Setter Property="Background" Value="#FF1F3B53"/>
            <Setter Property="Foreground" Value="#FF000000"/>
            <Setter Property="Padding" Value="3"/>
            <Setter Property="BorderThickness" Value="0"/>
            <Setter Property="Height" Value="36" />
            <Setter Property="Width" Value="118" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="local:HoverButton">
                        <Grid>
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">

Note the TargetType in both instances is pointed to the local namespace and the HoverButton class name.  The real magic occurs in the ControlTemplate portion of the code.  Here there are several Visual States defined.  We are concered with two of them (feel free to extend this to the others also): Normal and MouseOver.

You will notice in each of the two Visual States we are concerned with there is a storyboard.  The StroryBoard executes when the visual state is entered.  So for example, when the button starts out it is in the Normal state.  When the control is rendered the Normal visual state causes the storyboard defined here to execute:

 

<VisualState x:Name="Normal">
    <Storyboard>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" 
        Storyboard.TargetName="OverImage">
            <DiscreteObjectKeyFrame KeyTime="0">
                <DiscreteObjectKeyFrame.Value>
                    <Visibility>Collapsed</Visibility>
                </DiscreteObjectKeyFrame.Value>
            </DiscreteObjectKeyFrame>
        </ObjectAnimationUsingKeyFrames>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" 
        Storyboard.TargetName="NormalImage">
            <DiscreteObjectKeyFrame KeyTime="0">
                <DiscreteObjectKeyFrame.Value>
                    <Visibility>Visible</Visibility>
                </DiscreteObjectKeyFrame.Value>
            </DiscreteObjectKeyFrame>
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</VisualState>

What happens here is that the StoryBoard sets the OverImage’s Visibility property to Collapsed and the NormalImage’s Visibility property to Visible. 

Before confusion sets in, scroll down to the bottom of the style.  You will see the two images we just discussed.

<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" 
   Content="{TemplateBinding Content}" 
   HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
   Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<Image x:Name="NormalImage" DataContext="{TemplateBinding NormalImageUrl}" Source="{Binding}" />
<Image x:Name="OverImage" DataContext="{TemplateBinding MouseOverImageUrl}" Source="{Binding}" />

The two images defined inside the button’s visual surface are the actual images you will see at runtime.  Notice that we had to set the DataContext of the Image control to the property NormalImageUrl and MouseOverImageUrl and THEN set the Source property to {Binding}.  You need this little trick in order to allow databinding to convert the string based URL to an ImageSource.

So now we are good to go.  We can use our HoverButton like this:

<local:HoverButton Content="" Height="36" HorizontalAlignment="Left" 
    Margin="508,558,0,0" x:Name="btnSpin" VerticalAlignment="Top" Width="118" 
    Click="btnSpin_Click" Style="{StaticResource HoverButtonStyle}"
    NormalImageUrl="http://yourdomain.com/Normal.png"
    MouseOverImageUrl="http://yourdomain.com/Over.png"/>

So now you can specify the Normal and MouseOver images for your button.  The same style can be used for all these hover buttons, just specify the 2 images and you are set. 

 

Download File - HoverButtonSource