lunes, 10 de noviembre de 2008

Codificación: datos de la aplicación como recursos XML embebidos (parte IV).

Parte I: Resumen.

Parte II: La definción del archivo XML.

Parte III: Encapsulamiento.

Parte IV: Que funcione.

IV. Que funcione.

Ya tenemos nuestras clases de definición de menú alimentadas por un recurso incrustado en el ensamblado, ahora vamos desde el front-end.

Dejemos Form1 para pruebas. Agregamos un nuevo formulario: frmPrincipal, y en él un TreeView, trvMenu. El código del formulario será:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace EjemploRecursosXMLEmbebido
{
    public partial class frmPrincipal : Form
    {
        public frmPrincipal()
        {
            InitializeComponent();            
            CargarMenu(Menues.Raiz, this.trvMenu.Nodes);
        }

        private void CargarMenu(MenuItem definicionMenu, TreeNodeCollection contenedor)
        {
            TreeNode menuNodo = CrearNodo(definicionMenu);
            foreach (MenuItem definicionSubmenu in definicionMenu.Submenues())
                CargarMenu(definicionSubmenu, menuNodo.Nodes);

            contenedor.Add(menuNodo);
        }

        private TreeNode CrearNodo(MenuItem menu)
        {
            TreeNode nodo = new TreeNode();
            nodo.Text = menu.Titulo;
            nodo.Name = menu.Id;

            return nodo;
        }


    }
}

Son solamente dos procedimientos: CargarMenu, que recorre recursivamente la estructura de definiciones de menú y CrearNodo, que es una función que crea el nodo correspondiente a cada definición de ítem de menú.

Dejé para este momento el tema "que haga algo" para ejemplificar las bondades del encapsulamiento del XML en esta manera.

¿Qué sucede cuando seleccionamos alguna opción en el menú? Por lo pronto, nada. Lo lógico sería que de alguna manera se visualice el front-end de la funcionalidad correspondiente. Necesitaremos algunas definiciones extra para avanzar.

Sólo a modo de ejemplo (no me gusta mucho cómo queda), implementaremos el front-end de cada funcionalidad como un UserControl. Así que colocamos un SplitContainer (splitContainer1) en el formulario. Éste lo divide visualmente en dos controles Panel (splitContainer1.Panel1 y splitContainer1.Panel2). En splitContainer1.Panel1 colocamos trvMenu. En splitContainer1.Panel2 cargaremos un UserControl creado dinámicamente cuando el usuario haga click en un ítem del menú.

Comencemos con sólo dos funcionalidades del menú: Compras/Presupuestos y Compras/Ordenes.

Primero, agregamos dos nuevos UserControl al proyecto: uscComprasPresupuestos y uscComprasOrdenes. Pongamos cualquier cosa dentro de ellos. Yo he puesto simplemente un Label con el nombre de la funcionalidad.

Segundo, agregamos código al evento NodeMouseDoubleClick en frmPrincipal para que cargue el control correspondiente de acuerdo al menú que ha recibido el evento:

private void trvMenu_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
{
    UserControl frontendUC = null;
    switch (e.Node.Name)
    {
        case "compras_presupuestos":
            frontendUC = new uscComprasPresupuestos();
            break;

        case "compras_ordenes":
            frontendUC = new uscComprasOrdenes();
            break;
    }

    //si no tiene front end asociado no hace nada
    if (frontendUC == null)
        return;

    //si ya hay un front-end cargado, lo eliminamos.
    if (this.splitContainer1.Panel2.Controls.Count > 0)
        this.splitContainer1.Panel2.Controls.RemoveAt(0);

    frontendUC.Dock = DockStyle.Fill;
    this.splitContainer1.Panel2.Controls.Add(frontendUC);

}

Este código tiene un switch en el que se selecciona el control correspondiente de acuerdo al nombre del nodo (recordemos que habíamos asignado a cada nodo un nombre igual al id del menú en su definición en el archivo XML). Si no hay un UserControl asociado al menú no hace nada. Si lo hay, elimina el correspondiente a la opción anteriormente seleccionada y crea el nuevo, colocándolo en splitContainer1.Panel2.

Probemos... ok, funciona (por lo menos en mi máquina funciona).

A algunos la solución les parecerá buenísima... ¡manos a la obra! ¡Ya tenemos la estructura de la aplicación, ahora a programar funcionalidad! Hay como 200 funcionalidades que implementar, son 200 controles por crear, así que ¡rápido!

A otros la solución les hará un poco de ruido... "qué incómodo ese switch... por más que tengo los datos del menú en XML, sigo teniendo que agregar la línea que crea el UserControl... no es como crear el nodo a mano en diseño, pero... bueno, empecemos y veremos qué pasa".

Otros con más experiencia verán que ese switch va a convertirse en un problema, y ni hablar de cuando los menúes empiecen a "moverse" por los cambios estéticos y de funcionalidad... ¿quién mantendrá eso ordenado y legible dentro de un par de meses, con 200 o más entradas case, y probablemente con muchas "de basura"?

En realidad, en este momento no es demasiado importante la actitud a tomar. Pero en un par de meses será fundamental. Créanme, ese switch va a ser un problema. Si alguen lo detecta ahora, bien. Si lo corrige, mejor. Pero dentro de un tiempo, si no se corrige va a crecer transformándose en algo muy feo.

En la próxima entrada de la serie veremos una de las soluciones posibles para aislar al front-end de los problemas del crecimiento y los cambios en el menú.

Parte I: Resumen.

Parte II: La definción del archivo XML.

Parte III: Encapsulamiento.

Parte IV: Que funcione.

No hay comentarios.: