Mostrando las entradas con la etiqueta ActiveX. Mostrar todas las entradas
Mostrando las entradas con la etiqueta ActiveX. Mostrar todas las entradas

10 de junio de 2019

Código genérico corregido para ActiveX

Artículo original: ActiveX generic fix
http://www.foxpert.com/knowlbits_200804_1.htm
Autor: Christof Wollenhaupt
Traducido por: Ana María Bisbé York


Después de leer mi artículo ActiveFiX que trataba sobre la no respuesta de controles ActiveX, Carlos Alloatti llegó a una solución genérica del problema:

*!* Habilitar ventanas de controles ActiveX 

#Define GW_CHILD 5
#Define GW_HWNDNEXT 2

Declare Integer GetWindow In win32api As apiGetWindow ;
  Integer nhWnd, ;
  Integer uCmd

Declare Integer RealGetWindowClass In win32api ;
  As apiRealGetWindowClass ;
  Integer nhWnd, ;
  String @pszType, ;
  Integer cchType

Declare Integer EnableWindow In win32api As apiEnableWindow ;
  Integer nhWnd, ;
  Integer bEnable

Local ;
  m.lnChildHWnd As Integer, ;
  m.lnCmd As Integer, ;
  m.lnEnable As Integer, ;
  m.lcClassName As String, ;
  m.lnBufferLen As Integer

*!* Para probar, cambie  m.lnEnable a 0 
*!* para inhabilitar las ventanas OleControl 
m.lnEnable = 1

If Thisform.ShowWindow = 2 Or Thisform.ScrollBars > 0 Then
  m.lnChildHWnd = apiGetWindow(Thisform.HWnd, GW_CHILD)
Else
  m.lnChildHWnd = Thisform.HWnd
Endif

m.lnCmd = GW_CHILD

Do While .T.
  m.lnChildHWnd = apiGetWindow(m.lnChildHWnd, m.lnCmd)

  If m.lnChildHWnd = 0 Then
    Exit
  Endif

  m.lcClassName = Space(254)
  m.lnBufferLen = apiRealGetWindowClass(m.lnChildHWnd, ;
    @m.lcClassName, Len(m.lcClassName))
    m.lcClassName = Left(m.lcClassName , m.lnBufferLen)

  If m.lcClassName == "CtlFrameWork_ReflectWindow" Then
    apiEnableWindow(m.lnChildHWnd, m.lnEnable)
  Endif

  m.lnCmd = GW_HWNDNEXT
Enddo

1 de junio de 2015

Recorrer recursivamente un control TreeView

Amigos, humildemente, propongo una rutinita recursiva para recorrer un control TreeView, no es gran cosa, pero por ahí a alguien le puede venir bien.

Serán bienvenidas las mejoras del caso.
*-------------------------------------------------------*
*- CASO 1 - Le paso como NODO el primer Hijo del Nodo en el que estoy posicionado.
*- En este caso la rutina NO procesa el Nodo sobre el que estoy (LO EXCLUYE).
o=thisform.otree
o.selecteditem
primerhijo=o.selecteditem.child)
ver_rama(primerhijo)
*-------------------------------------------------------*
*- CASO 2 - Le paso como NODO áquel en el que estoy posicionado.
*- En este caso la rutina SI procesa el Nodo sobre el que estoy (LO
INCLUYE).
o=thisform.otree
o.selecteditem
ver_rama2(o.selecteditem)
*-------------------------------------------------------*
PROCEDURE ver_rama(onodo)
*--- Pasandole el Primer Hijo del Nodo que me Interesa
LOCAL hnodo,next_nodo,t,nhijos

IF ISNULL(onodo)
   RETURN
ENDIF
MESSAGEBOX(onodo.text)
nhijos=onodo.children
IF nhijos>0
 hnodo=onodo.child
 ver_rama(hnodo)
endif
next_nodo=onodo.next
IF ISNULL(next_nodo)
   RETURN
ELSE
   ver_rama(next_nodo)
ENDIF
RETURN
*-------------------------------------------------------*
PROCEDURE ver_rama2(onodo)
*--- Pasandole el NODO, lo muestra a él y todo lo que cuelga de él
LOCAL hnodo,next_nodo,t,nhijos

IF ISNULL(onodo)
   RETURN
ENDIF
MESSAGEBOX(onodo.text)
nhijos=onodo.children
IF nhijos>0
 hnodo=onodo.child
 ver_rama(hnodo)
endif
RETURN
*-------------------------------------------------------*
Nelson Rodriguez
Salto - Uruguay

28 de mayo de 2015

Formulario con menú con control TreeView

El otro día estaba buscando para hacer un formulario para menú con Treeview, como en varias aplicaciones de gestión. Busqué por varios lados y no lo encontré. Así que hice esto que lo comparto con Uds.


** Creo un Cursor con los datos del Menu,
** puede ser una tabla ya predefinida

CREATE CURSOR cMiMenu (Nivel C(20),Nombre C(50), DoWhat C(90))

** nivel = ####_ (separo con "_" cada 4 digitos
**         para identificar a que nivel pertenece

** nombre = el nombre que quiero asignar a ese nodo en el menu

** dowhath = que comando quiero ejecutar con el dobleclick, lo ideal
**           es que solo los hijos finales tengan algo, pero ...

** se pueden agregar mas campos, como por ej: imagen, parametros, usuarios, etc
INSERT INTO  cMiMenu (Nivel, Nombre, DoWhat) ;
  VALUES ('0001_', 'Padre 1', ' ')
INSERT INTO  cMiMenu (Nivel, Nombre, DoWhat) ;
  VALUES ('0002_', 'Padre 2', ' ')
INSERT INTO  cMiMenu (Nivel, Nombre, DoWhat) ;
  VALUES ('0001_0001_', 'Hijo 1', 'DO FORM \FRM\Hijo1.scx')
INSERT INTO  cMiMenu (Nivel, Nombre, DoWhat) ;
  VALUES ('0002_0001_','Hijo 2',' ')
INSERT INTO  cMiMenu (Nivel, Nombre, DoWhat) ;
  VALUES ('0002_0001_0001_', 'Hijo de Hijo 2', 'DO \PRG\hijo_de_hijo2.prg')


PUBLIC oForm
oForm = NEWOBJECT("Form1")
oForm.SHOW

DEFINE CLASS Form1 AS FORM

  TOP = 10
  LEFT = 100
  HEIGHT = 360
  WIDTH = 360
  DOCREATE = .T.
  CAPTION = "Menu con TreeView y DobleClick"
  NAME = "Form1"
  MINWIDTH = 100
  MINHEIGHT = 100

  ADD OBJECT Olecontrol1 AS OLECONTROL WITH ;
    TOP = 10, LEFT = 10, HEIGHT = 340, WIDTH = 340, ;
    NAME = "Olecontrol1", OLECLASS = "MSComctlLib.TreeCtrl.2"

  PROCEDURE Olecontrol1.DBLCLICK
    SELECT cMiMenu
    LOCATE FOR cMiMenu.Nivel = THIS.SELECTEDITEM.KEY
    IF FOUND()
      IF LEN(ALLTRIM(cMiMenu.DoWhat)) > 1
        WAIT WINDOW + cMiMenu.DoWhat
      ENDIF
    ENDIF
  ENDPROC

  PROCEDURE RESIZE
    THIS.Olecontrol1.WIDTH = THIS.WIDTH - 20
    THIS.Olecontrol1.HEIGHT = THIS.HEIGHT - 20
  ENDPROC

  PROCEDURE Olecontrol1.INIT
    LOCAL lcNivel,lcTexto,lnTipo,lnResta
    THISFORM.Olecontrol1.LineStyle = 1
    THISFORM.Olecontrol1.LabelEdit = 1
    THISFORM.Olecontrol1.FullRowSelect = .T.
    THISFORM.Olecontrol1.HotTracking = .T.
    SELECT cMiMenu
    GO TOP
    DO WHILE !EOF()
      lcNivel = ALLTRIM(cMiMenu.Nivel)
      lcTexto = ALLTRIM(cMiMenu.Nombre)
      IF LEN(ALLTRIM(lcNivel)) = 5
        ** Cuando el valor del LEN() = 5 asumo que es un nodo raiz
        lnTipo = 0
        THISFORM.Olecontrol1.Nodes.ADD(, lnTipo, lcNivel, lcTexto,,)
      ELSE
        ** si LEN() > 5 es un hijo, siempre multiplos de 5
        lnTipo=4
        lnResta = LEN(ALLTRIM(Nivel)) - 5
        lcKey = SUBSTR(ALLTRIM(lcNivel), 1, lnResta)
        THISFORM.Olecontrol1.Nodes.ADD(lcKey, lnTipo, lcNivel, lcTexto,,)
      ENDIF
      SKIP
    ENDDO
  ENDPROC

ENDDEFINE
Ramón González
Misiones, Argentina

14 de febrero de 2015

Controlando dispositivos TWAIN desde VFP

Artículo original: Controlling TWAIN devices from within VFP 
http://www.ml-consult.co.uk/foxst-29.htm
Autor: Mike Lewis 
Traducido por: Carlos A. Miranda



¿Necesita manejar un escaner o una cámara de video desde su aplicación?. Aquí le decimos como hacerlo.

Recientemente escribimos una aplicación FoxPro que manejaba el registro de delegados atendiendo a una conferencia internacional. el Cliente quería que la aplicación fotografiara a cada delegado que llegara, y también guardar la imagen digitalizada de las tarjetas de negocios de los delegados. Debido al gran número de delegados involucrados, la fotografía y el proceso de digitalización tenia que ser los más libre de problemas y fácil posible. Era particularmente importante para el operador ser capaz de controlar la cámara y el escanner mientras estaba sentado en su PC.

