c# - otro - El mejor enfoque para crear una nueva ventana en WPF usando MVVM
acceder a variables de otro formulario c# (6)
Algunos marcos MVVM (por ejemplo, MVVM Light ) hacen uso del patrón Mediator . Entonces, para abrir una nueva Ventana (o crear cualquier Vista), algún código específico de la Vista se suscribirá a los mensajes del mediador y ViewModel enviará esos mensajes.
Me gusta esto:
Subsription
Messenger.Default.Register<DialogMessage>(this, ProcessDialogMessage);
...
private void ProcessDialogMessage(DialogMessage message)
{
// Instantiate new view depending on the message details
}
En ViewModel
Messenger.Default.Send(new DialogMessage(...));
Prefiero hacer la suscripción en una clase singleton, que "vive" siempre que lo haga la parte de UI de la aplicación. Para resumir: ViewModel pasa mensajes como "Necesito crear una vista" y la IU escucha esos mensajes y actúa sobre ellos.
Sin embargo, no hay un enfoque "ideal".
En la publicación de vecinos: ¿cómo debe ViewModel cerrar el formulario? He publicado mi visión de cómo cerrar ventanas con el uso de MVVM. Y ahora tengo una pregunta: cómo abrirlos.
Tengo una ventana principal (vista principal). Si el usuario hace clic en el botón "Mostrar", se mostrará la ventana "Demo" (diálogo modal). ¿Cuál es una forma preferible de crear y abrir ventanas usando el patrón MVVM? Veo dos enfoques generales:
El primero (probablemente el más simple). El manejador de eventos "ShowButton_Click" debe implementarse en el código detrás de la ventana principal de la siguiente manera:
private void ModifyButton_Click(object sender, RoutedEventArgs e)
{
ShowWindow wnd = new ShowWindow(anyKindOfData);
bool? res = wnd.ShowDialog();
if (res != null && res.Value)
{
// ... store changes if neecssary
}
}
- Si el estado del botón "Mostrar" se debe cambiar (habilitar / deshabilitar), tendremos que agregar una lógica que administre el estado del botón;
- El código fuente es muy similar a las fuentes WinForms y MFC de "estilo antiguo". No estoy seguro de si esto es bueno o malo, por favor avise.
- ¿Algo más que me he perdido?
Otro enfoque:
En MainWindowViewModel implementaremos la propiedad "ShowCommand" que devolverá la interfaz ICommand del comando. Comman a su vez:
- levantará "ShowDialogEvent";
- administrará el estado del botón.
Este enfoque será más adecuado para MVVM pero requerirá codificación adicional: la clase ViewModel no puede "mostrar diálogo", por lo que MainWindowViewModel solo generará "ShowDialogEvent", MainWindowView necesitaremos agregar el controlador de eventos en su método MainWindow_Loaded, algo como esto :
((MainWindowViewModel)DataContext).ShowDialogEvent += ShowDialog;
(ShowDialog - similar al método ''ModifyButton_Click'').
Entonces mis preguntas son: 1. ¿Ves algún otro enfoque? 2. ¿Crees que uno de los enumerados es bueno o malo? (¿por qué?)
Cualquier otro pensamiento es bienvenido.
Gracias.
Eche un vistazo a mi solución MVVM actual para mostrar los cuadros de diálogo modales en Silverlight. Resuelve la mayoría de los problemas que mencionas, pero está completamente abstraído de las cosas específicas de la plataforma y puede reutilizarse. Además, no utilicé ningún código subyacente solo vinculante con DelegateCommands que implementan ICommand. El diálogo es básicamente una vista, un control separado que tiene su propio modelo de vista y se muestra desde el modelo de vista de la pantalla principal, pero se desencadena desde la interfaz de usuario a través del enlace DelagateCommand.
Vea la solución completa de Silverlight 4 aquí Diálogos modales con MVVM y Silverlight 4
Estoy un poco tarde, pero las respuestas existentes son insuficientes. Explicaré por qué. En general:
- está bien acceder a ViewModels desde View,
- es incorrecto acceder a Vistas desde ViewModels , porque introduce una dependencia circular y hace que ViewModels sea difícil de probar.
El anwer de Benny Jobigan:
App.Container.Resolve<MyChildView>().ShowDialog();
esto en realidad no soluciona nada. Está accediendo a su Vista desde ViewModel de una manera estrictamente acoplada. La única diferencia con el new MyChildView().ShowDialog()
es que new MyChildView().ShowDialog()
una capa de indirección. No veo ninguna ventaja sobre llamar directamente al Ctor de MyChildView.
Sería más claro si utilizó la interfaz para la vista:
App.Container.Resolve<IMyChildView>().ShowDialog();`
Ahora el ViewModel no está estrictamente acoplado a la vista. Sin embargo, me resulta bastante impráctico crear una interfaz para cada vista.
el anwer de arconaut:
Messenger.Default.Send(new DialogMessage(...));
es mejor. Parece que Messenger o EventAggregator u otros patrones de pub / sub son soluciones universales para todos en MVVM :) La desventaja es que es más difícil depurar o navegar a DialogMessageHandler
. Es demasiado indirecto. Por ejemplo, ¿cómo leería la salida del Diálogo? modificando DialogMessage?
Mi solución:
puede abrir la ventana desde MainWindowViewModel de esta manera:
var childWindowViewModel = new MyChildWindowViewModel(); //you can set parameters here if necessary
var dialogResult = DialogService.ShowModal(childWindowViewModel);
if (dialogResult == true) {
//you can read user input from childWindowViewModel
}
DialogService toma solo ViewModel del diálogo, por lo que sus viewmodels son totalmente independientes de las Vistas. En tiempo de ejecución, DialogService puede encontrar la vista apropiada (usando la convención de nomenclatura por ejemplo) y lo muestra, o puede ser burlado fácilmente en pruebas unitarias.
en mi caso, uso estas interfaces:
interface IDialogService
{
void Show(IDialogViewModel dialog);
void Close(IDialogViewModel dialog);
bool? ShowModal(IDialogViewModel dialog);
MessageBoxResult ShowMessageBox(string message, string caption = null, MessageBoxImage icon = MessageBoxImage.No...);
}
interface IDialogViewModel
{
string Caption {get;}
IEnumerable<DialogButton> Buttons {get;}
}
donde DialogButton especifica DialogResult o ICommand o ambos.
Estuve pensando en este tema recientemente también. Esta es una idea que tuve si usa Unity en su proyecto como un ''contenedor'' o lo que sea para la inyección de dependencia. Supongo que normalmente anularía App.OnStartup()
y crearía su modelo, App.OnStartup()
el modelo y App.OnStartup()
allí, y le daría a cada uno las referencias apropiadas. Usando Unity, le da al contenedor una referencia al modelo, luego usa el contenedor para ''resolver'' la vista. El contenedor de Unity inyecta su modelo de vista, por lo que nunca lo instanciará directamente. Una vez que se resuelva su vista, llame a Show()
en ella.
En un video de ejemplo que vi, el contenedor de Unity se creó como una variable local en OnStartup
. ¿Qué sucede si lo creó como una propiedad pública de solo lectura estática en su clase de aplicación? Luego puede usarlo en su modelo de vista principal para crear sus nuevas ventanas, inyectando automáticamente los recursos que necesite la nueva vista. Algo así como App.Container.Resolve<MyChildView>().ShowDialog();
.
Supongo que de alguna manera podría burlarse del resultado de esa llamada al contenedor de Unity en sus pruebas. Alternativamente, quizás podría escribir métodos como ShowMyChildView()
en la clase de la aplicación, que básicamente hace lo que describí anteriormente. Puede ser fácil App.ShowMyChildView()
una llamada a App.ShowMyChildView()
ya que simplemente devolvería un bool?
¿eh?
Bueno, eso podría no ser mejor que usar el new MyChildView()
, pero es una pequeña idea que tuve. Pensé en compartirlo. =)
Mi enfoque es similar al de Adrianm. Sin embargo, en mi caso, el controlador nunca funciona con los tipos de vista concretos. El controlador está completamente desacoplado de la vista, de la misma forma que el modelo de vista.
Cómo funciona esto se puede ver en el ejemplo ViewModel de WPF Application Framework (WAF) .
.
Atentamente,
jbe
Utilizo un controlador que maneja toda la información que pasa entre las vistas. Todos los modelos de vista usan métodos en el controlador para solicitar más información que se puede implementar como cuadros de diálogo, otras vistas, etc.
Se ve algo como esto:
class MainViewModel {
public MainViewModel(IView view, IModel model, IController controller) {
mModel = model;
mController = controller;
mView = view;
view.DataContext = this;
}
public ICommand ShowCommand = new DelegateCommand(o=> {
mResult = controller.GetSomeData(mSomeData);
});
}
class Controller : IController {
public void OpenMainView() {
IView view = new MainView();
new MainViewModel(view, somemodel, this);
}
public int GetSomeData(object anyKindOfData) {
ShowWindow wnd = new ShowWindow(anyKindOfData);
bool? res = wnd.ShowDialog();
...
}
}