Click here to Skip to main content
Licence CPOL
First Posted 30 Jun 2009
Views 16,571
Bookmarked 16 times

Silverlight: Reflection Image Button

By Jeremy Likness | 4 Jul 2009 | Technical Blog
A simple Silverlight control that creates an image button with zoom and reflection

1

2
1 vote, 50.0%
3

4
1 vote, 50.0%
5
4.33/5 - 2 votes
μ 4.33, σa 2.47 [?]

In continuing with building on Silverlight, I was working on a control strip that contains images. We want to style them fancy, and the "reflection" pattern seems to be quite popular. Applying a reflection to an image is fairly straightforward. My preference is to flip the image and apply an opacity mask:

<Image x:Name="Reflection" RenderTransformOrigin="0.0,0.0">
       <Image.RenderTransform>
           <ScaleTransform x:Name="ReflectionTransform" ScaleY="-1"/>
       </Image.RenderTransform>         
       <Image.OpacityMask>
           <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
               <GradientStop Color="#00000000" Offset="0.0"/>
               <GradientStop Color="#55555555" Offset="1.0"/>
           </LinearGradientBrush>
       </Image.OpacityMask>
   </Image>

Some people prefer a stronger gradient but you get the idea - turn it over and fade it out. If you wanted to get really fancy, you could do a SkewTranform and cast the reflection at an angle.

The next thing I wanted to do was emphasize the solid image a bit when hovered to indicate it is "active." I tried a typical "bouncy" algorithm that moved the image up and down, but personally I prefer scaling the image instead. There are plenty of examples of "zoom toolbars" out there. For our purposes, we'll just barely expand the image to show it is selected, and do it using an animation so it's smooth and doesn't just suddenly pop up. The animations for expanding and contracting are simple:

<Image.Resources>
    <Storyboard x:Name="ImageExpand">
        <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="MainTransform" 
		Storyboard.TargetProperty="ScaleX"
                         From="1.0" To="1.1"/>
        <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="MainTransform" 
		Storyboard.TargetProperty="ScaleY"
                         From="1.0" To="1.1"/>
    </Storyboard>
    <Storyboard x:Name="ImageContract">
        <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="MainTransform" 
		Storyboard.TargetProperty="ScaleX"
                         From="1.1" To="1.0"/>
        <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="MainTransform" 
		Storyboard.TargetProperty="ScaleY"
                         From="1.1" To="1.0"/>
    </Storyboard>
</Image.Resources

When initializing the control, we want to hook into the events for these animations completing so we can stop them for reuse:

public ReflectedZoomImage()
{
    InitializeComponent();           
    ImageExpand.Completed += _AnimationCompleted;
    ImageContract.Completed += _AnimationCompleted;
}

The _AnimationCompleted simply determines who triggered the call and then stops the animation and sets the final scale value - it could have been separate methods but we may eventually want to handle it generically by checking if sender is a storyboard, etc.

private void _AnimationCompleted(object sender, EventArgs e)
{
    if (sender == ImageExpand)
    {
        ImageExpand.Stop();
        MainTransform.ScaleX = 1.1;
        MainTransform.ScaleY = 1.1;
    }
    else if (sender == ImageContract)
    {
        ImageContract.Stop();
        MainTransform.ScaleX = 1.0;
        MainTransform.ScaleY = 1.0;
    }
}

Next, we wire in the events for mouse enter and mouse leave. We want to make sure an existing animation is not already firing before we start it over, so the code for expanding the image looks like this:

private void MainImage_MouseEnter(object sender, MouseEventArgs e)
{
    if (ImageExpand.GetCurrentState().Equals(ClockState.Stopped))
    {
        ImageExpand.Begin();
    }
}

Finally, I wanted to encapsulate this all into a control that I could reuse. The completed XAML for the control looks like this:

<UserControl x:Class="SilverTest.Controls.ReflectedZoomImage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    >
    <Grid x:Name="ReflectionGrid">
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Image MouseEnter="MainImage_MouseEnter" MouseLeave="MainImage_MouseLeave" 
		MouseLeftButtonDown="MainImage_MouseLeftButtonDown" 
		x:Name="MainImage" Grid.Row="0">
            <Image.RenderTransform>
                <ScaleTransform x:Name="MainTransform"/>
            </Image.RenderTransform>
<Image.Resources>
    <Storyboard x:Name="ImageExpand">
        <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="MainTransform" 
		Storyboard.TargetProperty="ScaleX"
                         From="1.0" To="1.1"/>
        <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="MainTransform" 
		Storyboard.TargetProperty="ScaleY"
                         From="1.0" To="1.1"/>
    </Storyboard>
    <Storyboard x:Name="ImageContract">
        <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="MainTransform" 
		Storyboard.TargetProperty="ScaleX"
                         From="1.1" To="1.0"/>
        <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetName="MainTransform" 
		Storyboard.TargetProperty="ScaleY"
                         From="1.1" To="1.0"/>
    </Storyboard>
