0

I'm trying to create a "DropDownButton" with an icon in it, and I want to be able to set the icon source via an attached property (I found this is the (only?) way to do this). But for some reason, everything I tried fails, the best I could get was an empty Image container.

I thought this looked pretty good, but now I'm getting these errors:

The local property "Image" can only be applied to types that are derived from "IconButton".
The attachable property 'Image' was not found in type 'IconButton'.
The attached property 'IconButton.Image' is not defined on 'Button' or one of its base classes.

I'm probably doing this completely wrong (I've been trying and editing for about 2 hours now), but I just know there must be a way of doing this.

Relevant code is provided below, if anybody can even point me in the right direction that would be awesome!

EDIT: Updated code, still experiencing issue

Now I get this error in debug log:

System.Windows.Data Error: 40 : BindingExpression path error: 'Image' property not found on 'object' ''ContentPresenter' (Name='')'. BindingExpression:Path=Image; DataItem='ContentPresenter' (Name=''); target element is 'Image' (Name=''); target property is 'Source' (type 'ImageSource')

ImageButton.cs (thank you Viv):

class ImageButton : Button
{
    public static readonly DependencyProperty ImageProperty =
        DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButton),
        new FrameworkPropertyMetadata(null,
            FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsRender));

    public ImageSource Image
    {
        get { return (ImageSource)GetValue(ImageProperty); }
        set { SetValue(ImageProperty, value); }
    }
}

ImageButton Style:

<Style TargetType="{x:Type Controls:ImageButton}" x:Key="FormIconDropDownButton">
    <Setter Property="Margin" Value="5" />
    <Setter Property="Padding" Value="10,5,4,5" />
    <Setter Property="ContentTemplate">
        <Setter.Value>
            <DataTemplate DataType="{x:Type Controls:ImageButton}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>

                    <Image Style="{StaticResource FormButtonIcon-Small}"
                           Source="{Binding Image, RelativeSource={RelativeSource TemplatedParent}}"/>

                    <TextBlock Grid.Column="1"
                                Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}"
                               VerticalAlignment="Center"
                               Margin="0,0,9,0"/>

                    <Path Grid.Column="2"
                          Fill="Black" 
                          Data="M 0 0 L 3.5 4 L 7 0 Z"
                          VerticalAlignment="Center"/>
                </Grid>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>

In window xaml:

<Controls:ImageButton Content="Hello"
                              Style="{StaticResource FormIconDropDownButton}"
                              Image="{StaticResource Icon-Small-Locations}" />
Kryptoxx
  • 561
  • 4
  • 22
  • The **IconButton** should **EXTEND** the **Button**, where you'll declare the DependencyProperty. In Style Write Target="IconButton". For Example, in your case: ` – mihai Jun 18 '13 at 13:26

4 Answers4

3

You're just using the wrong control type in your xaml. You're still using the base class instead of your derived class.

Also you're declaring a dependency property not an attached property.

Attached Properties are registered with DependencyProperty.RegisterAttached(...)

Now you'll need to add a namespace to where your IconButton class is defined in your xaml such as

xmlns:local="clr-namespace:Mynamespace"

and then switch occurrences of

{x:Type Button} to {x:Type local:IconButton}

and

<Button ...> to <local:IconButton ...>

I wouldn't recommend an attached property for this tbh. Attached properties get way over-used when they shouldn't be probably just my opinion.

Check This thread for some differences between DP and AP usage. In this case it's a custom Button that shows an Image. Make it unique than homogenize the lot.

Update:

Download link using derived class(ImageButton) of Button with normal DP.

Community
  • 1
  • 1
Viv
  • 17,170
  • 4
  • 51
  • 71
  • I tried this, but now it's giving me this error in my windows xaml: Default value type does not match type of property 'Image'. I tried with both AP and DP. Also, do you mean by making it unique I should make a different style/datatemplate for every button I need but with a static reference to the image source? – Kryptoxx Jun 18 '13 at 13:43
  • @TomVandenbussche I've updated my answer with a download link at the bottom that will show you what I mean. I don't mean a new Style for each IconButton/ImageButton. What I say at-least I prefer is to just have a new `Button` class specific to `IconButton` and use that with it's `Style` in multiple places where appropriate than use an attached property on a plain `Button` – Viv Jun 18 '13 at 13:55
  • Thank you for this, this seems to be going in the good direction! However I want to use the ContentTemplate property instead of the Template property to style the button, so I don't lose the default looks of the button, is this possible? – Kryptoxx Jun 18 '13 at 14:13
  • @TomVandenbussche Yeh sure, I updated the link in my answer to a demo of using the `ContentTemplate` instead of `Template` – Viv Jun 18 '13 at 14:20
  • My god, there's no way to express the feeling of seeing the image finally appearing in that button! Thank you so much for this, 3 hours of excessive transpiration has finally come to an end.. You're my hero man! :D – Kryptoxx Jun 18 '13 at 14:26
1

Everything looks correct except that you haven't declared an attached property. Instead you've just declared a normal DependencyProperty on your IconButton class which is then only valid to set on IconButton or classes derived from it. The declaration of an attached property (which can be set on any type) uses a different call to register and also uses get/set methods instead of a wrapper property:

    public static readonly DependencyProperty ImageProperty =
        DependencyProperty.RegisterAttached(
        "Image",
        typeof(ImageSource),
        typeof(IconButton),
        new FrameworkPropertyMetadata(null,
            FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.AffectsRender));

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

    public static void SetImage(DependencyObject target, ImageSource value)
    {
        target.SetValue(ImageProperty, value);
    }
