将System.Drawing.Icon转换为System.Media.ImageSource

55

我有一个IntPtr,它是在非托管/托管边界上进行封送的图标句柄。通过FromHandle()方法将其转换为Icon很容易,而且直到最近这还是令人满意的。

基本上,现在我的线程出现了足够奇怪的情况,以至于我一直在玩MTA / STA舞蹈,以避免托管的WinForm破坏应用程序的主要(WPF友好)UI,但过于脆弱难以继续使用WinForm。

那么,如何获取Icon的ImageSource版本?

请注意,我已经尝试过ImageSourceConverter,但无法成功。

另外,我可以获取一些(但不是全部)涉及到的图标的底层资源,它们通常存在于我的应用程序程序集之外(实际上,它们经常存在于非托管dll中)。

7个回答

97

不创建任何额外对象的简单转换方法:

    public static ImageSource ToImageSource(this Icon icon)
    {
        ImageSource imageSource = Imaging.CreateBitmapSourceFromHIcon(
            icon.Handle,
            Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());

        return imageSource;
    }

10
这个解决方案似乎比使用Imaging.CreateBitmapSourceFromHBitmap更加优雅。当Imaging可以直接从图标句柄创建BitmapSource时,就不需要创建未托管的位图(然后必须记得处理它)。 - Richard Walters
1
你正在将过时的 System.Drawing 引入到 WPF 中... 如果你是一个 WPF 纯粹主义者,这是不可接受的。 - Andy
CreateBitmapSourceFromHIcon 位于 System.Windows.Interop 命名空间中,因此请确保添加您的 Using 或 Import 语句。 - Fütemire
1
@Andy 我不明白你的意思,因为 OP 已经在使用 System.Drawing.Icon 了。 - radj307
@radj -- 请阅读他的问题。具体来说,我有一个IntPtr跨越非托管/托管边界进行了封送,它对应于一个图标句柄。他没有引入System.Drawing.Icon。因此根本没有理由使用System.Drawing。看看*@Justin Davis*的答案。那是正确的方法。 - Andy
@Andy 在那段文字的后半部分,OP提到了* FromHandle *方法,我理解为System.Drawing.Icon.FromHandle(IntPtr); 尽管我同意你提到的答案是一个更好的方法,并且如果我在原始评论中表现得粗鲁,我向你道歉。 - radj307

60

尝试这个:

Icon img;

Bitmap bitmap = img.ToBitmap();
IntPtr hBitmap = bitmap.GetHbitmap();

ImageSource wpfBitmap =
     Imaging.CreateBitmapSourceFromHBitmap(
          hBitmap, IntPtr.Zero, Int32Rect.Empty, 
          BitmapSizeOptions.FromEmptyOptions());

更新:采纳Alex的建议并将其变为扩展方法:

internal static class IconUtilities
{
    [DllImport("gdi32.dll", SetLastError = true)]
    private static extern bool DeleteObject(IntPtr hObject);

    public static ImageSource ToImageSource(this Icon icon)
    {            
        Bitmap bitmap = icon.ToBitmap();
        IntPtr hBitmap = bitmap.GetHbitmap();

        ImageSource wpfBitmap = Imaging.CreateBitmapSourceFromHBitmap(
            hBitmap,
            IntPtr.Zero,
            Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());

        if (!DeleteObject(hBitmap))
        {
            throw new Win32Exception();
        }

        return wpfBitmap;
    }
}

然后你可以这样做:

ImageSource wpfBitmap = img.ToImageSource();

2
完成此转换后,您应该在gdi32.dll中使用DeleteObject(IntPtr hObject)调用hBitmap以避免内存泄漏。 - Alex
即使更新后的解决方案可能在某些情况下引起“参数无效”问题http://blog.lavablast.com/post/2007/11/The-Mysterious-Parameter-Is-Not-Valid-Exception.aspx,但我需要进一步调查。使用Darren的解决方案可能更安全。 - Jeff Moser
我在网上搜索了相当长的时间,试图找到这个问题的答案。大多数人建议将“资源”而不是“嵌入式资源”用于他们的项目。问题在于,如果您不希望在项目中分发和隐藏资源信息,则需要将它们嵌入。有趣的是要注意返回的资源的<Type>。例如,在WPF中,要在窗口上设置图标,需要一个<IconSource>类型。访问“Properties.Resource.<Icon>”具有本机GDI类型,并且需要转换为Windows.Media。 - Latency
如果您为主应用程序使用图标,则它会将其本地化到项目中。如果您希望与MainWindow图标共享此内容,只需在XAML中使用Icon="pack://application:,,,/MyApp;component/image.ico">,它将以相同的方式嵌入到程序集中。 - Latency

