Desafortunadamente, no hay una gran aplicación de ejemplo MVVM que haga todo, y hay muchos enfoques diferentes para hacer las cosas. Primero, es posible que desee familiarizarse con uno de los marcos de la aplicación (Prism es una opción decente), ya que le proporcionan herramientas convenientes como inyección de dependencia, comando, agregación de eventos, etc. para probar fácilmente diferentes patrones que le convengan .
El lanzamiento del prisma:
http://www.codeplex.com/CompositeWPF
Incluye una aplicación de ejemplo bastante decente (el operador de bolsa) junto con muchos ejemplos más pequeños y cómo hacerlo. Por lo menos, es una buena demostración de varios subpatrones comunes que las personas usan para hacer que MVVM realmente funcione. Tienen ejemplos para CRUD y diálogos, creo.
Prism no es necesariamente para cada proyecto, pero es bueno familiarizarse con él.
CRUD:
esta parte es bastante fácil, los enlaces bidireccionales de WPF hacen que sea muy fácil editar la mayoría de los datos. El verdadero truco es proporcionar un modelo que facilite la configuración de la interfaz de usuario. Como mínimo, desea asegurarse de que su ViewModel (u objeto de negocio) se implemente INotifyPropertyChanged
para admitir el enlace y puede vincular propiedades directamente a los controles de la interfaz de usuario, pero también puede implementarlo IDataErrorInfo
para la validación. Por lo general, si usa algún tipo de solución ORM, configurar CRUD es muy fácil.
Este artículo muestra operaciones simples de crud:
http://dotnetslackers.com/articles/wpf/WPFDataBindingWithLINQ.aspx
Está construido en LinqToSql, pero eso es irrelevante para el ejemplo; todo lo que es importante es que sus objetos de negocio implementen INotifyPropertyChanged
(qué clases generadas por LinqToSql sí). MVVM no es el punto de ese ejemplo, pero no creo que importe en este caso.
Este artículo demuestra la validación de datos
http://blogs.msdn.com/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx
Una vez más, la mayoría de las soluciones ORM generan clases que ya implementan IDataErrorInfo
y generalmente proporcionan un mecanismo para facilitar la adición de reglas de validación personalizadas.
La mayoría de las veces puede tomar un objeto (modelo) creado por algún ORM y envolverlo en un ViewModel que lo contiene y los comandos para guardar / eliminar, y está listo para vincular la IU directamente a las propiedades del modelo.
La vista se vería así (ViewModel tiene una propiedad Item
que contiene el modelo, como una clase creada en el ORM):
<StackPanel>
<StackPanel DataContext=Item>
<TextBox Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
<TextBox Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</StackPanel>
<Button Command="{Binding SaveCommand}" />
<Button Command="{Binding CancelCommand}" />
</StackPanel>
Diálogos: los
diálogos y MVVM son un poco complicados. Prefiero usar una idea del enfoque de Mediador con diálogos, puede leer un poco más sobre esto en esta pregunta de StackOverflow:
ejemplo de diálogo WPF MVVM
Mi enfoque habitual, que no es MVVM clásico, se puede resumir de la siguiente manera:
Una clase base para un ViewModel de diálogo que expone comandos para las acciones de confirmación y cancelación, un evento que le permite a la vista saber que un diálogo está listo para cerrarse, y cualquier otra cosa que necesite en todos sus diálogos.
Una vista genérica para su diálogo: puede ser una ventana o un control de tipo de superposición "modal" personalizada. En esencia, es un presentador de contenido en el que volcamos el modelo de vista y maneja el cableado para cerrar la ventana; por ejemplo, en el cambio de contexto de datos, puede verificar si el nuevo ViewModel se hereda de su clase base, y si es así, suscríbase al evento de cierre relevante (el controlador asignará el resultado del diálogo). Si proporciona una funcionalidad de cierre universal alternativa (el botón X, por ejemplo), también debe asegurarse de ejecutar el comando de cierre correspondiente en ViewModel.
En algún lugar donde necesite proporcionar plantillas de datos para sus ViewModels, pueden ser muy simples, especialmente porque probablemente tenga una vista para cada cuadro de diálogo encapsulado en un control separado. La plantilla de datos predeterminada para un ViewModel se vería así:
<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}">
<views:AddressEditView DataContext="{Binding}" />
</DataTemplate>
La vista de diálogo debe tener acceso a estos, porque de lo contrario no sabrá cómo mostrar el ViewModel, aparte de la interfaz de usuario del diálogo compartido, su contenido es básicamente el siguiente:
<ContentControl Content="{Binding}" />
La plantilla de datos implícitos asignará la vista al modelo, pero ¿quién la inicia?
Esta es la parte no tan mvvm. Una forma de hacerlo es usar un evento global. Lo que creo que es mejor hacer es usar una configuración de tipo de agregador de eventos, proporcionada mediante inyección de dependencia, de esta manera el evento es global para un contenedor, no para toda la aplicación. Prism utiliza el marco de la unidad para la semántica de contenedores y la inyección de dependencia, y en general me gusta bastante Unity.
Por lo general, tiene sentido que la ventana raíz se suscriba a este evento: puede abrir el cuadro de diálogo y establecer su contexto de datos en ViewModel que se pasa con un evento generado.
Configurar esto de esta manera permite que ViewModels solicite a la aplicación que abra un cuadro de diálogo y responda a las acciones del usuario allí sin saber nada acerca de la interfaz de usuario, por lo que la mayor parte de la MVVM-ness permanece completa.
Sin embargo, hay momentos en que la interfaz de usuario tiene que abrir los diálogos, lo que puede hacer las cosas un poco más complicadas. Considere, por ejemplo, si la posición del diálogo depende de la ubicación del botón que lo abre. En este caso, debe tener información específica de la IU cuando solicite que se abra un cuadro de diálogo. Por lo general, creo una clase separada que contiene un ViewModel y alguna información relevante de la interfaz de usuario. Desafortunadamente, algunos acoplamientos parecen inevitables allí.
Seudocódigo de un controlador de botones que genera un diálogo que necesita datos de posición del elemento:
ButtonClickHandler(sender, args){
var vm = DataContext as ISomeDialogProvider; // check for null
var ui_vm = new ViewModelContainer();
// assign margin, width, or anything else that your custom dialog might require
...
ui_vm.ViewModel = vm.SomeDialogViewModel; // or .GetSomeDialogViewModel()
// raise the dialog show event
}
La vista de diálogo se unirá a los datos de posición y pasará el ViewModel contenido al interior ContentControl
. ViewModel en sí todavía no sabe nada sobre la interfaz de usuario.
En general, no uso la DialogResult
propiedad return del ShowDialog()
método ni espero que el hilo se bloquee hasta que se cierre el cuadro de diálogo. Un cuadro de diálogo modal no estándar no siempre funciona así, y en un entorno compuesto a menudo no desea que un controlador de eventos bloquee así de todos modos. Prefiero dejar que los ViewModels se ocupen de esto: el creador de un ViewModel puede suscribirse a sus eventos relevantes, establecer métodos de confirmación / cancelación, etc., por lo que no es necesario confiar en este mecanismo de interfaz de usuario.
Entonces, en lugar de este flujo:
// in code behind
var result = somedialog.ShowDialog();
if (result == ...
Yo suelo:
// in view model
var vm = new SomeDialogViewModel(); // child view model
vm.CommitAction = delegate { this.DoSomething(vm); } // what happens on commit
vm.CancelAction = delegate { this.DoNothing(vm); } // what happens on cancel/close (optional)
// raise dialog request event on the container
Lo prefiero de esta manera porque la mayoría de mis cuadros de diálogo son controles pseudo-modales que no bloquean y hacerlo de esta manera parece más sencillo que evitarlo. Prueba fácil de unidad también.