MVVM - Guía rápida

La forma bien ordenada y quizás la más reutilizable de organizar su código es usar el patrón 'MVVM'. losModel, View, ViewModel (MVVM pattern) se trata de guiarlo en cómo organizar y estructurar su código para escribir aplicaciones mantenibles, probables y extensibles.

Model - Simplemente contiene los datos y no tiene nada que ver con la lógica empresarial.

ViewModel - Actúa como enlace / conexión entre el modelo y la vista y hace que las cosas se vean bonitas.

View - Simplemente contiene los datos formateados y esencialmente delega todo al modelo.

Presentación separada

Para evitar los problemas causados ​​por poner la lógica de la aplicación en código subyacente o XAML, es mejor usar una técnica conocida como presentación separada. Estamos tratando de evitar esto, donde tendremos XAML y código subyacente con el mínimo requerido para trabajar directamente con objetos de la interfaz de usuario. Las clases de interfaz de usuario también contienen código para comportamientos de interacción complejos, lógica de aplicación y todo lo demás, como se muestra en la siguiente figura en el lado izquierdo.

  • Con una presentación separada, la clase de interfaz de usuario es mucho más simple. Tiene el XAML, por supuesto, pero el código detrás hace tan poco como es práctico.

  • La lógica de la aplicación pertenece a una clase separada, que a menudo se denomina modelo.

  • Sin embargo, esta no es toda la historia. Si se detiene aquí, es probable que repita un error muy común que lo llevará por el camino de la locura de vinculación de datos.

  • Muchos desarrolladores intentan usar el enlace de datos para conectar elementos en XAML directamente a propiedades en el modelo.

  • Ahora bien, a veces esto puede estar bien, pero a menudo no lo es. El problema es que el modelo se ocupa completamente de cuestiones de lo que hace la aplicación, y no de cómo el usuario interactúa con la aplicación.

  • La forma en que presenta los datos a menudo es algo diferente de cómo están estructurados internamente.

  • Además, la mayoría de las interfaces de usuario tienen algún estado que no pertenece al modelo de aplicación.

  • Por ejemplo, si su interfaz de usuario utiliza arrastrar y soltar, algo debe realizar un seguimiento de cosas como dónde se encuentra el elemento que se está arrastrando en este momento, cómo debería cambiar su apariencia a medida que se mueve sobre posibles destinos de colocación y cómo esos destinos de colocación también podrían cambiar a medida que el elemento se arrastra sobre ellos.

  • Este tipo de estado puede volverse sorprendentemente complejo y debe ser probado a fondo.

  • En la práctica, normalmente desea que haya otra clase entre la interfaz de usuario y el modelo. Esto tiene dos roles importantes.

    • Primero, adapta su modelo de aplicación para una vista de interfaz de usuario en particular.

    • En segundo lugar, es donde vive cualquier lógica de interacción no trivial, y con eso, me refiero al código necesario para que su interfaz de usuario se comporte de la manera que desee.

El patrón MVVM es, en última instancia, la estructura moderna del patrón MVC, por lo que el objetivo principal sigue siendo el mismo: proporcionar una separación clara entre la lógica del dominio y la capa de presentación. Estas son algunas de las ventajas y desventajas del patrón MVVM.

El beneficio clave es permitir una verdadera separación entre la Vista y el Modelo más allá de lograr la separación y la eficiencia que obtiene al tener eso. Lo que eso significa en términos reales es que cuando su modelo necesita cambiar, se puede cambiar fácilmente sin que la vista tenga que hacerlo y viceversa.

Hay tres cosas clave importantes que se derivan de la aplicación de MVVM, que son las siguientes.

Mantenibilidad

  • Una separación limpia de diferentes tipos de código debería facilitar el acceso a una o varias de esas partes más granulares y enfocadas y realizar cambios sin preocuparse.

  • Eso significa que puede permanecer ágil y seguir avanzando hacia nuevos lanzamientos rápidamente.

Probabilidad

  • Con MVVM, cada pieza de código es más granular y, si se implementa correctamente, sus dependencias externas e internas están en piezas de código separadas de las partes con la lógica central que le gustaría probar.

  • Eso hace que sea mucho más fácil escribir pruebas unitarias contra una lógica central.

  • Asegúrese de que funcione correctamente cuando esté escrito y siga funcionando incluso cuando las cosas cambien en el mantenimiento.

Extensibilidad

  • A veces se superpone con la capacidad de mantenimiento, debido a los límites de separación limpios y las piezas de código más granulares.

  • Tiene más posibilidades de hacer que cualquiera de esas piezas sea más reutilizable.

  • También tiene la capacidad de reemplazar o agregar nuevas piezas de código que hacen cosas similares en los lugares correctos de la arquitectura.

El propósito obvio del patrón MVVM es la abstracción de la Vista, lo que reduce la cantidad de lógica empresarial en el código subyacente. Sin embargo, a continuación se presentan algunas otras ventajas sólidas:

  • ViewModel es más fácil de probar unitariamente que el código subyacente o el código controlado por eventos.
  • Puede probarlo sin la incómoda automatización e interacción de la interfaz de usuario.
  • La capa de presentación y la lógica están débilmente acopladas.

Desventajas

  • Algunas personas piensan que para las IU simples, MVVM puede ser excesivo.
  • De manera similar, en casos más grandes, puede ser difícil diseñar ViewModel.
  • La depuración sería un poco difícil cuando tenemos enlaces de datos complejos.

El patrón MVVM consta de tres partes: modelo, vista y modelo de vista. La mayoría de los desarrolladores al principio están un poco confundidos en cuanto a lo que un modelo, vista y modelo de vista deben o no deben contener y cuáles son las responsabilidades de cada parte.

En este capítulo aprenderemos las responsabilidades de cada parte del patrón MVVM para que pueda comprender claramente qué tipo de código va y dónde. MVVM es realmente una arquitectura en capas para el lado del cliente, como se muestra en la siguiente figura.

  • La capa de presentación está compuesta por las vistas.

  • La capa lógica son los modelos de vista.

  • La capa de presentación es la combinación de los objetos del modelo.

  • Los servicios de cliente que los producen y los conservan dirigen el acceso en una aplicación de dos niveles o mediante llamadas de servicio en y luego a su aplicación.

  • Los servicios de cliente no forman parte oficialmente del patrón MVVM, pero a menudo se utilizan con MVVM para lograr más separaciones y evitar el código duplicado.

Responsabilidades del modelo

En general, el modelo es el más sencillo de entender. Es el modelo de datos del lado del cliente que admite las vistas en la aplicación.

  • Está compuesto por objetos con propiedades y algunas variables para contener datos en memoria.

  • Algunas de esas propiedades pueden hacer referencia a otros objetos del modelo y crear el gráfico de objetos que en su conjunto son los objetos del modelo.

  • Los objetos de modelo deben generar notificaciones de cambio de propiedad, lo que en WPF significa enlace de datos.

  • La última responsabilidad es la validación, que es opcional, pero puede incrustar la información de validación en los objetos del modelo utilizando las funciones de validación de enlace de datos de WPF a través de interfaces como INotifyDataErrorInfo / IDataErrorInfo

Ver responsabilidades

El principal propósito y responsabilidades de las vistas es definir la estructura de lo que el usuario ve en la pantalla. La estructura puede contener partes estáticas y dinámicas.

  • Las partes estáticas son la jerarquía XAML que define los controles y el diseño de los controles de los que se compone una vista.

  • La parte dinámica es como animaciones o cambios de estado que se definen como parte de la Vista.

  • El objetivo principal de MVVM es que no haya ningún código detrás en la vista.

  • Es imposible que no haya ningún código detrás a la vista. A la vista, al menos necesita el constructor y una llamada para inicializar el componente.

  • La idea es que el código lógico de manejo de eventos, acción y manipulación de datos no debería estar en el código subyacente en View.

  • También hay otros tipos de código que deben ir en el código detrás de cualquier código que se requiera para tener una referencia al elemento de la interfaz de usuario que es inherentemente el código de vista.

Responsabilidades de ViewModel

  • ViewModel es el punto principal de la aplicación MVVM. La responsabilidad principal de ViewModel es proporcionar datos a la vista, de modo que la vista pueda poner esos datos en la pantalla.

  • También permite al usuario interactuar con los datos y cambiarlos.

  • La otra responsabilidad clave de un ViewModel es encapsular la lógica de interacción para una vista, pero no significa que toda la lógica de la aplicación deba ir a ViewModel.

  • Debería poder manejar la secuencia apropiada de llamadas para que suceda lo correcto según el usuario o cualquier cambio en la vista.

  • ViewModel también debe administrar cualquier lógica de navegación, como decidir cuándo es el momento de navegar a una vista diferente.

En este capítulo, aprenderemos cómo usar patrones MVVM para una pantalla de entrada simple y la aplicación WPF a la que quizás ya esté acostumbrado.

Echemos un vistazo a un ejemplo simple en el que usaremos el enfoque MVVM.

Step 1 - Cree un nuevo proyecto de aplicación WPF MVVMDemo.

Step 2 - Agregue las tres carpetas (Modelo, ViewModel y Vistas) a su proyecto.

Step 3 - Agregue una clase StudentModel en la carpeta Modelo y pegue el siguiente código en esa clase

using System.ComponentModel;

namespace MVVMDemo.Model {
 
   public class StudentModel {}
	
   public class Student : INotifyPropertyChanged {
      private string firstName; 
      private string lastName;
		
