lunes, marzo 27, 2017

VFP: Algunas formas de usar los arrays

Los arrays son una colección de elementos muy útil y versátil en cualquier lenguaje, que a diferencia de los cursores o tablas permiten también almacenar objetos o referencias a los mismos.

En Visual FoxPro el uso de los arrays conviene mantererlo controlado en cuanto a tamaño, ya que por encima de algunos cientos de elementos, las búsquedas sobre un array comienzan a ponerse lentas. Por eso, para grandes tamaños siempre conviene usar cursores o tablas.



Definición y dimensionamiento


Hay dos tipos de arrays: en forma de variable y en forma de propiedad, ambos usan la misma sintaxis, pero solo se diferencian en la forma de declararse o en dónde se declaran.

Por ejemplo, como variable un array se puede definir de estas formas:

LOCAL laLista(1), laTabla(1,1)  && Se definen y dimensionan en un paso

Y luego se pueden redimensionar así:

DIMENSION laLista(10), laTabla(4,2)

Para una clase, usando el cuadro de diálogo de agregar propiedad (ej.en un form: menú form/New property), lo mismo de antes se define como laLista[1] y luego otra propiedad nueva como laTabla[4,2]

Luego, para redimensionar las propiedades se hace lo mismo, pero indicando el nombre del objeto:

DIMENSION thisform.laLista(10), thisform.laTabla(4,2)

Hay una sintaxis adicional cuando se requiere agregar un array que no sea posible poner en diseño, que es usando AddProperty:

AddProperty( thisform, "laLista[1]" )



Asignación de valores


Para asignar valores, se hace como cualquier variable o propiedad:

laLista(1) = 100
laLista(2) = CREATEOBJECT("custom")
laLista(3) = "Texto"

thisform.laLista(1) = 100
...



Redimensionamiento


Para redimensionar un array se usa el mismo comando que para definirlo, DIMENSION. Solo hay que tener en cuenta algunos casos:

- Para agrandar un array bidimensional con datos, solo se pueden agregar filas, no columnas, ya que si se agregan columnas (técnicamente posible) se distribuirán los datos existentes para usar las nuevas columnas, lo que hará que ya no estén en sus ubicaciones originales. En este caso conviene copiar de un array a otro nuevo con las columnas que se requieran.

- Para achicar un array, conviene hacerlo desde abajo hacia arriba, o sea, desde el índice mayor al menor

Para ejemplificar la eliminación de elementos, algunos habrán intentado hacerlo así:

lnFilas =  ALEN(laLista)
FOR I = 1 TO lnFilas
   ADEL(laLista, I) 
   lnFilas = lnFilas - 1
   DIMENSION laLista(lnFilas)
ENDFOR

El código anterior tiene el problema de que al llegar a la mitad del array, ocurrirá un error porque se estará intentando borrar (ADEL) un elemento cuyo índice supera la cantidad de elementos, y es por eso que esto debe hacerse en orden inverso:

lnFilas =  ALEN(laLista)
FOR I = lnFilas TO 1 STEP -1
   ADEL(laLista, I) 
   lnFilas = lnFilas - 1
   DIMENSION laLista(lnFilas)
ENDFOR

Si bien el algoritmo anterior no tiene sentido para cuando se quieren borrar todos los elementos, porque es mucho más rápido hacer un DIMENSION laLista(1) y luego laLista = "", es la forma correcta de borrar elementos intermedios.



Caso especial de uso


Normalmente los arrays se usan para listas de valores conocidos, como nombres de ciudades, paises y selecciones en general, pero hay un caso especial que requiere una forma distinta de manipular el array, que es cuando no se sabe con antelación cuántos elementos tendrá el array, pero es necesario tenerlo predefinido con un elemento.

El problema se presenta cuando se quiere agregar un elemento nuevo, ya que normalmente se usa ALEN() para saber cuantos tiene y agregar uno nuevo, pero ALEN() devolverá siempre como mínimo 1 y es posible que ese elemento tenga valor, o no, y no siempre es posible usar EMPTY() para controlar si hay un dato, porque un 0 o una cadena vacía podría ser el dato...

¿Cómo controlar esa situación? Para eso se puede usar un contador de elementos:

lnFilasLista = 0
DIMENSION laLista(1)

Y luego, cuando se va a agregar un nuevo elemento, simplemente se incrementa y redimensiona a la par:

lnFilasLista  = lnFilasLista + 1
DIMENSION laLista(lnFilasLista)
laLista(lnFilasLista) = "nuevo valor"


La siguiente es una técnica que define una API para interactuar con el array anterior, donde se encapsulan las operaciones de dimensionar y agregar valores. En este caso lo defino como una clase instanciable, pero los métodos bien podrían ponerse directamente en un form u otra clase.

Este es un ejemplo de uso:

loArrayMgr = createobject("cl_ArrayMgr") 
loArrayMgr.Add("un valor")
loArrayMgr.Add("otro valor")
loArrayMgr.Add("un valor más")
? loArrayMgr.Length
3
loArrayMgr.Delete(2)
? loArrayMgr.Datos(2)
"Un valor más"
? loArrayMgr.Length
2

Y la clase podría definirse así en un PRG o visualmente en la clase que se requiera:

DEFINE CLASS cl_ArrayMgr AS Custom
   DIMENSIO aDatos[1]
   nDatos = 0 

   FUNCTION Length
      RETURN THIS.nDatos
   ENDFUNC

   PROCEDURE Add(tuValor)
      THIS.nDatos = THIS.nDatos + 1
      DIMENSION THIS.aDatos(THIS.nDatos)
      THIS.aDatos(THIS.nDatos) = tuValor
   ENDPROC

   PROCEDURE Delete(tnElemento)
      IF THIS.nDatos > 1 AND tnElemento <= THIS.nDatos
         ADEL(THIS.aDatos, tnElemento)
         THIS.nDatos = THIS.nDatos - 1
         DIMENSION THIS.aDatos(THIS.nDatos)
      ENDIF
   ENDPROC
ENDDEFINE


Sobre este ejemplo se pueden hacer variaciones y mejoras, pero creo que se entiende la idea de cómo manipular un array en las condiciones comentadas.


Hasta la próxima! :D

martes, febrero 28, 2017

Los peligros de SET COLLATE

Se me ha presentado una situación que no veo descripta claramente en la documentación de Visual FoxPro y que hasta ahora creía que se aplicaba solamente a los índices de las tablas.

Como es sabido, el comando SET COLLATE (que por defecto es "MACHINE") permite indicar el algoritmo de comparación a usar para los índices y funciones relacionadas con ellos (búsquedas, ordenamientos, etc), de forma que si se definen unos datos así:

CREATE CURSOR test (palabra C(30))
INSERT INTO test VALUES ("Comparación")
INSERT INTO test VALUES ("Comparación 2")
INSERT INTO test VALUES ("Comparación 3")


Con SET EXACT OFF y SET COLLATE TO "MACHINE" las comparaciones son exactas a nivel de mayúsculas/minúsculas (capitalización), pero teniendo en cuenta cómo comienzan las cadenas:

LIST all for palabra = "Comparación"

Record#  PALABRA                      
      1  Comparación                  
      2  Comparación 2                
      3  Comparación 3                


Lo siguiente no retorna resultados:

LIST all for palabra = "COMPARACIÓN"

Record#  PALABRA                      

Cambiando a SET COLLATE TO "GENERAL", las comparaciones cambian y ya no distinguen entre minúsculas/mayúsculas:

LIST all for palabra = "Comparación"

Record#  PALABRA                      
      1  Comparación                  
      2  Comparación 2                
      3  Comparación 3                


LIST all for palabra = "COMPARACIÓN"

Record#  PALABRA                      
      1  Comparación                  
      2  Comparación 2                
      3  Comparación 3                



