More on Markup Extensions in Silverlight 5

In my previous post I showed how to write a simple mark up extension.  I also noticed that not too many folks are aware of the fact that you can obtain a lot of information about how markup extension is used in XAML from the provider object.  I am going to demonstrate how to do this.  I will write an extension that would invoke a method on view model when a button is clicked by “binding” directly to Click event of the button.

Frist, we have to know more about the object we are using mark up extension on.  I am going to call mine EventToMethod.  Here is what the class looks like

using System;

using System.Reflection;

using System.Windows;

using System.Windows.Markup;

using System.Xaml;

 

namespace SL5Features.Extensions

{

  /// <summary>

  /// Extension that invokes a specified method when an event occurs.

  /// </summary>

  public class EventToMethod : FrameworkElement, IMarkupExtension<Delegate>

  {

    /// <summary>

    /// method handle that will be invoked

    /// </summary>

    private MethodInfo executeMethod = null;

 

    /// <summary>

    /// Main method required to be implemented by mark up extension

    /// </summary>

    /// <param name="serviceProvider">Service provider</param>

    /// <returns>Event delegate</returns>

    public Delegate ProvideValue(IServiceProvider serviceProvider)

    {

      // obtain value target provider and get mark up extension’s target from it

      var target =

        (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));

 

      // since we are bound to an event, we are getting EventInfo object

      EventInfo prop = target.TargetProperty as EventInfo;

      // test to see what type of event we are getting

      if (prop.EventHandlerType.Equals(typeof(RoutedEventHandler)))

      {

        // create delegate for routed event and return it

        return new RoutedEventHandler(RoutedHandler);

      }

      else

      {

        //create delegate for regular event and return it

        return new EventHandler(RegularHandler);

      }

    }

 

    /// <summary>

    /// Routed event handler delegate to invoke in response to routed event

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="args"></param>

    public void RoutedHandler(object sender, RoutedEventArgs args)

    {

      RegularHandler(sender, args);

    }

 

    /// <summary>

    /// Event handler to invoke in response to plain vanilla event

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="args"></param>

    public void RegularHandler(object sender, EventArgs args)

    {

      if (executeMethod == null)

      {

        Type type = ViewModel.GetType();

        executeMethod = type.GetMethod(MethodName);

      }

      executeMethod.Invoke(ViewModel, new object[] { });

    }

 

    /// <summary>

    /// Name of the method to invoke when an event is invoked, such as button click

    /// </summary>

    public string MethodName

    {

      get { return (string)GetValue(MethodNameProperty); }

      set { SetValue(MethodNameProperty, value); }

    }

    public static readonly DependencyProperty MethodNameProperty =

        DependencyProperty.Register(

        "MethodName",

        typeof(string),

        typeof(EventToMethod),

        new PropertyMetadata(null));

 

    /// <summary>

    /// View model to invoke

    /// </summary>

    public Object ViewModel

    {

      get { return (Object)GetValue(ViewModelProperty); }

      set { SetValue(ViewModelProperty, value); }

    }

 

    public static readonly DependencyProperty ViewModelProperty =

        DependencyProperty.Register(

        "ViewModel", typeof(Object),

        typeof(EventToMethod),

        new PropertyMetadata(null));

 

  }

}

 

I documented properties, etc…  The basic idea is to get button (or any other) object from the provider, get event information, then create a delegate for that event and return it, just like any other mark up extension must return a value.  I added ViewModel and MethodName properties so that I can get event handle from ViewModel based on event name.  If you bind your mark up extension to a property on a control, TargetProperty will contain PropertyInfo object.

XAML could not look simpler:

 

<ResourceDictionary

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   xmlns:models="clr-namespace:SL5Features.Models"

   xmlns:ext="clr-namespace:SL5Features.Extensions">

 

  <DataTemplate DataType="models:Person">

    <Grid>

      <Grid.RowDefinitions>

        <RowDefinition Height="Auto"/>

        <RowDefinition Height="7"/>

        <RowDefinition Height="Auto"/>

        <RowDefinition Height="7"/>

        <RowDefinition Height="Auto"/>

      </Grid.RowDefinitions>

      <Grid.ColumnDefinitions>

        <ColumnDefinition Width="Auto"/>

        <ColumnDefinition Width="7"/>

        <ColumnDefinition Width="*"/>

      </Grid.ColumnDefinitions>

     

      <TextBlock Text="First Name:" 

                Grid.Column="0" Grid.Row="0" 

                HorizontalAlignment="Right"

                VerticalAlignment="Center"

                Foreground="Red"/>

     

      <TextBlock Text="Last Name:" 

                Grid.Column="0" Grid.Row="2" 

                HorizontalAlignment="Right"

                VerticalAlignment="Center"

                Foreground="Red"/>

     

      <TextBox Text="{Binding Path=FirstName, Mode=TwoWay}"

              Grid.Column="2" Grid.Row="0"

              Background="Yellow"/>

     

      <TextBox Text="{Binding Path=LastName, Mode=TwoWay}" 

              Grid.Column="2" Grid.Row="2"

              Background="Yellow"/>

 

      <Button Content="Save" HorizontalAlignment="Left" 

             Grid.Row="4" Grid.Column="0"

             Click="{ext:EventToMethod 

                 ViewModel={Binding ElementName=LayoutRoot, Path=DataContext}, 

                 MethodName=RunIt}"/>

    </Grid>

  </DataTemplate>

</ResourceDictionary>

 

As you can see, I am binding button’s click method via my mark up extension.  Now, in my view model I just need to declare RunIt method, and that is it:

using SL5Features.Models;

using System.Windows;

 

namespace SL5Features.ViewModels

{

  public class PersonViewModel : ViewModelBase<Person>

  {

    public PersonViewModel()

    {

      Model = new Person() { FirstName = "Sergey", LastName = "Barskiy" };

    }

 

    public void Run(object parameter)

    {

      MessageBox.Show("Run");

    }

 

    public void RunIt()

    {

      MessageBox.Show("RunIt");

    }

 

    public bool CanRun(object parameter)

    {

      return true;

    }

  }

}

 

I added all the functionality to the solution from the previous post, but I am using alternate template for this implementation.  Too see the end result, just add ?Template=Alt to the query string when you run the app. 

To summarize, the idea is the same as my previous post – eliminate the need to create commands in my view model.

 

You can download updated solution here.

Thanks.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *