views:

798

answers:

1

I am trying to embed an icon into my my WPF application so that I can pull it out for use as an icon in the Window 7 JumpList using the following code:

newScene.IconResourcePath = System.Reflection.Assembly.GetEntryAssembly().Location;
newScene.IconResourceIndex = 0;

I've gotten it to work using the following method: http://dennisdel.com/?p=38

However, it doesn't seem like the best approach and it seems like there should be an easier way to embed an icon resource into my application while still leaving the "Icon and Manifest" option checked in the Application properties for my program.

I've tried numerous methods including setting the icon build action as a resource and an embedded resource, but every time I open my .exe in a resource editor, the icon does not appear.

Any suggestions?

+4  A: 

Visual Studio does not come with a way to execute the Win32 resource compiler from a MSBuild task, and none of its embedded functionality for creating resources created raw resources. Because of this your choices are:

  1. Create the .res file "by hand" as described in the linked article, or
  2. Add a build task so you can call the Win32 resource compiler from your .csproj

First I will explain the differences between the five different kinds of "resources" that can exist in a .exe or .dll file, including "Win32 Resources" that JumpList requires.

I will then explain how to construct a custom build task that allows you to embed arbitrary Win32 Resources in a C# or VB.NET executable.


The five kinds of resources in a Win32 executable

There are five different kinds of "resources" that can exist in a .exe or .dll file:

  • Win32 Resources
  • NET Framework "Embedded Resources"
  • CLR Objects within a ResourceSet
  • XAML Resources
  • WPF Resources (Objects within a ResourceDictionary)

Win32 Resources

The original kind of resource was a Win32 "Resource". This kind of resource is defined in a .rc file and has either numbered or named resources, each of which has a type and a blob of data. The
Win32 resource compiler, rc.exe, compiles the .rc file into a binary .res file can then be added to the resulting executable.

Win32 resources are accessed using the Win32 FindResource and LoadResource functions.

Win32 resources are embedded into C++ applications by adding them to the .rc file, which is compiled to a .res file and linked into the executable. They can also be added after the fact using the rc.exe program. For C# and VB.NET applications, MSBuild can add a prebuilt .res file to the executable it creates via the Csc or Vbc compiler or it can build a default one for you. Neither C# nor VB.NET has the ability to build non-default .res files from .rc files, and there is no MSBuild task to do this for you.

You can view the Win32 Resources in a .exe or .dll by opening the .exe or .dll file itself in Visual Studio using File -> Open.

A typical C, C++ or MFC application will have many Win32 Resources, for example every dialog box will be specified by a resource.

A typical WPF application will have just the three default Win32 resources constructed by the C# or VB.NET compiler: The version resource, the RT_MANIFEST, and the application icon. The contents of these resources are constructed from Assembly attributes in the code and the <ApplicationIcon> element in the .csproj or .vbproj file file.

This is the kind of resource that the JumpList is looking for.

Embedded Resources

An "embedded resource" is a NET Framework resource. The data structure containing these resources is managed by the CLR in a manner more conducive for access by managed code. Each resource is identified by a string name, which by convention begins with the namespace of the class the resource is associated with.

An embedded resource is just a blob of binary data with a name. The actual data type is either known by the caller or inferred from the name, similar to files in a file system. For example, an embedded resource with a name ending in ".jpg" is likely to be a JPEG file.

Embedded resources are accessed using Assembly.GetManifestResourceStream and its siblings GetManifestResourceInfo and GetManifestResourceNames.

Embedded resources are embedded into .exe and .dll files by adding the file to the project and setting the build action to "Embedded Resource".

You can view the Embedded Resources in a .exe or .dll by opening it in NET Reflector and looking at the "Resources" folder.

Embedded resources are commonly used in WinForms but almost never with WPF.

Resource Sets (.resx/.resources)

Multiple NET Framework objects such as strings and icons can be combined together into a single "Resource Set" data struture that is stored in the .exe as a single NET Framework Embedded Resource. For example this is used by WinForms to store things like icons and strings that are not easy to include in the generated code.

Objects in a Resource Set can be retrieved individually using the ResourceManager and ResourceSet classes defined by the CLR.

Objects in a Resource Set are defined in source code by a .resx file. The data can be directly in the .resx file (as in the case of strings) or referenced by the .resx file (as in the case of icons). When the project is built, the content specified by each .resx files is serialized into a binary form and stored as a single Embedded Resource with the extension ".resx" replaced by ".resources".

You can view the objects in a Resource Set by opening the .exe or .dll in NET Reflector, opening the Resources folder, clicking on a ".resources" file, and looking at the items in the right-hand pane.

