c# - ejemplo - Locura de MVVM: Comandos
mvvm español (7)
Me gusta MVVM No me gusta, pero me gusta. La mayor parte tiene sentido. Pero sigo leyendo artículos que lo alientan a escribir mucho código para que pueda escribir XAML y no tenga que escribir ningún código en el código subyacente.
Dejame darte un ejemplo.
Recientemente, quería conectar un comando en mi ViewModel a ListView MouseDoubleClickEvent. No estaba muy seguro de cómo hacer esto. Afortunadamente, Google tiene respuestas para todo. Encontré los siguientes artículos:
- http://blog.functionalfun.net/2008/09/hooking-up-commands-to-events-in-wpf.html
- http://joyfulwpf.blogspot.com/2009/05/mvvm-invoking-command-on-attached-event.html
- http://sachabarber.net/?p=514
- http://geekswithblogs.net/HouseOfBilz/archive/2009/08/27/adventures-in-mvvm-ndash-binding-commands-to-any-event.aspx
- http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/
Si bien las soluciones fueron útiles en mi comprensión de los comandos, hubo problemas. Algunas de las soluciones mencionadas anteriormente inutilizaban al diseñador de WPF debido a un truco común de agregar "interno" después de una propiedad de dependencia; el diseñador de WPF no puede encontrarlo, pero el CLR sí puede. Algunas de las soluciones no permitieron múltiples comandos para el mismo control. Algunas de las soluciones no permitieron parámetros.
Después de experimentar durante unas horas, decidí hacer esto:
private void ListView_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
ListView lv = sender as ListView;
MyViewModel vm = this.DataContext as MyViewModel;
vm.DoSomethingCommand.Execute(lv.SelectedItem);
}
Entonces, puristas MVVM, por favor díganme qué pasa con esto? Aún puedo probar la unidad de mi comando. Esto parece muy práctico, pero parece violar la directriz de "ZOMG ... ¡tienes código en tu código detrás!" Por favor comparte tus pensamientos.
Gracias por adelantado.
Aunque prefiero no escribir código subyacente cuando uso el patrón MVVM, creo que está bien hacerlo siempre que ese código esté puramente relacionado con la IU.
Pero este no es el caso aquí: está llamando a un comando view-model desde el código subyacente, por lo que no está puramente relacionado con UI, y la relación entre la vista y el comando view-model no es directamente aparente en XAML.
Creo que podría hacerlo fácilmente en XAML, utilizando el http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/ . De esta forma, puede "enlazar" el evento MouseDoubleClick
a un comando de su modelo de vista:
<ListView ItemSource="{Binding Items}">
<local:CommandBehaviorCollection.Behaviors>
<local:BehaviorBinding Event="MouseDoubleClick" Action="{Binding DoSomething}" />
</local:CommandBehaviorCollection.Behaviors>
...
</ListView>
También puede acceder fácilmente al elemento seleccionado de ListView
sin consultarlo directamente, utilizando la interfaz ICollectionView
:
private ICommand _doSomething;
public ICommand DoSomething
{
get
{
if (_doSomething == null)
{
_doSomething = new DelegateCommand(
() =>
{
ICollectionView view = CollectionViewSource.GetDefaultView(Items);
object selected = view.CurrentItem;
DoSomethingWithItem(selected);
});
}
return _doSomething;
}
}
Creo que el objetivo de tener "Sin código en el código subyacente" es exactamente eso, un objetivo a alcanzar, no algo que se debe tomar como un dogma absoluto. Hay lugares apropiados para el código en la Vista, y este no es necesariamente un mal ejemplo de dónde o cómo el código puede ser más simple que un enfoque alternativo.
La ventaja de los otros enfoques que enumera, incluidas las propiedades adjuntas o los eventos adjuntos, es que son reutilizables. Cuando conecta un evento directamente y luego hace lo que hizo, es muy fácil terminar duplicando ese código en su aplicación. Al crear una sola propiedad o evento adjunto para manejar ese cableado, agrega un código adicional en la instalación de tuberías, pero es un código que se puede reutilizar para cualquier ListView donde desee hacer doble clic en el manejo.
Dicho esto, tiendo a preferir usar el enfoque más "purista". Mantener todo el manejo de eventos fuera de la Vista puede no afectar el escenario de prueba (que se aborda específicamente), pero sí afecta la designabilidad general y la facilidad de mantenimiento. Al introducir código en su código, está restringiendo su Vista a usar siempre un ListView con el controlador de eventos conectado, lo que vincula su Vista al código y restringe la flexibilidad de rediseñar por un diseñador.
Creo que la falla radica en el requisito de pureza. Los patrones de diseño, incluido MVVM, son una herramienta en la caja de herramientas, no un fin en sí mismos. Si tiene más sentido romper con la pureza del modelo para un caso bien considerado (y claramente parece que usted ha considerado este caso), entonces rompa con el modelo.
Si eso funciona para usted, y no cree que sea una carga de mantenimiento indebida, entonces diría que nada está mal con lo que ha hecho. Creo que evidentemente se ha encontrado con la carga de la prueba para demostrar que esta es una solución razonable a su problema a pesar de lo que podría ser una implementación pura de MVVM.
(Considero que este argumento es similar a los argumentos para los lenguajes multiparadigma. Si bien se puede aplicar un enfoque de OO puro, a veces es más apropiado hacer las cosas de una manera más funcional. Mientras que se puede aplicar un enfoque de Funcionalidad pura, a veces las compensaciones muestran que OO las técnicas son más que vale la pena).
El desacoplamiento es una de las principales características de MVVM. Si supone que quiere cambiar, digamos view or binded model a él. ¿Cuán fácil es para su aplicación?
Tome un ejemplo donde View1 y View2 comparten el mismo ViewModel. Ahora implementará el código detrás del método para ambos.
Además, supongamos que si necesita cambiar el modelo de vista para una vista en una etapa posterior, su comando fracasará a medida que se modifique el modelo de vista y la declaración
MyViewModel vm = this.DataContext as MyViewModel;
devolverá nulo y, por lo tanto, bloqueará el código. Entonces, existe una carga adicional para cambiar también el código. Este tipo de escenarios surgirán si lo haces de esta manera.
Por supuesto, hay muchas maneras de lograr lo mismo en la programación, pero cuál es el mejor llevará al mejor enfoque.
El mando es para tontos. Los hombres reales conectan toda su interfaz de usuario a eventos en código subyacente.
Estoy de acuerdo con usted en que muchas soluciones de MVVM-Command son demasiado complicadas. Personalmente, utilizo un enfoque mixto y defino mis Comandos en la Vista en lugar de en ViewModel, usando métodos y propiedades del ViewModel.
XAML:
<Window.Resources>
<RoutedCommand x:Key="LookupAddressCommand" />
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource LookupAddressCommand}" x:Name="cmdLookupAddress" />
</Window.CommandBindings>
Código (Ver):
Private Sub cmdLookupAddress_CanExecute(ByVal sender As System.Object, ByVal e As System.Windows.Input.CanExecuteRoutedEventArgs) Handles cmdLookupAddress.CanExecute
e.CanExecute = myViewModel.SomeProperty OrElse (myViewModel.SomeOtherProperty = 2)
End Sub
Private Sub cmdLookupAddress_Executed(ByVal sender As System.Object, ByVal e As System.Windows.Input.ExecutedRoutedEventArgs) Handles cmdLookupAddress.Executed
myViewModel.LookupAddress()
End Sub
No es MVVM puro, pero es simple, funciona, no necesita clases de comando MVVM especiales y hace que su código sea mucho más fácil de leer para los expertos que no son MVVM (= mis compañeros de trabajo).
Lo que @JP describe en la pregunta original y las menciones de @Heinzi en answer es un enfoque pragmático para manejar comandos difíciles. Usar un pequeño código de manejo de eventos en el código subyacente es especialmente útil cuando necesita hacer un poco de trabajo de IU antes de invocar el comando.
Considere el caso clásico de OpenFileDialog. Es mucho más fácil utilizar un evento de clic en el botón, mostrar el diálogo y luego enviar los resultados a un comando en su ViewModel que adoptar cualquiera de las complicadas rutinas de mensajería utilizadas por los kits de herramientas de MVVM.
En tu XAML:
<Button DockPanel.Dock="Left" Click="AttachFilesClicked">Attach files</Button>
En tu código detrás:
private void AttachFilesClicked(object sender, System.Windows.RoutedEventArgs e)
{
// Configure open file dialog box
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
dlg.FileName = "Document"; // Default file name
dlg.DefaultExt = ".txt"; // Default file extension
dlg.Filter = "Text documents (.txt)|*.txt"; // Filter files by extension
// Show open file dialog box
bool? result = dlg.ShowDialog();
// Process open file dialog box results
if (result == true)
{
string filename = dlg.FileName;
// Invoke the command.
MyViewModel myViewModel = (MyViewModel)DataContext;
if (myViewModel .AttachFilesCommand.CanExecute(filename))
{
noteViewModel.AttachFilesCommand.Execute(filename);
}
}
}
La programación de computadoras es inflexible. Los programadores tenemos que ser flexibles para poder lidiar con eso.