Si estás empezando a programar con Xojo esntonces a buen seguro que tendrás toda una serie de conceptos revoloteando por la cabeza, y con un lenguaje de programación orientado a objetos (como es el caso de Xojo) uno de los principales conceptos es el de las clases: plantillas que definen los objetos que puedes crear a partir de ellas y que suponen, de hecho, los ladrillos mediante los cuales se compone una aplicación.
En el Framework de Xojo ya dispones de una gran cantidad de clases listas para usar mediante la instanciación (creación) de nuevos objetos. Por ejemplo, sólo tienes que darte una vuelta por la documentación correspondiente al nuevo framework para verlo, incluyendo no sólo explicaciones sobre qué hacen sino también ejemplos de cómo puedes utilizar dichas clases.
Como parte de las clases incluidas de serie también cuentas con los controles que, desde la librería del IDE, sólo has de arrastrar y soltar sobre el Editor de diseño para componer las interfaces gráficas de tus aplicaciones.
Descarga el proyecto Xojo con las clases e Interfaz de clase creadas en este tutorial desde este enlace
Si lo deseas, puedes descargar este tutorial en formato .ePub desde este enlace.
Subclases, buscando la especialización
Ahora bien, se producen ocasiones en las que te gustaría que cualquiera de las clases incluidas de serie te ofreciesen un comportamiento personalizado, por así decirlo. Por ejemplo, si tomamos como caso el control correspondiente a la introducción de texto para aplicaciones de escritorio (TextField), sería maravilloso que nos ofreciese sugerencias de autocompletado a medida que vamos escribiendo un texto.
En la programación orientada a objetos ganamos este tipo de especialización sobre una clase mediante el procedimiento de crear subclases, ya sea incluida de serie en el framework del lenguaje o bien la creada desde cero por nosotros. Este es el mecanismo conocido como herencia de clase y que será una de las partes fundamentales de este tutorial para, precisamente, crear nuestra propia subclase de un TextField que nos ofrezca capacidad de autocompletado de textos.
Interfaces de clase
Las Interfaces de clase son en Xojo otro de los mecanismos disponibles en la programación orientada a objetos, y que en otros lenguajes de programación puedes encontrar bajo la referencia de Protocolos. Su potencia radica en que podemos reunir, y por tanto tratar, objetos procedentes de diferentes clases como si fuesen del mismo tipo.
Desde el punto de vista del código y de la arquitectura de nuestras aplicaciones esto supone una tremenda flexibilidad, dado que añade una capa de abstracción que nos permitirá, por ejemplo, abordar futuras modificaciones o incluso cambios radicales sobre las piezas que puedan estar involucradas en nuestro programa sin que por ello debamos de retocar o cambiar ingentes cantidades de código en la aplicación.
Cuando creamos una Interfaz de clase en Xojo sólo hemos de incluir el conjunto de métodos que posteriormente han de implementar todas aquellas clases que sean conformes a dicha interfaz (es decir, que quieran presentarse como el tipo definido por la interfaz de clase).
Aquí es donde comenzamos a ver la grandeza y la flexibilidad procurada por las interfaces de clase: diferentes clases implementarán idénticos métodos de forma diferente en función de cuál sea su estructura y función. Sin embargo, aquellos objetos que utilicen llamadas a un mismo método en los objetos procedentes de diferentes clases… se abstrae de todo ello; sólo ha de conocer cuál es la signatura del método, qué parámetros ha de pasar (si debe hacerlo) y cuál es el tipo o valor devuelto (si procede), y punto. Cada una de las diferentes implementaciones será responsable de hacer lo que deba hacer para procurar la funcionalidad esperada.
En nuestro tutorial usaremos precisamente una interfaz de clase para definir así el tipo abstracto correspondiente a nuestro modelo: el diccionario encargado de contener las palabras a utilizar como sugerencia para el autocompletado de texto; y que en este ejemplo concreto se corresponde con una clase creada desde cero.
Así, si en el futuro queremos cambiar el modelo o fuente que haga las funciones de diccionario, podremos hacerlo sin problema sin que se vea afectado el resto del programa. ¡Incluso podemos utilizar varios modelos o fuentes y cambiar entre ellos sobre la marcha! Después de todo, no importa cual sea la clase base utilizada por cada uno de ellos, mientras que también declaren su conformidad con la Interfaz de clase esperada, e implementen los tipos definidos por dicha interfaz de clase, todo estará correcto.
Por supuesto, estos y otros conceptos, así como paradigmas de las programación orientada a objetos, forman parte de lo que trato desde cero y de forma progresiva en el libro “Programación multiplataforma Xojo”.
TextField con autocompletado
Teniendo en cuenta todo lo anterior, el proyecto Xojo de nuestro tutorial estará compuesto por tres elementos principales: una subclase creada a partir de un TextField, la definición de una interfaz de clase, y la creación de una clase que hará las funciones de modelo para el diccionario con las palabras del autocompletado y que añadirá la conformidad con la interfaz de clase definida, implementando por tanto los métodos definidos por la misma.
Empezaremos de hecho por la definición de la Interfaz de clase.
Crear la Interfaz de clase
- Abre el IDE de Xojo y crea un nuevo proyecto de tipo Escritorio con el nombre que desees.
- Crea una nueva interfaz de clase pulsando sobre el icono Insert > Class Interface.
- En la ventana Inspector introduce TextAutocompleteSource en el campo Name. Este será el nombre del nuevo Tipo de dato (toda nueva clase define un nuevo tipo de dato, al igual que ocurre con las interfaces de clase).
- A continuación, selecciona el icono correspondiente a TextAutocompleteSource en el Navegador del Proyecto. Accede al menú contextual y selecciona la opción Add to “TextAutocompleteSource” > Method. Dicha acción nos llevará nuevamente al Inspector donde podremos declarar la signatura del nuevo método. ¡Ojo!, advierte que en el caso de las interfaces de clase, tal y como se ha explicado previamente, sólo se declaran los métodos; la implementación corre a cargo de las clases que posteriormente se adhieran —es decir, utilicen— la interfaz de clase propiamente dicha.
- El primero de los métodos declarados tendrá como finalidad añadir una nueva palabra a las incluidas en el diccionario. Utiliza “addWordToDictionary” como nombre del método, e introduce “s as String” en el campo de parámetros.
- Vuelve a añadir un nuevo método (tal y como se ha indicado en el cuarto punto) y, en este caso, utilizaremos “returnMatch” como nombre de parámetro, “s as String” en el campo de parámetros, y “String” en el campo correspondiente a Returned Type (tipo o valor devuelto por el método o función). Este, de hecho, será el método que se invocará para hallar alguna sugerencia en función de la cadena de texto recibida.
¡Y eso es todo lo que requiere nuestra interfaz de clase! Simple, ¿verdad?
Crear el modelo de diccionario
Ahora es el turno de crear una clase que haga las funciones de un diccionario. Dicha clase implementará la interfaz de clase definida en el paso anterior y, por tanto, implementará los métodos definidos por dicha interfaz.
- Para crear nuestra nueva clase, selecciona Insert > Class en la barra de herramientas o desde el menú Insert disponible en la barra de menús de Xojo.
- A continuación, introduce StringStack como nombre de nuestra nueva clase. Ya sabes, este será el nombre del nuevo tipo de dato que estamos definiendo mediante la clase.
- Pulsa en esta ocasión el botón Choose… asociado a la etiqueta “Interfaces” en el Inspector. Dicha acción abrirá una hoja con el listado de todas las interfaces de clase disponibles, ¡incluida la que acabamos de crear en el apartado anterior! Navega por el listado hasta que la encuentres y marca la casilla de verificación asociada. Pulsa Ok cuando finalices.
- Comprobarás que Xojo se ha encargado de añadir automáticamente en nuestra recién creada clase los métodos declarados por dicha interfaz de clase. Sólo tenemos que encargarnos de la implementación. Lo haremos en un paso posterior.
- Ahora añadiremos una nueva Propiedad a nuestra clase. Ya sabes, con StringBack seleccionado, dirígete a Insert > Property (o el atajo de teclado correspondiente en función de que estés utilizando un equipo con OS X, Windows o Linux). Introduce Collector(-1) en el campo Name del Inspector. “Collector” es el nombre que le damos a nuestra nueva propiedad y con “(-1)” estamos indicando que se trata de un Array inicializado a un estado sin contenidos; es decir, vacío. En el campo Type indica “String” y en Scope selecciona la opción “Private”. En definitiva, nuestra propiedad Collector es un array privado cuyos contenidos serán cadenas de texto.
- ¡Ya podemos implementar los métodos añadidos por la Interfaz de Clase! Selecciona addWordToDictionary. En el Editor de Código resultante introduce la siguiente línea:
if s <> "" then Collector.Append s
Es una forma muy simplificada dado que no se hace ningún tipo de comprobación sobre si el texto recibido ya estaba incluido en el array, por ejemplo; sin embargo nos sirve perfectamente para lo que queremos ver en este tutorial: un TextField con autocompletado.
Elige ahora el método returnMatch e introduce el siguiente código en el Editor de Código resultante:dim n as integer = len(s) for each element as String in Collector dim t as string = element.mid(1,n) if t = s then return element next Return ""
Básicamente recorremos cada uno de los elementos de nuestra propiedad Collector (es un array, recuerda) y comparamos si la parte inicial de cada palabra recorrida coincide con el texto recibido. Si es así, se devuelve como sugerencia. Si terminamos de recorrer todos los elementos sin encontrar ninguna posible correspondencia, devolvemos una cadena vacía.
Añadir un Constructor de clase
Ahora añadiremos un método muy especial: el constructor. En el caso de que una clase defina dicho método, el código contenido en el mismo será lo primero que se ejecute cada vez que se cree una nueva instancia (objeto) de dicha clase. Generalmente utilizamos los constructores para definir el estado inicial del objeto. En nuestro caso crearemos un método Constructor para poblar el estado inicial del diccionario (la propiedad Collector, de tipo Array) durante la creación de una instancia de objeto.
- Añade un nuevo método a la clase tal y como harías en cualquier otro caso (Insert > Method). En esta ocasión, sin embargo, accede al menú desplegable “Method Name” en el Inspector y elige la opción Constructor.
- Tal y como haríamos con un método normal, podemos refinar su signatura indicando, por ejemplo, si recibe parámetros y de qué tipo son en cada caso. ¡De hecho nuestra clase puede tener tantos constructores como deseemos! Sólo han de diferenciarse en la cantidad y tipo de los parámetros recibidos. (Estas cuestiones, entre otras, se explican en mayor detalle así como su utilidad práctica en mi libro “Programación multiplataforma Xojo”). En este caso es suficiente con indicar “Source as String” en el campo Parameters.
Crear una subclase
Con todas las cuestiones “auxiliares” a punto, por fin podemos centrarnos en el meollo de la cuestión: crear una subclase que nos proporcione una funcionalidad especializada al tiempo que sea reutilizable y flexible para utilizarla en cualquier otro proyecto (mínimo acoplamiento).
- Para crear una subclase utilizamos el mismo procedimiento que cuando añadimos una clase base al proyecto: Insert > Class. En el Inspector, utiliza “TextFieldAutocomplete” como nombre de clase (definición de nuevo tipo) y, aquí es donde está la magia, indica “TextField” en el campo Super. Si lo deseas también puedes pulsar sobre el icono con forma de lápiz para examinar todas las clases que puedes utilizar de entre las disponibles en el Framework de Xojo.
Importante: Cuando creamos una clase como subclase de otra ya existente, la nueva clase recibe automáticamente el uso de propiedades, métodos y eventos definidos en la clase superior. Esto es lo que se conoce como herencia de clases. Por tanto, nuestra recién creada clase TextFieldAutocomplete se podría comportar ahora mismo, sin más, como si fuese un TextField.
- Sin embargo deseamos que tenga una funcionalidad especializada: autocompletado de texto a medida que vayamos escribiendo. Para ello comenzaremos añadiendo una nueva propiead. Con TextFieldAutocomplete seleccionado en el Navegador de Proyecto, utiliza Insert > Property. En la ventana Inspector resultante, introduce “Autocomplete” en el campo Name, “Boolean” en el campo Type, “true” para el campo Default y la opción “Public” asociada al menú Scope. Esto nos permitirá que, si lo deseamos, los objetos creados a partir de nuestra clase tengan la capacidad de autocompletado (propiedad Autocomplete a True) o bien se comporte como un TextField estándar (valor Autocomplete a False).
- Añade una nueva propiedad y utiliza en este caso “autocompleteSource” como texto para el campo Name en el Inspector. Define su valor Scope a Public y el tipo como TextAutocompleteSource.¡Exacto, se trata del nombre de la Interfaz de clase! Esto permite desacoplar el máximo posible nuestra clase de cualquier tipo de objeto que vaya a actuar como diccionario o fuente de datos, a partir del cual obtener las sugerencias. Dicho de otro modo: cualquier clase que quiera convertirse en una fuente de datos para nuestra clase TextFieldAutocomplete solo ha de agregar la conformidad con la Interfaz de Clase TextAutocompleteSource.
- Incorporemos una nueva propiedad. En este caso utilizaremos “TabKeyAutocompletes” como nombre, Boolean como Tipo, un ámbito (Scope) público y su valor por defecto definido a True. Utilizaremos esta propiedad para definir el comportamiento de la tecla Tabulador para que se acepte la sugerencia que aparezca como autocompletado del texto introducido, o bien para que tenga la función habitual de un TextField y que consiste en cambiar el foco activo al siguiente control en la interfaz de usuario.
Propiedades Compartidas
Todas las propiedades que hemos creado hasta ahora son lo que se conocen como Propiedades de Instancia. Esto es, cada uno de los objetos creados a partir de las correspondientes clases guardarán sus propios valores para cada una de las propiedades. Ahora bien, existen casos o situaciones en las que desearíamos que una propiedad tuviese un mismo valor para todas las instancias creadas a partir de la clase… y esto es lo que se conoce como Propiedades de clase o, en el caso de Xojo, Propiedades Compartidas (Shared Properties).
Definiremos dos de estas propiedades en nuestra clase TextFieldAutocomplete para verlas en acción. Una de ellas permitirá utilizar un diccionario de sugerencias global para todas las instancias de la clase. De existir se utilizará en vez del diccionario que podemos asignar de forma individual a cada una de las instancias a través de la propiedad “autocompleteSource”, creada con anterioridad. La segunda propiedad será un Array de tipo String que contendrá aquellos caracteres que deseemos excluir de entre los aceptados por el campo de texto.
- Crea la primera de las propiedades utilizando “globalAutocompleteSource” como nombre, un ámbito Público y “TextAutocompleteSource” como Tipo de dato.
- Crea la segunda de las propiedades utilizando “specialCharacters(-1)” como nombre, un ámbito Protected y “String” como tipo de dato.
Añadir Métodos a TextFieldAutocomplete
El siguiente paso no es otro si no definir un par de métodos a nuestra clase, y que serán los que pueda utilizar cualquier consumidor de la clase para indicar cuáles son los caracteres no aceptados. Nuevamente, esto mismo se podría realizar de otras formas (probablemente más elegantes) si bien en este caso nos sirve de forma suficiente.
- Añade un nuevo método a la clase utilizando “addSpecialCharacters” como nombre, “s() as String” en el campo de Parameters y ámbito público. Introduce la siguiente línea en el Editor de Código resultante:
specialCharacters = s
- Añade el segundo de los métods, utilizando “specialCharacter” como nombre, “key as String” como parámetro, “Boolean” como tipo devuelto y “Protected” como ámbito. Introduce las siguientes líneas en el Editor de Código resultante:
if specialCharacters.Ubound > 0 then for each item as string in specialCharacters if item = key then Return true next end return False
Básicamente, recorremos la propiedad encarga de contener los caracteres que no deseamos aceptar como entrada de nuestro TextFieldAutocomplete. Si el caracter pasado está entre los no aceptados, devolvemos “True” (cierto) como resultado. Si se recorren todos los valores sin hallar una correspondencia devolveremos “False” (falso), de modo que se tratará de un caracter válido.
Incorporar Manejadores de Eventos
Probablemente lo habrás leído ya en otros de mis artículos: Xojo no sólo es un lenguaje de programación orientado a objetos, sino también orientado a eventos. ¿Qué significa esto? Se suele aplicar generalmente (si bien no es exclusivo) a los controles gráficos o instancias de clase que proporcionan controles de interfaz gráfica con los que puede interactuar el usuario final de nuestras aplicaciones. Así podemos añadir eventos cuyo código responda a la acción recibida por parte del usuario: clic sobre el control, pulsación de una tecla, pérdida de foco en el control, etc.
Precisamente será esta la capacidad que utilicemos para añadir el código respondable de procurar la especialización del comportamiento en nuestra clase TextFieldAutocomplete, derivada de un TextField estándar. Recuerda que, al indicar dicha clase como la Super de nuestra clase, estaremos heredando todas las propiedades, métodos y Eventos disponibles para un TextField estándar. Por tanto, añadamos uno de sus eventos soportados: KeyDown. En concreto este es el evento cuyo código se ejecutará cada vez que el control tenga el foco y el usuario de nuestra aplicación pulse una tecla.
- Para añadir el Manejador de Evento (Event Handler) a nuestra clase, asegúrate de que esté seleccionado el icono de la clase TextFieldAutocomplete en el Navegador de Proyecto y utiliza a continuación Insert > Event Handler.En la hoja resultante, ubica la entrada correspondiente al evento KeyDown. Al seleccionar cada una de las entradas del listado observarás que se muestra una descripción de la función de dicho evento. Confirma la selección y comprobarás como pasa a añadirse a la clase en el Navegador de Proyecto bajo una nueva rama etiquetada “Event Handlers”.
- Selecciona el evento KeyDown recién añadido e introduce el siguiente código. ¡aquí reside toda la lógica de nuestra subclase!:
if autocomplete = false then return raiseevent KeyDown(Key) if specialCharacter(key) = true then Return False if asc(key) = 9 and TabKeyAutocompletes = true and me.SelLength > 0 then me.SelStart = me.Text.Len me.SelLength = 0 Return true end dim tempText as String = me.Text.Left(me.SelStart) + Key dim matchingText as String = "" if autocompleteSource <> nil then matchingText = autocompleteSource.returnMatch(tempText) elseif globalAutocompleteSource <> nil then matchingText = globalAutocompleteSource.returnMatch(tempText) end if matchingText <> "" then me.Text = matchingText dim startOffset as integer = len(tempText) me.SelStart = startOffset me.SelLength = len(matchingText) - startOffset Return true end return false
Como puedes comprobar el código es bastante sencillo y fácil de seguir. Probablemente la primera linea sea la que más te llame la atención. ¿Qué es eso de “RaiseEvent”? Es la función del lenguaje Xojo que permite invocar otros eventos presentes en la misma clase. ¿Estamos llamando entonces al mismo evento en el que se encuentra ese mismo código? No.
Nuestra clase ha consumido el manejador de evento KeyDown, de modo que este no estará disponible en el caso de que el consumidor de nuestra clase (por ejemplo, otro desarrollador) quiera añadir algún otro código adicional en las instancias creadas a partir de nuestra clase.
Para solucionarlo encontramos en Xojo la capacidad de definir nuevos eventos para una clase, y esto es lo que haremoa a continuación.
Definir eventos de clase
Asegúrate de que esté seleccionado en el Navegador de Proyecto el icono correspondiente a la clase TextFieldAutocomplete y, a continuación, utiliza la opción Insert > Event Definition. En la ventana Inspector resultante, introduce “KeyDown” como nombre, “Key as String” en el apartado Parameters, y “Boolean” como tipo devuelto. Exacto, se trata de la misma signatura del evento que está consumiendo la clase y será, de hecho, el evento cuyo código se ejecutará con la línea “raiseEvent KeyDown(Key)”.
Diseñar la interfaz de usuario
Ya tenemos todo listo para probar nuestra recién creada clase de autocompletado de texto con Xojo. El primer paso consiste en seleccionar el elemento Window1 en el Navegador de Proyecto y utilizar los diferentes elementos gráficos desde la Librería (Library) para que la interfaz muestre un aspecto similar a este:
En concreto has de utilizar dos controles CheckBox, un PushButton y un TextFieldAutocomplete, arrastrándolo desde el Navegador de Proyecto sobre el Editor de Diseño. También puedes arrastrar sobre el Editor de Diseño correspondiente a “Window1” un TextField normal, seleccionarlo a continuación y cambiar su Super a TextFieldAutocomplete en la ventana Inspector, tal y como se muestra en la siguiente imagen.
Haz doble clic sobre el “CheckBox1” que habrás etiquetado previamente como “Autocomplete”. Accederás a la hoja que ya conocemos y que nos permite añadir un nuevo Manejador de Evento sobre el control. Selecciona Action en esta ocasión y pulsa sobre Ok. Introduce la siguiente línea en el Editor de Código resultante:
TextField1.Autocomplete = me.Value
Repite la operación con el control “CheckBox2”. En este caso introduce la siguiente línea de código:
TextField1.TabKeyAutocompletes = me.Value
Por último, añade el mismo evento para el control “PushButton1” con la siguiente línea de código:
stack.addWordToDictionary TextField1.Text
Propiedades y Eventos para poner la demo en marcha
¿De dónde sale “stack”? Efectivamente, aun no existe y es lo que crearemos a continuación, junto al código responsable de poner nuestra demostración en marcha. Con “Window1” aun seleccionado en el Navegador de Proyecto, añade una nueva propiedad cuyo nombre sea stack, su ámbito Público y su tipo “StringStack”. Efectivamente, se trata de la clase base definada con anterioridad y conforme al protocolo TextAutocompleteSource.
A continuación añade el Manejador de Evento Open a “Window1” e introduce el siguiente código:
stack = new StringStack(array("uno", "unitario", "universidad", "universitario", "dos", "das", "des", _ "desactivar", "desactivado", "tres", "tresillo", "tremendo", "cuatro", "cuatrero", "cuadrilátero")) TextField1.addSpecialCharacters(array(chr(8))) TextField1.autocompleteSource = stack
Como puedes observar, a la hora de crear una nueva instancia a partir de la clase StringStack utilizamos el Constructor para pasar un array de strings. De este modo contamos con un diccionario sencillo y con contenidos que nos permite comprobar el funcionamiento de nuestro TextField con autocompletado, dado que lo asignamos posteriormente con la linea “TextField1.autocompleteSource = stack”.
Conclusiones
¿Recuerdas lo comentado sobre la flexibilidad que proporcionan las Interfaces de clase? Si en el futuro quiero cambiar el diccionario por otra solución más potente, sólo tendría que cambiar la línea de asignación “stack = nuevo_objeto_de_clase”, siempre y cuando la instancia se cree a partir de otra clase conforme a la interfaz TextAutocompleteSource, sin necesidad de tocar ninguna otra parte de código o hacer modificaciones en las diferentes clases.
Confío que hayas encontrado interesante este tutorial. Si tienes dudas o quieres hacer algún comentario… ¡aquí me tienes!
[…] estas situaciones lo primero que tenemos que hacer es crear nuestra propia subclase a partir de la clase MenuItem, dado que este es el único modo que tendremos para ejecutar código […]
[…] *Read this post in Spanish […]
[…] nos muestran todas las propiedades disponibles, incluyendo no sólo las heredadas a partir de la jerarquía de clases, sino también aquellas definidas por nosotros. Será precisamente en dicha ventana donde podremos […]
Hola, acabo de encontrar este tutorial, felicidades, muy completo, hice algo parecido hace unos años pero tenía el problema de la fuente de datos, no sabía q podía usar una interfase como propiedad, aunque como es un type, dahaaa. gracias por publicarlo.
Javier muchas gracias por tus aportes, he logrado depegar en Xojo gracias a tus tutoriales.
Any news to this wonderful topic about using Autocomplete for each new word added to the textfield? Also if you edit a word after and within the complete String?
Hi Max,
Thank you for your comments! Stay tuned, work in progress 😉