El mejor diseño para formularios de Windows que compartirán una funcionalidad común


20

En el pasado, he usado la herencia para permitir la extensión de formularios de Windows en mi aplicación. Si todos mis formularios tuvieran controles, ilustraciones y funcionalidad comunes, crearía un formulario base que implementara los controles y la funcionalidad comunes y luego permitiría que otros controles heredaran de ese formulario base. Sin embargo, me he encontrado con algunos problemas con ese diseño.

  1. Los controles solo pueden estar en un contenedor a la vez, por lo que cualquier control estático que tenga será complicado. Por ejemplo: suponga que tiene un formulario base llamado BaseForm que contiene un TreeView que crea protegido y estático para que todas las demás instancias (derivadas) de esta clase puedan modificar y mostrar el mismo TreeView. Esto no funcionaría para múltiples clases heredadas de BaseForm, porque ese TreeView solo puede estar en un contenedor a la vez. Probablemente estaría en la última forma inicializada. Aunque cada instancia podría editar el control, solo se mostraría en uno en un momento dado. Por supuesto, hay soluciones alternativas, pero todas son feas. (Esto parece ser un diseño realmente malo para mí. ¿Por qué varios contenedores no pueden almacenar punteros en el mismo objeto? De todos modos, es lo que es).

  2. Estado entre formularios, es decir, estados de botón, texto de etiqueta, etc., tengo que usar variables globales para restablecer los estados en Load.

  3. Esto realmente no es muy compatible con el diseñador de Visual Studio.

¿Existe un diseño mejor, pero aún fácil de mantener para usar? ¿O la herencia de formas sigue siendo el mejor enfoque?

Actualización Pasé de mirar MVC a MVP al patrón de observador al patrón de evento. Esto es lo que estoy pensando por el momento, por favor critique:

Mi clase BaseForm solo contendrá los controles y los eventos conectados a esos controles. Todos los eventos que necesitan algún tipo de lógica para manejarlos pasarán inmediatamente a la clase BaseFormPresenter. Esta clase manejará los datos de la IU, realizará cualquier operación lógica y luego actualizará el BaseFormModel. El Modelo expondrá eventos, que se dispararán en caso de cambios de estado, a la clase Presentador, a la que se suscribirá (u observará). Cuando el presentador recibe la notificación del evento, realizará cualquier lógica, y luego el presentador modificará la vista en consecuencia.

Solo habrá una de cada clase de Modelo en la memoria, pero podría haber muchas instancias de BaseForm y, por lo tanto, BaseFormPresenter. Esto resolvería mi problema de sincronizar cada instancia de BaseForm con el mismo modelo de datos.

Preguntas:

¿Qué capa debería almacenar cosas como, el último botón presionado, para que pueda mantenerlo resaltado para el usuario (como en un menú CSS) entre formularios?

Por favor critica este diseño. ¡Gracias por tu ayuda!


No veo por qué estás obligado a usar variables globales, pero si es así, sí, ciertamente debe haber un mejor enfoque. ¿Tal vez fábricas para crear componentes comunes combinados con composición en lugar de herencia?
stijn

Todo tu diseño es defectuoso. Si ya comprende lo que quiere hacer, Visual Studio no lo admite, eso debería decirle algo.
Ramhound

1
@Ramhound Es compatible con Visual Studio, pero no está bien. Así es como Microsoft te dice que lo hagas. Simplemente encuentro que es un dolor en el A. msdn.microsoft.com/en-us/library/aa983613(v=vs.71).aspx De todos modos, si tienes una idea mejor, soy todo un ojo.
Jonathan Henson

@stijn Supongo que podría tener un método en cada formulario base que cargue los estados de control en otra instancia. es decir, LoadStatesToNewInstance (instancia de BaseForm). Podría llamarlo en cualquier momento que quiera mostrar una nueva forma de ese tipo base. form_I_am_about_to_hide.LoadStatesToNewInstance (this);
Jonathan Henson

No soy un desarrollador de .net, ¿puede ampliar el punto número 1? Podría tener una solución
Imran Omar Bukhsh

Respuestas:


6
  1. No sé por qué necesitas controles estáticos. Tal vez sabes algo que yo no. He usado mucha herencia visual pero nunca he visto que los controles estáticos sean necesarios. Si tiene un control de vista de árbol común, deje que cada instancia de formulario tenga su propia instancia del control y comparta una sola instancia de los datos vinculados a las vistas de árbol.

  2. Compartir el estado de control (en oposición a los datos) entre formularios también es un requisito inusual. ¿Está seguro de que FormB realmente necesita saber sobre el estado de los botones en FormA? Considere los diseños MVP o MVC. Piense en cada formulario como una "vista" tonta que no sabe nada de las otras vistas o incluso de la aplicación misma. Supervise cada vista con un presentador / controlador inteligente. Si tiene sentido, un solo presentador puede supervisar varias vistas. Asociar un objeto de estado con cada vista. Si tiene algún estado que necesita ser compartido entre las vistas, deje que el presentador (s) medie esto (y considere la vinculación de datos, ver más abajo).

  3. De acuerdo, Visual Studio te dará dolores de cabeza. Al considerar la herencia del formulario o del control del usuario, debe sopesar cuidadosamente los beneficios contra el costo potencial (y probable) de luchar con las frustrantes peculiaridades y limitaciones del diseñador de formularios. Sugiero mantener la herencia de formularios al mínimo: úsela solo cuando la recompensa sea alta. Tenga en cuenta que, como alternativa a la subclasificación, puede crear un formulario "base" común y simplemente crear una instancia para cada posible "hijo" y luego personalizarlo sobre la marcha. Esto tiene sentido cuando las diferencias entre cada versión del formulario son menores en comparación con los aspectos compartidos. (IOW: forma de base compleja, formas secundarias un poco más complejas)

Utilice los controles de usuario cuando lo ayude a evitar una duplicación significativa del desarrollo de la interfaz de usuario. Considere la herencia de control de usuario, pero aplique las mismas consideraciones que para la herencia de formularios.

Creo que el consejo más importante que puedo ofrecer es que, si actualmente no utiliza alguna forma del patrón de vista / controlador, le recomiendo que comience a hacerlo. Te obliga a aprender y apreciar los beneficios del corte suelto y la separación de capas.

respuesta a su actualización

Qué capa debería almacenar cosas como, el último botón presionado, para que pueda mantenerlo resaltado para el usuario ...

Puede compartir el estado entre vistas de la misma manera que compartiría el estado entre un presentador y su vista. Cree una clase especial SharedViewState. Para simplificar, puede convertirlo en un singleton, o puede crear una instancia en el presentador principal y pasarlo a todas las vistas (a través de sus presentadores) desde allí. Cuando el estado está asociado con los controles, use el enlace de datos cuando sea posible. La mayoría de las Controlpropiedades pueden estar vinculadas a datos. Por ejemplo, la propiedad BackColor de un botón podría estar vinculada a una propiedad de su clase SharedViewState. Si realiza este enlace en todos los formularios que tienen botones idénticos, puede resaltar Button1 en todos los formularios simplemente configurando SharedViewState.Button1BackColor = someColor.

Si no está familiarizado con el enlace de datos WinForms, presione MSDN y lea un poco. No es dificil. Aprende INotifyPropertyChangedy estás a medio camino.

Aquí hay una implementación típica de una clase viewstate con la propiedad Button1BackColor como ejemplo:

public class SharedViewState : INotifyPropertyChanged
{
    // boilerplate INotifyPropertyChanged stuff
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    // example of a property for data-binding
    private Color button1BackColor;
    public Color Button1BackColor
    {
        get { return button1BackColor; }
        set
        {
            if (value != button1BackColor)
            {
                button1BackColor = value;
                NotifyPropertyChanged("Button1BackColor");
            }
        }
    }
}

Gracias por tu atenta respuesta. ¿Conoces una buena referencia al patrón de vista / controlador?
Jonathan Henson

1
Recuerdo haber pensado que esta fue una buena introducción: codebetter.com/jeremymiller/2007/05/22/…
Igby Largeman

por favor vea mi actualización
Jonathan Henson

@ JonathanHenson: respuesta actualizada.
Igby Largeman

5

Mantengo una nueva aplicación WinForms / WPF basada en el patrón MVVM y los controles de actualización . Comenzó como WinForms, luego creé una versión de WPF debido a la importancia de marketing de una interfaz de usuario que se ve bien. Pensé que sería una restricción de diseño interesante mantener una aplicación que admitiera dos tecnologías de interfaz de usuario completamente diferentes con el mismo código de fondo (modelos de vista y modelos), y debo decir que estoy bastante satisfecho con este enfoque.

En mi aplicación, cada vez que necesito compartir funcionalidad entre partes de la IU, uso controles personalizados o controles de usuario (clases derivadas de WPF UserControl o WinForms UserControl). En la versión WinForms hay un UserControl dentro de otro UserControl dentro de una clase derivada de TabPage, que finalmente está dentro de un control de pestaña en el formulario principal. La versión WPF en realidad tiene UserControls anidados un nivel más profundo. Usando controles personalizados, puede componer fácilmente una nueva IU a partir de UserControls que creó anteriormente.