      public string FirstName { 
         get { 
            return firstName; 
         }
			
         set { 
            if (firstName != value) { 
               firstName = value; 
               RaisePropertyChanged("FirstName"); 
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
		
      public string LastName { 
         get {return lastName; } 
			
         set {
            if (lastName != value) { 
               lastName = value;
               RaisePropertyChanged("LastName");
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
		
      public string FullName { 
         get { 
            return firstName + " " + lastName; 
         } 
      }
		
      public event PropertyChangedEventHandler PropertyChanged;
		
      private void RaisePropertyChanged(string property) {
         if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
         } 
      } 
   } 
}

Step 4 - Agregue otra clase StudentViewModel en la carpeta ViewModel y pegue el siguiente código.

using MVVMDemo.Model; 
using System.Collections.ObjectModel;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel { 
	
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
				
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      } 
   } 
}

Step 5 - Agregue un nuevo Control de usuario (WPF) haciendo clic con el botón derecho en la carpeta Vistas y seleccione Agregar> Nuevo elemento ...

Step 6- Haga clic en el botón Agregar. Ahora verá el archivo XAML. Agregue el siguiente código en el archivo StudentView.xaml que contiene diferentes elementos de la interfaz de usuario.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
		
         <ItemsControl ItemsSource = "{Binding Path = Students}">
			
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
                  <StackPanel Orientation = "Horizontal">
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/>
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/>
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/>
								
                  </StackPanel> 
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
				
         </ItemsControl> 
			
      </StackPanel> 
   </Grid> 
	
</UserControl>

Step 7 - Ahora agregue StudentView en su archivo MainPage.xaml usando el siguiente código.

<Window x:Class = "MVVMDemo.MainWindow"
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:local = "clr-namespace:MVVMDemo"
   xmlns:views = "clr-namespace:MVVMDemo.Views"
   mc:Ignorable = "d"
   Title = "MainWindow" Height = "350" Width = "525">
	
   <Grid>
      <views:StudentView x:Name = "StudentViewControl" Loaded = "StudentViewControl_Loaded"/>
   </Grid>
	
</Window>

Step 8 - Aquí está la implementación del evento Loaded en el archivo MainPage.xaml.cs, que actualizará la Vista desde ViewModel.

using System.Windows;

namespace MVVMDemo {

   /// <summary>
      /// Interaction logic for MainWindow.xaml
   /// </summary>
	
   public partial class MainWindow : Window {
	
      public MainWindow() {
         InitializeComponent();
      }
		
      private void StudentViewControl_Loaded(object sender, RoutedEventArgs e) {
         MVVMDemo.ViewModel.StudentViewModel studentViewModelObject = 
            new MVVMDemo.ViewModel.StudentViewModel();
         studentViewModelObject.LoadStudents();
			
         StudentViewControl.DataContext = studentViewModelObject;
      }
   }
}

Step 9 - Cuando se compila y ejecuta el código anterior, recibirá la siguiente salida en su ventana principal.

Le recomendamos que ejecute el ejemplo anterior paso a paso para una mejor comprensión.

En este capítulo, cubriremos diferentes formas en las que puede conectar sus vistas a ViewModel. Primero, echemos un vistazo a la primera construcción de View, donde podemos declararla en XAML. Como hemos visto en el ejemplo del último capítulo donde hemos conectado una vista desde la ventana principal. Ahora veremos otras formas de conectar vistas.

También usaremos el mismo ejemplo en este capítulo. A continuación se muestra la misma implementación de la clase Modelo.

using System.ComponentModel;

namespace MVVMDemo.Model {
 
   public class StudentModel {} 
	
   public class Student : INotifyPropertyChanged { 
      private string firstName; 
      private string lastName;
		
      public string FirstName { 
         get { return firstName; }
			
         set { 
            if (firstName != value) { 
               firstName = value; 
               RaisePropertyChanged("FirstName");
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
	
      public string LastName {
         get { return lastName; } 
			
         set { 
            if (lastName != value) { 
               lastName = value; 
               RaisePropertyChanged("LastName");
               RaisePropertyChanged("FullName"); 
            } 
         } 
      }
	
      public string FullName { 
         get { 
            return firstName + " " + lastName; 
         } 
      }
	
      public event PropertyChangedEventHandler PropertyChanged;
	
      private void RaisePropertyChanged(string property) { 
         if (PropertyChanged != null) { 
            PropertyChanged(this, new PropertyChangedEventArgs(property)); 
         } 
      } 
   }  
}

Aquí está la implementación de la clase ViewModel. Esta vez se llama al método LoadStudents en el constructor predeterminado.

using MVVMDemo.Model; 
using System.Collections.ObjectModel;

namespace MVVMDemo.ViewModel{ 

   public class StudentViewModel { 
	
      public StudentViewModel() { 
         LoadStudents(); 
      } 
		
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
			
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      } 
   } 
}

Ya sea que la vista sea una ventana, un control de usuario o una página, el analizador generalmente funciona de arriba a abajo y de izquierda a derecha. Llama al constructor predeterminado para cada elemento cuando lo encuentra. Hay dos formas de construir una vista. Puedes usar cualquiera en ellos.

  • Ver la primera construcción en XAML
  • Ver la primera construcción en código subyacente

Ver la primera construcción en XAML

Una forma es simplemente agregar su ViewModel como un elemento anidado en el setter para la propiedad DataContext como se muestra en el siguiente código.

<UserControl.DataContext> 
   <viewModel:StudentViewModel/> 
</UserControl.DataContext>

Aquí está el archivo View XAML completo.

<UserControl x:Class="MVVMDemo.Views.StudentView"
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
   xmlns:local = "clr-namespace:MVVMDemo.Views"
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
   mc:Ignorable = "d"
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.DataContext>
      <viewModel:StudentViewModel/>
   </UserControl.DataContext>
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}">
			
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"> 
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                      <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/>
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate>
				
         </ItemsControl> 
      </StackPanel> 
   </Grid> 
	
</UserControl>

Ver la primera construcción en código subyacente

Otra forma es que puede obtener la primera construcción de la Vista simplemente construyendo el modelo de vista usted mismo en el código detrás de su Vista configurando la propiedad DataContext allí con la instancia.

Normalmente, la propiedad DataContext se establece en el método de vista del constructor, pero también puede aplazar la construcción hasta que se active el evento Load de la vista.

using System.Windows.Controls;

namespace MVVMDemo.Views {
 
   /// <summary> 
      /// Interaction logic for StudentView.xaml 
   /// </summary> 
	
   public partial class StudentView : UserControl { 
      public StudentView() { 
         InitializeComponent(); 
         this.DataContext = new MVVMDemo.ViewModel.StudentViewModel(); 
      } 
   } 
}

Una razón para construir el modelo de vista en código subyacente en lugar de XAML es que el constructor del modelo de vista toma parámetros, pero el análisis de XAML solo puede construir elementos si se define en el constructor predeterminado.

Ahora, en este caso, el archivo XAML de View se verá como se muestra en el siguiente código.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" 
   d:DesignWidth = "300">
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}"> 
			
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"<
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/> 
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate>
				
         </ItemsControl> 
      </StackPanel> 
   </Grid>
	
</UserControl>

Puede declarar esta Vista en MainWindow como se muestra en el archivo MainWindow.XAML.

<Window x:Class = "MVVMDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMDemo" 
   xmlns:views = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525"> 
	
   <Grid> 
      <views:StudentView x:Name = "StudentViewControl"/> 
   </Grid> 
	
</Window>

Cuando se compile y ejecute el código anterior, verá el siguiente resultado en su ventana principal.

Le recomendamos que ejecute el ejemplo anterior paso a paso para una mejor comprensión.

En este capítulo, cubriremos cómo conectar ViewModel. Es una continuación del último capítulo en el que discutimos la construcción de Vista primera. Ahora, la siguiente forma de la primera construcción es unmeta-pattern que se conoce como ViewModelLocator. Es un pseudopatrón y se superpone al patrón MVVM.

  • En MVVM, cada vista debe estar conectada a su modelo de vista.

  • ViewModelLocator es un enfoque simple para centralizar el código y desacoplar más la vista.

  • Significa que no tiene que saber explícitamente sobre el tipo ViewModel y cómo construirlo.

  • Hay varios enfoques diferentes para usar ViewModelLocator, pero aquí usamos el más similar al que forma parte del marco PRISM.

ViewModelLocator proporciona una forma estándar, coherente, declarativa y débilmente acoplada de hacer la primera construcción de la vista que automatiza el proceso de conectar ViewModel a la vista. La siguiente figura representa el proceso de alto nivel de ViewModelLocator.

Step 1 - Averigüe qué tipo de vista se está construyendo.

Step 2 - Identifique el modelo de vista para ese tipo de vista en particular.

Step 3 - Construya ese ViewModel.

Step 4 - Establezca Views DataContext en ViewModel.

Para comprender el concepto básico, echemos un vistazo al ejemplo simple de ViewModelLocator continuando con el mismo ejemplo del capítulo anterior. Si observa el archivo StudentView.xaml, verá que hemos conectado estáticamente el ViewModel.

Ahora, como se muestra en el siguiente programa, comente este código XAML y elimine el código de Code-behind.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
	
   <!--<UserControl.DataContext> 
      <viewModel:StudentViewModel/> 
   </UserControl.DataContext>-->
	
   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}">
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal">
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/> 
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
         </ItemsControl> 
      </StackPanel> 
   </Grid>
	
</UserControl>

Ahora creemos una nueva carpeta VML y agreguemos una nueva clase pública ViewModelLocator que contendrá una única propiedad adjunta (propiedad de dependencia) AutoHookedUpViewModel como se muestra en el siguiente código.

public static bool GetAutoHookedUpViewModel(DependencyObject obj) { 
   return (bool)obj.GetValue(AutoHookedUpViewModelProperty); 
}

public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) { 
   obj.SetValue(AutoHookedUpViewModelProperty, value); 
}

// Using a DependencyProperty as the backing store for AutoHookedUpViewModel. 
//This enables animation, styling, binding, etc...
 
public static readonly DependencyProperty AutoHookedUpViewModelProperty =
   DependencyProperty.RegisterAttached("AutoHookedUpViewModel",
   typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false,
   AutoHookedUpViewModelChanged));

Y ahora puede ver una definición básica de propiedad adjunta. Para agregar comportamiento a la propiedad, necesitamos agregar un controlador de eventos modificado para esta propiedad que contiene el proceso automático de conectar ViewModel para View. El código para hacer esto es el siguiente:

private static void AutoHookedUpViewModelChanged(DependencyObject d, 
   DependencyPropertyChangedEventArgs e) { 
   if (DesignerProperties.GetIsInDesignMode(d)) return; 
   var viewType = d.GetType(); 
   string str = viewType.FullName; 
   str = str.Replace(".Views.", ".ViewModel."); 
	
   var viewTypeName = str; 
   var viewModelTypeName = viewTypeName + "Model"; 
   var viewModelType = Type.GetType(viewModelTypeName); 
   var viewModel = Activator.CreateInstance(viewModelType);
   ((FrameworkElement)d).DataContext = viewModel; 
}

