views:

2741

answers:

6

Hi everyone,

I implemented a simple button with an image in it:

    <Button Command="{Binding ButtonCommand, ElementName=ImageButtonControl}">
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding ButtonImage, ElementName=ImageButtonControl}"/>
            <TextBlock Text="{Binding ButtonText, ElementName=ImageButtonControl}" Margin="2,0,0,0"/>
        </StackPanel>
    </Button>

As you can see, I expose a ButtonCommand property in order to be able to attach an ICommand to this UserControl:

public partial class ImageButton : UserControl
{
    /// <summary>
    /// The dependency property that gets or sets the source of the image to render.
    /// </summary>
    public static DependencyProperty ImageSourceProperty = 
        DependencyProperty.Register("ButtonImage", typeof(ImageSource), typeof(ImageButton));

    public static DependencyProperty TextProperty =
        DependencyProperty.Register("ButtonText", typeof(string), typeof(ImageButton));

    public static DependencyProperty ButtonCommandProperty =
        DependencyProperty.Register("ButtonCommand", typeof(ICommand), typeof(ImageButton));

    public ImageButton()
    {
        this.DataContext = this;
        InitializeComponent();
    }

    /// <summary>
    /// Gets or sets the button command.
    /// </summary>
    public ICommand ButtonCommand
    {
        get { return (ICommand)GetValue(ImageButton.ButtonCommandProperty); }
        set { SetValue(ImageButton.ButtonCommandProperty, value); }
    }

    /// <summary>
    /// Gets or sets the button image.
    /// </summary>
    public ImageSource ButtonImage
    {
        get { return (ImageSource)GetValue(ImageButton.ImageSourceProperty); }
        set { SetValue(ImageButton.ImageSourceProperty, value); }
    }

    /// <summary>
    /// Gets or sets the button text.
    /// </summary>
    public string ButtonText
    {
        get { return (string)GetValue(ImageButton.TextProperty); }
        set { SetValue(ImageButton.TextProperty, value); }
    }
}

Then when I declare my button it gives this:

<uc:ImageButton Grid.Row="1" Grid.Column="0" ButtonCommand="{Binding AttachContextCommand}" ButtonImage="{StaticResource AssociateImage}" ButtonText="Associer"/>

And badaboom, nothing never happen when I click on my ImageButton. When I replace the ImageButton with a simple button, the ICommand is called.

I even tried to simply extends the Button class and bind an ICommand, but once again, it didn't work...

Help appreciated !

Thx.

A: 

The built-in WPF button contains code that fires the attached command in response to being clicked. Your "ImageButton" derives from UserControl, so you don't get that behavior. Probably the shortest route to get what you want is for your ImageButton class to actually derive from the WPF Button class. To accomplish that, change the markup for ImageButton from

<UserControl
...
>
...
</UserControl>

to

<Button
...
>
...
</Button>

Then change the base class of ImageButton from UserControl to Button.

You'll probably need to make some other minor changes before it all works.

Daniel Pratt
Hello Daniel,I indeed tried to extends the button class but the command is never called either...
Roubachof
+2  A: 

If the only added functionality that you want for your button is to have an image on it, then I think you're approaching this from the wrong direction. WPF is as awesome as it is because the UI controls are look-less. This means that a Control is merely a definition of functionality + some template to define how it looks. This means that the template can be swapped out at any time to change the look. Also, almost any content can be placed inside of almost any control

For instance, to define a button in your xaml that has the look your going for all you need is this:

<Window ...>
    ...
    <Button Command="{Binding AttachContextCommand}">
        <StackPanel Orientation="Horizontal">
            <Image Source="{StaticResource AssociateImage}"/>
            <TextBlock Text="Associer"/>
        </StackPanel>
    </Button>
    ...
</Window>

Just keep in mind that with WPF you don't have to define a new CustomControl or UserControl every time you want to change the look and feel of something. The only time you should need a CustomControl is if you want to add functionality to an existing Control or to create functionality that doesn't exist in any other Control.

Edit Due to comment:

If you're wanting to keep from defining the content for the button every time, the other option is to just have a poco (plain old CLR object) class that would define everything your interested in (I'll write my example as if you're doing this for a tool bar, because it makes sense to me):

public class ToolBarItem : INotifyPropertyChanged
{
    public string Text { get ... set ... }
    public ICommand Command { get ... set ... }
    public ImageSource Image { get ... set ... }
}

That has a data template defined somewhere (App.xaml, Window.Resources, etc):

<DataTemplate DataType="{x:Type l:ToolBarItem}">
    <Button Command="{Binding Command}">
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding Image}"/>
            <TextBlock Text="{Binding Text}"/>
        </StackPanel>
    </Button>
