2010-12-01 5 views
5

Estoy diseñando una interfaz gráfica de usuario que tiene la siguiente idea básica (de manera similar el modelo de aspecto y la sensación básica de Visual Studio):Cómo estructurar un programa C# WinForms Model-View-Presenter (vista pasiva)?

  1. de navegación de archivos selector de control
  2. (para seleccionar lo que desea mostrar en el componente Editor)
  3. Editor
  4. Logger (errores, advertencias, confirmación, etc.)

por ahora, voy a utilizar un TreeView para la navegación de archivos, un ListView para seleccionando los controles que se mostrarán en el Editor y un RichTextBox para el Registrador. El Editor tendrá 2 tipos de modos de edición dependiendo de lo que se seleccione en TreeView. El Editor será un RichTextBox para editar manualmente el texto dentro de los archivos, o será un Panel con Arrastrar/Soltar DataGridViews y sub-TextBoxes para editar en este Panel.

que estoy tratando de seguir el patrón de Vista Diseño pasivo para la separación completa del modelo de Vista y viceversa. La naturaleza de este proyecto es que cualquier componente que agregue está sujeto a edición/eliminación. Como tal, necesito independencia para pasar de un control dado a otro. Si hoy estoy usando un TreeView para navegación de archivos, pero mañana me dicen que use algo más, entonces quiero implementar un nuevo control con relativa facilidad.

Simplemente no entiendo cómo estructurar el programa. Comprendo un Presentador por Control, pero no sé cómo hacerlo funcionar de modo que tenga una Vista (la GUI completa del programa) con controles (sub-Vistas) tal que la Vista ENTERA sea reemplazable, así como también el individuo controles que reflejan mi modelo.

en la vista principal, que se supone debe ser ligero por pasiva Ver normas, puedo implementar las sub-Vistas de forma individual? Si es así, supongamos que tengo una interfaz INavigator para abstraer el rol del objeto Navigator. El navegador necesitará un presentador y un modelo para actuar entre la vista del navegador y la vista principal. Siento que me estoy perdiendo en la jerga del patrón de diseño en alguna parte.

La pregunta más relacionada de manera similar se puede encontrar here, pero no responde a mi pregunta con suficiente detalle.

¿Alguien por favor me ayudan a entender cómo la "estructura" de este programa? Agradezco cualquier ayuda.

Gracias,

Daniel

Respuesta

22

abstracción es buena, pero es importante recordar que en algún momento algo tiene que saber una cosa o dos acerca de una cosa o dos, o de lo contrario sólo tendremos que Tenga una pila de legos bien dibujados sentados en el piso en vez de que estén ensamblados en una casa.

Un contenedor de inversión de control/inyección de dependencia/flippy-dippy-upside-down-whatever-we-are-calling-it-this-week como Autofac realmente puede ayudar a unir todo esto.

Cuando tiro juntos una aplicación de Windows Forms, por lo general terminan con un patrón repetitivo.

Comenzaré con un archivo Program.cs que configura el contenedor Autofac y luego obtiene una instancia del MainForm y muestra el MainForm.Algunas personas lo llaman el shell o el espacio de trabajo o el escritorio, pero en cualquier caso es "el formulario" que tiene la barra de menú y muestra ventanas secundarias o controles de usuario secundarios, y cuando se cierra, la aplicación sale.

Siguiente es el MainForm antes mencionado. Hago las cosas básicas, como arrastrar y soltar algunos SplitContainers y MenuBar s y tal en el diseñador visual de Visual Studio, y luego empiezo a obtener un código sofisticado: voy a tener ciertas interfaces clave "inyectadas" en el constructor MainForm para que pueda hacer uso de ellos, para que mi MainForm pueda orquestar controles secundarios sin tener que saber mucho sobre ellos.

Por ejemplo, podría tener una interfaz IEventBroker que permite que varios componentes publiquen o se suscriban a "eventos" como BarcodeScanned o ProductSaved. Esto permite que partes de la aplicación respondan a los eventos de una manera débil, sin tener que depender del cableado de eventos .NET tradicionales. Por ejemplo, el EditProductPresenter que va junto con mi EditProductUserControl podría decir this.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah)) y el IEventBroker verificaría su lista de suscriptores para ese evento y llamaría a sus devoluciones de llamada. Por ejemplo, el ListProductsPresenter podría escuchar ese evento y actualizar dinámicamente el ListProductsUserControl al que está conectado. El resultado neto es que si un usuario guarda un producto en un control de usuario, el presentador de otro usuario puede reaccionar y actualizarse si está abierto, sin que el control tenga que conocer su existencia y sin que el MainForm tenga que orquestar ese evento.

