The problem is a simple one: add a toolbar image into an assembly and make it available as a resource in WPF, but use only XAML and F#.
The first obstacle you have is embedding an image into the application assembly. You can do this by hacking the project file, or (more easily) by adding a second project to the solution. This second project can be created as a “C# WPF Custom Control Library”, although you do not have to use any C# and those files can be removed. The Settings and Resources files can also be removed, and the XAML file is optional. I will use it in this example because it makes things easier.
You can now add an image file to the control library project and give it a build action of Resource. This is necessary to bundle the file in the compiled assembly.
At this point, you can create an assembly with a bundled resource file and optionally a bundled XAML resource as well. In my case, I am using the default XAML file Themes/Generic.xaml and a standard Visual Studio image Resources/DocumentHS.png. The project will look something like the following:
To reference resources, you need a basic understanding of WPF Pack URIs. See the MSDN link at the bottom of this post for detailed information on these. Essentially, these URIs tell WPF where it can find a resource, and that resource may not be in the same assembly as the calling assembly. This is true in our case. We have two resources that need referencing as follows:
The pack prefix tells WPF that these URIs use the WPF Pack URI scheme. Next, application:,,, tells WPF that these resources are identified by assemblies. Then, /XamlResources;component lets WPF know that it is the XamlResources assembly that is being referenced. Finally, /Resources/DocumentHS.png and /Themes/Generic.xaml are the relative paths to those resources within the assembly.
To make a working example, I define an image in a resource dictionary in Generic.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Image x:Key="NewButtonImage" Source="pack://application:,,,/XamlResources;component/Resources/DocumentHS.png"/> </ResourceDictionary>
Note that I had to use the full URI in this XAML even though the resource is part of the same assembly.
Putting it all together
To complete this example, an application is needed and that application must show the image using only the pack URI for the XAML file and the resource key, and it must be in F#. There are a few parts to this:
- An application must be constructed
- Referenced resources must be added to the active list
- The image must be shown
I am going to make the example as simple as possible in order to focus solely on the resource bundling behaviour.
Constructing the application
If you have not already got one, add an F# application to the solution. It will work as a console application but if you want to change it then I recommend editing the project file directly and changing the target type to WinExe manually.
Next, add assembly references to System.Xml, WindowsBase, PresentationCore and PresentationFramework. Of course, you also need a reference to your resource library project!
Now, add an application skeleton as follows:
#light namespace ResourceBundling open System open System.Windows type App() = inherit Application() // Place-holder function for adding custom // resources to the main assembly member private self.AddLocalResources (res:ResourceDictionary) = () // Place-holder function for adding custom resources // from other assemblies member private self.AddReferencedResources (res:ResourceDictionary) = // This code will be changed later in this post () // Skeleton for constructing resources member private self.BuildResources() = let res = new ResourceDictionary() do self.AddLocalResources res do self.AddReferencedResources res do self.Resources <- res // Placeholder for creating main window member private self.CreateMainWindow() = // This code will be changed later in this post let wnd = new Window() wnd // Skeleton for running application member self.RunApp() = do self.BuildResources() do self.Run(self.CreateMainWindow()) // Module to contain application entry point module Program = [<STAThread>] [<EntryPoint>] let Main (args:string array) = (new App()).RunApp()
Adding referenced resources to the active list
The most appropriate place for these to go (that I am aware of) is in the MergedDictionaries property of the application resources. This is because other properties defined elsewhere (in the main application resources or on in the WPF tree) will override the values. It also means that any keys specified in the merged dictionary do not conflict with the same keys specified in other resource dictionaries.
So, AddReferencedResources needs to be changed to modify the application resource dictionary. This involves creating a new ResourceDictionary with its Source property set to the pack URI of the referenced resource dictionary:
member private self.AddReferencedResources
let inline assemblyResourceDictionary
(asm:string) (file:string) =
let res = new ResourceDictionary()
res.Source <- new Uri( List.fold (+) "" [ @"pack://application:,,,/" ; asm ; @";component/" ; file]) res let resm = res.MergedDictionaries resm.Add (assemblyResourceDictionary @"XamlResources" @"Themes/Generic.xaml")[/sourcecode] I have factored out the construction of the URI and the creation of the ResourceDictionary so that multiple assemblies and resources can easily be referenced.
Showing the image
Finally, to display the image we need to load the resource from the resource dictionary and set it as the content of the main window:
member private self.CreateMainWindow() =
let wnd = new Window()
// You can cast the result of get_Item to an Image
// but it is unnecessary in this example
<- (Application.Current.Resources.get_Item( @"NewButtonImage")) wnd[/sourcecode] I am using this technique to construct a toolbar but it is flexible to a range of scenarios:
- Images, icons, videos, sounds
- Custom control templates
- Embedding XAML more generally in F#, including layout and composition markup
- MSDN: WPF resource files reference: http://msdn.microsoft.com/en-us/library/aa970494.aspx
- MSDN: Pack URI reference: http://msdn.microsoft.com/en-us/library/aa970069.aspx
- MSDN: Getting resources using code (including walking the WPF containment tree): http://msdn.microsoft.com/en-us/library/ms752326.aspx
- MSDN: Merged Resource Dictionaries reference: http://msdn.microsoft.com/en-us/library/aa350178.aspx