Entendiendo el patrón de diseño MVVM (II)


Buenas a todos,

Hace unos días escribí un post hablando sobre lo que había aprendido del patrón MVVM y lo que me había parecido una vez que lo comprendí.

Entendiendo el patrón de diseño MVVM

En el post, terminaba emplazándoos a una nueva ocasión en la que os mostraría un ejemplo sobre cómo aplicar este patrón en WPF. Y por fin hoy, después de unos días trabajando en él os lo puedo traer. A continuación os voy mostrar e intentar explicar lo máximo posible el ejemplo de la aplicación del patrón de diseño Model-View-ViewModel. Vamos a ir poco a poco avanzando en el ejemplo.

Lo primero que debemos hacer es dotar a nuestra aplicación de un pequeño “Core”, que nos proporcione las clases que necesitamos para trabajar con MVVM. En mi caso, y al estar empezando a trabajar con esto, he decidido utilizar unas clases que he encontrado en otro blog. A medida que vaya profundizando en el conocimiento de este patron de diseño y las clases crearé unas propias o si es necesario mejoraré las que ya he tomado como partida. Este es el blog donde se encuentran las clases que he cogido:

http://www.c-sharpcorner.com/UploadFile/1a81c5/a-simple-wpf-application-implementing-mvvm/

Necesitamos como partida dos clases básicamente: BaseViewModel y DelegateCommand. La primera nos servirá como clase base para todos los ViewModel que haya que definir mientras que la segunda nos permite crear los comandos que después serán utilizados por la vista.

El ejemplo que voy a crear a continuación es muy sencillo, lo que quiero hacer es crear una ventana que accede a dos modelos distintos. Para uno de ellos mostrará una lista a la que se podrán añadir nuevos elementos y para el otro permitirá indicar los valores del modelo y mostrarlos después en una ventana emergente. Para la funcionalidad se necesitarán dos comandos distintos, uno que añadirá elementos a la lista y otro que mostrará la ventana emergente.

Definiendo nuestros modelos

