Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Silverlight 5 Beta Rough Notes–Markup Extensions

Blogs

Mike Taulty's Blog

Elsewhere

Archives

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.


Posted Tue, Apr 26 2011 10:34 AM by mtaulty
Filed under: , ,

Comments

Anonymous wrote re: Silverlight 5 Beta Rough Notes–Markup Extensions
on Tue, Apr 26 2011 2:56 PM

Could you please clarify why you would want to invoke a UI control's event handler in a VM?  Wouldn't it be a better design to bind the button to an ICommand where you'd have programatic control over CanExecute and more importantly, preventing the VM from knowing about UI-specific classes (the specific event args, etc)?

Hugo Cabrera wrote re: Silverlight 5 Beta Rough Notes–Markup Extensions
on Wed, Apr 27 2011 2:54 AM

Great men!!!!!

mtaulty wrote re: Silverlight 5 Beta Rough Notes–Markup Extensions
on Wed, Apr 27 2011 9:21 AM

Anonymous,

I wasn't invoking a UI control's event handler from a VM.

I was invoking a VM's method from a UI control's event handler - that's the other way around.

Now you can argue (and I did mention that this was the case in my text) that you might not want the ViewModel to receive the sender and EventArgs arguments. That's perfectly reasonable.

You could also argue that it would be better to use ICommand. CanExecute is useful but it loses some meaning if you associate the command with the SelectionChanged event on a ListBox - what does "CanExecute" mean in that scenario? You can't really stop the selection changing in a ListBox by using a command.

So...my code here was really about trying to have a markup extension that made use of something from the DataContext.

I thought it might lead to something like;

<ListBox SelectionChanged={x:Invoke Source="{Binding}" Command="SomeCommand"}/>

but if I can't invoke a method on the object found in the DataContext then I don't see how I can progress to something more advanced like you're talking about - do you?

Naturally, it's not the end of the world - there are attached properties and actions/triggers to rely on around commanding but I was trying to see how far I could go with markup extensions.

Mike.

EGO wrote re: Silverlight 5 Beta Rough Notes–Markup Extensions
on Tue, May 3 2011 3:09 PM

Mike, Not sure if it is what you want but you can try to get control instance by using :

var element=ipvt.targetObject as FrameworkElement which supposed to gives you Button instance in you case.

Hope that helps.

Cheers,

Eryk

mtaulty wrote re: Silverlight 5 Beta Rough Notes–Markup Extensions
on Tue, May 3 2011 10:46 PM

Hi EGO,

That's true but the problem is that the DataContext on that Button isn't available at parse time. It's only available later at runtime and, naturally, it can change over time.

So, the problem becomes one of being given a Button (in this case) and trying to figure out whenever its DataContext changes.

A DataContextChanged event would help.

Mike.

Mike Taulty's Blog wrote Silverlight 5 RC–MarkupExtensions and DataContextChanged
on Mon, Sep 5 2011 9:42 AM

When the Silverlight 5 beta shipped, one of the things that I wrote about was trying to use a markup

Community Blogs wrote 10 Laps around Silverlight 5 (Part 2 of 10)
on Mon, Oct 17 2011 10:26 PM

This article is sponsored by Telerik RadControls for Silverlight . Related content from the sponsor: