Generic Support In XAML

I have worked with XAML/WPF for a while now, but it wasn’t until recently I thought about generic support in XAML. I had no idea if it was possible, so I decided to have a quick look into this.

Luckily the WPF team thought about this and do actually enable WPF developers the correct mechanisms to fully support generics from within XAML.

The first thing you need to know is that a typical WPF window is made up of 2 partial classes, the code behind and the XAML, then at compilation time a combined file known as the generated file is formed with the combination of the code behind file and the XAML file.

build

So in order to correctly support generics for a file that is made up of both code behind and XAML we MUST ensure that both parts of the file are using generic type arguments. This should become clearer as we move on to see a small example.

In order to demonstrate the support for generics in XAML let us consider a small example, which requires the following arrangement :

  1. We have a common window base class that is generic
  2. We inherit from the common window base class, but we then need to provide generic arguments within the code behind and the XAML in order to make it a generic type.

That’s basically what we are trying to do. In order to demonstrate this, I have constructed a small demo app that carries out the following:

I have an abstract base class called LoggingStrategy which looks like this

   1:  using System;
   2:  
   3:  namespace GenericViews
   4:  {
   5:      public abstract class LoggingStrategy
   6:      {
   7:          public abstract void LogText(string textToLog);
   8:      }
   9:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } I then have a specific class that overrides the LogText(…) method, this is as follows:

   1:  using System;
   2:  using System.Windows;
   3:  
   4:  namespace GenericViews
   5:  {
   6:      public class MessageBoxLogger : LoggingStrategy
   7:      {
   8:          public override void LogText(string textToLog)
   9:          {
  10:              MessageBox.Show(textToLog);
  11:          }
  12:      }
  13:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }


.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Then within the main App class that you get if you start a new WPF application in VS2008, I have created a simple Dictionary lookup for a Type to get the correct logging strategy to use. The idea being that the Type will come from a generic argument provided by the view, as we will see in just a minute.

For now all you need to know is that there is a lookup against a Type that returns a LoggingStrategy inherited object.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Windows;
   4:  
   5:  namespace GenericViews
   6:  {
   7:      public partial class App : Application
   8:      {
   9:          public static Dictionary<Type, LoggingStrategy> Loggers =
  10:              new Dictionary<Type, LoggingStrategy>();
  11:  
  12:  
  13:          static App()
  14:          {
  15:              Loggers.Add(typeof(MessageBoxLogger), new MessageBoxLogger());
  16:          }
  17:      }
  18:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }


.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

So we have pretty much covered all the demo stuff, now its just a question of dealing with the actual generic stuff. In the attached demo I have provided a small base class for views, and the base class is generic, and will lookup the correct LoggingStrategy inherited object, from the Dictionary stored in the static App Dictionary.

Here is the base class for the views.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Windows;
   4:  
   5:  namespace GenericViews
   6:  {
   7:      public class ViewBase<T> : Window where T : LoggingStrategy
   8:      {
   9:          public LoggingStrategy CurrentLogger { get; private set; }
  10:  
  11:          public ViewBase()
  12:          {
  13:              CurrentLogger = App.Loggers[typeof(T)];
  14:          }
  15:      }
  16:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

It can be seen that this base class expects a generic argument T in order to work. T must inherit from LoggingStrategy. What then happens is generic argument is used to obtain the correct LoggingStrategy inherited object from the Dictionary stored in the static App Dictionary.

Ok we are nearly there, all we now have to do is make a custom window which inherits from the ViewBase<T>, and passes a generic argument .

Let’s start with the easy part, the code behind. This part is dead easy, we simply use standard generic stuff, as we would any other generic class:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Windows;
   4:  
   5:  
   6:  namespace GenericViews
   7:  {
   8:  
   9:      public partial class MessageLoggingWindow : ViewBase<MessageBoxLogger>
  10:      {
  11:          public MessageLoggingWindow() : base()
  12:          {
  13:              InitializeComponent();
  14:          }
  15:  
  16:          private void Button_Click(object sender, RoutedEventArgs e)
  17:          {
  18:              base.CurrentLogger.LogText(
  19:                  String.Format("Sometext which was logged at {0}",
  20:                  DateTime.Now.ToShortTimeString()));
  21:          }
  22:      }
  23:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Now that the easy part is done, why don’t we focus our efforts on the XAML part.

   1:  <local:ViewBase
   2:      x:Class="GenericViews.MessageLoggingWindow"
   3:      x:TypeArguments="local:MessageBoxLogger"
   4:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   5:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   6:      xmlns:local="clr-namespace:GenericViews">
   7:  ...
   8:  ...
   9:  </local:ViewBase>

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Now, what is going on here exactly. Well a couple of things actually, let’s go through them

  1. x:Class : that’s the name of the class, this matches the code behind namespace and class name
  2. xmlns:local : This is a namespace alias that allows use to use local: in the XAML to point to classes in the GenericViews namespace
  3. <local:ViewBase…/> : Means we are using GenericViews.ViewBase<T> as the base class for this new Window, but wait what about the generic argument. Well that’s step 4
  4. x:TypeArguments : This is where the generic value gets plugged in. So we now have a window declared in XAML which is really GenericViews.ViewBase<MessageBoxLogger>

That’s it…..I know my example is not exactly real world, but that was not the point of all this, I simply wanted to show you how to create generics in XAML, and to that end this post should do just that.

Here is the small demo app genericviews.zip, that creates a generic window with a MessageBoxLogger, and when run looks like this:

image

Enjoy

NOTE:

A reader of this blog also pointed me at another entry by Mike Hillberg, which covers a lot of other generic support in XAML, have a look here for that, its quite cool

http://blogs.msdn.com/mikehillberg/archive/2006/10/06/LimitedGenericsSupportInXaml.aspx

Advertisements

5 thoughts on “Generic Support In XAML

  1. cechode says:

    Thought of this issue a while back and got distracted by another that took precedence before i had a chance to dig in. NICE
    and thank you of course 🙂

  2. sacha says:

    Cool, glad it helps

  3. […] Generic Support in XAML (Sacha Barber) […]

  4. Bhuvan says:

    Sacha.. You are genious.. this example helped me a lot.

    Also.. Is there any way actually to define a Generic class inheriting a Window.

    In short I should be able to do is something like a MessageBox, which can be called like this..

    IEnumarable list = WindowControl.SelectMany(IEnumerable inputlist, string displaymemberpath);

    If I achieve this, I can eliminate creating gazillion, selector popup windows, which I have to use in my app.

    • sacha says:

      Bhuvan

      As far as I know there is no way to do what you want, but what I do in this situation is have a single popup, that I can open using a service such as my Cinch MVVM Frameworks IUIVisualizer service, where there is a single popup that has its datacontext set via the Cinch IUIVisualizer service.

      Then in the single popup I subscribe to the DataContextChanged event, and have a case statement that checked the new ViewModel being supplied to the popup, and then I simple swap in a new UserControl that goes with that ViewModel as the Content for the single Popup Window.

      You may need to read my Cinch MVVM article series to see what I mean about the UIVisualizer service

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

%d bloggers like this: