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

No hay comentarios. :

Publicar un comentario

Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.