A continuación se muestra la implementación completa de la clase ViewModelLocator.

using System; 
using System.ComponentModel; 
using System.Windows;

namespace MVVMDemo.VML { 

   public static class ViewModelLocator { 
	
      public static bool GetAutoHookedUpViewModel(DependencyObject obj) {
         return (bool)obj.GetValue(AutoHookedUpViewModelProperty); 
      }
		
      public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) { 
         obj.SetValue(AutoHookedUpViewModelProperty, value); 
      }
		
      // Using a DependencyProperty as the backing store for AutoHookedUpViewModel. 
		
      //This enables animation, styling, binding, etc...
      public static readonly DependencyProperty AutoHookedUpViewModelProperty =
         DependencyProperty.RegisterAttached("AutoHookedUpViewModel", 
         typeof(bool), typeof(ViewModelLocator), new
         PropertyMetadata(false, AutoHookedUpViewModelChanged));
		
      private static void AutoHookedUpViewModelChanged(DependencyObject d,
         DependencyPropertyChangedEventArgs e) { 
         if (DesignerProperties.GetIsInDesignMode(d)) return; 
         var viewType = d.GetType(); 
			
         string str = viewType.FullName; 
         str = str.Replace(".Views.", ".ViewModel."); 
			
         var viewTypeName = str; 
         var viewModelTypeName = viewTypeName + "Model";
         var viewModelType = Type.GetType(viewModelTypeName); 
         var viewModel = Activator.CreateInstance(viewModelType);
			
        ((FrameworkElement)d).DataContext = viewModel; 
      } 
   } 
}

Lo primero que debemos hacer es agregar un espacio de nombres para que podamos llegar a ese tipo de ViewModelLocator en la raíz de nuestro proyecto. Luego, en el elemento de ruta que es un tipo de vista, agregue la propiedad AutoHookedUpViewModel y configúrelo en verdadero.

xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"

Aquí está la implementación completa del archivo StudentView.xaml.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
   
   <!--<UserControl.DataContext> 
      <viewModel:StudentViewModel/> 
   </UserControl.DataContext>-->

   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}"> 
            <ItemsControl.ItemTemplate> 
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"> 
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/> 
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/>
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
         </ItemsControl> 
      </StackPanel>
   </Grid> 
	
</UserControl>

Cuando se compila y ejecuta el código anterior, verá que ViewModelLocator está conectando el ViewModel para esa vista en particular.

Una cosa clave a tener en cuenta sobre esto es que la vista ya no está acoplada de alguna manera al tipo de su ViewModel o cómo se construye. Todo eso se ha trasladado a la ubicación central dentro de ViewModelLocator.

En este capítulo, aprenderemos cómo el enlace de datos admite el patrón MVVM. El enlace de datos es la característica clave que diferencia MVVM de otros patrones de separación de UI como MVC y MVP.

  • Para el enlace de datos, necesita tener una vista o un conjunto de elementos de la interfaz de usuario construidos, y luego necesita algún otro objeto al que apunten los enlaces.

  • Los elementos de la interfaz de usuario de una vista están vinculados a las propiedades expuestas por ViewModel.

  • El orden en el que se construyen la Vista y el Modelo de Vista depende de la situación, ya que hemos cubierto la Vista primero.

  • Se construyen una vista y un modelo de vista y el DataContext de la vista se establece en ViewModel.

  • Los enlaces pueden ser enlaces de datos OneWay o TwoWay para hacer fluir datos de un lado a otro entre View y ViewModel.

Echemos un vistazo a los enlaces de datos en el mismo ejemplo. A continuación se muestra el código XAML de StudentView.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">

   <!--<UserControl.DataContext> 
      <viewModel:StudentViewModel/> 
   </UserControl.DataContext>--> 

   <Grid> 
      <StackPanel HorizontalAlignment = "Left"> 
         <ItemsControl ItemsSource = "{Binding Path = Students}"> 
            <ItemsControl.ItemTemplate>
               <DataTemplate> 
					
                  <StackPanel Orientation = "Horizontal"> 
                     <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
                        Width = "100" Margin = "3 5 3 5"/>
								
                     <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
                        Width = "100" Margin = "0 5 3 5"/> 
								
                     <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
                        Margin = "0 5 3 5"/> 
								
                  </StackPanel> 
						
               </DataTemplate> 
            </ItemsControl.ItemTemplate> 
         </ItemsControl> 
      </StackPanel> 
   </Grid> 

</UserControl>
  • Si observa el código XAML anterior, verá que ItemsControl está vinculado a la colección Students expuesta por ViewModel.

  • También puede ver que la propiedad del modelo Student también tiene sus propios enlaces individuales, y estos están enlazados a Textboxes y TextBlock.

  • ItemSource de ItemsControl se puede enlazar a la propiedad Students, porque el DataContext general para la vista está establecido en ViewModel.

  • Los enlaces individuales de las propiedades aquí también son enlaces DataContext, pero no están vinculados al ViewModel en sí, debido a la forma en que funciona un ItemSource.

  • Cuando la fuente de un elemento se une a su colección, representa un contenedor para cada elemento en la representación y establece el DataContext de ese contenedor en el elemento. Entonces, el DataContext general para cada cuadro de texto y bloque de texto dentro de una fila será un Estudiante individual en la colección. Y también puede ver que estos enlaces para TextBoxes son enlaces de datos TwoWay y para TextBlock es un enlace de datos OneWay, ya que no puede editar TextBlock.

Cuando vuelva a ejecutar esta aplicación, verá el siguiente resultado.

Ahora cambiemos el texto en el segundo cuadro de texto de la primera fila de Allain a Upston y presione tabulador para perder el enfoque. Verá que el texto TextBlock también se actualiza.

Esto se debe a que los enlaces de los TextBoxes se establecen en TwoWay y también actualiza el Modelo, y desde el modelo nuevamente se actualiza el TextBlock.

Una plantilla describe el aspecto general y el aspecto visual del control. Para cada control hay una plantilla predeterminada asociada que le da la apariencia de ese control. En la aplicación WPF, puede crear fácilmente sus propias plantillas cuando desee personalizar el comportamiento visual y la apariencia visual de un control. La conectividad entre la lógica y la plantilla se puede lograr mediante el enlace de datos.

En MVVM, hay otra forma primaria que se conoce como primera construcción de ViewModel.

  • El primer enfoque de construcción de ViewModel aprovecha las capacidades de las plantillas de datos implícitas en WPF.

  • Las plantillas de datos implícitas pueden seleccionar automáticamente una plantilla adecuada del diccionario de recursos actual para un elemento que utiliza el enlace de datos. Lo hacen en función del tipo de objeto de datos que representa el enlace de datos. Primero, debe tener algún elemento que se vincule a un objeto de datos.

Echemos un vistazo a nuestro ejemplo simple nuevamente en el que comprenderá cómo puede ver el modelo primero aprovechando las plantillas de datos, específicamente las plantillas de datos implícitas. Aquí está la implementación de nuestra clase StudentViewModel.

using MVVMDemo.Model; 
using System.Collections.ObjectModel;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel {
	
      public StudentViewModel() { 
         LoadStudents(); 
      } 
		
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
			
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      } 
   } 
}

Puede ver que el ViewModel anterior no ha cambiado. Continuaremos con el mismo ejemplo del capítulo anterior. Esta clase de ViewModel solo expone la propiedad de colección de Estudiantes y la llena durante la construcción. Vayamos al archivo StudentView.xaml, eliminemos la implementación existente y definamos una plantilla de datos en la sección Recursos.

<UserControl.Resources> 
   <DataTemplate x:Key = "studentsTemplate">
	
      <StackPanel Orientation = "Horizontal"> 
         <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
            Width = "100" Margin = "3 5 3 5"/> 
				
         <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
            Width = "100" Margin = "0 5 3 5"/> 
				
         <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
            Margin = "0 5 3 5"/> 
      </StackPanel> 
		
   </DataTemplate> 
</UserControl.Resources>

Ahora agregue un cuadro de lista y enlace de datos ese cuadro de lista a la propiedad de Estudiantes como se muestra en el siguiente código.

<ListBox ItemsSource = "{Binding Students}" ItemTemplate = "{StaticResource studentsTemplate}"/>

En la sección de Recursos, el DataTemplate tiene una clave de StudentsTemplate y luego, para usar esa plantilla, necesitamos usar la propiedad ItemTemplate de un ListBox. Entonces, ahora puede ver que le indicamos al cuadro de lista que use esa plantilla específica para representar a esos Estudiantes. A continuación se muestra la implementación completa del archivo StudentView.xaml.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.Resources> 
      <DataTemplate x:Key = "studentsTemplate"> 
		
         <StackPanel Orientation = "Horizontal"> 
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
					
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>
	
   <Grid> 
      <ListBox 
         ItemsSource = "{Binding Students}" 
         ItemTemplate = "{StaticResource studentsTemplate}"/> 
   </Grid>
	
</UserControl>

Cuando se compile y ejecute el código anterior, verá la siguiente ventana, que contiene un ListBox. Cada ListBoxItem contiene los datos del objeto de la clase Student que se muestran en TextBlock y Text boxes.

Para hacer de esto una plantilla implícita, necesitamos eliminar la propiedad ItemTemplate de un cuadro de lista y agregar una propiedad DataType en nuestra definición de plantilla como se muestra en el siguiente código.

<UserControl.Resources> 
   <DataTemplate DataType = "{x:Type data:Student}">
	
      <StackPanel Orientation = "Horizontal"> 
         <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
            Width = "100" Margin = "3 5 3 5"/> 
				
         <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
            Width = "100" Margin = "0 5 3 5"/> 
				
         <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
            Margin = "0 5 3 5"/> 
				
      </StackPanel> 
		
   </DataTemplate> 
</UserControl.Resources>
 
<Grid> 
   <ListBox ItemsSource = "{Binding Students}"/> 
</Grid>

En DataTemplate, la extensión de marcado x: Type es muy importante, que es como un tipo de operador en XAML. Entonces, básicamente necesitamos apuntar al tipo de datos Student que está en el espacio de nombres MVVMDemo.Model. A continuación se muestra el archivo XAML completo actualizado.