</DataTemplate>

And then use the guy in your xaml like this:

<Window ...>
    ...
    <ContentControl>
        <ContentControl.Content>
            <l:ToolBarItem Image="..." Command="..." Text="..."/>
        </ContentControl.Content>
    </ContentControl>
    ...
</Window>

I just don't know that the way you're trying to do it is the most WPF way you could do it.

EDIT Updated based on second comment Sorry, I forgot to include the ContentControl surrounding that. Now that I remembered that, I realize that that's not much less verbose than the original where you are specifying the content manually. I'll post a new answer to help with your original question.

dustyburwell
Hi,I know this. Actually the goal of creating a user control was to avoid writing this for all the buttons. Doing this, I only had to write:<uc:ImageButton ButtonCommand="{Binding AttachContextCommand}" ButtonImage="{StaticResource AssociateImage}" />It's just refactoring if you want.
Roubachof
Sorry but this isn't working...ToolBarItem is not an UIElement and when I try to launch the app, I get an exception that tells me so.
Roubachof
A: 

I found several blogs saying user control or butten inheritance is the right choice for this...

http://blogs.msdn.com/knom/archive/2007/10/31/wpf-control-development-3-ways-to-build-an-imagebutton.aspx

The solution given by ascalonx doesn't work. Does anybody can give me the correct way to handle ICommands through UserControl or inheritance?

Thx.

Roubachof
+2  A: 

You can achieve this in a much cleaner way using a style and a couple of attached properties.

The attached properties will store your specific information. The style will use these properties and build the look you want.

The element will still be a button so the command and everything else will work.

 public class ImageButton
    {

        public static ImageSource GetImage(DependencyObject obj)
        {
            return (ImageSource)obj.GetValue(ImageProperty);
        }

        public static void SetImage(DependencyObject obj, ImageSource value)
        {
            obj.SetValue(ImageProperty, value);
        }

        public static readonly DependencyProperty ImageProperty =
            DependencyProperty.RegisterAttached("Image", typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));

        public static String GetCaption(DependencyObject obj)
        {
            return (String)obj.GetValue(CaptionProperty);
        }

        public static void SetCaption(DependencyObject obj, String value)
        {
            obj.SetValue(CaptionProperty, value);
        }

        public static readonly DependencyProperty CaptionProperty =
            DependencyProperty.RegisterAttached("Caption", typeof(String), typeof(ImageButton), new UIPropertyMetadata(null));

    }

<Style TargetType="{x:Type Button}"
               x:Key="ImageButton">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Source="{Binding Path=(local:ImageButton.Image), RelativeSource={RelativeSource AncestorType={x:Type Button}}}" />
                            <TextBlock Text="{Binding Path=(local:ImageButton.Caption), RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
                                       Margin="2,0,0,0" />
                        </StackPanel>
                    </DataTemplate>
                </Setter.Value>
            </Setter>   
        </Style>

You can then use this to declare buttons:

   <Button Style="{DynamicResource ImageButton}"
                        local:ImageButton.Caption="Foo" 
                        local:ImageButton.Image="..." />

Note:

I'm pretty sure it would be cleaner to go through the "Template" property and use a ControlTemplate and TemplateBindings, but that would mean re-creating the border and other stuff around your content, so if you are looking to just define a default "Content", my example would be the way to go, I think.

Denis Troller
I like this answer much better, too. I don't usually think to do attached properties, but they're an awesome feature of WPF.
dustyburwell
Indeed they are.The fact that you can attach behavior to elements through attached properties (like declarative handling of events for things like Drag and Drop) is even more awesome when you get to it.
Denis Troller
+1  A: 

To re-answer the original question:

What I think you want to do is create a new CustomControl called ImageButton. Then change it to extend from Button instead of Control. You won't need a Command property since Button already has one. You'll only need to add an Image property and you can reuse the Content property from button instead of having a Text property.

When your CustomControl is created, it'll add an entry in your Generic.xaml for the default style of your ImageButton. In the Setter for the Template property you can change the ControlTemplate to this:

<ControlTemplate TargetType="{x:Type local:ImageButton}">
    <StackPanel Orientation="Horizontal">
        <Image Source="{TemplateBinding Image}"/>
        <ContentPresenter/>
    </StackPanel>
</ControlTemplate>

Then, again, when you want to use it:

<Window ... >
...
    <l:ImageButton Image="{StaticResource ...}" Command="...">
        Associer
    </l:ImageButton>
...
</Window>
dustyburwell
A: 

And what if the images are NOT to embedded in the resources? how do you do it then?