Compartiendo Código: Móvil + Web + Escritorio (2/2)

Hola a todos, por fin he encontrado un momento para poder acabar mi entrada sobre MVVM y compartir código.

Antes que nada, os dejo el enlace a la 1º parte por si no lo habéis visto, para poneros en situación:

Compartiendo Código: Móvil + Web + Escritorio (1/2)

Vamos al toro, empecemos por ver que son los comandos en WPF/Silverlight

Comandos

Los comandos son mecanismos de ejecución de nuestro código que reemplazan a los tradicionales eventos para conseguir algo fundamental en MVVM: Separar la lógica de visualización de la visualización en si misma.

Para crear un nuevo comando, solo debemos crear una nueva clase que implemente la interface ICommand (System.Windows.Input):

class HelloCommand : ICommand
{
    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        CanExecuteChanged(this, EventArgs.Empty);
        return true;
    }
    public void Execute(object parameter)
    {
        MessageBox.Show("Hola");
    }
}

Una vez hecho esto, referenciamos esta nueva clase en xaml y la añadimos como un recurso:

xmlns:comando="clr-namespace:WpfApplication1"
<Window.Resources>
    <comando:HelloCommand x:Key="Hello"></comando:HelloCommand>
</Window.Resources>

Si añadimos un nuevo botón a nuestra ventana veremos que una de sus propiedades se llama Command, simplemente debemos añadir un Binding estático a nuestra clase:

<Button Content="Button" Height="23" HorizontalAlignment="Left" 
        Margin="119,63,0,0" Name="button1" VerticalAlignment="Top" 
        Width="75" Command="{Binding Source={StaticResource Hello}}" />

Al ejecutar nuestra aplicación veremos que presionando el botón aparece el cuadro de mensajes que hemos definido en nuestro comando.

El método CanExecute controla si nuestro comando se puede ejecutar, si devuelve False, el control asociado al evento se desactivará automáticamente.

De todas formas, crear una clase para cada acción que queramos realizar en nuestra aplicación puede ser largo y complicado de mantener, para evitar esto podemos crear una clase que centralice la infraestructura del comando y, ya en cada clase de nuestro ViewModel crear variables públicas de tipo ICommand que usarán esa infraestructura y métodos que serán ejecutados:

class RelayCommand : ICommand
{
    private Action handler;
    private bool _IsEnabled;

    //Constructor
    public RelayCommand(Action _handler)
    {
        handler = _handler;
    }
    //Determina si el comando se puede ejecutar y desactiva o activa los controles asociados.
    public bool IsEnabled
    {
        get { return _IsEnabled; }
        set
        {
            if (value != _IsEnabled)
            {
                _IsEnabled = value;
                if (CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, EventArgs.Empty);
                }
            }
        }
    }
    //Llamada desde WPF, le indica si el comando se puede ejecutar, no la llamamos directamente nosotros.
    public bool CanExecute(object parameter)
    {
        return IsEnabled;
    }
    public event EventHandler CanExecuteChanged;
    //Ejecuta la acción indicada en la variable handler.
    public void Execute(object parameter)
    {
        handler();
    }
}

Esta clase es muy sencilla, implementa los mismos métodos de ICommand , simplemente en el constructor le pasamos el método a ejecutar y usamos su propiedad pública IsEnabled para establecer si está o no habilitado el comando.

 

Compartiendo Código

Vamos a empezar con la parte divertida, todo esto que hemos contado está muy bien MVVM, Comandos, desacoplar la lógica visual de la capa visual propiamente dicha… pero ¿para que nos puede servir?

Pues nos puede servir para hacer una aplicación WPF, Silverlight y WP7 que compartan TODO su código de presentación, a parte del xaml que obviamente hay que tener en cada proyecto.

¿Como? Vamos a verlo.

Para hacer un ejemplo sencillo he elegido el servicio http://is.gd un acortador de direcciones muy sencillo de implementar por código. Lo primero que tenemos que hacer es crear una nueva solución de Visual Studio 2010 vacía y agregar 4 proyectos: un proyecto WPF, un proyecto Silverlight, un proyecto Silverlight for Windows Phone 7 y una librería de clases. Al terminar debería tener algo así:

image

Vamos a empezar a trabajar con la librería de clases, añade una nueva clase que se llame RelayCommand y pon el siguiente código, será la encargada de soportar la infraestructura de nuestros comandos (debes añadir un using System.Windows.Input al archivo):

private Action handler;
private bool _IsEnabled;

//Constructor
public RelayCommand(Action _handler)
{
    handler = _handler;
}
//Determina si el comando se puede ejecutar y desactiva o activa los controles asociados.
public bool IsEnabled
{
    get { return _IsEnabled; }
    set
    {
        if (value != _IsEnabled)
        {
            _IsEnabled = value;
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, EventArgs.Empty);
            }
        }
    }
}
//Llamada desde WPF, le indica si el comando se puede ejecutar, no la llamamos directamente nosotros.
public bool CanExecute(object parameter)
{
    return IsEnabled;
}
public event EventHandler CanExecuteChanged;
//Ejecuta la acción indicada en la variable handler.
public void Execute(object parameter)
{
    handler();
}