<UserControl x:Class="MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:data = "clr-namespace:MVVMDemo.Model" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.Resources> 
      <DataTemplate DataType = "{x:Type data:Student}"> 
		
         <StackPanel Orientation = "Horizontal"> 
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
					
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>
	
   <Grid>
      <ListBox ItemsSource = "{Binding Students}"/> 
   </Grid> 
	
</UserControl>

Cuando vuelva a ejecutar esta aplicación, seguirá obteniendo la misma representación de la plantilla Estudiantes con datos porque asigna automáticamente el tipo de objeto que se representa al ubicar la plantilla de datos adecuada.

Le recomendamos que ejecute el ejemplo anterior en un método paso a paso para una mejor comprensión.

En este capítulo, aprenderemos cómo agregar interactividad a las aplicaciones MVVM y cómo llamar a la lógica de forma limpia. También verá que todo esto se hace manteniendo el acoplamiento flojo y la buena estructuración que es el corazón del patrón MVVM. Para comprender todo esto, primero aprendamos sobre los comandos.

Comunicación View / ViewModel a través de comandos

El patrón de comando ha sido bien documentado y utiliza con frecuencia el patrón de diseño durante un par de décadas. En este patrón hay dos actores principales, el invocador y el receptor.

Invocador

  • El invocador es un fragmento de código que puede ejecutar alguna lógica imperativa.

  • Normalmente, es un elemento de la interfaz de usuario con el que el usuario interactúa, en el contexto de un marco de interfaz de usuario.

  • Podría ser simplemente otro fragmento de código lógico en otro lugar de la aplicación.

Receptor

  • El receptor es la lógica que debe ejecutarse cuando el invocador dispara.

  • En el contexto de MVVM, el receptor suele ser un método en su ViewModel que debe llamarse.

Entre estos dos, tienes una capa de obstrucción, lo que implica que el invocador y el receptor no tienen que conocerse explícitamente. Esto se representa típicamente como una abstracción de interfaz expuesta al invocador y una implementación concreta de esa interfaz es capaz de llamar al receptor.

Echemos un vistazo a un ejemplo simple en el que aprenderá los comandos y cómo usarlos para comunicarse entre View y ViewModel. En este capítulo, continuaremos con el mismo ejemplo del capítulo anterior.

En el archivo StudentView.xaml, tenemos un ListBox que conecta los datos del estudiante desde un ViewModel. Ahora agreguemos un botón para eliminar un alumno del ListBox.

Lo importante es que trabajar con comandos en el botón es muy fácil porque tienen una propiedad de comando para conectarse a un ICommand.

Entonces, podemos exponer una propiedad en nuestro ViewModel que tiene un ICommand y se une a él desde la propiedad de comando del botón como se muestra en el siguiente código.

<Button Content = "Delete" 
   Command = "{Binding DeleteCommand}" 
   HorizontalAlignment = "Left" 
   VerticalAlignment = "Top" 
   Width = "75" />

Agreguemos una nueva clase en su proyecto, que implementará la interfaz ICommand. A continuación se muestra la implementación de la interfaz ICommand.

using System; 
using System.Windows.Input;

namespace MVVMDemo { 

   public class MyICommand : ICommand { 
      Action _TargetExecuteMethod; 
      Func<bool> _TargetCanExecuteMethod;
		
      public MyICommand(Action executeMethod) {
         _TargetExecuteMethod = executeMethod; 
      }
		
      public MyICommand(Action executeMethod, Func<bool> canExecuteMethod){ 
         _TargetExecuteMethod = executeMethod;
         _TargetCanExecuteMethod = canExecuteMethod; 
      }
		
      public void RaiseCanExecuteChanged() { 
         CanExecuteChanged(this, EventArgs.Empty); 
      }
		
      bool ICommand.CanExecute(object parameter) { 
		
         if (_TargetCanExecuteMethod != null) { 
            return _TargetCanExecuteMethod(); 
         } 
			
         if (_TargetExecuteMethod != null) { 
            return true; 
         } 
			
         return false; 
      }
		
      // Beware - should use weak references if command instance lifetime 
         is longer than lifetime of UI objects that get hooked up to command 
			
      // Prism commands solve this in their implementation 
      public event EventHandler CanExecuteChanged = delegate { };
		
      void ICommand.Execute(object parameter) { 
         if (_TargetExecuteMethod != null) {
            _TargetExecuteMethod(); 
         } 
      } 
   } 
}

Como puede ver, esta es una implementación de delegación simple de ICommand donde tenemos dos delegados, uno para executeMethod y otro para canExecuteMethod, que se pueden pasar en la construcción.

En la implementación anterior, hay dos constructores sobrecargados, uno solo para executeMethod y otro para executeMethod y I canExecuteMethod.

Agreguemos una propiedad de tipo MyICommand en la clase StudentView Model. Ahora necesitamos construir una instancia en StudentViewModel. Usaremos el constructor sobrecargado de MyICommand que toma dos parámetros.

public MyICommand DeleteCommand { get; set;} 

public StudentViewModel() { 
   LoadStudents(); 
   DeleteCommand = new MyICommand(OnDelete, CanDelete); 
}

Ahora agregue la implementación de los métodos OnDelete y CanDelete.

private void OnDelete() { 
   Students.Remove(SelectedStudent); 
}

private bool CanDelete() { 
   return SelectedStudent != null; 
}

También necesitamos agregar un nuevo SelectedStudent para que el usuario pueda eliminar el elemento seleccionado de ListBox.

private Student _selectedStudent;
 
public Student SelectedStudent { 
   get { 
      return _selectedStudent; 
   } 
	
   set { 
      _selectedStudent = value;
      DeleteCommand.RaiseCanExecuteChanged(); 
   } 
}

A continuación se muestra la implementación completa de la clase ViewModel.

using MVVMDemo.Model; 

using System.Collections.ObjectModel; 
using System.Windows.Input; 
using System;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel { 
	
      public MyICommand DeleteCommand { get; set;} 
		
      public StudentViewModel() { 
         LoadStudents(); 
         DeleteCommand = new MyICommand(OnDelete, CanDelete); 
      }
		
      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }
		
      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();
			
         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" }); 
			
         Students = students; 
      }
		
      private Student _selectedStudent; 
		
      public Student SelectedStudent { 
         get {
            return _selectedStudent; 
         } 
			
         set { 
            _selectedStudent = value;
            DeleteCommand.RaiseCanExecuteChanged(); 
         } 
      }
		
      private void OnDelete() { 
         Students.Remove(SelectedStudent); 
      }
		
      private bool CanDelete() { 
         return SelectedStudent != null; 
      }
   } 
}

En StudentView.xaml, necesitamos agregar la propiedad SelectedItem en un ListBox que se vinculará a la propiedad SelectStudent.

<ListBox ItemsSource = "{Binding Students}" SelectedItem = "{Binding SelectedStudent}"/>

A continuación se muestra el archivo xaml completo.

<UserControl x:Class = "MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:data = "clr-namespace:MVVMDemo.Model" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d"
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <UserControl.Resources> 
      <DataTemplate DataType = "{x:Type data:Student}"> 
		
         <StackPanel Orientation = "Horizontal"> 
			
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
					
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>
	
   <Grid> 
      <StackPanel Orientation = "Horizontal"> 
         <ListBox ItemsSource = "{Binding Students}" 
            SelectedItem = "{Binding SelectedStudent}"/> 
				
         <Button Content = "Delete" 
            Command = "{Binding DeleteCommand}"
            HorizontalAlignment = "Left" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </StackPanel> 
   </Grid>
	
</UserControl>

Cuando se compile y ejecute el código anterior, verá la siguiente ventana.

Puede ver que el botón Eliminar está desactivado. Se habilitará cuando seleccione cualquier elemento.

Cuando selecciona cualquier elemento y presiona eliminar. Verá que la lista de elementos seleccionados se elimina y el botón Eliminar nuevamente se desactiva.

Le recomendamos que ejecute el ejemplo anterior paso a paso para una mejor comprensión.

Al crear aplicaciones MVVM, normalmente descompone pantallas complejas de información en un conjunto de vistas principales y secundarias, donde las vistas secundarias se encuentran dentro de las vistas principales en paneles o controles de contenedor, y forman una jerarquía de uso.

  • Después de descomponer las vistas complejas, no significa que todos y cada uno de los elementos del contenido secundario que separe en su propio archivo XAML necesariamente deben ser una vista MVVM.

  • El fragmento de contenido solo proporciona la estructura para representar algo en la pantalla y no admite ninguna entrada o manipulación por parte del usuario para ese contenido.

  • Puede que no necesite un ViewModel separado, pero podría ser solo un fragmento XAML que se procesa según las propiedades expuestas por el ViewModel principal.

  • Finalmente, si tiene una jerarquía de Vistas y ViewModels, el ViewModel principal puede convertirse en un centro de comunicaciones para que cada ViewModel secundario pueda permanecer desacoplado de los otros ViewModels secundarios y de su principal tanto como sea posible.

Echemos un vistazo a un ejemplo en el que definiremos una jerarquía simple entre diferentes vistas. Crear un nuevo proyecto de aplicación WPFMVVMHierarchiesDemo

Step 1 - Agregue las tres carpetas (Modelo, ViewModel y Vistas) a su proyecto.

Step 2 - Agregue las clases Customer y Order en la carpeta Model, CustomerListView y OrderView en la carpeta Views, y CustomerListViewModel y OrderViewModel en la carpeta ViewModel como se muestra en la siguiente imagen.

Step 3- Agregue bloques de texto tanto en CustomerListView como en OrderView. Aquí está el archivo CustomerListView.xaml.

<UserControl x:Class="MVVMHierarchiesDemo.Views.CustomerListView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <TextBlock Text = "Customer List View"/> 
   </Grid> 
	
</UserControl>

A continuación se muestra el archivo OrderView.xaml.

<UserControl x:Class = "MVVMHierarchiesDemo.Views.OrderView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d ="http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <TextBlock Text = "Order View"/> 
   </Grid> 
	
</UserControl>

