32

If I have a multi-resolution icon file (.ico), how can I insure that WPF picks the right sized one? Does setting the width and height of the Image force it, or does WPF simply resize the first icon in the ico file?

This is what I'm using currently (it works, but I'd like to avoid the resizing if that's what's happening).

<MenuItem.Icon>
    <Image Source="MyIcons.ico" Width="16" Height="16"  />
</MenuItem.Icon>

I'd like to declare this in Xaml if possible without having to code for it.

SergioL
  • 4,905
  • 3
  • 26
  • 30

4 Answers4

36

I use simple Markup Extension for that:

/// <summary>
/// Simple extension for icon, to let you choose icon with specific size.
/// Usage sample:
/// Image Stretch="None" Source="{common:Icon /Controls;component/icons/custom.ico, 16}"
/// Or:
/// Image Source="{common:Icon Source={Binding IconResource}, Size=16}"
/// </summary> 
public class IconExtension : MarkupExtension
{
    private string _source;

    public string Source
    {
        get
        {
            return _source;
        }
        set
        {
            // Have to make full pack URI from short form, so System.Uri recognizes it.
           _source = "pack://application:,,," + value;
        }
    }

    public int Size { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var decoder = BitmapDecoder.Create(new Uri(Source), 
                                           BitmapCreateOptions.DelayCreation,
                                           BitmapCacheOption.OnDemand);

        var result = decoder.Frames.SingleOrDefault(f => f.Width == Size);
        if (result == default(BitmapFrame))
        {
            result = decoder.Frames.OrderBy(f => f.Width).First();
        }

        return result;
    }

    public IconExtension(string source, int size)
    {
        Source = source;
        Size = size;
    }

    public IconExtension() { }
}

Xaml usage:

<Image Stretch="None"
       Source="{common:Icon Source={Binding IconResource},Size=16}"/>

or

<Image Stretch="None"
       Source="{common:Icon /ControlsTester;component/icons/custom-reports.ico, 16}" />
g t
  • 7,287
  • 7
  • 50
  • 85
Nikolay
  • 3,658
  • 1
  • 24
  • 25
  • 1
    I make a mistake. You can't set property in Markup Extension with binding (so can't be ) But you can easily write similar converter, that can be used with binding: – Nikolay Aug 11 '11 at 11:55
  • 6
    I love it when I have an odd WPF problem ... and I find a StackOverflow question and answer ... with an elegant solution. Very nice. +1 – cplotts Feb 11 '16 at 20:13
  • 2
    Does this impact performance when using several icons on a `ListBox` for example? Would be nice to _cache_ the decoded icon, so it requires one decode for each icon size. – JobaDiniz Jul 05 '17 at 14:57
4

(based on @Nikolay great answer and follow-up comment about binding)

You probably will be better off creating a Converter instead of a MarkupExtension so that you can leverage Binding. Using the same logic as provided by @Nikolay

    /// <summary>
    /// Forces the selection of a given size from the ICO file/resource. 
    /// If the exact size does not exists, selects the closest smaller if possible otherwise closest higher resolution.
    /// If no parameter is given, the smallest frame available will be selected
    /// </summary>
    public class IcoFileSizeSelectorConverter : IValueConverter
    {
        public virtual object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            var size = string.IsNullOrWhiteSpace(parameter?.ToString()) ? 0 : System.Convert.ToInt32(parameter);

            var uri = value?.ToString()?.Trim();
            if (string.IsNullOrWhiteSpace(uri))
                return null;

            if (!uri.StartsWith("pack:"))
                uri = $"pack://application:,,,{uri}";

            var decoder = BitmapDecoder.Create(new Uri(uri),
                                              BitmapCreateOptions.DelayCreation,
                                              BitmapCacheOption.OnDemand);

            var result = decoder.Frames.Where(f => f.Width <= size).OrderByDescending(f => f.Width).FirstOrDefault()
                ?? decoder.Frames.OrderBy(f => f.Width).FirstOrDefault();

            return result;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

You must then create a resource from your converter class somewhere in a ResourceDictionary as usual:

<localConverters:IcoFileSizeSelectorConverter x:Key="IcoFileSizeSelector" />

And then you can use Binding:

<Image Source="{Binding Path=IconResource, Converter={StaticResource IcoFileSizeSelector}, ConverterParameter=16}" />

PS: in the converter code, we accept all inputs for parameter, even missing or invalid ones. That behaviour is more convenient if like me you like to play with live XAML edit.

Askolein
  • 3,250
  • 3
  • 28
  • 40
3

It doesn't appear to be possible using Xaml only.

SergioL
  • 4,905
  • 3
  • 26
  • 30
1

If the reason you're asking is that the icon looks blurry to you, check out this very good article on the topic that I used to solve that problem: http://blogs.msdn.com/dwayneneed/archive/2007/10/05/blurry-bitmaps.aspx

You will have to use a custom control that not only sizes the icon exactly, but ensures that it coincides exactly with the pixel grid. Only then will you avoid interpolation and therefore blurriness.

Trying to find some info on your query about image size selection in icons...will post back if I find any...

PeterAllenWebb
  • 10,319
  • 3
  • 37
  • 44
  • 2
    Well, it's only blurry in the sense that the image was rescaled. I'm just really curious on how to pick the right image from a multi-image, multi-resolution .ico file in Xaml. – SergioL Jun 05 '09 at 19:07