En este artículo, nosotros le diremos como desarrollamos este proyecto. La estrategia que adoptamos es razonablemente genérica y no es específica de ningún escanner en particular. Usted no debería tener dificultades en aplicar nuestras técnicas en sus propias aplicaciones si lo desea.

Primer paso: escoger el equipo


Para la fotografía, nosotros desacartamos una cámara digital estandar, principalmente porque no encontrabamos un método de transferir las imágenes sin utilizar las manos hacia nuestra aplicación. En vez de eso, nosotros escogimos una Philips ToUcam web camera (izquierda). Este tipo de dispositivo es utilizado usualmente para video conferencias y como una cámara on-line web cam, pero también puede capturar un solo frame. Este tiene la ventaja de ser un dispositivo TWAIN-compliant y puede ser controlado enteramente desde la PC.
El escanner que nosotros escogimos fue un Targus Mini Business Card Scanner (izquierda). Como su nombre sugiere, este está diseñado especificmente para digitalizar tarjetas de negocios. Como la cámara, esta es TWAIN-compliant también.

A pesar de que nosotros estamos contentos de recomendar ambos dispositivos, la mayoría de los modelos de cámara web o escanner habrían servido para nuestros propositos. El código que nosotros mostraremos en este artículo es capaz de capturar imagenes desde cualquier dispositivo compatible con TWAIN.




 ... Y el software

Hay muchos productos de sofware disponibles que le permiten a uste controlar un dispositivo TWAIN de forma programatica. El que nosotros optamos fue EZTWAIN, de Dosadi. Nos gustó este producto por las siguientes razones:
  • Fácil de programar. Nosotros teníamos media docena o algo así de funciones que preocuparnos de llamar.
  • Fácil de distribuir. Porque es un DLL que a diferencia de un control ActiveX, no tenemos que preocuparnos de registrarlo en el sistema del usuario.
  • Bajo costo. Dependiendo de las necesidades y del tipo de aplicaciones que escribas, el precio varia de nada a alrededor de US$200.
  • Excelente soporte del autor del producto, Spike McLarty.
Declarando sus función

El EZTWAIN DLL tiene alrededor de 70 funciones, pero para muchas aplicaciones usted nunca utilizará más de 7 u ocho de ellas. Aquí estan las declaraciones de las funciones más comunes:

DECLARE INTEGER TWAIN_SelectImageSource ;
  IN Eztw32.DLL INTEGER hWnd
DECLARE INTEGER TWAIN_GetSourceList ;
  IN Eztw32.dll
DECLARE INTEGER TWAIN_GetNextSourceName ;
  IN Eztw32.dll STRING @cSourceName
DECLARE INTEGER TWAIN_OpenSource ;
  IN Eztw32.DLL STRING cSourceName
DECLARE INTEGER TWAIN_AcquireNative ;
  IN Eztw32.DLL INTEGER nAppWind, INTEGER nPixelTypes
DECLARE INTEGER TWAIN_WriteNativeToFilename ;
  IN Eztw32.DLL INTEGER nDIB, STRING cFilename
DECLARE INTEGER TWAIN_FreeNative ;
  IN Eztw32.DLL INTEGER nDIB
DECLARE INTEGER TWAIN_SetMultiTransfer ;
  IN Eztw32.dll INTEGER nFlag

Capturando una imagen

Si uste solo tiene un dispositivo TWAIN device instalado, simplemente llame a la función TWAIN_AcquireNative() para capturar la imagen. Esta función inicia el proceso de captura. Cuando este ha finalizado, la imagen será presentada en memoria, en formato "device-independent bitmap (DIB)". La función utiliza dos parámetros de tipo integer; in la mayoría de casos estos serán cero. Retorna un manejador ( handle ) para la imagen.

En el caso de nuestra camara Web ToUcam, llamando a TWAIN_AcquireNative() lanza el visor de la cámara en pantalla (Figura 1). Esto despliega una alimentación continua de la imagen. En cualquier momento, el usuario puede hacer click en el botón de Captura para tomar la fotografía.


Figura 1: Esto es lo que el usuario ve cuando usted empieza el proceso de captura desde la cámar web.

Una vez que la imagen DIB esta en memoria, usted puede llamarl a la función TWAIN_WriteNativeToFilename() para escribir a un archivo que uste escoja. Por defecto, este será un BMP file, pero otros formatos también son soportados. Usted pasa dos parámetros para esta función: El primero es el manejador (handle) DIB retornado por TWAIN_AcquireNative(), y el segundo el nombre calificado del archivo destino.

Finalmente, llamar al TWAIN_FreeNative() para borrar de memoria la imagen DIB. Si usted no hace esto, usted rápidamente perdería memoria.

Aquí esta nuestro código para tomar una fotografía con la cámara web:

LOCAL lcFile, lnImageHandle, lnReply
lcFile = "c:testtest_image.bmp"
* Captura la imágen
lnImageHandle = TWAIN_AcquireNative(0,0)
* copia la imagen a un archivo
lnReply = ;
  TWAIN_WriteNativeToFilename(lnImageHandle,lcFile)
* Libera la memoria del manejador de la imágen
TWAIN_FreeNative(lnImageHandle)
* Chequear errores
IF lnReply = 0
  * imagen fue exitosamente grabada
ELSE
  * algo no estuvo bien
ENDIF

Note que la respuesta de TWAIN_WriteNativeToFilename() le dice a usted si el archivo fue escrito de forma exitosa. Sin embargo, esto no le dice a usted si el trabajo para obtener la imagen trabajó apropiadamente - la captura podría haber fallado por alguna razón, o podría haber sido cancelada por el usuario. Una manera de probarlo es verificando el tamaño del archivo resultante; si es cero, entonces ninguna imagen fue capturada.

Múltiples dispositivos

El código anterior captura una imagen desde cualquier dispositivo TWAIN que usted tenga instalado. Si usted tiene un escaner o una camera, el código iniciará el proceso de digitalización y grabará la imagen.

Pero que pasa si uste necesita manejar dos dispositivos de captura para la misma PC? Este fue el caso de nuestra aplicación, en la cual el usuario necesitaba controlar tanto la cámara como el escaner de tarjeta de negocios.

Por defecto, TWAIN_AcquireNative() capturará del primer dispositivo TWAIN que encuentre. Sin embargo, el EZTWAIN DLL tiene una función llamada TWAIN_SelectImageSource(), la cual le da al usuario la oportunidad de seleccionar un diferente dispositivo de captura. Cuando uste llama a esta función (usualmente con 0 como parámetro), el usuario ve el diálogo estñandar de la Figura 2 de las fuentes de disposivitos TWAIN que tiene disponibles para seleccionar. La función retorna 0 si el usuario cancela el dialogo o si no hay dispositivos de captura instaladps, de otra manera este retorna 1.


Figura 2: Dialogo estándar para seleccionar la fuente de captura de los dispositivos TWAIN.

En nuestro caso, nosotros no hemos querido utilizar esto para ver el díalogo. Porque nuestra aplicación tenía un botón específico para la cámara y otro para el digitalizador de tarjetas, nosotros quisimos seleccionar el dispositivo de forma progrmática.

Para hacer eso, nosotros utilizamos las dos siguientes funciones: TWAIN_GetSourceList(), la cual lee una lista de nombres de dispositivos dentro de la memoria del EZTWAIN; y TWAIN_GetNextSourceName(), la cual trae el siguiente dispositivo de la lista. Después llamamos a TWAIN_GetSourceList() una vez, y llamamos a TWAIN_GetNextSourceName() repetidamente hasta que este retorne 0 para indicar que no hay más nombre en la lista.

Como un ejemplo, aquí esta algo de código que usted podría utilizar para llenar un combo box con los nombres de los dispositivos disponibles:

LOCAL lcSource, lnReply
* Obtiene la lista de los dispositivos en memoria
TWAIN_GetSourceList()
lcSource = SPACE(255)
DO WHILE .T.
  * Obtiene el siguiente nombre de dispositivo
  lnReply = TWAIN_GetNextSourceName(@lcSource)
  IF lnReply = 0
    * No hay más nombres de dispositivos
    EXIT
  ENDIF
  
  * quitar los nulos, etc
  lcSource = ;
    LEFT(lcSource,AT(CHR(0),lcSource)-1)
  
* Agreagar al combo
  THISFORM.cboDevices.AddItem(lcSource)
ENDDO

Una vez que usted conozca los nombres de las fuentes, usted puede pasar estos a la función TWAIN_OpenSource(). Esta establecerá el dispositivo para la siguiente llamada a TWAIN_AcquireNative().

Por defecto, TWAIN_AcquireNative() cerrará la fuente de captura despùes de que finalice el proceso. Así que, si usted tiene más de un dispositivo, usted necesitará llamar a TWAIN_OpenSource() antes de cada llamada a TWAIN_AcquireNative(). Desafortunadamente, abrir la fuente de captura consume tiempo. Dependiendo del dispositivo, los usuarios podrían notar un retraso de algunos segundo antes de que la captura pueda empezar.

Como una alternativa usted puede llamar a la función TWAIN_SetMultiTransfer(1) para decirle al EZTWAIN que deje la fuente de captura abierta. De esta manera, usted solo necesita llamar a TWAIN_OpenSource() cuando usted quiera cambiar a un dispostivo diferente. Cuando nosotros tratamos de hacer esto, sin embargo, encontramos que the el visor para la cámar web ToUcam permanecia en la pantalla, frente a la ventana de nuestras aplicaciones todo el tiempo. Esto obstruía parte de la ventana de nuestra aplicación. Por esta razón, nosotros escogimos no mantener el dispositivo abierto.

