0

I am trying to edit a bitmap and use it as the icon for my System Tray Icon. I want to know if there is a way I can edit it and use it without having to save it as a local file as this seems messy and leaves a .ico file in the root directory of .exe. The code I have is as follows.

//To Save icon to disk
bitmap.Save("icon.ico", System.Drawing.Imaging.ImageFormat.Icon);
Icon createdIcon = Icon.FromHandle(bitmap.GetHicon());
bitmap.Dispose();
return createdIcon;

Is there a more graceful way to do this, save it as an internal resource? or in a memory stream? The other thing, is this function runs every 5 minutes to update the system tray icon, and it has to pass the icon, so I can work out when I have to close the stream.

Any help would be greatly appreciated as this has me stumped.

Gunwalloe
  • 1
  • 1

1 Answers1

1

The problem is simpler to solve than you're making it. There is no reason to write the bitmap out to a file on disk. You can simply omit the call to the Save method.

The GetHIcon method is the real trick, as it allows you to convert a Bitmap into an Icon, and you've already found that one.

Here's my sample code. I added a NotifyIcon and Timer control to a form, and wired up the event handlers in the obvious manner. rnd is a class-level instance of Random, just for testing purposes.

[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool DestroyIcon(IntPtr hIcon);

private void timer1_Tick(object sender, EventArgs e)
{
   Icon icon;

   // Create a temporary new Bitmap with the size of a notification icon.
   using (Bitmap bmp = new Bitmap(SystemInformation.SmallIconSize.Width, SystemInformation.SmallIconSize.Height))
   {
      // Fill the temporary bitmap with a random number.
      using (Graphics g = Graphics.FromImage(bmp))
      {
         g.DrawString(rnd.Next().ToString(),
                        SystemFonts.MessageBoxFont,
                        SystemBrushes.ControlText,
                        0.0F, 0.0F);
      }

      // Convert this bitmap to an icon.
      icon = Icon.FromHandle(bmp.GetHicon());
   }

   // Update the notification icon to use our new icon,
   // and destroy the old icon so we don't leak memory.
   Icon oldIcon = notifyIcon1.Icon;
   notifyIcon1.Icon = icon;
   DestroyIcon(oldIcon.Handle);
   oldIcon.Dispose();
}

Works perfectly, no temporary files required.

Edit: Modified the above code sample to solve a GDI object leak that would eventually bring your application to its knees. How quickly would depend on the interval at which the Timer was set to tick (or however you're determining that the icon should change in your app).

It turns out that when you call the Dispose method on an icon that was created using Icon.FromHandle, the associated native GDI object is not destroyed. I think that's a bug in the WinForms implementation since it defies programmer expectations, but apparently Icon.FromHandle does not assume ownership of the handle. To be fair, the documentation does say this in the "Remarks" section, but who reads that?

If you don't know to do this, you've got a memory leak on your hands. To fix it, you have to P/Invoke the Win32 function for destroying a GDI icon object (DestroyIcon) and call it explicitly.

Please make sure that your application does this, however and wherever appropriate, to ensure that the old icons get destroyed and the associated memory is freed!

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574