Herencia visual en C#
Una de las características que distinguen a un programador profesional de uno aficionado es el uso de ciertos recursos que permiten ahorrar tiempo de programación reutilizando código... y diseño. Desde Delphi 2, es posible usar herencia visual. Y Delphi 5 introdujo un nuevo recurso: los frames, o marcos, que permiten reutilizar fragmentos de un formulario, en vez de tener que cargar con todo el formulario.
Por supuesto, existen equivalentes en Visual C# para estos dos recursos de programación. La "herencia visual" de Delphi se llama simplemente "herencia", y el equivalente de un frame es el user control, o control de usuario, con variantes específicas para Windows Forms y ASP.NET.
SORPRESA
Pero si intentamos aplicar mecánicamente en Visual Studio las técnicas que hemos aprendido en Delphi, podemos llevarnos alguna "sorpresa". Suponga que tenemos un formulario simple, con una barra de herramientas, como en la siguiente imagen:
Vamos a crear otro formulario por herencia, usando el formulario anterior como base. En el submenú Proyecto, elegimos el comando Agregar formulario heredado, y en el diálogo que se muestra a continuación, elegimos Form2 como formulario base de la herencia:
Este es el resultado de la herencia... aunque el garabato que aparece en la esquina superior izquierda de la barra de herramientas heredada no presagia nada bueno:
Y así es, en efecto. Si intentásemos añadir un nuevo botón a la barra de herramientas heredada, nos llevaríamos una sorpresa: ¡no podemos!
Al seleccionar la barra de herramientas, encontramos que todas las propiedades de la misma se encuentran inactivas en el Inspector de Propiedades, en particular, la propiedad Buttons, que permite controlar la colección de botones. Conozco el caso de algún programador que, al llegar a este punto, dejó Visual Studio por incorregible y corrió a llorar sus penas sobre el tibio regazo de Delphi 7.
ORIENTADO A OBJETOS... PERO DE VERDAD
Lo que sucede, sin embargo, es que Visual Studio está aplicando a rajatabla los preceptos de la Programación Orientada a Objetos, algo que Delphi se pasa sistemáticamente por el forro de sus plumones. Si escudriña el código fuente del formulario base, descubrirá la siguiente declaración para la barra de herramientas:
private System.Windows.Forms.ToolBar toolBar1;
¡La barra de herramientas es un campo privado del formulario original! Por lo tanto, es imposible modificar sus características desde una clase derivada. Normalmente, esto es lo adecuado, pero en este caso se trata de un incordio. Para solucionarlo, nos vamos al formulario base, seleccionamos la barra de herramientas en el Inspector de Propiedades y buscamos la "propiedad" Modifiers:
Está claro que Modifiers no es una propiedad, en el sentido estricto de la palabra. Estamos hablando del nivel de visibilidad del campo asociado al componente, y debemos cambiarlo a protected. Si revisa ahora el código fuente, notará enseguida el cambio:
protected System.Windows.Forms.ToolBar toolBar1;
private System.Windows.Forms.ToolBarButton toolBarButton1;
private System.Windows.Forms.ToolBarButton toolBarButton2;
Como puede ver, los botones de la barra siguen siendo privados, y no pueden modificarse directamente en los descendientes mediante el Inspector de Propiedades. Si quisiéramos tal cosa, tendríamos que entrar en el editor de la propiedad Buttons de la barra de herramientas, y cambiar también el valor de Modifiers para aquellos botones para los que permitiremos modificaciones en formularios derivados. Será usted quien decida cuánta flexibilidad debe dejarse a quienes hereden del formulario base.
Es curioso, no obstante, que en este caso sí podamos modificar características de los botones heredados en tiempo de ejecución:
public Form3()
{
InitializeComponent();
toolBar1.Buttons[0].Text = "Nuevo texto";
}
UNA ALTERNATIVA
De todos modos, reconozcamos que dejar un objeto expuesto al público cuando sólo nos interesa que se pueda modificar una de sus propiedades, es excesivo. Si es éste nuestro caso, podemos tomar otra vía: añadir una propiedad al formulario base.
// Definida en Form2.cs
public string ButtonText
{
get { return button1.Text; }
set { button1.Text = value; }
}
Como se trata de una propiedad pública, Visual Studio la muestra en el Inspector de Propiedades al seleccionar Form3:
Esta es una grata novedad, porque mostrar una nueva propiedad en un formulario o en un frame de Delphi, en tiempo de diseño, es toda una hazaña.
ODIOSAS COMPARACIONES
Hablando de Delphi, ¿qué tal queda su sistema de herencia visual en comparación con el modelo implementado por Visual Studio? En mi opinión, aunque el sistema de Visual Studio parece más rígido, es mucho más práctico, y no hablemos ya de su potencia.
Para calentar motores, ¿cómo es posible que el diseñador de Visual Studio tuviese tanta información inicialmente sobre la barra de herramientas, si es que se trataba de un campo privado introducido en un ancestro? La respuesta está en la reflexión: a pesar de todo, la clase guarda suficiente información para tiempo de ejecución como para hacer posible esto. La reflexión debe poder indagar en toda la estructura del objeto, sin importar su visibilidad. Tenga presente que esto es lo que hace posible su uso para la serialización automática de las técnicas de control remoto. Recuerde que el motivo para declarar, y por lo tanto esconder un campo privado no es la seguridad: no se trata de que haya que esconder algo para protegerlo, sino que ocultamos información para ahorrar detalles innecesarios a quienes utilizarán una clase o componente. El otro objetivo es responsabilidad de las técnicas de seguridad, hablando con propiedad.
Pues bien: Delphi históricamente ha confundido RTTI con niveles de visibilidad. En Delphi, published es necesariamente public. ¿El resultado? Cada vez que lanzamos un TDataSet sobre un módulo de datos y configuramos sus campos, estamos publicando automáticamente toda esa información. Es imposible impedir que, desde algún otro módulo o formulario, alguien "toque" información almacenada en estos objetos involuntariamente publicados, creando las consabidas y perniciosas dependencias.
Para mayor escarnio, los frames de Delphi sufren un molesto bug desde que fueron introducidos en la herramienta. Al parecer, el mecanismo de restauración de propiedades a partir del fichero DFM tiene problemas cuando se trata de frames. Una de las actualizaciones a Delphi 6 menciona de pasada que este bug ha sido resuelto... pero un servidor encuentra, con excesiva frecuencia, que la posición de un frame en tiempo de diseño se pierde, y que al cargar un proyecto, sus frames aparecen más allá de la esquina inferior derecha del monitor.
Y aunque estamos hablando, para ser exactos, de herencia visual, debe saber que los frames de Delphi se muestran bastante timoratos cuando se trata de su inicialización. Un frame no tiene eventos OnCreate/OnDestroy, y para inicializar o destruir recursos debemos reescribir su constructor o su destructor. El problema, como comprenderá, está en que un frame, al igual que un formulario, debe ofrecer mecanismos para interceptar tanto el momento en que se crea la instancia, como el momento en que se le asignan todas sus propiedades a partir del recurso DFM asociado. Para ello, el programador se ve obligado a cambiar de paradigma de trabajo y a utilizar métodos protegidos como Loaded, que no todos conocen. Para colmo, en algunas zonas de la VCL, los propios programadores de Borland no han manejado consistentemente el patrón de uso de Loaded (DB Express es el caso más sangrante).
|