Si estoy diseñando una aplicación MDI, que tenga la MainForm implementar una interfaz que tiene IWindowWorkspaceOpen() y Close() métodos. Podría inyectar esa interfaz en mis varios presentadores para permitirles abrir y cerrar ventanas adicionales sin que ellos estén al tanto del MainForm directamente. Por ejemplo, el ListProductsPresenter podría querer abrir un EditProductPresenter y EditProductUserControl correspondiente cuando el usuario hace doble clic en una fila en una cuadrícula de datos en un ListProductsUserControl. Puede hacer referencia a IWindowWorkspace, que en realidad es MainForm, pero no es necesario que lo sepa, y llame al Open(newInstanceOfAnEditControl) y suponga que el control se mostró en el lugar apropiado de la aplicación de alguna manera. (La aplicación MainForm sería, presumiblemente, cambiar el control a la vista en un panel en alguna parte.)

Pero, ¿cómo diablos la ListProductsPresenter crear esa instancia de la EditProductUserControl? Autofac's delegate factories son una verdadera alegría aquí, ya que sólo se puede inyectar un delegado en el presentador y Autofac automágicamente cablear como si se tratara de una fábrica (pseudocódigo siguiente):


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. 
    } 
} 
 

Así que la ListProductsPresenter sabe acerca de la función Edit set (es decir, el presentador de edición y el control de edición de usuario) - y esto está perfectamente bien, van de la mano - pero no necesita conocer todas las dependencias del conjunto de características Edit , en lugar de confiar en un delegado proporcionado por Autofac para resolver todas esas dependencias.

En general, encuentro que tengo una correspondencia uno-a-uno entre un "presentador/modelo de vista/controlador supervisor" (no nos pongamos demasiado al tanto de las diferencias ya que al final del día todas son bastante similares) y un "UserControl/Form". El UserControl acepta el modelo/controlador del presentador/vista en su constructor y databinds a sí mismo como es apropiado, difiriendo al presentador tanto como sea posible.Algunas personas ocultan el UserControl del presentador a través de una interfaz, como IEditProductView, que puede ser útil si la vista no es completamente pasiva. Tiendo a usar el enlace de datos para todo, de modo que la comunicación se realiza a través del INotifyPropertyChanged y no me moleste.

Pero, hará que su vida sea mucho más fácil si el presentador está descaradamente atada a la vista. ¿Una propiedad en su modelo de objeto no se combina con enlace de datos? Exponer una nueva propiedad así lo hace. Nunca va a tener un EditProductPresenter y un EditProductUserControl con un diseño y luego desea escribir una nueva versión del control de usuario que funcione con el mismo presentador. Simplemente los editará a ambos, son para todos los efectos y tienen una función, una función, el presentador solo existe porque es fácilmente comprobable por unidad y el control del usuario no.

Si desea que una característica sea reemplazable, debe abstraer toda la función como tal. Por lo tanto, es posible que tenga una interfaz INavigationFeature con la que se comunique su MainForm. Puede tener un TreeBasedNavigationPresenter que implemente INavigationFeature y lo consuma un TreeBasedUserControl. Y es posible que tenga un CarouselBasedNavigationPresenter que también implemente INavigationFeature y lo consuma un CarouselBasedUserControl. Los controles de usuario y los presentadores aún van de la mano, pero su MainForm no tendría que importar si está interactuando con una vista basada en árbol o en un carrusel, y podría cambiarlos sin que el MainForm sea el más sabio

Para terminar, es fácil confundirse. Todo el mundo es pedante y utiliza una terminología ligeramente diferente para transmitir diferencias sutiles (y muchas veces poco importantes) entre patrones arquitectónicos similares. En mi humilde opinión, la inyección de dependencia hace maravillas para construir aplicaciones compostables y extensibles, ya que el acoplamiento se mantiene abajo; la separación de características en "presentadores/modelos/controladores de vista" y "vistas/controles/formularios de usuario" hace maravillas por la calidad, ya que la mayoría de la lógica se integra en la primera, lo que permite que sea probada de manera sencilla; y combinar los dos principios parece ser realmente lo que estás buscando, simplemente te confundes con la terminología.

O, podría estar lleno de eso. ¡Buena suerte!

+0

Gracias por la respuesta. No estoy tratando de dejarme absorber por la jerga de la filosofía que puede inspirarme hablando de patrones de diseño, pero estoy tratando de obtener algunos conocimientos técnicos a mis espaldas. Como tal, esta Vista Pasiva realmente me está agravando. ¿Cómo puedo crear, desde el principio, una vista maestra con subvistas dentro de ella (de modo que toda la vista maestra sea intercambiable, así como cualquiera de las subvistas dentro de la vista maestra)? No puedo encontrar nada en línea para abordar este problema de manera adecuada (tal vez nadie lo haya resuelto definitivamente todavía). –

+0

Esta respuesta tiene más de 6 años y, sin embargo, es una de las mejores explicaciones del uso práctico de MVP que he encontrado en Internet. Gracias, es increíblemente educativo, incluso si la programación de WinForms se volvió algo obsoleta hoy en día. – Tarec

2

Sé que esta pregunta tiene casi 2 años pero me encuentro en una situación muy similar. Al igual que usted, he buscado en Internet durante DÍAS y no he encontrado un ejemplo concreto que se ajuste a mis necesidades: cuanto más busqué, más seguí volviendo a los mismos sitios una y otra vez hasta el punto en que tenía aproximadamente 10 páginas de color púrpura. enlaces en Google!

De todos modos, me preguntaba si alguna vez se le ocurrió una solución satisfactoria al problema? Describiré cómo lo he hecho hasta ahora, basado en lo que he leído durante la última semana:

Mis objetivos fueron: Forma pasiva, presentador primero (el presentador crea el formulario de forma que el formulario no tiene conocimiento de que es presentador) métodos de llamada en el presentador de eventos de recaudación en la forma (ver)

la aplicación tiene una única FormMain que contiene 2 controles de usuario:

ControlsView (tiene 3 botones) DocumentView (a tercera imagen del partido visor de miniaturas)

El "Formulario principal" contiene una barra de herramientas para guardar archivos, etc., y poco más. El control de usuario "ControlsView" permite al usuario hacer clic en "Escanear documentos" También contiene un control de vista de árbol para mostrar una jerarquía de documentos y páginas El "DocumentView" muestra miniaturas de los documentos escaneados

Realmente se sentía a que cada control debería tener su propia tríada de MVP, así como también el formulario principal, pero quería que todos ellos hicieran referencia al mismo modelo. Simplemente no pude resolver cómo coordinar la comunicación entre los controles.

Por ejemplo, cuando el usuario hace clic en "Escanear", ControlsPresenter se encarga de adquirir las imágenes del escáner y quería agregar la página a la vista de árbol como cada página devuelta desde el escáner, no hay problema, pero también quería que la miniatura apareciera en el DocumentsView al mismo tiempo (problema ya que los presentadores no se conocen entre sí).

Mi solución fue que el ControlsPresenter llamara a un método en el modelo para agregar la nueva página al objeto comercial, y luego en el modelo presento un evento "PageAdded".

Entonces tengo tanto la ControlsPresenter y la DocumentPresenter "escuchando" a este evento para que la ControlsPesenter dice que es objeto de añadir la nueva página a la vista de árbol, y el DocumentPresenter dice que es objeto de añadir la nueva imagen en miniatura.

En resumen:

controles de vista - plantea evento "ScanButtonClicked"

Controles Presentador - escucha el evento, llama a la clase escáner a AcquireImages de la siguiente manera:

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     
} 

Como se escanea cada página , el bucle de exploración llama a una "nueva página de retorno de rendimiento (PageID)". El método anterior llama a m_DocumentModel.AddPage (página). La nueva página se agrega al modelo, lo que genera un evento. Tanto el presentador de controles como el presentador del documento "escuchan" el evento y agregan elementos en consecuencia.

El bit no estoy "seguro" es de la inicialización de todos los presentadores - que estoy haciendo esto dentro de Program.cs de la siguiente manera:

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

No estoy seguro si esto es bueno, malo o ¡indiferente!

De todos modos, lo que es un gran post sobre una vieja la pregunta dos años - ser bueno para obtener alguna información, aunque ...

+0

Bien, voy a intentar eso si no te importa. Actualmente estoy probando MVPVM con Passive View en mi programa .Net 2.0 WinForms.También prefiero que la Vista sea lo más estúpida posible, pero en muchos ejemplos en la web unen la Vista con el Presentador: -/ – Heliac

+0

Bien, eliminé las interfaces del presentador, no es necesario, y luego hice ControlsView y las propiedades públicas DocumentView de DocumotiveCaptureView, que se crean instancias en el constructor DocumotiveCaptureView. Por lo tanto, solo paso "ver" en cada Presentador, y luego me suscribo a view.FormMainButtonClicked o view.DocumentView.DocumentViewButtonClicked en el constructor del presentador. – Heliac

Cuestiones relacionadas