23 de agosto de 2018

Cerrar aplicación si un usuario esta mucho tiempo sin trabajar

¿Cuantas veces nos hemos encontrado que nuestros usuarios se van a tomar un cafe o marchan a comer con la aplicación abierta?

La verdad, es que a mi esto me há dado bastantes problemas, ya que realizo las copias de seguridad al mediodia. Y día si, día también hay usuarios que dejan la aplicación abierta, con el problema de que la copia no se realiza ya que los usuarios dejan archivos abiertos.

La solución es la siguiente:

crear un timer (p.ejemplo) programado para 15-20 minutos, y el el evento timer programar los pasos necesarios para cerrar la aplicación.

para que el timer no se dispare cuando los usuarios estan trabajando hago lo siguiente:

En el evento mouse move del formulario y en el keypress de cada objeto, hago:

IF VARTYPE(gotimer_inactividad)="O"
 * la cuenta atras de tiempo se inicia otra vez
    gotimer_inactividad.interval = gocsapp.tiempo_espera
ENDIF

En mi caso la parte del formulario la hago solo una vez, ya que todos mis formularios son hijos de uno maestro definido como una clase base.

¿Que codigo se pone para cerrar la aplicación? Depende de lo que desees hacer, es decir puedes no querer cerrar a un usuario si esta en el medio de una grabación,... yo en mi caso les cierro la aplicación descartando cualquier cambio que hayan realizado.

¿Drastico? Si, pero despues de que los usuarios pierden su trabajo dos veces, debido a su negligencia, os aseguro que no volveran a dejar el ordenador indebidamente encendido.

NOTAS:

gotimer_inactividad - es el objeto timer creado al iniciar la aplicación

gocsapp - es una clase general a todas las aplicaciones que desarrollo,
   tiene propiedades y metodos habituales.

gocsapp.tiempo_espera - define el tiempo de espera para lanzar el timer. Es
   por si aumento o disminuyo el tiempo de espera para no tener que cambiarselo
   a todos los objetos. Normalmente ahi le pongo 900000

Pablo Roca

8 de agosto de 2018

Copiar todo el contenido de un GRID a una hoja de cálculo de Excel

A veces puede ser cómodo utilizar VFP para calcular, extraer o preparar una vista, la cual puede ser visualizada utilizando un GRID. Pero no hay duda que una hoja de cálculo de Excel ofrece muchas mas posibilidades para elaborar, analizar y estudiar los datos preparados, o simplemente el usuario a veces sabe manejar bien Excel y prefiere usar esta herramienta.

VFP tiene una gran integración con estos sistemas y veremos lo fácil que es copiar el contenido de un GRID en una hoja de Excel.

Lo único que necesitamos es unas pocas líneas de código que podríamos poner, por ejemplo, en el evento click de un botón. Imaginando que el botón esté en el mismo formulario que el grid, el código será el siguiente:

LOCAL cErrores, lExcel

* BUSCO UNA SESION DE EXCEL YA ACTIVA:
cErrores = ON("ERROR")
ON ERROR lExcel = .F.
oExcel = GetObject(,"excel.application")
ON ERROR &cErrores

IF TYPE("oExcel")    * NO ESTABA ACTIVA. PREPARO UNA NUEVA SESION DE EXCEL:
   oExcel = CREATEOBJECT("Excel.Application")
ENDIF
oExcel.VISIBLE = .T.    && VISUALIZO EXCEL
oExcel.Workbooks.ADD    && PREPARO UN NUEVO TRABAJO DE EXCEL

SELE (THISFORM.GRID1.RECORDSOURCE)
GO TOP
nRows = 0
* EMPIEZO A CARGAR LA HOJA DE EXCEL LEYENDO LA ESTRUCTURA DEL GRID:
WITH THISFORM.GRID1
   SCAN
      nRows = nRows + 1
      FOR nColumn = 1 TO .COLUMNCOUNT
         oExcel.Cells(nRows,nColumn) = EVAL(.COLUMNS(nColumn).CONTROLSOURCE)
      NEXT nColumn
   ENDSCAN
ENDWITH

Pablo Roca

20 de julio de 2018

Evitar que un programa activado desde VFP se cargue más de una vez y visualizarla

La misma función que hemos visto en el caso anterior puede ser usada para evitar que un programa externo se cargue mós de una vez. Un ejemplo sencillo es el de la calculadora de Windows.

Imaginemos que en nuestra aplicación demos la posibilidad de utilizar la calculadora. Pondríamos una línea come esta:

RUN /N CALC.EXE

Pero si esta línea la ejecutamos más de una vez, se cargarás la calcuadora una y otra vez.

* Antes de activar la calculadora:
IF NOT F_ActivaWin("Calculadora")
    * La calculadora no está cargada:
    RUN /N CALC.EXE
ENDIF

* Y ESTA ES LA FUNCION QUE LO HACE TODO:
*-----------------------------
FUNCTION F_ActivaWin(cCaption)
*-----------------------------
LOCAL nHWD
DECLARE INTEGER FindWindow IN WIN32API ;
STRING cNULL, ;
STRING cWinName

DECLARE SetForegroundWindow IN WIN32API ;
INTEGER nHandle

DECLARE SetActiveWindow IN WIN32API ;
INTEGER nHandle

DECLARE ShowWindow IN WIN32API ;
INTEGER nHandle, ;
INTEGER nState

nHWD = FindWindow(0, cCaption)
IF nHWD > 0
    * VENTANA YA ACTIVA
    * LA "LLAMAMOS":
    ShowWindow(nHWD,9)

    * LA PONEMOS ENCIMA
    SetForegroundWindow(nHWD)

    * LA ACTIVAMOS
    SetActiveWindow(nHWD)
    RETURN .T.
ELSE
    * VENTANA NO ACTIVA
    RETURN .F.
ENDIF

Pablo Roca

9 de julio de 2018

Evitar que una aplicación se cargue más de una vez y visualizarla en tal caso

En muchos casos es importante que nuestras aplicaciones puedan estar cargadas una sola vez. Los motivos pueden ser muchos: un programa VFP ocupa muchos recursos del sistema, en algunos casos puedo necesitar abrir los archivos en modo exclusivo, etc.

Utilizando las APIs que Windows pone a disposición, se puede controlar si existe una ventana con un nombre determinado. Si nosotros controlamos esto antes de asignar el título a la ventana de nuestro programa, podemos saber nuestra aplicación ya estaba cargada desde antes.

Lo úmico que necesitamos hacer es lo siguiente:

En nuestras primeras líneas de programa ponemos estas líneas de código:

* Antes de ponerle el título a nuestra ventana:
IF F_ActivaWin("Mi programa")
    * El programa ya estaba activo:
    RETURN && Termina el programa
ENDIF

* Empezamos a definir las características de la ventana principal
* siempre y cuando sea necesario visualizarla.
WITH _SCREEN
    * PREPARO LA VENTANA PRINCIPAL
    * .WIDTH = ...
    * .HEIGHT = ...
    * ETC, ETC
    * 
    *
    .CAPTION = "Mi programa"    && Título de la ventana
    .VISIBLE = .T.
ENDWITH

* Y ESTA ES LA FUNCION QUE LO HACE TODO:
*-----------------------------
FUNCTION F_ActivaWin(cCaption)
*-----------------------------
LOCAL nHWD
DECLARE INTEGER FindWindow IN WIN32API ;
STRING cNULL, ;
STRING cWinName

DECLARE SetForegroundWindow IN WIN32API ;
INTEGER nHandle

DECLARE SetActiveWindow IN WIN32API ;
INTEGER nHandle

DECLARE ShowWindow IN WIN32API ;
INTEGER nHandle, ;
INTEGER nState

nHWD = FindWindow(0, cCaption)
IF nHWD > 0
    * VENTANA YA ACTIVA
    * LA "LLAMAMOS":
    ShowWindow(nHWD,9)

    * LA PONEMOS ENCIMA
    SetForegroundWindow(nHWD)

    * LA ACTIVAMOS
    SetActiveWindow(nHWD)
    RETURN .T.
ELSE
    * VENTANA NO ACTIVA
    RETURN .F.
ENDIF

Pablo Roca

30 de junio de 2018

Acceder a objetos y su código dentro de contenedores

Muchas veces, mientras trabajamos con los Diseñadores de clases y formularios, repetimos acciones de búsqueda de controles que se encuentran debajo de uno o varios contenedores, o de búsqueda de código relativo a controles. En este escrito, resumo algunas de las técnicas que nos puedes ayudar a ganar productividad.

Acceder a objetos y su código dentro de contenedores

Hoy vamos a comentar sobre técnicas para lograr un acceso más rápido a controles dentro de contenedores y al código de métodos o eventos de estos controles.

Vamos a suponer que tenemos un contenedor (por ejemplo un formulario), que contiene, digamos un pageframe y en una de sus páginas otro contenedor con otro grupo de controles y etc etc. Cada vez que deseamos posicionarnos en un control determinado tenemos varias variantes, por cierto, todas tediosas.

Podemos, por ejemplo, acceder desde la ventana Propiedades. En la esquina superior existe un cuadro desplegable con todos los controles existentes en el formulario o clase en cuestión. Si nos desplazamos por esta lista desplegada nos podemos colocar directamente en el control deseado. Pero sucede que a veces, como en nuestro caso, todos los controles no caben en esta lista lo cual hace necesario buscar por la lista y luego posicionarnos en el control.

Otra opción es aprovechar las teclas de acceso directo que han sido programadas y vienen de forma nativa con VFP. Así tenemos que al trabajar con los contenedores, tales como el pageframe (marco de página) se puede modificar rápidamente el objeto dentro del contenedor utilizando Ctrl+Clic sobre el objeto. Esto "pasa por encima" del contenedor y permite tomar el control del objeto que está dentro. Si existen contenedores dentro de otros contenedores, puede utilizar Ctrl+Shift+Clic para profundizar aun más en el nivel. Por ejemplo, en nuestro caso, tenemos un control optiongroup en una página del pageframe, entonces podemos acceder a uno de los botones presionando Ctrl+Shift+Clic, y VFP pasa por encima de los controles pageframe, page y optiongroups y va directo al control optionbutton.

Pero la verdad es que, al menos yo, no suelo acordarme nunca de esto. Y he aquí que me he encontrado algo que me parece fenomenal en estos casos en que tenemos muchos controles y mucha herencia de contenedores. Se trata de un árbol de contenedores y es sacado de http://fox.wikis.com/wc.dll?Wiki~ContainerTree

Su autor, Burkhard Stiller, ha presentado un código que nos permite obtener un árbol para acceder directamente a cada objeto de nuestra clase o formulario. Es muy cómodo especialmente si tenemos gran cantidad de objetos o controles dentro de varios niveles de contenedores. Es preciso tener VFP 9.0 para poderlo aplicar.

Se trata sólo de copiar y pegar en un editor de programas y ejecutar siempre que nos sea necesario. Automáticamente rellena el árbol a partir del formulario o clase activa. Según el autor puede encontrar datos que estén dentro de un pageframe, que están a su vez, dentro de otro pageframe y a su vez dentro de otro pageframe, .... así sucesivamente ...De esta forma nos ilustra la profundidad de contenedores que tiene en cuenta. Todos los controles son tenidos en cuenta, sin importar si tiene el método SetFocus(). Se puede emplear en contenedores basados en SCX y VSC.

Veamos una imagen para irnos familiarizando. Como vemos, nos encontramos con un árbol en el que se encuentran todos los controles y los contenedores son nodos que se abren para dejar ver los controles contenidos. Podemos cerrar y abrir los nodos que no vamos a necesitar y de esta forma nuestra lista de controles no está abierta para todos aquellos que no vamos a necesitar.

Pero aun no terminan las bondades de este aporte, que como han dicho, puede ser candidato a formar parte del proyecto Sedna. Bueno, ¿qué más tiene? Pues es Anclable, lo que significa que lo podemos mantener abierto formando parte de nuestra configuración y así tenemos que al cerrar el formulario actual y abrir uno nuevo, bastaría con pararnos en la ficha FrmClassTree (que además, podemos renombrar), yo le he llamado Arbol_controles y lo tengo en mi ventana de comandos junto a las otras ventanas que me gusta mantener: la ventana Propiedades, la ventana Sesión de Datos y la ventana Vista de Documento

Para llamar a esta utilidad basta con ejecutar su programa; pero ... ¿qué tal si nos acordamos de IntelliSense? Pues, podemos agregar un registro de usuario a nuestra tabla FoxCode.DBF e indicar una abreviatura y el código o la llamada al prg, como nos parezca mejor. De esta forma, bastaría con escribir la abreviatura para activar nuestro árbol de controles para la sesión de trabajo actual.

¿Qué tal? Yo lo encuentro muy cómodo. ¿Y ... qué tal un acceso directo a los métodos o eventos de los controles contenidos? Pues también hay posibilidad de lograrlo y nos la brinda VFP de forma nativa.

Tenemos la posibilidad de acceder desde la ventana propiedades, ficha Métodos. Lo que sucede es que nos tendríamos que colocar primeramente en el control para que se muestren sus métodos. Otra variante es desde el diseñador de Formularios / Clases; pero ... nuevamente tenemos que localizar el control y luego el método asociado. Aquí la ventaja sobre seleccionarlo desde la ventana Propiedades es que los procedimientos que tienen código en la instancia actual se muestran al inicio para cada control y muchas veces, si no son muchos, no hay que desplazarse.

Una forma más o menos cómoda es recorrer con las teclas de Avance y Retroceso de páginas desde la ventana de códigos de los Métodos y eventos. Esto lo que va a hacer es que nos movamos por los distintos procedimientos que hemos creado para todos los controles. Eso puede ayudar; pero... lo que sí es verdaderamente productivo es emplear una herramienta de VFP a partir de 8.0 que se llama Vista del Documento. La podemos mantener activada todo el tiempo y es válida para programas de procedimientos o con los Diseñadores de Clases y Formularios.

En la figura hay un ejemplo donde los controles no tienen contenedores adicionales; pero trabaja de la misma forma si los controles están contenidos, independientemente de la complejidad de la herencia de contenedores. La vista de Documentos se puede mantener anclada a la ventana comandos y siempre activa, ya que VFP guarda la configuración al salir.

Pues hasta aquí algunas ideas para lograr acceso directo a controles y el código vinculado a ellos. Aplicando estas técnicas ganamos en productividad, tan necesaria para nuestros desarrollos.

Saludos,

Ana María Bisbé York

24 de junio de 2018

Conocer la fecha en que fue iniciado el servidor de SQLServer

Es posible saber cuándo fué iniciado el SQLServer (o MSDE), a travéz de VFP usando técnicas SPT (SQL Pass Through).

lcServer = "(local)"
TEXT TO lcConnString NOSHOW TEXTMERGE
[DRIVER=SQL Server;SERVER=< < lcServer > >;
DATABASE=tempdb;Network=DBMSSOCN;
Trusted_Connection=Yes]
ENDTEXT
lnHandle = SQLStringConnect(lcConnString)
IF lnHandle > 0
   TEXT TO lcQuery NOSHOW 
      SELECT crdate AS dFecha
        FROM master.dbo.sysdatabases
        WHERE name = 'tempdb' 
   ENDTEXT

   IF SQLExec(lnHandle,lcQuery,"cSQLServer") > 0
        Messagebox("Fecha de Inicio del Servidor:"+cSQLServer.dFecha)
   ELSE
       IF AERROR(laError) > 0
           Messagebox("Error al consultar fecha de Inico de SQLServer"+;
               CHR(13)+"Error:"+laError[2])
       ELSE
           Messagebox("Error inesperado...")
       ENDIF
   ENDIF
ELSE
   IF AERROR(laError) > 0
       Messagebox("Error al intentar conectar"+CHR(13)+;
              "Error:"+laError[2])
   ELSE
          Messagebox("Error inesperado al intentar conectar"+CHR(13)+;
                "Error:"+laError[2])
   ENDIF
ENDIF

Espero que les sea de utilidad.

Espartaco Palma Martínez

18 de junio de 2018

Manipular Ventanas desde VFP - Parte 2

En Esta Ocasion envio un Formulario que permite modificar el tamaño y la posicion de una ventana cualquiera desde VFP, y para variar por medio de APIs.

DEFINE CLASS cambia_ventanas AS form
 Height = 158
 Width = 351
 ShowWindow = 2
 DoCreate = .T.
 AutoCenter = .T.
 BorderStyle = 2
 Caption = "Manipulando Ventanas 2"
 TitleBar = 0
 Name = "Cambia_Ventanas"

 ADD OBJECT salir AS commandbutton WITH ;
  Top = 120, ;
  Left = 176, ;
  Height = 27, ;
  Width = 84, ;
  FontBold = .T., ;
  Caption = "Salir", ;
  TabIndex = 13, ;
  ForeColor = RGB(239,58,58), ;
  Name = "Salir"

 ADD OBJECT texto AS textbox WITH ;
  Height = 23, ;
  Left = 98, ;
  TabIndex = 3, ;
  Top = 36, ;
  Width = 240, ;
  Name = "texto"

 ADD OBJECT label1 AS label WITH ;
  AutoSize = .T., ;
  FontBold = .T., ;
  Caption = "Caption", ;
  Height = 17, ;
  Left = 14, ;
  Top = 39, ;
  Width = 45, ;
  TabIndex = 2, ;
  ForeColor = RGB(50,41,218), ;
  Name = "Label1"

 ADD OBJECT cx AS textbox WITH ;
  Alignment = 3, ;
  Value = 0, ;
  Format = "k", ;
  Height = 23, ;
  InputMask = "999,999", ;
  Left = 98, ;
  TabIndex = 5, ;
  Top = 60, ;
  Width = 72, ;
  Name = "cX"

 ADD OBJECT label2 AS label WITH ;
  AutoSize = .T., ;
  FontBold = .T., ;
  Caption = "Posicicion X", ;
  Height = 17, ;
  Left = 14, ;
  Top = 63, ;
  Width = 72, ;
  TabIndex = 4, ;
  ForeColor = RGB(50,41,218), ;
  Name = "Label2"

 ADD OBJECT cy AS textbox WITH ;
  Alignment = 3, ;
  Value = 0, ;
  Format = "k", ;
  Height = 23, ;
  InputMask = "999,999", ;
  Left = 266, ;
  TabIndex = 7, ;
  Top = 60, ;
  Width = 72, ;
  Name = "cY"

 ADD OBJECT label4 AS label WITH ;
  AutoSize = .T., ;
  FontBold = .T., ;
  Caption = "Posicicion Y", ;
  Height = 17, ;
  Left = 194, ;
  Top = 63, ;
  Width = 71, ;
  TabIndex = 6, ;
  ForeColor = RGB(50,41,218), ;
  Name = "Label4"

 ADD OBJECT ancho AS textbox WITH ;
  Alignment = 3, ;
  Value = 0, ;
  Format = "k", ;
  Height = 23, ;
  InputMask = "999,999", ;
  Left = 98, ;
  TabIndex = 9, ;
  Top = 86, ;
  Width = 72, ;
  Name = "ancho"

 ADD OBJECT label3 AS label WITH ;
  AutoSize = .T., ;
  FontBold = .T., ;
  Caption = "Ancho", ;
  Height = 17, ;
  Left = 14, ;
  Top = 89, ;
  Width = 38, ;
  TabIndex = 8, ;
  ForeColor = RGB(50,41,218), ;
  Name = "Label3"

 ADD OBJECT alto AS textbox WITH ;
  Alignment = 3, ;
  Value = 0, ;
  Format = "k", ;
  Height = 23, ;
  InputMask = "999,999", ;
  Left = 266, ;
  TabIndex = 11, ;
  Top = 86, ;
  Width = 72, ;
  Name = "alto"

 ADD OBJECT label5 AS label WITH ;
  AutoSize = .T., ;
  FontBold = .T., ;
  Caption = "Alto", ;
  Height = 17, ;
  Left = 194, ;
  Top = 89, ;
  Width = 24, ;
  TabIndex = 10, ;
  ForeColor = RGB(50,41,218), ;
  Name = "Label5"

 ADD OBJECT label6 AS label WITH ;
  FontBold = .T., ;
  FontSize = 12, ;
  Alignment = 2, ;
  BackStyle = 0, ;
  Caption = "Manipulando Ventanas 2", ;
  Height = 26, ;
  Left = 3, ;
  Top = 4, ;
  Width = 346, ;
  TabIndex = 1, ;
  ColorSource = 0, ;
  ForeColor = RGB(26,62,206), ;
  BackColor = (thisform.backcolor), ;
  Name = "Label6"

 ADD OBJECT cambiar AS commandbutton WITH ;
  Top = 120, ;
  Left = 90, ;
  Height = 27, ;
  Width = 84, ;
  FontBold = .T., ;
  Caption = "Cambiar", ;
  TabIndex = 12, ;
  ForeColor = RGB(239,58,58), ;
  Name = "Cambiar"

 PROCEDURE salir.Click
  thisform.Release
 ENDPROC

 PROCEDURE cambiar.Click
  Local handle As Long
  Declare Long MoveWindow In "user32" Long HWnd, Long x, Long Y, Long nWidth, Long nHeight, Long bRepaint
  Declare Long FindWindow In "User32" String Clase, String texto
  Declare long IsWindowEnabled IN "user32" long HWnd 
  With Thisform
   handle = FindWindow(.Null.,Alltrim(.texto.Value))
   If handle =0
    Wait Window "Ventana No Encontrada..."
    Return
   Endif
   MoveWindow(handle, .cX.Value, .cY.Value, .ancho.Value, .alto.Value, 1)
   IsWindowEnabled(handle)
  Endwith
 ENDPROC

ENDDEFINE

Saludos.

Jorge Mota

11 de junio de 2018

Manipular ventanas desde VFP - Parte 1

En esta ocasión envío un pequeño programa que nos permite habilitar /deshabilitar cualquier ventana buscándola por medio de su Caption, y también podemos cambiar el Caption de cualquier ventana!!!

Al Deshabilitar una ventana no podremos dar Click ni escribir nada en esa ventana, ni siquiera restaurarla o maximizarla, cerrarla, moverla, minimizarla, etc. (útil si queremos que no puedan cerrar X ventana mientras corremos un proceso)

Solo hay que tener cuidado cuando busquen la ventana, tiene que ir exactamente igual que como aparece en el título de la misma, mayúsculas y minúsculas.

Aquí el código:

Public oFormulario
oFormulario=Newobject("Ventanas")
oFormulario.Show
Return

Define Class Ventanas As Form
 Top = 118
 Left = 121
 Height = 177
 Width = 465
 DoCreate = .T.
 Caption = "Manipulando Ventanas desde VFP"
 Name = "Manipula_Ventanas"

 Add Object deshabilita As CommandButton With ;
  Top = 136, ;
  Left = 24, ;
  Height = 27, ;
  Width = 84, ;
  Caption = "Deshabilitar", ;
  TabIndex = 5, ;
  Name = "deshabilita"

 Add Object Titulo_ventana As TextBox With ;
  BackStyle = 1, ;
  Height = 23, ;
  Left = 24, ;
  TabIndex = 2, ;
  Top = 30, ;
  Width = 420, ;
  Name = "Titulo_ventana"

 Add Object Habilitar As CommandButton With ;
  Top = 136, ;
  Left = 108, ;
  Height = 27, ;
  Width = 84, ;
  Caption = "Habilitar", ;
  TabIndex = 6, ;
  Name = "Habilitar"

 Add Object label1 As Label With ;
  AutoSize = .T., ;
  FontBold = .T., ;
  BackStyle = 0, ;
  Caption = "Titulo de La Ventana ", ;
  Height = 17, ;
  Left = 24, ;
  Top = 6, ;
  Width = 120, ;
  TabIndex = 1, ;
  Name = "Label1"

 Add Object Nuevo_Titulo As TextBox With ;
  BackStyle = 1, ;
  Height = 23, ;
  Left = 24, ;
  TabIndex = 3, ;
  Top = 77, ;
  Width = 420, ;
  Name = "Nuevo_Titulo"

 Add Object Cambiar As CommandButton With ;
  Top = 136, ;
  Left = 192, ;
  Height = 27, ;
  Width = 84, ;
  Caption = "Cambiar", ;
  TabIndex = 7, ;
  Name = "Cambiar"

 Add Object Estado As Label With ;
  AutoSize = .T., ;
  BackStyle = 0, ;
  Caption = "Estado de la Ventana:", ;
  Height = 17, ;
  Left = 24, ;
  Top = 112, ;
  Width = 122, ;
  TabIndex = 4, ;
  Name = "Estado",;
  Tag ="Estado de la Ventana:"

 Add Object label3 As Label With ;
  AutoSize = .T., ;
  FontBold = .T., ;
  BackStyle = 0, ;
  Caption = "Nuevo Titulo para la Ventana ", ;
  Height = 17, ;
  Left = 24, ;
  Top = 58, ;
  Width = 166, ;
  TabIndex = 1, ;
  Name = "Label3"

 Procedure Load
  Declare Long IsWindowEnabled In "user32" Long handle
  Declare Long EnableWindow In "user32" Long handle, Long fEnable
  Declare Integer FindWindow In WIN32API String cNULL, String cWinName
  Declare Long SetWindowText In "user32" Long handel, String lpString
 Endproc

 Procedure deshabilita.Click
  Local Estado, retval As Long, handle As Long
  handle = FindWindow(.Null.,Alltrim(Thisform.Titulo_ventana.Value))
  If handle=0 Or Empty(Thisform.Titulo_ventana.Text)
   Wait Window 'Ventana no Encontrada'
   Return
  Endif
  retval = EnableWindow(handle, 0)
  Estado= IsWindowEnabled(handle)
  If Estado=0
   Thisform.Estado.Caption =Alltrim(Thisform.Estado.Tag)+' Deshabilitada'
  Else
   Thisform.Estado.Caption =Alltrim(Thisform.Estado.Tag)+' Habilitada'
  Endif
 Endproc

 Procedure Habilitar.Click
  Local Estado, retval As Long, handle As Long
  handle = FindWindow(.Null.,Alltrim(Thisform.Titulo_ventana.Value))
  If handle=0 Or Empty(Thisform.Titulo_ventana.Text)
   Wait Window 'Ventana no Encontrada'
   Return
  Endif
  retval = EnableWindow(handle, 1)
  Estado= IsWindowEnabled(handle)
  If Estado=0
   Thisform.Estado.Caption =Alltrim(Thisform.Estado.Tag)+' Deshabilitada'
  Else
   Thisform.Estado.Caption =Alltrim(Thisform.Estado.Tag)+' Habilitada'
  Endif
 Endproc

 Procedure Cambiar.Click
  Local Estado, retval As Long, handle As Long
  handle = FindWindow(.Null.,Alltrim(Thisform.Titulo_ventana.Value))
  If handle=0
   Wait Window 'Ventana no Encontrada'
   Return
  Endif
  If Empty(Thisform.Nuevo_Titulo.Text) Or Empty(Thisform.Titulo_ventana.Text)
   Wait Window 'Debe escribir un Caption valido'
   Return
  Endif
  SetWindowText(handle, Alltrim(Thisform.Nuevo_Titulo.Text))
  Estado= IsWindowEnabled(handle)
  If Estado=0
   Thisform.Estado.Caption =Alltrim(Thisform.Estado.Tag)+' Deshabilitada'
  Else
   Thisform.Estado.Caption =Alltrim(Thisform.Estado.Tag)+' Habilitada'
  Endif
 Endproc
Enddefine

Saludos.

Jorge Mota

4 de junio de 2018

Engancharse a los Eventos de Un Objeto Cualquiera

Este código permite por ejemplo ejecutar código en el evento Moved del _Screen y en el evento Resize... ... también permite "Engancharse" cualquier otro objeto de VFP, siempre y cuando sea nativo de Visual FoxPro. Al querer colgarme al evento Activate del _Screen, a veces da Error.

Para colgarse a un cuadro de texto podemos definir

OBJETO = 'THISFORM.TEXTO1'

Y podríamos sobreescribir el evento Valid.

OBJETO = '_SCREEN'
_SCREEN.ADDOBJECT('HOOK_1', '_GANCHO')

DEFINE CLASS _GANCHO AS CUSTOM
 OBJEVALUADO = EVAL(OBJETO)

 PROCEDURE OBJEVALUADO.MOVED
  IF THIS.WINDOWSTATE = 0
   IF (THIS.LEFT < 0) OR (THIS.TOP < 0)
    THIS.AUTOCENTER=.T.
   ENDIF
  ENDIF  
 ENDPROC
 
 PROCEDURE OBJEVALUADO.RESIZE
  ACTIVATE SCREEN
  IF THIS.WINDOWSTATE = 1
   THIS.CAPTION = 'Minimizado'
  ENDIF
  IF THIS.WINDOWSTATE = 2
   THIS.CAPTION = 'Microsoft Visual FoxPro'
  ENDIF
  IF THIS.WINDOWSTATE = 0
   THIS.CAPTION = 'Normal'
   THIS.AUTOCENTER = .T.
  ENDIF
 ENDPROC

ENDDEFINE

Jorge Mota

30 de mayo de 2018

Construyendo Objetos Multicapa en VisualFoxPro (n-tier)

Construyendo Objetos MultiCapa en Visual Foxpro

Autor: J. Booth

Traducción de:: Sergio E. Aguirre

El modelo N-Tier

El término N-tier se refiere a los varios niveles de responsabilidad en el diseño de un sistema. La N en N-Tier puede ser cualquier número de 2 en adelante. Un diseño muy común es el modelo 3-Tier. En el modelo 3-tier la aplicación es dividida en 3 capas de responsabilidades distintas, la interface del usuario, la lógica de negocio, y la base de datos. Cada una de estas capas puede ser implementada usando uno o más objetos que se dedican a las responsabilidades de esa capa.

Interface del usuario

La capa interface del usuario contendría todos los aspectos visuales del sistema. Cualquier cosa involucrada en la interacción con el usuario del sistema es manejada por esta capa. Todos los diálogos, cuadros de mensajes, formularios, informes, y otros componentes de interacción con el usuario residirían en la capa interface de usuario de un sistema.

Lógica de Negocio

La capa lógica de negocio tiene la responsabilidad de determinar la forma en que vienen los datos y cómo deben estructurarse para la interface del usuario. También aplica toda regla de validación a los datos provenientes de la interface del usuario antes de mandar estos datos a la base de datos.

La capa lógica de negocio no tiene ningún componente de la interface del usuario en ella como tampoco tiene la responsabilidad de actuar recíprocamente con el usuario. Los problemas percibidos con los datos deben ser comunicados a la capa interface del usuario a través de valores devueltos por métodos y la capa interface del usuario debe mostrar los mensajes al usuario.

Manejo de la Base de Datos

La base de datos es la responsable de manejar el dominio de validación sobre los datos, de actualizar y recuperar los datos en las tablas. Las reglas en la base de datos deben restringirse a sólo aquellas que son de directa aplicación del dominio de validación. "Reglas de Negocio" no son parte de las reglas de la base de datos, en cambio ellas fueron puestas en vigor en la capa lógica de negocio.

Otras Capas

3-Tier no es el único diseño N-Tier. N puede ser cualquier número. Algunas de las cosas que podrían ser consideradas para las capas adicionales son, Interface del Sistema Operativo, Interface de la Red, y los Múltiples Niveles de Capas de Lógica de Negocio.

Por ejemplo, usted puede diseñar un sistema para un banco donde el objeto de lógica de negocio para una Cuenta necesita tener varios formatos diferentes dependiendo de la sección del banco que está usando los datos. En este caso usted puede tener un objeto de lógica de negocio para Cuenta que es genérico para el banco entero, y tiene otros objetos de lógica de negocio que son específicos para las secciones particulares (usando el objeto Cuenta genérico y agregando o restringiendo rasgos basados en los requisitos de la sección).

Ventajas / Desventajas del Diseño N-Tier

Las ventajas del diseño de un sistema N-Tier son múltiples. La siguiente lista muestra algunas de las ventajas.

  1. Usted puede modificar la lógica de negocio sin hacer cambios a la interface del usuario o a la base de datos.
  2. Si lo construyó correctamente, el objeto de lógica de negocio puede ser usado por múltiples interfaces del usuario.
  3. Aisla el conocimiento requerido en cualquier capa dada a esa capa.

Algunas de las desventajas son:

  1. El diseño del sistema es más complejo.
  2. El enlace de datos inherente de Visual FoxPro es indisponible.
  3. La huella de memoria de la aplicación aumenta.

Con estas desventajas, ¿Por qué querría alguien construir un sistema N-Tier? La respuesta es una sola palabra, scalability. El diseño N-Tier puede descascarar a los sistemas sumamente grandes sin compromisos. Por grande estamos refiriéndonos al número de usuarios, el número de diferentes componentes de la interface del usuario, el tamaño de la base de datos, la estructura de la red, y todos los otros problemas de tamaño para una aplicación.

Usando el diseño N-Tier, usted puede diseñar un sistema que pueda manejar múltiples intefaces del usuario divergentes sin tener que volver a escribir la lógica de negocio para cada interface construida. La lógica de negocio puede ser compartida por múltiples interfaces del usuario. Mediante la posibilidad de hacer subclases, las clases de lógica de negocio pueden personalizarse para manejar diferentes servidores de base de datos.

El diseño N-Tier no es apropiado para todos los proyectos, pero cuando se necesita de él es un concepto de diseño sumamente poderoso.

Construyendo Aplicaciones N-Tier en Visual FoxPro

Visual Foxpro puede ser usado para construir cualquiera de las capas comúnes del modelo N-Tier. Visual FoxPro tiene las herramientas para construir interfaces del usuario destacadas. La base de datos nativa de Visual FoxPro es rápida y robusta. Sin embargo, para cada una de estas dos capas hay otras herramientas que también lo hacen bien o mejor. ¿Si la interface necesita imitar a una hoja de cálculo, Excel no sería una mejor opción? Si la base de datos necesita seguridad agregada en la base de datos del servidor, no serían una opción mejor SQL Server o Oracle?

La capa en la que Visual FoxPro tiene ventajas es la capa media, o capa de lógica de negocio, del modelo N-Tier. Debido a la construcción en Lenguaje de Manejo de Datos (DML) de Visual FoxPro, es el primer candidato para manipular datos de un servidor y presentarlos a una interface. También, la posibilidad de crear clases OLE públicas con Visual FoxPro permite interfaces divergentes y bases de datos para usar el mismo objeto de media capa para comunicarse entre sí.

Las Responsabilidades de un Objeto de Lógica de Negocio

Las responsabilidades de un objeto de media capa varían ampliamente. Cosas como la aplicación de la regla de negocio, separación de la interface del usuario y el origen de la base de datos, y proporcionando una sola capa para el acceso a datos para los múltiples servidores de la base de datos está entre las posibles responsabilidades.

Como con muchas otras cosas en el desarrollo orientado a objetos, el diseño del sistema dicta las reales funciones proporcionadas por un objeto de media capa.

Aplicando las reglas de negocio

En cualquier sistema de base de datos hay reglas que controlan que son datos válidos y que son datos inválidos. Estas reglas pueden ser divididas en el dominio de validación y reglas de negocio. Un dominio describe todos los valores posibles que pueden encontrarse en la entidad o atributo al que el dominio aplica. Por ejemplo, el dominio para un campo ciudad puede incluir todos los nombres posibles de ciudades del mundo.

Las reglas de negocio son un subconjunto de un dominio. Las reglas de negocio llevan más allá el límite de valores posibles para ser sólo parte del dominio completo. Con el ejemplo del campo ciudad, quizás nuestra compañía se localiza en Alemania y tiene clientes sólo alemanes. En este caso el dominio para el campo ciudad será todas las ciudades del mundo, pero las reglas de negocio limitarían al campo a sólo ciudades en Alemania.

Mientras que las bases de datos son muy buenas en la aplicación de dominios, ellas pueden ser demasiadas restrictivas si ponen en vigor reglas de negocio. El objeto de lógica de negocio de media capa es un candidato ideal para poner en vigor las reglas de negocio. Visual FoxPro está especialmente preparado para este trabajo porque su motor local de datos permite la creación de diseños de metadata para describir las reglas específicas a ser puestas en vigor. Esto le permite al programador crear reglas de negocios para el manejo de datos que puedan cambiar con el tiempo sin requerir cualquier modificación de código.

Trasladando Datos para la Interface del Usuario

En el diseño 3-Tier la interface del usuario está separada del origen de datos por la capa media, o la capa de la lógica de negocio. Esta separación le permite al programador construir una interface del usuario independiente y capas de almacenamiento de datos. Los perfeccionamientos futuros del sistema pueden incorporar nuevas interfaces del usuario o nuevas tecnologías de almacenamiento de datos sin un cambio que provoque la necesidad de cambiar el otro.

El objeto de media capa realiza el papel de traductor de datos en el formato encontrado en el sistema de almacenamiento de datos a un formato que puede ser usado por la interface del usuario. También traduce los datos desde la interface del usuario a un formato que puede ser guardado por la base de datos.

Con este diseño un cambio en la base de datos requiere sólo que el objeto de media capa sea reforzado, así como un cambio en la interface del usuario también sólo requiere que el objeto de media capa sea reforzado.

Usando Clases de Visual FoxPro para construir un Objeto de Lógica de Negocio

Los ejemplos se pueden descargar aquí.

Exploremos ahora en algo de código para ver una de las muchas maneras que usted puede diseñar un objeto de media capa en Visual FoxPro. La clase de lógica de negocio que nosotros crearemos se va a llamar customer y proporcionará acceso a los datos de ejemplo Fitch Mather que vienen con Visual FoxPro 6.0. La tabla que usaremos es la tabla Stores (Tiendas).

El primer problema con el cual yo traté era qué clase de base usar para crear el objeto customer. Yo escogí usar la clase de base form porque proporciona una sesión de datos privada que protegerá los datos de otras instancias del objeto customer.

A la clase customer yo agregué una propiedad llamada oRDS para usarse como una referencia a un Control de Datos RDS. Yo usé RDS como la metodología de acceso a datos para que la clase customer pueda ser fácilmente modificada para acceder a otros sistemas de base de datos. El control de Datos RDS se crea en el Init de la clase Customer. El código del método Init se describe debajo.

* Creo el Control de Datos RDS

This.oRDS = CreateObject("rds.datacontrol")

* Verificamos si la creación tuvo éxito

If Type("This.oRDS") <> "O"

* Si es no devuelvo .F.

Return .F.

Else

* Si tuvo éxito fijo algunas propiedades del datacontrol

With This.oRDS

* Establezco el nombre del origen de Datos

.Connect = "dsn=dsnFitchMather"

* Prepare la declaración SQL para ejecutarla

.SQL = "Select * from stores"

* Fijo la ejecución síncrona

.ExecuteOptions = adcExecSync

* Fijo el saque en el fondo

.FetchOptions = adcFetchBackground

* Ejecuto la consulta

.Refresh

EndWith

EndIf

Los comentarios en el código anterior son autoexplicativos. Una vez que el objeto customer existe tiene el control de Datos RDS dentro de él y el Control de Datos RDS está sacando los datos.

NOTA:

Las constantes referidas en el código anterior se toman de un archivo llamado adcvbs.h. Los contenidos de este se listan debajo.

*--------------------------------------------------------------------

* Microsoft ADC

* (c) 1997 Microsoft Corporation. All Rights Reserved.

* ADO constants include file for VBScript

*--------------------------------------------------------------------

*---- enum Values ----

#Define adcExecSync 1

#Define adcExecAsync 2

*---- enum Values ----

#Define adcFetchUpFront 1

#Define adcFetchBackground 2

#Define adcFetchAsync 3

*---- enum Values ----

#Define adcReadyStateLoaded 2

#Define adcReadyStateInteractive 3

#Define adcReadyStateComplete 4

A esta clase yo he agregado varios métodos que se listan en la siguiente tabla.

Método

Propósito

GetValue

Usado para obtener el valor de un campo

SetValue

Usado para establecer el valor de un campo

MoveFirst

Mueve al primer registro en el RecordSet

MoveLast

Mueve al último registro

MoveNext

Mueve al próximo registro

MovePrev

Mueve al registro anterior

Requery

Refresca el Control de Datos mediante la re-ejecución de la consulta SQL. El método requery está para hacer que la sintaxis del objeto customer sea similar a la sintaxis nativa de VFP nativa para el requerying de una vista.

RevertChanges

Descarta los cambios pendientes de los datos

SaveChanges

Graba los cambios pendientes de los datos

Las siguientes secciones presentarán el código que está en estos métodos.

GetValue

LPARAMETERS PcField

* Verifico por un parámetro válido

IF NOT EMPTY(pcField) AND VARTYPE(pcField) = "C"

* Verifico si es un nombre de campo válido para este objeto

IF LOWER(pcField) $ "store_name~store_add1~store_addr2~store_addr3~" + ;

"store_city~store_id~store_desc~store_phone1~" + ;

"store_state~store_type~store_zip"

* Demanda de un campo válido para este objeto, así que devuelvo el valor

RETURN THIS.oRDS.Recordset.Fields(pcField).Value

ENDIF

ENDIF

* Demanda de un campo inválida

RETURN .NULL.

SetValue

LPARAMETERS PcField, pxValue

IF NOT EMPTY(pcField) AND VARTYPE (pcField) = "C"

IF LOWER(pcField) $ "store_name~store_add1~store_addr2~store_addr3~" + ;

"store_city~store_id~store_desc~store_phone1~" + ;

"store_state~store_type~store_zip"

IF VarType(pxValue) = "C"

pxValue = ALLTRIM(pxValue)

ENDIF

THIS.oRDS.Recordset.Fields(pcField).Value = pxValue

RETURN .T.

ENDIF

ENDIF

RETURN .F.

MoveFirst

ThisForm.oRDS.RecordSet.MoveFirst

RETURN 1

MoveLast

ThisForm.oRDS.RecordSet.MoveLast

RETURN 1

MoveNext

LOCAL LnRet

lnRet = 1

ThisForm.oRDS.RecordSet.MoveNext

If ThisForm.oRDS.RecordSet.Eof

lnRet = -1

ThisForm.oRDS.RecordSet.MoveLast

EndIf

RETURN lnRet

MovePrev

