Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Embed Icons into WPF Application as Resource

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 Windows 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?

like image 506
Scott Scowden Avatar asked Jun 11 '10 15:06

Scott Scowden


1 Answers

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">

  <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).

like image 142
Ray Burns Avatar answered Nov 15 '22 09:11

Ray Burns