Controls

How to use the ButtonImage control that supports the Pressed and IsEnabled states

Introduction

There is a lot of examples in the Windows Phone community about creating a button with an image only. We can achieve this result with the following code:

<Button>
    <Image Source="MyImage.png"/>
</Button>

The problem with the previous code is the impossibility to set a different image when the button is disabled or pressed.


Solution

I created a ButtonImage class to set a different image for the disabled and the pressed states.

If you are not interested on the code of the ButtonImage class, you can skip this section and go directly to the Usage section.

The ButtonImage class has XML code and C# code.

XML code

<Button x:Class="DotNetApp.ButtonImage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    IsEnabledChanged="ButtonIsEnabledChanged"
    MouseEnter="ButtonMouseEnter"
    MouseLeave="ButtonMouseLeave">

    <Image Stretch="None"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        x:Name="image" />
</Button>

There is nothing fancy here, it is a button that has an image has content.

C# code

using System;
using System.Windows;
using System.Windows.Media.Imaging;

namespace DotNetApp
{
    public partial class ButtonImage
    {
        #region Fields

        public new static readonly DependencyProperty IsEnabledProperty = DependencyProperty.Register("IsEnabled", typeof (bool), typeof (ButtonImage), null);
        public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof (string), typeof (ButtonImage), null);
        public static readonly DependencyProperty ImagePressedSourceProperty = DependencyProperty.Register("ImagePressedSource", typeof (string), typeof (ButtonImage), null);
        public static readonly DependencyProperty ImageDisabledSourceProperty = DependencyProperty.Register("ImageDisabledSource", typeof (string), typeof (ButtonImage), null);

        private BitmapImage _image;
        private BitmapImage _imagePressed;
        private BitmapImage _imageDisabled;
        private bool _isPressed;

        #endregion

        #region Constructor

        public ButtonImage()
        {
            InitializeComponent();
        }

        #endregion

        #region Properties

        public new bool IsEnabled
        {
            get { return (bool)GetValue(IsEnabledProperty); }

            set
            {
                SetValue(IsEnabledProperty, value);

                SetImageFromState();
            }
        }

        public string ImageSource
        {
            get { return (string) GetValue(ImageSourceProperty); }

            set
            {
                SetValue(ImageSourceProperty, value);

                _image = SetImage(value);
                SetImageFromState();
            }
        }
        
        public string ImagePressedSource
        {
            get { return (string) GetValue(ImagePressedSourceProperty); }

            set
            {
                SetValue(ImagePressedSourceProperty, value);

                _imagePressed = SetImage(value);
                SetImageFromState();
            }
        }

        public string ImageDisabledSource
        {
            get { return (string) GetValue(ImageDisabledSourceProperty); }

            set
            {
                SetValue(ImageDisabledSourceProperty, value);

                _imageDisabled = SetImage(value);
                SetImageFromState();
            }
        }

        #endregion

        #region Event Handlers

        private void ButtonIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            SetImageFromState();
        }

        private void ButtonMouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
        {
            _isPressed = true;

            SetImageFromState();
        }

        private void ButtonMouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            _isPressed = false;

            SetImageFromState();
        }

        #endregion

        #region Private Methods

        private static BitmapImage SetImage(string imageSource)
        {
            BitmapImage bitmapImage = null;

            if (!string.IsNullOrEmpty(imageSource))
            {
                bitmapImage = new BitmapImage(new Uri(imageSource, UriKind.RelativeOrAbsolute));
            }

            return bitmapImage;
        }

        private void SetImageFromState()
        {
            if (!IsEnabled)
            {
                image.Source = _imageDisabled;
            }
            else if (_isPressed)
            {
                image.Source = _imagePressed;
            }
            else
            {
                image.Source = _image;
            }
        }

        #endregion
    }
}

Once again, there is nothing scientific here. I load the images and based on the event handlers (ButtonIsEnabledChanged, ButtonMouseEnter and ButtonMouseLeave), I set the right image.


Usage

ButtonImage.png

The button has an image for each state:

  • Normal

ImageNormal.png

  • Pressed

ImagePressed.png

  • Disabled

ImageDisabled.png

To use the ButtonImage control in your app, just follow those steps:

  1. Add the files ButtonImage.xaml and ButtonImage.xml.cs to your project.
  2. Add the normal image, the pressed image and the disabled image to your project. If your button does not need a pressed image or a disabled image, you don’t have to specify one.
  3. Add the XML code
