Crear subclase ComboBox con ítems de menú ordenados

¡El control ComboBox es realmente potente! Combina las capacidades de un TextField y de un PopupMenu. Esto significa que puedes seleccionar cualquiera de las opciones disponibles desde el menú asociado (por ejemplo, aquellos asignados mediante la opción Appearance > Initial Value en el Panel Inspector), o simplemente escribiendo cualquier otro valor en el campo de texto del TextField.

Pero, en algunos casos, probablemente querrás que el ComboBox realice otras tareas no incluidas de serie en la clase propiamente dicha. Por ejemplo, puede que quieras añadir el texto tecleado por el usuario como una nueva opción disponible en el menú asociado con el ComboBox (por supuesto, asegurándote de no crear entradas duplicados), y que dichas entradas permanezcan ordenadas alfabéticamente.

Además, mientras que podemos utilizar el método AddAllRows para añadir un array de cadenas sobre las entradas ya disponibles en el menú ComboBox, es probable que también quieras hacer lo opuesto: recuperar u obtener todas las entradas de menú como un Array de Strings.

pastedGraphic.png

Es en estos casos cuando encontrarás realmente conveniente el hecho de que Xojo sea un lenguaje de programación OOP (Orientado a Objetos), de modo que puedas crear una clase a partir de una ya existente. De este modo, puedes crear tu propia subclase especializada a partir de la clase ComboBox, añadiendo la funcionalidad adicional y lista para usar tantas veces y en tantos proyectos multiplataforma de escritorio como precises.

¡Sigue leyendo para ver lo fácil que resulta crear tus propias subclases de modo que “encajen” en tus necesidades! (puedes descargar el proyecto Xojo de ejemplo).

1. Añadir una Clase al Proyecto

Comienza añadiendo un nuevo proyecto Xojo y seleccionando la opción Insert > Class del menú para añadir una nueva clase al Navegador de Proyecto.

Con el nuevo elemento Class1 seleccionado en el Navegador de Proyecto, cambia los siguientes valores en el Panel Inspector asociado:

  • Name: MyComboBox (puedes elegir cualquier otro nombre que desees).
  • Super: ComboBox

Confirma los cambios.

Verás como la nueva clase pasa a llamarse ahora MyComboBox en el Navegador de Proyecto, al tiempo que su icono pasa a ser el correspondiente al control ComboBox.

2 Añadir Manejadores de Evento a la Clase

Mantén seleccionado MyComboBox y selecciona a continuación la opción Insert > Event Handler en el menú. Esta acción abrirá una nueva ventana listando todos los Manejadores de Evento disponibles para la clase ComboBox y, por tanto, también para todas las clases creadas a partir de ella (como es el caso de nuestra clase).

Selecciona las entradas Change, KeyDown, Open y LostFocus en el listado y confirma los cambios haciendo clic sobre el botón “OK”. Se cerrará la ventana "Add Event Handler" y las entradas seleccionadas se añadirán al elemento MyComboBox en el Navegador de Proyecto.

Utilizaremos los manejadores de evento KeyDown y LostFocus para añadir las nuevas entradas en el menú del ComboBox.

Selecciona el evento KeyDown bajo el elemento MyComboBox en el Navegador de Proyecto y teclea el siguiente código en el Editor de Código asociado:

AddNewEntry(key)
Return RaiseEvent KeyDown(Key)

El método AddNewEntry será el encargado de añadir una nueva entrada al menú ComboBox.

Observa la línea de código RaiseEvent KeyDown(Key). Dado que nuestra subclase hace uso de dicho manejador de evento, esto significa que no estará disponible para cualquier instancia (objeto) creada a partir de la clase, como por ejemplo aquellas creadas cuando se arrastra la clase a una ventana en el Editor de Diseño. 

3 Añadir Definiciones de Evento

Con MyComboBox todavía seleccionado en el Navegador de Proyecto, selecciona la opción de menú Insert > Event Definition, utilizando a continuación los siguientes valores en el Panel Inspector asociado:

  • Event Name: KeyDown
  • Parameters: Key As String
  • Return Type: Boolean

De esta forma hemos creado el mismo Manejador de Evento para la clase, de modo que pueda ser implementado por cualquiera de las instancias creadas a partir de la clase; mientras que la línea de código RaiseEvent KeyDown(Key) se asegurará de invocar dicho evento para las instancias.

Selecciona ahora el manejador de evento LostFocus en el item MyComboBox del Navegador de Proyecto y escribe el siguiente código en el Editor de Código asociado:

Me.AddRow(Me.Text)
RaiseEvent LostFocus

Nuevamente, dado que estamos implementando dicho manejador de evento… necesitamos que esté disponible de nuevo para las instancias de clase. Con MyComboBox seleccionado, elige la opción de menú Insert > Event Definition utilizando los siguientes valores en el Panel Inspector asociado:

  • Event Name: LostFocus

Selecciona ahora el manejador de evento Open bajo MyComboBox, escribiendo el siguiente código en el Editor de Código asociado:

Var selectedIndex As Integer = Me.mSelectedRowIndex
Var s() As String = Me.rows
s.Sort
Me.RemoveAllRows
Me.AddAllRows s
If selectedIndex = -1 Then
  Me.mSelectedRowIndex = -1
  Me.Text = ""
End If
RaiseEvent Open

De nuevo, hemos de crear una nueva Definición de Evento para la clase utilizando estos valores:

  • Event Name: Open

Repite la última operación para añadir la última Definición de Evento necesaria, utilizando los siguientes valores:

  • Event Name: Change

4 Añadir Métodos a la Clase

Con MyComboBox aún seleccionado en el Navegador de Proyecto, elige la opción de menú Insert > Method, utilizando los siguientes valores en el Panel Inspector asociado:

  • Method Name: AddNewEntry
  • Parameters: Key As String
  • Scope: Protected

Y teclea a continuación el siguiente fragmento de código en el Editor de Código asociado con el nuevo método:

// If return key or tab key is pressed then we add the current text to the menu options
If key.Asc = 13 Or key.Asc = 9 And Me.Text <> "" Then
  Me.AddRow Me.Text
End If

Añade un segundo método a la clase, utilizando ahora los siguientes valores en el Panel Inspector asociado:

  • Method Name: Rows
  • Return Type: String()
  • Scope: Public

Y teclea el siguiente código en el Editor de Código correspondiente a dicho método:

Var r() As String
Var i As Integer = Me.RowCount-1
For n As Integer = 0 To i
  r.AddRow Me.RowValueAt(n)
Next
Return r

5 Sobreescribiendo los Métodos Disponibles

Queremos que los elementos de menú de nuestro ComboBox estén ordenados siempre alfabéticamente, y esto significa sustituir la funcionalidad obtenida por omisión mediante los métodos AddRow y AddAllRows incluidos de serie en el ComboBox. Además, no queremos que esté disponible el método AddRowAt del ComboBox para añadir una nueva entrada en una posición determinada (no tendría mucho sentido añadir una nueva entrada en una posición determinada del menú cuando no sabemos de antemano si permanecerá en ella una vez se ordenen alfabéticamente).

Añade un par de nuevos métodos a MyComboBox utilizando los siguientes valores:

  • Method Name: AddAllRows
  • Parameters: Items() As String
  • Scope: Public
  • Method Name: AddRow
  • Parameters: Item As String
  • Scope: Public

Selecciona el método AddAllRows y teclea el siguiente código en el Editor de Código asociado:

Var selectedItem As String = Me.SelectedRow
Var lastAddedItem As String = items(items.lastrowindex).Trim.Titlecase
Var d As New Dictionary
Var s() As String = Me.Rows
For n As Integer = 0 To s.LastRowIndex
  d.Value(s(n)) = Me.RowTagAt(n)
Next
For Each item As String In items
  If Not d.HasKey(item) and item <> "" Then s.add item.Trim.Titlecase
Next
s.Sort
Me.RemoveAllRows

// Calling the overridden superclass method.
Super.AddAllRows(s)
For n As Integer = 0 To s.LastRowIndex
  If d.HasKey(s (n)) Then
    Me.RowTagAt(n) = d.Value(s(n))
  End If
  If s(n) = selectedItem Then
    Me.mSelectedRowIndex = n
  End If
  If s(n) = lastAddedItem Then
    Me.mLastAddedRowIndex = n
  End If
Next

Selecciona a continuación el método AddRow de modo que puedas teclear el siguiente código en el Editor de Código asociado:

If item = "" Then Return
item = item.Trim.Titlecase
If Not Me.HasMember(item) Then
  Var selectedItem As String = Me.SelectedRow
  Var d As New Dictionary
  Var s() As String = Me.rows
  For n As Integer = 0 To s.LastRowIndex
    d.Value(s(n)) = Me.RowTagAt(n)
  Next
  s.AddRow item
  s.Sort
  Me.RemoveAllRows
  Super.AddAllRows(s)

  // Let's restore the original rowtags to their new spot in the menu
  For n As Integer = 0 To s.LastRowIndex
    If d.HasKey(s(n)) Then
      Me.RowTagAt(n) = d.Value(s(n))
    End If
    If s(n) = selectedItem Then
      Me.mSelectedRowIndex = n
    End If
    If s(n) = item Then
      Me.mLastAddedRowIndex = n
    End If
  Next
End If

Todavía necesitamos añadir un último método a nuestra clase, encargado de comprobar si el elemento a añadir ya existe entre las opciones disponibles en el menú. Por tanto, añade un nuevo método utilizando los siguientes valores:

  • Method Name: HasMember
  • Parameters: Item As String
  • Return Type: Boolean
  • Scope: Protected

Y tecla el siguiente código en el Editor de Código asociado al nuevo método:

Var b As Boolean
For Each s As String In Me.Rows
  If s = item Then
    b = True
    Exit For
  End If
Next
Return b

6 Sobreescribir Propiedades Existentes

Dado a que estamos ordenando alfabéticamente las entradas del menú, también tendremos que asegurarnos de que las propiedades LastAddedRowIndex y SelectedRowIndex apunten en todo momento al elemento correcto y también indique el índice correcto, respectivamente. Esto significa que necesitamos sobreescribir la funcionalidad encontrada en la clase base.

Para ello, selecciona la opción de menú Insert > Property utilizando los siguientes valores en el Panel Inspector asociado:

  • Name: LastAddedRowIndex
  • Type: Integer
  • Scope: Public

Con la nueva propiedad seleccionada en la clase MyComboBox, accede al menú contextual y elige la opción Convert to Computed Property. Dicha acción añadirá los métodos Get y Set bajo la propiedad, además de añadir una nueva propiedad mLastAddedRowIndex cuyo ámbito será Private (Privado).

Añadamos la segunda Propiedad utilizando estos valores:

  • Name: SelectedRowIndex
  • Type: Integer
  • Scope: Public

Nuevamente, con la propiedad recién añadida seleccionada en la clase MyComboBox en el Navegador de Proyecto, selecciona la opción Convert to Computed Property en el menú contextual. Selecciona a continuación el método Set bajo SelectedRowIndex y teclea el siguiente código en el Editor de Código asociado:

Var r() As String = Me.Rows
If value > r.LastRowIndex Then Raise New OutOfBoundsException
If value > 0 Then
  Me.Text = r(value)
Else
  value = -1
  Me.Text = ""
End If
mSelectedRowIndex = value
RaiseEvent Change

Este es el código que se ejecutará cada vez que se defina un nuevo valor para la propiedad, de modo que hemos de asegurarnos de que esté en el rango permitido, lanzando una excepción OutBoundsException en el caso de que no sea así.

Al mismo tiempo, si no se trata de un valor positivo esto significará que no queremos seleccionar ninguna entrada del menú, de modo que definiremos la propiedad Text del ComboBox como una cadena vacía y la propiedad privada mSelectedRowIndex con el valor -1.

7 Poniéndolo en la Práctica

Ya tenemos nuestra subclase ComboBox lista para funcionar. Selecciona el ítem Window1 en el Navegador de Proyecto para acceder al Editor de Diseño. A continuación, arrastra el ítem MyComboBox desde el Navegador de Proyecto y suéltalo sobre Window1 en el Editor de Diseño (probablemente quieras utilizar las guías de alineamiento para que el control quede alineado sobre los márgenes de la ventana).

Con el item MyCombobox1 aún seleccionado en el Editor de Diseño, accede a su Panel Inspector de modo que podamos asignar algunos valores iniciales al menú del ComboBox desde la sección Appearance > Initial Value. En nuestro ejemplo podemos introducir las líneas “One”, “Two” y “Three”.

Por supuesto, también puedes activar la opción de autocompletado de texto desde Behavior > Allow Auto Complete.

Ejecuta la app y observa como se añade lo que teclees en el ComboBox como nuevas entradas del menú, y que estas siempre estarán ordenadas alfabéticamente.

Adicionalmente, también puedes añadir más controles de interfaz de usuario a Window1 para probar los otros métodos y propiedades, o simplemente puedes descargar el proyecto de ejemplo y ejecutarlo desde el IDE de Xojo.

Deja un comentario

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