[WPF] Plantillas de datos dinámicas.

En muchas ocasiones podemos mostrar en una misma lista datos con diferentes significados o propiedades y podemos querer que sea fácil para el usuario distinguir entre los distintos tipos.

Por ejemplo, a la hora de mostrar una lista de los empleados de una empresa, tenemos diferentes puestos y habilidades, sería ideal que de un simple vistazo pudiésemos localizar los empleados que cumplen con lo que necesitamos para poder abrir su ficha y asignarles un trabajo.

En WPF podemos realizar esto de una forma bastante sencilla usando el interface IValueConverter y las clases DataTemplate y DataTemplateSelector.

IValueConverter

En muchas ocasiones el tipo de dato guardado en nuestra base de datos no es exactamente el mismo que deseamos presentar al cliente. Por ejemplo guardamos un código de país en nuestros datos pero queremos mostrar la bandera del país, no su código. También puede darse el caso de que queramos presentar sutiles diferencias entre diferentes valores de un campo, por ejemplo cambiando el color de letra o resaltando en negrita ante ciertos valores.

Esto, que se podría decir es una lógica de presentación propia de nuestros datos, podemos realizarlo de forma automática y muy sencilla con el interface IValueConverter, en tres pasos: Crear una clase que implemente el interface IValueConverter , referenciar esta clase como un recurso en nuestro XAML y usar la opción Converter en el binding de nuestro dato:

1º Crear una clase que implemente IValueConverter, debemos crear dos métodos Convert y ConvertBack:

class BoolToColorConverter : System.Windows.Data.IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, 
                            System.Globalization.CultureInfo culture)
    {
        bool Valor = (bool)value;

        if (Valor == true)
        {
            return new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Green);
        }
        else
        {
            return new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, 
                                System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

 

En este ejemplo podemos ver un conversor que pasa un valor boolean a una interpretación en color (verde para verdadero, rojo para falso) con lo que conseguimos una interpretación visual más sencilla para el usuario. En la estructura de la clase que implementa el interface IValueConverter, los parámetros de ambos métodos son los mismos aunque su significado varía:

  • Convert:
    • Value: es un objeto con el valor de la propiedad original que queremos convertir.
    • targetType: es el tipo de la propiedad que queremos devolver.
    • parameter: un parámetro adicional que podemos enviar al método para la conversión.
    • culture: la cultura actual de la aplicación, para conversiones que puedan depender de ella.
  • ConvertBack:
    • Value: es un objeto con el valor de la propiedad que hemos convertido.
    • targetType: es el tipo de nuestra propiedad original.
    • parameter: un parámetro adicional que podemos enviar al método para la conversión.
    • culture: la cultura actual de la aplicación, para conversiones que puedan depender de ella.

2º Referenciar esta clase como un recurso en nuestro XAML:

Una vez creada nuestra clase deberemos referenciarla en XAML como un recurso para poder utilizarla.

Si no lo hemos hecho ya, primero deberemos referenciar nuestro namespace en el XAML:

xmlns:converters="clr-namespace:DynamicTemplates"

A continuación creamos un recurso con nuestro conversor:

<Window.Resources>
    <converters:BoolToColorConverter x:Key="ColorConverter"></converters:BoolToColorConverter>
</Window.Resources>  

Le asignamos una Key como a cualquier otro recurso para poder acceder más tarde a él.

3º Usamos nuestro conversor en la propiedad que queramos afectar.

Por ejemplo el Foreground de un TextBlock enlazado a una propiedad Disponible de nuestras clases de datos:

<TextBlock Name="txtDisponibilidad" 
            Foreground="{Binding Path=Disponibilidad,
                                Converter={StaticResource ColorConverter}}" 
            Text="{Binding Path=UserName}" Margin="0,0,192,285">
</TextBlock>

Con esto, cuando la propiedad Disponibilidad tenga el valor True, el Foreground será verde, mientras que cuando tenga el valor False será rojo:

imageimage

DataTemplate