Hasta aquí, todo esto es de conocimiento público.

Algo importante a tener en cuenta es que el COLLATE también afecta a las comparaciones lógicas que no tengan que ver con tablas ni datos de las mismas, lo que abarca cualquier tipo de comparación ASCII, o sea, de caracteres y cadenas.

Por ejemplo, cuando comparamos caracteres, internamente lo que se compara son los valores ASCII, de modo que A < Z porque el ASCII de A es 65 y el de Z es 90 y la comparación que realmente se hace es 65 < 90:

? "A" < "Z" && En ASCII: 65 < 90
.T.

? "a" < "z" && En ASCII: 97 < 122.T.

? BETWEEN("F", "a", "Z") && En ASCII: 70 está entre 97 y 90
.F.

? "a" < "Z" && En ASCII: 97 < 90
.F.

Lo anterior es cierto cuando el COLLATE es "MACHINE", pero cuando es "GENERAL" las cosas cambian:

? "A" < "Z" && En ASCII: 65 < 90
.T.


? "a" < "z" && En ASCII: 97 < 122
.T.

? BETWEEN("F", "a", "Z") && En ASCII: 70 está entre 97 y 90
.T.

? "a" < "Z" && En ASCII: 97 < 90
.T.

Estos dos últimos casos son los que pueden resultar más confusos, ya que va contra toda lógica... a menos que se sepa cuál es la lógica a aplicar, y es que cuando se evalúan las opciones con COLLATE "GENERAL", lo que realmente se hace es comparar todo en la misma capitalización (que supongo que es en mayúsculas, aunque no puedo confirmarlo).

Bajo esta lógica, si normalizamos primero la capitalización de la última comparación, realmente sería lo mismo que hacerla así:

? "A" < "Z" && En ASCII: 65 < 90
.T.


? BETWEEN("F", "A", "Z") && En ASCII: 70 está entre 65 y 90
.T.

Y lo mismo se aplica a las demás funciones de comparación de cadenas.

Este último caso es el que se me presentó recientemente, y realmente puede resultar algo perturbador ver algo como esto sin lógica aparente:


Lo que se ve en la imagen es el depurador abierto con una aplicación antigua en ejecución (parte superior) y la ventana de comandos en la parte inferior. Como se puede observar, la aplicación hace una comparación que en definitiva hace esto:

IF BETWEEN(caracter, "a", "Z")
   lnCuantos = lnCuantos + 1
ELSE
   EXIT
ENDIF

A primera vista, esa comparación no tiene sentido porque jamás un valor puede estar "entre 97 y 90" (comparando en ASCII) y la ventana de comandos lo confirma, pero resulta que la aplicación tiene sesión de datos privada y COLLATE "GENERAL", por lo que realmente está comparando todo con la misma capitalización de mayúsculas/minúsculas, y por eso funciona.



Resumen:


Este es uno de esos casos donde se vuelve a comprobar la importancia de usar buenas prácticas de programación. En este caso algunas formas correctas de escribir esta condición sería:

IF BETWEEN(carac, "A", "z") && carac entre 65 y 122

o bien:

IF BETWEEN(UPPER(carac), "A", "Z") && carac entre 65 y 90


Y de esa forma independizarse del valor del SET COLLATE.

Espero que esto les evite algunos problemas,

Hasta la próxima! :D

miércoles, agosto 31, 2016

Referencia: Temas y Artículos Agrupados - Para no perderse en el blog

Este post pretende poner un poco de orden en el caos de artículos y temas tratados que pasado un tiempo se hacen algo difíciles de encontrar, a modo de resumen y temas relacionados, para poder, en los casos que corresponda, tener los artículos ordenados desde el inicio hacia los temas más avanzados.

Inicialmente lo aplicaré a los artículos de Plastic, ya luego iré agregando otros donde ese orden sea beneficioso para el seguimiento del tema tratado.

