Se trata de uno de las cáses gráficas más versátiles que puedas encontrar en el framework Desktop de Xojo, dado que permite crear controles de UI complejos como si se tratase de plantear su diseño sobre una ventana. Luego podrás agregar el Container Control sin más en los diseños de las ventanas de tu app, facilitando tanto la encapsulamiento como también la posibilidad de añadir más instancias de dicho control en tiempo de ejecución (dinamismo). ¿Quieres verlo en acción? Te propongo que sigas este tutorial para crear un campo de búsqueda multiplataforma basado en un par de clases personalizadas, a partir del Canvas, y también en el uso de un Container Control.
Las explicaciones que se indican a continuación se basan en el proyecto que puedes descargar desde este enlace, y que de hecho supuso el punto de partida en el que se basa el control de búsqueda utilizado en la aplicación Snippery. Por tanto, puedes hacerte una mejor idea del tipo de aplicaciones y funcionamiento que se puede añadir en una aplicación real. Eso sí, con la ventaja de que su comportamiento como su aspecto (unificación en la interfaz de usuario) serán exáctamente los mismos tanto si despliegas tus aplicaciones bajo macOS como en Windows o Linux.
Los preparativos
Para crear nuestro control de búsqueda utilizaremos una serie de componentes que cooperarán entre sí: un TextField que recibirá la entrada de teclado por parte del usuario (es decir, donde se introduce el texto a buscar); una subclase de Canvas que se encargará de dibujar el típico icono de lupa, indicando así la función del control en su conjunto, además de pintar el fondo y el borde de contorno del control en su totalidad; y una segunda subclase de Canvas que, en este caso, se encargará de pintar el icono de cancelación de búsqueda cuando se haga clic sobre él, eliminando por tanto el texto tecleado en el TextField hasta ese momento.
Por supuesto, partiendo con dichos elementos no representaría problema alguno trabajar con ellos de forma individual sobre el diseño o Layout de la ventana de Interfaz de usuario; pero estaríamos perdiendo algo muy importante: encapsulación. Es decir, presentar hacia el resto del código (y nuestra app) la funcionalidad que proporcionan de forma simplificada como si se tratase de un único elemento.
Por otra parte, mediante el uso de un Container Control estaremos ganando también en comodidad por ejemplo a la hora de ajustar sus tamaños y la posición relativa de cada uno de los elementos entre sí.
Las imágenes
El control utiliza tres imágenes para un total de dos funciones: la lupa y también el elemento gráfico que aparecerá sólo cuando el TextField contenga texto, de modo que si pulsamos sobre él se borre el texto introducido. Dicho elemento gráfico tendrá además que mostrar un cambio de aspecto cada vez que el apuntador del ratón esté sobre él, mostrando de este modo que se trata de un control activo. Es decir, que se puede hacer clic sobre él.
Afortunadamente Xojo facilita enormemente la gestión de gráficos en resolución normal y HiDPI/Retina en tiempo de ejecución, y nosotros sólo hemos de encargarnos de utilizar nuestro editor favorito para preparalas y exportarlas con la suficiente calidad. Si usas macOS, te recomiendo utilizar el formato PNG para los gráficos que prepares de cara a su uso en la interfaz de usuario de tus aplicaciones, así como el fabuloso editor gráfico Sketch.
Ahora bien, para que esto sea así no te limites a arrastrar las imágenes que desees utilizar directamente en tu proyecto Xojo. De ser así, se importará el gráfico sin más (en realidad un alias o un apuntador a la ubicación real del archivo gráfico en el disco duro de tu ordenador). En vez de ello, utiliza la opción Insert > Image para que se añada al proyecto una instancia Image mostrando el editor correspondiente. De este modo ya podrás importar cada una de las versiones del gráfico a diferentes resoluciones sobre los correspondientes contendores. Por supuesto, la etiqueta de texto que utilices en el apartado Name del Inspector se corresponderá con el nombre de la instancia que podrás referenciar posteriormente desde código o bien cuando la asignes como propiedad en otros controles del proyecto.
Si examinas el proyecto de ejemplo, observarás que cada una de las tres imágenes utilizadas —searchIcon, CloseSelected y Close— tienen sus correspondientes versiones de baja y alta resolución.
Elemento gráfico de cancelación
Ya vimos en un tutorial anterior cuan fácil resulta crear controles gráficos personalizados con Xojo como subclases de Canvas. Para crear el elemento gráfico de cancelación seguiremos exáctamente el mismo principio:
- Creamos una subclase de Canvas, en nuestre ejemplo denominada CloseButtonClean.
- Añadimos los eventos Open, MouseEnter y MouseExit
- En el evento Open introducimos el siguiente código, básicamente asignar el gráfico que representa su estado inicial, además de asegurarnos de que no será inicialmente visible:
me.Backdrop = close me.Visible = false
- Utilizamos los eventos MouseEnter y MouseExit para modificar el gráfico que indica su estado de control activo. Así en MouseEnter se incluye la siguiente línea de código:
me.Backdrop = CloseSelected
- Y en el evento MouseExit, la siguiente línea:
me.Backdrop = close
Con lo anterior tenemos resuelto el aspecto visual de nuestra pequeña clase. Ahora bien, también es deseable que presente una interface externa para que otros elementos del programa puedan actuar de forma sencilla sobre el estado del control. ¿Qué mejor que incorporar un par de métodos?
Con la clase seleccionada sólo hemos de utilizar Insert > Method para añadir nuevos métodos sobre la misma. Repite la operación un par de veces utilizando el panel Inspector para cambiar el nombre de cada método a Activate y Deactivate, respectivamente. En ambos casos hay que dejar el ámbito (Scope) de los métodos como Public, de modo que éstos sean visibles —es decir, que se puedan invocar— desde fuera de la propia clase y sus instancias.
Selecciona el método recién creado Activate e incluye el siguiente código en el Editor de código resultante:
me.Visible = true
El método Deactivate contendrá el siguiente código:
me.Visible = false me.Backdrop = Close
Dibujar el control de Búsqueda
La segunda de las clases basadas en Canvas no es mucho más compleja. En este caso no se trata de asignar un gráfico determinado a la propiedad Backdrop tal y como hemos visto, sino que empleamos la segunda de las vías para crear un control gráfico personalizado: emplear el evento Paint del Canvas. Es decir, dibujamos directamente el control cada vez que este necesite refrescar su aspecto.
Por tanto, sólo tendremos que añadir una nueva subclase basada en Canvas al proyecto —en nuestro ejemplo denominada cSearchBackground— y añadir el siguiente código en el evento Paint:
g.PenWidth = 1 g.PenWidth = 1 g.ForeColor = &caaaaaa g.DrawRoundRect(0,0,g.Width,g.Height,5,5) g.ForeColor = &cffffff g.FillRoundRect(1,1,g.Width-2, g.Height-2,5,5) dim midY as integer = (g.Height/2) - (searchIcon.Graphics.Height/2) g.DrawPicture(searchIcon,5,midy)
Básicamente, se trata de pintar el borde del control con un determinado color (&caaaaaa, por su notación hexadecimal), y a continuación pintar de color blanco el fondo. Por último, se dibuja el icono de la lupa en la posición izquierda con unos márgenes determinados sobre los bordes superior e izquierdo del control. Eso es todo.
Crear el Container Control: ContainerSearchField
Con todos los elementos base creados, ya estamos en disposición de crear nuestro control gráfico compuesto, y para ello nada mejor que el Container Control que añadiremos al proyecto con la opción Insert > Container Control.
Lo primero que observarás cuando añadas un Container Control al proyecto es que su Editor es prácticamente el mismo que el Editor de Ventanas. Después de todo se trata de añadir otros controles gráficos sobre su superficie en los que además podrás ajustar sus posiciones relativas y el tamaño de cada uno de ellos. Tal y como harías realmente en el diseño de una ventana.
Con el recién ContainerControl seleccionado, utiliza el Panel Inspector y asigna ContainerSearchField al atributo Name.
Ahora solo tendremos que añadir una instancia de nuestra clase cSearchBackground al Container Control, así como un TextField desde la Biblioteca del IDE de Xojo y, por último, el control de cancelación de búsqueda a la derecha del TextField. El aspecto del conjunto, una vez ajustada la altura del Container Control y también su ancho, debería ser similar al siguiente:
Querremos que el fondo se ajuste correctamente al tamaño que se utilice del Container Control en cada momento sobre el diseño de las ventanas en las que se utilice. Por tanto, selecciona la instancia de clase —cSearchBackground1, en el ejemplo— y añade el evento Open para asignar el siguiente código:
me.Width = self.Width
Por otro lado, y también como parte de los preparativos, también querremos que el TextField se encargue de mostrar nuestro control de cancelación cada vez que exista texto. Por tanto, selecciona la instancia —searchField, en el ejemplo—, añade el evento TextChange e introduce el siguiente código en el Editor resultante:
if me.Text <> "" then CloseButtonClean1.activate else CloseButtonClean1.deactivate end
De igual modo, y con el TextField aun seleccionado, dirígete al Panel Inspector asociado e introduce el texto “Search” en el atributo CueText bajo el apartado Initial State. Este será el texto que se muestre por omisión cuando el TextField no contenga texto introducido por el usuario, dando así una pista adicional de su función.
Por supuesto, también tenemos que añadir la funcionalidad sobre la propia instancia de nuestro control de cancelación de búsqueda —CloseButtonClean1, en el ejemplo—, añadiendo para ello el eventoMouseDown** e introduciendo el siguiente código:
searchField.Text = ""
Cuando utilizamos Continer Control es más que probable que debamos de resolver el modo en el que se ajustan cada uno de los controles gráficos que lo componen en respuesta al redimensionado del propio Container Control. En este caso se resuelve añadiendo el método doResize sobre el propio Container Control. El código utilizado es el siguiente:
closeButtonClean1.Left = me.Width - 24 searchField.Width = (Me.Width - 27) - searchField.Left cSearchBackground1.Width = me.Width cSearchBackground1.Invalidate
Como puedes observar, dicho método recalcula la posición relativa para el widget de cancelación de búsqueda así como para el TextField, además de actualizar el ancho de nuestro control de fondo para que ocupe todo el ancho del propio Container Control. Por último se invoca el método Invalidate sobre la instancia de nuestra subclase para que se redibuje el fondo en consecuencia.
Por último, sólo resta añadir al Container Control un par de eventos: Open y Resizing. El primero se encargará de poner el estado inicial, mientras que el segundo invocará el método doResize cada vez que se modifique el tamaño del Container Control.
Una vez añadidos dichos eventos, selecciona el evento Open e introduce el siguiente código en el Editor asociado:
doResize
Al igual que en el editor correspondiente al evento Resizing:
doResize
Usar el Container Control
¡Y eso es todo! Para comenzar a utilizar el control sólo hay que añadir la instancia del Container Control, ContainerSearchField, a cualquiera de las ventanas del proyecto en curso o de cualquier otro proyecto en el que deseemos utilizarlo. Por supuesto, en este caso no está implementada la funcionalidad de qué es lo que ocurre cuando se ejecuta la búsqueda propiamente dicha, dado que esto variará de aplicación en aplicación y probablemente no te cueste mucho añadir dicha capacidad en el evento apropiado del TextField (por ejemplo al pulsar la tecla Retorno); o bien añadir prestaciones adicionales como pueda ser el autocompletado en el TextField utilizado para las búsquedas.
Si no has entendido alguna de las explicaciones de este tutorial, te recomiendo el libro electrónico “Programación Multiplataforma Xojo”.
* Esta entrada se ha escrito en formato Markdown y exportada como HTML en Snippery.
Hola Javier,
Que buen artículo y muy a propósito de una inquietud que me ha surgido. Utilizar este control en modo de diseño es relativamente sencillo. Pero, instanciarlo desde código para llamar uno ó mas de ellos y exponerlo al interior de una interfaz es otra cosa.
Un ejemplo sencillo mostrándolo en un formulario seria:
Sin embargo, como se maneja la visibilidad o el “Hide” de uno o mas controles container desde otra rutina de código ?
Quiero decir, si llamo por ejemplo el container2 después de haber llamado el container1, deberia tener la posibilidad de ocultar el numero 1 (no cerrarlo …); pero al utilizar el siguiente código:
No se expone la propiedad .Visible sino .Close, .Handle, .Index, etc
No sé si estoy mal en el enfoque, pero no encuentro una manera llamar estos containers por demanda, y exponerlos en un formulario sin necesidad de cerrar alguno previamente ya que perderia eventualmente los datos que ya tuviese, sin mencionar el hecho de que se me crearia una nueva instancia siendo esto innecesario si el control ya fué llamado previamente.
Un saludo,
Hola,
Los ContainerControl son realmente versátiles, si bien están a medio camino entre un Control (no son realmente un control como tal) y una Ventana (sin llegar a serlo). En ningún caso es posible “ocultarlo”, sólo puedes añadirlos a otro ContainerControl o una ventana, e invocar el método “close” como indicas para eliminarlos.
Para no perder la información de un containercontrol previo a su destrucción debes de implementar la lógica encargada de conservar los datos expuestos en el evento Close, de modo que puedas volver a recuperarlos posteriormente, ya sea desde otra parte de la lógica de la aplicación o bien para volver a mostrar dicha información nuevamente en el mismo ContainerControl.