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.
You can download the example here.
Thanks.
Another use of MarkupExtensions I’ve been playing with is implementing a replacement for the Binding markup extension to provide an alternative to IValueConverters. Sometimes you need to do a value conversion that’s a one-off and specific to a single ViewModel or screen. Rather, than having to implement a new IValueConverter and add it to the resources of your View, the new MarkupExtension has a ConverterDelegate property that let’s you specify a method on your ViewModel that will handle the conversion for you instead of relying on an outside piece of code. Everything is contained in your ViewModel. I’ll have to follow your lead and put up a blog post on it.
Somewhat similar to what you’re doing with the commanding example, but calling out to the methods as a converter instead of a command.
http://bignickolson.com/2011/04/18/custom-markup-extension-to-replace-ivalueconverter/
Pingback: More on Markup Extensions in Silverlight 5 « Sergey Barskiy's Blog
Doesn’t work for me.
Reproduction:
public class CommandMarkupExtension : FrameworkElement, IMarkupExtension
{
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 object ProvideValue(IServiceProvider serviceProvider)
{
//When reached here, ViewModel evaluates to null
Debugger.Break();
return ViewModel;
}
}
XAML:
Is anything wrong with my code?
Sorry here is the xaml in my previous post:
<UserControl
x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:SilverlightApplication1">
<Grid DataContext="sdfasdf" x:Name="LayoutRoot" Background="White">
<Button Content="{loc:CommandMarkupExtension ViewModel={Binding}}"/>
</Grid>
</UserControl>
You are setting the DataContext on the Grid, thus your button’s data context is not a view model, but just a string “sdfasdf”
Thanks
Hi sergey, my question is why ViewModel is null when reachen in debugger (btw, the button’s datacontext is null as well), it should have been an object, ain it??
I now changed the xaml to:
<Button Content="{loc:CommandMarkupExtension ViewModel={Binding}}">
<Button.DataContext>
<Button/>
</Button.DataContext>
</Button>
And it doesn’t work, it’s still null.
This code looks a bit strange to me. You are setting the content on the button, not DataContext.