Question

Why does TemplateBinding seems to fail in this specific case?

Take a basic extended button:

public class IconButton : Button
{
    public ImageSource Icon
    {
        get { return (ImageSource)GetValue(IconProperty); }
        set { SetValue(IconProperty, value); }
    }
    public static readonly DependencyProperty ImageProperty =
        DependencyProperty.Register("Icon", typeof(ImageSource), typeof(IconButton), new PropertyMetadata(null));

    public IconButton()
    {
        DefaultStyleKey = typeof(IconButton);
    }
}

The control template shows the icon using an OpacityMask:

<Style TargetType="controls:IconButton">
    <Setter Property="Width" Value="30" />
    <Setter Property="Height" Value="30" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="controls:IconButton">
                <Grid>
                    <Rectangle Fill="{StaticResource PhoneForegroundBrush}"
                               Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
                        <Rectangle.OpacityMask>
                            <ImageBrush ImageSource="{TemplateBinding Icon}" />
                        </Rectangle.OpacityMask>
                    </Rectangle>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

This fails silently -- the control appears as a solid rectangle. If I use a regular image, instead of the ImageBrush, then the binding is successful:

            <ControlTemplate TargetType="controls:IconButton">
                <Grid>
                    <Image Source="{TemplateBinding Icon}" />
                </Grid>
            </ControlTemplate>

It also works correctly if I hard-code the image source path:

            <ControlTemplate TargetType="controls:IconButton">
                <Grid>
                    <Rectangle Fill="{StaticResource PhoneForegroundBrush}"
                               Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
                        <Rectangle.OpacityMask>
                            <ImageBrush ImageSource="/Images/appbar.next.rest.png" />
                        </Rectangle.OpacityMask>
                    </Rectangle>
                </Grid>
            </ControlTemplate>

So, why does TemplateBinding fail inside an ImageBrush?


Update

By deduction (and thanks to the answer from Chris), possible factors are:

I still don't see how the dots connect, though ...

Was it helpful?

Solution 2

Interesting, I managed to replicate that behaviour from your example, and although I can't be entirely sure, I think I might understand what's going on.

Based on the answers to this question: WPF TemplateBinding vs RelativeSource TemplatedParent it seems that although two methods achieve the almost same thing, they differ in some key behaviours. The most relevant one being mentioned by Miroslav Nedyalkov -

"TemplateBindings don't allow value converting. They don't allow you to pass a Converter and don't automatically convert int to string for example (which is normal for a Binding)."

I would guess that in the second case, thebinding will use the built in WPF convertor to convert the bound string/URI to an ImageSource (usual behaviour when specifying an ImageSource - it's why you don't usually need to specify a binding convertor).

In the first case, you won't get the default value converting, so you won't see the mask. Interesting to see if it would work if a convertor was specified.

Edit: It looks there might be some additional complications from ImageBrush not inheriting from FrameworkElement: Bind an ImageBrush to a template with a DependencyProperty

OTHER TIPS

Here's a workaround. Bizarrely, using the RelativeSource TemplatedParent binding instead of TemplateBinding fixes the issue.

<ImageBrush ImageSource="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Icon}" />

Theoretically that is the exact same binding ... so, who knows? Whatever works.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top