Pregunta

He estado desarrollando una aplicación LOB muy grande con mi versión de M-V-VM que llamo M-V-MC (Model-View-ModelController), que es una especie de combinación entre M-V-C y M-V-VM. Había publicado esta respuesta con respecto a cómo las vistas se crean instancias en MV-VM a la pregunta " qué-son-los-errores-más-comunes-hechos-en-wpf-desarrollo " ;.

Sam hizo el siguiente comentario con respecto a mi respuesta:

  

Esto crea una pregunta de seguimiento: cómo   creas las vistas? yo suelo   RelayCommands para enlazar acciones de la   ver al ViewModel, por lo que la vista   Ni siquiera sabe que tiene una acción.   despedido, no sabe que debe abrir un   nueva vista. Solución: crear un evento en   la máquina virtual para la vista a la que se suscriba?

Cuando originalmente comencé el desarrollo de MV-VM tuve la idea de que TODO debería vivir en el ViewModel, y he estudiado muchos ejemplos de tipos como Josh Smith y Karl Shifflett . Sin embargo, todavía tengo que dar un buen ejemplo de cuándo un comando necesita vivir en el modelo de visualización.

Por ejemplo, supongamos que tengo un ListView que muestra Clientes y un botón en el que hago clic para permitir que edite el cliente seleccionado actualmente. El ListView (Vista) está vinculado a un CustomerVM (ViewModel). Al hacer clic en el botón, se activa el comando EditCustomerCommand, que abre una ventana emergente que me permite editar todas las propiedades de CustomerVM. ¿Dónde vive EditCustomerCommand? Si implica abrir una ventana, (funcionalidad UI), ¿no debería definirse en el código subyacente de la vista? alt text

¿Alguien tiene algún ejemplo de cuándo debo definir un comando en la vista en lugar de ver el modelo?

Matthew Wright a continuación:

  

Nuevo y eliminar de una lista sería   buenos ejemplos En esos casos, un espacio en blanco.   Se agrega el registro o el registro actual.   es eliminado por el ViewModel. Ninguna   La acción tomada por la vista debe estar en   respuesta a esos eventos ocurriendo.

Entonces, si hago clic en el nuevo botón, ¿qué sucede? Una nueva instancia de CustomerVM es creada por Parent ViewModel y se agrega a su colección, ¿verdad? Entonces, ¿cómo se abrirá mi pantalla de edición? La vista debe crear una nueva instancia de Customer ViewModel y pasarla al método ParentVM.Add (newlyCreatedVM), ¿verdad?

Supongamos que elimino un registro de cliente a través de DeleteCommand que vive en la VM. la máquina virtual llama a la capa empresarial e intenta eliminar el registro. No puede, por lo que devuelve un mensaje a la máquina virtual. Quiero mostrar este mensaje en el cuadro de diálogo. ¿Cómo consigue la vista que el mensaje salga de la acción de comando?

¿Fue útil?

Solución

Nunca pensé que me vería siendo citado en una pregunta.

Reflexioné sobre esta pregunta durante algún tiempo y tomé una decisión bastante pragmática para mi base de código:

En mi base de código, se llama a ViewModel cuando ocurren las acciones, y quería que siguiera siendo así. Además, no quiero que ViewModel controle las vistas.

¿Qué hice?
He añadido un controlador para la navegación:

public interface INavigation
{
  void NewContent(ViewModel viewmodel);
  void NewWindow(ViewModel viewmodel);
}

Este controlador contiene dos acciones: NewContent () muestra contenido nuevo en la ventana actual, NewWindow () crea una nueva ventana, la llena con el contenido y la muestra.
Por supuesto, mis modelos de vista no tienen ni idea de qué vista mostrar. Pero sí saben qué modelo de vista quieren mostrar, por lo que de acuerdo con su ejemplo cuando se ejecuta DeleteCommand, llamará a la función de servicio de navegación NewWindow (nuevo ValidateCustomerDeletedViewModel ()) para mostrar una ventana que indica "el cliente". se ha eliminado '(exageración para este simple cuadro de mensaje, pero sería fácil tener una función especial de navegador para los mensajes simples).

¿Cómo obtiene el viewmodel el servicio de navegación?

Mi clase de modelo de vista tiene una propiedad para el controlador de navegación:

public class ViewModel
{
  public INavigation Navigator { get; set; }
  [...]
}

Cuando un modelo de vista se adjunta a una ventana (o lo que sea que muestre la vista), la ventana establecerá la propiedad del navegador, por lo que el modelo de vista puede llamarla.

