Question

Je suis la conception d'une interface graphique qui a l'idée de base suivante (de la même calquée sur l'aspect et la sensation de base de Visual Studio):

  1. Navigation fichier
  2. sélecteur de commande (pour sélectionner les éléments à afficher dans le composant Editor)
  3. Editeur
  4. Logger (erreurs, avertissements, confirmation, etc.)

Pour l'instant, je vais utiliser un TreeView pour la navigation de fichiers, un ListView pour sélectionner les commandes à afficher dans l'éditeur et un RichTextBox pour l'enregistreur. L'éditeur aura 2 types de modes d'édition en fonction de ce qui est sélectionné dans l'arborescence. L'éditeur sera soit un RichTextBox pour l'édition de texte manuellement dans les fichiers, ou il sera un groupe avec Glisser / Déposer DataGridViews et sous-zones de texte pour l'édition dans ce panneau.

J'essaie de suivre le modèle de conception Voir passif pour une séparation complète du modèle de vue et vice-versa. La nature de ce projet est que tout ajout I composant est soumis à modification / suppression. En tant que tel, je dois là pour l'indépendance d'un contrôle donné à l'autre. Si aujourd'hui, je me sers d'un TreeView pour la navigation de fichiers, mais demain on me dit d'utiliser quelque chose d'autre, alors je veux mettre en œuvre un nouveau contrôle avec une relative facilité.

Je ne comprends tout simplement pas comment structurer le programme. Je comprends un présentateur par le contrôle, mais je ne sais pas comment faire fonctionner telle que j'ai une vue (l'interface graphique ensemble du programme) avec les contrôles (sous-vues) telle que la TOTALITÉ View est remplaçable ainsi que l'individu les contrôles qui reflètent mon modèle.

Dans la vue principale, qui est censé être léger par passif Voir les normes, dois-je mettre en œuvre les sous-vues individuellement? Si oui, dire que j'ai un INavigator d'interface abstraite le rôle de l'objet Navigator. Le navigateur aura besoin d'un présentateur et un modèle pour agir entre le navigateur View et la vue principale. Je sens que je suis à me perdre quelque part dans le jargon de modèle de conception.

se trouve La question la plus liée de la même , mais il ne fonctionne pas répondre à ma question de manière suffisamment détaillée.

ce que quelqu'un s'il vous plaît me aider à comprendre comment « structure » de ce programme? Je vous remercie de toute aide.

Merci,

Daniel

Était-ce utile?

La solution

L'abstraction est bonne, mais il est important de se rappeler que, à un moment quelque chose a savoir une chose ou deux sur une chose ou deux, ou bien nous allons avoir juste un tas de Legos bien soustraites assis sur le sol au lieu de les assembler étant dans une maison.

Une inversion de contrôle / injection de dépendance / Flippy-fofolle-envers-tout-nous sommes-calling-it-ce-semaine récipient comme autofac peut vraiment aider à reconstituer tout cela ensemble.

Quand je jette ensemble une application WinForms, je finissent généralement avec un motif répétitif.

Je vais commencer par un fichier Program.cs qui configure le conteneur Autofac et va chercher alors une instance de la MainForm de celui-ci, et montre la MainForm. Certaines personnes appellent cela la coquille ou l'espace de travail ou le bureau, mais en tout cas il est « la forme » qui a la barre de menus et affiche soit des fenêtres enfants ou des contrôles utilisateur des enfants, et quand il se ferme, l'application se ferme.

est le suivant MainForm mentionné ci-dessus. Je fais les choses de base comme le glisser-déposer certains SplitContainers et MenuBars et comme dans le concepteur visuel Visual Studio, puis-je commencer à obtenir de fantaisie dans le code: Je vais avoir certaines interfaces clés « Injecté » dans le constructeur de la MainForm pour que je peut les utiliser, de sorte que mon MainForm peut orchestrer les contrôles enfants sans vraiment avoir à savoir que beaucoup sur eux.

Par exemple, je pourrais avoir une interface IEventBroker qui permet aux différents composants publier ou de souscrire à des « événements » comme BarcodeScanned ou ProductSaved. Cela permet des parties de l'application pour répondre aux événements de manière à couplage lâche, sans avoir à compter sur le câblage des événements .NET traditionnels. Par exemple, le EditProductPresenter qui va de pair avec mon EditProductUserControl pourrait dire this.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah)) et la IEventBroker vérifiait sa liste d'abonnés pour cet événement et d'appeler leurs callbacks. Par exemple, le ListProductsPresenter pourrait écouter cet événement et mettre à jour dynamiquement le ListProductsUserControl qu'il est attaché. Le résultat net est que si un utilisateur enregistre un produit dans un contrôle utilisateur, le présentateur d'un autre contrôle utilisateur peut réagir et se mettre à jour si elle se trouve être ouverte, sans que ni le contrôle d'avoir à être au courant de l'existence de l'autre, et sans MainForm avoir à orchestrer cet événement.

