Silverlight 5 Beta Rough Notes–Markup Extensions

Note: these are early notes based on some initial experiments with the Silverlight 5 beta, apply a pinch of salt to what you read.

One of the WPF features that’s been missing from Silverlight has been the ability to extend the XAML language by writing your own markup extensions. Silverlight has few extensions like;

{Binding}

{StaticResource}

and up until the V5 beta, if you wanted to add your own you were really quite stuck. The beta comes along and adds support for custom markup extensions and so, by way, of illustration if I wanted to be able to use an extension such as the {x : Static} extension that you see in WPF I could write my own;

  public class StaticExtension : MarkupExtension
  {
    public string TypeName { get; set; }
    public string PropertyName { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
      object returnValue = null;

      // no checking of any return values at all here.
      var t = Type.GetType(this.TypeName);

      var p = t.GetProperty(this.PropertyName,
        BindingFlags.Static | BindingFlags.Public);

      var v = p.GetValue(null, null);

      // NB: there's more to do here no doubt than just try for IConvertible on
      // a simple property accessor, this is just a sketch.
      if (v is IConvertible)
      {
        IProvideValueTarget ipvt =
          serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

        if (ipvt != null)
        {
          PropertyInfo pi = (PropertyInfo)ipvt.TargetProperty;

          returnValue = Convert.ChangeType(v, pi.PropertyType, null);
        }
      }
      else
      {
        returnValue = v;
      }
      return (returnValue);
    }
  }

and then make use of it with something like;

 <TextBlock
      Text="{local:Static TypeName=System.DateTime,PropertyName=Now}" />

or maybe something even more “ambitious” like;

  <Grid
    x:Name="LayoutRoot">
    <Grid.Resources>
      <sys:String
        x:Key="type">System.Windows.Application,System.Windows, Version=5.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e</sys:String>
    </Grid.Resources>
    <TextBlock
      DataContext="{local:Static TypeName={StaticResource type},PropertyName=Current}"
      Text="{Binding InstallState}" />
  </Grid>

I often find that when I’m using bindings, I want to make sure that I switch on the flags ValidatesOnNotifyDataErrors (to make use of INotifyDataErrorInfo) and also NotifyOnValidationError and also TwoWay bindings and so a markup extension might provide a cheap and cheerful way of achieving that – for example;

  public class StandardTwoWayBindingExtension : Binding
  {
    public StandardTwoWayBindingExtension()
    {
      this.ValidatesOnNotifyDataErrors = true;
      this.NotifyOnValidationError = true;
      this.Mode = BindingMode.TwoWay;      
    }
  }

and then I can make use of that;

  <Grid
    x:Name="LayoutRoot">
    <Grid.DataContext>
      <local:DataObject />
    </Grid.DataContext>
    <TextBox
      Text="{local:StandardTwoWayBinding Path=SomeProperty}" />
  </Grid>

I can see a lot of other uses for custom extensions with another example perhaps being to do resource localisation in a very smart way ( as an aside I saw Jeff Prosise demonstrate his mechanism for resource localisation recently and I could see that combining nicely with a markup extension ).

However, so far I’ve failed with the one markup extension that I really wanted to make work. What I wanted to be able to do was the classic “MVVM” thing of having something along the lines of;

  public class ViewModel
  {
    public ViewModel()
    {      
    }
    public void SomeMethod(object sender, EventArgs args)
    {
    }
  }

and then I wanted to be able to invoke this from XAML with an extension;

  <Grid
    x:Name="LayoutRoot">
    <Grid.DataContext>
      <local:ViewModel />
    </Grid.DataContext>
    <Button
      Click="{local:Invoke MethodName=SomeMethod,Source={Binding}}" />
  </Grid>

Note – you can definitely argue about whether I should pass the sender and the EventArgs across to that method but I went with it for my first attempt because I can’t get this to work even in sketched form at this point Smile 

Here’s my sketch;

  public class InvokeExtension : MarkupExtension
  {
    public string MethodName { get; set; }
    public object Source { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
      Delegate returnValue = null;

      Type sourceType = this.Source.GetType();

      MethodInfo methodInfo = sourceType.GetMethod(this.MethodName, 
        BindingFlags.Public | BindingFlags.Instance);

      IProvideValueTarget ipvt =
        serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;

      if (ipvt != null)
      {
        EventInfo targetProperty = ipvt.TargetProperty as EventInfo;

        if (targetProperty != null)
        {
          // This is where we fall over if the types of the event and the
          // handler don't match. We could check IsAssignableFrom and do
          // something other than throw.
          returnValue = Delegate.CreateDelegate(
            targetProperty.EventHandlerType, this.Source, methodInfo);
        }
      }
      return (returnValue);
    }
  }

and here it is in use;

  <Grid
    x:Name="LayoutRoot">
    <Grid.Resources>
      <local:ViewModel x:Key="foo"/>
    </Grid.Resources>
    <Button
      Click="{local:Invoke MethodName=SomeMethod,Source={StaticResource foo}}" />
  </Grid>

and that works fine but what I really want is to be able to do;

  <Grid
    x:Name="LayoutRoot">
    <Grid.DataContext>
      <local:ViewModel />
    </Grid.DataContext>
    <Button
      Click="{local:Invoke MethodName=SomeMethod,Source={Binding}}" />
  </Grid>

but in this case what I get passed into my InvokeExtension at runtime is a Binding rather than a source object and the question becomes one of trying to figure out what object that Binding ultimately resolves into.

I’ve experimented quite a lot with that. I’ve tried making my Source into a DependencyProperty and I’ve tried inserting intermediate “dummy” methods and I’ve tried creating additional bindings which try to bind to the source and pick up the value but, so far, I’ve not come up with something that works.

So…if you know how to sort that problem then feel free to drop me a line. I’m wondering whether the future DataContextChanged event might help me and I’m also asking one or two people internally what the right answer might be so I’ll update this post as/when I figure it out.

Update – I’ve been digging into this a little more and I believe that when we get to the RTM of Silverlight 5, the new DataContextChanged event will help out in this area of trying to have a MarkupExtension that needs access to the DataContext.

The approach would be to use the IProvideValueTarget service in order to get hold of the TargetObject and then to sync up to the DataContextChanged event on that object. Meanwhile, the ProvideValue() function would need to return an intermediate value that can be updated when the DataContextChanged event fires.

I’ll attempt that again when there’s a new release of the Silverlight 5 bits that includes the DataContextChanged event.