¿Cómo crea el navegador la vista al modelo de vista?

Podría tener una lista simple que ver para crear para qué modelo de vista, en mi caso puedo usar una reflexión simple ya que los nombres coinciden:

public static FrameworkElement CreateView(ViewModel viewmodel)
{
  Type vmt = viewmodel.GetType();
  // big bad dirty hack to get the name of the view, but it works *cough*
  Type vt = Type.GetType(vmt.AssemblyQualifiedName.Replace("ViewModel, ", "View, ")); 
  return (FrameworkElement)Activator.CreateInstance(vt, viewmodel);
}

Por supuesto, la vista necesita un constructor que acepte el modelo de vista como parámetro:

public partial class ValidateCustomerDeletedView : UserControl
{
  public ValidateCustomerDeletedView(ValidateCustomerDeletedViewModel dac)
  {
    InitializeComponent();
    this.DataContext = dac;
  }
}

¿Cómo se ve mi ventana?

Simple: mi ventana principal implementa la interfaz de navegación, y muestra una página de inicio en la creación. Compruébelo usted mismo:

public partial class MainWindow : Window, INavigation
{
  public MainWindow()
  {
    InitializeComponent();
    NewContent(new StartPageViewModel());
  }

  public MainWindow(ViewModel newcontrol)
  {
    InitializeComponent();
    NewContent(newcontrol);
  }

  #region INavigation Member
  public void NewContent(ViewModel newviewmodel)
  {
    newviewmodel.Navigator = this;
    FrameworkElement ui = App.CreateView(newviewmodel);
    this.Content = ui;
    this.DataContext = ui.DataContext;
  }

  public void NewWindow(ViewModel viewModel)
  {
    MainWindow newwindow = new MainWindow(viewModel);
    newwindow.Show();
  }
  #endregion
}

(Esto funciona igual de bien con una ventana de navegación y envuelve la vista en una página)

Por supuesto, esto es comprobable, ya que el controlador de navegación se puede burlar fácilmente.

No estoy realmente seguro de si esta es una solución perfecta, pero funciona bien para mí en este momento. ¡Cualquier idea y comentario son bienvenidos!

Otros consejos

Para su caso de buzón de mensajes borrados, abstraigo los buzones de mensajes a través de una interfaz. Similar a ésto. También inyecto estas interfaces para mi aplicación WPF.

Constructor

    public MyViewModel(IMessage msg)
    {
      _msg = msg;
    }

Luego, en el método de eliminación de método en ViewModel ... algo como

    public void Delete()
    {
      if(CanDelete)
      {
        //do the delete 
      }
      else
      {
        _msg.Show("You can't delete this record");
      }
    }

Esto lo hará verificable, puede conectar implementaciones de IMessage diferentes que en realidad no muestran un cuadro de mensaje. Aquellos pueden simplemente imprimir en la consola, para propósitos de prueba. Obviamente, su aplicación WPF podría tener una implementación como

public class MessageBoxQuestion : IMessage
{
   public void Show(string message)
   {
     MessageBox.Show(message);
   }
}

Hacer esto hace que la prueba de las diferentes rutas (piense en los diálogos Sí / No) sea muy sencilla y directa. Puedes imaginar una confirmación de borrado. Puede usar una instancia concreta del mensaje IM para devolver verdadero / falso para confirmación o burlarse del contenedor durante su prueba.

[Test]
public void Can_Cancel_Delete()
{
  var vm = new ProductViewModel(_cancel);
  ...

}
[Test]
public void Can_Confirm_Delete()
{
  var vm = new ProductViewModel(_yes);
  ...

}

Para su otra pregunta sobre cuándo usar el Comando, instalo las vistas Agregar Nuevo o Detalles desde la Vista en cuestión. Igual que tienes en tu ejemplo. Las vistas solo son instanciadas por otras Vistas en nuestra aplicación. No uso un comando en esos casos. Sin embargo, sí uso las propiedades ViewModel de la vista principal para la vista secundaria.

public void Object_DoubleClick(object sender, EventArgs e)
{
  var detailView = new DetailView(ViewModel.Product);
  detailView.Show();
}

Espero que esto ayude!

Nuevo y eliminar de una lista serían buenos ejemplos. En esos casos, se agrega un registro en blanco o ViewModel elimina el registro actual. Cualquier acción tomada por la vista debe ser en respuesta a los eventos que ocurren.

Una forma sería usar un objeto de parámetro de comando que la capa empresarial pueda modificar y su VM pueda procesar después del comando ejecutado.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top