38

I have created an Image within a ButtonStyle. Now I have created an Attached Property so that I can set the Source for that Image. Should be straight forward but I am stuck with it.

This is my shortened ButtonStyle:

<Style x:Key="ToolBarButtonStyle"
        TargetType="Button">
    ...
    <Image x:Name="toolbarImage"
            Source="{TemplateBinding PrismExt:ImageSourceAttachable:ImageSource}"
            Width="48"
            Height="48" />
    ...
</Style>

And this is the attached property definition, Note that I have no idea how to fix the callback, as the dependencyproperty seems to be the button instead of the image. And Button doesn't expose my Image within its style. Its tricky.

namespace SalesContactManagement.Infrastructure.PrismExt
{
    public class ImgSourceAttachable
    {
        public static void SetImgSource(DependencyObject obj, string imgSource)
        {
            obj.SetValue(ImgSourceProperty, imgSource);
        }

        public static string GetImgSource(DependencyObject obj)
        {
            return obj.GetValue(ImgSourceProperty).ToString();
        }

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ImgSourceProperty =
            DependencyProperty.RegisterAttached("ImgSource", typeof(string), typeof(ImgSourceAttachable), new PropertyMetadata(Callback));

        private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //((Button)d).Source = new BitmapImage(new Uri(Application.Current.Host.Source, e.NewValue.ToString()));
        }
    }
}

This is how I set the image source within XAML:

<Button PrismExt:ImgSourceAttachable.ImgSource="./Images/New.png"
        Style="{StaticResource ToolBarButtonStyle}" />

Any ideas please? Many Thanks,

Artiom
  • 7,694
  • 3
  • 38
  • 45
Houman
  • 64,245
  • 87
  • 278
  • 460
  • I think I answered to soon here, do you want to set the property through a setter and then bind to it within the template? – Fredrik Hedblad Aug 21 '11 at 17:13
  • The View represents four button of a toolbar like this: http://sl.venuscloud.com/ I would like to inject the path to the Icon to each button from the view into the template through the attached property. Hope this makes it more clear. Thank you – Houman Aug 21 '11 at 17:18

1 Answers1

55

Here is how you can set your attached property in a style

<Style x:Key="ToolBarButtonStyle" TargetType="Button">
    <Setter Property="PrismExt:ImgSourceAttachable.ImgSource"
            Value="./Images/New.png"/>
    <!--...-->
</Style>

When binding to attached properties then the Path should be within parentheses so try to use RelativeSource Binding with TemplatedParent instead

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="Button">
            <Image x:Name="toolbarImage"
                    Source="{Binding RelativeSource={RelativeSource TemplatedParent},
                                    Path=(PrismExt:ImgSourceAttachable.ImgSource)}"
                    Width="48"
                    Height="48">
            </Image>
        </ControlTemplate>
    </Setter.Value>
</Setter>

Edit: The above code works in WPF, in Silverlight the Image shows in runtime but it fails in the designer with an exception. You can use the following code in the PropertyChangedCallback to get the Image as a workaround

private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    Button button = d as Button;
    Image image = GetVisualChild<Image>(button);
    if (image == null)
    {
        RoutedEventHandler loadedEventHandler = null;
        loadedEventHandler = (object sender, RoutedEventArgs ea) =>
        {
            button.Loaded -= loadedEventHandler;
            button.ApplyTemplate();
            image = GetVisualChild<Image>(button);
            // Here you can use the image
        };
        button.Loaded += loadedEventHandler;
    }
    else
    {
        // Here you can use the image
    }
}
private static T GetVisualChild<T>(DependencyObject parent) where T : DependencyObject
{
    T child = default(T);

    int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < numVisuals; i++)
    {
        DependencyObject v = (DependencyObject)VisualTreeHelper.GetChild(parent, i);
        child = v as T;
        if (child == null)
        {
            child = GetVisualChild<T>(v);
        }
        if (child != null)
        {
            break;
        }
    }
    return child;
}
Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
  • Thank you. This code compiles now, however I still dont see the images. I am pretty sure its because the CallBack of Attached Property is empty. However the "DependencyObject d" there is the button itself. I dont understand... – Houman Aug 21 '11 at 17:37
  • I just noticed your question had both WPF and Silverlight tags, my answer was for WPF. I'll try it out for Silverlight. – Fredrik Hedblad Aug 21 '11 at 17:44
  • You are right. apologies. I thought regarding attached properties they would be no different. :) – Houman Aug 21 '11 at 17:52
  • 1
    Updated my answer. That code worked in runtime on my end, but failed in the designer. Added code to get the image in the callback – Fredrik Hedblad Aug 21 '11 at 18:05
  • Thank you so much. It works like a charm. I don't think I could have done it without your help. :) – Houman Aug 21 '11 at 19:13