<ButtonImageApp:ButtonImage ImageSource="/ImageNormal.png"
                                                    ImageDisabledSource="/ImageDisabled.png"
                                                    ImagePressedSource="/ImagePressed.png"
                                                    Style="{StaticResource ButtonImageStyle}"
                                                    Height="50"
                                                    Width="150" />

All the properties are important. The ImageSource, ImageDisabledSource, ImagePressedSource are the actual images that you want for each state. The Height and the Width need to be the same as the specified images.

The last piece of the puzzle is the Style property set to "{StaticResource ButtonImageStyle}". If you do not set this property, there will be an unwanted transparent area around the image and the image will be smaller. Your last step is to define the ButtonImageStyle into your project. You can add it into your page or in the App resources section. The code to copy is:

Page

<phone:PhoneApplicationPage.Resources>

    <Style x:Key="ButtonImageStyle"
            TargetType="ButtonImageApp:ButtonImage">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ButtonImageApp:ButtonImage">
                    <Grid Background="Transparent">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal" />
                                <VisualState x:Name="MouseOver" />
                                <VisualState x:Name="Pressed" />
                                <VisualState x:Name="Disabled" />
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border x:Name="ButtonBackground">
                            <ContentControl x:Name="ContentContainer"
                                            ContentTemplate="{TemplateBinding ContentTemplate}"
                                            Content="{TemplateBinding Content}" />
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</phone:PhoneApplicationPage.Resources>

or App

<Application.Resources>

    <Style x:Key="ButtonImageStyle"
        TargetType="ButtonImageApp:ButtonImage">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ButtonImageApp:ButtonImage">
                    <Grid Background="Transparent">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal" />
                                <VisualState x:Name="MouseOver" />
                                <VisualState x:Name="Pressed" />
                                <VisualState x:Name="Disabled" />
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border x:Name="ButtonBackground">
                            <ContentControl x:Name="ContentContainer"
                                        ContentTemplate="{TemplateBinding ContentTemplate}"
                                        Content="{TemplateBinding Content}" />
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</Application.Resources>


Conclusion

The ButtonImage derives from a Button so you can add a Command or the Click handler.


Download

How to create a spinner control

Most of the commercial components of Windows Phone contain a spinner control. However, if you can afford to buy a licence, you can use the one I provide below.

Here is a screenshot:

Spinner

To include the SpinnerControl in your project, you need to:

1- Unzip SpinnerControl.zip.
2- Add the three unzipped files to your project in the root.
3- Set the build action of the Spinner.png to Content
4- Add the SpinnerControl XAML code.

If you look at my sample project, which is an empty Windows Phone application (see download link at end of blog post), you will see:

    <DotNetApp:SpinnerControl Grid.RowSpan="2"
                              IsSpinning="True"
                              Status="Loading..."
                              VerticalAlignment="Center"
                              x:Name="spinner"/>

The simple SpinnerControl that I provide contains only 2 bindable properties:

IsSpinning: When it is set to true, the spinner rotates and it is visible. When it is set to false, the control is collapsed.

Status: This property is optional and it is used to put text under the spinner. It is useful if you wish to display the progress.

If we look at the file in more detail, SpinnerControl.xaml has is two important parts:

1- The Storyboard to rotate the image

    <Storyboard x:Name="StoryBoardAnimateSpinner">

      <DoubleAnimation AutoReverse="False"
                       Duration="0:0:2"
                       From="0"
                       RepeatBehavior="Forever"
                       Storyboard.TargetName="SpinnerTransform"
                       Storyboard.TargetProperty="Angle"
                       To="360" />

    </Storyboard>

This storyboard can be translated to: The image will rotate through 360 degrees every two seconds at a constant rate of rotation, and will continue to rotate forever. If you wish, you can set it to rotate through 360 degrees at a different speed, for example, every five seconds instead of every two seconds. You simply need to modify the Duration property.

2- The Image

    <Image Height="50"
           Margin="10,10"
           Source="/Spinner.png"
           Stretch="Uniform"
           Width="50"
           x:Name="spinImage">

      <Image.RenderTransform>

        <RotateTransform x:Name="SpinnerTransform"
                         CenterX="25"
                         CenterY="25" />

      </Image.RenderTransform>

    </Image>

The Height and Width of spin image need to be equal, otherwise, you’ll get a weird rotation. The CenterX and CenterY need to be half the value of the Height (or Width). The image Spinner.png that I provided in the SpinnerControl is 40 x 40, but in the above code, I specified 50 x 50 and I made sure to set the Stretch property to Uniform.

The other control in the SpinnerControl is a TextBlock which you probably know. You can configure it with your own values and you can even add dependency properties to the control to make it more general.

Download SpinnerControl
Download Sample project