2010-12-08 29 views
2

j'ai mis en place une triade MVP en utilisant le modèle de vue passif - à savoir la vue ne contient que des accesseurs simples et setters. Cependant, j'ai de la difficulté à séparer les données de vue et les données de modèle. En particulier lors de la manipulation d'un changement dans l'état d'affichage.MVP passif View - afficher les données de maintien et les données de modèle distinct

La triade est utilisée pour permettre à l'utilisateur de sélectionner une partie d'une liste. La liste des pièces est fournie par le modèle avec chaque partie identifiée de façon unique par un identifiant unique.

permet de dire que les parties ressemblent à ceci:

class Part 
{ 
    int ID; // this code uniquely identifies the part within the model 
    String partCode; 
    String description; 
    double voltage; 
} 

La vue affiche la liste à l'utilisateur et leur permet de sélectionner une partie

La liste est affichée dans une DataGridView et une partie est sélectionnée en cliquant sur une ligne dans le dataGridView.

L'ID est de ne pas être affiché à l'utilisateur et n'est la tension, donc le modèle crée un DataTable qui contient juste la partCode et la description. Ce DataTable est affecté par le présentateur à une propriété de la vue qui correspond à la propriété DataSource de DataGridView.

class Presenter 
{ 
    IView _view; 
    IModel _model; 

    //.../// 

    _view.Data = _model.GetFilteredData(); 
} 

class Model 
{ 
    public DataTable GetFilteredData() 
    { 
     // create a DataTable with the partCode and Description columns only 
     // return DataTable 
    } 
} 

class View //winform 
{ 
     public DataTable Data 
     { 
      set 
      { 
       this.dataGridView.Source = value; 
      } 
     } 
} 

Jusqu'ici tout va bien. La vue affiche les données filtrées dans DataGridView.

Le problème que j'ai est de retourner la partie sélectionnée par l'utilisateur.

La vue n'a aucune connaissance de l'ID unique car elle n'est pas affichée et les autres informations ne peuvent pas être garanties comme étant uniques - il n'est donc pas possible d'identifier de manière unique la pièce sélectionnée.

Essentiellement, je suis en train de convertir des données d'affichage (la ligne sélectionnée) pour modéliser des données (la partie sélectionnée) sans un composant en utilisant les données des autres.

Jusqu'à présent, j'ai les solutions suivantes:

1) La vue est passé avec succès un DataTable qui contient l'ID et filtre alors l'affichage pour qu'il ne soit pas affiché à l'utilisateur. Il est alors trivial de retourner un identifiant pour la ligne sélectionnée. Le problème ici est que j'ai maintenant pollué la vue avec une logique qui n'a pas été testée (le filtrage de l'affichage).

2) L'affichage retourne l'index de ligne et le modèle correspond à l'indice d'une ligne dans les données d'origine. Cela signifierait que l'ordre dans la vue ne change jamais, ce qui, dans la mesure du possible, restreint la façon dont la vue peut montrer (et manipuler) les données. Cela pollue également le modèle avec des données d'affichage (l'index de ligne).

public int RowIndexSelected { get; private set; } 

    //...// 

    private void gridParts_CellEnter(object sender, DataGridViewCellEventArgs e) 
    { 
     if (SelectedPartChangedEvent != null) 
     { 
      RowIndexSelected = e.RowIndex; 

      SelectedPartChangedEvent();    
     } 
    } 

3) Une variation sur (2). Créez un objet adaptateur pour s'asseoir entre le présentateur et la vue. Déplacez la ligne vers le code de conversion d'ID du modèle à l'adaptateur. Le présentateur gère ensuite l'événement modifié par la partie dataGridAdapters.

public PartSelectDataGridAdapter(IPartSelectView view, PartCollection data) 
    { 
     _view = view; 
     _data = data; 

     _view.SelectedPanelChangedEvent += HandleSelectedPartChanged; 
    } 

    void HandleSelectedPartChanged() 
    { 
     int id = _data[_view.RowIndexSelected].ID; 

     if (SelectedPartChanged != null) 
     { 
      SelectedPartChanged(id); 
     } 
    } 

À l'heure actuelle im apprentissage vers 3 car il est testable, maintient la logique à partir des données de vue et vue sur le modèle et le présentateur.

Comment aborderiez-vous cela - y a-t-il une meilleure solution?

+0

Je devrais ajouter que j'ai suivi la méthodologie Presenter First décrite ici http://www.atomicobject.com/pages/Presenter+First. – Kildareflare

Répondre

1

L'ID est de ne pas être affiché à l'utilisateur et ni est la tension, donc le modèle crée un DataTable qui contient juste la partCode et description.

solution simple: faire créer une colonne d'identité dans le datatable et hide it in in the datagrid view.

0

Je pense que vous avez mal compris tout le concept ici!