Si je suis la conception d'une application MDI, je pourrais avoir la MainForm mettre en œuvre une interface IWindowWorkspace qui a des méthodes de Open() et Close(). Je pourrais injecter cette interface dans mes divers présentateurs pour leur permettre de fenêtres supplémentaires ouvrir et fermer sans qu'ils soient au courant de l'MainForm directement. Par exemple, le ListProductsPresenter pourrait vouloir ouvrir un EditProductPresenter et correspondant EditProductUserControl lorsque l'utilisateur double-clique sur une ligne dans une grille de données dans un ListProductsUserControl. Il peut faire référence à un IWindowWorkspace - qui est en fait le MainForm, mais il n'a pas besoin de savoir que - et Open(newInstanceOfAnEditControl) d'appel et le contrôle suppose que a été montré à l'endroit approprié de l'application d'une certaine manière. (La mise en œuvre de MainForm serait, sans doute, échanger le contrôle en vue sur un endroit du panneau.)

Mais comment diable serait le ListProductsPresenter créer cette instance de l'EditProductUserControl? usines de délégués de Autofac sont une vraie joie ici, puisque vous pouvez simplement injecter un délégué dans le présentateur et Autofac se câbler automagiquement vers le haut comme si elle était une usine (pseudocode suit):


public class EditProductUserControl : UserControl
{
    public EditProductUserControl(EditProductPresenter presenter)
    {
        // initialize databindings based on properties of the presenter
    }
}

public class EditProductPresenter
{
    // Autofac will do some magic when it sees this injected anywhere
    public delegate EditProductPresenter Factory(int productId);

    public EditProductPresenter(
        ISession session, // The NHibernate session reference
        IEventBroker eventBroker,
        int productId)    // An optional product identifier
    {
        // do stuff....
    }

    public void Save()
    {
        // do stuff...
        this.eventBroker.Publish("ProductSaved", new EventArgs(this.product));
    }
}

public class ListProductsPresenter
{
    private IEventBroker eventBroker;
    private EditProductsPresenter.Factory factory;
    private IWindowWorkspace workspace;

    public ListProductsPresenter(
        IEventBroker eventBroker,
        EditProductsPresenter.Factory factory,
        IWindowWorkspace workspace)
    {
       this.eventBroker = eventBroker;
       this.factory = factory;
       this.workspace = workspace;

       this.eventBroker.Subscribe("ProductSaved", this.WhenProductSaved);
    }

    public void WhenDataGridRowDoubleClicked(int productId)
    {
       var editPresenter = this.factory(productId);
       var editControl = new EditProductUserControl(editPresenter);
       this.workspace.Open(editControl);
    }

    public void WhenProductSaved(object sender, EventArgs e)
    {
       // refresh the data grid, etc.
    }
}

Ainsi, le ListProductsPresenter connaît l'ensemble des fonctionnalités de Edit (à savoir, le présentateur d'édition et le contrôle modifier utilisateur) - une cela est parfaitement bien, ils vont main dans la main - mais il n'a pas besoin de connaître tous les dépendances de l'ensemble des fonctionnalités de Edit, comptant plutôt sur un délégué fourni par Autofac à résoudre toutes ces dépendances pour elle.

En général, je trouve que j'ai une à une correspondance entre un « modèle présentateur / vue / contrôleur de supervision » (ne soyons pas trop pris sur les différences à la fin de la journée, ils sont tous assez semblables) et un "UserControl / Form". Le UserControl accepte le modèle présentateur / view / contrôleur dans son constructeur et databinds lui-même comme il convient, de reporter au présentateur autant que possible. Certaines personnes cachent la UserControl du présentateur via une interface, comme IEditProductView, ce qui peut être utile si la vue est pas complètement passif. J'ai tendance à utiliser pour tout databinding si la communication se fait via INotifyPropertyChanged et ne prend pas la peine.

Mais, vous vous rendre la vie beaucoup plus facile si le présentateur est sans vergogne lié à la vue. Est-ce une propriété dans votre modèle objet maille pas avec databinding? Exposez une nouvelle propriété si elle le fait. Vous n'allez avoir un EditProductPresenter et un EditProductUserControl avec une mise en page et que vous souhaitez ensuite écrire une nouvelle version du contrôle de l'utilisateur qui fonctionne avec le même présentateur. Vous aurez juste modifier les deux, ils sont pour toutes fins utiles une unité, une caractéristique, le présentateur n'existant car il est facilement testable et le contrôle de l'utilisateur n'est pas.

Si vous voulez une fonction soit remplaçable, vous devez faire abstraction de la fonction entière en tant que telle. Donc, vous pourriez avoir une interface INavigationFeature que vos pourparlers de MainForm à. Vous pouvez avoir un TreeBasedNavigationPresenter qui implémente INavigationFeature et consommés par un TreeBasedUserControl. Et vous pourriez avoir un CarouselBasedNavigationPresenter qui a également des outils INavigationFeature et consommés par un CarouselBasedUserControl. Les contrôles utilisateur et les présentateurs aller toujours main dans la main, mais votre MainForm ne pas se soucier si elle est en interaction avec une vue arborescente ou un à base de carrousel, et vous pouvez les échanger sans MainForm être le plus sage.