Ir más lejos....

En este artículo, nosotros hemos tratado de darle a usted un pequeñ bocado del EZTWAIN DLL. Esta es una herrmienta extremadamente capaz. Con muchas más funciones que nosotros no tendriamos espacio para describir aquí. Si usted necesita contolar uno o más dispositivos TWAIN devices desde su aplicación Visual Foxpro, porque no descarga una copia y la explorae por si mismo.

Para más información acerca de EZTWAIN y otros productos relacionados con TWAIN, y para descargar una copia de la DLL, visite www.dosadi.com.

Si usted quiere conecer más acerca de Philips ToUcam web camera:
El escaner "Targus card-scanner" cuesta alrededor de US$125:
Mike Lewis Consultants Ltd. February 2003

25 de mayo de 2011

Utilizando el control TreeView (4/4)

Cuarta y última parte de una serie de códigos de ejemplos sobre como utilizar el control TreeView en VFP, escritos por el turco Cetin Basoz (Microsoft Visual FoxPro MVP 1999-2010).

* Define some constant
#DEFINE tvwFirst     0
#DEFINE tvwLast      1
#DEFINE tvwNext      2
#DEFINE tvwPrevious  3
#DEFINE tvwChild     4
#DEFINE cnLOG_PIXELS_X 88
#DEFINE cnLOG_PIXELS_Y 90
* 1440 twips por pulgadas
#DEFINE cnTWIPS_PER_INCH 1440

oForm = CREATEOBJECT('myForm')
oForm.SHOW
READ EVENTS

DEFINE CLASS myForm AS FORM
  HEIGHT = 640
  WIDTH = 800
  AUTOCENTER = .T.
  CAPTION = "TreeView - TestPad"
  NAME = "myForm"

  *-- Node object reference
  nodx = .F.
  nxtwips = .F.
  nytwips = .F.

  ADD OBJECT oletreeview AS OLECONTROL WITH ;
    TOP = 0, LEFT = 0, HEIGHT = 600, WIDTH = 750, ;
    ANCHOR = 15, NAME = "OleTreeView", ;
    OLECLASS = 'MSComCtlLib.TreeCtrl'

  ADD OBJECT oleimageslist AS OLECONTROL WITH ;
    TOP = 0, LEFT = 0, HEIGHT = 100, WIDTH = 100, ;
    NAME = "oleImagesList",;
    OLECLASS = 'MSComCtlLib.ImageListCtrl'

  *-- Fill the tree values
  PROCEDURE filltree
    LPARAMETERS tcDirectory, tcImage
    THIS.SHOW
    CREATE CURSOR crsNodes (NodeKey c(15), ParentKey c(15), NodeText m, NewParent c(15))
    LOCAL oNode
    WITH THIS.oletreeview.nodes
      oNode=.ADD(,tvwFirst,"root"+PADL(.COUNT,3,'0'),tcDirectory,tcImage)
    ENDWITH
    INSERT INTO crsNodes (NodeKey, ParentKey, NodeText) VALUES (oNode.KEY, '',oNode.TEXT)
    THIS._SubFolders(oNode)

  ENDPROC

  PROCEDURE pixeltotwips

    *-- Code for PixelToTwips method
    LOCAL liHWnd, liHDC, liPixelsPerInchX, liPixelsPerInchY

    * Declare some Windows API functions.
    DECLARE INTEGER GetActiveWindow IN WIN32API
    DECLARE INTEGER GetDC IN WIN32API INTEGER iHDC
    DECLARE INTEGER GetDeviceCaps IN WIN32API INTEGER iHDC, INTEGER iIndex

    * Get a device context for VFP.
    liHWnd = GetActiveWindow()
    liHDC = GetDC(liHWnd)

    * Get the pixels per inch.
    liPixelsPerInchX = GetDeviceCaps(liHDC, cnLOG_PIXELS_X)
    liPixelsPerInchY = GetDeviceCaps(liHDC, cnLOG_PIXELS_Y)

    * Get the twips per pixel.
    THISFORM.nxtwips = ( cnTWIPS_PER_INCH / liPixelsPerInchX )
    THISFORM.nytwips = ( cnTWIPS_PER_INCH / liPixelsPerInchY )
    RETURN

  ENDPROC

  *-- Collect subfolders
  PROCEDURE _SubFolders
    LPARAMETERS oNode
    LOCAL nChild, oNodex
    lcFolder = oNode.FULLPATH
    lcFolder = STRTRAN(lcFolder,":\\",":\")
    oFS = CREATEOBJECT('Scripting.FileSystemObject')
    oFolder = oFS.GetFolder(lcFolder)
    WITH THISFORM.oletreeview
      lnIndent = 0
      lnIndex = oNode.INDEX
      DO WHILE lnIndex # oNode.Root.INDEX ;
          AND TYPE('.nodes(lnIndex).Parent')='O' ;
          AND !ISNULL(.nodes(lnIndex).PARENT)
        lnIndex = .nodes(lnIndex).PARENT.INDEX
        lnIndent = lnIndent + 1
      ENDDO
      lcChildKeyPrefix = 'L'+PADL(lnIndent,3,'0')+'_'
    ENDWITH
    WITH THISFORM.oletreeview.nodes
      IF oNode.Children > 0
        IF oNode.CHILD.KEY = oNode.KEY+"dummy"
          .REMOVE(oNode.CHILD.INDEX)
          FOR EACH oSubFolder IN oFolder.Subfolders
            INSERT INTO crsNodes ;
              (NodeKey, ParentKey, NodeText) ;
              VALUES ;
              (lcChildKeyPrefix+' '+PADL(RECCOUNT('crsNodes')+1,5,'0'), ;
              oNode.KEY, oSubFolder.PATH)
            oNodex = .ADD(oNode.KEY, tvwChild, ;
              crsNodes.NodeKey, oSubFolder.NAME, "ClosedFolder","OpenFolder" )
            oNodex.ExpandedImage = "OpenFolder"
            IF oSubFolder.NAME # "System Volume Information" AND oSubFolder.Subfolders.COUNT > 0
              oNodex = .ADD(crsNodes.NodeKey, tvwChild, ;
                crsNodes.NodeKey+"dummy", "dummy", "ClosedFolder","OpenFolder" )
            ENDIF
          ENDFOR
        ENDIF
      ELSE
        IF oFolder.Subfolders.COUNT > 0
          oNodex = .ADD(oNode.KEY, tvwChild, ;
            oNode.KEY+"dummy", "dummy", "ClosedFolder","OpenFolder" )
        ENDIF
      ENDIF
    ENDWITH
  ENDPROC

  PROCEDURE QUERYUNLOAD
    THISFORM.nodx = .NULL.
    CLEAR EVENTS
  ENDPROC

  PROCEDURE INIT
    THIS.pixeltotwips()
    SET TALK OFF
    * Check to see if OCX installed and loaded.
    IF TYPE("THIS.oleTreeView") # "O" OR ISNULL(THIS.oletreeview)
      RETURN .F.
    ENDIF
    IF TYPE("THIS.oleImagesList") # "O" OR ISNULL(THIS.oleimageslist)
      RETURN .F.
    ENDIF
    lcIconPath = HOME(0) + "Graphics\Icons\"
    WITH THIS.oleimageslist
      .ImageHeight = 32
      .ImageWidth = 32
      .ListImages.ADD(,"OpenFolder",LOADPICTURE(lcIconPath+"Win95\openfold.ico"))
      .ListImages.ADD(,"ClosedFolder",LOADPICTURE(lcIconPath+"Win95\clsdfold.ico"))
      .ListImages.ADD(,"Drive",LOADPICTURE(lcIconPath+"Computer\drive01.ico"))
      .ListImages.ADD(,"Floppy",LOADPICTURE(lcIconPath+"Win95\35floppy.ico"))
      .ListImages.ADD(,"NetDrive",LOADPICTURE(lcIconPath+"Win95\drivenet.ico"))
      .ListImages.ADD(,"CDDrive",LOADPICTURE(lcIconPath+"Win95\CDdrive.ico"))
      .ListImages.ADD(,"RAMDrive",LOADPICTURE(lcIconPath+"Win95\desktop.ico"))
      .ListImages.ADD(,"Unknown",LOADPICTURE(lcIconPath+"Misc\question.ico"))
    ENDWITH

    WITH THIS.oletreeview
      .linestyle =1
      .labeledit =1
      .indentation = 5
      .imagelist = THIS.oleimageslist.OBJECT
      .PathSeparator = '\'
      .OLEDRAGMODE = 1
      .OLEDROPMODE = 1
    ENDWITH

    oFS = CREATEOBJECT('Scripting.FileSystemObject')
    LOCAL ARRAY aDrvTypes[7]
    aDrvTypes[1]="Unknown"
    aDrvTypes[2]="Floppy"
    aDrvTypes[3]="Drive"
    aDrvTypes[4]="NetDrive"
    aDrvTypes[5]="CDDrive"
    aDrvTypes[6]="RAMDrive"

    FOR EACH oDrive IN oFS.Drives
      IF oDrive.IsReady
        THIS.filltree(oDrive.Rootfolder.PATH, aDrvTypes[oDrive.DriveType+1])
      ENDIF
    ENDFOR
  ENDPROC

  PROCEDURE oletreeview.Expand
    *** ActiveX Control Event ***
    LPARAMETERS NODE
    THISFORM._SubFolders(NODE)
    NODE.ensurevisible
  ENDPROC

  PROCEDURE oletreeview.NodeClick
    *** ActiveX Control Event ***
    LPARAMETERS NODE
    NODE.ensurevisible
    THIS.DropHighlight = .NULL.
  ENDPROC

  PROCEDURE oletreeview.MOUSEDOWN
    *** ActiveX Control Event ***
    LPARAMETERS BUTTON, SHIFT, x, Y
    WITH THISFORM
      oHitTest = THIS.HitTest( x * .nxtwips, Y * .nytwips )
      IF TYPE("oHitTest")= "O" AND !ISNULL(oHitTest)
        THIS.SELECTEDITEM = oHitTest
      ENDIF
      .nodx = THIS.SELECTEDITEM
    ENDWITH
    oHitTest = .NULL.
  ENDPROC

  PROCEDURE oletreeview.OLEDRAGOVER
    *** ActiveX Control Event ***
    LPARAMETERS DATA, effect, BUTTON, SHIFT, x, Y, state
    oHitTest = THIS.HitTest( x * THISFORM.nxtwips, Y * THISFORM.nytwips )
    IF TYPE("oHitTest")= "O"
      THIS.DropHighlight = oHitTest
    ENDIF
  ENDPROC

  PROCEDURE oletreeview.OLEDRAGDROP
    *** ActiveX Control Event ***
    LPARAMETERS DATA, effect, BUTTON, SHIFT, x, Y
    IF DATA.GETFORMAT(1)     &&CF_TEXT
      WITH THIS
        IF !ISNULL(THISFORM.nodx) AND TYPE(".DropHighLight") = "O" AND !ISNULL(.DropHighlight)
          loSource = THISFORM.nodx
          loTarget = .DropHighlight
          IF loSource.KEY # loTarget.KEY AND TYPE('loSource.Parent') = 'O'
            lcSourceParentKey = loSource.PARENT.KEY
            lcTargetParentKey = loTarget.PARENT.KEY
            IF SUBSTR(lcSourceParentKey,1,AT('_',lcSourceParentKey)-1) == ;
                SUBSTR(lcTargetParentKey,1,AT('_',lcTargetParentKey)-1)
              lcSourceKey = IIF(lcSourceParentKey == lcTargetParentKey,'',;
                IIF(SHIFT=1,'mv','cp'))+loSource.KEY
              lcSourceText = loSource.TEXT
              llRemoveSource = (lcSourceParentKey == lcTargetParentKey OR SHIFT=1)

              * Check here for children repopulation since we're simulating with existing directories
              * llGetChildren should be false for copy-move from another parent dir
              llGetChildren  = (lcSourceParentKey == lcTargetParentKey)

              IF llRemoveSource
                .nodes.REMOVE(loSource.INDEX)
              ENDIF
              * Check if node exists already
              IF TYPE('.Nodes(lcSourceKey)') # 'O'
                oNode=.nodes.ADD(loTarget.KEY,tvwPrevious,lcSourceKey,lcSourceText,;
                  "ClosedFolder","OpenFolder")
                .SELECTEDITEM = oNode
                IF llGetChildren
                  THISFORM._SubFolders(oNode)
                ENDIF
              ENDIF
            ENDIF
          ENDIF
        ENDIF
      ENDWITH
    ENDIF
    THIS.DropHighlight = .NULL.
  ENDPROC

ENDDEFINE

Gracias Cetin por compartir y autorizar esta publicación.

Utilizando el control TreeView (3/4)

Tercera parte de una serie de códigos de ejemplos sobre como utilizar el control TreeView en VFP, escritos por el turco Cetin Basoz (Microsoft Visual FoxPro MVP 1999-2010).

#DEFINE tvwFirst    0
#DEFINE tvwLast    1
#DEFINE tvwNext    2
#DEFINE tvwPrevious    3
#DEFINE tvwChild    4

#DEFINE cnLOG_PIXELS_X 88
#DEFINE cnLOG_PIXELS_Y 90
#DEFINE cnTWIPS_PER_INCH 1440

TEXT to myMenu noshow
Lparameters toNode,toForm

DEFINE POPUP shortcut SHORTCUT RELATIVE FROM MROW(),MCOL()
DEFINE BAR 1 OF shortcut PROMPT "Key"
DEFINE BAR 2 OF shortcut PROMPT "Text"
DEFINE BAR 3 OF shortcut PROMPT "Fullpath"
DEFINE BAR 4 OF shortcut PROMPT "Index"
DEFINE BAR 5 OF shortcut PROMPT "New Item"
ON SELECTION BAR 1 OF shortcut ;
    wait window toNode.Key timeout 2
ON SELECTION BAR 2 OF shortcut  ;
    wait window toNode.Text timeout 2
ON SELECTION BAR 3 OF shortcut  ;
    wait window toNode.Fullpath timeout 2
ON SELECTION BAR 4 OF shortcut  ;
    wait window Transform(toNode.Index) timeout 2
ON SELECTION BAR 5 OF shortcut toForm.ShowIt(toNode)
ACTIVATE POPUP shortcut

ENDTEXT

*StrToFile(m.myMenu,'myTVShcut.mpr')

oForm = CREATEOBJECT('myForm')
WITH oForm
  .ADDOBJECT('Tree','myTreeView')
  .ADDOBJECT('Lister','Lister')
  WITH .Tree
    .WIDTH = 700
    .HEIGHT = 600
    .Nodes.ADD(,0,"root0",'Main node 1')
    .Nodes.ADD(,0,"root1",'Main node 2')
    .Nodes.ADD(,0,"root2",'Main node 3')
    .Nodes.ADD('root1',4,"child11",'Child11')
    .Nodes.ADD('root1',4,"child12",'Child12')
    .Nodes.ADD('root2',4,"child21",'Child22')
    .Nodes.ADD('child21',3,"child20",'Child21')
    oNodx=.Nodes.ADD('child11',4,"child111",'child113')
    oNodx.Bold=.T.
    .Nodes.ADD('child111',3,"child112",'child112')
    .Nodes.ADD('child112',3,"child113",'child111')

    .Nodes.ADD('child12',4,"child121",'child121')
    .Nodes.ADD('child12',4,"child122",'child122')

    .Nodes.ADD('child112',4,"child1121",'child1121')
    .Nodes.ADD('child112',4,"child1122",'child1122')
    .Nodes.ADD('child112',4,"child1123",'child1123')
    .Nodes.ADD('child112',4,"child1124",'child1124')
    .Nodes.ADD('child112',4,"child1125",'child1125')

    .Nodes.ADD('child1121',4,"child11211",'child11211')
    .Nodes.ADD('child1121',4,"child11212",'child11212')

    .Nodes.ADD('child11211',4,"child112111",'child112111')
    .Nodes.ADD('child11212',4,"child112121",'child112121 last added')
    .VISIBLE = .T.
    .Nodes(.Nodes.COUNT).Ensurevisible
    WITH .FONT
      .SIZE = 12
      .NAME = 'Times New Roman'
      .Bold = .F.
      .Italic = .T.
    ENDWITH
  ENDWITH
  .Lister.LEFT = .WIDTH - .Lister.WIDTH
  .lister.VISIBLE = .T.
  .SHOW()
ENDWITH
READ EVENTS

FUNCTION TVLister
  LPARAMETERS toTV
  LOCAL lnIndex,lnLastIndex
  WITH toTV
    lnIndex     = .Nodes(1).Root.FirstSibling.INDEX
    lnLastIndex = .Nodes(1).Root.LastSibling.INDEX
    _GetSubNodes(lnIndex,toTV,lnIndex)
    DO WHILE lnIndex # lnLastIndex
      lnIndex = .Nodes(lnIndex).NEXT.INDEX
      _GetSubNodes(lnIndex,toTV,lnIndex)
    ENDDO
  ENDWITH

FUNCTION _GetSubNodes
  LPARAMETERS tnIndex, toTV, tnRootIndex
  LOCAL lnIndex, lnLastIndex
  WITH toTV
    WriteNode(tnIndex,toTV, tnRootIndex)
    IF .Nodes(tnIndex).Children > 0
      lnIndex  = .Nodes(tnIndex).CHILD.INDEX
      lnLastIndex = .Nodes(tnIndex).CHILD.LastSibling.INDEX
      _GetSubNodes(lnIndex,toTV,tnRootIndex)
      DO WHILE lnIndex # lnLastIndex
        lnIndex = .Nodes(lnIndex).NEXT.INDEX
        _GetSubNodes(lnIndex,toTV,tnRootIndex)
      ENDDO
    ENDIF
  ENDWITH

FUNCTION WriteNode
  LPARAMETERS tnCurIndex, toTV,tnRootIndex
  LOCAL lnRootIndex, lnIndex, lcPrefix, lcKey, lnLevel
  lnIndex = tnCurIndex

  WITH toTV
    lcPrefix = '+-' + .Nodes(lnIndex).TEXT
    lnLevel = 0
    DO WHILE lnIndex # tnRootIndex
      lnIndex = .Nodes(lnIndex).PARENT.INDEX
      lcPrefix = IIF(.Nodes(lnIndex).LastSibling.INDEX = lnIndex,' ','|')+SPACE(3)+lcPrefix
      lnLevel = lnLevel + 1
    ENDDO
    ? lcPrefix
  ENDWITH

FUNCTION WalkTree
  LPARAMETERS oNode,lnIndent,tlPlus
  ? IIF(tlPlus,'+','')+REPLICATE(CHR(9),lnIndent)+oNode.TEXT
  IF !ISNULL(oNode.CHILD)
    WalkTree(oNode.CHILD,lnIndent+1,.T.)
  ENDIF
  IF !ISNULL(oNode.NEXT)
    WalkTree(oNode.NEXT,lnIndent,.F.)
  ENDIF
  RETURN
ENDFUNC

DEFINE CLASS myForm AS FORM
  AUTOCENTER = .T.
  HEIGHT = 640
  WIDTH = 800

  nxtwips = .F.
  nytwips = .F.

  PROCEDURE QUERYUNLOAD
    CLEAR EVENTS
  ENDPROC

  PROCEDURE ShowIt
    LPARAMETERS toNode
    MESSAGEBOX("Form method called with " + toNode.FULLPATH)
  ENDPROC

  PROCEDURE INIT
    *-- Code for PixelToTwips method
    LOCAL liHWnd, liHDC, liPixelsPerInchX, liPixelsPerInchY

    * Declare some Windows API functions.
    DECLARE INTEGER GetActiveWindow IN WIN32API
    DECLARE INTEGER GetDC IN WIN32API INTEGER iHDC
    DECLARE INTEGER GetDeviceCaps IN WIN32API INTEGER iHDC, INTEGER iIndex

    * Get a device context for VFP.
    liHWnd = GetActiveWindow()
    liHDC = GetDC(liHWnd)

    * Get the pixels per inch.
    liPixelsPerInchX = GetDeviceCaps(liHDC, cnLOG_PIXELS_X)
    liPixelsPerInchY = GetDeviceCaps(liHDC, cnLOG_PIXELS_Y)

    * Get the twips per pixel.
    THIS.nxtwips = ( cnTWIPS_PER_INCH / liPixelsPerInchX )
    THIS.nytwips = ( cnTWIPS_PER_INCH / liPixelsPerInchY )
    RETURN
  ENDPROC


  PROCEDURE CheckRest
    LPARAMETERS tnIndex, tlCheck, toTreeView
    LOCAL lnIndex, lnLastIndex
    WITH toTreeView
      .Nodes(tnIndex).Checked = tlCheck
      IF .Nodes(tnIndex).Children > 0
        lnIndex  = .Nodes(tnIndex).CHILD.INDEX
        lnLastIndex = .Nodes(tnIndex).CHILD.LastSibling.INDEX
        THIS.CheckRest(lnIndex, tlCheck, toTreeView)
        DO WHILE lnIndex # lnLastIndex
          lnIndex = .Nodes(lnIndex).NEXT.INDEX
          THIS.CheckRest(lnIndex, tlCheck, toTreeView)
        ENDDO
      ENDIF
    ENDWITH
  ENDPROC

ENDDEFINE

DEFINE CLASS myTreeView AS OLECONTROL
  OLEDRAGMODE = 1
  OLEDROPMODE = 1
  NAME = "OleTreeView"
  OLECLASS = 'MSComCtlLib.TreeCtrl'

  PROCEDURE INIT
    WITH THIS
      .OBJECT.CheckBoxes = .T.
      .linestyle =1
      .labeledit =1
      .indentation = 5
      .PathSeparator = '\'
    ENDWITH
  ENDPROC
  PROCEDURE NodeClick
    *** ActiveX Control Event ***
    LPARAMETERS NODE
    NODE.ensurevisible
    MESSAGEBOX(NODE.FULLPATH + CHR(13) +TRANS(NODE.INDEX),0,"NodeClick",2000)
  ENDPROC

  PROCEDURE MOUSEDOWN
    LPARAMETERS BUTTON, SHIFT, x, Y
    IF BUTTON=2
      lcWhere = ''
      oNode = THIS.HitTest( x * THISFORM.nxtwips, Y * THISFORM.nytwips )
      IF TYPE("oNode")= "O" AND !ISNULL(oNode)
        *        DO myTVShcut.mpr with oNode
        EXECSCRIPT(m.MyMenu, oNode, THISFORM)
      ENDIF
    ENDIF
  ENDPROC

  PROCEDURE MOUSEUP
    LPARAMETERS BUTTON, SHIFT, x, Y
    *!*      if button=2
    *!*          nodefault
    *!*          Wait window 'Right click occured in Mup' timeout 2
    *!*      endif
    IF BUTTON=1
      oNode = THIS.HitTest( x * THISFORM.nxtwips, Y * THISFORM.nytwips )
      IF TYPE("oNode")= "O" AND !ISNULL(oNode)
        IF oNode.KEY # 'root1'
          oNode.Checked = .F.
        ELSE
          THISFORM.CheckRest(oNode.INDEX,oNode.Checked,THIS)
        ENDIF
      ENDIF
    ENDIF
  ENDPROC

  *!*      Procedure NodeCheck
  *!*    *** ActiveX Control Event ***
  *!*    Lparameters node,dummy
  *!*    IF node.Key = 'root1'
  *!*    thisform.CheckRest(node.Index,node.Checked,this)
  *!*    endif
  *!*    endproc

  PROCEDURE _SubNodes
    LPARAMETERS tnIndex, tnLevel
    LOCAL lnIndex
    lcFs = ''
    WITH THIS
      ? IIF(tnLevel=0,'',REPLICATE(CHR(9),tnLevel))+.Nodes(tnIndex).TEXT, "[Actual index :"+TRANS(tnIndex)+"]"
      IF .Nodes(tnIndex).Children > 0
        lnIndex  = .Nodes(tnIndex).CHILD.INDEX
        ._SubNodes(lnIndex,tnLevel+1)
        DO WHILE lnIndex # .Nodes(tnIndex).CHILD.LastSibling.INDEX
          lnIndex = .Nodes(lnIndex).NEXT.INDEX
          ._SubNodes(lnIndex,tnLevel+1)
        ENDDO
      ENDIF
    ENDWITH
  ENDPROC

  PROCEDURE ExpandAll
    LPARAMETERS tnIndex
    LOCAL lnIndex
    WITH THIS
      .Nodes(tnIndex).Expanded = .T.
      IF .Nodes(tnIndex).Children > 0
        lnIndex  = .Nodes(tnIndex).CHILD.INDEX
        .ExpandAll(lnIndex)
        DO WHILE lnIndex # .Nodes(tnIndex).CHILD.LastSibling.INDEX
          lnIndex = .Nodes(lnIndex).NEXT.INDEX
          .ExpandAll(lnIndex)
        ENDDO
      ENDIF
    ENDWITH
  ENDPROC
ENDDEFINE

DEFINE CLASS Lister AS COMMANDBUTTON
  CAPTION = 'Listado'
  HEIGHT = 32
  WIDTH = 100

  PROCEDURE CLICK
    ACTIVATE SCREEN
    TvLister(THISFORM.Tree)
    WITH THISFORM.Tree
      *  WalkTree(.Nodes(1),0)
      *    .ExpandAll(.SelectedItem.Index)
    ENDWITH
  ENDPROC

  PROCEDURE click1
    ACTIVATE SCREEN
    CLEAR
    LOCAL lnIndex
    WITH THISFORM.Tree
      lnIndex = .Nodes(1).Root.FirstSibling.INDEX
      ._SubNodes(lnIndex,0)
      DO WHILE lnIndex # .Nodes(1).Root.LastSibling.INDEX
        lnIndex = .Nodes(lnIndex).NEXT.INDEX
        ._SubNodes(lnIndex,0)
      ENDDO
    ENDWITH
  ENDPROC
ENDDEFINE

Gracias Cetin por compartir y autorizar esta publicación.

Utilizando el control TreeView (2/4)

Segunda parte de una serie de códigos de ejemplos sobre como utilizar el control TreeView en VFP, escritos por el turco Cetin Basoz (Microsoft Visual FoxPro MVP 1999-2010).
SELECT PADR('Customer_'+cust_id,20) AS NodeID, ;
  PADR('',20) AS ParentID, ;
  PADR(Company,100) AS NodeText, ;
  0 AS LEVEL ;
  FROM (HOME(2)+'data\customer') ;
  UNION ;
  SELECT PADR('Orders_'+order_id,20) AS NodeID, ;
  PADR('Customer_'+c.cust_id,20) AS ParentID, ;
  PADR(ALLTRIM(TRANSFORM(order_id))+":"+TRANSFORM(Order_Date),100) AS NodeText, ;
  1 AS LEVEL ;
  FROM (HOME(2)+'data\Orders') o ;
  INNER JOIN (HOME(2)+'data\customer') c ON o.cust_id == c.cust_id ;
  UNION ;
  SELECT 'OrdItems_'+oi.order_id+'_'+PADL(line_no,3,'0') AS NodeID, ;
  'Orders_'+o.order_id AS ParentID, ;
  TRANSFORM(oi.line_no)+':'+p.Prod_Name-(' ['+TRANSFORM(oi.Quantity)+']') AS NodeText, ;
  2 AS LEVEL ;
  FROM (HOME(2)+'data\OrdItems') oi ;
  INNER JOIN (HOME(2)+'data\Orders') o ON oi.order_id == o.order_id ;
  INNER JOIN (HOME(2)+'data\customer') c ON o.cust_id == c.cust_id ;
  INNER JOIN (HOME(2)+'data\products') p ON oi.product_id == p.product_id ;
  ORDER BY LEVEL ;
  INTO CURSOR myTree ;
  nofilter

#DEFINE tvwFirst 0
#DEFINE tvwLast 1
#DEFINE tvwNext 2
#DEFINE tvwPrevious 3
#DEFINE tvwChild 4

PUBLIC oForm
oForm = CREATEOBJECT('myTreeForm','myTree')
oForm.SHOW

DEFINE CLASS myTreeForm AS FORM
  HEIGHT = 640
  WIDTH = 800
  Autocenter = .T.
  CAPTION = "TreeView - TestPad"

  nxtwips = 0
  nytwips = 0
  cursorbehind = ''

  ADD OBJECT TreeView AS OLECONTROL WITH ;
    HEIGHT = 640, WIDTH = 800, ;
    anchor = 15, OLECLASS = 'MSComCtlLib.TreeCtrl'

  PROCEDURE INIT
    LPARAMETERS tcCursorName
    WITH THIS.TreeView
      .linestyle =1
      .labeledit =1
      .indentation = 5
      .PathSeparator = '\'
      .SCROLL = .T.
      .OLEDRAGMODE = 0
      .OLEDROPMODE = 0
    ENDWITH
    THIS.cursorbehind = m.tcCursorName
    THIS.PixelToTwips()
    THIS.Populate()
  ENDPROC

  PROCEDURE Populate
    SELECT (THIS.cursorbehind)
    WITH THIS.TreeView.Nodes
      SCAN
        IF EMPTY(ParentID)
          oNode = .ADD(,tvwFirst,TRIM(NodeID),TRIM(NodeText))
          oNode.Bold = .T.
        ELSE
          oNode = .ADD(TRIM(ParentID),tvwChild,TRIM(NodeID) ,TRIM(NodeText))
          IF OCCURS('\',oNode.FULLPATH)=1
            oNode.BACKCOLOR = 0x00FFFF
            oNode.FORECOLOR = 0xFF0000
          ENDIF
          IF OCCURS('\',oNode.FULLPATH)=2
            oNode.FORECOLOR = 0x0000FF
          ENDIF
        ENDIF
      ENDSCAN
    ENDWITH
  ENDPROC

  PROCEDURE PixelToTwips
    LOCAL liHDC, liPixelsPerInchX, liPixelsPerInchY
    #DEFINE cnLOG_PIXELS_X 88
    #DEFINE cnLOG_PIXELS_Y 90
    #DEFINE cnTWIPS_PER_INCH 1440

    DECLARE INTEGER GetActiveWindow IN WIN32API
    DECLARE INTEGER GetDC IN WIN32API INTEGER iHDC
    DECLARE INTEGER GetDeviceCaps IN WIN32API INTEGER iHDC, INTEGER iIndex

    liHDC = GetDC(GetActiveWindow())

    liPixelsPerInchX = GetDeviceCaps(liHDC, cnLOG_PIXELS_X)
    liPixelsPerInchY = GetDeviceCaps(liHDC, cnLOG_PIXELS_Y)

    THIS.nxtwips = ( cnTWIPS_PER_INCH / liPixelsPerInchX )
    THIS.nytwips = ( cnTWIPS_PER_INCH / liPixelsPerInchY )
  ENDPROC

  PROCEDURE TreeView.MOUSEMOVE
    LPARAMETERS BUTTON, SHIFT, x, Y
    WITH THISFORM
      oHitTest = THIS.HitTest( x * .nxtwips, Y * .nytwips )
      IF TYPE("oHitTest")= "O" AND !ISNULL(oHitTest)
        WAIT WINDOW NOWAIT oHitTest.FULLPATH
      ENDIF
    ENDWITH
    oHitTest = .NULL.
  ENDPROC

  PROCEDURE TreeView.NodeClick
    LPARAMETERS oNode
    LOCAL aNodeInfo[1]
    IF ALINES(aNodeInfo,oNode.KEY,1,'_') = 2 && Customer or orders
      IF LOWER(aNodeInfo[1]) == 'customer'
        SELECT * FROM customer WHERE cust_id = aNodeInfo[2]
      ELSE
        SELECT * FROM orders WHERE VAL(order_id) = VAL(aNodeInfo[2])
      ENDIF
    ELSE
      SELECT * FROM orditems ;
        WHERE VAL(order_id) = VAL(aNodeInfo[2]) AND line_no = VAL(aNodeInfo[3])
    ENDIF
  ENDPROC
ENDDEFINE
Gracias Cetin por compartir y autorizar esta publicación.

Utilizando el control TreeView (1/4)

Primera parte de una serie de códigos de ejemplos sobre como utilizar el control TreeView en VFP, escritos por el turco Cetin Basoz (Microsoft Visual FoxPro MVP 1999-2010).
#DEFINE tvwFirst 0
#DEFINE tvwLast 1
#DEFINE tvwNext 2
#DEFINE tvwPrevious 3
#DEFINE tvwChild 4

oForm = CREATEOBJECT('myForm')
WITH oForm
  .ADDOBJECT('Tree','myTreeView')
  .ADDOBJECT('Lister','Lister')
  WITH .Tree
    .Nodes.ADD(,0,"root1",'Main node 2')
    .Nodes.ADD(,0,"root2",'Main node 3')
    .Nodes.ADD('root1',4,"child11",'Child11')
    .Nodes.ADD('root1',4,"child12",'Child12')
    .Nodes.ADD('root2',4,"child21",'Child22')
    .Nodes.ADD('child21',3,"child20",'Child21')
    .Nodes.ADD('child11',4,"child111",'child113')
    .Nodes.ADD('child111',3,"child112",'child112')
    .Nodes.ADD('child112',3,"child113",'child111')
    .Nodes.ADD('root1',3,"root0",'Main node 1')
    .VISIBLE = .T.
  ENDWITH
  .Lister.LEFT = .WIDTH - .Lister.WIDTH
  .Lister.VISIBLE = .T.
  .SHOW()
ENDWITH
READ EVENTS

DEFINE CLASS myForm AS FORM
  AUTOCENTER = .T.
  HEIGHT = 640
  WIDTH = 800
  PROCEDURE QUERYUNLOAD
    CLEAR EVENTS
  ENDPROC
ENDDEFINE

DEFINE CLASS myTreeView AS OLECONTROL
  OLEDRAGMODE = 1
  OLEDROPMODE = 1
  NAME = "OleTreeView"
  OLECLASS = 'MSComCtlLib.TreeCtrl'
  HEIGHT = 600
  WIDTH = 700

  PROCEDURE INIT
    WITH THIS
      .linestyle =1
      .labeledit =1
      .indentation = 5
      .PathSeparator = '\'
    ENDWITH
  ENDPROC

  PROCEDURE NodeClick
    *** ActiveX Control Event ***
    LPARAMETERS NODE
    NODE.ensurevisible
    MESSAGEBOX(NODE.FULLPATH,TRANS(NODE.INDEX))
  ENDPROC

  PROCEDURE _SubNodes
    LPARAMETERS tnIndex, tnLevel
    LOCAL lnIndex
    lcFs = ''
    WITH THIS
      ? IIF(tnLevel=0,'',REPLICATE(CHR(9),tnLevel))+.Nodes(tnIndex).TEXT, "[Actual index :"+TRANS(tnIndex)+"]"
      IF .Nodes(tnIndex).Children > 0
        lnIndex  = .Nodes(tnIndex).CHILD.INDEX
        ._SubNodes(lnIndex,tnLevel+1)
        DO WHILE lnIndex # .Nodes(tnIndex).CHILD.LastSibling.INDEX
          lnIndex = .Nodes(lnIndex).NEXT.INDEX
          ._SubNodes(lnIndex,tnLevel+1)
        ENDDO
      ENDIF
    ENDWITH
  ENDPROC
ENDDEFINE

DEFINE CLASS lister AS COMMANDBUTTON
  CAPTION = 'Listado'
  HEIGHT = 32
  WIDTH = 100

  PROCEDURE CLICK
    ACTIVATE SCREEN
    CLEAR
    LOCAL lnIndex
    WITH THISFORM.Tree
      lnIndex = .Nodes(1).Root.FirstSibling.INDEX
      ._SubNodes(lnIndex,0)
      DO WHILE lnIndex # .Nodes(1).Root.LastSibling.INDEX
        lnIndex = .Nodes(lnIndex).NEXT.INDEX
        ._SubNodes(lnIndex,0)
      ENDDO
    ENDWITH
  ENDPROC
ENDDEFINE
Gracias Cetin por compartir y autorizar esta publicación.

28 de marzo de 2008

ActiveFiX

Artículo original: ActiveFiX
http://www.foxpert.com/knowlbits_200801_1.htm
Autor: Christof Wollenhaupt
Traducido por: Ana María Bisbé York 


Los controles ActiveX no trabajan bien con formas modales de VFP. Bien, esto no es exactamente una gran sorpresa para nadie que lo haya intentado. Si contacta con el creador de un control ActiveX terminará recibiendo una de estas dos respuestas:

"Nuestros controles funcionan en todos los entornos soportados. ¿Qué es Visual FoxPro?" o "Váyase por ahí. Nosotros no escribimos código deficiente" Incluso los controles que funcionan bien con VFP, como los controles del dbi muestran este comportamiento.

¿No podemos hacer que los proveedores solucionen esto? Admitámoslo, no es su error, es un problema de Visual FoxPro. En realidad no es un error, es un problema de diseño. Uno de los problemas que tiene que resolver el FoxTeam con las ventanas modales que simplemente no existe en Windows API. OK, lo vemos; pero esto no significa que es real.

Windows crea una ventana modal inhabilitando la ventana padre. Esto trabaja bien en un único nivel de ventanas modales como es el caso de una ventana de diálogo. Sin embargo, con ventanas (o formularios) que pueden ser modales, puede ser con formas múltiples en un conjunto de formulario que mantenga accesible mientras otras ventanas no, las cosas se tornan más confusas.
Entonces, lo que ocurre con los controles ActiveX es que realmente se agregan dos ventanas al formulario. La más afuera es la ventana del OLE que guarda la ventana que es un control de Visual FoxPro. Dentro de esta ventana anfitriona, el control ActiveX crea su(s) propia(s) ventana(s) que se mantienen bajo el control del control ActiveX. La ventana interior (o ventanas) es lo que conocemos como "control ActiveX".

Cuando usted muestra una ventana modal, Visual FoxPro inhabilita la ventana anfitriona de todos los otros formularios. El control ActiveX de dentro permanece activo; pero no recibe ninguna entrada de usuario, porque la ventana padre está deshabilitada. Como resultado, el control no responde al ratón ni a eventos de teclado, ni recibe el foco. Sin embargo, continúa funcionando. Por ejemplo, se puede actualizar a sí mismo, responder a otros eventos y cosas así.

Una vez que el usuario cierra la ventana modal, Visual FoxPro activa todas las ventanas anfitrionas que fueron desactivadas. Mi apuesta (en realidad lo que yo creo) es que Visual FoxPro guarda el estado anterior cuando inhabilita la ventana OLE y lo restablece cuando la habilita. El control recibe el mensaje y responde las entradas de usuario. Bueno, esto es lo que ocurre justo en el primer nivel de los formularios modales.

Sin embargo, parece que hay solamente una variable para mantener el estado anterior habilitado para cada ventana OLE anfitriona. Entonces cuando se lanza el segundo formulario modal, Visual FoxPro guarda el estado de la ventana inhabilitada y la inhabilita nuevamente. Cuando se cierra el formulario, este estado inhabilitado comienza a ser restaurado. Al final, la ventana anfitriona permanece inhabilitada, incluso cuando todas las formas modales fueron cerradas.

La forma que existe para solucionar esto - desafortunadamente - no es genérica. Cuando se dispara el evento Activate de un formulario usted sabe que el formulario va a responder a las entradas del usuario. En este punto ninguno de los controles ActiveX del formulario actual deberían estar inhabilitados. Puede asegurarse de esto ejecutando el siguiente código para cada control ActiveX que tenga en su formulario.
Declare Long GetParent in Win32API Long
Declare Long EnableWindow in Win32API Long, Long
EnableWindow(GetParent(oleControl.Hwnd), 1)
Donde oleControl es una referencia regular al control ActiveX del formulario ( por ejemplo Thisform.oleControl1). La propiedad HWND no es una propiedad estándar que ofrezca VFP. Es problema del control ActiveX para proveer del controlador de ventana y toma el nombre de la propiedad o el método. Necesita referirse a la documentación del control ActiveX.


Nota del editor: Carlos Alloatti nos indica una corrección realizada para este artículo en: ActiveX generic fix.

¡ Gracias Carlos !

19 de diciembre de 2007

Cómo saber si un ActiveX ya fué registrado

A veces distribuimos ActiveX (archivos OCX), los cuales es necesario registrar en Windows para poder utilizarlos, pero cómo averiguar si ya lo está para evitar su registro cada vez que se ejecute el sistema o registrarlo si es necesario.

Determinar si un ActiveX esta registrado llamando la siguiente función:
? OcxRegistrado("mscomctl2.monthview.2") && MontView
? OcxRegistrado("mscomctl2.dtpicker.2") && Date Time Picker
? OcxRegistrado("mscomctllib.treectrl.2") && Treeview
? OcxRegistrado("mschart20lib.mschart.2") && Ms Chart
? OcxRegistrado("mscommlib.mscomm.1") && MsComm

FUNCTION OcxRegistrado(cClase)
    Declare Integer RegOpenKey In Win32API ;
        Integer nHKey, String @cSubKey, Integer @nResult
    Declare Integer RegCloseKey In Win32API ;
        Integer nHKey
    nPos = 0
    lEsta = RegOpenKey(-2147483648, cClase, @nPos) = 0
 
    If lEsta
        RegCloseKey(nPos)
    Endif

    Return lEsta
Endfunc
Los archivos OCX tienen una referencia o nombre interno, para averiguar cual es agregamos este a un formulario de VFP, lo seleccionamos y revisamos la propiedad OleClass y ese será el nombre que utilizaremos.

Para registrar el OCX puede ser:

1) Directamente desde la opción Run/Ejecutar del botón inicio de Windows:
REGSVR32 <ArchivoOCX>
2) Desde Fox con macro de sustitución:
cRun="REGSVR32 <ArchivoOCX>"
!&cRun
3) Con la rutina de Jorge Mota:
DECLARE INTEGER DLLSelfRegister IN "Vb6stkit.DLL" ;
   STRING lpDllName
