Xojo 2021r3: Añadir Controles UI en tiempo de ejecución

El proceso de añadir nuevos controles a nuestras ventanas (o contenedores) en tiempo de ejecución se ha simplificado enormemente. Ya no tendrás que lidiar con arrays de controles; en vez de ello sólo tendrás que llamar al método AddControl sobre la ventana o ContainerControl en la que desees añadir el nuevo control. Continúa leyendo y te mostraré cómo hacerlo a través de un ejemplo sencillo.

El nuevo método AddControl acepta cualquier instancia de DesktopUIControl que quieras añadir en una ventana o ContainerControl, de modo que sólo tendrás que crear una nueva instancia desde código y sobre la marcha para pasarla como parámetro para que el método AddControl haga su magia.

Para que la nueva instancia de un DesktopUIControl sea realmente visible en la ventana o ContainerControl de destino también tendrás que definir algunas propiedades básicas, como por ejemplo los valores de ancho y altura para la nueva instancia del control, así como las propiedades Left y Top que indican su posición relativa sobre la esquina superior izquierda de la ventana o el ContainerControl donde se va a incluir. De modo que un valor Top = 0 y Left = 0 significa que el control se añadirá en la esquina superior izquierda de la ventana.

Para ver cómo funciona todo esto vamos a crear un proyecto Desktop de ejemplo en el que podrás añadir tantas instancias como desees en tiempo de ejecución del control DesktopButton, también podrás eliminar dichas instancias del ContainerControl sobre el que se van a añadir (llamando para ello al método Close sobre la propia instancia), así como modificar la posición de los controles añadidos a medida que se modifique el tamaño del ContainerControl que los contiene.

1. Crear el ContainerControl Base

Abre Xojo y crea un nuevo proyecto Desktop. A continuación, haz doble clic en el ContainerControl desde la Librería para que se añada al Navegador en el IDE. Utiliza el Panel Inspector para cambiar las siguientes propiedades:

  • Name: BaseContainer

Añade ahora una nueva propiedad a la subclase BaseContainer en el Navegador (puedes hacerlo desde el menú contextual), definiendo los siguientes valores en el Panel Inspector asociado:

  • Name: Controls()
  • Type: DesktopUIControl
  • Scope: Public

2. Añadir Nuevos Controles a BaseContainer

Vamos a tener que añadir algunos métodos a nuestro BaseContainer, comenzando con el responsable de añadir nuevas instancias DesktopButton sobre el mismo. Por tanto, añade un nuevo Método a BaseContainer utilizando los siguientes valores en el Panel Inspector asociado:

  • Method Name: AddNewControl
  • Scope: Public

Y escribe el siguiente fragmento de código en el Editor de Código asociado con el método recién añadido:

// Definimos algunos valores estáticos para las nuevas instancias
// de boton: valores de ancho y nombre
Static cWidth As Integer = 40
Static cInitialName As String = "ControlName"

// Coordenadas X e Y iniciales para la instancia del control
Var startX As Integer = If(controls.Count = 0, 20, controls(controls.LastIndex).Left + cWidth + 20)
Var startY As Integer = If(controls.Count = 0, 20, controls(controls.LastIndex).top)

// Necesitamos ajustar las coordenadas X/Y si el control
// excede el ancho disponible del BaseContainer
// exceeds the available BaseContainer width
If startX + cWidth > Me.Width Then 
  startx = 20
  starty = starty + controls(controls.LastIndex).height + 20
End If

// Creamos la nueva instancia DesktopButton a añadir
// ajustando los valores esperados para que sea visible
// en el tamaño y posición deseados.
Var ncontrol As New DesktopButton
Var idxNumber As Integer = controls.LastIndex + 1
nControl.Name = cInitialName + idxNumber.ToString
nControl.Caption = idxNumber.ToString
nControl.Left = startx
nControl.Top = starty
nControl.Width = cWidth

// En macOS obtenemos la altura del control por omisión
// no así en Windows.
#If TargetWindows Then
  nControl.Height = 22
#EndIf

// Asignamos el método que llamará la instancia
// cuando el usuario pulse el botón
// de modo que podamos atraparlo y reenviarlo
// a la instancia creada a partir del BaseControl
AddHandler nControl.Pressed, AddressOf controlActionCallback

// Añadimos el control al Array de controles
controls.AddRow nControl

// …y también al propio BaseContainer
// utilizando para ello la llamada al método AddControl
Me.AddControl nControl

Como puedes ver, estamos usando el método AddHandler para sustituir el Evento disparado originalmente por el control por nuestro propio método; de modo que hemos de crearlo. Añade un nuevo método a BaseContainer utilizando los siguientes valores en el Panel Inspector:

  • Name: ControlActionCallback
  • Parameters: tControl As DesktopUIControl
  • Scope: Public

Y escribiendo la siguiente línea de código en el Editor de Código asociado:

RaiseEvent ControlPressed(tControl)

3. Eliminar Controles de BaseContainer

El tercer y último método que hemos de añadir a nuestra subclase BaseContainer es el responsable de ir eliminando los controles añadidos; de modo que añadiremos un nuevo método a la subclase BaseContainer utilizando los siguientes valores:

  • Method Name: RemoveLastControl
  • Scope: Public

Y escribiendo el siguiente fragmento de código en el Editor de Código asociado para el método:

If controls.LastIndex <> -1 Then
  
  Var tControl As DesktopUIControl = controls.pop
  
  // We need to remove the callback method from the original
  // Pressed event on the instance
  RemoveHandler DesktopButton(tControl).pressed, AddressOf controlActionCallback

  // Closing the control instance so it is removed from the
  // containing BaseControl
  tControl.close
  
End If

4. Reenvío de Eventos