Ahora necesitamos algo para alojar estas vistas y un buen lugar para eso en nuestra ventana principal porque es una aplicación simple. Necesitamos un control de contenedor en el que podamos colocar nuestras vistas y cambiarlas en forma de navegación. Para este propósito, necesitamos agregar ContentControl en nuestro archivo MainWindow.xaml y usaremos su propiedad de contenido y la vincularemos a una referencia de ViewModel.

Ahora defina las plantillas de datos para cada vista en un diccionario de recursos. A continuación se muestra el archivo MainWindow.xaml. Observe cómo cada plantilla de datos asigna un tipo de datos (el tipo ViewModel) a una Vista correspondiente.

<Window x:Class = "MVVMHierarchiesDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo" 
   xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views" 
   xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525"> 
   
   <Window.DataContext> 
      <local:MainWindowViewModel/> 
   </Window.DataContext>
	
   <Window.Resources> 
      <DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
         <views:CustomerListView/> 
      </DataTemplate>
		
      <DataTemplate DataType = "{x:Type viewModels:OrderViewModel}"> 
         <views:OrderView/> 
      </DataTemplate> 
   </Window.Resources>
	
   <Grid> 
      <ContentControl Content = "{Binding CurrentView}"/> 
   </Grid> 
	
</Window>

Cada vez que el modelo de vista actual se establece en una instancia de CustomerListViewModel, representará un CustomerListView con el ViewModel conectado. Es una orden ViewModel, renderizará OrderView y así sucesivamente.

Ahora necesitamos un ViewModel que tenga una propiedad CurrentViewModel y algo de lógica y comandos para poder cambiar la referencia actual de ViewModel dentro de la propiedad.

Creemos un ViewModel para esta MainWindow llamado MainWindowViewModel. Podemos simplemente crear una instancia de nuestro ViewModel desde XAML y usarla para establecer la propiedad DataContext de la ventana. Para esto, necesitamos crear una clase base para encapsular la implementación de INotifyPropertyChanged para nuestros ViewModels.

La idea principal detrás de esta clase es encapsular la implementación INotifyPropertyChanged y proporcionar métodos auxiliares a la clase derivada para que puedan desencadenar fácilmente las notificaciones apropiadas. A continuación se muestra la implementación de la clase BindableBase.

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Linq; 
using System.Runtime.CompilerServices; 
using System.Text; 
using System.Threading.Tasks;

namespace MVVMHierarchiesDemo { 

   class BindableBase : INotifyPropertyChanged { 
	
      protected virtual void SetProperty<T>(ref T member, T val,
         [CallerMemberName] string propertyName = null) { 
            if (object.Equals(member, val)) return;
				
            member = val;
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
      }
			
      protected virtual void OnPropertyChanged(string propertyName) { 
         PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
      } 
		
      public event PropertyChangedEventHandler PropertyChanged = delegate { }; 
   } 
}

Ahora es el momento de comenzar a cambiar de vista usando nuestra propiedad CurrentViewModel. Solo necesitamos alguna forma de controlar la configuración de esta propiedad. Y lo haremos de modo que el usuario final pueda ordenar que vaya a la lista de clientes oa la vista de pedidos. Primero agregue una nueva clase en su proyecto que implementará la interfaz ICommand. A continuación se muestra la implementación de la interfaz ICommand.

using System; 
using System.Windows.Input;

namespace MVVMHierarchiesDemo { 

   public class MyICommand<T> : ICommand { 
	
      Action<T> _TargetExecuteMethod; 
      Func<T, bool> _TargetCanExecuteMethod;
		
      public MyICommand(Action<T> executeMethod) {
         _TargetExecuteMethod = executeMethod; 
      }
		
      public MyICommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) {
         _TargetExecuteMethod = executeMethod;
         _TargetCanExecuteMethod = canExecuteMethod; 
      }

      public void RaiseCanExecuteChanged() {
         CanExecuteChanged(this, EventArgs.Empty); 
      } 
		
      #region ICommand Members

      bool ICommand.CanExecute(object parameter) { 
		
         if (_TargetCanExecuteMethod != null) { 
            T tparm = (T)parameter; 
            return _TargetCanExecuteMethod(tparm); 
         } 
			
         if (_TargetExecuteMethod != null) { 
            return true; 
         } 
			
         return false; 
      }
		
      // Beware - should use weak references if command instance lifetime is
         longer than lifetime of UI objects that get hooked up to command 
			
      // Prism commands solve this in their implementation 

      public event EventHandler CanExecuteChanged = delegate { };
	
      void ICommand.Execute(object parameter) { 
         if (_TargetExecuteMethod != null) {
            _TargetExecuteMethod((T)parameter); 
         } 
      } 
		
      #endregion 
   } 
}

Ahora necesitamos configurar una navegación de nivel superior para estos en ViewModels y la lógica para ese cambio debe pertenecer dentro de MainWindowViewModel. Para esto, usaremos un método llamado navegar que toma un destino de cadena y devuelve la propiedad CurrentViewModel.

private void OnNav(string destination) {
 
   switch (destination) { 
      case "orders": 
         CurrentViewModel = orderViewModelModel; 
      break; 
      case "customers": 
      default: 
         CurrentViewModel = custListViewModel; 
      break; 
   } 
}

Para navegar por estas diferentes Vistas, necesitamos agregar dos botones en nuestro archivo MainWindow.xaml. A continuación se muestra la implementación completa del archivo XAML.

<Window x:Class = "MVVMHierarchiesDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo" 
   xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views" 
   xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525">

   <Window.DataContext> 
      <local:MainWindowViewModel/> 
   </Window.DataContext>
	
   <Window.Resources> 
      <DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
         <views:CustomerListView/> 
      </DataTemplate> 
		
      <DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
         <views:OrderView/> 
      </DataTemplate> 
   </Window.Resources>
	
   <Grid>
      <Grid.RowDefinitions> 
         <RowDefinition Height = "Auto" /> 
         <RowDefinition Height = "*" /> 
      </Grid.RowDefinitions> 
	
      <Grid x:Name = "NavBar"> 
         <Grid.ColumnDefinitions> 
            <ColumnDefinition Width = "*" /> 
            <ColumnDefinition Width = "*" /> 
            <ColumnDefinition Width = "*" /> 
         </Grid.ColumnDefinitions> 
	
         <Button Content = "Customers" 
            Command = "{Binding NavCommand}"
            CommandParameter = "customers" 
            Grid.Column = "0" />
				
         <Button Content = "Order" 
            Command = "{Binding NavCommand}" 
            CommandParameter = "orders" 
            Grid.Column = "2" />
      </Grid> 
	
      <Grid x:Name = "MainContent" Grid.Row = "1"> 
         <ContentControl Content = "{Binding CurrentViewModel}" /> 
      </Grid> 
		
   </Grid> 
	
</Window>

A continuación se muestra la implementación completa de MainWindowViewModel.

using MVVMHierarchiesDemo.ViewModel; 
using MVVMHierarchiesDemo.Views; 

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

namespace MVVMHierarchiesDemo {
 
   class MainWindowViewModel : BindableBase {
	
      public MainWindowViewModel() { 
         NavCommand = new MyICommand<string>(OnNav); 
      } 
		
      private CustomerListViewModel custListViewModel = new CustomerListViewModel(); 
		
      private OrderViewModel orderViewModelModel = new OrderViewModel();
		
      private BindableBase _CurrentViewModel; 
		
      public BindableBase CurrentViewModel { 
         get {return _CurrentViewModel;} 
         set {SetProperty(ref _CurrentViewModel, value);} 
      }
		
      public MyICommand<string> NavCommand { get; private set; }

      private void OnNav(string destination) {
		
         switch (destination) { 
            case "orders": 
               CurrentViewModel = orderViewModelModel; 
               break; 
            case "customers": 
            default: 
               CurrentViewModel = custListViewModel; 
               break; 
         } 
      } 
   } 
}

Derive todos sus ViewModels de la clase BindableBase. Cuando se compile y ejecute el código anterior, verá el siguiente resultado.

Como puede ver, hemos agregado solo dos botones y un CurrentViewModel en nuestra MainWindow. Si hace clic en cualquier botón, navegará a esa Vista en particular. Hagamos clic en el botón Clientes y verá que se muestra CustomerListView.

Le recomendamos que ejecute el ejemplo anterior paso a paso para una mejor comprensión.

En este capítulo, aprenderemos sobre validaciones. También veremos una forma limpia de realizar la validación con lo que los enlaces de WPF ya admiten, pero vinculándolos a componentes MVVM.

Validación en MVVM

  • Cuando su aplicación comience a aceptar la entrada de datos de los usuarios finales, debe considerar validar esa entrada.

  • Asegúrese de que se ajuste a sus requisitos generales.

  • WPF tiene algunas compilaciones y características excelentes en el sistema de enlace para validar la entrada y aún puede aprovechar todas esas características al hacer MVVM.

  • Tenga en cuenta que la lógica que respalda su validación y define qué reglas existen para qué propiedades deben ser parte del Modelo o ViewModel, no la Vista en sí.

Todavía puede usar todas las formas de expresar la validación que son compatibles con el enlace de datos de WPF, incluidas:

  • Se establece el lanzamiento de excepciones en una propiedad.
  • Implementación de la interfaz IDataErrorInfo.
  • Implementando INotifyDataErrorInfo.
  • Utilice las reglas de validación de WPF.

En general, se recomienda INotifyDataErrorInfo y se introdujo en WPF .net 4.5 y admite la consulta del objeto en busca de errores asociados con las propiedades y también corrige un par de deficiencias con todas las demás opciones. Específicamente, permite la validación asincrónica. Permite que las propiedades tengan más de un error asociado.

Agregar validación

Echemos un vistazo a un ejemplo en el que agregaremos soporte de validación a nuestra vista de entrada, y en aplicaciones grandes probablemente necesitará esto en varios lugares de su aplicación. A veces, en Vistas, a veces en ViewModels y, a veces, en estos objetos auxiliares hay envoltorios alrededor de los objetos del modelo.

Es una buena práctica colocar el soporte de validación en una clase base común que luego puede heredar de diferentes escenarios.

La clase base admitirá INotifyDataErrorInfo para que la validación se active cuando cambien las propiedades.

Cree y agregue una nueva clase llamada ValidatableBindableBase. Como ya tenemos una clase base para el manejo de cambios de propiedad, derivemos la clase base de ella y también implementemos la interfaz INotifyDataErrorInfo.

A continuación se muestra la implementación de la clase ValidatableBindableBase.

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 

//using System.ComponentModel.DataAnnotations; 
using System.Linq; 
using System.Runtime.CompilerServices; 
using System.Text;
using System.Threading.Tasks; 
using System.Windows.Controls;

namespace MVVMHierarchiesDemo { 

   public class ValidatableBindableBase : BindableBase, INotifyDataErrorInfo { 
      private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

      public event EventHandler<DataErrorsChangedEventArgs> 
         ErrorsChanged = delegate { };

      public System.Collections.IEnumerable GetErrors(string propertyName) {
		
         if (_errors.ContainsKey(propertyName)) 
            return _errors[propertyName]; 
         else 
            return null; 
      }
      
      public bool HasErrors { 
         get { return _errors.Count > 0; } 
      }
		
      protected override void SetProperty<T>(ref T member, T val, 
         [CallerMemberName] string propertyName = null) {
		
         base.SetProperty<T>(ref member, val, propertyName);
         ValidateProperty(propertyName, val);
      }
		
      private void ValidateProperty<T>(string propertyName, T value) {
         var results = new List<ValidationResult>();
			
         //ValidationContext context = new ValidationContext(this); 
         //context.MemberName = propertyName;
         //Validator.TryValidateProperty(value, context, results);

         if (results.Any()) {
            //_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList(); 
         } else { 
            _errors.Remove(propertyName); 
         }
			
         ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName)); 
      } 
   } 
}

Ahora agregue AddEditCustomerView y AddEditCustomerViewModel en las carpetas respectivas. A continuación se muestra el código de AddEditCustomerView.xaml.

<UserControl x:Class = "MVVMHierarchiesDemo.Views.AddEditCustomerView"
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">
	
   <Grid> 
      <Grid.RowDefinitions> 
         <RowDefinition Height = "Auto" /> 
         <RowDefinition Height = "Auto" />
      </Grid.RowDefinitions>
		
      <Grid x:Name = "grid1" 
         HorizontalAlignment = "Left" 
         DataContext = "{Binding Customer}" 
         Margin = "10,10,0,0" 
         VerticalAlignment = "Top">
			
         <Grid.ColumnDefinitions> 
            <ColumnDefinition Width = "Auto" /> 
            <ColumnDefinition Width = "Auto" /> 
         </Grid.ColumnDefinitions>
		
         <Grid.RowDefinitions> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
            <RowDefinition Height = "Auto" /> 
         </Grid.RowDefinitions>
		
         <Label Content = "First Name:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "0" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "firstNameTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "0" 
            Text = "{Binding FirstName, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Last Name:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "1" 
            VerticalAlignment = "Center" /> 
			
         <TextBox x:Name = "lastNameTextBox"
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "1" 
            Text = "{Binding LastName, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Email:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "2" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "emailTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "2" 
            Text = "{Binding Email, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
         <Label Content = "Phone:" 
            Grid.Column = "0" 
            HorizontalAlignment = "Left" 
            Margin = "3" 
            Grid.Row = "3" 
            VerticalAlignment = "Center" />
			
         <TextBox x:Name = "phoneTextBox" 
            Grid.Column = "1" 
            HorizontalAlignment = "Left" 
            Height = "23" 
            Margin = "3" 
            Grid.Row = "3" 
            Text = "{Binding Phone, ValidatesOnNotifyDataErrors = True}"
            VerticalAlignment = "Center" 
            Width = "120" />
			
      </Grid> 

      <Grid Grid.Row = "1"> 
         <Button Content = "Save" 
            Command = "{Binding SaveCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "25,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" />
		
         <Button Content = "Add" 
            Command = "{Binding SaveCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "25,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
		
         <Button Content = "Cancel" 
            Command = "{Binding CancelCommand}" 
            HorizontalAlignment = "Left" 
            Margin = "150,5,0,0" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </Grid>
		
   </Grid> 
	
</UserControl>

A continuación se muestra la implementación de AddEditCustomerViewModel.

using MVVMHierarchiesDemo.Model;

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

namespace MVVMHierarchiesDemo.ViewModel { 

   class AddEditCustomerViewModel : BindableBase { 
	
      public AddEditCustomerViewModel() {
         CancelCommand = new MyIcommand(OnCancel); 
         SaveCommand = new MyIcommand(OnSave, CanSave); 
      } 
		
      private bool _EditMode; 
		
      public bool EditMode { 
         get { return _EditMode; } 
         set { SetProperty(ref _EditMode, value);} 
      }
		
      private SimpleEditableCustomer _Customer;
		
      public SimpleEditableCustomer Customer { 
         get { return _Customer; } 
         set { SetProperty(ref _Customer, value);} 
      }

      private Customer _editingCustomer = null;
		
      public void SetCustomer(Customer cust) {
         _editingCustomer = cust; 
			
         if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged; 
         Customer = new SimpleEditableCustomer();
         Customer.ErrorsChanged += RaiseCanExecuteChanged;
         CopyCustomer(cust, Customer); 
      }
		
      private void RaiseCanExecuteChanged(object sender, EventArgs e) { 
         SaveCommand.RaiseCanExecuteChanged(); 
      }

      public MyIcommand CancelCommand { get; private set; }
      public MyIcommand SaveCommand { get; private set; }

      public event Action Done = delegate { };
		
      private void OnCancel() { 
         Done(); 
      }

      private async void OnSave() { 
         Done(); 
      }
		
      private bool CanSave() { 
         return !Customer.HasErrors; 
      }  
   } 
}

A continuación se muestra la implementación de la clase SimpleEditableCustomer.

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

namespace MVVMHierarchiesDemo.Model { 

   public class SimpleEditableCustomer : ValidatableBindableBase { 
      private Guid _id; 
		
      public Guid Id { 
         get { return _id; } 
         set { SetProperty(ref _id, value); } 
      }
		
      private string _firstName; 
      [Required]
		
      public string FirstName { 
         get { return _firstName; } 
         set { SetProperty(ref _firstName, value); } 
      }
		
      private string _lastName; 
      [Required] 
		
      public string LastName {  
         get { return _lastName; } 
         set { SetProperty(ref _lastName, value); } 
      }
		
      private string _email; 
      [EmailAddress] 
		
      public string Email {
         get { return _email; } 
         set { SetProperty(ref _email, value); } 
      }
		
      private string _phone; 
      [Phone] 
		
      public string Phone { 
         get { return _phone; } 
         set { SetProperty(ref _phone, value); } 
      } 
   } 
}

Cuando se compile y ejecute el código anterior, verá la siguiente ventana.

Cuando presione el botón Agregar cliente, verá la siguiente vista. Cuando el usuario deja un campo vacío, se resaltará y el botón Guardar se desactivará.

En este capítulo, discutiremos brevemente sobre la inyección de dependencia. Ya hemos cubierto el enlace de datos que desacopla las Vistas y los ViewModels entre sí, lo que les permite comunicarse sin saber explícitamente qué está sucediendo en el otro extremo de la comunicación.

Ahora necesitamos algo similar para desacoplar nuestro ViewModel de los servicios al cliente.

En los primeros días de la programación orientada a objetos, los desarrolladores se han enfrentado al problema de crear y recuperar instancias de clases en aplicaciones. Se han propuesto varias soluciones para este problema.

Durante los últimos años, la inyección de dependencia y la inversión de control (IoC) han ganado popularidad entre los desarrolladores y han tenido prioridad sobre algunas soluciones más antiguas, como el patrón Singleton.

Inyección de dependencias / Contenedores de IoC

IoC y la inyección de dependencia son dos patrones de diseño que están estrechamente relacionados y el contenedor es básicamente un fragmento de código de infraestructura que realiza ambos patrones por usted.

  • El patrón de IoC se trata de delegar la responsabilidad de la construcción y el patrón de inyección de dependencia se trata de proporcionar dependencias a un objeto que ya se ha construido.

  • Ambos pueden tratarse como un enfoque de construcción de dos fases. Cuando usa un contenedor, el contenedor asume varias responsabilidades que son las siguientes:

    • Construye un objeto cuando se le pregunta.
    • El contenedor determinará de qué depende ese objeto.
    • Construyendo esas dependencias.
    • Inyectándolos en el objeto que se está construyendo.
    • Hacer proceso de forma recursiva.

Echemos un vistazo a cómo podemos usar la inyección de dependencia para romper el desacoplamiento entre ViewModels y los servicios del cliente. Conectaremos el formulario AddEditCustomerViewModel de manejo de guardado utilizando la inyección de dependencia relacionada con eso.

Primero necesitamos crear una nueva interfaz en nuestro proyecto en la carpeta Servicios. Si no tiene una carpeta de servicios en su proyecto, primero créela y agregue la siguiente interfaz en la carpeta Servicios.

using MVVMHierarchiesDemo.Model; 

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

namespace MVVMHierarchiesDemo.Services { 

   public interface ICustomersRepository { 
      Task<List<Customer>> GetCustomersAsync(); 
      Task<Customer> GetCustomerAsync(Guid id); 
      Task<Customer> AddCustomerAsync(Customer customer); 
      Task<Customer> UpdateCustomerAsync(Customer customer); 
      Task DeleteCustomerAsync(Guid customerId); 
   } 
}

A continuación se muestra la implementación de ICustomersRepository.

using MVVMHierarchiesDemo.Model; 

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

namespace MVVMHierarchiesDemo.Services { 

   public class CustomersRepository : ICustomersRepository {
      ZzaDbContext _context = new ZzaDbContext();

      public Task<List<Customer>> GetCustomersAsync() { 
         return _context.Customers.ToListAsync(); 
      }

      public Task<Customer> GetCustomerAsync(Guid id) { 
         return _context.Customers.FirstOrDefaultAsync(c => c.Id == id); 
      }
		
      public async Task<Customer> AddCustomerAsync(Customer customer){ 
         _context.Customers.Add(customer); 
         await _context.SaveChangesAsync(); 
         return customer;
      }

      public async Task<Customer> UpdateCustomerAsync(Customer customer) {
		
         if (!_context.Customers.Local.Any(c => c.Id == customer.Id)) { 
            _context.Customers.Attach(customer); 
         } 
			
         _context.Entry(customer).State = EntityState.Modified;
         await _context.SaveChangesAsync(); 
         return customer;
			
      }

      public async Task DeleteCustomerAsync(Guid customerId) {
         var customer = _context.Customers.FirstOrDefault(c => c.Id == customerId); 
			
         if (customer != null) {
            _context.Customers.Remove(customer); 
         }
			
         await _context.SaveChangesAsync(); 
      } 
   } 
}

La forma más sencilla de realizar el manejo de guardado es agregar una nueva instancia de ICustomersRepository en AddEditCustomerViewModel y sobrecargar el constructor AddEditCustomerViewModel y CustomerListViewModel.

private ICustomersRepository _repo; 

public AddEditCustomerViewModel(ICustomersRepository repo) { 
   _repo = repo; 
   CancelCommand = new MyIcommand(OnCancel);
   SaveCommand = new MyIcommand(OnSave, CanSave); 
}

Actualice el método OnSave como se muestra en el siguiente código.

private async void OnSave() { 
   UpdateCustomer(Customer, _editingCustomer); 
	
   if (EditMode) 
      await _repo.UpdateCustomerAsync(_editingCustomer); 
   else 
      await _repo.AddCustomerAsync(_editingCustomer); 
   Done(); 
} 

private void UpdateCustomer(SimpleEditableCustomer source, Customer target) { 
   target.FirstName = source.FirstName; 
   target.LastName = source.LastName; 
   target.Phone = source.Phone; 
   target.Email = source.Email; 
}

A continuación se muestra el AddEditCustomerViewModel completo.

using MVVMHierarchiesDemo.Model; 
using MVVMHierarchiesDemo.Services; 

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

namespace MVVMHierarchiesDemo.ViewModel { 

   class AddEditCustomerViewModel : BindableBase { 
      private ICustomersRepository _repo; 
		
      public AddEditCustomerViewModel(ICustomersRepository repo) { 
         _repo = repo;
         CancelCommand = new MyIcommand(OnCancel); 
         SaveCommand = new MyIcommand(OnSave, CanSave); 
      } 
		
      private bool _EditMode; 
		
      public bool EditMode { 
         get { return _EditMode; } 
         set { SetProperty(ref _EditMode, value); } 
      }

      private SimpleEditableCustomer _Customer; 
		
      public SimpleEditableCustomer Customer { 
         get { return _Customer; } 
         set { SetProperty(ref _Customer, value); } 
      }
		
      private Customer _editingCustomer = null;

      public void SetCustomer(Customer cust) { 
         _editingCustomer = cust; 
			
         if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged; 
         Customer = new SimpleEditableCustomer();
         Customer.ErrorsChanged += RaiseCanExecuteChanged;
         CopyCustomer(cust, Customer); 
      }

      private void RaiseCanExecuteChanged(object sender, EventArgs e) { 
         SaveCommand.RaiseCanExecuteChanged(); 
      }

      public MyIcommand CancelCommand { get; private set; } 
      public MyIcommand SaveCommand { get; private set; }

      public event Action Done = delegate { };
		
      private void OnCancel() { 
         Done(); 
      }

      private async void OnSave() { 
         UpdateCustomer(Customer, _editingCustomer); 
			
         if (EditMode) 
            await _repo.UpdateCustomerAsync(_editingCustomer); 
         else 
            await _repo.AddCustomerAsync(_editingCustomer); 
         Done(); 
      }

      private void UpdateCustomer(SimpleEditableCustomer source, Customer target) { 
         target.FirstName = source.FirstName; 
         target.LastName = source.LastName; 
         target.Phone = source.Phone; 
         target.Email = source.Email; 
      }

      private bool CanSave() { 
         return !Customer.HasErrors; 
      }
		
      private void CopyCustomer(Customer source, SimpleEditableCustomer target) { 
         target.Id = source.Id; 
			
         if (EditMode) { 
            target.FirstName = source.FirstName; 
            target.LastName = source.LastName; 
            target.Phone = source.Phone; 
            target.Email = source.Email; 
         }
      } 
   } 
}

Cuando se compila y ejecuta el código anterior, verá el mismo resultado, pero ahora los ViewModels están desacoplados de manera más flexible.

Cuando presione el botón Agregar cliente, verá la siguiente vista. Cuando el usuario deja un campo vacío, se resaltará y el botón Guardar se desactivará.

Un evento es una construcción de programación que reacciona a un cambio de estado, notificando cualquier punto final que se haya registrado para notificación. Principalmente, los eventos se utilizan para informar la entrada de un usuario a través del mouse y el teclado, pero su utilidad no se limita a eso. Siempre que se detecta un cambio de estado, tal vez cuando se ha cargado o inicializado un objeto, se puede disparar un evento para alertar a los terceros interesados.

  • En una aplicación WPF que usa el patrón de diseño MVVM (Model-View-ViewModel), el modelo de vista es el componente responsable de manejar la lógica y el estado de presentación de la aplicación.

  • El archivo de código subyacente de la vista no debe contener código para manejar eventos que se generan desde cualquier elemento de la interfaz de usuario (UI), como un botón o un cuadro combinado, ni debe contener ninguna lógica específica de dominio.

  • Idealmente, el código subyacente de una vista contiene solo un constructor que llama al método InitializeComponent y quizás algún código adicional para controlar o interactuar con la capa de vista que es difícil o ineficiente de expresar en XAML, por ejemplo, animaciones complejas.

Echemos un vistazo a un ejemplo simple de eventos de clic de botón en nuestra aplicación. A continuación se muestra el código XAML del archivo MainWindow.xaml en el que verá dos botones.

<Window x:Class = "MVVMHierarchiesDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMHierarchiesDemo" 
   xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views" 
   xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525">
	
   <Window.DataContext> 
      <local:MainWindowViewModel/> 
   </Window.DataContext>
	
   <Window.Resources> 
      <DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
         <views:CustomerListView/> 
      </DataTemplate>
		 
      <DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
         <views:OrderView/>
      </DataTemplate> 
   </Window.Resources> 

   <Grid> 
      <Grid.RowDefinitions> 
         <RowDefinition Height = "Auto" /> 
         <RowDefinition Height = "*" /> 
      </Grid.RowDefinitions> 
		
      <Grid x:Name = "NavBar"> 
         <Grid.ColumnDefinitions> 
            <ColumnDefinition Width = "*" />
            <ColumnDefinition Width = "*" /> 
            <ColumnDefinition Width = "*" /> 
         </Grid.ColumnDefinitions>
			
         <Button Content = "Customers" 
            Command = "{Binding NavCommand}" 
            CommandParameter = "customers" 
            Grid.Column = "0" />
				
         <Button Content = "Order" 
            Command = "{Binding NavCommand}" 
            CommandParameter = "orders" 
            Grid.Column = "2" />
      </Grid>
		
      <Grid x:Name = "MainContent" Grid.Row = "1"> 
         <ContentControl Content = "{Binding CurrentViewModel}" />
      </Grid> 
		
   </Grid> 

</Window>

Puede ver que la propiedad Click del botón no se usa en el archivo XAML anterior, pero las propiedades Command y CommandParameter se usan para cargar diferentes vistas cuando se presiona el botón. Ahora necesita definir la implementación de los comandos en el archivo MainWindowViewModel.cs pero no en el archivo Ver. A continuación se muestra la implementación completa de MainWindowViewModel.

using MVVMHierarchiesDemo.ViewModel; 
using MVVMHierarchiesDemo.Views; 

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

namespace MVVMHierarchiesDemo { 

   class MainWindowViewModel : BindableBase { 
	
      public MainWindowViewModel() { 
         NavCommand = new MyICommand<string>(OnNav); 
      } 

      private CustomerListViewModel custListViewModel = new CustomerListViewModel(); 
      private OrderViewModel orderViewModelModel = new OrderViewModel();

      private BindableBase _CurrentViewModel; 
		
      public BindableBase CurrentViewModel { 
         get { return _CurrentViewModel; } 
         set { SetProperty(ref _CurrentViewModel, value); } 
      } 
		
      public MyICommand<string> NavCommand { get; private set; }

      private void OnNav(string destination) { 
		
         switch (destination) { 
            case "orders": 
               CurrentViewModel = orderViewModelModel; 
               break; 
            case "customers":
               default: 
               CurrentViewModel = custListViewModel; 
               break; 
         } 
      } 
   }
}

Derive todos sus ViewModels de la clase BindableBase. Cuando se compile y ejecute el código anterior, verá el siguiente resultado.

Como puede ver, hemos agregado solo dos botones y un CurrentViewModel en nuestra MainWindow. Ahora, si hace clic en cualquier botón, navegará a esa Vista en particular. Hagamos clic en el botón Clientes y verá que se muestra CustomerListView.

Le recomendamos que ejecute el ejemplo anterior en un método paso a paso para una mejor comprensión.

La idea detrás de las pruebas unitarias es tomar fragmentos discretos de código (unidades) y escribir métodos de prueba que usen el código de la manera esperada, y luego probar para ver si obtienen los resultados esperados.

  • Al ser código en sí mismo, las pruebas unitarias se compilan como el resto del proyecto.

  • También son ejecutados por el software de ejecución de prueba, que puede acelerar cada prueba, dando efectivamente el pulgar hacia arriba o hacia abajo para indicar si la prueba ha pasado o no, respectivamente.

Echemos un vistazo a un ejemplo creado anteriormente. A continuación se muestra la implementación del modelo de estudiante.

using System.ComponentModel;

namespace MVVMDemo.Model {
 
   public class StudentModel {}
	
   public class Student : INotifyPropertyChanged { 
      private string firstName; 
      private string lastName;

      public string FirstName { 
         get { return firstName; }
			
         set { 
            if (firstName != value) { 
               firstName = value; 
               RaisePropertyChanged("FirstName");
               RaisePropertyChanged("FullName"); 
            } 
         }
      }

      public string LastName { 
         get { return lastName; } 
			
         set { 
            if (lastName != value) { 
               lastName = value; 
               RaisePropertyChanged("LastName");
               RaisePropertyChanged("FullName");
            } 
         } 
      }

      public string FullName { 
         get { 
            return firstName + " " + lastName; 
         } 
      }

      public event PropertyChangedEventHandler PropertyChanged;

      private void RaisePropertyChanged(string property) { 
         if (PropertyChanged != null) { 
            PropertyChanged(this, new PropertyChangedEventArgs(property)); 
         } 
      } 
   } 
}

A continuación se muestra la implementación de StudentView.

<UserControl x:Class="MVVMDemo.Views.StudentView" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:local = "clr-namespace:MVVMDemo.Views" 
   xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" 
   xmlns:data = "clr-namespace:MVVMDemo.Model" 
   xmlns:vml = "clr-namespace:MVVMDemo.VML" 
   vml:ViewModelLocator.AutoHookedUpViewModel = "True" 
   mc:Ignorable = "d" 
   d:DesignHeight = "300" d:DesignWidth = "300">

   <UserControl.Resources> 
      <DataTemplate DataType = "{x:Type data:Student}"> 
		
         <StackPanel Orientation = "Horizontal"> 
            <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" 
               Width = "100" Margin = "3 5 3 5"/> 
					
            <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" 
               Width = "100" Margin = "0 5 3 5"/> 
					
            <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" 
               Margin = "0 5 3 5"/> 
         </StackPanel> 
			
      </DataTemplate> 
   </UserControl.Resources>

   <Grid> 
      <StackPanel Orientation = "Horizontal"> 
         <ListBox ItemsSource = "{Binding Students}"
            SelectedItem = "{Binding SelectedStudent}"/> 
				
         <Button Content = "Delete" 
            Command = "{Binding DeleteCommand}" 
            HorizontalAlignment = "Left" 
            VerticalAlignment = "Top" 
            Width = "75" /> 
      </StackPanel> 
   </Grid>

</UserControl>

A continuación se muestra la implementación de StudentViewModel.

using MVVMDemo.Model;
 
using System.Collections.ObjectModel; 
using System.Windows.Input; 
using System;

namespace MVVMDemo.ViewModel { 

   public class StudentViewModel { 
	
      public MyICommand DeleteCommand { get; set;}
		
      public StudentViewModel() { 
         LoadStudents(); 
         DeleteCommand = new MyICommand(OnDelete, CanDelete); 
      }

      public ObservableCollection<Student> Students { 
         get; 
         set; 
      }

      public void LoadStudents() { 
         ObservableCollection<Student> students = new ObservableCollection<Student>();

         students.Add(new Student { FirstName = "Mark", LastName = "Allain" }); 
         students.Add(new Student { FirstName = "Allen", LastName = "Brown" }); 
         students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
			
         Students = students; 
      } 
		
      private Student _selectedStudent; 
		
      public Student SelectedStudent { 
         get { 
            return _selectedStudent; 
         } 
			
         set { 
            _selectedStudent = value;
            DeleteCommand.RaiseCanExecuteChanged(); 
         } 
      } 
		
      private void OnDelete() { 
         Students.Remove(SelectedStudent); 
      }

      private bool CanDelete() { 
         return SelectedStudent != null; 
      }
		
      public int GetStudentCount() { 
         return Students.Count; 
      } 
   } 
}

A continuación se muestra el archivo MainWindow.xaml.

<Window x:Class = "MVVMDemo.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:MVVMDemo" 
   xmlns:views = "clr-namespace:MVVMDemo.Views" 
   mc:Ignorable = "d" 
   Title = "MainWindow" Height = "350" Width = "525">

   <Grid> 
      <views:StudentView x:Name = "StudentViewControl"/> 
   </Grid>
 
</Window>

A continuación se muestra la implementación de MyICommand, que implementa la interfaz ICommand.

using System; 
using System.Windows.Input;

namespace MVVMDemo { 

   public class MyICommand : ICommand { 
      Action _TargetExecuteMethod; 
      Func<bool> _TargetCanExecuteMethod;

      public MyICommand(Action executeMethod) { 
         _TargetExecuteMethod = executeMethod; 
      }

      public MyICommand(Action executeMethod, Func<bool> canExecuteMethod) { 
         _TargetExecuteMethod = executeMethod;
         _TargetCanExecuteMethod = canExecuteMethod; 
      }

      public void RaiseCanExecuteChanged() {
         CanExecuteChanged(this, EventArgs.Empty); 
      }
		
      bool ICommand.CanExecute(object parameter) { 
		
         if (_TargetCanExecuteMethod != null) { 
            return _TargetCanExecuteMethod();
         } 
			
         if (_TargetExecuteMethod != null) { 
            return true; 
         } 
			
         return false; 
      }
		
      // Beware - should use weak references if command instance lifetime
         is longer than lifetime of UI objects that get hooked up to command
			
      // Prism commands solve this in their implementation
		
      public event EventHandler CanExecuteChanged = delegate { };

      void ICommand.Execute(object parameter) { 
         if (_TargetExecuteMethod != null) { 
            _TargetExecuteMethod(); 
         } 
      } 
   }
}

Cuando se compile y ejecute el código anterior, verá el siguiente resultado en su ventana principal.

Para escribir una prueba unitaria para el ejemplo anterior, agreguemos un nuevo Proyecto de prueba a la Solución.

Agregue una referencia al proyecto haciendo clic con el botón derecho en Referencias.

Seleccione el proyecto existente y haga clic en Aceptar.

Agreguemos ahora una prueba simple que verificará el conteo de estudiantes como se muestra en el siguiente código.

using System; 

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using MVVMDemo.ViewModel;

namespace MVVMTest { 
   [TestClass] 
	
   public class UnitTest1 { 
      [TestMethod] 
		
      public void TestMethod1() { 
         StudentViewModel sViewModel = new StudentViewModel(); 
         int count = sViewModel.GetStudentCount();
         Assert.IsTrue(count == 3); 
      } 
   } 
}

Para ejecutar esta prueba, seleccione la opción de menú Prueba → Ejecutar → Todas las pruebas.

Puede ver en el Explorador de pruebas que la prueba se aprobó, porque en StudentViewModel, se agregan tres estudiantes. Cambie la condición de recuento de 3 a 4 como se muestra en el siguiente código.

using System; 

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using MVVMDemo.ViewModel;

namespace MVVMTest { 
   [TestClass] 
	
   public class UnitTest1 { 
      [TestMethod] public void TestMethod1() {
         StudentViewModel sViewModel = new StudentViewModel(); 
         int count = sViewModel.GetStudentCount();
         Assert.IsTrue(count == 4);
      } 
   } 
}

Cuando el plan de prueba se ejecute nuevamente, verá que la prueba falló porque el recuento de estudiantes no es igual a 4.

Le recomendamos que ejecute el ejemplo anterior en un método paso a paso para una mejor comprensión.

En este capítulo, analizaremos los kits de herramientas o marcos de MVVM que están disponibles. También puede usar estos marcos para no tener que escribir un montón de código repetitivo para implementar el patrón MVVM usted mismo. Estos son algunos de los marcos más populares:

  • Prism
  • Luz MVVM
  • Caliburn Micro

Prisma

Prism proporciona orientación en forma de muestras y documentación que lo ayudan a diseñar y crear aplicaciones de escritorio de Windows Presentation Foundation (WPF) ricas, flexibles y fáciles de mantener. Aplicaciones de Internet enriquecidas (RIA) creadas con el complemento del navegador Microsoft Silverlight y las aplicaciones de Windows.

  • Prism utiliza patrones de diseño que incorporan importantes principios de diseño arquitectónico, como la separación de preocupaciones y el acoplamiento flexible.

  • Prism le ayuda a diseñar y crear aplicaciones utilizando componentes poco acoplados que pueden evolucionar de forma independiente, pero que pueden integrarse fácil y sin problemas en la aplicación general.

  • Estos tipos de aplicaciones se conocen como aplicaciones compuestas.

Prism tiene varias funciones listas para usar. A continuación se muestran algunas de las características importantes de Prism.

Patrón MVVM

Prism tiene soporte para el patrón MVVM. Tiene una clase Bindablebase similar a la que se implementó en los capítulos anteriores.

Tiene un ViewModelLocator flexible que tiene convenciones, pero le permite anular esas convenciones y conectar declarativamente sus Vistas y ViewModels de una manera poco acoplada.

Modularidad

Es la capacidad de dividir su código en bibliotecas de clases totalmente acopladas libremente en partes y unirlas en tiempo de ejecución en un todo cohesivo para el usuario final, mientras que el código permanece completamente desacoplado.

Composición / regiones de la interfaz de usuario

Es la capacidad de conectar vistas en contenedores sin la Vista la que está haciendo la conexión, necesitando tener una referencia explícita al contenedor de IU en sí.

Navegación

Prism tiene funciones de navegación que se superponen a las regiones, como navegación hacia adelante y hacia atrás y la pila de navegación que permite que sus modelos de vista participen en el proceso de navegación directamente.

Comandos

Prism tiene comandos, por lo que tienen un comando delegado que es muy similar al MyICommand que hemos usado en capítulos anteriores, excepto que tiene algo de robustez adicional para protegerlo de pérdidas de memoria.

Eventos de Pub / Sub

Prism también es compatible con eventos Pub / Sub. Estos son eventos débilmente acoplados en los que el editor y el suscriptor pueden tener diferentes tiempos de vida y no es necesario que tengan referencias explícitas entre sí para comunicarse a través de eventos.

Luz MVVM

MVVM Light es producido por Laurent Bugnion y lo ayuda a separar su vista de su modelo, lo que crea aplicaciones que son más limpias y fáciles de mantener y extender.

  • También crea aplicaciones probables y le permite tener una capa de interfaz de usuario mucho más delgada (que es más difícil de probar automáticamente).

  • Este conjunto de herramientas pone especial énfasis en abrir y editar la interfaz de usuario en Blend, incluida la creación de datos en tiempo de diseño para permitir que los usuarios de Blend "vean algo" cuando trabajan con controles de datos.

Caliburn Micro

Este es otro pequeño marco de código abierto que lo ayuda a implementar el patrón MVVM y también es compatible con una serie de cosas listas para usar.

  • Caliburn Micro es un marco pequeño pero potente, diseñado para crear aplicaciones en todas las plataformas XAML.

  • Con un fuerte soporte para MVVM y otros patrones de UI probados, Caliburn Micro le permitirá construir su solución rápidamente, sin la necesidad de sacrificar la calidad del código o la capacidad de prueba.