John Bowen
  • 24,213
  • 4
  • 58
  • 56
  • +1, I was about to write the same. Then there is still the problem with the binding in the Button Style. @Tom, how would you bind an image to the `Text` property of a TextBlock, despite that the binding expression contains a typo, i.e. opening paranthesis, but no closing paranthesis at the property name. – Clemens Jun 18 '13 at 13:47
  • Moreover, I doubt that the attached property really needs to support value inheritance. You may remove the `FrameworkPropertyMetadataOptions.Inherits` flags, as value inheritance may impact performance. – Clemens Jun 18 '13 at 13:48
  • Finally there is no need to derive class `IconButton` from `Control`. – Clemens Jun 18 '13 at 13:50
  • I tried this (think I tried it before I posted this too, not sure), but this also give me the exception "Default value type does not match type of property 'Image'" – Kryptoxx Jun 18 '13 at 13:56
  • @Clemens yeah I notied the typo too, but that's because I've been changing everything over and over just to make it work, most of the times this was correct though. Also, I'm not binding to the Text property but to the Source property of the Image – Kryptoxx Jun 18 '13 at 13:57
  • @TomVandenbussche Then you should perhaps fix your question in order not to confuse people. Right now I can see two bindings in the Style, but only one that binds to `Helpers:IconButton.Image`, and that is the `Text` binding. – Clemens Jun 18 '13 at 14:00
  • @Clemens Updated question, should be less confusing now – Kryptoxx Jun 18 '13 at 14:11
1

This is my example of Extending Base class, use Dependency Properties in Style and in View. For more details write in this post.

public class ItemsList : ListView {
      public static readonly DependencyProperty ItemIconProperty = DependencyProperty.Register("ItemIcon", typeof(ImageSource), typeof(ItemsList));
      public ImageSource ItemIcon {
         get { return (ImageSource)GetValue(ItemIconProperty); }
         set { SetValue(ItemIconProperty, value); }
      }  

      public static readonly DependencyProperty DoubleClickCommandProperty = DependencyProperty.Register("DoubleClickCommand", typeof(ICommand), typeof(ItemsList));
      public ControlTemplate DoubleClickCommand {
         get { return (ControlTemplate)GetValue(DoubleClickCommandProperty); }
         set { SetValue(DoubleClickCommandProperty, value); }
      }
}

/Style for Extended ItemList where is 'ItemIcon' DependencyProperty Declared/

<Style x:Key="BaseDataSourcesWindowListMenuStyle" TargetType="Controls:ItemsList">    
    <Setter Property="ItemIcon" Value="/Presentation.Shared;component/Resources/Images/data_yellow.png" />    
  </Style>

  <Style x:Key="DataSourcesListMenuStyle" TargetType="Controls:ItemsList"
         BasedOn="{StaticResource BaseDataSourcesWindowListMenuStyle}">    
    <Setter Property="DoubleClickCommand" Value="{Binding Path=VmCommands.EditDataSourceDBCommand}" />
  </Style>

/HOW I'M USING 'ItemIcon' DependencyProperty ON VIEW/

<Controls:ItemsList Grid.Column="0" Grid.Row="1" Margin="8" ItemsSource="{Binding DataSourceDbs}"
                        Style="{DynamicResource DataSourcesListMenuStyle}"
                        SelectedItem="{Binding SelectedDataSourceDB, Mode=TwoWay}" />
mihai
  • 2,746
  • 3
  • 35
  • 56
  • I've managed to make my code a little cleaner, but I don't really see where you use the ItemIcon property in your control, are you binding it to something in a datatemplate? Thanks – Kryptoxx Jun 18 '13 at 14:19
  • In the style **BaseDataSourcesWindowListMenuStyle** is the **Setter** for **ItemIcon** Dependency Property. The style **DataSourcesListMenuStyle** extends the first Style by setting the command for Second Dependency Property declared in ItemList class. In the View there is: `Controls:ItemList ...` - here is the usage of the Extended class where i set the style `Style="{DynamicResource DataSourcesListMenuStyle}"` – mihai Jun 18 '13 at 14:25
  • As result, I have All Items from that List rewriten with Icon and with action for DoubleClickCommand, Both Dependency Properties were set in style. Why 2 separate styles ? Because the Icon which I set is the same for More Lists with elements, but commands are different. According to Which List I have in view, I define one more style where I rewrite the **DoubleClickCommand** property to corresponding command in ViewModel. – mihai Jun 18 '13 at 14:28
0

First of all, I think that's a dependency property you declared there, and it belongs to the IconButton custom control, and you're trying to use it on a Button control.

See http://msdn.microsoft.com/en-us/library/ms749011.aspx for reference on how to declare attached properties, and assign that property to the Button class instead of the IconButton since you're not using that custom control in the code above.

Avram Tudor
  • 1,468
  • 12
  • 18
  • The class I made is purely to declare the DependencyProperties in, I don't know where I should declare them elsewhere so I can use them in my current situation (On a normal Button control with DataTemplate)? – Kryptoxx Jun 18 '13 at 13:26
  • You could declare it in a Helper class, it doesn't really matter since it's static. – Avram Tudor Jun 18 '13 at 14:00