hyperexpert hyperexpert - 4 months ago 30
C# Question

C# - WPF - Project size after adding icons (load icon from resources or pull from project EXE)

Edit1: edited title

Edit2: added Method 3

Edit3: added Method 4




I am creating a very small app/utility that is only 20KB total. My icon file which contains various sizes is 40KB.

The way I am adding my icon is by right-clicking my project in solution explorer and click "Existing Item...", then I would right-click my project again, go to "Properties" -> "Application" and under "Icon and manifest" I select my new icon there for the whole project.

Doing this shows my icon in all of my forms, exe, taskbar, etc...Except for the System Tray icon.

The way I am loading the System Tray icon is by extracting the icon from the EXE:

Method 1:

Icon ico = System.Drawing.Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location);
notifyIcon.Icon = ico;


now my total project size with all my icons loaded is 20KB + 40KB = 60KB <- which is what I am after.

I wanted to know if this is the right way of loading all my icons?

I am aware that I can add my icon in the project "Resources" and do:

Method 2:

notifyIcon.Icon = PROJECTNAME.Properties.Resources.icon;


But, this only doubles the icon size then add project size on top. So:

20KB (Project size) + 40KB (EXE icon) + 40KB (Resources icon) = 100KB

Method 3

"Discord" has posted in the answers below another way to grab the file directly from the executable resources.

IntPtr hIcon = LoadIcon(GetModuleHandle(null), new IntPtr(32512));
notifyIcon.Icon = Icon.FromHandle(hIcon);

[DllImport("user32.dll")]
static extern IntPtr LoadIcon (IntPtr hInstance, IntPtr iconName);
[DllImport("kernel32.dll")]
static extern IntPtr GetModuleHandle (string moduleName);


The only problem with Method 1 and Method 3 right now is that the icon extracts in 32x32 and then shrunk down to 16x16 which does not look good in the system tray.

This answer here by "vanmelle": Avoiding duplicate icon resources in a .NET (C#) project describes how to get the icon in 16x16 but not sure if this will get a 32x32 version for HiDPI resolutions

Method 4

Extracting the icon from the project EXE seems to either do 16x16 or 32x32 depending on the code used. Cant do both and have the OS choose the one it wants for HiDPI systems. The way I am going to do my System Tray icon is unfortunately create another icon file that only has my 16x16 and 32x32 versions of my Application icon then use it like this: (fortunately my 16x16, 32x32 app icon is only 4KB)

notifyIcon.Icon = new System.Drawing.Icon(Properties.Resources.ico, System.Windows.Forms.SystemInformation.SmallIconSize);


This method will call the 16x16 icon or the 32x32 icon if needed.

You could do:

notifyIcon.Icon = new System.Drawing.ico;


But this will always call the 32x32 icon and shrinks it down to 16x16 which may not look good in the System Tray.




Initially, the way I did it was add the icon to "Resources" and have notifyIcon call it from there. Then in Solution Explorer - Project Name -> "Properties" -> "Application" I would select the project icon there. This loads my icon size twice making my project jump from (20KB + 40KB icon) to 100KB

Currently, I am using method 1. I was wondering if there is a better way of loading my icon and keeping my app size small instead of adding the icon size twice?

While I do understand that the differences between 60KB and 100KB is not really a big deal, I just wanted to learn this generally for future references. Also, wanted to know if my first method has any caveats?

I come from a web development background and very new to C# and WPF.

Answer

The icon which you add in the project properties is added to the executable's resources (.rsrc section) with identifier 32512 (IDI_APPLICATION). If you don't like relying on Icon.ExtractAssociatedIcon, you can use a more straightforward way of extracting it — using LoadIcon function. It isn't wrapped in .NET though, so there's more code:

IntPtr hIcon = LoadIcon(GetModuleHandle(null), new IntPtr(32512));
notifyIcon.Icon = Icon.FromHandle(hIcon);

[DllImport("user32.dll")]
static extern IntPtr LoadIcon (IntPtr hInstance, IntPtr iconName);
[DllImport("kernel32.dll")]
static extern IntPtr GetModuleHandle (string moduleName);

See Using the same icon for the .exe and a form in a Windows Forms application without duplicating it? question for more details.

WPF includes methods for converting icons back and forth between HICON and ImageSource, so if you want to display the icon on the form, for example, you can do it too (see Imaging.CreateBitmapSourceFromHIcon etc.).

I see no reason to bother with this though if Icon.ExtractAssociatedIcon works fine. There may be some issues though, this function may extract not all icons, but only specific sizes.

See also Avoiding duplicate icon resources in a .NET C# project question.