Many WinForms-era features commonly used .resx files and ResourceSets in a manner similar to the old Win32 .rc files, to store multiple resources such as strings all together. They are also used by WinForms itself for storing settings on a form that cannot go in the code behind.

WPF applications almost never uses arbitrary objects in ResourceSets, though WPF itself uses ResourceSets internally to store compiled XAML.

XAML Resources

A WPF XAML Resource is a compiled XAML file that is stored inside a ResourceSet. The name inside the resource set is the original filename with ".xaml" replaced with ".g.baml". The content can be any valid XAML, the most common types being Window, Page, UserControl, ResourceDictionary, and
Application.

WPF Resources can be loaded using Application.LoadComponent() or by referencing the original XAML file name in a WPF context. In addition, any WPF Resource that has code behind (as specified by x:Class) will automatically be loaded and applied to each object that is created of that class during its InitializeComponent call.

WPF Resources are created by adding a .xaml file to your project and setting its build action to "Resource", "Page", or "ApplicationDefinition". This causes the compiler to compile the file to BAML and add it to the appropriate ResourceSet.

You can view the XAML resources in .exe or .dll by opening it in NET Reflector with the BamlViewer add-in installed, selecting Tools -> BAML Viewer from the menu, and using the BAML Viewer to browse to the specific .g.baml file inside the .resources.

WPF Resources within a ResourceDictionary

In WPF, almost all of what are known as "resources" are entries in a ResourceDictionary. The ResourceDictionaries are described in XAML, either within other objects such as Windows and UserControls, or in separate XAML files that contain only a ResourceDictionary. Each is identified by an "x:Key", which can be any object type. The resources themselves can also be any object type.

WPF resources can be referenced in XAML using the {StaticResource} and {DynamicResource} markup extensions, or can be loaded in code using FindResource.

WPF resources are added to a ResourceDictionary by adding them to the XAML file that contains the ResourceDictionary inside the <ResourceDictionary> element and giving them a x:Key attribute.

WPF resources are used extensively in WPF, including brushes, styles, data, geometries, templates, etc.

You can view the WPF Resources in a .exe or .dll by browsing the XAML Resources as described above and for each one looking inside the ResourceDictionary tags to see the resources themselves.


Including Win32 Resources in a C# or VB.NET executable

How to easily embed arbitrary Win32 Resources into a C# or VB.NET .exe

You will note from the discussion above that it is easy to add every type of resource to your C# or VB.NET application except for Win32 Resources. To make this easy you can add an additional build task and target. Here is how:

  1. Construct a project containing a single "Win32ResourceCompiler" build task and compile it
  2. Create a .targets file that contains a single target that uses this task to automatically build a .rc file into a .res
  3. Set your project to use the resulting .res file

The task is extremely simple:

public class Win32ResourceCompiler : ToolTask
{
  public ITaskItem Source { get; set; }
  public ITaskItem Output { get; set; }

  protected override string ToolName { get { return "rc.exe"; } }

