Los arrays de controles gráficos, ya sean los disponibles en el Framework o los basados en tus propias clases gráficas, son una gran ventaja cuando utilizas varias instancias en el diseño de tus ventanas y precisas referirte a ellas desde código de una forma elegante. Esto es, sin tener que utilizar directamente el nombre de cada una de las instancias creadas a partir de la clase. Por ejemplo, esto nos permite enviar una orden a una instancia en concreto, o bien la misma orden a todas las instancias, sin que sea necesario conocer de antemano cuántas de ellas existen.
El principal inconveniente de esta capacidad es que no podrás utilizarla cuando en el diseño de tus interfaces de usuario (UI) se ven involucrados los ContainerControl. Por su especial idiosincrasia, a partir de que quieras utilizar un array de controles como parte del diseño de un ContainerControl, la solución deja de ser sencilla.
Sin embargo, este problema tiene una fácil solución; como es, por ejemplo, la mostrada en la siguiente técnica.
Crear subclases
Lo prumero que necesitarás será crear una subclases de aquellos controles estándar del Framwework que quieras utilizar como un Array de Controles, o bien implementar esta capacidad directamente cuando diseñes tus propios controles.
Para ello, añade una nueva Clase a tu proyecto Xojo desde el menú Insert y utiliza el Panel Inspector para modificar tanto el nombre de la nueva clase como para indicar la clase padre en la cual se basarán. En este ejemplo utilizaremos los siguientes valores:
- Name: MMButton
- Super: PushButton
(Puedes cambiarlos por cualquier otro que necesites.)
Crear el array que guardará todas las instancias
A continuación necesitamos un tipo de datos que nos permita guardar las referencias a todos los botones que añadamos a nuestros diseños de UI; y para ello nada mejor que añadir sobre nuestra recién creada clase una Propiedad cuyo tipo de datos será el de un array de nuestra clase. En nuestro ejemplo, con la clase seleccionada, añadiremos una nueva Propiedad Compartida desde el menú Insert y utilizaremos los siguientes valores en el Panel Inspector:
- Name: ClassButtons()
- Type: MMButton
- Scope: Private
El hecho de que sea una Propiedad Compartida (Shared Property), significa que estará disponible de forma única para todas las instancias (objetos) creadas a partir de las clases; a diferencia de las Propiedades normales, donde cada instancia tiene su propia “copia” de la propiedad y, por tanto, capacees de contener diferentes valores por cada uno de los objetos de la clase.
Otro detalle importante es que, en este caso, hemos limitado el acceso a la Propiedad Compartido para que sólo esté disponible para la clase y los objetos creados a partir de ella; lo que significa que no será visible si intentásemos acceder al Array de la clase desde código que resida fuera de los eventos o métodos de la propia clase.
Definir un índice para cada objeto
Por supuesto, tendremos que buscar la forma de referirnos a cada una de las instancias creadas a partir de la clase; por ejemplo, cuando queramos actuar (o enviar un mensaje) únicamente a un elemento y no a todos ellos. Para esto, nada mejor que definir una nueva Propiedad (pero esta vez, Propiedad de instancia) y definiendo su tipo a Integer:
- Name: ButtonIndex
- Type: Integer
- Scope: Private
Conocer cuántos objetos utilizamos en el diseño
Emulando el propio comportamiento de los Array de Controles por defecto de Xojo, necesitamos conocer en tiempo de ejecución cuántas instancias estamos utilizando; y una buena forma de saberlo es utilizando el Evento Open, de modo que en este paso de la inicialización del control sea esta misma quien se encargue de agregarse al Array compartido y también de definir su valor de índice.
Para ello, añade el Manejador de Evento Open a la clase e introduce simplemente el siguiente fragmento de código:
Sub Open() Handles Open ClassButtons.Append Me Me.ButonIndex = ClassButtons.Ubound RaiseEvent Open End Sub
¿Qué significa RaiseEvent Open? No pierdas de vista que estamos definiendo el comportamiento de nuestra clase, de modo que si estamos consumiendo en esta el Evento Open, esto significará que no estaremos dando la oportunidad de que cada una de las instancias, creadas a partir de la clase, puedan ejecutar su propio código de inicialización en este mismo Evento.
La solución es bien simple, añade una Definición de Evento (Event Definition) y utiliza los siguientes valores en el Panel Inspector:
- Event Name: Open
Definir el comportamiento
Para ver el ejemplo en acción, necesitaremos definir un método que nos pemrita indicarle a cualquiera de las instancias disponibles que responda. En nuestro caso, nos limitaremos a presentar un mensaje en pantalla cuyo contenido será la propia etiqueta que hayamos asignado a cada uno de los botones basados en nuestra clase.
Al igual que ocurre con las Propiedades Compartidas (Shared Properties), los Métodos Compartidos (Shared Methods) son el recurso del lenguaje que nos permite definir un comportamiento para la Clase en vez de que este deba ser invocado como un método para una instancia concreta. Por tanto, añade un nuevo Método Compartido a la clase utilizando los siguientes valores en el Panel Inspector:
- Method Name: SayHello
- Parameter: Index as Integer
A continuación, introduce el siguiente código en el Editor de Código asociado:
if classButtons.ubound <> -1 and index >= 0 and index <= ClassButtons.Ubound then MsgBox ClassButtons(index).Caption
Probar la Clase
Ahora sólo queda probar su funcionamiento. Para ello, arrastra sobre una ventana tantas copias de nuesra clase de UI como quieras, así como un botón estándar (aunque podría ser una copia más de nuestra clase) y un campo de texto (TextField).
El campo de texto nos permitirá introducir el número corresondiente al Índice del control al que queremos enviar el mensaje, mientras que en el botón adicional simplemente tendremos que añadir el Manejador de Evento Action e introducir la siguiente línea de código:
MMButton.SayHello( TextField1.Text.val - 1)
Como ves, a la hora de invocar el método utilizamos el nombre de la clase y no el correspodiente a una de sus instancias.
Por supuesto, esta técnica no sólo funcionará cuando añadas los controles directamente sobre una Ventana, sino que también puedes añadirlos a un ContainerControl y este, a su vez, a una venana. El resultado será el mismo: ¡seguirá funcionando y tendrás una forma sencilla y elegante de dirigirte a cada una de los controles!