WPF, Resources and F#

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

Sounds easy…

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:

Resource library project

Resource library project

Referencing resources

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:

  • DocumentHS.png
    pack://application:,,,/XamlResources;component/Resources/DocumentHS.png
  • Generic.xaml
    pack://application:,,,/XamlResources;component/Themes/Generic.xaml

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:

  1. An application must be constructed
  2. Referenced resources must be added to the active list
  3. 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 =
  &#91;<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
(res:ResourceDictionary) =
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
wnd.Content
<- (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
  • Styles
  • Custom control templates
  • Embedding XAML more generally in F#, including layout and composition markup

Resources

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s