Recordar que al final de los artículos, suelo poner links relacionados al tema.


Visual FoxPro 9

Cómo trabaja FoxPro internamente (traducción)

Control de versiones: ¿para qué sirve? ¿por qué es necesario?

Guía de buenas prácticas de programación y recomendaciones

La importancia de un buen nombre para los procedimientos y las funciones

La Interfaz, las reglas de negocio y los datos - Cómo separarlos y por qué

Los peligros de SET COLLATE

Selección de artículos y notas sobre problemas y troubleshoting para tener a mano

Seleccionar palabras de un textbox con solo un click

Técnicas de optimización en VFP: Tablas, archivos de texto, accesos al disco y buffering

Técnicas de programación en VFP: Procesos desatendidos y control de progreso visual

Unit Testing: Qué es y cómo se usa en VFP 9

Whitepaper: Cacheo de lectura y bloqueo oportunista en redes Microsoft Windows



Varios

Cómo configurar las notificaciones de Google de forma óptima

(Linux) Ubuntu 16.04: Agregando soporte para nuevo hardware (kernel)



PlasticSCM y FoxBin2Prg

Control de código fuente: Terminología común que hay que conocer

FoxBin2Prg: Control de código fuente con PlasticSCM

FoxBin2Prg: Detalle de vistas, datos de uso, configuraciones y más

FoxBin2Prg: Guía rápida de uso y configuración

Instalación de PlasticSCM paso a paso

PlasticSCM: Cómo configurar el usuario Administrador del Servidor

PlasticSCM: Cómo crear una rama para comenzar a trabajar

FoxPro 9: Creando un componente y agregándolo al control de código PlasticSCM

FoxPro 9: Modificando un componente que ya está bajo control de código

PlasticSCM: Hice un checkin erróneo de un archivo nuevo, ¿cómo lo arreglo?

PlasticSCM: ¿Qué es el Diff?

PlasticSCM: ¿Para qué sirve el Annotate?

FoxPro 9 y PlasticSCM: Cómo deshacer un changeset sin borrarlo

PlasticSCM: ¿Qué es el Merge?

PlasticSCM: ¿Qué es el Shelve?

PlasticSCM: Opciones de búsqueda y visualización de ramas y changesets

PlasticSCM: El merge sustractivo como reemplazo del borrado

PlasticSCM: Qué es el Cherry Pick y cómo se usa

PlasticSCM: Cómo configurar la replicación de repositorios Plastic locales y remotos





lunes, agosto 22, 2016

(Linux) Ubuntu 16.04: Agregando soporte para nuevo hardware

Hace poco adquirí un portátil (MSI GL62 6QF) que para lo que es hoy --22/08/2016-- está casi a la última (Intel Core i7-6700HQ, 4 cores, 8 GB Ram, SSD 256 GB, etc), y como siempre, lo primero que hice fue instalarle Linux (Ubuntu 16.04) como Sistema Operativo principal, ya que venía sin ninguno instalado, y por eso salió algo más barato.

domingo, julio 24, 2016

Nueva versión v1.19.48 de FoxBin2Prg (arreglos) y Extensiones para PlasticSCM v2.5.48


Parte 1: FoxBin2Prg / Parte 2: Extensiones para Plastic

Parte 1: FoxBin2Prg


FoxBin2Prg es un programa pensado para sustituir a SccText/X y TwoFox y mejorar sus funcionalidades, generando versiones de texto estilo-PRG que pueden ser modificadas y permiten recrear el binario original. Puede ser utilizado con herramientas SCM (Administradores de Control de Código Fuente, como VSS, CVS, SVN) y herramientas DVCS (como Git, Mercurial, Plastic, and others), o como programa independiente, para hacer operaciones de Diff (ver diferencias) y Merge (mezclar cambios).


La lista de cambios es la siguiente:

* Bug Fix: Mensaje de error 'variable tcOutputFile not found' (german version) (Andy Kasper)
 

   Este error solo se muestra en la versión traducida al Alemán por el uso de un nombre de variable incorrecto en un mensaje de estado. (Fix enviado por Andy Kasper / Alemania).

* Bug Fix: Posición de menú BEFORE siempre cambiada a AFTER al convertir (Andy Kasper)
 

   Cuando se convierten menús de texto a binario y el menú está configurado como BEFORE, se estaba cambiando a AFTER por un error de comparación. (Fix enviado por Andy Kasper / Alemania).

* Bug Fix: No se respetan algunas restricciones de conversión para DBFs cuando se usan CFGs particulares por tabla (Nathan Brown)
 

   Cuando se usa el nuevo valor DBF_Conversion_Support: 8 en un config de DBF (filename.dbf.cfg), ciertas restricciones no son respetadas, permitiendo que otros DBFs que no tienen esa configuración también sean regenerados desde las verisones texto.

* Bug Fix db2: Cuando se lee un memo multilínea de un db2 con datos antiguo, se produce un error de índice fuera de rango

   Hasta la versión v1.19.46 los memos de los DBFs se exportaban en una única línea, reemplazando los retornos de carro por su equivalente &#13 y &#10, pero desde la v1.19.47 los memos se exportan como multi-línea. El problema era que al importar los DB2 no se estaban teniendo en cuenta los antiguos memos de línea única anteriores a la posibilidad de importarlos con DBF_Conversion_Support: 8.

* Bug Fix db2: Cuando se usa ExcludeDBFAutoincNextval: 1 en FoxBin2Prg.cfg y a la vez la importación de datos de una tabla con campo AutoInc, se produce el error "Error 2088, Field <FIELD> is read-only" (Nathan Brown)

   La opción de ExcludeDBFAutoincNextVal: 1 estaba usando un armado de SQL distinto al que se usa para importar datos, lo que provocaba ese error. Se ha cambiado para usar el mismo armado SQL que se usa en la nueva conversión.

* Bug Fix pj2: Cuando se regenera el binario de un PJ2 con archivos en una ruta con paréntesis y espacios, se genera un error "Error 36, Command contains unrecognized phrase/keyword" (Nathan Brown)

   Este error solo ocurre en las condiciones descriptas y fue descubierto al convertir las clases de ejemplo de VFP (Solution, FFC) en un Windows de 64 bits. (Bug Fix enviado por Nathan Brown / EEUU).

