Introduction
This article is of threefold purpose. It explains how to use resources housed in separate assemblies with pleasant syntax. A tool is also provided to help generate the plumbing necessary to use the nice syntax. Finally there are a few vector versions of some familiar icons that should be useful.
This article started when I wanted to place the icon for a public type on a button as part of an application that selected types. I was surprised and dismayed at being unable to find one but rather than put “type” on my button, I created one in Illustrator. Being pleased with the result, more icons followed and they ended up in their own library. At this point, my pleasure started to subside. The syntax using the ComponentResourceKey
bothered me much more than I expected. I began to wonder if the reason I was unable to get results from my vector icon search was this difficulty (Bitmaps are plentiful). This article was written to promote vector icons, so I included the application I used to create the XAML. Understanding the road to the pleasant syntax is a good thing, but nice icons are important enough that understanding binding should be optional. Also graphic artists, who create a lot of the icons out there, often care very little for the subtleties of programming syntax.
References to Resources in Other Assemblies
Referencing resources is separate assemblies is significantly harder than those in the assembly using the resource, but with a little preparation it does not need to be. A reference of...
{StaticResource MethodIconBrush}
... can easily become something on the order of...
{DynamicResource {ComponentResourceKey TypeInTargetAssembly=
{x:Type icon:LibraryResources}, ResourceId=MethodIconBrush}}
... which can be a bit on the disheartening side. Fortunately for DrawingBrush
objects, we can walk all the way back to the simpler syntax.
ComponentResourceKey Method
In order to access resource in separate assemblies, Microsoft provides the ComponentResourceKey
markup extension. In order to use it, there are some preconditions.
- There must be at least one
public
type in the library assembly. (It cannot be entirely composed of resource dictionaries.)
- The Resource must be defined either directly of indirectly in Generic.xaml which is located in the themes folder.
- The Key for the resource should be in a special form.
If those preconditions are met, then the following syntax will work.
<Rectangle Margin="31,12,0,0" Stroke="Black" Height="62" VerticalAlignment="Top"
Fill="{DynamicResource {ComponentResourceKey TypeInTargetAssembly=
{x:Type icon:LibraryResources}, ResourceId=EventIconBrush}}"
HorizontalAlignment="Left" Width="81" />
This fills the Rectangle
with the EventIconBrush
so one can see the lightning bolt. The EventIconBrush
is a DrawingBrush
that is located in the ReflectionIconLib
assembly. It is referenced in the window via icon
.
xmlns:icon ="clr-namespace:ReflectionIconLib;assembly=ReflectionIconLib"
ReflectionIconLib
is for this purpose simply a class in the assembly. Finally the EventIconBrush
has the following key where it is defined.
x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:LibraryResources},
ResourceId=EventIconBrush}"
Static ComponentResourceKey Property Method
One notices that a large portion of the binding expression is taken up with getting the ComponentResourceKey
. Since we are required to have a class in the resource library, we could use it to generate the ComponentResourceKey
.
public static ComponentResourceKey XmlIconBrushKey
{
get
{
ComponentResourceKey result =
new ComponentResourceKey(typeof(LibraryResources), "XmlIconBrush");
return result;
}
}
This generates the ComponentResourceKey
for the XmlIconbrush
. It also allows one to simplify the binding expression to:
{StaticResource {x:Static icon:LibraryResources.DelegateIconBrushKey}}
Static Resource Property Method
Now if one can have a static
method generate the ComponentResourceKey
, why shouldn't one just generate the entire resource as a static
property.
public static DrawingBrush XmlIconBrush
{
get
{
ResourceDictionary rd = new ResourceDictionary();
rd.Source = new Uri
("ReflectionIconLib;component/Themes/XmlIconBrush.xaml",
UriKind.Relative);
DrawingBrush dt = (DrawingBrush)rd[LibraryResources.XmlIconBrushKey];
return dt;
}
}
This gets us the XmlIconBrush
and allows us to use the following still shorter binding expression syntax.
{x:Static icon:LibraryResourcesDelegateIconBrush}
This is probably the best that one can do that will work for arbitrary resource types. There is probably some overhead from creating the ResourceDictionary
but it could easily be cached if desired.
Static Resource Method
We can take advantage of the fact that the Drawing
property of a DrawingBrush
is a read-write DependencyProperty
. This means that the Drawing
property can be set by a binding expression, so one can do the following!
<DrawingBrush x:Key="A"/>
<DrawingBrush x:Key="B" Drawing="{Binding Source={StaticResource A}, Path=Drawing}"/>
Normally this is not very exciting, however in the case of these extra-assembly brushes, it allows the following:
<DrawingBrush x:Key="PropertyIconBrush"
Drawing="{Binding Source={x:Static icon:LibraryResources.PropertyIconBrush},
Path=Drawing}" Stretch="Uniform"/>
Assuming that the proper setup has been performed, Drawing
brushes outside the assembly can be used with the same syntax as those within. The next section will explain how easily and efficiently we can generate the necessary plumbing.
Generating An Icon Library - Step by Step
Illustrator
First create your icons in Illustrator. The ai file is among this article's downloads so it can be used as a template. As this file is going to be imported into Blend, there are two ways one can work. Either one can put each icon on its own layer or have one icon per file. Personally I prefer the more organized method although it may be slightly more time consuming.
Blend
Blend is capable of Importing illustrator files and then converting them to DrawingBrush
resources.
It is important that one makes DrawingBrush
rather than VisualBrush
resources. One gets the poetic "freezable cannot be frozen" error if one tries to use a VisualBrush
resource across assemblies.
Icon Library Helper
The Icon Library Helper helps generate the code for the four files that need to be created/updated for each resource that is added. These are the:
- Resource dictionary to hold the new resource definition
- Generic.Xaml to reference this new resource dictionary
- LibraryResources.cs to contain the
static
properties used to access the resource
- The satellite resource dictionary that allows us to use the
static
resource syntax
The helper will generate either the content for entire files or if "Additions Only" is checked, it will only generate the new information. As a special bonus if "Turbo Copy" is checked, then selecting one of the treeview
nodes will not only generate the code but place it in the clipboard as well. If you are creating a new icon library, it is best to start with a WPF custom control library as that will create the appropriate necessary structure.
New Resource Dictionary
The Helper will generate:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:ReflectionIconLib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DrawingBrush x:Key="{ComponentResourceKey TypeInTargetAssembly=
{x:Type local:LibraryResources}, ResourceId=NewResource}" >
<drawingbrush>
One needs only to create a new resource dictionary in the themes folder.
Replace it's contents with the generated contents of (in this case)
NewItem.xaml from the helper. Then copy the DrawingBrush.Drawing from Blend
into the DrawingBrush. You may also want to
copy in the Viewbox, ViewboxUnits and Stretch attributes.
</resourcedictionary>
Generic.xaml
In order to be accessed from other assemblies, the resource needs to be merged into the Generic.xaml resource dictionary. The helper will generate the appropriate XAML which for a Library assembly named ReflectionIconLib
and resource NewResource
would be as follows:
<resourcedictionary source="/ReflectionIconLib;component/Themes/NewResource.xaml" />
It is worth noting that the /ReflectionIconLib;component
portion is not optional.
LibraryResources.cs
After that, the LibraryResources.cs file needs to be edited. Unlike Generic.xaml the use of LibraryResources
is an arbitrary convention. For each Resource, two static
properties are needed that return the ComponentResourceKey
and the resource itself. The helper will generate the appropriate C# code which for a Library assembly named ReflectionIconLib
and resource NewResource
would be as follows:
public static ComponentResourceKey NewResourceKey
{
get
{
ComponentResourceKey result =
new ComponentResourceKey(typeof(LibraryResources), "NewResource");
return result;
}
}
public static DrawingBrush NewResource
{
get
{
ResourceDictionary rd = new ResourceDictionary();
rd.Source = new
Uri("ReflectionIconLib;component/Themes/NewResource.xaml",
UriKind.Relative);
DrawingBrush dt = (DrawingBrush)rd[LibraryResources.NewResourceKey ];
return dt;
}
}
At this point, we have completed all of the alterations needed in the library assembly.
Satellite Resource Dictionary
All that remains is the satellite resource dictionary. The helper will generate something like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:icon ="clr-namespace:ReflectionIconLib;assembly=ReflectionIconLib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DrawingBrush x:Key="NewResource"
Drawing="{Binding Source={x:Static icon:LibraryResources.NewResource},
Path=Drawing}" Stretch="Uniform"/>
</ResourceDictionary>
The convention used is to give it the name of the Library assembly, but that is arbitrary. It is also noteworthy that there is no reference in the file to the assembly in which it is being used. In other words, the same resource dictionary can be used in any assembly.
Testing
The Icon Library Helper can also test assemblies that are put together via the conventions in this article.
Conclusion
In conclusion, using references across assemblies is significantly more trouble than it probably ought to be. Fortunately with some tools and organization, it can be made no harder for the end developer than using in assembly resources. You are most welcome and encouraged to use the ReflectionIconLib
. There is also a clickonce publish for IconLibraryHelper.