Three ways to make your WPF images pop out on MouseOver

时间:2022-02-25 06:26:31

There are a couple of ways in WPF to make an image pop out when moving mouse over it. Of course we want the image to pop out smoothly, so in this quick rundown we're going to use the following animation storyboards:

<!-- This storyboard will make the image grow to double its size in 0.2 seconds -->
<Storyboard x:Key="expandStoryboard">
    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" 
        To="2" Duration="0:0:0.2" />
    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" 
        To="2" Duration="0:0:0.2" />
</Storyboard>
<!-- This storyboard will make the image revert to its original size -->
<Storyboard x:Key="shrinkStoryboard">
    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" 
        To="1" Duration="0:0:0.2" />
    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" 
        To="1" Duration="0:0:0.2" />
</Storyboard>

One thing worth noticing here is the lack of "From" attribute in animation elements. That's because we want the animation pick up from whatever animation state the image is in to make it smoother. Specifying the beginning value of animation (with e.g. "From='1'" in expandStoryboard) would mean the image growing would always start at its original, not current size.

Triggering storyboards with event handlers

If you like imperative coding, you'll probably rush to implement image's MouseEnter and MouseLeaveevent handlers to trigger the animations. Image declaration would in Xaml look somewhat like this:

<Image Name="image1" Source="Image1.png" 
UIElement.MouseEnter="image_MouseEnter"
UIElement.MouseLeave="image_MouseLeave">

<Image.RenderTransform>
<!-- Initial values we're going to animate -->
<ScaleTransform ScaleX="1" ScaleY="1"/>
</Image.RenderTransform>
</Image>

... complemented with a couple of event handlers:

private void image_MouseEnter(object sender, MouseEventArgs e)
{
Storyboard story = (Storyboard)FindResource("expandStoryboard");
Image image = sender as Image;
image.BeginStoryboard(story);
} private void image_MouseLeave(object sender, MouseEventArgs e)
{
Storyboard story = (Storyboard)FindResource("shrinkStoryboard");
Image image = sender as Image;
image.BeginStoryboard(story);
}

[Both storyboards are declared as resources, hence the use of FindResource method for retrieving them.]

Using Event triggers instead of event handlers

Although there's nothing wrong with the previous method, why not do it all in Xaml? Enter EventTriggers:

<Image Name="image2" Source="Image2.png">
<Image.Triggers>
<EventTrigger RoutedEvent="Image.MouseEnter">
<BeginStoryboard Storyboard="{StaticResource expandStoryboard}" />
</EventTrigger>
<EventTrigger RoutedEvent="Image.MouseLeave">
<BeginStoryboard Storyboard="{StaticResource shrinkStoryboard}" />
</EventTrigger>
</Image.Triggers>
<Image.RenderTransform>
<ScaleTransform ScaleX="1" ScaleY="1"/>
</Image.RenderTransform>
</Image>

Looks better, doesn't it?

Finishing with Property triggers

The third method is very similar to the second, except it uses Property triggers instead of Event triggers. Currently, Property triggers have to be declared within a style:

<Style TargetType ="{x:Type Image}">
    <Setter Property="RenderTransform">
        <Setter.Value>
            <ScaleTransform ScaleX="1" ScaleY="1"/>
        </Setter.Value>
    </Setter> 
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Trigger.EnterActions>
                <BeginStoryboard Storyboard="{StaticResource expandStoryboard}" />
            </Trigger.EnterActions>
            <Trigger.ExitActions>
                <BeginStoryboard Storyboard="{StaticResource shrinkStoryboard}" />
            </Trigger.ExitActions>
        </Trigger>
    </Style.Triggers>
</Style>

Additional benefit of using styles is the ability to reuse/apply them to all elements of specified type within the scope the style is declared in. For example, declaring the above style (together with both storyboards) on the application level would generally make all images within application behave the same way. Unless, of course, some images specify their own styles, overriding this behavior. 
And the image?

<Image Name="image3" Source="Image3.png" />

[Note that specifying RenderTransform on this Image is no longer needed, because it's already set with the Style.]