=DLLSelfRegister(<ArchivoOCX>)
-- REGISTRAR Y DESREGISTRAR UN ARCHIVO OCX O DLL --
http://comunidadvfp.blogspot.com/2002/08/registrar-y-desregistrar-un-archivo-ocx.html

Saludos.

Jesus Caro V

8 de junio de 2007

Registrar OCX / DLL en Windows Vista

Antes de Windows Vista cada vez que necesitabamos registrar una libreria ActiveX (OCX, DLL) o un EXE lo podiamos hacer de diversas maneras, una de las comunes era simplemente utilizando el comando REGSVR32 de la suiguiente manera...
regsvr32 [/u] [/s] [/n] [/i[:líneaDeComandos]] nombrelibreria.DLL o OCX
Parámetros:
  • /u: Elimina el registro del servidor.
  • /s: Especifica que regsvr32 se ejecute sin interfaz y que no presente ningún cuadro de mensaje.
  • /n: Especifica que no se invoca DllRegisterServer. Esta opción se tiene que utilizar con /i.
  • /i: líneaComandos Invoca DllInstall y le pasa una [líneaComandos] opcional. Cuando se utiliza con /u, activa la desinstalación de .dll.
  • nombrelibreria.DLL o OCX: Especifica el nombre del archivo .dll que se va a registrar.
  • /?: Muestra Ayuda en el símbolo del sistema.