Una vez hecho esto, vamos a añadir otra clase, que será nuestro ViewModel, llamada URLShortenerViewModel, debe implementar el interface INotifyPropertyChanged:

//Class constructor.
public URLShortenerViewModel()
{
    _ShortURLCommand = new RelayCommand(ShortURLMethod) { IsEnabled = false };
}

//Private state variables.
private string _originalurl = string.Empty;
private string _shortenedurl = string.Empty;

//Public properties for binding.
public string OriginalURL
{
    get { return _originalurl; }
    set
    {
        _originalurl = value;
        OnPropertyChanged("OriginalURL");

        if (String.IsNullOrEmpty(_originalurl))
        {
            (ShortURLCommand as RelayCommand).IsEnabled = false;
        }
        else
        {
            (ShortURLCommand as RelayCommand).IsEnabled = true;
        }
    }
}

public string ShortenedURL
{
    get { return _shortenedurl; }
    set
    {
        _shortenedurl = value;
        OnPropertyChanged("ShortenedURL");
    }
}

//Change Notifications
public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(String propertyName)
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

//Comand to short URL
private readonly ICommand _ShortURLCommand;

public ICommand ShortURLCommand
{
    get { return _ShortURLCommand; }
}

public void ShortURLMethod()
{
    String url = OriginalURL;
    url = Uri.EscapeUriString(url);
    ShortenedURL = string.Empty;
    url = String.Format(@"http://is.gd/api.php?longurl={0}", url);
    WebClient peticionUrl = new WebClient();
    peticionUrl.DownloadStringCompleted += DownloadComplete;
    peticionUrl.DownloadStringAsync(new Uri(url));
}

private void DownloadComplete(object sender, DownloadStringCompletedEventArgs e)
{
    if (e.Error != null)
    {
        throw e.Error;
    }
    if (e.Result != null)
    {
        ShortenedURL = e.Result;
    }
}

Con esto ya tenemos nuestro ViewModel terminado, y con el hemos terminado TODO el código necesario para nuestras 3 aplicaciones, vamos a diseñar cada una de ellas.

 

Aplicación WPF

Lo primero que tenemos que hacer es añadir una referencia en nuestro proyecto WPF a nuestro ViewModel, despues de esto solo tenemos que añadir en MainWindow.xaml una referencia a nuestra clase URLShortenerViewModel:

xmlns:viewmodel="clr-namespace:URLShortenerViewModel;assembly=URLShortenerViewModel"

Y tras eso, usar nuestro ViewModel como el DataContext de la ventana:

<Window.DataContext>
    <viewmodel:URLShortenerViewModel></viewmodel:URLShortenerViewModel>
</Window.DataContext>

Ahora solo tenemos que diseñar la pantalla, es muy sencilla, un textbox para la url original, un botón que la acorte y un textbox para la url corta:

image

<StackPanel x:Name="LayoutRoot" Background="White">
    <TextBlock Text="Original URL:" Margin="5"></TextBlock>
    <TextBox Height="23" HorizontalAlignment="Stretch" Margin="5" Name="txtOriginalURL" VerticalAlignment="Top" Width="Auto" 
                Text="{Binding Path=OriginalURL,Mode=TwoWay}" />
    <Button Content="Short IT!" Height="23" HorizontalAlignment="Stretch" Margin="5" Name="button1" VerticalAlignment="Top" Width="Auto" 
            Command="{Binding ShortURLCommand}" />
    <TextBlock Text="Shortened URL:" Margin="5"></TextBlock>
    <TextBox Height="23" HorizontalAlignment="Stretch" Margin="5" Name="txtShortenedURL" VerticalAlignment="Top" Width="Auto" 
                Text="{Binding Path=ShortenedURL,Mode=TwoWay}" />
</StackPanel>

Aquí es donde el enlace a datos de WPF entra en acción, simplemente enlazamos los textbox a las propiedades de nuestro ViewModel usando el modo bidireccional y enlazamos el botón a nuestro comando.

Ya no tenemos nada más que hacer, si ejecutamos el proyecto y escribimos una url en el cuadro superior, el botón se activará automáticamente al salir el foco del textbox y al pulsarlo obtendremos nuestra url acortada totalmente funcional.

 

Aplicación Silverlight

Con Silverlight las cosas se nos complican un poco, ya que no podemos añadir una referencia a nuestra librería de clases directamente, y si copiásemos las clases RelayCommand y URLShortenerViewModel, sería un infierno mantener dos veces el mismo código, además de que es precisamente lo que queremos evitar.

Para solucionar esto, Visual Studio nos permite hacer algo muy util, a la hora de añadir un elemento existente a un proyecto la ventana que sale es la siguiente:

image

El truco se encuentra en la flecha del botón abrir, si la pulsamos tendremos dos opciones: Add que es el comportamiento por defecto y Add as a Link que es el que nos interesa, Add as a Link añade una referencia al archivo seleccionado, pero no mueve el archivo a nuestro proyecto, podemos realizar cambios en el archivo desde nuestro proyecto y se reflejarán en el original o cambios en el original y se reflejarán en el nuestro, problema resuelto. usando Add as a Link navega hasta la carpeta de la librería de clases y añade los dos archivos .cs RelayCommand.cs y URLShortenerViewModel.cs.

