An MVVM Friendly Property for the XamDataPresenter DoubleClick Event

Brian Lagunas / Friday, June 1, 2012

If you are building a WPF application, chances are you are using the MVVM pattern.  If you are using the MVVM pattern, I am sure you have discovered certain scenarios that become difficult to accomplish in a ViewModel when trying to respond to something in the View.  If you are building a WPF application, using MVVM, and using the Infragistics XamDataPresenter family of controls, then this post if for you.  Actually, if you are just using MVVM in any XAML application, this post is for you.

One of the most common hurdles for dealing with controls in a View from within a ViewModel is the limitation of events.  Yes, those tightly coupling mechanisms for responding to a control’s actions.  Well, in this post I will show you how to crate an attached property that will allow you to attach any command to any control for an event of your choosing.  Now I know what you’re thinking, "but Brian, I can already do this with a behavior”.  Yeah, but I don’t like using Microsoft.Expression.Interactivity in my apps.  Plus this is a good exercise to learn how to create an attached property.

The scenario I am going to cover is as follows:  I have a XamDataPresenter populated with some important data.  When a user double clicks on a row in the grid I want to act on the selected item in a ViewModel. 

Here is our View (DataContext set to our ViewModel):

<igDP:XamDataPresenter DataSource="{Binding People}">
    <igDP:XamDataPresenter.View>
        <igDP:GridView />
    </igDP:XamDataPresenter.View>
</igDP:XamDataPresenter>

Let’s take a look at our ViewModel.

public class MainViewModel : INotifyPropertyChanged
{
    public ICommand DoubleClickCommand { get; set; }

    public ObservableCollection<Person> People { get; set; }

    public MainViewModel()
    {
        DoubleClickCommand = new DelegateCommand(ExecuteClick);

        People = new ObservableCollection<Person>();

        for (int x = 0; x < 10; x++)
        {
            People.Add(new Person() { FirstName = String.Format("First {0}", x), LastName = String.Format("Last {0}", x) });
        }
    }

    private void ExecuteClick(object parameter)
    {
        var person = parameter as Person;
        
        if (person == null)
            return;

        MessageBox.Show(String.Format("You double clicked on {0} {1}", person.FirstName, person.LastName));
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

As you can see, we are not dealing with anything complicated here.  We have a XamDataPresenter data bound to a collection of Person objects.  Our ViewModel has an ICommand defined in the form of a DelegateCommand, also known as a RelayCommand.  When this command executes, we simply display the object being clicked on which is passed as a parameter to the command.

Now, how do we hook our command up to the XamDataPresenter.DoubleClick event?  Enter our attached property.  Start by creating a class named XamDataPresenter.  Then add the following code.

public class XamDataPresenter
{
    public static readonly DependencyProperty DoubleClickCommandProperty = DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(XamDataPresenter), new PropertyMetadata(null, OnDoubleClickCommandChanged));
    public static void SetDoubleClickCommand(DataPresenterBase grid, ICommand command)
    {
        grid.SetValue(DoubleClickCommandProperty, command);
    }
    public static ICommand GetDoubleClickCommand(DataPresenterBase grid)
    {
        return grid.GetValue(DoubleClickCommandProperty) as ICommand;
    }

    private static void OnDoubleClickCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {

    }
}

As you can see, the syntax for an attached property is very similar to a dependency property.  But notice the syntactical differences.  We are using the DependencyProperty.RegisterAttached method and then providing the common parameters.  The name of the property, the type of the property, the owner of the property, and of course our meta data about the property.  In this case we added a null default value and provided a property changed callback.

I am not going to go into all the specifics of attach properties.  For that information read this.  What we are going to concentrate on is hooking into our DoubleClick event and executing our command.  So let’s add some code to our property changed callback handler.

private static void OnDoubleClickCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    DataPresenterBase presenter = (DataPresenterBase)d;
    presenter.MouseDoubleClick += (o, ea) =>
    {
        ICommand command = (ICommand)presenter.GetValue(DoubleClickCommandProperty);
        object commandParameter = null; //TODO: add support for parameter

        if (command.CanExecute(commandParameter))
            command.Execute(commandParameter);
    };
}

When the value of our attached property is assigned, this callback handler will execute.  This allows us to get the instance of the element, in this case the XamDataPresenter.  Once we have the instance we can hook into it’s MouseDoubleClick event.  Then we get the command assigned to the element, get it’s paramter (we will implement this in a minute), and then execute the command.  As you can see you use the DependencyObject.GetValue to retrieve the attached property’s value.

Now that we have a command, let’s add the CommandParameter.

public static readonly DependencyProperty DoubleClickCommandParameterProperty = DependencyProperty.RegisterAttached("DoubleClickCommandParameter", typeof(object), typeof(XamDataPresenter));
public static void SetDoubleClickCommandParameter(DataPresenterBase grid, object parameter)
{
    grid.SetValue(DoubleClickCommandParameterProperty, parameter);
}
public static object GetDoubleClickCommandParameter(DataPresenterBase grid)
{
    return grid.GetValue(DoubleClickCommandParameterProperty);
}

Now we can go back to our OnDoubleClickCommandChange callback handler and implement our command parameter.

presenter.MouseDoubleClick += (o, ea) =>
{
    ICommand command = (ICommand)presenter.GetValue(DoubleClickCommandProperty);
    object commandParameter = presenter.GetValue(DoubleClickCommandParameterProperty);

    if (command.CanExecute(commandParameter))
        command.Execute(commandParameter);
};

The last thing to do is to attach our properties to our XamDataPresenter and set their values.  This is what our view should look like now.

<igDP:XamDataPresenter DataSource="{Binding People}"
                      local:XamDataPresenter.DoubleClickCommand="{Binding DoubleClickCommand}"
                      local:XamDataPresenter.DoubleClickCommandParameter="{Binding ActiveDataItem, RelativeSource={RelativeSource Self}}">
    <igDP:XamDataPresenter.View>
        <igDP:GridView />
    </igDP:XamDataPresenter.View>
</igDP:XamDataPresenter>

Notice how we are using an xmlns namespace that has been declared at the top of our view.  We are binding the Command property to the DoubleClickCommand that is defined in our ViewModel and we are binding the CommandParameter to the ActiveDataItem (SelectedItem) of the XamDataPresenter.

Now run the application and double click on a row.

The Best Part about it is that this will work the same way no matter what type of view you are using in your XamDataPresenter.

And yes, this will even work for the XamDataGrid because it derives from XamDataPresenter.  Hopefully you can start to see how you can leverage attached properties in your application to extend Infragistics controls to make them more MVVM friendly.  For example, you could use this same approach to expose a SelectedItems property to store multiple selected rows in a ViewModel.  Hmm… Maybe I will just blog about that too.

xamdatagriddoubleclickcommand.zip