I have noticed some interest in my earlier post on how to add a WPF data template to a resource dictionary in code and so this post is a demonstration of the technique. The code is presented in F# but I have deliberately kept the code simple and fairly non-functional so that those of a C# or VB.NET background can follow the process easily.
At the end of my previous post (Programmatic Addition of a WPF Data Template to a Resource Dictionary), I recommended that the XamlReader class was used to create data templates. Using the FrameworkElementFactory class is now deprecated.
The following code is a very simple example of the recommended process:
- Create a window containing a StackPanel and a ListBox
- Add a list of strings to the ListBox
- Control the display of the ListBox using a dynamically added data template
Here is the code (for the F# interpreter):
// Reference required assemblies #r "System.Xml" #r "WindowsBase" #r "PresentationCore" #r "PresentationFramework" // Open required namespaces open System.Xml open System.IO open System.Windows open System.Windows.Controls open System.Windows.Markup // Load an object from a XAML string let loadFromString xaml = use sr = new StringReader(xaml) use xr = XmlReader.Create(sr) XamlReader.Load xr // Create a data template resource pair let buildDataTemplate<'T> xaml = (new DataTemplateKey(typeof<'T>), (xaml |> loadFromString :?> FrameworkTemplate)) // XAML for the data template let myDataTemplateXaml = @"<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:m=""clr-namespace:System;assembly=mscorlib"" DataType=""{x:Type m:String}"" ><TextBox Text=""{Binding Mode=OneWay}""/></DataTemplate>" // Create a list box to hold the templated items let lb = new ListBox() // Add the data template do myDataTemplateXaml |> buildDataTemplate<System.String> |> lb.Resources.Add do lb.ItemsSource <- ["Item 1" ; "Item 2" ; "Item 3"] // Build the window let wnd = new Window() let sp = new StackPanel() do (sp:>IAddChild).AddChild lb do wnd.Content <- sp // Show the window and run the application // Note: the F# Interpreter must be reset // between executions because of the // behaviour of Application do (new Application()).Run(wnd) |> ignore
I have chosen to use System.String as a very simple example but this method works for referencing any assembly available to your application. The Binding element is OneWay because it is bound to a constant string, but this is not generally required.
Pingback: Rick Minerich's Development Wonderland : F# Discoveries This Week 08/31/2009
Hi Steve,
I love this example, but I’m having a hard time getting it to work with new types specified in F# Interpreter (i.e. not in any assembly). Is there a namespace for the FSI that I can’t find? Or another way around this?
Much appreciated.
Matt
Hi Matt,
I am not sure you will be able to make it work (certainly not easily) because of the way FSI creates classes. In particular, you cannot use namespaces directly. Instead, FSI creates namespaces implicitly. Here is a very simple test using a clean FSI environment:
let docClass o = printf "%s (%s)\n" (o.GetType().FullName) (o.GetType().Assembly.FullName);;
type C() = member s.m() = ();;
module X = type D() = member s.m() = ();;
docClass (new C()) ; docClass (new X.D()) ;;
In my case, the response is:
FSI_0002+C (FSI-ASSEMBLY, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)
FSI_0009+X+D (FSI-ASSEMBLY, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null)
Further investigation shows that FSI creates a dynamic assembly that does not support the Assembly CodeBase property and so you are quite limited in how you can reference it.
I hope that helps a little. Let me know if I have misunderstood your problem.
Steve
Thanks Steve, that is exactly what I am looking for.
Looks like I’m going to have to compile my custom dataTemplate :(
Cheers,
Matt
Glad to help, although I am not sure I did! You may be able to get around this by using your own dynamic assembly but you will probably need to wait until code generation is fully part of the .NET libraries — Microsoft are planning this, but it may be .NET 5.0 (or later!).