10
MemoryStream iconStream = new MemoryStream();
myForm.Icon.Save(iconStream);
iconStream.Seek(0, SeekOrigin.Begin);
_wpfForm.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(iconStream);

1
你需要处理MemoryStream吗? - devios1
应该在流操作完成后使用Dispose,但由于某种原因,这给我带来了非常低质量、黑白的图像。还有其他人遇到这个问题吗? - Patrick Klug
1
@Patrick,我也曾经使用MemoryStream遇到过输出质量非常低的问题。我使用了下面Byte提供的这个解决方案 - Jon Peterson
你不需要处理MemoryStream,它除了标记为已处理外什么也不做:http://referencesource.microsoft.com/#mscorlib/system/io/memorystream.cs,0b83d17ca69bf8ea,references。然而,这将稍微提高GC的性能,因为Stream.Dispose会将其从终结队列中移除。 - poizan42

10

在使用一次性流时,几乎总是建议使用 'using' 块来强制正确释放资源。

using (MemoryStream iconStream = new MemoryStream())
{
   icon.Save(iconStream);
   iconStream.Seek(0, SeekOrigin.Begin);

   this.TargetWindow.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(iconStream);
}

其中icon是源System.Drawing.Icon,this.TargetWindow是目标System.Windows.Window。


1
使用这种方法得到的图像质量非常低。 - user230910
MemoryStream.Dispose方法并不释放任何东西,参考http://referencesource.microsoft.com/#mscorlib/system/io/memorystream.cs,0b83d17ca69bf8ea,references。 - poizan42
这会导致我的WPF应用程序崩溃并出现disposing异常。因此,当显示图标时,Windows稍后需要该内存流。您需要在整个窗口的生命周期中保持该内存流处于活动状态,并在窗口关闭时将其处理掉。 - Andy
我已经修复了它...使用这个格式,你可以像上面的示例一样处理内存流:BitmapFrame.Create(iconStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad) - Andy

2

从上面的一些内容中,我为自己创建了最高质量的图标。从字节数组中加载图标。我在缓存加载时使用缓存,因为如果不这样做,在释放内存流时会出现已释放异常。

   internal static ImageSource ToImageSource(this byte[] iconBytes)
    {
        if (iconBytes == null)
            throw new ArgumentNullException(nameof(iconBytes));
        using (var ms = new MemoryStream(iconBytes))
        {
            return BitmapFrame.Create(ms, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
        }
    }

0

这是一个类似的例子,只是根据开发者的使用情况进行了调整...

    [DllImport("shell32.dll")]
    public static extern IntPtr ExtractIcon(IntPtr hInst, string file, int nIconIndex);

    [DllImport("user32.dll", SetLastError = true)]
    static extern bool DestroyIcon(IntPtr hIcon);

    /// <summary>
    /// Gets application icon from main .exe.
    /// </summary>
    /// <param name="setToObject">object to which to set up icon</param>
    /// <param name="bAsImageSource">true if get it as "ImageSource" (xaml technology), false if get it as "Icon" (winforms technology)</param>
    /// <returns>true if successful.</returns>
    public bool GetIcon(object setToObject, bool bAsImageSource)
    {
        String path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
        path = Path.Combine(path, "yourmainexecutableName.exe");
        int iIconIndex = 0;

        // If your application contains multiple icons, then
        // you could change iIconIndex here.

        object o2set = null;
        IntPtr hIcon = ExtractIcon(IntPtr.Zero, path, iIconIndex);
        if (hIcon == IntPtr.Zero)
            return false;

        Icon icon = (Icon)Icon.FromHandle(hIcon);
        if (bAsImageSource)
        {
            o2set = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                icon.ToBitmap().GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, 
                System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
        } else {
            icon = (Icon)icon.Clone();
        }

        DestroyIcon(hIcon);
        setToObject.GetType().GetProperty("Icon").SetValue(setToObject, o2set);
        return true;
    } //GetIcon

-1

这个问题有一个非常简单的解决方案。

步骤:

(1) 在解决方案资源管理器中将图像添加到资源 -> resources.resx (2) 在解决方案资源管理器中编辑“Resources”目录中的图像属性,并将“生成操作”更改为“Resource”

在xaml中,添加以下内容...

Icon="resources/图像名称"(其中“图像名称”是您添加到资源中的图像名称 - 请参见第(1)点)。


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