Question

J'ai développé une très grande application métier utilisant ma version de M-V-VM, que j'appelle M-V-MC (Model-View-ModelController), qui est une sorte de combinaison entre M-V-C et M-V-VM. J'avais posté cette réponse sur comment les vues sont instanciées dans MV-VM à la question " Sam a fait le commentaire suivant concernant ma réponse:

  

Cela crée une question de suivi: comment   créez-vous les vues? j'utilise   RelayCommands pour lier des actions de la   voir le ViewModel, donc la vue   ne sait même pas qu'une action a   licencié, ne sait pas qu'il devrait ouvrir une   nouvelle vue. Solution: créer un événement dans   la VM pour la vue à laquelle s'abonner?

Lorsque j'ai commencé le développement de MV-VM, j'avais cette idée que TOUT devrait vivre dans le ViewModel et j'ai étudié beaucoup d'exemples de gars comme Josh Smith et Karl Shifflett . Cependant, je n'ai pas encore trouvé un bon exemple de cas dans lequel une commande doit vivre dans le ViewModel.

Par exemple, supposons que j'ai un ListView qui affiche les clients et un bouton sur lequel je clique pour me permettre de modifier le client actuellement sélectionné. ListView (View) est lié à un CustomerVM (ViewModel). Un clic sur le bouton déclenche la commande EditCustomerCommand qui ouvre une fenêtre contextuelle qui me permet d’éditer toutes les propriétés de CustomerVM. Où habite cette EditCustomerCommand? Si cela implique d'ouvrir une fenêtre (fonctionnalité de l'interface utilisateur), ne devrait-il pas être défini dans le code-behind de la vue? texte alt

Quelqu'un at-il des exemples du moment où je devrais définir une commande dans la vue par rapport au modèle de vue?

Matthew Wright indique ci-dessous:

  

Nouveau et supprimer d'une liste serait   bons exemples. Dans ces cas, un blanc   enregistrement est ajouté ou l'enregistrement en cours   est supprimé par le ViewModel. Tout   l'action prise par la vue devrait être en   réponse à ces événements.

Donc si je clique sur le nouveau bouton, que se passe-t-il? Une nouvelle instance de CustomerVM est créée par le parent ViewModel et ajoutée à sa collection, n'est-ce pas? Alors, comment mon écran de montage s’est-il ouvert? La vue doit créer une nouvelle instance de Customer ViewModel et la transmettre à la méthode ParentVM.Add (latestCreatedVM), n'est-ce pas?

Supposons que je supprime un enregistrement de client via la commande DeleteCommand installée sur la machine virtuelle. la machine virtuelle appelle la couche de gestion et tente de supprimer l'enregistrement. Il ne peut donc pas renvoyer un message à la machine virtuelle. Je veux afficher ce message dans la boîte de dialogue. Comment la vue extrait-elle le message de l'action de commande?

Était-ce utile?

La solution

Je n'aurais jamais pensé me voir cité dans une question.

Je réfléchissais moi-même à cette question depuis quelque temps et prenais une décision plutôt pragmatique pour ma base de code:

Dans ma base de code, le ViewModel est appelé lorsque des actions se produisent et je voulais que cela reste ainsi. De plus, je ne souhaite pas que ViewModel contrôle les vues.

Qu'est-ce que j'ai fait?
J'ai ajouté un contrôleur pour la navigation:

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

Ce contrôleur contient deux actions: NewContent () affiche un nouveau contenu dans la fenêtre en cours, NewWindow () crée une nouvelle fenêtre, le remplit avec le contenu et l'affiche.
Bien sûr, mes modèles de vue n’ont aucune idée de la vue à afficher. Mais ils savent quel modèle de vue ils veulent afficher. Ainsi, selon votre exemple, lorsque DeleteCommand est exécuté, il appelle la fonction de service de navigation NewWindow (new ValidateCustomerDeletedViewModel ()) pour afficher une fenêtre indiquant "le client a été supprimé '(surcharge pour cette boîte de message simple, mais il serait facile de disposer d'une fonction de navigation spéciale pour les boîtes de message simples).

Comment le modèle de vue obtient-il le service de navigation?

Ma classe viewmodel a une propriété pour le contrôleur de navigation:

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

Lorsqu'un modèle de vue est associé à une fenêtre (ou quel que soit l'affichage de la vue), la fenêtre définit la propriété Navigator afin que le modèle puisse l'appeler.

Comment le navigateur crée-t-il la vue dans le modèle de vue?

Vous pourriez avoir une simple liste des vues à créer pour quel modèle de vue. Dans mon cas, je peux utiliser une réflexion simple car les noms correspondent:

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

Bien sûr, la vue nécessite un constructeur acceptant le modèle de vue en tant que paramètre:

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

À quoi ressemble ma fenêtre?

Simple: ma fenêtre principale implémente l'interface INavigation et affiche une page de démarrage lors de la création. Voyez vous-même:

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
}

(Cela fonctionne aussi bien avec une NavigationWindow et en encapsulant la vue dans une page)

Bien sûr, cela est testable, car le contrôleur de navigation peut être facilement moqué.

Je ne suis pas vraiment sûr que ce soit une solution parfaite, mais cela fonctionne bien pour moi en ce moment. Toutes les idées et commentaires sont les bienvenus!

Autres conseils

Pour votre cas, supprimer les boîtes de message via une interface. Semblable à cela. J'injecte également ces interfaces pour mon application WPF.

Constructeur

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

Ensuite, dans la méthode delete de la méthode ViewModel ... quelque chose comme

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

Cela le rend testable, vous pouvez connecter une autre implémentation IMessage qui n’affiche pas de boîte de message. Ceux-ci pourraient simplement imprimer sur la console, à des fins de test. Évidemment, votre application WPF peut avoir une implémentation telle que

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

Faire ceci rend le test des différentes routes (pensez aux dialogues Oui / Non) très facile et simple. Vous pouvez imaginer une confirmation de suppression. Vous pouvez utiliser une instance concrète de IMessage pour renvoyer true / false à des fins de confirmation ou simuler le conteneur lors de votre test.

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

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

}

Pour votre autre question sur l'utilisation de la commande, j'instancie les vues Ajouter un nouveau ou Détails à partir de la vue en question. Tout comme vous avez dans votre exemple. Les vues ne sont instanciées que par d'autres vues dans notre application. Je n'utilise pas de commande dans ces cas. J'utilise cependant les propriétés ViewModel de la vue parente à la vue par enfant.

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

J'espère que ça aide!

Nouveau et supprimer d'une liste seraient de bons exemples. Dans ces cas, un enregistrement vide est ajouté ou l'enregistrement actuel est supprimé par ViewModel. Toute action entreprise par la vue doit être en réponse à ces événements.

Une solution consiste à utiliser un objet paramètre de commande que la couche de gestion peut modifier et que votre machine virtuelle peut traiter après l'exécution de la commande.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top