Para el ejemplo he creado dos modelos simples que os mostraré a continuación: Worker y Contact

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExampleApplication
{
    public class Worker
    {
        public string name { get; set; }
        public string department { get; set; }
        public double amount { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ExampleApplication
{
    public class Contact
    {
        public string phone { get; set; }
        public string email { get; set; }
    }
}

Definiendo el ViewModel

El ViewModel, como definíamos en el artículo anterior, será el almacén que contendrá los modelos y comandos que usará la vista.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace ExampleApplication
{
    public class ViewModel : BaseViewModel
    {
        private Worker _worker;
        private ObservableCollection<Contact> _contacts;
        private Contact _contact;
        private ICommand _addContactCommand;
        private ICommand _showWorker;

        public Worker Worker
        {
            get
            {
                return _worker;
            }
            set
            {
                _worker = value;
                NotifyPropertyChanged("Worker");
            }
        }

        public ObservableCollection<Contact> Contacts
        {
            get
            {
                return _contacts;
            }
            set
            {
                _contacts = value;
                NotifyPropertyChanged("Contacts");
            }
        }
        
        public Contact Contact
        {
            get
            {
                return _contact;
            }
            set
            {
                _contact = value;
                NotifyPropertyChanged("Contact");
            }
        }

        public ICommand AddContactCommand
        {
            get 
            {
                if (_addContactCommand == null)
                    _addContactCommand = new DelegateCommand(param => this.AddContact(),null);

                return _addContactCommand;
            }
        }

        public ICommand ShowWorkerCommand
        {
            get
            {
                if (_showWorker == null)
                    _showWorker = new DelegateCommand(param => this.ShowWorker(), null);

                return _showWorker;
            }
        }

        public ViewModel()
        {
            Worker = new Worker();
            Contact = new Contact();
            Contacts = new ObservableCollection<Contact>()
            {
                new Contact(){phone = "950123123", email = "a@b.com"},
                new Contact(){phone = "951321321", email = "b@c.com"}
            };
            Contacts.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ContactCollectionChanged);      
        }

        void ContactCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            NotifyPropertyChanged("Contacts");
        }

        private void AddContact()
        {
            Contacts.Add(Contact);
            Contact = new Contact();
        }

        private void ShowWorker()
        {
            MessageBox.Show(Worker.name + Worker.department + Worker.amount.ToString(),"WorkerData");
        }

    }
}

Vamos a ver algunos aspectos a tener en cuenta de esta clase:

  • En el constructor se inicializan los modelos que se van a utilizar
  • En la clase se definen como propiedades tanto los modelos, como los comandos que se van a utilizar, y sus descriptores de acceso respectivos. En el caso de los modelos, con el descriptor set se llama al método NotifyPropertyChanged para indicar que ha habido un cambio en dicho modelo.
  • Los comandos se asocian a un método privado a través de la clase de nuestro “core” que hemos creado previamente. La clase DelegateCommand

Definiendo la vista

Por último la vista, aquí es donde veremos las capacidades del binding, tanto de datos como de comandos que nos ofrece WPF, veremos como se conectan tanto los modelos como los comandos del ViewModel con la vista. Primero vamos a ver el código XAML

<Window x:Class="ExampleApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewmodel="clr-namespace:ExampleApplication"
        Title="MainWindow" Height="500" Width="525">
    <Window.Resources>
        <viewmodel:ViewModel x:Key="ViewModel"/>
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource ViewModel}}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition />
        </Grid.RowDefinitions>
        <DataGrid HorizontalAlignment="Center" VerticalAlignment="Center" ItemsSource="{Binding Contacts}" Grid.Row="0" Grid.Column="0"/>
        <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="0" Margin="0 10">
            <Label Content="Phone"></Label>
            <TextBox Text="{Binding Contact.phone}" Width="100"></TextBox>
            <Label Content="Email"></Label>
            <TextBox Text="{Binding Contact.email}" Width="100"></TextBox>
            <Button Content="Add Contact" Command="{Binding AddContactCommand}" Margin="10 0 0 0"></Button>
        </StackPanel>
        <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0" Margin="0 10">
            <Label Content="Name"></Label>
            <TextBox Text="{Binding Worker.name}" Width="100"></TextBox>
            <Label Content="Department"></Label>
            <TextBox Text="{Binding Worker.department}"  Width="100"></TextBox>
            <Label Content="Amount"></Label>
            <TextBox Text="{Binding Worker.amount}"  Width="50"></TextBox>
            <Button Content="Show Worker" Command="{Binding ShowWorkerCommand}" Margin="5 0 0 0"></Button>
        </StackPanel>
    </Grid>
</Window>
  • Inicialmente debemos de indicar el ViewModel al que vamos a hacer referencia en la vista. (Líneas 6 a 8)
  • En segundo lugar, debemos de indicar el DataContext del Grid de la vista, indicándolo como StaticResource y haciendo referencia al ViewModel, veremos a partir de ahora que la información de nuestro ViewModel aparece en el IntelliSense de la vista. (Línea 9)
  • Cuando queramos asociar una propiedad de alguno de los modelos del ViewModel a uno de los controles de la vista, tendremos que hacerlo por medio del Binding, indicando el modelo y su propiedad. (Línea 22)
  • Por último, podemos asociar también los botones de la vista a comandos del ViewModel por medio de la propiedad “Command” de los mismos. (Línea 25)

Y para terminar el code-behind de la vista

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ExampleApplication
{
    /// <summary>
    /// Lógica de interacción para MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

¿Y qué pasa con el code-behind?, pues como se puede ver, no hay nada. Con esto, cumplimos uno de los objetivos y beneficios de este patrón de diseño que os comentaba en el artículo anterior, separar el diseño, de la lógica y la funcionalidad. De esta forma un diseñador puede dedicarse exclusivamente al look&feel de la aplicación sin centrarse en nada de su funcionalidad, ni eventos de los botones ni nada, todo queda en el ViewModel. ¿Y el funcionamiento del mini ejemplo que hemos hecho?, pues os dejo algunos capturas a continuación, para que podáis verlo.

Captura de pantalla 2014-11-29 a las 23.07.52

Captura de pantalla 2014-11-29 a las 23.07.52

Captura de pantalla 2014-11-29 a las 23.08.12

Y nada más, espero que os sea de utilidad el código y os pueda servir como guía para aplicar este patrón de diseño en aplicaciones de escritorio basadas en WPF.

Saludos

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s