2010-11-30 20 views
20

Estoy desarrollando una aplicación WinForms en C#. Tengo una experiencia limitada en la programación de GUI, y tengo que aprender mucho sobre la marcha. Dicho esto, esto es lo que estoy construyendo.C# WinForms Model-View-Presenter (Vista pasiva)

ver la expresión general de interfaz gráfica de usuario en el siguiente enlace:

GUI http://img227.imageshack.us/img227/1084/program0.jpg

Ahora, he hecho un montón de trabajo ya, pero en el patrón de diseño muy mala Autónoma. No sabía que el proyecto llegaría a un cierto tamaño y, como tal, es hora de hacer una refactorización importante.

He estado estudiando mucho sobre los patrones de diseño de GUI, y el patrón que deseo implementar es la Vista pasiva (ver http://martinfowler.com/eaaDev/PassiveScreen.html). Estoy buscando ayuda sobre cómo unir todo esto.

Antecedentes:

1) En función de lo que el usuario hace clic en el "TreeView", la "lista" en la esquina inferior izquierda se mostrará una lista de objetos que pueden poblar la zona "Editor". Estos objetos pueden ser un TextBox o un DataGridView. El usuario alterna la Lista para elegir lo que quiere ver en el "Editor"

2) El modelo es esencialmente una carpeta con datos y archivos de configuración. Hay un programa externo que se ejecuta en un directorio determinado, crea archivos/carpetas de salida, etc. Este programa que estoy desarrollando está diseñado para administrar/configurar estos objetos de una manera fácil de usar

3) El problema con La forma en que he estado haciendo las cosas es que es casi imposible de probar, y de ahí el paso al patrón de diseño de Vista pasiva MVP-esque

Estoy tratando de hacerlo para que el programa funcione independientemente de la Vista. No he podido encontrar ningún ejemplo donde se use una vista más compleja e interactiva con el patrón de Vista pasiva.

Preguntas:

1) ¿Es necesario poner en práctica una amplia interfaz/vista de todo el "look" del programa, a continuación, aplicar sub-interfaces/sub-puntos de vista de cada uno de los TreeView, Editor, Logger , etc.? ¿O hay una mejor "estructura" para hacer esto?

2) Cuando se trata de "entregar" eventos de la Vista al Presentador/Controlador (cualquiera que sea la terminología que desee utilizar W.R.T. el patrón de diseño de Vista Pasiva), ¿cuál es la forma en que debería estar haciendo esto? A veces tengo propiedades simples que necesitan ser actualizadas, y algunas veces necesito una serie completa de pasos para desarrollar.

Me gustaría sugerencias y consejos sobre este tema. He buscado en Internet y no he encontrado ejemplos adecuados para ayudarme a continuar con este proyecto.

¡Gracias de antemano!

Daniel

Respuesta

18

Aquí está un ejemplo sencillo que muestra el concepto de puntos de vista pasivos utilizando el patrón de diseño MVP. Debido a que estamos usando vistas pasivas, la vista no tiene conocimiento del presentador. El presentador simplemente se suscribirá a eventos publicados por la vista y actuará en consecuencia.

Para empezar, necesitamos definir un contrato para nuestra vista. Esto se logra típicamente usando una interfaz, esencialmente, queremos tener un acoplamiento muy flexible con nuestra vista.Queremos la capacidad de cambiar a diferentes vistas o eventos para crear vistas simuladas para pruebas unitarias.

Aquí es un contrato que describe una visión simple que se utiliza para mostrar la información del cliente

public interface ICustomerManagementView 
{ 
    void InitializeCustomers(ICustomer[] customers); 
    void DisplayCustomer(ICustomer customer); 
    event EventHandler<EventArgs<ICustomer>> SelectedCustomerChanged; 
} 

Se expone un método único InitializeCustomers que serán utilizados para inicializar nuestro punto de vista con los objetos de nuestro modelo.

También tenemos un evento SelectedCustomerChanged que será utilizado por nuestro presentador para recibir la notificación de que se ha producido una acción en la vista.

Una vez que tenemos nuestro contrato, podemos comenzar a manejar estas interacciones en nuestro presentador.

public class CustomerManagementPresenter 
{ 
    private ICustomer _selectedCustomer; 
    private readonly ICustomerManagementView _managementView; 
    private readonly ICustomerRepository _customerRepository; 

    public CustomerManagementPresenter(ICustomerManagementView managementView, ICustomerRepository customerRepository) 
    { 
     _managementView = managementView; 
     _managementView.SelectedCustomerChanged += this.SelectedCustomerChanged; 

     _customerRepository = customerRepository; 

     _managementView.InitializeCustomers(_customerRepository.FetchCustomers()); 
    } 

    private void SelectedCustomerChanged(object sender, EventArgs<ICustomer> args) 
    { 
     // Perform some logic here to update the view 
     if(_selectedCustomer != args.Value) 
     { 
      _selectedCustomer = args.Value; 
      _managementView.DisplayCustomer(_selectedCustomer); 
     } 
    } 
} 

En el presentador podemos utilizar otro patrón de diseño llamado dependency injection para proporcionar acceso a nuestro punto de vista y las clases de modelo que podamos necesitar. En este ejemplo, tengo un CustomerRepository que es responsable de obtener los detalles del cliente.

En el constructor tenemos dos líneas de código importantes, en primer lugar nos hemos suscrito al evento SelectedCustomerChanged en nuestra vista, aquí es donde podemos realizar acciones asociadas. En segundo lugar, hemos llamado a InitilaizeCustomers con datos del repositorio.

En este momento no hemos definido una implementación concreta para nuestra vista, todo lo que tenemos que hacer es crear un objeto que implemente ICustomerManagementView. Por ejemplo, en una aplicación de Windows Forms que podemos hacer la siguiente

public partial class CustomerManagementView : Form, ICustomerManagementView 
{ 
    public CustomerManagementView() 
    { 
     this.InitializeComponents(); 
    } 

    public void InitializeCustomers(ICustomer[] customers) 
    { 
     // Populate the tree view with customer details 
    } 

    public void DisplayCustomer(ICustomer customer) 
    { 
     // Display the customer... 
    } 

    // Event handler that responds to node selection 
    private void CustomerTreeViewAfterSelect(object sender, TreeViewEventArgs e) 
    { 
     var customer = e.Node.Tag as ICustomer; 
     if(customer != null) 
     { 
      this.OnSelectedCustomerChanged(new EventArgs<ICustomer>(customer)); 
     } 
    } 

    // Protected method so that we can raise our event 
    protected virtual void OnSelectedCustomerChanged(EventArgs<ICustomer> args) 
    { 
     var eventHandler = this.SelectedCustomerChanged; 
     if(eventHandler != null) 
     { 
      eventHandler.Invoke(this, args); 
     } 
    } 

    // Our view will raise an event each time the selected customer changes 
    public event EventHandler<EventArgs<ICustomer>> SelectedCustomerChanged; 
} 

Si quisiéramos poner a prueba nuestra lógica de presentación podríamos burlarse de nuestro punto de vista y realizar algunas afirmaciones.

EDIT: Incluido evento args personalizados

public class EventArgs<T> : EventArgs 
{ 
    private readonly T _value; 

    public EventArgs(T value) 
    { 
     _value = value; 
    } 

    public T Value 
    { 
     get { return _value; } 
    } 
} 
+0

Agradezco la respuesta informativa, pero aborda particularmente mis preguntas. ¿Debo usar una Vista (con estos 4-5 subcomponentes con los que el usuario puede interactuar), o debería haber Vistas dentro de Vistas (de ahí las interfaces dentro de las interfaces)? Además, todavía no estoy seguro de la "transmisión" de eventos desde la Vista hasta el Presentador, y aún necesito una aclaración sobre cómo la Vista observa el Modelo. También me preocupa la "estructura" del programa. –

+1

Prefiero descomponerlo en componentes más pequeños, especialmente si desea fomentar la reutilización.Cree vistas que tengan un único propósito pero que se puedan usar juntas para crear una interfaz de usuario compuesta. –

+0

Además, si observa el constructor del presentador, puede ver cómo manejar eventos desde la vista. –

0

Me descomponerlas en vistas separadas con sus propios regalos, y utilizar un "control" presentador/vista para gestionar la delegación de mensajes entre todos ellos. Esto no solo ayudará a la capacidad de prueba, sino que también mantendrá sus controles cumpliendo SRP.

Así que en su caso podría tener un IFormManager que implementará la ventana principal, y luego un IFileManager, ILoggerWindow etc etc

Aunque podría ser un poco excesivo para usar, sugeriría que usted tiene un vistazo a Smart Client Software Factory (del equipo de Patrones y Prácticas de Microsoft): ya no está siendo desarrollado activamente, pero tiene una buena implementación de MVP y hace bastante bien este tipo de composición de vistas, por lo que podría darte algo bueno ideas.

+0

Gracias por la entrada. Pero, ¿dónde estaría poniendo los controles individuales? ¿Dentro de la vista principal? Pero estoy tratando de mantener el peso ligero de la vista principal en primer lugar. –

+0

Encontré una imagen que podría detallar lo que estoy preguntando. ¿Crees que este gráfico será suficiente para resolver mi problema? http://www.diskordia.ch/blog/wp-content/uploads/2009/03/subview.png –

+0

No hay nada que te impida colocar las vistas secundarias dentro de la vista principal: tu vista principal sigue siendo liviana, lógicamente, es decir, su único la responsabilidad es mantener el diseño de los mensajes delegados en las vistas; no controlaría nada sobre ellos. –

Cuestiones relacionadas