Поделиться через


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

Comments

  • Anonymous
    August 18, 2008
    Excellent summary of the different approaches you can use to create the imagebutton Thanks

  • Anonymous
    October 08, 2008
    The 3rd way was exactly what I was looking for. Thank you for your explanation!

  • Anonymous
    January 07, 2009
    I used to do take an alternative route through attached properties. I think they are a very flexible alternative, that nicely complements custom controls. In case somebody's interested, I published a short tutorial on my blog: http://www.hardcodet.net/2009/01/create-wpf-image-button-through-attached-properties WPF never ceases to amaze me with its flexibility :-) Cheers, Philipp

  • Anonymous
    February 26, 2009
    The problem with the 3rd way is that you don't get Click events on your button.

  • Anonymous
    March 15, 2009
    Could you tell me how to set image to the dynamically created button

  • Anonymous
    July 06, 2009
    The comment has been removed

  • Anonymous
    July 19, 2011
    this is so useful, thank you so much :)

  • Anonymous
    October 27, 2011
    A very nice sample, but how can I associate a Command to the UserControl (the 3rd way) any help? thx :)

  • Anonymous
    October 27, 2011
    A very naice sample, but how I can associate a Command to the UserControl (the 3rd way..) any help? thx :)