Ya solo nos queda realizar las mismas modificaciones en el xaml que ya hicimos en la aplicación WPF y nuestra aplicación Silverlight estará lista y funcional:

xmlns:viewmodel="clr-namespace:URLShortenerViewModel"
<UserControl.DataContext>
    <viewmodel:URLShortenerViewModel></viewmodel:URLShortenerViewModel>
</UserControl.DataContext>
<StackPanel x:Name="LayoutRoot" Background="White">
    <TextBlock Text="Original URL:" Margin="5"></TextBlock>
    <TextBox Height="23" HorizontalAlignment="Stretch" Margin="5" Name="txtOriginalURL" VerticalAlignment="Top" Width="Auto" 
                Text="{Binding Path=OriginalURL,Mode=TwoWay}" />
    <Button Content="Short IT!" Height="23" HorizontalAlignment="Stretch" Margin="5" Name="button1" VerticalAlignment="Top" Width="Auto" 
            Command="{Binding ShortURLCommand}" />        
    <TextBlock Text="Shortened URL:" Margin="5"></TextBlock>
    <TextBox Height="23" HorizontalAlignment="Stretch" Margin="5" Name="txtShortenedURL" VerticalAlignment="Top" Width="Auto" 
                Text="{Binding Path=ShortenedURL,Mode=TwoWay}" />
</StackPanel>

Como podéis ver el xaml es idéntico, salvo que en el caso de Silverlight trabajamos en un UserControl y en el caso de WPF con una Window. Si ejecutáis veréis que el comportamiento es el mismo que el de la aplicación WPF, ya tenemos nuestra aplicación lista para usarse desde escritorio y desde internet, ¿Que tal si también la ponemos en nuestras manos con Windows Phone 7? Vamos a ello.

Aplicación Windows Phone 7

Quizás la versión de Windows Phone 7 es la más problemática, por una simple razón: los comando fueron introducidos en Silverlight en la versión 4.0, como WP7 usa Silverlight 3.0 con algunas mejoras, los comandos no están disponibles desde xaml.

Pero siempre hay formas de hacer las cosas, aunque en este caso necesitaremos 1 evento y 2 líneas de C# para llevar nuestra idea a buen puerto. Lo primero es repetir los mismos pasos que en la aplicación Silverlight, añadimos como enlaces (Add as a Link) las clases de nuestro ViewModel y las referenciamos en xaml, añadimos los controles, con la diferencia de que en el botón especificamos un manejador de eventos para el evento Click:

xmlns:viewmodel="clr-namespace:URLShortenerViewModel"
<phone:PhoneApplicationPage.DataContext>
    <viewmodel:URLShortenerViewModel></viewmodel:URLShortenerViewModel>
</phone:PhoneApplicationPage.DataContext>
<StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <TextBlock Text="URL Original:"></TextBlock>
    <TextBox Height="72" Name="txtOriginalURL"  Text="{Binding Path=OriginalURL,Mode=TwoWay}" VerticalAlignment="Top" />
    <Button Content="Short IT!" Height="72" HorizontalAlignment="Stretch" Name="button2" VerticalAlignment="Top"  
            Click="button1_Click"  />            
    <TextBlock Text="URL Acortada:"></TextBlock>
    <TextBox Height="72" Name="txtShortenedURL"  Text="{Binding Path=ShortenedURL,Mode=TwoWay}" VerticalAlignment="Top" />
</StackPanel>

 

Ahora en el code behind MainPage.xaml.cs en el evento Click vamos a invocar manualmente a nuestro comando:

private void button1_Click(object sender, RoutedEventArgs e)
{
    URLShortenerViewModel.URLShortenerViewModel vm = (URLShortenerViewModel.URLShortenerViewModel)this.DataContext;
    vm.ShortURLCommand.Execute(null);
}

 

No mentía, solo hemos necesitado dos líneas de código para resolver el problema. Lo único que perdemos es la desactivación automática del botón, al no estar enlazado al comando, a cambio de esto gracias a dos líneas tenemos la misma lógica de presentación que nuestras aplicaciones en WPF y Silverlight.

Si ejecutas la aplicación Windows Phone 7 verás que funciona igual de bien que las otras dos.

Conclusión

Las posibilidades de integración y reutilización de código que nos ofrece MVVM junto con .NET 4 WPF, Silverlight y Windows Phone 7 son enormes, podremos transformar aplicaciones de escritorio en aplicaciones móviles o web con una rapidez nunca vista hasta ahora.

Espero que os haya parecido instructivo, espero vuestros comentarios, criticas y sugerencias para seguir mejorando con vosotros día a día. También os dejo el código fuente de una solución con todos los proyectos funcionando perfectamente por si queréis echarle un vistazo y ya sabéis que ando por los foros de MSDN y por twitter (@JosueYeray) para lo que os haga falta.

Un gran saludo y Happy Coding!

2 comentarios sobre “Compartiendo Código: Móvil + Web + Escritorio (2/2)”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *