如何在WPF中从多分辨率的.ico文件中选择正确大小的图标?

33

如果我有一个多分辨率的图标文件(.ico),如何确保WPF选择正确大小的图标? 设置Image的宽度和高度是否会强制进行选择,还是WPF只是调整ico文件中的第一个图标?

这是我目前使用的方法(虽然有效,但我希望避免调整大小,如果那是发生的事情)。

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

如果可能的话,我希望在Xaml中声明它,而不必为它编写代码。

4个回答

37

我使用简单的标记扩展来实现这个功能:

/// <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用法:

<Image Stretch="None"
       Source="{common:Icon Source={Binding IconResource},Size=16}"/>
或者
<Image Stretch="None"
       Source="{common:Icon /ControlsTester;component/icons/custom-reports.ico, 16}" />

1
我犯了一个错误。你不能在标记扩展中使用绑定来设置属性(因此不能是<Image Stretch="None" Source="{common:Icon Source={Binding IconResource},Size=16}"/>)。 但是你可以轻松编写类似的转换器,可与绑定一起使用: <Image Stretch="None" Source="{Binding Path=IconPath, Converter={StaticResource IconConverter},ConverterParameter=128}"></Image> - Nikolay
6
当我遇到一个奇怪的WPF问题时,我很喜欢在StackOverflow上找到一个问答帖子,里面有一种优雅的解决方案。非常不错,点赞!+1 - cplotts
2
当在 ListBox 上使用多个图标时,这是否会影响性能?如果能够 缓存 解码后的图标,那么每个图标大小只需要解码一次就可以了。 - JobaDiniz

5

(基于@Nikolay的出色答案和后续评论,关于绑定)

你最好创建一个转换器而不是标记扩展,这样你就可以利用绑定。使用@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();
        }

接下来,您需要像往常一样在ResourceDictionary中从您的转换器类创建一个资源:

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

然后您可以使用 Binding

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

PS:在转换器代码中,我们接受所有参数的输入,即使缺失或无效。如果像我一样喜欢使用实时 XAML 编辑器玩耍,这种行为更加方便。


3

仅使用Xaml似乎不可能实现。


1
这真的很不幸,但感谢您的残酷诚实,这比没有回答要好。 - jpierson

1

如果你询问的原因是图标看起来模糊,可以查看这篇非常好的文章,我用它来解决这个问题:http://blogs.msdn.com/dwayneneed/archive/2007/10/05/blurry-bitmaps.aspx

你需要使用自定义控件,不仅要精确调整图标的大小,还要确保它与像素网格完全重合。只有这样才能避免插值和模糊。

正在尝试查找关于图标中图像大小选择的信息...如果找到了会回复...


2
嗯,只是因为图像被重新缩放而变得模糊。我只是非常好奇如何在Xaml中从多图像、多分辨率的.ico文件中选择正确的图像。 - SergioL

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接