C'est le présentateur qui devrait gérer cela, pas le modèle. Le modèle devrait se concentrer uniquement sur sa seule responsabilité, sinon, vous gardez la vue et le modèle trop près!

Ma suggestion est de garder une colonne cachée dans votre tableau a passer l'événement de la ligne sélectionnée à votre présentateur, puis laisser le présentateur gérer le travail!

Ce sera l'utilisation correcte de MVP.

+0

En supposant que le présentateur indique à la vue quelle colonne masquer cela nécessiterait toujours du code dans la vue pour masquer la colonne ID, n'est-ce pas? Ou existe-t-il un moyen de masquer une colonne sur le DataTable lui-même. Je veux garder la vue passive et limiter l'interface aux seuls getters et setters. – Kildareflare

+1

Je ne vois pas de gros problème en laissant la vue cacher la colonne car elle est strictement relatée à l'état d'affichage/comportement. –

+1

Si vous voulez le faire par le livre, vous pouvez laisser le présentateur explicitement dire à la vue de cacher la colonne ID. Ainsi, la vue elle-même ne permet pas de masquer la colonne, elle agit uniquement comme indiqué par le présentateur. – Marijn

2

J'ai posté une solution simple plus tôt; ceci est une réponse plus détaillée à la question

Y a-t-il une raison pour laquelle vous ne voulez pas passer un List<Part> à la vue?

Vous pouvez configurer la grille pour masquer la colonne d'id et de tension. Vous pouvez simplement obtenir l'objet sélectionné à partir de la source de liaison dans la vue. Le présentateur peut interroger l'affichage pour cette sélection ou la vue peut appeler un SelectionChanged(Part selected) sur le présentateur. Cela signifie que vous ne suivez plus strictement le modèle passive-view, mais un supervising controller, car votre vue est désormais connue du modèle.

Si vous n'aimez pas cela, vous pouvez introduire un modèle d'affichage , ce que vous faites déjà implicitement avec votre DataTable. (Ce n'est pas nécessairement mauvais, btw.)

Dans votre exemple, les classes de modèle connaissent les modèles de vue, car vous avez des méthodes sur le modèle qui les génèrent. Je vous conseille d'inverser cette relation: créez des méthodes sur votre modèle de vue qui dépendent de vos objets de modèle. De cette façon, vous garderez vos classes de modèle belles et propres et indépendantes de toutes les données de l'interface utilisateur nécessaires dans la couche de présentation. Lorsque vous utilisez le modèle de vue/contrôleur de supervision, envisagez d'abandonner le concept DataTable en faveur de classes simples.

EDIT: alternative pour faire le point de vue complètement ignorant du modèle:

Construct une instance de cette classe dans le présentateur, où vous connaissez le modèle de modèle à la fois et vue:

public class PartViewModel 
{ 
    object PartModel { get; set; } 
    string Name { get; set; } 
    string Description { get; set; } 
} 

Transmettez un List<PartViewModel> en tant que source de données à DataGridView. Vous pouvez renvoyer l'objet PartViewModel sélectionné au présentateur (en utilisant un événement ou en utilisant une méthode). Le présentateur sait qu'il peut renvoyer la propriété PartModel à une instance Part. La vue n'a pas besoin de savoir quoi que ce soit sur le modèle, comme vous le dites.Mais vous pouvez toujours utiliser une identité d'objet simple dans le présentateur, en évitant une recherche "compliquée" en utilisant des identifiants.

Avec un rappel du présentateur:

interface IPartListPresenter 
{ 
    // other methods 
    void SelectedPartChanged(PartViewModel nowSelected); 
} 

Si l'on suppose partBindingSource est BindingSource le gridview est connecté, vous pouvez gérer l'événement CurrentChanged de partBindingSource comme ceci:

private void partBindingSource_CurrentChanged(object sender, EventArgs e) 
{ 
    _presenter.SelectedPartChanged(partBindingSource.Current as PartViewModel); 
} 

Dans le présentateur:

public void SelectedPartChanged(PartViewModel nowSelected) 
{ 
    if(nowSelected == null) 
    { 
    return; 
    } 
    part myPart = (Part) nowSelected.Part; 
    // dos stuff 
} 

Espérons que cela aide.

+1

Je ne veux pas passer Liste à la vue depuis que je veux que la vue ne sache rien sur les objets de domaine. Aussi parce que je devrais ajouter le code (non testé) à la vue pour formater la partie pour l'affichage. Je veux garder la vue aussi "mince" que possible. – Kildareflare

+0

Je comprends ce que vous voulez dire. Dans ce cas, je pense que vous êtes sur la bonne voie avec votre approche du modèle de vue. Jetez un oeil à mes suggestions dans cette réponse et voir si elles sont utiles pour vous. – Marijn

+0

Vous pourriez aimer [voir les modèles en combinaison avec AutoMapper] (http://www.google.nl/search?q=automapper+viewmodel) – Marijn