En conclusion, il est facile de vous confondre. Tout le monde est pédant et utilise une terminologie légèrement différente pour qu'elles véhiculent des différences subtiles (et souvent sans importance) entre ce que sont semblables motifs architecturaux. À mon humble avis, l'injection de dépendance fait des merveilles pour la création d'applications composables, extensible, étant donné que le couplage est maintenu vers le bas; la séparation des fonctions en « présentateurs / modèles / contrôleurs vue » et « vues / contrôles utilisateur / formulaires » fait des merveilles pour la qualité logique puisque la plupart est tiré dans l'ancien, ce qui lui permet d'être facilement unité testée; et en combinant les deux principes semble vraiment être ce que vous cherchez, vous êtes juste confus sur la terminologie.

Ou, je pourrais être pleine. Bonne chance!

Autres conseils

Je sais que cette question est près de 2 ans, mais je me trouve dans une situation très similaire. Comme vous, je l'ai parcouru l'Internet pendant des jours et pas trouvé un exemple concret qui correspond à mes besoins - plus je fouillé plus je continuais de revenir aux mêmes sites, encore et encore au point où j'avais environ 10 pages de pourpre liens dans Google!

Quoi qu'il en soit, je me demandais si vous êtes jamais venu avec une solution satisfaisante au problème? Je vais décrire comment je suis allé à ce sujet jusqu'à présent, après ce que j'ai lu au cours de la dernière semaine:

Mes objectifs sont les suivants: forme passive, présentateur d'abord (le présentateur instancie le formulaire de sorte que la forme n'a aucune connaissance du présentateur de lui) Appeler des méthodes dans le présentateur en soulevant des événements sous la forme (vue)

L'application a une seule FormMain qui contient 2 commandes d'utilisateur:

ControlsView (a 3 boutons) DocumentView (Une image 3ème partie miniature spectateur)

Le « formulaire principal » contient une barre d'outils pour le fichier de sauvegarde habituelle des choses, etc., et rien d'autre. Le contrôle utilisateur « ControlsView » permet à l'utilisateur de cliquer sur « Numérisation de documents » Il contient également un contrôle TreeView pour afficher une hiérarchie des documents et des pages Les spectacles « DocumentView » vignettes des documents numérisés

Il m'a vraiment senti que chaque contrôle doit avoir sa propre triade de MVP, ainsi que la principale forme, mais je voulais tout de référencer le même modèle. Je ne pouvais pas trouver comment coordonner la communication entre les commandes.

Par exemple, lorsque l'utilisateur clique sur « Scan », le ControlsPresenter prend en charge l'acquisition des images du scanner et je voulais pour ajouter la page à l'arborescence que chaque page retournée du scanner - aucun problème - mais je aussi voulait la vignette apparaisse dans le DocumentsView en même temps (problème que les présentateurs ne connaissent pas les uns les autres).

Ma solution a été pour la ControlsPresenter d'appeler une méthode dans le modèle d'ajouter la nouvelle page dans l'objet métier, puis dans le modèle que je soulève un événement « PageAdded ».

J'ai alors à la fois la ControlsPresenter et la DocumentPresenter « écoute » à cet événement afin que le ControlsPesenter dit sa vue d'ajouter la nouvelle page à l'arborescence et le DocumentPresenter indique sa vue d'ajouter la nouvelle vignette.

Pour résumer:

Contrôles Vue - pose événement "ScanButtonClicked"

Contrôles Présentateur - entend l'événement, appelle la classe Scanner à AcquireImages comme suit:

GDPictureScanning scanner = new GDPictureScanning();

IEnumerable<Page> pages = scanner.AquireImages();
foreach (Page page in pages)
{
m_DocumentModel.AddPage(page);                
//The view gets notified of new pages via events raised by the model
//The events are subscribed to by the various presenters so they can 
//update views accordingly                
}

Chaque page est numérisée, la boucle de balayage appelle un « retour de rendement nouvelle page (PageID) ». La méthode ci-dessus appelle m_DocumentModel.AddPage (page). La nouvelle page est ajoutée au modèle, ce qui déclenche un événement. Les deux contrôles présentateur et le document présentateur « entendre » l'événement et ajouter des éléments en conséquence.

Le bit je ne suis pas « sûr » est sur l'initialisation de tous les présentateurs - je fais cela dans Program.cs comme suit:

static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

IDocumotiveCaptureView view = new DocumotiveCaptureView();
IDocumentModel model = new DocumentModel();
IDocumotiveCapturePresenter Presenter = new DocumotiveCapturePresenter(view, model);
IControlsPresenter ControlsPresenter = new ControlsPresenter(view.ControlsView, model);
IDocumentPresenter DocumentPresenter = new DocumentPresenter(view.DocumentView, model);

Application.Run((Form)view);                                                         
}

Je ne sais pas si cela est bon, mauvais ou indifférent!

Quoi qu'il en soit, quel énorme poste sur une vieille question de deux ans - être bon pour obtenir des commentaires si ...

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