Ahora que ya sabemos como modificar valores para presentarlos de formas diferentes a como se representarían de forma normal, vamos a ver como modificar el aspecto general de nuestros datos en un control de lista, por ejemplo, un ListBox o un ComboBox, creando una plantilla visual específica que muestre nuestros datos al usuario de forma más atractiva y más sencilla para su entendimiento.

Esta operación se realiza en dos pasos: Definir nuestro DataTemplate y asignarlo al control que queramos que lo use.

1º Definir nuestro DataTemplate

En XAML debemos especificar un DataTemplate como recurso, y dentro de este, crear el árbol visual que compondrá a los Items de nuestra lista. De esta forma una vez que especifiquemos al control de lista el DataTemplate que queremos que use, se encargará de formatear nuestros datos visualmente como le hayamos indicado. Vamos a Crear una plantilla sencilla, que muestre los datos de un empleado usando un estilo de tarjeta de visita:

<DataTemplate x:Key="MiTemplate">
    <Grid Height="74" Width="250" HorizontalAlignment="Stretch" Margin="5">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="64"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height=".3*"></RowDefinition>
            <RowDefinition Height=".3*"></RowDefinition>
            <RowDefinition Height=".3*"></RowDefinition>
        </Grid.RowDefinitions>
        <Border Grid.ColumnSpan="2" Grid.RowSpan="3" BorderBrush="Black" BorderThickness="2" CornerRadius="5">
            <Border.Background>
                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Offset="0" Color="White"></GradientStop>
                    <GradientStop Offset="1" Color="LightGray"></GradientStop>
                </LinearGradientBrush>
            </Border.Background>
        </Border>
        <Image Name="imgUserPhoto" Grid.Column="0" Grid.RowSpan="3" Margin="10" Source="{Binding Path=UserPhotoFile}"></Image>
            
        <TextBlock Name="tblUserName" Grid.Column="1" Grid.Row="0" 
                    FontSize="14" FontWeight="Bold"
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Text="{Binding Path=UserName}">
        </TextBlock>
        <TextBlock Name="tblUserFullName" Grid.Column="1" Grid.Row="1" 
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Text="{Binding Path=UserFullName}">
        </TextBlock>
        <TextBlock Name="tblUserArea" Grid.Column="1" Grid.Row="2" 
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Text="{Binding Path=UserArea}">
        </TextBlock>
    </Grid>
</DataTemplate>

Dentro de la propia DataTemplate hemos incluido los Bindings a las propiedades para cada control, al insertar la plantilla en cada Item, el DataContext que usará será el de la ventana que lo contenga.

2º Asignarlo al control

Una vez que hemos creado nuestra DataTemplate y le hemos asignado un nombre (x:Key) simplemente tenemos que especificar la propiedad ItemTemplate en el control que queremos que use nuestra plantilla, apuntando hacia el nombre de la misma:

<ListBox Name="lstUsers" Margin="0,10,0,10" 
            HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
            ItemTemplate="{StaticResource MiTemplate}"
</ListBox>  

Como puedes ver simplemente hemos definido un Layout de elementos, usando una grid, un Border, un Image y varios Textblocks para mostrar los datos, en vez de uno al lado del otro como se hacia tradicionalmente, en forma de ficha o tarjeta, y después hemos indicado a nuestro ListBox que use este Layout, por medio de la propiedad ItemTemplate. El resultado al enlazar con datos sería este:

image

Como puedes ver, es mucho más atractivo que simplemente mostrar la información del usuario en una línea solo con texto.

DataTemplateSelector

Bien, ya hemos visto como usar el interface IValueConverter para cambiar la apariencia visual de nuestros datos y como usar la clase DataTemplate para personalizar el árbol visual de nuestros controles de listas.

Pero, que pasa si, siguiendo el ejemplo anterior de la lista de empleados, tenemos diferentes perfiles de empleados, con diferentes habilidades y queremos que sea más sencillo distinguir entre tipos de empleados y entre habilidades de los mismos? Bueno podríamos usar clases con el interface IValueConverter para mostrar, ocultar datos o modificar colores… Pero eso nos exigiría tener varias clases para poder personalizar la plantilla. ¿Que tal si pudiésemos tener varias DataTemplate independientes y la lista fuese capaz, en base a los datos de cada Item, elegir cual es la que debe aplicar? Eso podemos realizarlo con la clase DataTemplateSelector en tres sencillos pasos: crear las plantillas que queramos usar, crear una clase que herede de DataTemplateSelector y establecer la propiedad ItemTemplateSelector de nuestro control de lista.

