¿Existe algún tipo de estrategia sistemática para diseñar e implementar interfaces gráficas de usuario?


18

Estoy usando Visual Studio para crear una aplicación GUI en C #. Toolbox sirve como una paleta de componentes ingeniosa que me permite arrastrar y soltar fácilmente botones y otros elementos (para mayor claridad, diré botón cada vez que me refiero a "control") en mi formulario, lo que hace que los formularios estáticos sean bastante fáciles de hacer. Sin embargo, me encuentro con dos problemas:

  • Crear los botones en primer lugar es mucho trabajo. Cuando tengo un formulario que no es estático (es decir, los botones u otros controles se crean en tiempo de ejecución de acuerdo con lo que hace el usuario) no puedo usar la paleta en absoluto. En cambio, tengo que crear cada botón manualmente, llamando al constructor en cualquier método que esté usando, y luego inicializar manualmente especificando la altura, el ancho, la posición, la etiqueta, el controlador de eventos, etc. del botón. Esto es extremadamente tedioso porque tengo que adivinar todos estos parámetros cosméticos sin poder ver cómo se verá la forma, y ​​también genera muchas líneas de código repetitivo para cada botón.
  • Hacer que los botones hagan algo también es mucho trabajo. Hacer frente a los eventos en una aplicación con todas las funciones es un gran dolor. La única forma en que sé cómo hacer esto es seleccionar un botón, ir a la pestaña de eventos en sus propiedades, hacer clic en el OnClickevento para que genere el evento en el Formcódigo de s, y luego completar el cuerpo del evento. Como quiero separar la lógica y la presentación, todos mis controladores de eventos terminan siendo llamadas de una sola línea a la función de lógica de negocios adecuada. Pero usar esto para muchos botones (por ejemplo, imagine la cantidad de botones presentes en una aplicación como MS Word) contamina el código de my Formcon docenas de métodos de control de eventos repetitivos y es difícil mantener esto.

Debido a esto, cualquier programa GUI más complicado que Hello World es muy poco práctico para mí. Para ser claros, no tengo ningún problema con la complejidad de los programas que escribo que tienen una interfaz de usuario mínima. Siento que soy capaz de usar OOP con un alto grado de competencia para estructurar perfectamente mi código de lógica de negocios. Pero al desarrollar la GUI, estoy atascado. Parece tan tedioso que siento que estoy reinventando la rueda, y hay un libro en algún lugar que explica cómo hacer GUI correctamente que no he leído.

¿Me estoy perdiendo de algo? ¿O todos los desarrolladores de C # simplemente aceptan las interminables listas de controladores de eventos repetitivos y el código de creación de botones?


Como una sugerencia (con suerte útil), espero que una buena respuesta hable sobre:

  • Usar técnicas OOP (como el patrón de fábrica) para simplificar la creación repetida de botones
  • Combina muchos controladores de eventos en un solo método que comprueba Senderpara averiguar qué botón lo llamó y se comporta en consecuencia
  • XAML y usando WPF en lugar de Windows Forms

No tiene que mencionar ninguno de estos, por supuesto. Es solo mi mejor suposición en cuanto a qué tipo de respuesta estoy buscando.


Tal como lo veo, sí, Factory sería un buen patrón, pero veo mucho más un patrón Modelo-Vista-Controlador con Comandos conectados a controles en lugar de eventos directamente. WPF es generalmente MVVM pero MVC es muy posible. Actualmente tenemos un gran software en WPF usando MVC al 90%. Todos los comandos se envían al controlador y él se encarga de todo
Franck

¿No puede crear el formulario dinámico con todos los botones posibles con un editor de GUI y simplemente hacer que las cosas que no necesita sean dinámicamente invisibles? Suena como mucho menos trabajo.
Hans-Peter Störr el

@hstoerr Pero sería difícil lograr que el diseño funcione. Muchos de los botones se superpondrían.
Excelente

Respuestas:


22

Hay varias metodologías que han evolucionado a lo largo de los años para abordar estos problemas que ha mencionado, que son, estoy de acuerdo, los dos problemas principales que los marcos de interfaz de usuario han tenido que abordar en los últimos años. Viniendo de un fondo WPF, estos se abordan de la siguiente manera:

Diseño declarativo, más que imperativo.

Cuando describe el código minuciosamente escrito para crear instancias de controles y establecer sus propiedades, está describiendo el modelo imperativo del diseño de la interfaz de usuario. Incluso con el diseñador WinForms, solo está usando un contenedor sobre ese modelo: abra el archivo Form1.Designer.cs y verá todo ese código allí.

Con WPF y XAML, y modelos similares en otros marcos, por supuesto, desde HTML en adelante, se espera que describa su diseño y deje que el marco haga el trabajo pesado de implementarlo. Utiliza controles más inteligentes como Paneles (Cuadrícula o WrapPanel, etc.) para describir la relación entre los elementos de la IU, pero la expectativa es que no los coloque manualmente. Los conceptos flexibles, como los controles repetibles, como el repetidor de ASP.NET o los elementos de control de WPF, lo ayudan a crear una IU de escala dinámica sin escribir código repetitivo, lo que permite que las entidades de datos que crecen dinámicamente se representen dinámicamente como controles.

Las plantillas de datos de WPF le permiten definir, nuevamente, declarativamente, pequeñas pepitas de bondad de UI y relacionarlas con sus datos; por ejemplo, una lista de objetos de datos del Cliente puede estar vinculada a un ItemControl, y se invoca una plantilla de datos diferente dependiendo de si es un empleado regular (use una plantilla de fila de cuadrícula estándar con nombre y dirección) o un Cliente principal, mientras que una plantilla diferente , con una imagen y se muestran botones de fácil acceso. Una vez más, sin escribir código en la ventana específica , solo controles más inteligentes que sean conscientes de su contexto de datos y, por lo tanto, le permitan simplemente vincularlos a sus datos y permitirles realizar operaciones relevantes.

Enlace de datos y separación de comandos

Al tocar el enlace de datos, el enlace de datos de WPF (y, nuevamente, también en otros marcos, como AngularJS) le permite expresar su intención al vincular un control (por ejemplo, un cuadro de texto) con una entidad de datos (por ejemplo, el nombre del cliente) y dejar que El marco maneja la fontanería. La lógica se maneja de manera similar. En lugar de conectar manualmente los controladores de eventos de código subyacente a la lógica de negocios basada en el Controlador, utiliza el mecanismo de Enlace de datos para vincular el comportamiento de un controlador (por ejemplo, la propiedad de un comando de botón) a un objeto de comando que representa la pepita de actividad.

Permite que este comando se comparta entre ventanas sin tener que volver a escribir los controladores de eventos cada vez.

Mayor nivel de abstracción

Ambas soluciones a sus dos problemas representan un movimiento a un nivel de abstracción más alto que el paradigma de Windows Forms basado en eventos que legítimamente le resulta aburrido.

La idea es que no desee definir, en código, todas las propiedades que tiene el control y cada comportamiento desde el clic del botón y hacia adelante. Desea que sus controles y marco subyacente trabajen más para usted y le permitan pensar en conceptos más abstractos de enlace de datos (que existe en WinForms, pero que no es tan útil como en WPF) y el patrón de comando para definir enlaces entre la interfaz de usuario y comportamiento que no requiere llegar al metal.

El patrón MVVM es el enfoque de Microsoft para este paradigma. Sugiero leer sobre eso.

Este es un ejemplo aproximado de cómo se vería este modelo y cómo le ahorraría tiempo y líneas de código. Esto no se compilará, pero es pseudo-WPF. :)

En algún lugar de los recursos de su aplicación, usted define plantillas de datos:

<DataTemplate x:DataType="Customer">
   <TextBox Text="{Binding Name}"/> 
</DataTemplate>

<DataTemplate x:DataType="PrimeCustomer">
   <Image Source="GoldStar.png"/>
   <TextBox Text="{Binding Name}"/> 
</DataTemplate>

Ahora, vincula su pantalla principal a ViewModel, una clase que expone sus datos. Digamos, una colección de Clientes (simplemente a List<Customer>) y un objeto Command (nuevamente, una propiedad pública simple de tipo ICommand). Este enlace permite la unión:

public class CustomersnViewModel
{
     public List<Customer> Customers {get;}
     public ICommand RefreshCustomerListCommand {get;}  
}

y la interfaz de usuario:

<ListBox ItemsSource="{Binding Customers}"/>
<Button Command="{Binding RefreshCustomerListCommand}">Refresh</Button>

Y eso es. La sintaxis del ListBox tomará la lista de Clientes fuera del ViewModel e intentará representarlos en la IU. Debido a las dos DataTemplates que definimos anteriormente, importará la plantilla relevante (basada en el DataType, suponiendo que PrimeCustomer herede del Cliente) y la colocará como el contenido del ListBox. Sin bucles, sin generación dinámica de controles a través del código.

Del mismo modo, el botón tiene una sintaxis preexistente para vincular su comportamiento a una implementación de ICommand, que presumiblemente sabe actualizar la Customerspropiedad, lo que provoca que el marco de enlace de datos actualice la IU nuevamente, automáticamente.

He tomado algunos atajos aquí, por supuesto, pero esto es lo esencial.


5

Primero, recomiendo esta serie de artículos: http://codebetter.com/jeremymiller/2007/07/26/the-build-your-own-cab-series-table-of-contents/ - no aborda los problemas detallados con su código de botón, pero le brinda una estrategia sistemática para diseñar e implementar su propio marco de aplicación, especialmente para aplicaciones GUI, que le muestra cómo aplicar MVC, MVP, MVVM a su aplicación de formularios típica.

Abordar su pregunta sobre "tratar con eventos": si tiene muchos formularios con botones que funcionan de manera similar, probablemente valga la pena implementar la asignación del controlador de eventos en su "marco de aplicación". En lugar de asignar los eventos de clic con el diseñador de GUI, cree una convención de nomenclatura sobre cómo se debe nombrar un controlador de "clic" para un botón con un nombre específico. Por ejemplo, para el botón MyNiftyButton_ClickHandlerpara el botón MyNiftyButton. Entonces no es realmente difícil escribir una rutina reutilizable (utilizando la reflexión) que itera sobre todos los botones de un formulario, verifique si el formulario contiene un controlador de clic relacionado y asigne el controlador al evento automáticamente. Ver aquí para un ejemplo.

Y si solo desea utilizar un solo controlador de eventos para un grupo de botones, también será posible. Como cada controlador de eventos hace pasar al remitente, y el remitente es siempre el botón que se presionó, puede enviarlo al código comercial utilizando la propiedad "Nombre" de cada botón.

Para la creación dinámica de botones (u otros controles): puede crear botones u otros controles con el diseñador en un formulario no utilizado solo con el fin de crear una plantilla . Luego, usa la reflexión (vea aquí un ejemplo ) para clonar el control de plantilla y cambiar solo las propiedades como ubicación o tamaño. Probablemente será mucho más simple que inicializar dos o tres docenas de propiedades manualmente en el código.

Escribí esto arriba teniendo en cuenta Winforms (igual que tú, supongo), pero los principios generales deberían aplicarse a otros entornos GUI como WPF con capacidades similares.


De hecho, es posible hacerlo en WPF y es aún mucho más fácil. Simplemente complete una lista de comandos (funciones específicas precodificadas) y configure la fuente en su ventana. Usted crea una plantilla visual simple y agradable y todo lo que necesita para cuidar es llenar una colección con los comandos que desee y la gran encuadernación WPF se encarga del resto visualmente.
Franck
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.