14 de diciembre de 2017

Extraer el oro del XSource - Parte 3

Artículo original: Mining for Gold in XSource - Part 3
http://msdn.microsoft.com/library/en-us/dnfoxtk04/html/ft04l6.asp
Autor: Doug Hennig
Traducido por: Ana María Bisbé York


En las dos primeras partes de esta serie, Doug Hennig examinó algunos componentes interesantes de XSource, el código fuente de la mayoría de herramientas "Xbase" que vienen con VFP.  Este último artículo examina dos componentes: una clase como región con desplazamiento y un framework para la creación de generadores propios.

Como he descrito en los dos artículos anteriores, el código fuente para casi todas las herramientas XBase viene con VFP en un archivo .ZIP: XSource.ZIP en la carpeta Tools\XSource debajo de la carpeta raíz de VFP. Al descomprimir este archivo obtenemos como resultado una carpeta llamada VFPSource que queda debajo de Tools\XSource que contiene el código fuente para las herramientas XBase.

En la Parte 1, he comentado sobre algunas de las imágenes que se incluyen en el XSource y componentes que puede utilizar para guardar la configuración de los recursos de VFP y para crear menús contextuales orientados a objetos. La Parte 2 describió cómo mostrar archivos de videos en formularios VFP, crear diálogos de progreso y utilizar un control que imite la popular barra del control Outlook.

En este artículo, el último de la serie, voy a mostrar cómo crear una región desplazable en un formulario VFP y examinaré un framework para la creación de generadores propios.

Crear una región con desplazamiento (scrolling)

Puede ser que haya necesitado mostrar controles en una región desplazable. Por ejemplo, suponga que permite que sus usuarios añadan sus propios campos a la aplicación. Esta es una gran cualidad debido a que permite a sus usuarios personalizar la aplicación acorde a sus necesidades. El problema es entonces, cómo mostrar los campos personalizables. Si hay pocos campos, no es problema, porque van a caber en un formulario. Si tiene muchos campos, podría utilizar un formulario con desplazamiento. Sin embargo, ¿qué pasaría si necesita mostrar estos campos en un formulario donde además hay controles no desplazables? En ese caso, necesita una región con desplazamiento con los campos a personalizar dentro de ella.

La herramienta Lista de tareas es una aplicación relativamente poco utilizada que salió con VFP desde VFP 7.0. Permite agregar campos de usuario y mostrarlos en una región con desplazamiento en la página Fields (Campos) del diálogo Task Properties (Propiedades de tareas) (vea Figura 1)

Figura 1

La región con desplazamiento es una instancia de la clase cntScrollRegion en TaskListUI.VCX en la carpeta TaskList XSource. La clase cntScrollRegion consiste en un contenedor (donde irán los controles) y un control Microsoft Flat ScrollBar (necesita distribuir MSCOMCT2.OCX con su aplicación para poder brindar este control activex). Cada control añadido al contenedor (por ejemplo, las etiquetas y cuadros de texto en la Figura 1) es una instancia de una clase desplazable (como edtScrolling), también desde TaskListUI.VCX. Se requieren clases especiales en lugar de clases bases de tal forma que cuando el usuario se mueva de un control al siguiente, la región con desplazamiento se desplace automáticamente si es necesario.

Veamos cómo utilizar estas clases en nuestras propias aplicaciones. Decidí crear campos propios a la tabla Customer en la base de datos de ejemplo Northwind. En lugar de agregar campos a la tabla directamente, lo que causa problemas cuando quiero actualizar la estructura de la tabla cuando instalo una actualización de mi aplicación, he creado una tabla adicional, CustomerFields, que tiene el campo CustomerID que se corresponde con el valor CustomerID de un registros en Customers, además los campos que desee mi usuario agregar. Otra tabla, CustFldDef, define los campos que el usuario va agregando (presumiblemente, yo proporciono un diálogo en el que el usuario puede definir los campos y actualizar su contenido de CustFldDef si fuera necesario). Esta tabla tiene los campos FIELDNAME, FIELDTYPE, FIELDLEN, FIELDCAPTION, ORDER y PICTURE, la que guarda la información con la definición de cada campo de usuario.

SFCustomFieldsForm en SFXSource.VCX, es una clase base basada en formulario con un cntScrollRegion dentro y los botones OK y Cancel.( El botón OK se llama cmdApply, porque algunos métodos en la clase Task List lo requieren). El método Init abre la tabla de campos de usuario (para hacerlo más genérico, el nombre de la tabla, el orden del índice a utilizar, y el nombre del campo clave están contenidos en las propiedades  cCustomFilesTable, cCustomFilesOrder y cCustomFilesKey en lugar de estar escrito en el código) y llama al método SetupCustomFields para configurar la región de desplazamiento.

He aquí el código para SetupCustomFields

local lnSelect, ;
  lcAlias, ;
  lnTop, ;
  lcField, ;
  lcType, ;
  lnLen, ;
  lcCaption, ;
  lcPicture, ;
  lcClass, ;
  lcTemplate, ;
  lcLabel, ;
  loLabel, ;
  loField
with This.cntScrollRegion
  * El control de desplazamiento TaskList requiere que el formulario
  * tenga un miembro Task cuyas propiedades se correspondan 
  * con los nombres de archivos. Entonces, lo creamos
  This.AddProperty('Task', createobject('Empty'))
  * Abrir la tabla que contiene información acerca de 
  * los campos de usuario y recorrer cada registro.
  lnSelect = select()
  select 0
  use (This.cMetaDataTable) again shared order ORDER
  lcAlias = alias()
  lnTop = 10
  scan
    * Tomar información sobre cada campo.
    lcField = trim(FIELDNAME)
    lcType = FIELDTYPE
    lnLen = FIELDLEN
    lcCaption = trim(CAPTION)
    lcPicture = trim(PICTURE)
    lcTemplate = ''
    * Determinar qué clase utilizar para la entrada de datos
    * basado en el tipo de datos. Para los campos Date y DateTime, 
    * puede utilizar las clases oleDateScrolling y oleTimeScrolling, 
    * excepto que ellos no pueden controlar las fechas en blanco.
    do case
      case lcType = 'L'
        lcClass = 'chkScrolling'
      case lcType = 'M'
        lcClass = 'edtScrolling'
      case lcType $ 'NFIBY'
        lcClass = 'txtScrolling'
        lcTemplate = lcPicture
      otherwise
        lcClass = 'txtScrolling'
        lcTemplate = replicate('N', lnLen)
    endcase
    *Si no estamos utilizando una casilla de verificación (checkbox), crear una etiqueta.
    if lcClass <> 'chkScrolling'
      lcLabel = 'lbl' + lcField
      .cntPane.NewObject(lcLabel, 'labScrolling', ;
        home() + 'Tools\XSource\VFPSource\TaskList\' +;
        'TaskListUI.vcx')
      loLabel = evaluate('.cntPane.' + lcLabel)
      with loLabel
        .Top = lnTop
        .Left = 10
        .Caption = lcCaption
        .FontName = This.FontName
        .FontSize = This.FontSize
        .Visible = .T.
        lnTop = lnTop + .Height + 4
      endwith
    endif lcClass <> 'chkScrolling'
    * Crea un control para utilizar entrada de datos para este campo 
    cntPane.NewObject(lcField, lcClass, ;
      home() + 'Tools\XSource\VFPSource\TaskList\' + ;
      'TaskListUI.vcx')
    loField = evaluate('.cntPane.' + lcField)
    with loField
      if lcClass = 'chkScrolling'
        .Caption = lcCaption
      endif lcClass = 'chkScrolling'
      .Top = lnTop
      .Left = 10
      .Visible = .T.
      lnTop = lnTop + .Height + 10
      .ControlSource = This.cCustomFieldsAlias + '.' +;
        lcField
      if inlist(lcClass, 'oleDateScrolling', ;
        'oleTimeScrolling')
        .Font.Name = This.FontName
        .Font.Size = This.FontSize
      else
        .FontName = This.FontName
        .FontSize = This.FontSize
      endif inlist(lcClass ...
      if not empty(lcTemplate)
        .Width = txtwidth(lcTemplate, This.FontName, ;
          This.FontSize) * ;
          fontmetric(6, This.FontName, This.FontSize) + 12
      endif not empty(lcTemplate)
      if lcClass = 'txtScrolling'
        .InputMask = lcPicture
      endif lcClass = 'txtScrolling'
    endwith
    * Agrega una propiedad enlazando el nombre del campo al miembro Task
    addproperty(This.Task, lcField)
  endscan
  * Refresca la región de desplazamiento.
  .cntPane.Height = lnTop
  .SetViewPort()
  * Restablecemos.
  use
  select (lnSelect)
endwith

Comienza agregando una propiedad nueva al formulario llamada Task y colocar en ella un objeto Empty. Las clases Task List requieren que esta propiedad del formulario contenga un objeto cuyas propiedades se correspondan con el nombre en los campos de usuario (esto se utiliza para el enlace con los datos en lugar de los campos de la tabla; pero en realidad nosotros vamos a enlazar directamente a los campos,) así que luego agregaremos las propiedades al objeto Empty para corresponder con sus nombres.

El código abre la tabla que contiene el metadato para los campos de usuario, el nombre de los cuales se guarda en la tabla cMetaData en lugar de ser escrito en el código determina que clase *Scrolling utilizar como control para cada campo basado en su tipo de datos, y si el control no es una casilla de verificación (checkbox), añade una etiqueta a la región de desplazamiento (utilizando la clase lblScrolling). Luego, agrega el control apropiado para el campo y establece varias propiedades incluyendo ControlSource, el que enlaza el control al campo, en la tabla de campos de usuario.

Una propiedad con el mismo nombre del campo, se agregó al objeto Empty contenido en la propiedad Task, como se ha dicho antes. Finalmente, después de que todos los controles fueron agregados, el código establece la altura de la región de desplazamiento, según sea necesario (posiblemente más alta que el formulario si hay muchos controles) y llama al método SetPreviewPort del objeto cntScrollRegion, el que ajusta el tamaño, posición y otros atributos de la barra de desplazamiento, según sea necesario.

Hay un error (bug) en el método SetViewPort de cntScrollRegion, que impide que la barra de desplazamiento aparezca adecuadamente. Comente la siguiente línea de código:

This.oleScrollBar.LargeChange = This.Height - 20

TestScrolling.SCX es un formulario basado en SFCustomFieldsForm, su cCustomFilesTable, cCustomFilesOrder, cCustomFilesKey y cMetaDataTable son establecidos con sus valores apropiados para utilizar las tablas CustomerFileds y CustFldDef. Puede incluso ejecutar este formulario y pasarle un valor CustomrID desde la tabla Customers (por ejemplo "ALFKI") o ejecutar el formulario TestToolBox que hemos explicado en la Parte 2 de esta serie, seleccione el módulo Customers, seleccione un usuario de la lista, y haga clic sobre el botón Edit Custom Fields. La figura 2 muestra cómo el formulario TestScrolling aparece cuando es parcialmente desplazado hacia abajo.

Figura 2

Crear sus propios generadores

La tecnología de generadores existe en VFP desde su liberación. Sin embargo, los generadores son subutilizados, parcialmente debido a que mucha gente piensa que son difíciles de crear. Mientras esto no es realmente cierto, existen muchas cosas que controlar al crear un generador, tales como obtener una referencia a un objeto que será mantenido por el generador, controlar los aspectos de su modalidad (si el generador es no modal, se debe cerrar cuando se cierre el formulario o la clase), y así sucesivamente. Afortunadamente, XSource, una vez más acude a nuestro rescate.

XSource viene con un framework completo para la creación de generadores. No está en su propio directorio; pero en su lugar, en parte de los generadores CursorAdapter y DataEnvironment (en la carpeta DEBuilder hija de la carpeta XSource Wizards). Este framework facilita la creación de generadores propios, ya que controla los siguientes aspectos:

  • Puede ser llamado desde el Builder.APP (incluso como un generador registrado en la tabla Builder en la carpeta Wizards del directorio raíz de VFP, o especificado en la propiedad Builder para un objeto) o como un formulario standalone.
  • Admite diálogos modales y no modales.
  • Controla todos los aspectos internos, tales como mantenimiento de las referencias de objetos a ser mantenidos.
  • Se auto-refresca al activar, entonces al establecerlo en la ventana Propiedades, hace los cambios, y reactiva los cambios al generador y actualiza el generador automáticamente.
  • Se cierra automáticamente si se cierra la clase o el formulario.
  • Controla los cambios revertidos al hacer clic en el botón Cancelar (Cancel).

No voy a detallar aquí cómo trabaja el generador o cómo es su arquitectura; pero lo interesante acerca del generador es que no haya que entender esos aspectos para crear un generador útil. Los únicos detalles que se necesitan conocer son los siguientes:

  • Para crear un generador, subclasear la clase BuilderForm en BuilderControls.VCX. Los controles que utiliza en este formulario se pueden instanciar de otras clases en BuilderControls.VCX ( tales como BuilderLabel o BuilderTextbox); pero ellos no tienen que estar, usted utiliza los controles si desea.
  • La manera más sencilla de especificar qué generador es usado por una clase en particular agregue una propiedad de usuario Builder de la clase y llenar con el nombre de la clase a utilizar como el generador y la biblioteca de clases contenido en el, utilizando el formato: Library, Class.

Vamos a echar un vistazo a un ejemplo, donde el Generador va a ser muy útil. SSFile es una clase en SFCCtrls.VCX que proporciona un control donde un usuario puede escribir un fichero o hacer clic sobre un botón para que se muestre un cuadro de diálogo desde el cual el o ella pueda seleccionar el fichero. Esta clase consiste en una etiqueta, que ofrece un texto para el control, un cuadro de texto, que contiene el nombre del archivo y una instancia del objeto SFGetFile, una clase en SFButton.VCX que muestra un cuadro de diálogo Abrir fichero.

Para utilizar SFFile, simplemente arrástrela a un formulario y establezca los valores para sus propiedades, así cómo para cExtensions (una lista de extensiones permitidas en el cuadro de diálogo), cCaption (Encabezado para el cuadro de diálogo Abrir) y cControlSource (algo que enlaza el control con el generador si se desea). Sin embargo, hay un par de cosas molestas:

  • Para fijar la propiedad Caption de la etiqueta, hay que hacer Clic derecho sobre el objeto SFFile, seleccionar Modificar y luego hacer Clic sobre la etiqueta, ir a la ventana Propiedades, seleccionar la propiedad Caption y escribir el nuevo texto. Por supuesto, tendrá que mover el cuadro de texto y los botones SFGetFile hasta tanto sea necesario para que quepa el texto de la etiqueta y es posible que necesite redimensionar el objeto SFFile si resulta muy pequeño.
  • Si desea alargar o acortar el objeto SFFile, lo primero que hace falta es redimensionarlo, luego colocarse dentro de el y redimensionar el cuadro de texto y mover el botón SFGetFile a la posición adecuada.

Nada de eso es un trabajo agotador; pero puede llevar un tiempo hacerlo. Como todas aquellas pequeñas tareas que toman "pequeños momentos" en su labor diaria. Cualquier cosa que podamos hacer por incrementar nuestra productividad, especialmente para tareas de este tipo, es bienvenida.

He aquí algunos pasos para crear un generador para SFFIle:

1. Como esta clase tiene una propiedad de usuario Builder, no necesita agregar una. Sin embargo, llénela con el nombre de la clase a utilizar como generador y la librería de clases que la contiene, utilizando el formato Library,Class.

2. Cree una subclase de BuilderForm utilizando los nombres de la clase y la librería de clases que ha especificado en SSFleBuilde. Establezca Caption igual aSSFFile Builder.

3. Agregue un objeto BuilderLabel y establezca su Caption igual a \<Label caption.

4. Agregue un objeto BuilderTextbox  al lado de la etiqueta, haga su ControlSource para Thisform.oSource.lblFile.Caption y coloque el siguiente código en su método Valid.(Thisform.oSource es una referencia a un objeto que será mantenido por el generador, de tal forma que puede acceder a cualquier propiedad o miembro del objeto a través de esa referencia.) Este código automáticamente ajusta las propiedades Left y Width del cuadro de texto en el fichero SSFFile como sea necesario para que quepa el texto de la etiqueta:

with Thisform.oSource
  .txtFile.Left = .lblFile.Width + 5
  .txtFile.Width = .cmdGetFile.Left - .txtFile.Left
endwith

5. Agregue otra BuildLabel y establezca su Caption igual a \<Width.

6. Agregar un objeto BuildSpinner además de una nueva etiqueta, haga su ControlSource igual a Thisform.oSource.Width, y coloque el siguiente código en su método Valid. Este código ajusta la posición del botón SFGetFile y el ancho del cuadro de texto en SFFile para que quepa en el nuevo ancho del control.

with Thisform.oSource
  .cmdGetFile.Left = .Width - .cmdGetFile.Width
  .txtFile.Width = .cmdGetFile.Left - .txtFile.Left
endwith

7. Guardar y cerrar la clase builder (generador).

Para probar este generador nuevo, arrastre el objeto SFFile a un formulario, haga clic derecho y seleccione Builder. Cambie el texto de la etiqueta y la propiedad Width y vea que el objeto SSFile se ajusta adecuadamente. La figura 3 muestra cómo se ve este generador en acción.

Figura 3

Vea que el framework del generador ha cambiado algo con la liberación de VFP 8.0, así que con la versión actualizada (la que viene con VFP 9.0) se incluye en el archivo Dowload que acompaña este escrito.

Conclusiones

Es interesante que muchas de las herramientas que vienen con VFP fueron escritas en VFP y lo mejor es que tenemos el código fuente de ellas. Esto permite cambiar estas herramientas para lograr nuestras necesidades o incluso reutilizar algo del código de estas herramientas en sus propias aplicaciones. En esta serie de tres partes, he detallado algunos de los componentes de XSource que creo son útiles; pero lo invito a que bucee por si mismo en los directorios XSource para que vea la cantidad de técnicas útiles que puede encontrar.

Descarga

Download 412HENNIG.ZIP

8 de diciembre de 2017

Extraer el oro del XSource - Parte 2

Artículo original: Mining for Gold in XSource - Part 2
http://msdn.microsoft.com/library/en-us/dnfoxtk04/html/ft04k8.asp
Autor: Doug Hennig
Traducido por: Ana María Bisbé York


En la primera parte de esta serie, Doug Hennig examinó algunos componentes interesantes en XSource, el código fuente de la mayoría de herramientas "Xbase" que vienen con VFP.  El artículo de este mes continúa escarbando por el código que usted puede utilizar en sus propias aplicaciones.

Como he descrito en el artículo del mes pasado, el código fuente para casi todas las herramientas XBase viene con VFP en un archivo .ZIP: XSource.ZIP en la carpeta Tools\XSource debajo de la carpeta raíz de VFP. Al descomprimir este archivo obtenemos como resultado una carpeta llamada VFPSource que queda debajo de Tools\XSource que contiene el código fuente para las herramientas XBase. En la Parte 1, he comentado sobre algunas de las imágenes que se incluyen en el XSource y componentes que puede utilizar para guardar la configuración de los recursos de VFP y para crear menús contextuales orientados a objetos. Este mes, voy a mostrar otros componentes que puede utilizar en sus propias aplicaciones.

Mostrar archivos de video.

Cuando elimina o mueve un grupo de archivos, Explorador de Windows despliega un diálogo muy agradable que muestra una versión animada de qué es lo que se está haciendo. Esto no hace que el proceso sea más rápido; pero al menos permite conocer que el sistema no se ha quedado parado mientras procesa la acción.

Hace varias versiones, VFP salió con archivos animados en la carpeta Graphics\Videos. Puede utilizar estos videos para representar el mismo tipo de diálogo de proceso que tiene Explorador de Windows. La clase cAnimation en FoxRef.VCX (en la carpeta FoxRef XSource) hace esta labor de forma sencilla. Arrastre simplemente la clase a un formulario y llame a su método Load, pasando el nombre del archivo de video a mostrar. Si desea cargar el archivo; pero no ejecutarlo inmediatamente, establezca lAutoPlay = .F., llame a Load para que cargue el archivo y luego llame a Play cuando esté listo para ejecutar el video. La clase cAnimation es una subclase de Microsoft Animation Control, así que es necesario distribuir este control ActiveX (MSCOMCT2.OCX) con su aplicación.

Para hacerlo aun más fácil de usar, he creado una subclase de cAnimation llamada SFAnimation en SFXSource.VCX. La subclase tiene una propiedad cFileName que contiene el nombre del archivo de video, y su método Init carga al Load, pasándole This.cFileName. Entonces, simplemente arrastro el control SFAnimation en un formulario, establezco cFileName y ya está listo.

Para ver un ejemplo de este control, ejecute TestAnimation.SCX, incluido en el archivo Download de este mes. Ejecuta seis archivos de video que vienen con VFP, mostrando animaciones similares a las que ve en el diálogo del Explorador de Windows. La figura 1 muestra la apariencia de este formulario durante la ejecución.

Figura 1

Diálogo de progreso

Mostrar un  video animado no siempre brinda al usuario suficiente información sobre el progreso de un proceso de larga duración. Muchas veces, los usuarios desean verificar el progreso de procesos de múltiples pasos o desean ver un termómetro que indique cuán largo ha sido un proceso. La clase cProgressForm de FoxToolbox.VCX en la carpeta ToolBox XSource brinda una barra de progreso muy agradable que puede utilizar para una tarea de este tipo ( Existe también una versión de esta clase en FoxRef.VCX en la carpeta FoxRef XSource; pero la versión ToolBox tiene un poco más de posibilidades.)

Como puede ver en la figura 2, cProgressForm tiene varias posibilidades:

  • Una imagen animada - el PC con la lente de aumento - que indica que algo está ocurriendo (no es un archivo de video, es un GIF animado).
  • Una descripción general del proceso.
  • Un termómetro que indica el progreso del proceso.
  • Un mensaje de estado que muestra qué paso del proceso se está ejecutando actualmente.
  • Un botón cancelar, que permite al usuario terminar el proceso.

Figura 2

Cuando instancia cProgressForm, puede pasarle opcionalmente la descripción del proceso (algo como "Recuperando datos") para establecer el valor de Caption de la etiqueta Description. Puede además, establecer la descripción llamando al método SetDescription. En la medida que va progresando el proceso, puede querer actualizar el Caption de la etiqueta Status debajo del termómetro para indicar en qué paso se encuentra, pase la cadena deseada al método SetStatus.

Si desea que el termómetro muestre valores absolutos, tales como registro actual de todos los registros existentes, en lugar del porcentaje de ejecución, establezca el valor máximo para el termómetro llamando a SetMax. Si no quiere que se muestre el botón Cancel, establezca la propiedad lShowCancel a .F.. Si no desea que se muestre el termómetro, establezca la propiedad lShowThermometer a .F.

Para actualizar el termómetro, llame al método SetProgress, pase el valor al termómetro (ya sea como un valor absoluto si llama a SetMax o como un valor porcentual, por ejemplo, 50 cuando el proceso está cumplido a la mitad) y una cadena opcional para utilizar como el Caption para la etiqueta Status, SetProgress devuelve .T. si el proceso debe continuar o .F. si se debe detener, debido a que el usuario ha presionado el botón cancelar.

cProgressForm tiene un par de comportamientos que yo deseaba modificar. Primero, debemos manualmente llamar al método Show para mostrar el formulario. Yo preferiría que lo mostrara automáticamente si es necesario la primera vez que es llamado SetProgress. Segundo, establece una propiedad de usuario nSecond a SECONDS() en el Init. Esta propiedad puede utilizarse para determinar el tiempo total requerido para un proceso; pero, estableciéndolo en el Init puede ser muy pronto. Entonces, he creado una subclase llamada SFProgressForm en SFXSource.VCX. El método Init utiliza DODEFAULT() y establece entonces nSeconds a 0. El método SetProgress simplemente hace Visible = .T. si el formulario no es visible y nSeconds a SECONDS() si fue 0, y utiliza luego DODEFAULT() para el comportamiento habitual.

Uno de los archivos que se incluyen en el archivo Download, TestProgress.PRG, es un ejemplo de que busca los archivos en la carpeta FFC en la carpeta Home de VFP para localizar cadenas (constantes con "_LOC" en el nombre). Esto utiliza SetMax para especificar la cantidad total para procesar, y actualizar el medidor de progreso después de que cada archivo ha sido buscado. Quite los comentarios al código estableciendo lShowCancel o lShowThermometer a .F. para ver que cómo se ve la barra de progreso en cada caso.

* Determinar cuántos ficheros deseamos procesar.
lnFiles = adir(laFiles, home() + 'ffc\*.*')
* Instanciar el diálogo de progreso y dar valor a algunas propiedades.
loProgress = newobject('cProgressForm', 'SFXSource.vcx')
loProgress.SetDescription('Buscando las cadenas localizadas ')
loProgress.SetMax(lnFiles)
*loProgress.lShowCancel = .F.
*loProgress.lShowThermometer = .F.
* Buscar los archivos según las cadenas localizadas,
* actualizando el medidor de progreso según vamos.
lnFound = 0
for lnI = 1 to lnFiles
  lcFile = laFiles[lnI, 1]
  lcFullFile = forcepath(lcFile, home() + 'ffc')
  lcExt = justext(lcFile)
  do case
    case inlist(lcExt, 'H', 'PRG')
      lcContents = filetostr(lcFullFile)
      lnFound = lnFound + occurs('_LOC', ;
        upper(lcContents))
    case lcExt = 'VCX'
      use (lcFullFile) again shared
      lnFound = lnFound + occurs('_LOC', upper(METHODS))
  endcase
  if not loProgress.SetProgress(lnI, 'Verificando ' + ;
    lcFile + '...')
    exit
  endif not loProgress.SetProgress ...
next lnI
* Mostrar los resultados
messagebox(transform(lnFound) + 'instancias encontradas ' + ;
  'en ' + transform(seconds() - loProgress.nSeconds) + ;
  ' segundos.')

Vea que cProgressForm utiliza el control ActiveX Microsoft ProgressBar, entonces necesitará distribuir MSCOMCT.OCX con su aplicación. Además, utiliza un archivo GIF animado, FindComp.GIF en la carpeta BitMaps de la carpeta Toolbox XSource, entonces este archivo sería agregado a su proyecto cuando lo genera.

Barra Outlook

Aun cuando ha sido liberado hace muchos años, Outlook ha influido en la apariencia y el comportamiento de otras aplicaciones. Una de las interfaces qué más ha sido copiada ha sido la de Outlook  Estos controles tienen un conjunto de categorías plegables, cada una de las cuales contienen elementos individuales que pueden seleccionar que determina qué aparece a la derecha del Outlook.

Existen controles ActiveX que se pueden adquirir que simulan la apariencia de la barra Outlook, algunos de los controles trabajan con VFP y muchos otros no lo hacen. Sin embargo, existe una aplicación que viene con VFP que utiliza un control similar, y tenemos el código fuente en el XSource: el Toolbox.

El  Toolbox se agregó en VFP 8 como una vía para propiciar una interfaz similar a Visual Studio.NET para seleccionar controles y otros elementos frecuentemente utilizados. Debido a que no tiene apariencia exacta a Outlook, yo llamaría al control que mantiene las categoría y elementos debajo de cada uno del "toolbox".

Como puede ver en la Figura 3,  la caja de herramientas (toolbox) consta de cuatro áreas de interés (sin contar el texto de ayuda al final). Una categoría representa un conjunto de elementos. Las categorías son como las barras en el toolbox. Solamente una categoría se expande en cada momento, el resto aparece como barra de categoría. Al hacer Clic en una barra, esta se expande, lo que provoca que la que estaba actualmente expandida se contrae.

Figura 3

Una herramienta es un elemento dentro de una categoría. Las herramientas tienen una imagen, un nombre, y un ToolTip. Puede realizar varias acciones en la herramienta: hacer clic, hacer doble clic, arrastrar, etc. Además, dentro de una categoría hay botones de desplazamiento hacia arriba y abajo. No necesita hacer clic sobre estos botones, simplemente desplace el puntero del ratón sobre un botón por un segundo y comenzará a desplazar la lista en la dirección indicada.

Las clases que proporcionan el toolbox están en FoxToolbox.VCX y _Toolbox.VCX en la carpeta Toolbox XSource. La clase de interés en FoxToolbox.VCX  es ToolContainer. Contiene los controles y la lógica para una simple categoría. Un formulario que muestra una caja de herramientas va a contener tantos objetos ToolContainer como categorías haya que mostrar.

La propiedad oToolItem de cada ToolContainer tiene una referencia a un objeto _Category ( o uno de sus subclases, las diferentes subclases son utilizadas por tipos diferentes de categorías en la aplicación VFP Toolbox) desde _Toolbox.VCX, _Category tiene propiedades y métodos para la categoría. Para nuestro uso, _Category no es realmente importante; pero algunos métodos de ToolContainer requieren de que exista.

Cada ToolContainer también mantiene una colección de herramientas, cada una de las cuales es representada por un objeto _Tool (o una de sus subclases, subclases diferentes son utilizadas para diferentes tipos de herramientas en la aplicación Toolbox de VFP) desde _Toolbox.VCX. _Tool tiene propiedades y métodos para una herramienta.

El formulario principal en la aplicación Toolbox se basa en la clase ToolboxForm en FoxToolbox.VCX. De inicio consideré subclasear este formulario; pero desafortunadamente es demasiado dependiente de las tareas relacionadas con la aplicación Toolbox en lugar de un toolbox genérico. Entonces, en su lugar, he creado SFToolboxForm (en SFXSource.VCX) como la base para formularios con una barra de herramientas.

El método ShowToolbox, llamado desde el Init, es el responsable de crear el toolbox.

local loCategories, ;
  lnI, ;
  loCategory, ;
  lcCategory, ;
  loContainer, ;
  loTools, ;
  lnJ, ;
  loTool
with This
  * Tomar una colección de categorías y procesar cada una.
  loCategories = .GetCategories()
  .nCategories = loCategories.Count
  for lnI = 1 to .nCategories
    loCategory = loCategories.Item(lnI)
    * Crear un objeto ToolContainer y establecer sus propiedades.
    lcCategory = 'Category' + loCategory.cID
    .NewObject(lcCategory, 'ToolContainer', ;
      'FoxToolbox.vcx')
    loContainer = evaluate('.' + lcCategory)
    with loContainer
      .SetFont(This.FontName, This.FontSize, '')
      .oToolItem = newobject('_Category', ;
        '_Toolbox.vcx')
      .CategoryID = loCategory.cID
      .Caption = loCategory.cName
      .Width = This.nToolboxWidth
      .Visible = .T.
    endwith
    * Si esta es la primera categoría, fija la altura estándar
    * para las categorías según la altura de esta y hace que sea 
    * la abierta de forma predeterminada
    if lnI = 1
      .nStandardHeight = loContainer.Height
      .cCurrentCategory = loCategory.cID
    endif lnI = 1
    * Toma una colección de herramientas para la categoría actual. 
    * Para cada una, enlaza su método OnClick con nuestro método
    * ToolSelected. La clase toolbox espera su propiedad
    * oEngine referencie un objeto con una propiedad
    * DblClickToOpen, entonces, crea una. Agrega el objeto herramienta
    * al contenedor de categorías.
    loTools = .GetToolsForCategory(loCategory.cID)
    for lnJ = 1 to loTools.Count
      loTool = loTools.Item(lnJ)
      bindevent(loTool, 'OnClick', This, 'ToolSelected')
      loTool.oEngine = createobject('Empty')
      addproperty(loTool.oEngine, 'DblClickToOpen', .F.)
      loContainer.AddTool(loTool)
    next lnJ
  next lnI
  * Ajusta los tamaños de las categorías.
  .ResizeToolbox()
endwith

Este código comienza llamando al método GetCategories para devolver una colección de objetos con información sobre cada categoría. GetCategories es abstracto en esta clase porque el mecanismo exacto para llenar la colección puede variar. El código entonces puede ir a través de la colección, agregando un objeto ToolContainer al formulario de cada categoría. El ancho del ToolContainer se establece en la propiedad nToolboxWidth del formulario, para asegurarse de establecer esta propiedad en una subclase o formulario creado desde esta clase.

Solamente para la primera categoría, el código establece la propiedad nStandardHeight, que contiene el estándar (esto es, cerrado) altura para cada categoría, para la altura del contenedor actual. Además establece cCurrentCategory al ID para esta categoría de tal forma que se abrirá automáticamente en el código que explicaré más tarde.

Luego, el código llama al método GetToolsForCategory, el que es abstracto en esta clase, para devolver una colección de objetos_Tool que representan las herramientas para la categoría especificada. Cuando un usuario hace clic en una herramienta, el toolbox llama al método OnClick en el objeto herramienta apropiado. Decidí controlar los clics en el nivel formulario, entonces, el código utiliza BINDEVENT() para enlazar el método OnClick de cada objeto herramienta del método ToolSelected del formulario. Debido a que varios métodos toolbox esperan que la propiedad oEngine de un objeto herramienta referencie un objeto con una propiedad DblClickToOpen, el código crea un objeto. El objeto herramienta es agregado a la colección de herramientas en la categoría contenedor.

Finalmente, ShowToolbox llama al método Resize Toolbox para abrir la categoría seleccionada (en este caso, la primera) y cierra las otras. Este código no se muestra aquí por razones de espacio, examínelo con toda libertad.

El método SetCategory es llamado desde el código toolbox cuando el usuario hace clic sobre una categoría. Todo este código establece cCurrentCategory al ID para la categoría seleccionada y llama a ResizeToolbox, para controlar la nueva selección.

SFToolboxForm contiene otros tres métodos abstractos: OnRightClick, llamado cuando el usuario hace clic derecho sobre una herramienta, ResetHelpFile, la que establece el archivo Help a uno predeterminado, y SetHelpText, el que, en la aplicación Toolbox, además alguna información acerca de la herramienta seleccionada dentro del área Help al final del formulario. Estos métodos son necesarios debido a que algunos métodos del toolbox lo llaman. Aunque ellos no hacen nada en esta clase, puede implementar el comportamiento apropiado en una subclase.

TestToolbox.SCX es un formulario de ejemplo basados en SFToolboxForm. Esto utiliza una caja de herramientas para mostrar los diferentes módulos en una aplicación contable. Lea tres módulos desde una tabla MODULES, el que contiene registros para las categorías (tales como "Cuentas admisibles" y "Entrada de órdenes") y el módulo debajo de cada uno (como "Usuarios" y "Ordenes") Tiene el siguiente código en GetCategories, el método llamado por ShowToolbox para llenar una colección de categorías.

local loCollection, ;
  loCategory
loCollection = createobject('Collection')
select NAME, ID ;
  from MODULES ;
  where PARENTID = 0 and ACTIVE ;
  into cursor _CATEGORIES
scan
  loCategory = createobject('Empty')
  addproperty(loCategory, 'cName', trim(NAME))
  addproperty(loCategory, 'cID', transform(ID))
  loCollection.Add(loCategory)
endscan
use
return loCollection

Este código llena una colección con objetos que tienen propiedades cName y cID desde las categorías en la tabla MODULES (aquellos registros con ParentID = 0).

GetToolsForCategory llamado además desde ShowToolbox, es responsable para llenar una colección de herramientas para la categoría especificada.

lparameters tcID
local loCollection, ;
  loCategory
loCollection = createobject('Collection')
select NAME, ID, CLASS, LIBRARY, IMAGEFILE ;
  from MODULES ;
  where PARENTID = val(tcID) and ACTIVE ;
  into cursor _MODULES
scan
  loModule = newobject('_Tool', '_Toolbox.vcx')
  with loModule
    .ToolName = trim(NAME)
    .UniqueID = transform(ID)
    .ParentID = tcID
    .ImageFile = textmerge(trim(IMAGEFILE))
    addproperty(loModule, 'cClass', trim(CLASS))
    addproperty(loModule, 'cLibrary', trim(LIBRARY))
  endwith
  loCollection.Add(loModule)
endscan
use
return loCollection

Este código selecciona los registros desde Modules que tienen ParentID conteniendo el ID de la categoría para llenar. Cada objeto en la colección es un objeto _Tool, con sus propiedades establecidas adecuadamente. Este código agrega además un par de propiedades de usuario: la clase y la librería para el contenedor de los controles a mostrar a la derecha del formulario cuando la herramienta es seleccionada.

Gracias a BINDEVENT(), el método ToolSelected es llamado cuando el usuario hace clic sobre la herramienta. ToolSelected agrega un objeto a la derecha del formulario empleando las propiedades cClass y cLibrary de la herramienta seleccionada para indicar que clase utiliza el objeto.

local llLockScreen, ;
  laEvents[1], ;
  loModule, ;
  lcClass, ;
  lcLibrary, ;
  llHaveIt, ;
  lnI, ;
  loControl, ;
  llDesiredModule
with This
  * Bloqueamos la pantalla y no vemos los cambios 
  * hasta que no hemos acabado.
  llLockScreen = .LockScreen
  .LockScreen = .T.
  * Tomamos la clase y la biblioteca que suponemos vamos a utilizar 
  * desde el módulo seleccionado.
  aevents(laEvents, 0)
  loModule = laEvents[1]
  lcClass = lower(loModule.cClass)
  lcLibrary = lower(loModule.cLibrary)
  * Ocultar todos los módulos, menos el seleccionado.
  llHaveIt = .F.
  for lnI = 1 to .ControlCount
    loControl = .Controls[lnI]
    if pemstatus(loControl, 'cAlias', 5)
      llDesiredModule = ;
        lower(loControl.Class) == lcClass
      llHaveIt = llHaveIt or llDesiredModule
      loControl.Visible = llDesiredModule
    endif pemstatus(loControl, 'cAlias', 5) ...
  next lnI
  * Instanciar la clase adecuada si lo necesitamos.
  if not llHaveIt
    try
      .NewObject(lcClass, lcClass, lcLibrary)
      loControl = evaluate('.' + lcClass)
      with loControl
        .Left = 210
        .Width = This.Width - .Left
        .Height = This.Height
        if version(5) >= 900
          .Anchor = 15
        else
          .OnResize()
        endif version(5) >= 900
        .Visible = .T.
        .SetupControls()
      endwith
    catch to loException
      messagebox('Este módulo no se ha definido.')
    endtry
  endif not llHaveIt
  * Refrescar la pantalla.
  .LockScreen = llLockScreen
endwith

La Figura 4 muestra que este formulario de la misma forma que se ejecuta.

Figura 4

Conclusiones

Mostrar archivos de video en formularios de VFP, mostrar un medidor de progreso durante un proceso largo y agregar una barra tipo Outlook a sus aplicaciones son tareas que se simplifican si se utilizan los componentes que vienen con VFP en XSource. En mi próximo artículo veremos como crear una zona con desplazamiento en un formulario y cuán fácil es crear generadores utilizando componentes XSource.

Descarga

Download 411HENNIG.ZIP

2 de diciembre de 2017

Extraer el oro del XSource - Parte 1

Artículo original: Mining for Gold in XSource
https://msdn.microsoft.com/en-us/library/ms947609.aspx
Autor: Doug Hennig
Traducido por: Ana María Bisbé York


Visual FoxPro viene con código fuente para la mayoría de las herramientas Xbase que salen con el producto, incluyendo Class Browser (Examinador de clases), Code References (Referencias de código), Toolbox (Caja de herramientas), y Task Pane (Panel de tareas). El observar el código fuente trae como consecuencia nuevas y poderosas herramientas escritas por los gurús más importantes de VFP. En este artículo, el primero de la serie, Doug Hennig comenta sobre varios archivos en el XSource para mostrar muy buenas ideas y código que se puede utilizar en aplicaciones, por ejemplo: guardar la configuración de usuarios en archivos de recursos y crear menús contextuales orientados a objetos.

Comenzando con la edición de Diciembre 1998, escribí una serie de tres partes en FoxTalk llamada "Mining for Gold in the FFC" que debatía algunos aspectos de la utilidad del código fuente en la carpeta FFC (FoxPro Foundation Classes) hija de la carpeta raiz de VFP. Sin embargo, ¡ VFP viene con mucho más código fuente que eso !

Muchas otras herramientas que vienen con VFP fueron en realidad escritas en VFP en lugar de C++ (lenguaje utilizado para la creación de VFP). Se les conoce como herramientas "Xbase" para distinguirlas de componentes internos. Algunas de las herramientas Xbase están disponibles desde el menú Tools (Herramientas), tales como: Class Browser (Examinador de clases), IntelliSense Manager (Administrador de IntelliSense), Toolbox (Caja de herramientas), Task Pane Manager (Administrador del Panel de tareas), y Report Wizard (Asistente de informes). Otras están disponibles en el contexto de sus sitios apropiados, tales como el Generador de Integridad Referencial, Generador de CursorAdapter, y en VFP 9.0, ReportBuilder.APP, que proporciona los diálogos nuevos para el Diseñador de informes.

Este artículo es el primero en una serie que explora XSource, buscando técnicas interesantes y código fuente que podemos utilizar en nuestras aplicaciones. No voy a explorar todo el código Xsource en esta serie, que tomaría todo un libro. En su lugar, voy a comentar sobre ciertas cosas que son útiles, incluyendo:

  • Cómo trabajan las barras de herramientas estilo Outlook.
  • Cómo utilizar el archivo de recursos FOXUSER como una alternativa a archivos INI o el Registro de Windows.
  • Mostrar archivos AVI en formularios VFP.
  • Crear menús contextuales orientados a objetos.

Descubriendo Xsource

Por varias razones, ahora VFP ha salido con el código fuente de casi todas las herramientas Xbase. (Beautify.APP, que proporciona la funcionalidad para el elemento de menú Beautify (Presentación), es una excepción). Debido a que es mucho código, viene todo dentro de un archivo ZIP: XSource.ZIP en la carpeta Tools\XSource debajo de la carpeta raíz de VFP. Al descompactar esos archivos resulta la carpeta hija de Tools\XSource llamada VFPSource, que contiene el código fuente para las herramientas XBase. La tabla 1 lista el objetivo de cada carpeta en XSource y la localización de cada archivo APP generado en el código.

Tabla1. Objetivo y localización de las aplicaciones creadas con código fuente XSource.

Carpeta XSourceObjetivoLocalización
AddLabelAddLabel applicationTools\AddLabel\AddLabel.APP
BrowserClass Browser y Component GalleryBrowser.APP y Gallery.APP
BuildersMain builder program (delega en su correspondiente generador de APP) y main wizard program (delega en el correspondiente asistente de  APP)Builder.APP y Wizard.APP
Builders\BuildersArchivos comunes utilizados en generadores -
Builders\CmdBldrCommand Group BuilderWizards\CmdBldr.APP
Builders\EditBldrEditBox BuilderWizards\EditBldr.APP
Builders\FormBldrForm BuilderWizards\FormBldr.APP
Builders\GridBldrGrid BuilderWizards\GridBldr.APP
Builders\ListBldrListBox BuilderWizards\ListBldr.APP
Builders\RIBuildrReferential Integrity BuilderWizards\RIBuildr.APP
Builders\StylBldrStyle BuilderWizards\StylBldr.APP
ConvertConverter (convierte archivos a versiones nuevas)Convert.APP
CoverageCoverage ProfilerCoverage.APP
DataExplorer (VFP9)Data Explorer (disponible en Task Pane Manager)DataExplorer.APP
EnvMgrEnvironment Manager (disponible en Task Pane Manager)EnvMgr.APP
FoxCodeIntelliSense ManagerFoxCode.APP
FoxRefCode ReferencesFoxRef.APP
OBrowserObject BrowserObjectBrowser.APP
ReportBuilder (VFP9)Diálogos y otros comportamientos para el Diseñador de informesReportBuilder.APP
ReportOutput (VFP9)Report listeners (for example, XML y HTML)ReportOutput.APP
ReportPreview (VFP9)New report preview dialogReportPreview.APP
TaskListTask ListTaskList.APP
TaskPaneTask Pane ManagerTaskPane.APP
ToolBoxToolBoxToolBox.APP
VFPCleanUtilidad para restaurar archivos de usuarios de VFP y parámetros de registroVFPClean.APP
WebServiceWeb Services PublisherWizards\WebService.APP
Wizards\AutomateArchivos comunes utilizados en los asistentes Graph, Mail Merge, y PivotTable -
Wizards\DEBuilderCursorAdapter y DataEnvironment BuildersWizards\DEBuilder.APP
Wizards\WZAppApplication WizardWizards\WZApp.APP
Wizards\WZCommonArchivos comunes (Common) utilizados en asistentes y generadores-
Wizards\WZFormForm WizardWizards\WZForm.APP
Wizards\WZFoxDocDocumenting WizardWizards\WZFoxDoc.APP
Wizards\WZGraphGraph WizardWizards\WZGraph.APP
Wizards\WZImportImport WizardWizards\WZImport.APP
Wizards\WZIntNetWWW Search Page WizardNo longer included as tool
Wizards\WZMailMail Merge WizardWizards\WZMail.APP
Wizards\WZPivotPivotTable WizardWizards\WZPivot.APP
Wizards\WZQueryQuery WizardWizards\WZQuery.APP
Wizards\WZReportReport y Label WizardsWizards\WZReport.APP
Wizards\WZTableTable y Database WizardsWizards\WZTable.APP y
Wizards\WZDBC.APP
Wizards\WZUpsizeUpsizing WizardWizards\WZUpsize.APP
Wizards\WZWebWeb Publishing WizardWizards\WZWeb.APP

Nota: Algunas otras carpetas de Tools–Analyzer, CPZero, Filer, GenDBC, HexEdit, MSAA, y Test contienen código fuente para las carpetas en estos directorios. Estas herramientas no son accesibles desde los menús de VFP, son herramientas StandAlone disponibles al ejecutarlas en el archivo apropiado en la carpeta apropiada. No voy a comentar sobre estas herramientas en esta serie.

Ventajas del código XSource

¿Qué tiene de bueno tener el código fuente para estas herramientas? Al menos dos cosas:

  • Si una de estas herramientas no hace exactamente lo que deseas, puedes cambiarlo y generar un nuevo archivo APP.
  • Yo siempre gusto de resaltar el trabajo de otros, y las herramientas XBase VFP fueron escritas por algunas de las mejores y más brillantes estrellas del universo VFP. (Muchas de ellas fueron escritas por no-empleados de MS, como Markus Egger, que escribió el Examinador de objetos.) Se han utilizado técnicas muy interesantes en algunas de estas herramientas, y he aprendido mucho explorando el código fuente. Lo más importante, muchas de las clases, imágenes y PRGs son útiles para otras aplicaciones.

Si decide utilizar algo del código fuente XSource en sus propias aplicaciones, sea consciente de que algunas clases y subclases están en diferentes VCX, y algunos de los VCX tienen gran cantidad de clases en ellos. Incluso, si necesita solamente una de ellas, todas las clases relacionadas, PRGs e imágenes se van a incorporar a su proyecto cuando lo genere.

Puede incluso no tener esto en cuenta (el único efecto negativo es el incremento del tamaño del EXE y el espacio del disco es barato en nuestros días) o colocar las clases que desea en un VCX propio. Herramientas como Class Browser y White Light Computing's HackCX (una herramienta anteriormente de Geeks y Gurus sobre la que escribí en mi artículo de Diciembre 2003, "Tools for and by Geeks and Gurus," y altamente recomendada ver:www.whitelightcomputing.com) puede ayudar en esto, aunque puede ser un poco complicado de dominar.

Nota: El archivo de Ayuda de VFP (Help File) es un poco confuso al tratar el tema de reutilizar código desde XSource; pero Microsoft recientemente indicó que es posible utilizar el código de XSource ya que este código ha sido levemente modificado y podemos utilizar incluso bibliotecas de clases enteras si fueron incluidas en el proyecto y en la aplicación generada. Esto es similar a las reglas para utilizar código fuente y bibliotecas de la carpeta Sanples, que se describe en el tema "Características de archivos distribuibles y restringidos de Visual FoxPro"

La riqueza de la fuente de imágenes

Cuando estaba buscando imágenes para los botones o iconos para formularios, uno de los primeros lugares donde fui a buscar fue el Xsource, especialmente la carpeta de las herramientas Xbase más novedosas (aquellas que fueron añadidas en VFP 8 o superior). Con frecuencia encuentro exactamente lo que ando buscando, o a veces, algo que necesito sólo retocar con el Paint. La figura 1 muestra algunas de las imágenes útiles que he encontrado en la carpeta XSource.


Figura 1

Guardar la configuración del archivo de recursos.

La escritura de aplicaciones profesionalmente guarda cierta configuración, de tal forma que la siguiente vez que el usuario lo ejecute, utilice nuevamente la misma configuración. Por ejemplo, es bueno, que una aplicación "recuerde" el tamaño y posición de los formularios, la ubicación desde dónde se importó el último archivo importado, qué tipo de archivo exportado creado la última vez, y así sucesivamente. Hay varios lugares de la aplicación que pueden almacenar estos parámetros, incluyendo archivos INI, el Registro Windows, un archivo XML o una tabla.

El entorno de desarrollo de VFP (IDE) hace un gran trabajo al recordar cosas como el tamaño, posición, y texto seleccionado en los archivos de código y la última ventana abierta en un formulario o clase. Esta configuración se guarda en su archivo de recursos, típicamente FoxUser.DBF en su carpeta HOME(7). ¿No sería magnífico poder utilizar este archivo de recursos para guardar su propia configuración?

Esto es exactamente lo que hace la clase FoxResource en FoxResource.PRG. Esta clase, que se encuentra en varias carpetas incluyendo Toolbox, proporciona métodos para leer de y escribir en un archivo de recursos. Debe ser el archivo actual (esto es, uno devuelto por SYS(2005)); puede especificar el nombre utilizado en la propiedad ResourceFile.

Un archivo de recursos tiene los siguientes campos:

  • TYPE - contiene el tipo de registro. LA mayoría de los registros tienen "PREFW" en este campo; pero puede utilizar cualquier valor que desee para configurar la propiedad ResourceType de FoxResource (el valor predeterminado es "PREFW".
  • ID - El ID del registro. Puede ser cualquier valor que desee; pero debe ser único.
  • NAME - Nombre para el registro. Puede quedar en blanco si lo desea.
  • READONLY - .T. Si el registro no se puede cambiar.
  • CKVAL - El checksum (suma de verificación) para el campo DATA.
  • DATA - Los parámetros. FoxResource guarda sus parámetros como una matriz utilizando SAVE TO MEMO DATA.
  • UPDATED - La fecha de la última actualización del registro.

La clase FoxResource utiliza el método Load para cargar un conjunto de parámetros desde un registro en el archivo de recursos. Pasa al método el ID y el nombre (en correspondencia con los campos ID y NAME en el archivo) y cargará la configuración (en caso de que exista ese registro) en una colección interna de elementos nombre-valor. Una vez que haya cargado el conjunto, puede recuperar los valores pasando al método Get el nombre del parámetro que desea recuperar.

No se tiene que preocupar sobre los tipos de datos, una vez que los parámetros son guardados como una matriz en el archivo de recursos, los valores regresan con el mismo tipo de datos con el que fueron guardados. Hay un cambio interesante en archivos INI, donde todo es una cadena. Si el parámetro que necesita no existe, el método Get devuelve .NULL., entonces probablemente utilizará la función NVL() en el valor devuelto.

He aquí un ejemplo que mantiene lo que viene en Top si no existe este parámetro.

This.Top = nvl(oFoxResource.Get('Top'), This.Top)

Puede verificar si existe un parámetro o no utilizando el método OptionExists. Si le pasa el nombre del parámetro y devuelve .T., entonces existe el parámetro.

Para guardar un parámetro, llame al método Set, pásele el nombre y valor a configurar. Una vez que ha guardado un conjunto de parámetros, puede guardar un conjunto entero al archivo de recursos con el método Save. Este método espera los mismos valores para los parámetros ID y nombre que el método Load. Save además, actualiza las columnas UPDATED y CKVAL y admite registros de sólo lectura en el archivo de recursos rehusando actualizar el registro si el campo READONLY es .T.

Existen otros métodos, menos útiles. Clear limpia la colección, GetData devuelve el contenido del campo memo DATA para el registro especificado, SaveTo guarda en un campo memo especificado en lugar de en el campo memo DATA y RestoreFrom restaura desde un campo memo especificado.

TestMenu.SCX, que se incluye en el archivo de descarga de este mes, demuestra el uso de FoxResource. El método Init instancia FoxResource, carga los parámetros para el nombre TESTMENU, llama a SetFormPosition para restaurar el tamaño y posición del formulario, y entonces, utiliza el método Get de FoxResource para restaurar el puntero de registro y filtro.

local lnRecno, ;
  lcFilter
with This
  .oResourceOptions = newobject('FoxResource', ;
    home() + 'Tools\XSource\VFPSource\Toolbox\' + ;
    'FoxResource.prg')
  .oResourceOptions.Load('TESTMENU')
  .SetFormPosition()
  lnRecno = nvl(.oResourceOptions.Get('RecNo'), 0)
  if between(lnRecno, 1, reccount())
    go lnRecno
  endif between(lnRecno, 1, reccount())
  lcFilter = nvl(.oResourceOptions.Get('Filter'), '')
  .SetFilter(lcFilter)
endwith

Hablando de "chupar descaradamente" - ups!, quiero decir valorar código de otros, el código para el método SetFormPosition viene directamente del método de igual nombre en la clase cFoxForm de la biblioteca ToolboxCtrls.VCX  en la carpeta de código fuente ToolBox. Este código hace mucho más que simplemente recuperar las propiedades del formulario Top, Left, Width, y Height, además se asegura de que estos valores no provoquen que el formulario quede fuera de la pantalla, en caso de que haya sido abierto a la derecha y con una resolución muy alta y luego haya sido abierto con una resolución más baja:

LOCAL nTop, nLeft, nWidth, nHeight
IF !ISNULL(THIS.oResourceOptions)
  m.nHeight = THIS.oResourceOptions.Get("HEIGHT")
  IF VARTYPE(m.nHeight) == 'N' AND m.nHeight >= 0
    THIS.Height = m.nHeight
  ENDIF
  m.nWidth = THIS.oResourceOptions.Get("WIDTH")
  IF VARTYPE(m.nWidth) == 'N' AND m.nWidth >= 0
    THIS.Width = m.nWidth
  ENDIF
  m.nTop = THIS.oResourceOptions.Get("TOP")
  IF VARTYPE(m.nTop) == 'N'
    * asegúrese de que está visible
    IF !THIS.Desktop
      IF m.nTop > _SCREEN.Height
        m.nTop = MAX(_SCREEN.Height - THIS.Height, 0)
      ELSE
        IF m.nTop + THIS.Height < 0
          m.nTop = 0
        ENDIF
      ENDIF
    ENDIF
    THIS.Top = m.nTop
  ENDIF
  m.nLeft = THIS.oResourceOptions.Get("LEFT")
  IF VARTYPE(m.nLeft) == 'N'
    * asegúrese de que está visible
    IF !THIS.Desktop
      IF m.nLeft > _SCREEN.Width
        m.nLeft = MAX(_SCREEN.Width - THIS.Width, 0)
      ELSE
        IF m.nLeft + THIS.Width < 0
          m.nLeft = 0
        ENDIF
      ENDIF
    ENDIF
    THIS.Left = m.nLeft
  ENDIF
ENDIF 

El método Destroy guarda la configuración que deseamos preservar y luego escribe todo eso en el conjunto TESTMENU en el archivo de recursos.

with This
  .oResourceOptions.Set('Left', .Left)
  .oResourceOptions.Set('Top', .Top)
  .oResourceOptions.Set('Height', .Height)
  .oResourceOptions.Set('Width', .Width)
  .oResourceOptions.Set('Filter', .cFilterName)
  .oResourceOptions.Set('RecNo', recno())
  .oResourceOptions.Save('TESTMENU')
endwith

Para probarlo, ejecute TestMenu.SCX y mueva el formulario para otro lugar de la pantalla. Haga Clic derecho sobre el formulario y seleccione una configuración de filtro desde el submenú setfilter y luego haga clic derecho y seleccione Next en varis ocasiones. (Luego vemos cómo fue creado el menú contextual) Cierre este formulario y luego re-ejecútelo; se debe abrir la misma posición con el mismo filtro y el mismo registro que tenía al cerrarlo.

Menús contextuales orientados a objetos

Los menús de sistema de VFP es uno de los pocos aspectos del producto que se mantienen procedural en lugar de basados en objetos. Aunque puede crear sus menús propios utilizando los comandos relativos al menú (tales como DEFINE POPUP y DEFINE BAR), casi nadie lo hace porque VFP incluye el Diseñador de Menú, que permite que cree menús visualmente. Sin embargo, el mayor problema en la utilización de menús  generados por el Diseñador de Menú es que ellos no pueden ser alterados en tiempo de ejecución. Esto significa que no puede realizar búsquedas fácilmente de ellos o mostrar u ocultar barras específicas bajo ciertas condiciones.

¡ ContextMenu llega al rescate ! Esta clase, definida en FoxMenu.PRG en la carpeta de código fuente ToolBox, permite crear un menú contextual al vuelo, sin tener que escribir los tan feos comandos DEFINE POPUP y DEFINE BAR. Simplemente instancie la clase, establezca las barras según su deseo (el que podría ser utilizar código condicional para localizar el menú o decidir qué barras incluir), y dígale que muestre el menú. Podría incluso manipular el menú con datos si desea.

Para agregar barras al menú, llame al método AddMenu. Este método acepta hasta seis parámetros (los últimos cuatro son opcionales): el prompt para la barra, una expresión para ejecutarla cuando se selecciona la barra, el nombre de un archivo imagen a utilizar por la imagen de la barra, .T. si está la marca , .T., si la barra está activa y .T. si la barra aparece en negrita.

Debido a que el menú de sistema de VFP vive fuera de su objeto de sistema, las referencias del tipo This o ThisForm, no van a trabajar en la expresión a ejecutar, entonces, coloque una referencia al formulario u objeto en una variable privada y utilice esa variable en la expresión. El código de ejemplo del archivo Download ilustra esto.

AddMenu devuelve una referencia a un objeto que contiene propiedades sobre la barra de menú:  Caption, Picture, Checked, ActionCode, IsEnabled, y Bold. Es más interesante, sin embargo, que contiene además una propiedad SubMenu que contiene una referencia a otro objeto ContextMenu. Entonces, para crear un submenú para una barra, simplemente llame al método AddMenu del objeto SubMenu.

Para mostrar el menú, llama al método Show. En la versión VFP 8, puede opcionalmente pasar dos parámetros: la fila y la columna en la que se debe mostrar el menú. La versión 9 añade un tercer parámetro, opcional: el nombre del formulario en el cual debe aparecer el menú (ContextMenu  utiliza en la cláusula IN WINDOW del comando DEFINE POPUP en este caso). Si llama a Show sin parámetros, el menú no será colocado a la derecha. En su lugar, pasa cualquiera MROW('') y MCOL('') para la fila y columna y omite el nombre del formulario, u omite la fila y columna y pasa This.Name para el nombre del formulario.

Un bug en el método BuildMenu de la beta pública de VFP 9 de esta clase impide que trabaje del todo bien. Falta un punto y coma luego de "m.nCol" en el siguiente código. Asegúrese de agregarla en su copia de FoxMenu.PRG. (Nota: Debe ser corregido en la versión definitiva de VFP 9.)

DEFINE POPUP (m.cMenuName) SHORTCUT ;
  RELATIVE FROM m.nRow, m.nCol ;
  IN WINDOW (m.cFormName)

ContextMenu  tiene un par de propiedades. MenuBarCount contiene la cantidad de barras en el menú. ShowInScreen  se supone que indique que ele menú sea mostrado en _SCREEN, pero todo el código que referencia esta propiedad se comenta fuera, así que podemos ignorarla.

He aquí un ejemplo, tomado del método ShowMenu de TestMenu.SCX (vea Figura 2). Instancia la clase ContextMenu que llama al método AddMenu para crear varias barras para mostrar. El código que crea las barras de navegación (First, Next, Previous, y Last) pasa la imagen y admite parámetros para que las barras tengan imágenes y están habilitados sólo cuando corresponde. La barra Set Filter tiene un submenú de diferentes tipos de filtros que se pueden establecer, y la barra que se corresponde con el filtro actual tiene su marca activada.


Figura 2

local loMenu, ;
  loMenuItem
private poForm
* Crea el objeto menu.
loMenu = newobject('ContextMenu', ;
  home() + ;
  'Tools\XSource\VFPSource\Toolbox\FoxMenu.prg')
* Crea una referencia privada a este formulario
* para quetrabajen las acciones del menú.
poForm = This
* Define las barras de menú.
loMenu.AddMenu('First', 'poForm.FirstRecord()', ;
  'frsrec_s.bmp', .F., not This.lFirstRecord)
loMenu.AddMenu('Next', 'poForm.NextRecord()', ;
  'nxtrec_s.bmp', .F., not This.lLastRecord)
loMenu.AddMenu('Previous', 'poForm.PreviousRecord()', ;
  'prvrec_s.bmp', .F., not This.lFirstRecord)
loMenu.AddMenu('Last', 'poForm.LastRecord()', ;
  'lstrec_s.bmp', .F., not This.lLastRecord)
loMenu.AddMenu('\-')
loMenuItem = loMenu.AddMenu('Set Filter')
loMenuItem.SubMenu.AddMenu('North American Customers', ;
  'poForm.SetFilter("NA")', '', This.cFilterName = 'NA')
loMenuItem.SubMenu.AddMenu('European Customers', ;
  'poForm.SetFilter("EU")', '', This.cFilterName = 'EU')
loMenuItem.SubMenu.AddMenu('South American Customers', ;
  'poForm.SetFilter("SA")', '', This.cFilterName = 'SA')
loMenuItem.SubMenu.AddMenu('\-')
loMenuItem.SubMenu.AddMenu('Clear Filter', ;
  'poForm.SetFilter("")', '', empty(This.cFilterName))
loMenu.AddMenu('\-')
loMenu.AddMenu('Close', 'poForm.Release()')
* Muestra el menú.
loMenu.Show(, , This.Name)

Conclusiones

Xsource no es solamente código fuente para las herramientas Xbase que vienen con VFP; es además una rica fuente de técnicas y código reutilizable. El próximo mes, voy a comentar sobre técnicas y código que muestran archivos de videos en formularios, mostrando una barra de progreso durante un proceso prolongado y agregando una barra de Outlook a sus aplicaciones.

Download

Download FT04_10Hennig.zip