Desde hace unos días estoy preparando el examen de certificación en desarrollo de aplicaciones Windows, y ha sido buscando información sobre patrones de capa de presentación, cuando me he percatado de la gran cantidad de implementaciones y frameworks de MVVM que existen para WPF y Silverlight. Muchas veces, sin embargo, tener tantas opciones desconcierta a los nuevos desarrolladores, primero por no conocer las diferencias entre cada una de estas opciones, pero sobre todo porque generalmente los ejemplos de implementación no son sencillos o, incluso, contienen errores.
Con esta entrada voy a iniciar una serie dedicada a explicar las distintas implementaciones del patrón MVVM existentes, los principales problemas que nos podemos encontrar y cómo los frameworks de MVVM nos pueden ayudar a resolverlos. Para comenzar, vamos a sentar las bases y en esta entrada veremos la implementación básica del patrón de presentación en una sencilla aplicación de demostración.
El proyecto WPF del que partimos va a consistir en una vista (ventana) con cinco controles: cuatro controles Button y un control ComboBox. Los botones realizarán cuatro acciones: modificar y restablecer el título de la ventana y añadir y eliminar elementos al ComboBox. Al seleccionar un elemento del ComboBox se mostrará la descripción, valor y fecha de creación del elemento.
El patrón MVVM se caracteriza por no utilizar el code-behind de la vista para realizar una acción de negocio, que siempre debe ser una responsabilidad del ViewModel. En nuestro ejemplo, la vista no tiene ni siquiera el fichero de code-behind, aunque podemos encontrar algunas implementaciones que utilizan este código para establecer el DataContext. Otra característica es que la vista siempre se comunica con el ViewModel mediante binding, utilizando la propiedad Command de los controles.
Comenzamos creando nuestra clase ViewModel con la propiedad Title, a la que le asignaremos un valor en el constructor de la clase. Como es evidente, esto no es una acción de negocio, este código podría ir perfectamente en la vista, pero nos sirve a modo de ejemplo.
class ViewModel
{
public string Title { get; set; }
public ViewModel()
{
Title = "My title";
}
}
Ahora creamos la vista a la que añadiremos una referencia al modelo y además indicaremos que el DataContext de la ventana es la clase ViewModel que acabamos de crear.
<Window x:Class="BasicMVVM.Views.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewmodel="clr-namespace:BasicMVVM.ViewModels"
Title="{Binding Title}">
<Window.DataContext>
<viewmodel:ViewModel />
</Window.DataContext>
</Window>
Si ejecutamos la aplicación, veremos que el título de la ventana se muestra correctamente. No está mal para comenzar, ¿no? ¡Continuemos añadiendo más funcionalidad!
Hagamos que al pulsar un botón, el título de la ventana cambie. Para llevar a cabo esta funcionalidad vamos a definir un comando mediante la interfaz ICommand. En el proyecto podemos encontrar la clase RelayCommand que implementa la interfaz ICommand. Esta clase nos permite definir, mediante delegados, un método que se llamará cuando se invoque el comando y un método para determinar si el comando se puede ejecutar. Además la clase tiene el evento CanExecuteChanged, que es parte de la interfaz ICommand, que se lanza cuando se producen cambios que modifiquen si el comando se debe ejecutar o no y delega en el evento CommandManager.RequerySuggested.
public class RelayCommand : ICommand
{
public Predicate<object> CanExecuteDelegate;
public Action<object> ExecuteDelegate;
public bool CanExecute(object parameter)
{
return CanExecuteDelegate(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
ExecuteDelegate(parameter);
}
}
Lo siguiente es definir el comando en el ViewModel y crear los métodos CanUpdateTitle, que comprueba que el título tenga menos de 50 carácteres, y el método SetTitle que modifica la propiedad Title.
public ICommand SetTextCommand
{
get
{
this._SetTitleCommand = new RelayCommand()
{
CanExecuteDelegate = c => CanUpdateTitle(),
ExecuteDelegate = c => SetTitle()
};
return this._SetTitleCommand;
}
}
private bool CanUpdateTitle()
{
return Title.Length < 50;
}
private void SetTitle()
{
Title += " grows! ";
}
Para que todo funcione correctamente, y la vista conozca cuando se produce algún cambio en alguna propiedad, el ViewModel debe implementar la interfaz INotifyPropertyChanged para notificar los cambios mediante el evento PropertyChanged.
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Cómo esta funcionalidad es común a todos los ViewModels, una buena práctica es crear una clase base y hacer que todos nuestros ViewModels hereden de ella. En nuestro ejemplo, por simplificar al máximo, todo está en una única clase.
Ahora, en la propiedad Title debemos llamar al método PropertyChanged pasando el nombre de la propiedad que se ha modificado.
public string Title
{
get { return _title;}
set
{
_title = value;
NotifyPropertyChanged("Title");
}
}
Y crear el botón en la vista:
<Button Command="{Binding SetTitleCommand}" Content="Change Title" Name="cmdSetTitle" />
Para terminar, vamos a ver como enlazar desde la vista un control ComboBox.
<ComboBox DisplayMemberPath="Name" ItemsSource="{Binding Items}" Name="cmbItems" />
En la propiedad ItemsSource indicamos la colección Items de nuestro ViewModel y con la propiedad DisplayMemberPath le indicamos la propiedad que debe mostrar. La colección Items es una ObservableCollection. Añadir ahora un botón que añada un nuevo elemento a esta colección, no debería ser tarea difícil.
public ICommand AddItemCommand
{
get
{
this._AddItemCommand = new RelayCommand()
{
CanExecuteDelegate = p => true,
ExecuteDelegate = p => AddItem()
};
return this._AddItemCommand;
}
}
private void AddItem()
{
Model newItem = new Model();
newItem.Code = Items.Count == 0 ? 1 : Items[Items.Count - 1].Code + 1;
newItem.Name = "Item #" + newItem.Code;
Items.Add(newItem);
NotifyPropertyChanged("Items");
}
Sólo queda que al seleccionar un elemento del ComboBox nos muestre en un TextBlock la información del elemento seleccionado. Esto lo hacemos enlazando la propiedad SelectedItem del ComboBox con su homónima del ViewModel.
<ComboBox Name="cmbItems"
SelectedValuePath="Value" DisplayMemberPath="Name"
ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
<TextBlock Name="textBlock1" Text="{Binding SelectedItem}" />
Hasta aquí esta primera introducción al patrón Model View ViewModel en WPF. Como he dicho al principio, seguiré dedicando los próximos posts a la mejora de esta implementación del patrón y al funcionamiento de los muchos frameworks de MVVM que tenemos disponibles.
Descarga código fuente: BasicMVVM_cs.zip (4.35 kB) - 523 descargas
Hola Álex! Gracias por tu tiempo para enseñar lo que sabes. Estoy realmente interesado en este patrón de diseño y últimamente lo veo por todos lados. Nunca he desarrollado una aplicación WPF precisamente por no entender su nuevo modelo de enlaces y separación entre capas. He visto implementaciones con delegados, RelayCommands y cachondeos varios que me han dejado totalmente confuso. Me encuentro apunto de crear una nueva solución con todo lo que expones en tu artículo y pronto querré avanzar (me conozco). Espero poder sacar algo en claro. Me gustaría seguir tus artículos de cerca y creo que los consecutivos serán tanto o más valiosos que éste
Por cierto que me interesa también MUCHO la manera de aplicar este modelo a bases de datos usando el nuevo Entity Framework. He pensado que podría desarrollar una aplicación en la que una ListBox reflejase los cambios de determinados datos en todo momento de manera declarativa; es decir, que si programáticamente borro un registro, también sea borrado de la ListBox sin ninguna operación adicional. Lo mismo con las creaciones y actualizaciones.
¿Sabes si es posible? ¿Tienes esos conocimientos? Gracias.
Hola José Manuel, ¡gracias por tu comentario! Tengo pendiente unos cuantos posts sobre MVVM, pero no acabo de encontrar el tiempo para terminarlos. ¡Espero poder hacerlo pronto!
En cuanto a la consulta de EF, yo creo que sí que se puede conseguir utilizando la notificación de cambios, pero me has dado una gran idea para escribir otro post.
Álex, gracias a ti por tu magistral artículo. Gracias a él he podido hacer ya algo muy básico. Había leído decenas de ellos y no acababa de enterarme lo suficiente como para escribir yo algo de código. Yo (y supongo que mucha más gente) estaré esperando leer lo que publiques. Es realmente didáctico y fijo que muy útil. Pienso que el acceso a datos con el nuevo Entity Framework y su uso correcto en el WPF puede ser clave para programar aplicaciones elegantes desde el punto de vista metodológico y ¡mucho más fáciles de mantener! que es precisamente lo que creo que vamos buscando.
Además, todo esto es muy nuevo y con Visual Studio 2010 seguramente que hay cambios importantes en esto. He estado buscando y no hoy documentación sobre el tema.
Aquí te dejo en enlace a una pregunta que hice y la contestación que me han dado. No me acaba de convencer, porque hace falta pedir expresamente que se actualicen los contenidos enlazados:
http://stackoverflow.com/questions/3386950/entity-framework-collections-binding-to-a-listbox-to-reflect-changes
¿Qué opinas?