  protected override string GenerateCommandLineCommands()
  {
    return @"/r /fo """ + Output.ItemSpec + @""" """ + Source.ItemSpec + @"""";
  }

  protected override string GenerateFullPathToTool()
  {
    // TODO: Return path to rc.exe in your environment
  }
}

The .targets file is also very simple. It will be something along these lines:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"&gt;

  <UsingTask TaskName="SomeNamespace.Win32ResourceCompiler" AssemblyFile="Something.dll" />

  <PropertyGroup>
    <CoreCompileDependsOn>$(CoreCompileDependsOn);CompileWin32RCFile</CoreCompileDependsOn>
  </PropertyGroup>

  <Target Name="CompileWin32RCFile" Outputs="@(Win32RCFile->'%(filename).res')">
    <Win32ResourceCompiler
      Source="@(Win32RCFile)"
      Output="@(Win32RCFile->'%(filename).res')" />
  </Target>
</Project>

Now in your .csproj file, add a reference to your .targets file:

<Import Project="Win32ResourceCompiler.targets" />

And of course you need to give your .rc file a file type of Win32RCFile:

<ItemGroup>
  <Win32RCFile Include="MyWin32Resources.rc" />
</ItemGroup>

With this setup you can create a traditional Win32 .rc file to specify all your Win32 resources, including your version, manifest, application icon, and as many additional icons as you want. Every time you compile, all these Win32 resources will be added to your .exe file.

This takes a little while to set up but is much more satisfying and simpler in the long run than manually editing a .res file.

You can specify multiple icons in your .rc file like this:

1 ICON ApplicationIcon.ico
2 ICON JumpListIcon.ico
3 ICON AnotherIcon.ico

Here is the documentation for all the resource definition statements you can use in a .rc file.

Also note that the above .targets file was typed up on the spur of the moment and has not been tested. Documentation on the syntax of MSBuild (.csproj and .targets) files can be found here and here, and good examples of .targets files can be found in the c:\Windows\Microsoft.NET\Framework\v3.5 directory).

Ray Burns
This option works for the main program icon, but I need to add additional icons to my application.
rattrick1
Using this technique you can add as many icons as you like to your .exe file as Win32 Resources. The first will be the main application icon. The others can be used in Jump Lists, shell shortcuts, etc. I added an example of this to the bottom of my answer.
Ray Burns
Thank you very much for this detailed explanation. I was certainly shooting in the dark with regards to resources until your explanation.However, I am having a problem with the editing of the .csproj and importing my .targets file. I get the following error:The element <Target> is unrecognized, or not supported in this context.I think it's my lack of understanding regarding the referencing of the task as I wasn't able to figure out how to do this based on the Microsoft.Common.targets file. Other than that, I think I've got everything.
rattrick1
From the error my guess is that your .targets file omitted the `<Project>` or the xmlns definition. I edited my answer to better explain how the .targets file should be created, including complete (but untested) example code. See if using what I wrote gets you past this error.
Ray Burns
Alright, so I've tried my hardest to get this working on my own, but have had no luck. Forgot to mention that I am using VS2010/.NET4.I have gotten to the point where it appears my .csproj is calling into my assembly, but is unable to find my ToolTask. I have had to close/reopen VS2010 everytime as it doesn't appear to rebuild correctly (even if I clean) unless I do so. I also have had better luck using the "AssemblyName" attribute of UsingTask since MSBuild 4 has a bug with using parentheses right now, but I've gotten the same error either way.
rattrick1
Ok, so the error I am getting is this: I get the following error: The "Win32ResourceCompiler" task was not found. Check the following: 1.) The name of the task in the project file is the same as the name of the task class. 2.) The task class is "public" and implements the Microsoft.Build.Framework.ITask interface. 3.) The task is correctly declared with <UsingTask> in the project file, or in the *.tasks files located in the "C:\Windows\Microsoft.NET\Framework\v4.0.30319" directory.
rattrick1
This error can mean several things: 1. Your TaskName= in UsingTask doesn't end in ".Win32ResourceCompiler" (including spelling errors), 2. The namespace is wrong on the TaskName=, 3. The assembly identified by AssemblyFile= does not contain a class matching TaskName=, 4. The class in AssemblyFile= does not implement ITask.
Ray Burns
To diagnose the exact problem, I would run MSBuild.exe under the debugger in the directory containing your .csproj with a breakpoint on Assembly.LoadFrom() and see whether the assembly is being loaded. If so, carefully compare the class name reported by Reflector against the one in UsingTask and make sure it implements ITask. If not, compare the name in UsingTask against the target element name. And definitely use AssemblyFile for testing not AssemblyName. AssemblyName can easily pick up the wrong file whereas AssemblyFile can take an absolute path name.
Ray Burns
If that fails, please post your .targets file and the full code for your task project. Your task project should be a Class Library (dll) project containing only the single class Win32ResourceCompiler. Also make sure the ITask it implements is from the same Microsoft.Build.Framework.dll as the one MSBuild is using.
Ray Burns
It appears that it would be working after some further toying with it, but I get the following now: Unable to copy file "obj\Debug\AssemblyName.exe" to "bin\Debug\AssemblyName.exe". The process cannot access the file 'bin\Debug\AssemblyName.exe' because it is being used by another process.Is it a requirement that I need to place this logic in a different assembly since it appears to be trying to read from the same assembly it is trying to build or am I on the total wrong path? Again, thanks so much for your time and help.
rattrick1
I think you just answered my question in your previous comment that you must have typed while I was typing mine. Let me give that a try.
rattrick1
Well, it seems to compile the .res file just fine now and everything would appear to be working.... Except, when I try to view the resources in Visual Studio (or in an Icon editor program), they are not there. By using File -> Open in Visual Studio on my resulting .exe, I get "Cannot enumerate resources in the executable." Any thoughts?
rattrick1
In your project properties under the Application tab, make sure "Resource File" is selected instead of "Icon and manifest", and that it specifies the .res file that is being built by your custom task. You can also diagnose this by using rc.exe to manually add the resources to your executable and seeing if that allows you to see the resources in Visual Studio.
Ray Burns
Well Ray, you are a genius and a very helpful man. I did have the Resource File option selected, but had some other issues that I was able to figure out and solve after doing some rudimentary tests to prove some of the basics were working. I can prove that the resources are being loaded into the .res and in turn into my .exe correctly, so everything you've posted is working correctly. My JumpList seems to only work when specifying an IconResourceIndex of 0, but that's just something I'll have to work through, but everything you've done has been extremely helpful so thank you very much.
rattrick1