Cómo invocar comandos de un ViewModel en Windows 8.1 con Behaviors SDK

domingo, 23 de marzo de 2014

Con la llegada de windows 8.1 tenemos una librería para soporte nativo de behaviors. Vamos a ver unos ejemplos de como podemos invocar comandos que están en el viewmodel enlazado .
Nos creamos una aplicación para la tienda de windows en blanco.

Añadimos una referencia al esamblado BehaviorsSDK


Ya podemos declarar los namespaces Microsoft.Xaml.Interactivity y Microsoft.Xaml.Interactions en la página que nos harán falta para poder invocar comandos del viewmodel que vamos a tener enlazado con esta vista.
<Page
    x:Class="SearchBoxWindows81MVVM.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SearchBoxWindows81MVVM"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:interactivity="using:Microsoft.Xaml.Interactivity"    
    xmlns:intecore="using:Microsoft.Xaml.Interactions.Core"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    </Grid>
</Page>

Dejemos aparcada la vista un momento y vamos con el viewmodel.

Para este ejemplo voy a añadir mediante NuGet la libreria MVVMLight que nos proporciona el objeto RelayCommand y facilita el trabajo.
public class Person
{
    public string Name { get; set; }
    public string LastName { get; set; }
}

public class MainViewModel : ViewModelBase
{

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

    string _realizedAction;
    public string RealizedAction
    {
        get { return _realizedAction; }
        set
        {
            this.Set(() => RealizedAction, ref this._realizedAction, value);
        }
    }

    public MainViewModel()
    {
        Persons = new ObservableCollection<Person>();

        Persons.Add(new Person { Name = "Fernando", LastName = "Alonso" });
        Persons.Add(new Person { Name = "Pau", LastName = "Gasol" });
        Persons.Add(new Person { Name = "Marc", LastName = "Gasol" });
        Persons.Add(new Person { Name = "Rafael", LastName = "Nadal" });
        Persons.Add(new Person { Name = "Marc", LastName = "Marquez" });
        Persons.Add(new Person { Name = "Iker", LastName = "Casillas" });
    }

    public RelayCommand ItemActionCommand
    {
        get
        {
            return new RelayCommand(() =>
            {
                RealizedAction = "Accion ? realizada";
            });
        }
    }
}

Nos hemos definido un modelo person y el viewmodel, el viewmodel tiene una lista de personas y un comando ItemActionCommand que simplemente asigna texto a una propiedad indicando que se ha realizado una acción.

Ahora vamos a definirnos la vista, tendremos un GridView enlazado con la lista de personas y un TextBlock enlazado con la propiedad RealizedAction y por ultimo vamos a indicar que cuando el evento ItemClick se produce se invoque nuestro comando ItemActionCommand.

<Page
    x:Class="InvokeCommandsBehaviorsSDK.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:InvokeCommandsBehaviorsSDK"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:interactivity="using:Microsoft.Xaml.Interactivity"    
    xmlns:core="using:Microsoft.Xaml.Interactions.Core"
    mc:Ignorable="d" RequestedTheme="Light"
    DataContext="{Binding Main, Source={StaticResource Locator}}">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50*"/>
            <ColumnDefinition Width="50*"/>
        </Grid.ColumnDefinitions>

        <GridView Margin="25" Width="400" Height="300" ItemsSource="{Binding Persons}" IsItemClickEnabled="True">
            <GridView.ItemTemplate>
                <DataTemplate>
                    <Grid Width="200" Margin="0" >
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Border Background="LightBlue" Margin="0" Width="50" Height="50"/>

                        <StackPanel Grid.Column="1" Margin="10,10,0,0"  Orientation="Vertical" VerticalAlignment="Top">
                            <TextBlock >
                                        <Run Text="{Binding Name}"/>
                                        <Run Text="{Binding LastName}"/>
                            </TextBlock>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
            <interactivity:Interaction.Behaviors>
                <core:EventTriggerBehavior EventName="ItemClick">
                    <core:InvokeCommandAction Command="{Binding ItemActionCommand}"/>

                </core:EventTriggerBehavior>
            </interactivity:Interaction.Behaviors>
        </GridView>

        <StackPanel Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center">
            <TextBlock FontSize="33" >Acción realizada:</TextBlock>
            <TextBlock FontSize="25" Text="{Binding RealizedAction}" ></TextBlock>
        </StackPanel>
    </Grid>
</Page>

