Enlazar comandos a cualquier evento en MVVM

- 5 minute read

En la primera entrada dedicada a MVVM vimos que en este patrón no utilizamos eventos, en su lugar nos valemos de los comandos para asociar una acción con un determinado control. El problema es que los controles WPF no tienen propiedades para enlazar todos los eventos con comandos. Por ejemplo, en un botón podemos utilizar la propiedad Command para ejecutar una acción al pulsar el botón. Pero, ¿qué sucede si queremos asignar un comando a otro evento? ¿Cómo hacemos, por ejemplo, para ejecutar un comando al pasar por encima de un control? ¿Y al escribir en un TextBox? ¿Y al cargar una vista?

En esta entrada vamos a ver dos métodos para responder a estas preguntas: el primero es mediante la implementación del patrón Attached Behavior y el segundo es haciendo uso de los comportamientos que nos proporcionan las clases de System.Windows.Interactivity.

Patrón Attached Behavior

Básicamente el patrón Attached Behavior consiste en una clase estática en la que se definen una o varias propiedades asociadas (attached properties), un tipo especial de propiedad de dependencia (dependency property). En nuestra solución de ejemplo vamos a definir la clase MouseBehavior dentro del namespace BasicMVVM.Behaviors y dentro de esta clase definiremos la propiedad asociada MouseEnter. A diferencia de las dependency properties, no definimos un wrapper para las propiedades sino que tenemos que crear el par de métodos estáticos GetMouseEnter y SetMouseEnter. En el método controlador MouseEnterCallback, que se llamará cada vez se cambie la propiedad, asignamos al evento MouseEnter el código para ejecutar el comando. La implementación completa de la clase queda de la siguiente forma:

namespace BasicMVVM.Behaviors 
{ 
  public static class MouseBehavior 
  { 
    public static readonly DependencyProperty MouseEnterProperty;

    static MouseBehavior()
    {
      MouseEnterProperty = DependencyProperty.RegisterAttached("MouseEnter",
      typeof(ICommand), typeof(MouseBehavior), new PropertyMetadata(MouseEnterCallback));
    }
    
    public static ICommand GetMouseEnter(UIElement element)
    {
      return (ICommand)element.GetValue(MouseEnterProperty);
    }
    
    public static void SetMouseEnter(UIElement element, ICommand value)
    {
      element.SetValue(MouseEnterProperty, value);
    }
    
    private static void MouseEnterCallback(object obj, DependencyPropertyChangedEventArgs e)
    {
      var element = obj as UIElement;
    
      element.MouseEnter += (sender, ev) =>
      {
        var el = sender as UIElement;
        var command = GetMouseEnter(el);
    
        if (command.CanExecute(null))
        {
          command.Execute(el);
        }
      };
    }   
  } 
}

Para poder utilizar este comportamiento en la vista, que en nuestro ejemplo es la vista Basic, solo tenemos que declarar el espacio de nombres BasicMVVM.Behaviors en la etiqueta Window.

<Window x:Class="BasicMVVM.Views.Basic"
...
xmlns:b="clr-namespace:BasicMVVM.Behaviors"
...>

Y para comprobar el funcionamiento, añadimos un nuevo botón al que enlazamos el comando deseado, en este caso que el comando ChangeColorCommand.

<Button Content="Attached behavior" b:MouseBehavior.MouseEnter="{Binding ChangeColorCommand}" />

Esta solución puede llegar a ser bastante tediosa porque tenemos que implementar todas las clases con los comportamientos que queremos controlar. Así que una alternativa a este método es utilizar los comportamientos incluidos en las clases de System.Windows.Interactivity que veremos a continuación.

Comportamientos en System.Windows.Interactivity

System.Windows.Interactivity.dll es un ensamblado que tenemos disponible a través del SDK de Microsoft Expression Blend, y que podemos descargar en el Centro de descargas de Microsoft. Esencialmente este ensamblado lo que intenta es formalizar de alguna forma el patrón attached behavior que hemos visto en el punto anterior.

Una vez hemos instalado el SDK, podemos utilizar el ensamblado añadiendo en el proyecto la referencia a System.Windows.Interactivity.dll que se encuentra en la carpeta {Program Files}Microsoft SDKsExpressionBlend.NETFrameworkv4.0Libraries y declarando el espacio de nombres correspondiente en la vista.

<Window x:Class="BasicMVVM.Views.Basic"
...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...>

Para añadir el mismo comportamiento que en el primer ejemplo anterior añadimos el botón con el siguiente código.

<Button Content="System.Windows.Interactivity">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="MouseEnter">
      <i:InvokeCommandAction Command="{Binding ChangeColorCommand}"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
</Button>

En este código definimos un desencadenador (trigger) que está escuchando el evento MouseEnter y que al activarse invocará el comando ChangeColorCommand. Podemos definir estos desencadenadores en cualquier elemento. Por ejemplo, si queremos lanzar un comando cuando la vista se haya cargado, podemos utilizar el siguiente código XAML:

<Window x:Class="BasicMVVM.Views.Basic"
...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">

<Window.DataContext>
<vm:Basic />
</Window.DataContext>

<i:Interaction.Triggers>
  <i:EventTrigger EventName="Loaded">
    <i:InvokeCommandAction Command="{Binding ShowWelcomeMsgCommand}"/>
  </i:EventTrigger>
</i:Interaction.Triggers>
...
</Window>

Enlaces relacionados
Descarga Microsoft Expression Blend Software Development Kit (SDK) for .NET 4
Información general sobre propiedades asociadas
Espacio de nombres System.Windows.Interactivity

Descarga código fuente:
BasicMVVM-Behaviors.zip