September 28, 2012 09:03 by
Scott
Another cool feature available in Silverlight 5 is markup extensions. The idea behind the feature is ability to allow developers to supply values to XAML parser at the time it parses visual tree. In other words, once parser finds a markup extension in XAML, it will create an instance of it, set any properties that the extension might have, then call ProvideValue method that will return a value of the type that the property that markup extension supports expects. For example, I am writing a markup extension to supply an ICommand to the button, my XAML would looks like the following:
<Button Content="Save" HorizontalAlignment="Left"
Grid.Row="4" Grid.Column="0"
Command="{ext:CommandMarkupExtension
ViewModel={Binding ElementName=LayoutRoot, Path=DataContext},
ExecuteMethodName=Run,
CanExecuteMethodName=CanRun}"/>
So, in this example for markup extension I am writing a command extension. As you can see in the XAML above, my extension takes three parameters: ViewModel to invoke, execute and can execute method names. This way I can de-clutter my view model by removing all the command instantiation code, and just write the two methods I need. The code in this extension is very easy – just a handful of dependency properties and interface implementation for IMarkupExtension:
using System;
using System.Windows;
using System.Windows.Input;
using System.Xaml;
namespace SL5Features.Extensions
{
public class CommandMarkupExtension : FrameworkElement, IMarkupExtension<ICommand>
{
public Object ViewModel
{
get { return (Object)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register(
"ViewModel", typeof(Object),
typeof(CommandMarkupExtension),
new PropertyMetadata(null));
public string ExecuteMethodName
{
get { return (string)GetValue(ExecuteMethodNameProperty); }
set { SetValue(ExecuteMethodNameProperty, value); }
}
public static readonly DependencyProperty ExecuteMethodNameProperty =
DependencyProperty.Register(
"ExecuteMethodName",
typeof(string),
typeof(CommandMarkupExtension),
new PropertyMetadata(null));
public string CanExecuteMethodName
{
get { return (string)GetValue(CanExecuteMethodNameProperty); }
set { SetValue(CanExecuteMethodNameProperty, value); }
}
public static readonly DependencyProperty CanExecuteMethodNameProperty =
DependencyProperty.Register(
"CanExecuteMethodName",
typeof(string),
typeof(CommandMarkupExtension),
new PropertyMetadata(null));
public ICommand ProvideValue(IServiceProvider serviceProvider)
{
ReflectionCommand command =
new ReflectionCommand(ViewModel, ExecuteMethodName, CanExecuteMethodName);
return command;
}
}
}
To support the extension I need to write a command object that would actually implement ICommand. This class is pretty trivial, so I am not commenting it much:
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
namespace SL5Features.Extensions
{
public class ReflectionCommand : ICommand
{
private MethodInfo canExecuteMethod = null;
private MethodInfo executeMethod = null;
private object viewModel = null;
private ReflectionCommand() {
public ReflectionCommand(
object viewModel,
string executeMethodName,
string canExecuteMethodName)
{
this.viewModel = viewModel;
Type type = viewModel.GetType();
if (!string.IsNullOrEmpty(canExecuteMethodName))
{
this.canExecuteMethod = type.GetMethod(canExecuteMethodName);
}
this.executeMethod = type.GetMethod(executeMethodName);
}
public void Execute(object parameter)
{
executeMethod.Invoke(viewModel, new[] { parameter });
}
public bool CanExecute(object parameter)
{
if (viewModel != null && canExecuteMethod != null)
{
return (bool)canExecuteMethod.Invoke(viewModel, new[] { parameter });
}
else
{
return true;
}
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
So, far it is pretty easy. Now, all I have to do is add Run and CanRun method on my view model that my command will invoke:
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 bool CanRun(object parameter
{
return true;
}
}
}
Hopefully, I demonstrated the power of markup extension for you. The goal of using them to me is reduction of the amount of code elsewhere in the system, since you will obviously have to write more XAML. Of course, I am sure Blend 5 will support mark up extensions, so potentially you can just drag my extension on top of the button and setup a few properties in properties window. You will also see a number of demos where an extension is used to support localization, which seems pretty intuitive use of the feature. Bottom line is: I love getting new features that make my job easier by allowing me to write less code and re-use more of the code written.