LOCAL LnRet

lnRet = 1

ThisForm.oRDS.RecordSet.MovePrevious

If ThisForm.oRDS.RecordSet.Bof

lnRet = -1

ThisForm.oRDS.RecordSet.MoveFirst

EndIf

RETURN lnRet

Requery

ThisForm.oRDS.Refresh

RevertChanges

THISFORM.Requery()

SaveChanges

ThisForm.oRDS.SubmitChanges()

THIS.Requery()

Usted puede preguntarse por qué yo he creado todos estos métodos para hacer cosas que podría haber hecho refiriéndome directamente al Control de Datos RDS. La respuesta es que esto proporciona una interface de desarrollo para el objeto de negocio que es independiente de la naturaleza del objeto de datos. Yo puedo hacer subclases de esta clase y escribir código que con datos locales de VFP, o ADO en lugar de RDS, o ODBC a través de vistas remotas. Ninguno de éstas modificaciones requeriría que cualquier código, en el nivel de UI, sea cambiado en absoluto.

Usando el Objeto de Negocio

El proyecto también incluye un formulario de VFP que demanda esta clase de negocio. El nombre del formulario es Customer.scx. Examinemos el código de este formulario que usa la clase de negocio.

En este formulario se agrega una propiedad llamada oBusObj. En el evento Load del formulario el código será el siguiente.

THISFORM.oBusObj = NewObject("Customer")

Esto crea una instancia del objeto customer y almacena una referencia a él en la propiedad oBusObj del formulario. En el Refresh del textbox número de tienda este es el código:

THIS.Value = THISFORM.oBusObj.GetValue("store_id")

Este código llama al GetValue del objeto de negocio y fija la propiedad Value del textbox para ser el valor devuelto por el método. El evento Valid para el mismo textbox es:

THISFORM.oBusObj.SetValue("store_id",THIS.Value)

Que escribe el Valor devuelto a la fuente de datos del objeto de negocio.

El evento Click para el botón Top es:

THISFORM.oBusObj.MoveFirst()

THISFORM.Refresh()

¿Está empezando a ver un patrón aquí? ¿Puede apreciar lo fácil que es usar este objeto de negocio para manejar el acceso de datos? El otro código en el formulario es similar salvo que se llaman métodos del objeto de negocio.

Creando un servidor ActiveX desde las clases de Visual FoxPro

Así que, ¿Cuál es el gran trato? ¿Por qué es mejor usar un objeto separado para manejar el acceso a datos cuándo el formulario tiene que un entorno de datos maravilloso y controles que pueden enlazar directamente los datos?

La respuesta a estas mentiras es la palabra scalability. Scalability es la habilidad de un sistema de crecer incluir nuevos rasgos con el tiempo, grandes volúmenes de datos, interfaces del usuario adicionales, y otros perfeccionamientos. Si usted demanda el entorno de datos de un formulario de VFP por acceder a los datos, entonces usted se limita a usar VFP para construir la interface del usuario o usted necesitará crear las mismas capacidades de acceso a datos en alguna otra herramienta.

¿Qué pasa si las mismas necesidades de acceso a datos estén disponibles a VFP y a Microsoft Excel? La respuesta es hacer a la clase customer como una clase OLE Pública y construir un COM DLL con ella incluida. Para hacer una clase OLE Pública, abra el diseñador de clases y luego elija Información de clase... en el menú Clase. Verifique que esté marcada la casilla OLE público.

Para construir el COM DLL abra el proyecto y escoja la opción Información del proyecto... del menú Proyecto, seleccione la etiqueta Servidores y establezca sus opciones de la clase (estas opciones están bastante bien documentadas en el archivo de ayuda). Guarde esas opciones y entonces escoja Generar y seleccione la opción Servidor COM (dll) y pulse el botón Aceptar. Esto va a generar el COM DLL con su clase COM servidor en él y registrará la DLL en su máquina. Para otros para usar la clase DLL necesitará ser instalada y registrada en sus máquinas, esto puede hacerse como parte del proceso de Instalación para su aplicación.

Una vez que usted ha hecho esto puede modificar sus formularios de VFP para que usen la clase COM. Simplemente cambie el evento Load para que sea:

THISFORM.oBusObj = NewObject("BusObj.Customer")

Donde BusObj es el nombre del archivo DLL que usted creó.

Usando Múltiples Interfaces del Usuario en el Diseño N-Tier

La ventaja de este diseño es que el mismo objeto de negocio puede ser usado por múltiples interfaces del usuario diferentes permitiendo una definición de clase para controlar el acceso a datos para cada UI que su sistema usa. Aunque su aplicación sólo pueda limitarse a formularios de VFP al principio, siguiendo este diseño N-Tier le permitirá, de manera más fácil, agregar otras interfaces al sistema en el futuro (haciendo el sistema escalable).

Usando Microsoft Excel para la Interface del Usuario

Aquí hay un ejemplo de una macro de Microsoft Excel que usa la misma clase de negocio para llenar con datos una hoja de cálculo.

Option Explicit

Public Dummy As Variant

Public oCustomer As Object

Sub nTier()

' nTier Macro

'

Dim lnRet As Integer

Set oCustomer = CreateObject("BusObj.Customer")

ActiveSheet.Cells(1, 1) = "Store ID"

ActiveSheet.Cells(1, 2) = "Store Name"

ActiveSheet.Cells(1, 3) = "Store City"

lnRet = Refresh()

' frmRefresh.Show

End Sub

Public Function Refresh()

' nTier Macro

' Refreshes the contents of the business logic object and the sheet.

Dim lnRet As Integer

Dim lnRow As Integer

lnRet = oCustomer.MoveFirst()

lnRow = 2

Do While lnRet > 0

ActiveSheet.Cells(lnRow, 1) = oCustomer.GetValue("store_id")

ActiveSheet.Cells(lnRow, 2) = oCustomer.GetValue("store_name")

ActiveSheet.Cells(lnRow, 3) = oCustomer.GetValue("store_city")

lnRet = oCustomer.MoveNext()

lnRow = lnRow + 1

Loop

Refresh = 1

End Function

Esta macro creará una instancia de la clase customer y entonces llenará las filas y columnas de la hoja con datos del objeto de negocio.

Resumen

Hay muchas charlas alrededor del diseño de sistemas N-Tier. Algunas personas son fuertes defensoras de usar N-Tier para todo, en cambio otras personas sienten que el N-Tier es excesivo en muchos lugares. Mi opinión es que algo que yo pueda hacer para mejorar con el tiempo un sistema para mi cliente es bien esfuerzo bien hecho.

El diseño N-Tier me da la capacidad de manejar datos a través del uso de clases de las que se pueden hacer subclases y pueden ser especializadas dentro de VFP y al mismo tiempo pueden hacer que esas clases estén disponibles a otras herramientas de desarrollo que manteniendo el lugar solo modificando el acceso de datos. Esta sola situación es uno de los mayores beneficios logrados a través del diseño N-Tier. Si el cliente cambia el servidor de la base de datos a otra, hay sólo un lugar para hacer los cambios necesarios y todas las interfaces del usuario se actualizarán.

25 de mayo de 2018

Tableupdate

TableUpdate() en Visual FoxPro

Autor: Jim Booth

Traducido por: Roberto Alfredo Moré

TableUpdate() es una de las funciones usadas muy frecuentemente cuando se utiliza buffering en Visual FoxPro.  La versión 5.0 de Visual FoxPro ha mejorado la funcionalidad de esta función. Este mes, examinaremos las mejoras a la función TableUpdate().

La sintaxis.

En Visual FoxPro versión 5 se ha mejorado la función TableUpdate().  Hay nuevas opciones que pueden usarse para hacer que esta función sea más útil a nosotros como desarrolladores. TableUpdate() tiene cuatro argumentos que controlan sus acciones. La siguiente es la sintaxis para la función TableUpdate.

TableUpdate( <nFilas>, <lForzar>, <cAliasTabla or nÁreaTrabajo>, <cMatrizErrores> )

nFilas.

Miremos cada uno de los argumentos de esta función. El primer argumento controla como la función trabajará con múltiples registros en el buffer.  Este es probablemente el argumento más confuso de la función.

nFilas aceptará uno de los tres valores 0, 1, o 2.  Un valor de 0 sólo actualizará el registro actual independientemente si el buffering es tabla o fila. Este argumento puede ser usado para controlar la edición de un único registro cuando se usa buffering de tabla o para procesar cada registro en el buffer separadamente.

Un valor de 1 actualizará todos los registros en el buffer en una llamada a TableUpdate.  Si TableUpdate encuentra un registro que no puede ser actualizado, fallará en ese punto y retornará un valor de .F., indicando que ha fallado. Ningún registro más allá del que tiene el problema será procesado.

Usando 2 como valor de nFilas también actualiza todos los registros en una llamada a la función, pero reacciona diferentemente si falla en un registro. Si un registro no puede ser actualizado, continuará con el resto de los registros y actualizará todos los que pueda.  TableUpdate retornará un valor .F. indicando que hubo un problema y llenará la matriz referenciada en el cuarto argumento con los registros que no pudo actualizar.

Si no se define un valor para nFilas se usa el valor 0.

lForzar.

El segundo argumento es un valor lógico .T. o .F. y controla como TableUpdate tratará los conflictos cuando otro usuario haya cambiado el registro mientras estábamos trabajando sobre él. Un valor de .T. forzará nuestra actualización y sobrescribirá los cambios realizados por el otro usuario, mientras que un valor de .F. no realizará la actualización si otro usuario ha hecho modificaciones al mismo registro.

Es una buena idea usar .F. para este argumento, ya que el sobrescribir los cambios de otros usuarios puede producir resultados no deseados en la tabla. Siempre podemos escribir código de recuperación para un TableUpdate fallido que pueda rectificar las diferencias y luego forzar una actualización (discutiremos este tipo de código el próximo mes).

cAliasTabla/nÁreaTrabajo

Este argumento es usado para determinar qué área de trabajo o alias será afectado por el llamado a la función TableUpdate.  Si se omite este argumento, se afecta el área de trabajo actualmente seleccionada. Se recomienda que siempre se indique el nombre de alias a la función TableUpdate() para prevenir resultados inesperados. En Visual FoxPro es muy fácil encontrarse con un área de trabajo cambiada y mediante el uso de este argumento no quedará ninguna duda sobre que área de trabajo debe ser actualizada.

cMatrizErrores

Este argumento es el nombre de una matriz unidimensional que contendrá los números de registro de aquellos registros que no hayan podido actualizarse cuando se usa como valor del primer argumento 2. Este argumento debería indicarse siempre cuando se usa un valor de 2 para el primer argumento, ya que no hay otra manera de averiguar cuáles son los registros no actualizados.