1º Crear las plantillas que queramos usar

En nuestro ejemplo anterior hemos creado una plantilla para mostrar datos de usuarios, ahora vamos a crear dos más, una específica para desarrolladores y otra para diseñadores, dejando la que hemos creado anteriormente para directores.

<DataTemplate x:Key="TemplateDevelopers">
    <Grid Height="74" Width="250" HorizontalAlignment="Stretch" Margin="5">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="64"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height=".25*"></RowDefinition>
            <RowDefinition Height=".25*"></RowDefinition>
            <RowDefinition Height=".25*"></RowDefinition>
            <RowDefinition Height=".25*"></RowDefinition>                
        </Grid.RowDefinitions>
        <Border Grid.ColumnSpan="2" Grid.RowSpan="4" BorderBrush="DarkBlue" BorderThickness="2"  CornerRadius="5">
            <Border.Background>
                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Offset="0" Color="Azure"></GradientStop>
                    <GradientStop Offset="1" Color="LightBlue"></GradientStop>
                </LinearGradientBrush>
            </Border.Background>
        </Border>
        <Image Name="imgUserPhoto" Grid.Column="0" Grid.RowSpan="4" Margin="10" Source="{Binding Path=UserPhotoFile}"></Image>

        <TextBlock Name="tblUserName" Grid.Column="1" Grid.Row="0" 
                    FontSize="14" FontWeight="Bold"
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Text="{Binding Path=UserName}">
        </TextBlock>
        <TextBlock Name="tblUserFullName" Grid.Column="1" Grid.Row="1" 
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Text="{Binding Path=UserFullName}">
        </TextBlock>
        <TextBlock Name="tblUserArea" Grid.Column="1" Grid.Row="2" 
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Text="{Binding Path=UserArea}">
        </TextBlock>
        <TextBlock Name="tblDevLanguaje" Grid.Column="1" Grid.Row="3" 
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Text="{Binding Path=DevLang}" FontWeight="Bold"
                    Foreground="{Binding Path=DevLang,Converter={StaticResource DevConv}}">
        </TextBlock>
    </Grid>
</DataTemplate>

<DataTemplate x:Key="TemplateDesigners">
    <Grid Height="74" Width="250" HorizontalAlignment="Stretch" Margin="5">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="64"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height=".25*"></RowDefinition>
            <RowDefinition Height=".25*"></RowDefinition>
            <RowDefinition Height=".25*"></RowDefinition>
            <RowDefinition Height=".25*"></RowDefinition>
        </Grid.RowDefinitions>
        <Border Grid.ColumnSpan="2" Grid.RowSpan="4" BorderBrush="LimeGreen" BorderThickness="2"  CornerRadius="5">
            <Border.Background>
                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Offset="0" Color="LightYellow"></GradientStop>
                    <GradientStop Offset="1" Color="LightGreen"></GradientStop>
                </LinearGradientBrush>
            </Border.Background>
        </Border>
        <Image Name="imgUserPhoto" Grid.Column="0" Grid.RowSpan="4" Margin="10" Source="{Binding Path=UserPhotoFile}"></Image>

        <TextBlock Name="tblUserName" Grid.Column="1" Grid.Row="0" 
                    FontSize="14" FontWeight="Bold"
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Text="{Binding Path=UserName}">
        </TextBlock>
        <TextBlock Name="tblUserFullName" Grid.Column="1" Grid.Row="1" 
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Text="{Binding Path=UserFullName}">
        </TextBlock>
        <TextBlock Name="tblUserArea" Grid.Column="1" Grid.Row="2" 
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Text="{Binding Path=UserArea}">
        </TextBlock>
        <TextBlock Name="tblDesignEnvironment" Grid.Column="1" Grid.Row="3" 
                    HorizontalAlignment="Left" VerticalAlignment="Center"
                    Text="{Binding Path=DesEnv}" FontWeight="Bold"
                    Foreground="{Binding Path=DesEnv,Converter={StaticResource DesConv}}">
        </TextBlock>
    </Grid>
