Image attached properties to make x:Phase work with BitmapImage source

Image attached properties to make x:Phase work with BitmapImage source

Today I wanted to do a pretty simple thing - use UWP x:Phase on an Image object inside a DataTemplate, but also set the DecodePixelWidth/DecodePixelHeight properties on a BitmapImage source to ensure that the decoded image width/height is optimal. Turns out, it's not exactly such a simple thing, so I wrote a couple of attached properties to make it simpler.

This is what I tried to do:

<Image x:Name="Image"  
        Stretch="Uniform"
        MaxWidth="120"
        x:Phase="2">
    <Image.Source>
        <BitmapImage UriSource="{x:Bind image_url}"
                     DecodePixelWidth="120"
                     DecodePixelType="Logical" />
    </Image.Source>
</Image>  

It made sense to me, but not to the compiler.

x:Phase can only be used with x:Bind

Yes, x:Phase should be used with x:Bind, I was aware of that, but this scenario wasn't covered. Since BitmapImage is not a UIElement, this also wouldn't work:

<Image x:Name="Image"  
        Stretch="Uniform"
        MaxWidth="120">
    <Image.Source>
        <BitmapImage UriSource="{x:Bind image_url}"
                     x:Phase="2"
                     DecodePixelWidth="120"
                     DecodePixelType="Logical" />
    </Image.Source>
</Image>  

So I wrote a couple of quick and dirty attached properties that I can attach to an Image object to set the DecodePixelWidth, DecodePixelHeight and DecodePixelType properties.

A couple of assumptions:
1. Image source will be a BitmapImage
2. UriSource will be set after the desired DecodePixelWidth, DecodePixelHeight and DecodePixelType properties have been set.
3. No other image source will be set.

Here are the extensions:

public static class ImageExtensions  
{
    public static readonly DependencyProperty DecodableUriSourceProperty = DependencyProperty.RegisterAttached(
        "DecodableUriSource", typeof(string), typeof(Image), new PropertyMetadata(null));

    public static readonly DependencyProperty DecodePixelHeightProperty = DependencyProperty.RegisterAttached(
        "DecodePixelHeight", typeof(int), typeof(Image), new PropertyMetadata(0));

    public static readonly DependencyProperty DecodePixelTypeProperty = DependencyProperty.RegisterAttached(
        "DecodePixelType", typeof(DecodePixelType), typeof(Image), new PropertyMetadata(DecodePixelType.Physical));

    public static readonly DependencyProperty DecodePixelWidthProperty = DependencyProperty.RegisterAttached(
        "DecodePixelWidth", typeof(int), typeof(Image), new PropertyMetadata(0));

    public static string GetDecodableUriSource(UIElement element)
    {
        return (string)element.GetValue(DecodableUriSourceProperty);
    }

    public static void SetDecodableUriSource(UIElement element, string value)
    {
        Image img = (Image)element;

        element.SetValue(DecodableUriSourceProperty, value);

        img.Source = new BitmapImage(new Uri(value))
        {
            DecodePixelHeight = GetDecodePixelHeight(img),
            DecodePixelWidth = GetDecodePixelWidth(img),
            DecodePixelType = GetDecodePixelType(img)
        };
    }

    public static int GetDecodePixelHeight(UIElement element)
    {
        return (int)element.GetValue(DecodePixelHeightProperty);
    }

    public static void SetDecodePixelHeight(UIElement element, int value)
    {
        element.SetValue(DecodePixelHeightProperty, value);
    }

    public static DecodePixelType GetDecodePixelType(UIElement element)
    {
        return (DecodePixelType)element.GetValue(DecodePixelTypeProperty);
    }

    public static void SetDecodePixelType(UIElement element, DecodePixelType value)
    {
        element.SetValue(DecodePixelTypeProperty, value);
    }

    public static int GetDecodePixelWidth(UIElement element)
    {
        return (int)element.GetValue(DecodePixelWidthProperty);
    }

    public static void SetDecodePixelWidth(UIElement element, int value)
    {
        element.SetValue(DecodePixelWidthProperty, value);
    }
}

And this is how I use them:

<Image x:Name="Image"  
       Stretch="Uniform"
       MaxWidth="120"
       x:Phase="2"
       controls:ImageExtensions.DecodePixelType="Logical"
       controls:ImageExtensions.DecodePixelWidth="320" 
       controls:ImageExtensions.DecodePixelHeight="240"
       controls:ImageExtensions.DecodableUriSource="{x:Bind image_url}" />

Conclusion

Compiled bindings are cool - if something doesn't work, you get to know it immediately. One of the things that doesn't work is x:Phase on an Image that has an explicit BitmapImage source (let's say, used for setting the DecodePixelWidth and DecodePixelHeight properties) because in that case the UriSource is set on a BitmapImage.

I wrote a couple of attached properties that can be used with Image to make x:Phase work with BitmapImage source set explicitly. Hope it helps!

Igor Ralic

igor ralic

View Comments
Microsoft Certified Solutions Developer: Windows Store Apps in C#