Al tratar de instalar librerias OCX y/o DLL's en Windows Vista (Asi tambien como arhivos .EXE que necesitan ser registrados) nos damos con la desagradable sorpresa que no se puede, el SO reporta un mensaje de error que dice mas o menos :

Se cargo el modulo Libreria.ocx pero se produjo un error en la llamada a dllRegisterserver (codigo de error: 0x80004005)

Otro error encontrado es:

"Unexpected error; quitting"

Al empezar a indagar con ese problema me di con que los usuarios de VB6 y anteriores tienen el mismo problema asi que e aqui la solucion:

El problema radica fundamentalmente en que Windows Vista hace mucho mas incapie en la seguridad del sistema y ya que cualquiera de estos tipos de archivos son potencialmente peligrosos, a menos que el ususario actual sea el Admnistrador no permitira la registración de estos archivos.

Por lo tanto una solucion es loguearse en el sistema como administrador (Primero debemos activar este usuario ya que por defecto viene deshabilitado y al mismo tiempo desactivar el UAC (User Account Control), todo esto se hace dentro del Panel de control/Control de Usuarios (Debemos aclarar que no alcanza que el usuario actual tenga perfil de administrador debemos logueaes especificamente con la cuenta Administrador o Administrator en su version inglesa)

Una vez hecho esto ya podremos instalar las librerias tal como lo haciamos antes.

Una solucion alternativa y mas rapida es:
  1. Clicear en Inicio
  2. En "Iniciar busqueda" o "Start Search" tipear cmd
  3. Una vez encontrado el icono de cmd en el menu
  4. click derecho en el icono del cmd (command)
  5. elegir la opcion "Run as Administrator" ("Ejecutar como Administrador")
  6. Ir a la carpeta en donde se encuentran las librerias
  7. Tipear nomlibreria.ext /regserver o REGSVR32 nomlibreria.ext (En donde .ext seria OCX/DLL o EXE según el caso)
Esto solucionara el problema y nos permitira probar nuestros sistemas/librerias en el nuevo SO de M$
Para mas información sobre seguridad en windows Vista:

http://technet2.microsoft.com/WindowsVista/en/library/0d75f774-8514-4c9e-ac08-4c21f5c6c2d91033.mspx?mfr=true

Daniel Salazar
www.ZondaSoftware.com.ar
Salta - Argentina

8 de agosto de 2006

Conociendo Zip Component

En este artículo vamos a conocer un componente ActiveX freeware que puede comprimir / descomprimir fácilmente un archivo o carpeta con una sola línea de código. Su nombre es "Zip Component" de Belus Technology Inc.

Introducción

Con esta utilidad se puede comprimir y descomprimir archivos y carpetas muy facilmente desde Visual FoxPro. A continuación vamos a conocer los métodos del componente y algunos ejemplos de uso con código VFP.

El enlace para descargar este componente es el siguiente:

https://web.archive.org/web/20200214062545/http://xstandard.com/en/downloads/?product=zip

Instalación

Para su instalación de debe copiar el archivo "XZip.dll" descargado en una carpeta (Ej: "C:\ZipComponent\") y desde la consola de comandos (DOS), en el directorio creado, ejecutamos: "regsvr32 XZip.dll".

En el caso de querer desinstalar el componente, ejecutamos desde la consola de comandos: "regsvr32 -u XZip.dll"

Métodos

Estos son los métodos y sus sintaxis:

Pack: Agrega un archivo o carpeta a un archivo ZIP. El nivel de compresión puede ser de 1 a 9. El valor por omisión es 6.
Pack(cRutaArchivo, cArchivoZip, lAlmacenaRuta, cNuevaRuta, nNivelCompresión)
UnPack: Extrae el contenido de un archivo ZIP de una carpeta.
UnPack(cArchivoZip, cRutaCarpeta, cPatron)
Delete: Elimina un archivo de un archivo ZIP.
Delete(cArchivo, sArchivoZip)
Move: Mueve o renombra un archivo en el archivo ZIP.
Move(cDeArchivo, cAArchivo, cArzhivoZip)
Contents: Recibe en un objeto la lista de archivos y carpetas de un archivo ZIP.
Contents(cArchivoZip)
El objeto Items recibido contiene las siguientes propiedades:
  • Count: Retorna la cantidad de miembros de la colección
  • Item: Retorna un miembro específico de la colección.
La clase Item contiene las siguientes propiedades:
  • Name: Nombre del archivo
  • Date: Fecha última modificaión
  • Path: Ruta relativa del archivo
  • Size: Tamaño en bytes del archivo
  • Type: Tipo del item: 1=Carpeta y 2=Archivo

Propiedades

ErrorCode: Retorna el código de error de la última operación.
ErrorDescription: Retorna la descripción del código de error de la última operación.
Version: Retorna la versión del producto.

Ejemplos en VFP

Veremos algunos ejemplos en código de Visual FoxPro, y lo fácil de su uso:

Comprimir archivos:
loZip = CREATEOBJECT("XStandard.Zip")
loZip.Pack("C:\Prgs\Prog1.prg", "C:\Zips\Programas.zip")
loZip.Pack("C:\Prgs\Prog2.prg", "C:\Zips\Programas.zip")
loZip.Pack("C:\Prgs\Prog3.prg", "C:\Zips\Programas.zip")
loZip = NULL
Comprimir archivos con la ruta por omisión:
loZip = CREATEOBJECT("XStandard.Zip")
loZip.Pack("C:\Prgs\Prog1.prg", "C:\Zips\Programas.zip", .T.)
loZip = NULL
Comprimir archivos con una ruta específica:
loZip = CREATEOBJECT("XStandard.Zip")
loZip.Pack("C:\Prgs\Prog1.prg", "C:\Zips\Programas.zip", .T., "VFP\Original")
loZip.Pack("C:\Prgs\Prog1.prg", "C:\Zips\Programas.zip", .T., "VFP\Copia")
loZip = NULL
Comprimir multiples archivos usando comodines:
loZip = CREATEOBJECT("XStandard.Zip")
loZip.Pack("C:\Prgs\*.prg", "C:\Zips\Programas.zip")
loZip = NULL

7 de agosto de 2006

Como enviar un email desde VFP sin MAPI

Este es siempre un tema recurrente en el grupo, y como he respondido varias consultas privadas sobre este mismo tema, creo que es hora de hacerlo un poco mas públicamente.

Una forma de enviar un email desde VFP sin lidiar con los problemas de MAPI, Outlook, OE, etc, es usar un componente de 3ros. Existe uno sumamente funcional y gratuito llamado w3JMail, de la empresa DIMAC (http://www.dimac.net/), el cual funciona perfecto en VFP y no requiere de ningún otro componente instalado.

El componente puede ser descargado desde esta dirección:

http://www.dimac.net/FreeDownloads/v3DlStart.asp?ProductID=5

La ayuda la encontrarán aquí:

http://www.dimac.net/default2.asp?M=Products/MenuCOM.asp&P=Products/w3JMail/start.htm

Y adicionalmente les anexo un pequeño ejemplo de como enviar un email con attachments desde VFP usando el w3JMail.

* Ejemplo de como enviar un email con
* adjuntos usando el componente
* w3JMail de DIMAC
*
* Por: Victor Espina
*
CLOSE ALL
CLEAR ALL
CLEAR
*
*-- Se instancia el componente
*
LOCAL oEmail
oEmail = CREATEOBJECT("JMail.Message")
*
*-- Se activa el logging interno del componente
*   y se desactiva la notificacion de errores
*
oEmail.Logging = .T.
oEmail.Silent = .T.
*
*-- Remitente
*
oEmail.From = "remitente@server.com"
oEmail.FromName = "Nombre del Remitente"
*
*-- Destinatario(s). El 2do parametro es opcional. Se puede
*   invocar el metodo AddRecipient las veces que sea necesario.
*
oEmail.AddRecipient("destinatario@server.com","Nombre del Destinatario")
*
*-- Asunto
*
oEmail.Subject = "Email de prueba con w3JMail"
*
*-- Texto. La propiedad Body es de lectura/escritura. Adicionalmente
*   se puede usar el metodo AppendText() para anadir texto al final
*   del mensaje.
*
*   Para enviar un mensaje en formato HTML, use la propiedad HTMLBody
*   y/o el metodo AppendHTML()
*
oEmail.Body = "Este es un email de prueba enviado programáticamente " + ;
  "usando el componente w3JMail de DIMAC."
*
*-- Adjuntos. Se puede invocar el metodo AddAttachment() tantas veces
*   como sea necesario. El 2do parámetro indica si el archivo adjunto
*   sera incluido dentro del mensaje (in-line Attachment) o no.
*
oEmail.AddAttachment(FULLPATH("mail1.prg"),.F.)
*
*-- Se envia el mensaje. El metodo Send() devuelve .T. si se envio
*   el mensaje correctamente o .F. en caso de un error. La propiedad
*   Log contiene el log del problema ocurrio (si Logging = .T.)
*
*   El metodo Send() acepta como parametro una lista de uno o mas
*   servidores SMTP separados por coma. Es posible indicar un
*   usuario/pwd para cada servidor, usando la sintaxis:
*
*   user:pwd@server
*
LOCAL lOk
lOk = oEmail.SEND("smtp.server.com")
IF lOk
  MESSAGEBOX("Mensaje enviado!")
ELSE
  MESSAGEBOX(eMail.Log)
ENDIF
*

Saludos.

Victor Espina

5 de diciembre de 2005

Solucionar Error: OLE error code 0x80040112: Appropriate license for this class

Este es un error común al momento de trabajar con algunos ActiveX, veremos la forma de solucionar (o por lo menos darle la vuela)...

Cuando se trabaja con los ActiveX que están incluidos dentro de la distribución de VFP, suele pasar un error justo cuando se ejecuta una línea como la siguiente:
Local loWSock, lcIp
loWSock = CreateObject("MSWinsock.Winsock")
lcIp = loWSock.LocalIP
MessageBox(lcIp)

El código anterior funciona correctamente dentro del IDE de VFP, pero cuando se crea un .EXE y éste tiene algún código donde se crea un objeto por medio de las funciones CREATEOBJECT() , NEWOBJECT(), o por medio del método ADDObject marca el citado error.

Por qué pasa eso?

Este error sucede debido a una restricción de los mismos, que para que funcionen en VFP es necesario que los ActiveX estén embebidos ya sea en un formulario o en una clase heredada de OLEControl.

Cómo solucionarlo

Como comentaba anteriormente, es buena práctica crear clases en donde se tenga embebido dicho control, como un ejemplo aquí tiene un código que hace uso del control MSCommonDialog:
frmMyForm = CREATEOBJECT("Form")

FrmMyForm.AddObject("oleObject1","oleComDialObject")
   WITH FrmMyForm.OleObject1
      .SetOptions()
      .showopen()
      ?.FileName
   ENDWITH

DEFINE CLASS oleComDialObject as OLEControl
    OleClass ="MSComDlg.CommonDialog.1"
    PROCEDURE SetOptions
      #define COMMDLOG_DEFAULT_FLAG 0x00080000
      #define COMMDLOG_RO 4
      #define COMMDLOG_MULTFILES 512

      This.Flags = COMMDLOG_DEFAULT_FLAG + COMMDLOG_RO + COMMDLOG_MULTFILES
      This.FileName = "*.dbf"
      This.filter = "DBF Files|*.dbf"
    ENDPROC
ENDDEFINE

Si deseas mayor documentacion Doug Hennig tiene un documento que explica a mayor detalle el manejo de ActiveX con VFP:

--- Using Visual FoxPro ActiveX Controls (118K) ---
http://downloads.stonefield.com/pub/axsamp.zip

Y también está documentado en el MSDN de VFP como un Bug:

--- BUG: License Error with ActiveX Control Added at Run-Time ---
http://support.microsoft.com/?scid=192693

Espero les sea de utilidad.

Un agradecimiento a Alex Feldstein por el código de MSCommonDialog

Espartaco Palma Martínez

27 de febrero de 2004

Soluciones para Comprimir Archivos (.ZIP) con Visual FoxPro

Una actividad casi fundamental de las aplicaciones de Base de Datos es hacer respaldos de la información, una de las opciones más viables es dejarlos en formato .ZIP, hay varias maneras de poder hacerlo, aquí te presentamos como...

Para poder crear archivos .ZIP, lo que te recomendaría es utilizar una DLL o ActiveX, aquí te comento sobre varias gratuitas, cada una de ellas tiene su propia documentación.

--- EEVA ZIPMASTER---
http://www.eetasoft.ee/zipmaster.htm

--- SAWZipNG ---
http://users.skynet.be/saw/

Si no te hace falta comprimir con contraseñas, este te puede valer
http://www.xstandard.com/download/x-zip.zip

La documentación:
http://www.xstandard.com/page.asp?p=C9891D8A-5390-44ED-BC60-2267ED6763A7

--- Zip it ---
http://www.ketoan-fas.com/download/zipit.zip

--- Artículo "Comprime/Descomprime ZIPs" de FoxPress ---
http://www.fpress.com/revista/Num1103/Truco.htm


--- ZBit zip-unzip component lite --- [Agregado 29/Marzo/2004]
http://www.zbitinc.com/product.asp?prodid=2

Pero si quieres algo mas profesional puedes optar por las opciones de pago, con esto, se incluyen más métodos, mejores interfaces y claro, el soporte del fabricante:

--- Xceed ZipLibray ---
http://www.xceedsoft.com/products/ZipCompL/

o

--- DynaZip ---
http://www.dynazip.com

Tambien existe uno mas, el compañero E. Paredes lo recomienda:

--- Abale ZIP component ---
http://www.abale.com

Todas las anteriores opciones han sido probadas por la comunidad de Visual FoxPro, es decir, si funcionan con la herramienta, y han sido recopiladas de los mensajes que se mandan al newsgroup de microsoft: microsoft.public.es.vfoxpro.* , así como también un servidor las ha comprobado.

Espero te sirva.

------------------------------------
Espartaco Palma Martínez