Cuando el primer argumento es 0 o 1 el puntero de registro en el alias que está siendo actualizado permanece en el registro que no pudo actualizarse. f

Muéstranos algo de código.

Con todas estas opciones en la función, habría varias maneras de utilizarla en nuestro código. Examinemos algunos escenarios.

 Escenario 1 – Actualización de un registro único.

Tenemos un formulario que permite la edición de sólo un registro de cliente y está usando solamente la tabla de clientes (Customer). El código que podríamos tener en el método de grabado de este formulario se muestra en el Listado 2.

LOCAL lcFldState

lcFldState = GetFldState( -1,”Customer”)

IF “2” $ lcFldState OR “4” $ lcFldState

   * Se ha editado un registro existente o bien uno nuevo.

   IF NOT TableUpdate( 0, .F., “Customer” )

      * The update failed

      TableRevert(.F., “Customer”)

   ENDIF

ENDIF

 Listado 2 – Un ejemplo de actualización de un registro único.

 Escenario 2 – Múltiples registros, todos o ninguno.

En esta situación tenemos un formulario para editar clientes que permite al usuario editar varios registros de clientes antes de guardar el trabajo. Deseamos guardar todas las ediciones o no guardar ninguna.

* Se ha editado un registro existente o bien uno nuevo.

BEGIN TRANSACTION

IF NOT TableUpdate( 1, .F., “Customer” )

   * The update failed

   ROLLBACK

   TableRevert(.T., “Customer”)

ELSE

   END TRANSACTION

ENDIF

Listado 3 – Actualizando múltiples registros usando 1 como primer argumento de TableUpdate()

 Escenario 3 – Múltiples registros, haga lo que pueda.

En este ejemplo tenemos la misma situación de edición que en el Escenario 2, pero deseamos guardar todo lo que podamos y revertir únicamente los registros que no puedan ser guardados.

*  Se ha editado un registro existente o bien uno nuevo.

DIMENSION laBadRecs(1)

IF NOT TableUpdate( 2, .F., “Customer”, laBadRecs )

   * The update failed

   FOR EACH nRecord IN laBadRecs

      GOTO nRecord

      TableRevert(.F., “Customer”)

   ENDFOR

ENDIF

Listado 4 – Actualizando múltiples registros usando 2 como el primer argumento de TableUpdate()

En resumen.

La función TableRevert() fue usada en cada uno de los ejemplos. Esta función toma dos argumentos. El primero es  .T. para revertir todos los registros y .F. para revertir sólo el registro actual. El segundo argumento es el alias a ser revertido.

En el segundo escenario, se usó una transacción para englobar toda la actualización en una operación de o todo o nada. Las transacciones serán el tema de otra publicación.

Las mejoras a la función TableUpdate nos dan un control muy fino sobre la forma de trabajar de la operación de actualización. Como usted puede ver en los ejemplos, podemos controlar la actualización de nuestras tablas de forma de lograr el comportamiento exacto que deseamos para cada uno de los formularios que construyamos.

23 de mayo de 2018

Transacciones


Usando Transacciones

Autor: J. Booth

Traducido por: Pablo Roca

Previamente habíamos hablado sobre TableUpdate y TableRevert() y hemos visto como nos permiten controlar actualizaciones mientras usamos "buffering" en tablas y cursores. Estas dos opciones son muy buenas para manejar la actualización o la reversión de una sóla tabla o cursor, pero cómo coordinamos las actualizaciones o cancelaciones de varias tablas relacionadas. Aquí entran las Transacciones.

Pero, ¿qué es una transacción?

Hay situaciones en las que necesitamos actualizar más de una tabla o cursor y la combinación de actualizaciones representa una de las acciones en la Base de Datos. Esta acción debería ser una operación "todo o nada", es decir, Todo si todo va bien o Nada si algo falla. Por ejemplo, cuando escribimos código para guardar una nueva factura tenemos que añadir un registro en la tabla de cabecera de facturas, añadir un grupo de registros en la tabla de líneas de factura, modificar el saldo en el registro del cliente ya añadir un registro en la tabla de recibos. Si alguna de estas operaciones falla necesitamos deshacer todas las operaciones anteriores que sí han funcionado y asegurarnos que no se han realizado cambios en los datos. Las transacciones se han diseñado para este propósito.

Utilizaremos la instrucción BEGIN TRANSACTION para comenzar el seguimiento de la transacción en Visual FoxPro y la instrucción ENDTRANSACTION para terminar correctamente la transacción o la intrucción ROLLBACK para deshacer la transacción.

¿Cómo funcionan las transacciones?

Cuando VFP encuentra un BEGIN TRANSACTION comienza a hacer un seguimiento de las operaciones de actualización de fichero que se realizan. Para cada modificación VFP obtiene los bloquedos adecuados pero no realiza la actualización todavía. Cuando encuentra un ENDTRANSACTION Visual FoxPro realiza todas las escrituras de fichero involucradas en la transacción y libera los bloqueos que se había procurado con anterioridad. Si encuenta un ROLLBACK, Visual FoxPro abandona los bloqueos sin realizar ninguna operación de escritura.

Gracias a este proceso, Visual FoxPro es capaz de asegurar que todos los cambios se realizarán o no se realizará ninguno. Como podemos apreciar examinando este proceso es una muy buena idea poner las instrucciones BEGIN TRANSACTION y ENDTRANSACTION/ROLLBACK lo más cerca posible una de otra en el código como sea posible. Haciendo esto reduciremos el tiempo en el que los recursos están bloqueados.

¿Qué tablas y cursores pueden participar en una transacción?

En Visual FoxPro las transacciones sólo están disponibles para tablas que están en Bases de Datos (DBC). Las tablas libres no pueden participar en una transacción, y un rollback (roll back es literalmente "enrollar") u otro final anormal de una transacción no afecta a las modificaciones realizadas sobre tablas libres durante una transacción.

Esto puede causar efectos sorprendentes si una tabla libre es la fuente de datos de una tabla local que es gestionada por una transacción. Vamos a examinar los pasos del proceso con una transacción ficticia. En esta transacción hay una tabla perteneciente a una Base de Datos que se llama Tabla1 y una vista llamada Vista1 que consigue los datos de una tabla libre que se llama (originalidad sin fronteras) Tabla2. El código para la transacción es el que sigue:

BEGIN TRANSACTION
IF TableUpdate( 1, .F., "Vista1")
IF TableUpdate ( 1, .F., "Tabla1")
END TRANSACTION
ELSE
ROLLBACK
ENDIF
ELSE
ROLLBACK
ENDIF

Ahora las preguntas, si el TableUpdata de Tabla1 falla, ¿puede el ROLLBACK deshacer los cambios de Vista1 y dejarla en el estado anterior a la transacción? La respuesta es sí, puede deshacer los cambios en Vista1. ¿Pueden los cambios hechos en Table2 como resultado de la actualización de Vista1 ser deshechos por el ROLLBACK? No, porque Tabla2 es una tabla libre y, como tal, no participa en la transacción. Esto puede dejar tus datos en un estado inconsistente, deberías manejar la tabla libre por ti mismo. Lo mejor en no depender de transacciones cuando estén relacionadas tablas libres.

¡Enséñanos el código!

¿Cómo integraremos transacciones en nuestros formularios? Se puede hacer en un formulario directamente o genéricamente en una clase de formularios. Debajo hay un pseudocódigo que nos dará una idea de cómo es la estructura para incorporar transacciones en nuestras actualizaciones.

* Establecer una variable para el seguimiento de fallos
LOCAL llRollBack
llRollBack = .F.
* Esperar hasta que todo el proceso de datos está completo antes de comenzar la transacción
BEGIN TRANSACTION
* Realizar cada actualización comprobando el resultado
IF NOT TableUpdate( ... )
llRollBack = .T.
ELSE
IF NOT TableUpdate( ... )
llRollBack = .T.
ELSE
IF NOT TableUpdate( ... )
llRollBack = .T.
ENDIF
ENDIF
ENDIF
IF llRollBack
ROLLBACK
ELSE
ENDTRANSACTION
ENDIF

Este código de arriba es pseudocódigo, no lo tomes al pie de la letra. Hemos usado una serie de instrucciones IF anidadas para controlar las acciones. Podríamos haber puesto todos los alias en una matriz y utilizar un bulce FOR/ENDFOR para procesarlos, terminando el bucle cuando hubiera un error o termináramos con todos.

¿Qué puede ir mal?

En el pseudocódigo de arriba asumimos que la transacción falla y que se se ha realizado el ROLLBACK. ¿Qué, exactamente, ha hecho el ROLLBACK? ¿Los "buffers" se han dejado en su estado pre-edición o todavía tienen basura de la edición del usuario?

La respuesta es que los "buffers" se restauran al estado en el que estaban antes del BEGIN TRANSACTION. Esto quiere decir que todavía tenemos que ocuparnos de la edición del usuario. Aquí nosotros decidimos, podemos poner lo editado por el usuario de nuevo en el formulario y dejarle decidir a él, o podemos realizar un TableRevert con todos los alias y descartar lo que ha escrito el usuario. El límite lo ponemos nosotros.

¿Qué ocurre si el ordenador se apaga (o cuelga) durante la transacción? Obviamente VFP no tiene registro de la transacción cuando el sistema es reiniciado. Entonces ¿las tablas se han actualizado o no? Ninguna tabla se modificó y el resultado es como se hubiera ejecutado un ROLLBACK.

Otra situación que puede ocurrir y causar muchos problemas es esta, un desarrollador escribe un formulario y pone un BEGIN TRANSACTION en el evento Init y pone un ENDTRANSACTION o un ROLLBACK en el evento destroy, dependiendo del botón que pulse el usuario para salir del formulario. ¿Qué problema puede causar esto? En primer lugar ningún otro usuario puede hacer nada con las tablas implicadas en la transacción mientras tanto, hasta que el usuario sale del formulario y se eliminan los bloqueos. He visto esta situación usada como argumento contra las transacciones. Bueno, vamos allá, cualquier cosa que se utiliza incorrectamente puede causar problemas. Cuando se usan transacciones correctamente se limitará el tiempo entre el comienzo y el final al mínimo imprescindible. Los eventos Init y Destroy de un formulario NO son el menor periodo de tiempo posible. Este periodo de tiempo no está nunca bajo el control del programador.

He visto escrito que las transacciones de Visual FoxPro no son tan "robustas" como las transacciones que utilizan los servidores de bases de datos cliente/servidor. ¿Y qué?. De hecho, los frenos de un tractor son más "robustos" que los frenos de mi coche, ¿ es eso una razón para NO usar los frenos de mi coche? NO. Las transacciones de Visual FoxPro son útiles y sirven para su propósito. Se deben utilizar, incluso cuando los datos están almacenados en un Base de Datos cliente/servidor.

Resumen

Las transacciones de Visual FoxPro son una utilidad muy valiosa del producto. Nos permiten agrupar varias operaciones de actualización en una operación "todo o nada". Hoy por hoy raramente creamos formularios que sólo gestionan una tabla, el formulario con múltiples tabla se ha convertido en una norma. Gestionar estas actualizaciones de varias tablas se ha convertido en un aspecto muy importante de nuestro trabajo y las transacciones tienen un valor inestimable para hacerlo.

18 de mayo de 2018

Buffering - Parte 2

Buffering Parte II


 

Buffering, El Vampiro Asesino, la historia continua

En el artículo anterior se pudo ver que con el bufer de datos activado, la modificación se realiza en una copia de los datos. La cuestión obvia es ¿Cómo hacer que dicha copia se actualice en la tabla?. Con la función TableUpdate.

Una definición ayudará con la discusión, bufer sucio. Un bufer sucio es un bufer de datos que contiene cambios pendientes que no han sido actualizados en la tabla.

Actualizar o no actualizar

TableUpdate es la función que provoca que los datos de un bufer se escriban sobre una tabla. Esta función tiene (4) argumentos, la sintaxis es,

TABLEUPDATE( [nFilas|lFilas> [, lForzar]] [, cAliasTabla | nÁreaTrabajo] [, cMatrizErrores])

Como se puede ver, todos los argumentos de esta función son opcionales. Realizando un TableUpdate sin argumentos actualizará el registro actual de la área de trabajo activa y no avisará de un conflicto de actualización. Vamos a ver los argumentos en detalle.

nFilas | lFilas

En Visual Foxpro 3.0 el primer argumento era lógico y .T. significaba que se debían actualizar todos los registros cambiados, mientras que .F. solo significaba que se actualizará el registro actual. Este argumento lógico todavía funciona en Visual FoxPro 6.0, pero la alternativa numérica nos da un mayor control sobre esto.

El argumento numérico puede ser 0, 1 o 2 y controla como se actualizan los registros. Un argumento de 0 (el valor por defecto) solo actualizará el registro actual. Usando 1 o 2 actualizará todos los registros modificados. La diferencia entre 1 y 2 tiene que ver en que sucede si uno o más de los registros modificados no pueden ser actualizados.

Poniendo 1originará que el tableupdate falle si cualquiera de los registros no puede ser actualizado. Mientras que con 2 forzará a que Visual FoxPro actualice todos los registros que pueda y que guarde una lista de los registros que no pueden ser actualizados (esta lista es almacenada en la matriz pasada como cuarto argumento).

lForzar

Este argumento, de tipo lógico, controla si Visual FoxPro dará un fallo en el TableUpdate si el registro que está siendo actualizado ha sido cambiado por otro usuario mientras estabamos realizando las modificaciones. Un valor de .T. fuerza la actualización aunque haya un conflicto, mientras que .F. provocará que la actualización falle si el registro del disco no está igual que como estaba al principio de las modificaciones.

Recomiendo encarecidamente que este argumento esté siempre a .F.. La razón es que si no lo haces, tu código puede reemplazar las modificaciones de otros usuarios indiscriminadamente. Lo que realmente debes hacer es controlar el valor de retorno del TableUpdate para ver si se ha realizado correctamente o no, y responder en este caso.

cAliasTabla | nÁreaTrabajo

Este argumento es el nombre del área de trabajo al cual le afecta el tableupdate. Puede ser o bien el alias ó el número del área de trabajo (¿Sigue alguien usando números de áreas de trabajo en su código?).

Este es otro de los argumentos opcionales que es recomendado usar siempre. Si se omite este argumento entonces el TableUpdate afectará solo al área de trabajo activa. Como podemos controlar esta situación, es recomendable que le digamos al TableUpdate a que área de trabajo debe afectar y no depender que el área de trabajo haya sido correctamente seleccionada. La razón es que Visual FoxPro está orientado a eventos y puede llegar a suceder que no seas capaz de saber cual es el área de trabajo activa debido a que diversos eventos pueden cambiar el área de trabajo activa. Poniendo el alias en la función TableUpdate eliminarás cualquier posible efecto de equivocarte en el área de trabajo a actualizar.

cMatrizErrores

Este último argumento solo es válido si el primer argumento es 2. Con el primer argumento puesto a 2, la función TableUpdate intentará actualizar todos los registros modificados y si alguno falla se pondrá el número de registro que ha fallado en la matriz cMatrizErrores.

 

Un ejemplo de valores 1 y 2 como primer argumento pueden ayudar a saber que significa todo esto. Vamos a suponer que los registros 1, 3, 5, 6 y 9 han sido modificados en un bufer por tablas. Realizamos un TableUpdate con 1 ó 2 como primer argumento y hay un conflicto de actualización en los registros 3 y 6.

Con un valor de 1 en el primer argumento, la función TableUpdate actualizará el registro 1 y entonces cuando no pueda actualizar el registro 3 parará de intentar actualizar registros y devolverá un .F. indicando un fallo de la operación de actualización. Usar el valor 2 como primer argumento, provocará que la función TableUpdate continúe intentando actualizar el resto de los registros después de que fallara el registro 3. La matriz definida en el cuarto argumento mantendrá la lista de registros que no han podido ser actualizados (3 y 6), el TableUpdate de todos modos devolverá .F. indicando un fallo en la actualización debido a que no se ha completado dicha actualización.

…No Actualizar

Utilizar el almacenamiento de datos en bufer no solo nos permite guardar las modificaciones que ha realizado un usuario, también ofrece un método para descartar las modificaciones. La función TableRevert restaurará un bufer sucio poniendo los valores como estaban almacenados anteriormente en el disco, la sintaxis es la siguiente;

TABLEREVERT([lTodasFilas [, cAliasTabla | nÁreaTrabajo]])

Los dos argumentos de esta función son descritos en las siguientes secciones.

lTodasFilas

Este argumento, de tipo lógico, controla que registros serán restaurados, si es .T. entonces todos los registros sucios serán restaurados en el alias seleccionado, un valor de .F., solo restaurará el registro actual. Si no se especifica este argumento solo afectará al registro actual.

cAliasTabla | nÁreaTrabajo

Es el nombre de alias o número de área de trabajo sobre la cual la función TableRevert actúa. Si no se especifica se hará sobre el área de trabajo activa.

¿Cómo es el vampiro asesino?

Las funciones TableUpdate y TableRevert devuelven valores que indican si han sido realizadas correctamente o con fallos. TableUpdate devuelve un valor lógico .T. indicando que la actualización ha sido completada satisfactoriamente o una valor .F. indicando que la actualización ha fallado en parte o completamente. TableRevert devuelve un número que indica el número de registros que han sido restaurados.

En ambos casos es importante conocer los valores devueltos y reaccionar a ellos. Por ejemplo, el siguiente código podría ser usado para actualizar un bufer de la tabla clientes.

IF NOT TableUpdate(1,.F.,”Clientes”)

   Wait Window “Imposible grabar su trabajo”

   TableRevert(.T.,”Clientes”)

ENDIF

La sentencia IF comprueba el valor devuelto por el TableUpdate y actúa en consecuencia. El TableRevert es necesario porque un TableUpdate que falle no actualiza la tabla, pero deja el bufer sucio y este debe ser limpiado antes de cerrar la tabla. Hay otras maneras de controlar fallos del TableUpdate, por ejemplo;

IF NOT TableUpdate(1,.F.,”Clientes”)

  IF MessageBox(“Imposible grabar su trabajo. ¿Quieres descartar los cambios?”,;

                 MB_YESNO,”Actualización fallida”) = IDYES

    TableRevert(.T.,”Clientes”)

  ENDIF

ENDIF

En este caso el fallo del tableupdate originará que el usuario reciba un message box preguntándole si desea descartar los cambios. Si se escoge "si" entonces la tabla es restaurada, si no se mantendrá en el formulario manteniendo sus cambios intactos. El usuario podrá intentar guardar los cambios mas tarde.

Téngase en cuenta en ambos ejemplos que no se ha puesto una selección del área de trabajo. Esto es debido a que el área de trabajo es especificada en las funciones y así no hay necesidad de seleccionar ningún área de trabajo en particular.

Resumen

En el ultimo artículo hemos revisado el sistema de almacenamiento de tablas en bufer a grandes rasgos y descubrimos lo que puede hacer por nosotros. Vimos que el almacenamiento en bufer puede hacer que nuestro código sea mas simple y fácil de entender

En este artículo han sido revisadas las dos principales funciones que afectan al almacenamiento en bufer, TableUpdate() y TableRevert(). Estas dos funciones nos dan un control definitivo acerca de si las modificaciones en el bufer serán escritas en el disco y cuando serán realizadas.

 

16 de mayo de 2018

Buffering - Parte 1

Buffering Parte I

 

Buffering, El Vampiro asesino

En FoxPro 2.x hemos gastado un montón de esfuerzos para preservar los valores de los registros de una tabla, así dábamos la oportunidad al usuario de deshacer las modificaciones realizadas. Visual FoxPro nos ofrece un mecanismo para realizar esta tarea mas fácilmente. La herramienta es el bufering de datos. En este artículo y el siguiente examinaremos los matices del bufering de datos y las otros necesidades en Visual FoxPro. El tema de este mes son los principios básicos del bufer de datos.

Usar buffering o nó

La primera pregunta es, ¿debo usar bufer de datos para todo? ó debo seguir con el SCATTER y GATHER para solucionar el problema? Bien, la respuesta no es simple. SCATTER y GATHER requieren que el rango de las variables o matrices sean definido correctamente, y así sean visibles a todos los objetos que necesiten acceso a ellas. Puesto que las variables se definen por defecto como privadas (es decir, son destruidas cuando la rutina que las creo termina), entonces tenemos un problema.

Si en el método de un formulario hago un SCATTER MEMVAR, las variables creadas se van fuera de rango (es decir se destruyen), tan pronto como el método finaliza, a menos que declare las variables como PUBLIC (publicas) antes de hacer el SCATTER. Declarar variables como publicas tiene otra serie de problemas que van más allá de este artículo, así que vamos a ponernos de acuerdo en que no queremos declarar las variables como públicas a menos que no haya absolutamente otra forma para hacerlo.

Con los controles de VisualFoxpro y su capacidad de vincularse a datos, tiene sentido tener los datos disponibles durante la creación del control. La secuencia de creación es tal, que el Entorno de Datos (DE) de un formulario se crea antes de que se creen los controles. Esto permite al Entorno de Datos abrir los cursores de datos, antes de que los controles intenten vincularse con sus controlsources. Si nosotros vinculamos los controles directamente con los campos del cursor todo funcionará bien, sin embargo, si estamos vinculándolos a variables de memoria debemos asegurarnos que dichas variables existen en el momento que los controles son creados.

Esta creación de variables puede ser realizada en el evento Load del formulario (el cual se dispara antes de que los controles sean creados), declarando las variables como públicas y entonces haciendo un scatter . Vaya! aquí esta el tema de las variables publicas (donde está un vampiro asesino cuando necesitas uno).

El bufer de datos nos da lo mejor de ambos mundos, podemos vincularlo a los campos y podemos controlar la actualización o deshacer las modificaciones del registro sobre el disco. Vinculándolo directamente a los campos, todo el problema del rango de las variables desaparece.

¿Hay alguna situación donde puedes no desear el buffering? Si, si una parte de las necesidades es que las modificaciones son grabadas inmediatamente e irrevocablemente. En esta situación no usar el bufer de datos es el camino a seguir. También, si usas una tabla solo para lectura, entonces puedes definir el buffering a ninguno.

¿Que es el bufer (buffering) de datos?

En FoxPro 2.x, cuando definíamos nuestros GETs contra los campos de una tabla, decíamos que hacíamos ediciones directas. Las llamábamos directas porque asumíamos que se editaban directamente los campos de la tabla. No teníamos control sobre el proceso de actualización. De hecho, Foxpro 2.x hacia la edición del registro en un bufer de memoria que podía ser después usado para actualizar la tabla. En Foxpro 2.x no teníamos control sobre el bufer, él podría ser escrito cuando Foxpro se saliera de él y no había ninguna manera de que pudiéramos parar lo que estuviera sucediendo.

En Visual FoxPro tenemos el bufer de datos, que nos da un control sobre este buffer de edición. Nos permite, cuando está activado el buffering, controlar cuando y como se graba el bufer al disco. El bufer de datos es simplemente una tecnología que hace más fácil lo que hacíamos antes.

El bufer de datos de Visual Foxpro añade alguna funcionalidad al proceso. Con el uso de las funciones tales como OldVal() y CurVal() podemos saber que valores tenían inicialmente el bufer y lo que esta grabado en el disco respectivamente. Podremos encontrar mas funciones para mejorar el bufer de datos en VFP tanto como vayamos explorando más.

¿Porque hay 5 ó 6 modos de buffering?

Actualmente hay cinco modos para definir el buffering, pero en la propiedad BufferModeOverride de un cursor del Entorno de Datos podrás ver seis opciones. La opción adicional es "Usar el ajuste del formulario", el cual causa que VFP ponga la propiedad  BufferMode como el formulario y se decide en modo tabla o fila en función del tipo de control que se está usando (más información sobre esto más adelante).

Los seis modos de buffering son:

·         Ninguno (0)

·         Usar configuración del formulario (1)

·        Pesimista de Filas (2)

·        Optimista de Filas (3)

·         Pesimista de Tablas (4)

·         Optimista de Tablas (5)

El valor Ninguno (0) desactiva el bufer de datos y provoca que VFP actúe como lo hacía Foxpro 2.x, respecto a la edición de datos. Los otros (del 2 al 5) son Optimista/Pesimista por Fila/Tabla.

Optimista

Con modo de bufer optimista, VFP no bloquea ningún registro cuando empieza la edición. En vez de eso, cuando sucede un intento de actualizar la tabla, VFP comprueba si el registro que se está actualizando es el mismo que el que empezó nuestro bufer. Si son iguales, se realiza la actualización, si no la actualización no es realizada (mas sobre esto mas adelante).

Pesimista

Con modo de bufer pesimista, VFP intenta bloquear un registro cuando se inicia una edición, si se puede bloquear se permite la edición. Si VFP no puede obtener el bloqueo entonces se produce un error ("El registro esta siendo accedido por otro usuario") y se rechaza la edición.

Fila

El bufer de Filas permite ensuciar un solo registro del buffer a un tiempo. Ensuciar, tal y como es usado aquí, significa que el registro ha sido editado en el bufer. Un intento de mover el puntero de registros en un cursor que tiene buffer por filas causara que VFP intente actualizar la tabla.

Tabla

El bufer de Tablas permite que haya múltiples registros sucios en el bufer del cursor. Mover el puntero de registros no tiene ninguna actividad de actualización implicíta.

Combinando Optimista/Pesimista con Fila/Tabla obtienes cuatro modos distintos de buffering a mayores de ninguno.

¿Cual es el modo correcto?

En primer lugar vamos a echar un vistazo en cuanto a Optimista en contra de Pesimista. Pesimista bloquea el registro en el servidor. Pesimista asegura el derecho a grabar antes de que se haya realizado la edición. Esto da lugar a pensar que hay muy buenas razones de peso para preferir el modo de bufer pesimista. Bien, antes de que realicemos conclusiones vamos a ver los inconvenientes del modo de bufer pesimista.

El modo de bufer pesimista bloquea el registro cuando se inicia la edición y mantiene ese bloqueo hasta que sucede la actualización. Esto significa que si María empieza a editar y se marcha a comer, nadie puede trabajar con el registro que María estaba editando. Pesimista utiliza los bloqueos en el servidor de red, lo cual consume recursos del servidor. A menudo el servidor de red tiene un numero limitado de bloqueos simultáneos y si tu numero de usuarios crece, podrás llegar a tener errores de red cuando el modo de bufer pesimista intenta bloquear un registro y no puede.

El modo de bufer optimista no realiza bloqueos seguros hasta que sucede la actualización. Se realiza el bloqueo, se hace la actualización y se libera el bloqueo. Estos bloqueos son mantenidos sencillamente. Pero el modo de bufer optimista puede fallar en la actualización debido a que en ese momento haya un bloqueo pesimista. ¿No es esto una buena razón para preferir pesimista? No, usando el modo de bufer optimista uno puede garantizar su propio bloqueo cuando se inicia la edición y liberar el bloqueo cuando se quiera, así que el modo de bufer optimista puede dar la misma funcionalidad que pesimista. El problema, tal y como yo lo veo, es que el modo de buffer pesimista no me ofrece ningún control sobre que es lo que se debe bloquear y cuando (sucede por arte de magia) mientras que el modo de bufer optimista me da el control. ¡Odio la magia! Así que mi preferencia es optimista para todo. Si necesito garantizar la grabación de información, lo realizaré en mi código.

¿Y acerca de Fila en lugar de Tabla? El mismo razonamiento que utilice para optimista en lugar de pesimista, también puede ser utilizado aquí. Modo de bufer por fila, solo permite ensuciar un registro a un tiempo y realizar una actualización "mágica" cuando se mueve el puntero de registro. El modo de bufer en tablas no realiza ningún tipo de magia, porque permite ensuciar múltiples registros al mismo tiempo, no necesita actualizar cuando el puntero de registros se mueve. Se puede restringir la edición de un solo registro al mismo tiempo, mediante el interface de usuario, no permitiendo moverse a otro registro mientras se está en la edición.

¿Porqué la actualización implicíta es un problema? VFP es orientado a objetos, de tal manera que creamos clases que tienen un comportamiento específico. Tenemos que intentar hacer estas clases genéricas, de modo que puedan ser utilizadas en múltiples lugares y ocasiones. Si creas un formulario, utilizas bufer de filas, y después añades un control basado en una clase que busca algo dentro de un cursor, tu podrás obtener una actualización automática no deseada sobre el bufer. No solo puede suceder, sino que puede ser una pesadilla de ejecutar y depurar. Con bufer en tablas no hay nada de magia y así el problema se convierte en ningún problema.

Así que si leo correctamente lo anterior la conclusión es usar el modo de bufer optimista por tablas en cualquier situación. ¡Vaya! Si esto es lo que hago. Si yo necesito editar solo un registro, lo codificaré. Si necesito garantizar las grabaciones lo codificaré.

¿Donde y como defino el modo del bufer?

Tienes una serie de métodos para definir los modos del bufer. Están en la propiedad BufferMode del formulario, la propiedad BufferModeOverride del cursor, y usando la función CursorSetProp().

Propiedad BufferMode del formulario

La propiedad BufferMode de un formulario tiene tres opciones. Son 0-Ninguno, 1-Pesimista, y 2-Optimista respectivamente. La opción fila o tabla del modo del bufer es manejada por el tipo de control que está vinculádo a la tabla, si se vincula a un grid se utiliza modo de bufering tabla y si se vincula a otro control se utiliza el modo bufer de filas.

Propiedad BufferModeOverride del cursor

La propiedad BufferModeOverride de un cursor permite seis opciones. Son 0-Ninguna, 1-Usar configuración del formulario, 2-Pesimista de Filas, 3-Optimista de Filas, 4-Pesimista de Tablas, 5-Optimista de Tablas. El BufferModeOverride debe ser definido por cada cursor del Entorno de Datos si se quiere uno diferente al 1-Usar configuración del formulario (por defecto).

CursorSetProp()

La función CursorSetProp() puede ser utilizada programaticamente para definir el modo del buffer, La función CursorSetProp() tiene la siguiente sintaxis;

CURSORSETPROP(cPropiedad [, eExpresion] [, cAliasTabla | nAreadeTrabajo])

Para definir los modos de almacenamiento en bufer, el primer argumento es "BUFFERING", el segundo es el número del modo de bufer como se explico en la sección BufferModeOverride hace un momento, y el último es el nombre del alias del cursor en donde se quiere definir el modo de bufer. Por ejemplo si queremos definir un cursor (con alias Clientes) con modo de almacenamiento optimista de tablas yo haría lo siguiente:

CursorSetProp(“buffering”, 5, ”Customer”)

Para definir el modo de bufer con la función CursorSetProp() debe estar activada previamente la opción SET MULTILOCKSS ON (estará off por defecto a menos que lo hayas cambiado en el cuadro de dialogo Herramientos-Opciones). Definir los modos de almacenamiento en bufer requiere un multilocks explícito, así el formulario hará los bloqueos de múltiples registros por ti..

¿Como se controlan las actualizaciones?

El proceso de actualización está controlado por dos funciones: TableUpdate() y TableRevert(). El primero realiza una actualización del registro y el segundo deshace las modificaciones y devuelve al bufer los datos anteriores en el registro. El mes siguiente investigaremos estas dos funciones en mayor profundidad.

Resumen

Lo podemos hacer por el camino difícil o por el fácil. Utilizar almacenamiento de datos en bufer es el camino mas fácil, una vez que se entienda como funciona. Lleva menos codificación para conseguir los mismos resultados de la tecnología de Foxpro 2.x y permite el aprovechar de las capacidades de vinculación de datos de los controles de VFP. Hay una multitud de opciones referidos al bufer de datos. Un examen cuidadoso de cada una de estas opciones nos harán mas simple la decision.