* Bug Fix frx: Los ControlSource de objetos OLE que contienen comillas se generan mal (Nathan Brown)

   Cuando en un reporte se usa un objeto OLE cuyo Control Source se define como "Expression or variable name" y se forma con una expresión del tipo ["] + nombre_var + ["], el motro de parser interpretaba un fin anticipado de expresión, cortándola en el primer ["]. Se ha agregado codificación/decodificación XML para este caso.

Como actualizar el FoxBin2Prg existente:Con descargar el zip y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.



Link de descarga de VFPx


Link de descarga de GitHub



Parte 2: Extensiones para Plastic



Las extensiones para Plastic son un grupo de scripts vbs y programas Visual FoxPro 9 que se configuran dentro de PlasticSCM (herramienta de control de versiones) para poder invocar a FoxBin2Prg desde dentro de la interfaz de Plastic.


La lista de cambios es la siguiente:

> Actualización de FoxBin2Prg: Versión sincronizada con la última versión de FoxBin2Prg



El README.txt explica como se configura en Inglés y Español, y también está explicado en esta nota: Cómo configurar las Herramientas de VFP 9 para Plastic


Como actualizar las existentes:
Con descargarlas y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.



Link de descarga de las Extensiones FoxBin2Prg para Plastic de GitHub



Thank you for your support!



Artículos relacionados:

FoxBin2Prg, el sucesor mejorado del Scctext

FoxBin2Prg: Detalle de vistas, Datos de uso, Configuraciones y más

FoxBin2Prg: Guía rápida de uso y configuración


 Saludos!

jueves, junio 09, 2016

Nueva versión v1.19.47 de FoxBin2Prg (arreglos y mejoras) y Extensiones para PlasticSCM v2.5.47


Parte 1: FoxBin2Prg / Parte 2: Extensiones para Plastic

Parte 1: FoxBin2Prg


FoxBin2Prg es un programa pensado para sustituir a SccText/X y TwoFox y mejorar sus funcionalidades, generando versiones de texto estilo-PRG que pueden ser modificadas y permiten recrear el binario original. Puede ser utilizado con herramientas SCM (Administradores de Control de Código Fuente, como VSS, CVS, SVN) y herramientas DVCS (como Git, Mercurial, Plastic, and others), o como programa independiente, para hacer operaciones de Diff (ver diferencias) y Merge (mezclar cambios).


La lista de cambios es la siguiente:

* Bug Fix: Cuando se indica como nombre de archivo "*" y como tipo "*", se regeneran automáticamente todos los archivos binarios desde los archivos de texto (Alejandro Sosa)
 

   Al usar FoxBin2Prg desde la línea de comandos de VFP e indicar los primeros 2 parámetros como "*", se regeneraban todos los binarios, siendo que es necesario indicar una extensión. Ahora está corregido y se comprueba este parámetro.

* Mejora: Agregados los fuentes de Filename_Caps.exe
 

   El programa Filename_Caps.exe se encarga de normalizar la capitalización de los archivos convertidos, pero como era un programa desarrollado aparte, solo estaba incluido el EXE. Ahora se incluyen los fuentes en el directorio Filename_Caps.

* Mejora: Permitir importar los datos exportados de los DBF (Walter Nichols)
 

   Desde esta versión se pueden importar los datos exportados de los DBF usando el nuevo valor DBF_Conversion_Support: 8 en un config de DBF (filename.dbf.cfg).

* Bug Fix vbs scripts: Algunos arreglos en los scripts vbs de conversión (Doug Hennig)
 

* Mejora: Nuevos métodos API de bajo nivel para automatización

   - get_DBF_Configuration: Permite obtener un objeto CFG con la configuración de la tabla indicada (tabla.dbf.cfg).
   - conversionSupportType: Permite devolver el código de soporte del archivo binario o texto indicado (0,1,2,4,8)



Como actualizar el FoxBin2Prg existente:Con descargar el zip y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.



Link de descarga de VFPx


Link de descarga de GitHub



Parte 2: Extensiones para Plastic



Las extensiones para Plastic son un grupo de scripts vbs y programas Visual FoxPro 9 que se configuran dentro de PlasticSCM (herramienta de control de versiones) para poder invocar a FoxBin2Prg desde dentro de la interfaz de Plastic.


La lista de cambios es la siguiente:

> Actualización de FoxBin2Prg: Versión sincronizada con la última versión de FoxBin2Prg



El README.txt explica como se configura en Inglés y Español, y también está explicado en esta nota: Cómo configurar las Herramientas de VFP 9 para Plastic


Como actualizar las existentes:
Con descargarlas y reemplazar los archivos en el sitio que los hayan puesto antes es suficiente.



Link de descarga de las Extensiones FoxBin2Prg para Plastic de GitHub




Artículos relacionados:

FoxBin2Prg, el sucesor mejorado del Scctext

FoxBin2Prg: Detalle de vistas, Datos de uso, Configuraciones y más

FoxBin2Prg: Guía rápida de uso y configuración


 Saludos!

miércoles, febrero 24, 2016

VFP: Seleccionar palabras de un textbox con un solo click

Más de uno habrá tenido la necesidad de poder seleccionar una palabra con un solo click dentro de un textbox:




Aprovechando que alguien hizo la pregunta en un foro de si se podía hacer esto en FoxPro con un solo click, me tomé un momento para aceptar el reto y dar la respuesta.

El siguiente código está en formato texto (guardarlo como "test_selword.sc2") y se puede regenerar el form original con FoxBin2Prg.

*--------------------------------------------------------------------------------------------------------------------------------------------------------
* (ES) AUTOGENERADO - ¡¡ATENCIÓN!! - ¡¡NO PENSADO PARA EJECUTAR!! USAR SOLAMENTE PARA INTEGRAR CAMBIOS Y ALMACENAR CON HERRAMIENTAS SCM!!
* (EN) AUTOGENERATED - ATTENTION!! - NOT INTENDED FOR EXECUTION!! USE ONLY FOR MERGING CHANGES AND STORING WITH SCM TOOLS!!
*--------------------------------------------------------------------------------------------------------------------------------------------------------
*< FOXBIN2PRG: Version="1.19" SourceFile="test_selword.scx" /> (Solo para binarios VFP 9 / Only for VFP 9 binaries)
*
*
DEFINE CLASS dataenvironment AS dataenvironment
     *< CLASSDATA: Baseclass="dataenvironment" Timestamp="" Scale="" Uniqueid="" ClassIcon="1" />

    DataSource = .NULL.
    Height = 0
    Left = 0
    Name = "Dataenvironment"
    Top = 0
    Width = 0

ENDDEFINE

DEFINE CLASS form1 AS form
     *< CLASSDATA: Baseclass="form" Timestamp="" Scale="" Uniqueid="" />

    *-- OBJECTDATA items order determines ZOrder / El orden de los items OBJECTDATA determina el ZOrder
    *< OBJECTDATA: ObjPath="Text1" UniqueID="" Timestamp="" />
    *< OBJECTDATA: ObjPath="Text2" UniqueID="" Timestamp="" />
    *< OBJECTDATA: ObjPath="Label1" UniqueID="" Timestamp="" />

    Caption = "Form1"
    DoCreate = .T.
    Name = "Form1"

    ADD OBJECT 'Label1' AS label WITH ;
        Caption = "Selected word:", ;
        Height = 17, ;
        Left = 32, ;
        Name = "Label1", ;
        Top = 84, ;
        Width = 84
        *< END OBJECT: BaseClass="label" />

    ADD OBJECT 'Text1' AS textbox WITH ;
        Height = 23, ;
        Left = 28, ;
        Name = "Text1", ;
        Top = 28, ;
        Value = This is phrase for selecting words with a click, ;
        Width = 276
        *< END OBJECT: BaseClass="textbox" />

    ADD OBJECT 'Text2' AS textbox WITH ;
        Height = 23, ;
        Left = 28, ;
        Name = "Text2", ;
        Top = 104, ;
        Width = 100
        *< END OBJECT: BaseClass="textbox" />
  
    PROCEDURE Text1.Click
        LOCAL lnLeftLim, lnRightLim, lnWordLen, lnSelStart
        lnSelStart    = THIS.SelStart
       
        *-- Search Left Limit
        lnLeftLim    = 1
        FOR I = lnSelStart + 1 TO lnLeftLim STEP -1
            IF SUBSTR(THIS.Text, I, 1) == ' ' THEN
                lnLeftLim    = I + 1
                EXIT
            ENDIF
        ENDFOR
       
        *-- Search Right Limit
        lnRightLim    = LEN(THIS.Text)
        FOR I = lnSelStart + 1 TO lnRightLim
            IF SUBSTR(THIS.Text, I, 1) == ' ' THEN
                lnRightLim    = I - 1
                EXIT
            ENDIF
        ENDFOR
       
        lnWordLen    = lnRightLim-lnLeftLim+1
       
        *-- If a space was selected, then do nothing
        IF lnWordLen >= 1
            THISFORM.text2.Value = SUBSTR(THIS.text,lnLeftLim,lnWordLen)
            THIS.SelStart = lnLeftLim-1
            THIS.SelLength = lnWordLen
        ENDIF
       
    ENDPROC

ENDDEFINE



Hasta la próxima! :-)