Dado que recibimos el evento Pressed del DesktopButton en nuestro método ControlActionCallback, y este realiza a su vez una llamada al Evento ControlPressed pasando la instancia de control recibida, tendremos que definir dicho Evento en nuestra subclase BaseContainer utilizando para ello los siguientes valores en el Panel Inspector asociado:

  • Event Name: ControlPressed
  • Parameters: tControl As DesktopUIControl

5. Reaccionar al cambio de Tamaño

Queremos que nuestro BaseContainer reaccione a los cambios realizados a su ancho o altura, de modo que tengamos la oportunidad de cambiar la posición de los controles añadidos para que continúen siendo visibles. Por tanto, con el BaseContainer seleccionado en el Navegador, añade el Evento Resizing y escribe el siguiente fragmento de código en el Editor de Código asociado:

#Pragma DisableBackgroundTasks
#Pragma DisableBoundsChecking

Var startX As Integer = 20
Var startY As Integer = 20

Var lastIndex As Integer = controls.LastIndex
Var tControl As DesktopUIControl

For n As Integer = 0 To lastIndex
  
  tControl = controls(n)
  
  If n <> 0 Then startx = controls(n-1).Left + controls(n-1).Width + 20
  
  If startx > Me.Width - tControl.Width Then
    startx = 20
    if n <> 0 then starty = starty + tControl.Height + 20
  End If
  
  tControl.Left = startx
  tControl.top = starty
  
  
Next n

6. Diseñar la Interfaz de Usuario

Selecciona la ventana Window1 en el Navegador para que se muestre en el Editor de Diseño. A continuación, arrastra un Botón desde la Librería y sitúalo en la parte superior izquierda de la ventana respetando los márgenes indicados por las guías de alineación.

Con el botón añadido seleccionado, cambia los siguientes valores en el Panel Inspector asociado:

  • Name: AddControlButton
  • Locking: Left and Top locks closed.
  • Caption: “Add Control”

Utiliza los manejadores de tamaño del control para que sea lo suficientemente ancho como para que se muestre correctamente el texto de la propiedad Caption

Añade el Evento Pressed al botón y escribe la siguiente línea de código en el Editor de Código asociado:

BaseContainer1.AddNewControl

Añade un segundo botón y sitúalo justo debajo del anterior. Utiliza el Panel Inspector asociado para definir los siguientes valores:

  • Name: RemoveControlButton
  • Locking: Left and Top locks closed.
  • Caption: “Remove Control”

Utiliza los manejadores de cambio de tamaño del control para que sea lo suficientemente ancho como para mostrar correctamente el texto de la propiedad Caption.

Añade el evento Pressed al botón recién añadido y escribe la siguiente línea de código en el Editor de Código asociado:

BaseContainer1.RemoveLastControl

Añade ahora una Label desde la Librería y sitúala justo a la derecha del primer botón. Utiliza el Panel Inspector para definir los siguientes valores:

  • Locking: Left and Top locks closed.
  • Text: “Control Pressed:”

Utiliza los manejadores de cambio de tamaño del control para que sea lo suficientemente ancho como para mostrar el texto de la propiedad Text.

Añade una segunda Label justo a la derecha de la anterior, utilizando el Panel Inspector para definir los siguientes valores:

  • Name: ControlPressedName
  • Locking: Left and Top locks closed.
  • Text: “”

Por último, arrastra el BaseContainer desde el Navegador y sitúalo bajo el segundo botón. Utiliza los manejadores de cambio de tamaño del control de modo que cubra toda la superficie restante de la ventana. El diseño final debería de tener el siguiente aspecto:

Utiliza el Panel Inspector para definir los siguientes valores:

Locking: left, top, right and bottom clocks closed

Con la instancia de BaseContainer1 seleccionada en el Editor de Diseño, añade el evento ControlPressed y escribe la siguiente línea de código en el Editor de Código asociado:

ControlPressedName.Text = tControl.name

7. Ejecutar la App

¡Todo listo! Ejecuta la app, pulsa el botón “Add Control” y añade tantos botones como quieras al control BaseControl1, pulsa el botón “Remove Control” para eliminar controles. Prueba a cambiar el tamaño de la ventana y observarás cómo los controles cambian sus posiciones a medida que sea necesario.

3 comentarios en “Xojo 2021r3: Añadir Controles UI en tiempo de ejecución

  1. Juanjo Menéndez

    Buenos días, Javier: quería hacerte una sugerencia que, evidentemente, dejo por completo a tu libre albedrío y es que en el curso de programación de Xojo pusieses algún ejemplo de “arrastar y soltar” texto en un campo de texto. He intentado comprender el ejemplo de youtube en inglés (con imagen o fichero), pero creo que el narrador da muchas cosas por supuestas y que un ejemplo en español facilitaría las cosas a muchos usuarios noveles. Muchas gracias y saludos.

    1. Javier Rodriguez

      Hola Juanjo,

      ¿Podrías pasarme el enlace al vídeo en inglés al que haces referencia? Así tendría una mejor comprensión de lo que necesitas.

      1. Juanjo Menéndez

        Buenas tardes, Javier:
        perdón por la tardanza en contestar. He estado de vacaciones en Armenia con mi pareja y no hemos parado. Te adjunto el enlace, pero, en realidad lo que intento es averiguar cómo funciona el “arrastrar y soltar”, por ejemplo, un texto desde cualquier aplicación externa a cualquier control de texto , sea un textfield o un textarea) sw Xojo, si eso es posible. En el ejemplo en inglés, por lo poco que he entendido, explica cómo arrastrar una imagen o un fichero a un canvas. Muchas gracias y saludos. Enlace ejemplo en inglés: https://youtu.be/jKBl3tLot74

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *