tutorial mvc wpf model-view-controller viewmodel

mvc - WPF: ¿Debería un control de usuario tener su propio ViewModel?



mvvm vs mvc (4)

Absolutamente, positivamente

NO

Sus UserControls NO deben tener ViewModels diseñados específicamente para ellos. Esto es, de hecho, un olor a código. No rompe su aplicación inmediatamente, pero le causará dolor a medida que trabaja con ella.

Un UserControl es simplemente una forma fácil de crear un Control utilizando la composición. Los controles de usuario siguen siendo controles y, por lo tanto, solo deben preocuparse por los asuntos de la interfaz de usuario.

Cuando crea un ViewModel para su UserControl, está colocando lógica empresarial o UI allí. Es incorrecto usar ViewModels para contener la lógica de la interfaz de usuario, así que si ese es tu objetivo, deshazte de tu VM y coloca el código en el código de ese control. Si está colocando la lógica de negocios en el UserControl, lo más probable es que lo esté usando para segregar partes de su aplicación en lugar de simplificar la creación de control. Los controles deben ser simples y tener un solo 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á más dolor. Para demostrar, considere este ejemplo simple.

Tenemos un ViewModel que contiene personas, cada una de las cuales es una instancia del tipo de persona.

public class ViewModel { public IEnumerable<Person> 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.

<Window x:Class="YoureDoingItWrong.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:l="clr-namespace:YoureDoingItWrong" Title="Derp"> <Window.DataContext> <l:ViewModel /> </Window.DataContext> <Window.Resources> <DataTemplate DataType="{x:Type l:Person}"> <l:PersonView /> </DataTemplate> </Window.Resources> <ListView ItemsSource="{Binding People}" /> </Window>

La lista recoge automáticamente la plantilla de elementos correcta para la Persona y utiliza el 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 visualización para una persona, de manera similar a como TextBlock es un control de visualización para texto. Está diseñado para unirse contra una persona, y como tal funciona sin problemas. Note en la ventana de arriba cómo el ListView transfiere cada instancia de Person a un PersonView donde se convierte en el DataContext para este subárbol de lo visual.

<UserControl x:Class="YoureDoingItWrong.PersonView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <StackPanel> <Label>Name</Label> <TextBlock Text="{Binding Name}" /> <Label>Age</Label> <TextBlock Text="{Binding Age}" /> </StackPanel> </UserControl>

Para que esto funcione sin problemas, el ViewModel del 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 estas instancias terminas con soluciones repugnantes, la más común es crear una propiedad pseudo-DataContext para lo que realmente debería ser tu DataContext. Y ahora no puedes enlazar una con la otra, así que terminas con hacks horribles 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. Enlaza la propiedad de su máquina virtual a la propiedad de texto del control, y el control muestra su texto en su interfaz de usuario.

MVVM no significa "No Codebehind". Ponga su lógica de UI para su control de usuario en el código de código. Si es tan complejo que necesita lógica de negocios dentro del control del usuario, eso sugiere que es demasiado amplio. ¡Simplificar!

Piense en UserControls en MVVM de esta manera: para cada modelo, tiene un UserControl y está diseñado para presentar los datos de ese modelo al usuario. Puedes usarlo donde quieras para mostrarle al usuario ese modelo. ¿Necesita un botón? Exponga una propiedad de ICommand en su UserControl y deje que su lógica de negocios se una a ella. ¿Su lógica de negocios necesita saber algo dentro? Añade un evento enrutado.

Normalmente, en WPF, si te encuentras preguntando por qué duele hacer algo, es porque no debes hacerlo.

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


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


Supongo que su aplicación está haciendo algún tipo 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 incrustarlos en otras ventanas de host sin cambiar el modelo de vista global de la ventana.

Como un bono adicional, su aplicación será más adecuada para evolucionar hacia un modelo de composición con un diseño más arquitectónico como el que brindan los marcos de trabajo Prism o Caliburn, si surgen los requisitos de la aplicación.


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

Como lo entiendo, su ventana es un Compuesto de controles de usuario, por lo que siempre puede crear un ViewModel que componga todos los ViewModels separados para cada uno de los controles del usuario. Esto te dará lo mejor de ambos mundos.