</DataTemplate>

Estas plantillas son muy parecidas a la primera, hemos añadido una fila más de información con el lenguaje de desarrollo o software de diseño que mejor usa el empleado y en este texto hemos usado un converter para modificar el color del texto dependiendo del texto en cuestión.

2º Crear una clase que herede de DataTemplateSelector

La clase DataTemplateSelector se encuentra en el namespace System.Windows.Controls, tenemos que sobreescribir el método SelectTemplate, que toma dos parámetros item (el item actual de la lista) y container (el objeto en cuestión que contiene nuestro item), debemos devolver el DataTemplate a aplicar en este item en concreto:

class UsersTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item != null && item is UserClass)
        {
            UserClass Usuario = item as UserClass;
            switch (Usuario.UserArea)
            {
                case "director":
                    {
                        return App.Current.Resources["TemplateDirectors"] as DataTemplate;
                    }
                case "developer":
                    {
                        return App.Current.Resources["TemplateDevelopers"] as DataTemplate;
                    }
                case "designer":
                    {
                        return App.Current.Resources["TemplateDesigners"] as DataTemplate;
                    }
                default:
                    {
                        return App.Current.Resources["TemplateDirectors"] as DataTemplate;
                    }
            }
        }

        return null;
    }

}

Es muy sencillo, simplemente convertimos el item a una instancia de nuestra clase UserClass que es la que está enlazada a la lista y usamos el campo UserArea para saber que plantilla debemos devolver, luego como nuestras plantillas están definidas en un ResourceDictionary añadido a nuestro app.xaml simplemente buscamos el recurso y lo convertimos a un DataTemplate para devolverlo.

3º Establecer la propiedad ItemTemplateSelector de nuestro control de lista

Una vez que tenemos nuestra clase creada, debemos referenciarla como un recurso de XAML, añadiendo primero el namespace donde se encuentra a la ventana o usercontrol:

xmlns:cls="clr-namespace:DynamicTemplates"

Y creando después un recurso con nuestra clase de selección de plantillas:

<Window.Resources>
    <cls:UsersTemplateSelector x:Key="TempSelector"></cls:UsersTemplateSelector>
</Window.Resources>

Una vez hecho esto, debemos eliminar la propiedad ItemTemplate de nuestro control de listas y sustituirla por la propiedad ItemTemplateSelector apuntando a nuestro nuevo recurso de selección de plantillas:

<ListBox Name="lstUsers" Margin="0,10,0,10" 
            HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
            ItemTemplateSelector="{StaticResource TempSelector}">
</ListBox>

Una vez terminado todo, si añadimos distintos tipos de usuarios a la colección y ejecutamos tendremos un resultado parecido a este:

image

Como podemos ver cada tipo de usuario ha usado una plantilla diferente y además usando las clases que implementan IValueConverter hemos cambiado el color del texto de algunas opciones dependiendo del valor del texto.

De esta manera es muy fácil saber cuantos diseñadores tenemos disponibles o ver que hay menos directores disponibles que el resto de tipos de empleados, sin tener que leer o buscar de forma más complicada y , por supuesto, nuestro interface de usuario es mucho más agradable que una simple lista de texto en líneas.

Conclusión

Espero que os haya parecido instructivo este pequeño artículo y que podáis dotar de mayor dinamismo a vuestras aplicaciones WPF a la hora de mostrar datos al usuario, la era de las listas de texto en filas interminables ha terminado, tenemos el poder de cambiar la forma en la que los usuarios consumen datos, y podemos hacer que sea más sencillo y agradable.

Os dejo el proyecto para descarga y como siempre, estoy a vuestra entera disposición para cualquier cosa que necesitéis, duda, crítica, sugerencia o simplemente tomar un café y conocernos!

Un saludo a todos, gracias por leerme y HAPPY CODING!

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!