Como uso el patrón MVVM, pongo tanta lógica de programa como sea posible en ViewModel (incluido el Modelo de Presentación / Navegación ) o el Modelo (dependiendo de si el código está relacionado con la interfaz de usuario o no), pero desde el mismo ViewModel se usa tanto en una vista de WinForms como en una vista de WPF, ViewModel no puede contener código diseñado para WinForms o WPF o que interactúa directamente con la interfaz de usuario; dicho código DEBE entrar en la vista.

¡También en el patrón MVVM, los objetos de la interfaz de usuario deben evitar interactuar entre sí! En cambio, interactúan con los modelos de vista. Por ejemplo, un TextBox no le preguntaría a un ListBox cercano qué elemento está seleccionado; en cambio, el cuadro de lista guardaría una referencia al elemento seleccionado actualmente en algún lugar de la capa del modelo de vista, y TextBox consultaría la capa del modelo de vista para averiguar qué está seleccionado en este momento. Esto resuelve su problema acerca de averiguar qué botón se presionó en una forma desde una forma diferente. Simplemente comparte un objeto del Modelo de navegación (que es parte de la capa del modelo de vista de la aplicación) entre los dos formularios y coloca una propiedad en ese objeto que representa qué botón se presionó.

WinForms en sí mismo no admite el patrón MVVM muy bien, pero Update Controls proporciona su propio enfoque único MVVM como una biblioteca que se encuentra en la parte superior de WinForms.

En mi opinión, este enfoque para el diseño de programas funciona muy bien y planeo usarlo en proyectos futuros. Las razones por las que funciona tan bien son (1) que Update Controls administra las dependencias automáticamente, y (2) que generalmente es muy claro cómo debe estructurar su código: todo el código que interactúa con los objetos de la IU pertenece a la Vista, todo el código que está Relacionado con la interfaz de usuario, pero NO TIENE que interactuar con los objetos de la interfaz de usuario, pertenece al ViewModel. Muy a menudo dividirá su código en dos partes, una parte para la vista y otra parte para el modelo de vista. Tuve algunas dificultades para diseñar menús contextuales en mi sistema, pero finalmente se me ocurrió un diseño para eso también.

También estoy entusiasmado con los Controles de actualización en mi blog . Sin embargo, este enfoque lleva mucho tiempo acostumbrarse, y en aplicaciones a gran escala (por ejemplo, si sus cuadros de lista contienen miles de elementos), puede encontrar problemas de rendimiento debido a las limitaciones actuales de la administración automática de dependencias.


3

Voy a responder esto aunque ya hayas aceptado una respuesta.

Como otros han señalado, no entiendo por qué tuvo que usar algo estático; Parece que has estado haciendo algo muy mal.

De todos modos, he tenido el mismo problema que usted: en mi aplicación WinForms tengo varios formularios que comparten algunas funciones y algunos controles. Además, todos mis formularios ya se derivan de un formulario base (llamémoslo "MyForm") que agrega funcionalidad a nivel de marco (independientemente de la aplicación). El Diseñador de formularios de Visual Studio supuestamente admite la edición de formularios que heredan de otros formularios, pero en la práctica funciona solo mientras sus formularios no hagan más que "¡Hola, mundo! - OK - Cancelar".

Lo que terminé haciendo es esto: mantengo mi clase base común "MyForm", que es bastante compleja, y continúo derivando todos los formularios de mi aplicación. Sin embargo, no hago absolutamente nada en estas formas, por lo que VS Forms Designer no tiene problemas para editarlas. Estos formularios consisten únicamente en el código que el Diseñador de formularios genera para ellos. Luego, tengo una jerarquía paralela separada de objetos que llamo "sustitutos" que contienen toda la funcionalidad específica de la aplicación, como inicializar los controles, manejar eventos generados por el formulario y los controles, etc. Hay un uno a uno correspondencia entre clases sustitutas y diálogos en mi aplicación: hay una clase sustituta base que corresponde a "MyForm", luego se deriva otra clase sustituta que corresponde a "MyApplicationForm",

Cada sustituto acepta como parámetro de tiempo de construcción un tipo específico de formulario, y se adhiere a él al registrarse para sus eventos. También delega a la base, hasta "MySurrogate", que acepta un "MyForm". Ese sustituto se registra con el evento "Disposed" del formulario, de modo que cuando el formulario se destruye, el sustituto invoca a un reemplazo sobre sí mismo para que él y todos sus descendientes puedan realizar la limpieza. (Darse de baja de eventos, etc.)

Ha estado funcionando bien hasta ahora.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.