Domanda

Sto sviluppando un'app LOB molto grande usando il mio stile di M-V-VM che chiamo M-V-MC (Model-View-ModelController), che è una specie di combinazione tra M-V-C e M-V-VM. Avevo pubblicato questa risposta riguardante come le visualizzazioni vengono istanziate in MV-VM alla domanda " cosa-sono-il-più-comuni-errori-made-in-WPF-sviluppo ".

Sam ha formulato il seguente commento sulla mia risposta:

  

Questo crea una domanda di follow-up: come   crei le viste? Io uso   RelayCommands per associare azioni dal   visualizzare il ViewModel, quindi la vista   non sa nemmeno che un'azione abbia   licenziato, non sa che dovrebbe aprire a   nuova vista. Soluzione: creare un evento in   la macchina virtuale a cui la Vista deve iscriversi?

Quando inizialmente ho iniziato lo sviluppo di MV-VM avevo l'idea che TUTTO dovrebbe vivere nel ViewModel e ho studiato molti esempi di ragazzi come Josh Smith e Karl Shifflett . Tuttavia devo ancora trovare un buon esempio di quando un comando deve vivere nel ViewModel.

Ad esempio, supponiamo di avere un ListView che visualizza i clienti e un pulsante su cui faccio clic per consentirmi di modificare il cliente attualmente selezionato. ListView (View) è associato a CustomerVM (ViewModel). Facendo clic sul pulsante si attiva EditCustomerCommand che apre una finestra popup che mi consente di modificare tutte le proprietà di CustomerVM. Dove vive questo EditCustomerCommand? Se implica l'apertura di una finestra (funzionalità dell'interfaccia utente), non dovrebbe essere definito nel code-behind della vista? alt text

Qualcuno ha degli esempi di quando dovrei definire un comando in View rispetto a ViewModel?

Matthew Wright dichiara di seguito:

  

Sarebbe nuovo ed elimina da un elenco   buoni esempi. In questi casi, uno spazio vuoto   viene aggiunto il record o il record corrente   viene eliminato da ViewModel. Qualunque   l'azione intrapresa dal punto di vista dovrebbe essere in   risposta a quegli eventi che si verificano.

Quindi, se faccio clic sul nuovo pulsante, cosa succede? Una nuova istanza di CustomerVM viene creata dal ViewModel padre e aggiunta alla sua raccolta, giusto? Quindi come si aprirà la mia schermata di modifica? La vista dovrebbe creare una nuova istanza di Customer ViewModel e passarla al metodo ParentVM.Add (newlyCreatedVM) giusto?

Supponiamo di eliminare un record cliente tramite DeleteCommand residente nella VM. la VM chiama nel livello aziendale e tenta di eliminare il record. Non può quindi restituire un messaggio alla VM. Voglio visualizzare questo messaggio nella finestra di dialogo. In che modo la vista fa uscire il messaggio dall'azione comando?

È stato utile?

Soluzione

Non avrei mai pensato di vedermi citato in una domanda.

Ho riflettuto su questa domanda per un po 'di tempo e ho preso una decisione piuttosto pragmatica per la mia base di codice:

Nella mia base di codice, ViewModel viene chiamato quando si verificano azioni e volevo che rimanesse così. Inoltre, non voglio che ViewModel controlli le visualizzazioni.

Che cosa ho fatto?
Ho aggiunto un controller per la navigazione:

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

Questo controller contiene due azioni: NewContent () mostra il nuovo contenuto nella finestra corrente, NewWindow () crea una nuova finestra, lo popola con il contenuto e lo mostra.
Naturalmente i miei modelli non hanno idea di quale vista mostrare. Ma sanno quale modello di visualizzazione vogliono mostrare, quindi secondo il tuo esempio quando viene eseguito DeleteCommand, chiamerebbe la funzione del servizio di navigazione NewWindow (new ValidateCustomerDeletedViewModel ()) per mostrare una finestra indicando 'il cliente è stato eliminato "(eccessivo per questa semplice finestra di messaggio, ma sarebbe facile avere una funzione di navigazione speciale per semplici finestre di messaggio).

In che modo il modello di visualizzazione ottiene il servizio di navigazione?

La mia classe viewmodel ha una proprietà per il controller di navigazione:

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

Quando un viewmodel è collegato a una finestra (o qualsiasi altra cosa visualizzi la vista), la finestra imposterà la proprietà Navigator, in modo che il viewmodel possa chiamarla.

In che modo il navigatore crea la vista per il modello di visualizzazione?

Potresti avere un semplice elenco che vista creare per quale viewmodel, nel mio caso posso usare un semplice riflesso poiché i nomi corrispondono:

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);
}

Ovviamente la vista necessita di un costruttore che accetta il modello di visualizzazione come parametro:

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

Come appare la mia finestra?

Semplice: la mia finestra principale implementa l'interfaccia INavigation e mostra una pagina iniziale sulla creazione. Guarda tu stesso:

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
}

(Funziona ugualmente bene con una NavigationWindow e racchiude la vista in una Pagina)

Naturalmente questo è testabile, poiché il controller di navigazione può essere deriso facilmente.

Non sono davvero sicuro se questa sia una soluzione perfetta, ma funziona bene per me in questo momento. Eventuali idee e commenti sono ben accetti!

Altri suggerimenti

Per il caso della finestra di messaggio di eliminazione, astraggo le finestre di messaggio tramite un'interfaccia. Simile a questo Iniezione anche queste interfacce per la mia app WPF.

Constructor

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

Quindi, nel metodo metodo di eliminazione sul ViewModel ... qualcosa di simile

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

Questo lo renderà testabile, puoi collegare diverse implementazioni di IMessage che in realtà non mostrano una finestra di messaggio. Quelli potrebbero semplicemente stampare sulla console, a scopo di test. Ovviamente la tua app WPF potrebbe avere un'implementazione come

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

In questo modo, testare i diversi percorsi (pensa a dialoghi Sì / No) è molto semplice e diretto. Puoi immaginare una conferma di cancellazione. È possibile utilizzare un'istanza concreta dell'IMessage per restituire vero / falso per conferma o deridere il contenitore durante il test.

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

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

}

Per l'altra domanda su quando usare il comando, istanzio le viste Aggiungi nuovo o Dettagli dalla Vista in questione. Proprio come hai nel tuo esempio. Le visualizzazioni sono istanziate solo da altre visualizzazioni nella nostra app. Non uso un comando in questi casi. Uso tuttavia le proprietà ViewModel della vista padre per la vista figlio.

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

Spero che questo aiuti!

Nuovi ed eliminati da un elenco sarebbero buoni esempi. In questi casi, viene aggiunto un record vuoto o il record corrente viene eliminato da ViewModel. Qualsiasi azione intrapresa dal punto di vista dovrebbe essere in risposta a quegli eventi che si verificano.

Un modo sarebbe quello di utilizzare un oggetto parametro comando che il livello aziendale può modificare e la macchina virtuale può elaborare dopo l'esecuzione del comando.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top