WPF Control Development - 3 Ways to build an ImageButton

During a WPF workshop last week I was asked to do an ImageButton control. *Huh* I thought, this is going to be an easy task (as probably most of you think right now).

1st Way: Use Buttons Content Property directly:

And it was easy: Thanks to the concept of ContentControls I easily assigned the Button.Content property (in the sample below left out because it's the default property) a Stackpanel arranging the Image and the Text next to each other.

 ...
<Button VerticalAlignment="Top" HorizontalAlignment="Left" Click="Button_Click" Background="Blue">
   <StackPanel Orientation="Horizontal" Margin="10">
      <Image Source="calendar.png" Width="16" Height="16" Margin="0,0,10,0"/>
      <TextBlock>Calendar</TextBlock>
   </StackPanel>
</Button>
...

Bravely I showed them these simple 6 lines - I really enjoyed it! - They didn't :-(

"We don't wanna use this 6 lines everywhere just to get an image button!", they complained..

So I showed them how to refactor a custom control out of this. First of all I tried inheriting from Button (which is the hard way for this case):

2nd Way: Create an inherited control:

I created a new control, inherited it from Button and created the visual tree for the content property out of C# code in the constructor:

     public class ImageButton : Button
    {
        Image _image = null;
        TextBlock _textBlock = null;

        public ImageButton()
        {
            StackPanel panel = new StackPanel();
            panel.Orientation = Orientation.Horizontal;

            panel.Margin = new System.Windows.Thickness(10);
            
            _image = new Image();
            _image.Margin = new System.Windows.Thickness(0, 0, 10, 0);
            panel.Children.Add(_image);

            _textBlock = new TextBlock();
            panel.Children.Add(_textBlock);

            this.Content = panel;
        }

        // Properties
    }

This does exactly the same like the 1st sample, but encapsulates everything in an own class. I also added properties, to set the _textBlock.Text and _image.Source for the button.

The advantage is you can now use the ImageButton directly out of XAML in one line:

Specify a custom Xml-Namespace prefix:

 <Window ... xmlns:my="clr-namespace:ImageButtonDemo">

and use the button in your XAML

 <my:ImageButton Image="calendar.png" Text="Calendar" />

This is quite simple to use, but the creation of the Content via C# is more complex than it needed to be.

So I decided to do a 3rd - mixed way of the first ones:

3rd Way: Create a UserControl:

In contrary to inherited controls, UserControls are composite controls, which can consist of many subcontrols. UserControl inherits like Button or Window from ContentControl, so you can think of it as an embeddable window area. As with window, UserControl consists of both, a XAML file as well as a code behind file.

So what I did is placing a single button into the UserControl and set its content as before:

 <UserControl Name="UC"  ...>
    <Grid>
        <Button>
            <StackPanel Orientation="Horizontal">
                <Image .../>
                <TextBlock .../>
            </StackPanel>
        </Button>
    </Grid>
</UserControl>

For setting the image's and the textblock's values I could again have used simple properties, but this time I decided to use databinding:

I made up dependency properties, to externally access the imagebutton's properties:

public string Text

{

  get { return (string)GetValue(TextProperty); }

  set { SetValue(TextProperty, value); }

}

public static readonly DependencyProperty TextProperty =

  DependencyProperty.Register("Text", typeof(string), typeof(ImageButton2), new UIPropertyMetadata(""));

public ImageSource Image

{

get { return (ImageSource)GetValue(ImageProperty); }

set { SetValue(ImageProperty, value); }

}

public static readonly DependencyProperty ImageProperty =

DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButton2), new UIPropertyMetadata(null));

A tipp: Dependency Properties as shown above can be easily created with a code snippet by typing propd <tab><tab> in Visual Studio.

What I did then, is bind these dependency properties to the XAML object's properties:

 <Image Source="{Binding ElementName=UC, Path=Image}"/>
<TextBlock Text="{Binding ElementName=UC, Path=Text}" />

That's it!

 

In my opinion the last method is a good combination: Everything encapsulated in an own object, but having the benefits of XAML as well.

And here is the ImageButton:

image

For the entire code download the attached project!

UPDATE: Another fine approach using attached properties (in a more decorator like way) can be found Philipp Sumi’s blog.

Knom.WPF.ImageButton.zip