</Image.Resources>
        </Image>
        <Image x:Name="Reflection" RenderTransformOrigin="0.0,0.0" Grid.Row="1">
            <Image.RenderTransform>
                <ScaleTransform x:Name="ReflectionTransform" ScaleY="-1"/>
            </Image.RenderTransform>         
            <Image.OpacityMask>
                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                    <GradientStop Color="#00000000" Offset="0.0"/>
                    <GradientStop Color="#55555555" Offset="1.0"/>
                </LinearGradientBrush>
            </Image.OpacityMask>
        </Image>
    </Grid>
</UserControl>

To actually use the control, we need to expose a property so the consumer can set the image. I made a property called "ReflectionSource" and once that's set, I can wire it into the main image and the reflection and also set the heights:

public ImageSource ReflectionSource
{
    get
    {
        return MainImage.Source;
    }

    set
    {
        MainImage.Source = value;
        Reflection.Source = value;
        _ControlInit();
    }
}

private void _ControlInit()
{
    MainImage.Width = Width;
    MainImage.Height = Height / 2;

    MainTransform.CenterX = MainImage.Width/2;
    MainTransform.CenterY = MainImage.Height/2; 

    Reflection.Width = Width;
    Reflection.Height = Height / 2;
    
    ReflectionTransform.CenterX = Reflection.Width / 2;
    ReflectionTransform.CenterY = Reflection.Height / 2; 
}

If you noticed in the XAML, I wired into the "MouseLeftButton" event so we can register a click. I expose this as an actual Click event:

...
public event EventHandler<MouseButtonEventArgs> Click; 
...

... and then pass that through:

private void MainImage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (Click != null)
    {
        Click(this, e); 
    }
}

Now I can reuse the control ... in this case, I've styled an "up" and "down" image, and put them next to each other with a spacer, like this:

<Border Background="White" CornerRadius="30" Margin="2" Grid.Column="1" Grid.Row="0">
    <StackPanel Orientation="Horizontal" VerticalAlignment="Center" 
		HorizontalAlignment="Center" Margin="1">
        <silvertest:ReflectedZoomImage Width="21" Height="42" 
		Click="ReflectedZoomImage_Click" 
		ReflectionSource="../Resources/green.png"/>
        <Rectangle Opacity="0" Width="5"/>
        <silvertest:ReflectedZoomImage Width="21" Height="42" 
		Click="ReflectedZoomImage_Click" 
		ReflectionSource="../Resources/red.png"/>
    </StackPanel>
</Border>

Note wiring into the click event and that I can set the width and height of the actual, rendered control, not the individual image (the image is 21, so the rendered control should be 42 to account for the reflection).

The finished product:

Reflected Zoom Image

...and with the red button emphasized:

Reflected Zoom Image 2

And there you have a simple Silverlight user control that takes advantage of the animation and transformation functionality that is built-in to the framework.

Jeremy Likness

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Jeremy Likness

Architect
Wintellect
United States United States

Member

Follow on Twitter Follow on Twitter
Jeremy Likness is a Microsoft Silverlight MVP who works as Project Manager and Senior Consultant for Wintellect with 15 years of experience developing enterprise applications. He has worked with software in multiple verticals ranging from insurance, health and wellness, supply chain management, and mobility. His primary focus for the past decade has been building highly scalable web-based solutions using the Microsoft technology stack with a focus on Silverlight since version 2.0.
 
Prior to Wintellect, Jeremy was Director of Information Technology and served as development manager and architect for AirWatch, LLC, where he helped the company grow and solidify its position as one of the leading wireless technology solution providers in the United States by managing the development of their product portfolio that includes public HotSpot solutions and a management console for enterprise grade wireless networks, mobile devices, and their consumers. A fluent Spanish speaker, Jeremy served as Director of Information Technology for Hispanicare, where he architected a multi-lingual content management system for the company's Hispanic-focused online diet program. Jeremy accepted his role there after serving as Development Manager for Manhattan Associates, a software company that provides supply chain management solutions.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralSmall question PinmemberShani Natav5:58 24 Sep '09  
GeneralThe code, an example... Pinmembercwp429:04 13 Jul '09  
GeneralRe: The code, an example... PinmemberAnurag Gandhi4:36 23 Jul '09  
GeneralRe: The code, an example... PinassociateJeremy Likness10:51 24 Jul '09  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web01 | 2.5.120210.1 | Last Updated 4 Jul 2009
Article Copyright 2009 by Jeremy Likness
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid