WPF: ¿Debería un usuario tener su propio ViewModel?

Tengo una ventana formada por varios controles de usuario y me preguntaba si cada control de usuario tiene su propio modelo de vista o si la ventana en su conjunto tiene solo un ViewModel.

Esta no es una pregunta de sí o no. Depende de si tener modelos de vista adicionales le permite una mejor capacidad de mantenimiento o capacidad de prueba. No tiene sentido agregar modelos de vista si no gana nada. Tendrá que evaluar si la sobrecarga vale la pena para su caso de uso particular.

Absolutamente, positivamente

NO

Tus UserControls NO deben tener ViewModels diseñados específicamente para ellos. Esto es, de hecho, un olor a código. No interrumpe su aplicación de inmediato, pero le causará dolor al trabajar con ella.

Un UserControl es simplemente una manera fácil de crear un control usando la composición. Los UserControls siguen siendo controles y, por lo tanto, solo deberían preocuparse por cuestiones de interfaz de usuario.

Cuando crea un ViewModel para su UserControl, aquí está colocando lógica de negocios o de IU. Es incorrecto usar ViewModels para contener la lógica de UI, por lo que si ese es su objective, abandone su máquina virtual y coloque el código en el código subyacente de ese control. Si coloca la lógica comercial en UserControl, lo más probable es que la utilice para segregar partes de su aplicación en lugar de simplificar la creación de controles. Los controles deben ser simples y tener un único propósito para el cual están diseñados.

Cuando crea un ViewModel para su UserControl, también rompe el flujo natural de datos a través del DataContext. Aquí es donde experimentarás más dolor. Para demostrar, considere este simple ejemplo.

Tenemos un modelo de vista que contiene personas, siendo cada una de ellas una instancia del tipo de persona.

public class ViewModel { public IEnumerable People { get; private set; } public ViewModel() { People = PeopleService.StaticDependenciesSuckToo.GetPeople(); } } public class Person { public string Name { get; set; } public int Age { get; set; } } 

Mostrar una lista de personas en nuestra ventana es trivial.

            

La lista recoge automáticamente la plantilla de artículo correcta para la persona y utiliza PersonView para mostrar la información de la persona al usuario.

¿Qué es PersonView? Es un UserControl que está diseñado para mostrar la información de la persona. Es un control de pantalla para una persona, de forma similar a como TextBlock es un control de pantalla para texto. Está diseñado para unirse a una Persona, y como tal funciona sin problemas. Observe en la ventana de arriba cómo el ListView transfiere cada instancia de Person a una PersonView donde se convierte en DataContext para este subárbol de la visual.

         

Para que esto funcione sin problemas, ViewModel UserControl debe ser una instancia del tipo para el que está diseñado . Cuando rompes esto haciendo cosas estúpidas como

 public PersonView() { InitializeComponent(); this.DataContext = this; // omfg } 

o

 public PersonView() { InitializeComponent(); this.DataContext = new PersonViewViewModel(); } 

has roto la simplicidad del modelo. Por lo general, en estos casos termina con soluciones aberrantes, la más común de las cuales es crear una propiedad pseudo-DataContext para lo que su DataContext debería ser realmente . Y ahora no puedes vincular a uno con el otro, así que terminas con horribles hacks como

 public partial class PersonView : UserControl { public PersonView() { InitializeComponent(); var vm = PersonViewViewModel(); // JUST KILL ME NOW, GET IT OVER WITH vm.PropertyChanged = (o, e) => { if(e.Name == "Age" && MyRealDataContext != null) MyRealDataContext.Age = vm.PersonAge; }; this.DataContext = vm; } public static readonly DependencyProperty MyRealDataContextProperty = DependencyProperty.Register( "MyRealDataContext", typeof(Person), typeof(PersonView), new UIPropertyMetadata()); public Person MyRealDataContext { get { return (Person)GetValue(MyRealDataContextProperty); } set { SetValue(MyRealDataContextProperty, value); } } } 

Debería pensar en un UserControl como nada más que un control más complejo. ¿El TextBox tiene su propio ViewModel? No. Vincula la propiedad de su VM a la propiedad Text del control, y el control muestra su texto en su UI.

MVVM no significa “Sin código detrás”. Coloque su lógica de UI para su control de usuario en el código subyacente. Si es tan complejo que necesita lógica comercial dentro del control del usuario, eso sugiere que es demasiado abarcador. ¡Simplificar!

Piense en UserControls en MVVM de esta manera: para cada modelo, tiene un UserControl y está diseñado para presentar los datos en ese modelo al usuario. Puede usarlo en cualquier lugar que desee para mostrarle al usuario ese modelo. ¿Necesita un botón? Exponga una propiedad ICommand en su UserControl y permita que su lógica de negocios se vincule. ¿Su lógica de negocio necesita saber algo que está sucediendo adentro? Agrega un evento enrutado.

Normalmente, en WPF, si te preguntas por qué duele hacer algo, es porque no deberías hacerlo.

Yo diría que cada control de usuario debería tener su propio modelo de vista, porque eso le permitiría reutilizar el par de ViewModel / UserControl en nuevas constelaciones en el futuro.

Según tengo entendido, su ventana es un Compuesto de controles de usuario, por lo que siempre puede crear un ViewModel que comprenda todos los ViewModels por separado para cada uno de los controles de usuario. Esto te dará lo mejor de ambos mundos.

Supongo que su aplicación está haciendo una especie de composición de vista, por lo que si hace que sus controles de usuario tengan su propio modelo de vista, tendrá más libertad para insertarlos en otras ventanas de host sin cambiar el modelo de vista global de la ventana.

Como una ventaja adicional, su aplicación será más adecuada para evolucionar hacia un modelo de composición más arquitectónico que el proporcionado por los marcos Prism o Caliburn, si surgen los requisitos de la aplicación.