How do you select the right size icon from a multi-resolution .ico file in WPF? How do you select the right size icon from a multi-resolution .ico file in WPF? wpf wpf

How do you select the right size icon from a multi-resolution .ico file in WPF?


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}" />


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


(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.