Este es un caso sencillo pero ahora imaginemos que queremos pasarle un parámetro al comando, para esta finalidad podemos usar la propiedad CommandParameter y especificar en el comando del viewmodel el tipo de parámetro a recibir.
El codigo xaml nos quedaria así en la parte de behaviors
<interactivity:Interaction.Behaviors>
    <core:EventTriggerBehavior EventName="ItemClick">
        <core:InvokeCommandAction Command="{Binding ItemActionCommand}"
                                      CommandParameter="grid de personas"/>
    </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>

Y así el comando en el viewmodel
public RelayCommand<string> ItemActionCommand
{
    get
    {
        return new RelayCommand<string>(param =>
        {
            RealizedAction = string.Format("Accion realizada sobre " + param);
        });
    }
}

Ahora imaginemos que en el comando del viewmodel necesitamos saber sobre que person se ha hecho click. Podemos definir como parámetro del commando ItemClickEventArgs y automáticamente sin especificar nada en el behavior, los argumentos del evento ItemClick se pasan al comando.
public RelayCommand<Windows.UI.Xaml.Controls.ItemClickEventArgs> ItemActionCommand
{
    get
    {
        return new RelayCommand<Windows.UI.Xaml.Controls.ItemClickEventArgs>(e =>
            {
                RealizedAction = string.Format("Accion realizada sobre {0} {1}",
                    (e.ClickedItem as Person).Name, (e.ClickedItem as Person).LastName);
            });
    }
}


Pero esta forma de hacerlo mantiene el viewmodel acoplado a componentes que son propios de la interfaz de usuario. Para poder desaclopar el view model podemos utilizar un converter que se encargue de convertir los argumentos del evento ItemClick al objeto Person.
public class ItemClickEventArgsToModelConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var args = (ItemClickEventArgs)value;

        if (args == null) return value;

        var item = args.ClickedItem;

        return item;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        return value;
    }
}


Para hacer uso de este converter en el xaml utilizamos la propiedad InputConverter de InvokeCommandAction. Recordad que el namespace y el converter tienen que ser declarados previamente.

Asi nos queda el código de la vista completo
<Page
    x:Class="InvokeCommandsBehaviorsSDK.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:InvokeCommandsBehaviorsSDK"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:interactivity="using:Microsoft.Xaml.Interactivity"    
    xmlns:core="using:Microsoft.Xaml.Interactions.Core"
    xmlns:conv="using:InvokeCommandsBehaviorsSDK.Converter"
    mc:Ignorable="d" RequestedTheme="Light"
    DataContext="{Binding Main, Source={StaticResource Locator}}">

    <Page.Resources>
        <conv:ItemClickEventArgsToModelConverter x:Key="ItemClickEventArgsToModelConverter"/>
    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50*"/>
            <ColumnDefinition Width="50*"/>
        </Grid.ColumnDefinitions>

        <GridView Margin="25" Width="400" Height="300" ItemsSource="{Binding Persons}" IsItemClickEnabled="True">
            <GridView.ItemTemplate>
                <DataTemplate>
                    <Grid Width="200" Margin="0" >
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Border Background="LightBlue" Margin="0" Width="50" Height="50"/>

                        <StackPanel Grid.Column="1" Margin="10,10,0,0"  Orientation="Vertical" VerticalAlignment="Top">
                            <TextBlock >
                                        <Run Text="{Binding Name}"/>
                                        <Run Text="{Binding LastName}"/>
                            </TextBlock>
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
            <interactivity:Interaction.Behaviors>
                <core:EventTriggerBehavior EventName="ItemClick">
                    <core:InvokeCommandAction Command="{Binding ItemActionCommand}"
                    InputConverter="{StaticResource ItemClickEventArgsToModelConverter}"/>
                </core:EventTriggerBehavior>
            </interactivity:Interaction.Behaviors>
        </GridView>

        <StackPanel Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center">
            <TextBlock FontSize="33" >Acción realizada:</TextBlock>
            <TextBlock FontSize="25" Text="{Binding RealizedAction}" ></TextBlock>
        </StackPanel>
    </Grid>
</Page>

Y así el comando en el viewmodel
public RelayCommand<Person> ItemActionCommand
{
    get
    {
        return new RelayCommand<Person>(person =>
        {
            RealizedAction = string.Format("Accion realizada sobre {0} {1}", person.Name, person.LastName);
        });
    }
}

De esta forma el viewmodel queda totalmente desacoplado de la vista, lo que nos da la posibilidad de sustituir el control del GridView por otro control y solo deberíamos cambiar el converter pero el viewmodel no se vería afectado, que es el objetivo del patrón MVVM.

Libros Relacionados

Windows 8.1 Apps with XAML and C# Unleashed

Programming Windows: Writing Windows 8 Apps With C# and XAML

Desarrollo en Windows 8 y Windows Phone 8 con XAML y C#

No hay comentarios:

Publicar un comentario