23 de noviembre de 2015

Usando la Nueva clase Empty de VFP 8

Este es un Ejemplo de Como Aprovechar la Nueva Clase "Empty" que nos proporciona VFP8.

Esta Rutina, nos permite Leer Los Nombres de las variables que usaremos en Nuestro Programa, en una Tabla y su Respectivo Valor, para No Tener que recompilar el programa cada vez que tengamos que cambiar el valor de una variable.

En Lugar de Variables Publicas, podemos tener un Objeto Global, que contendra una propiedad por cada Registro que encuentre en la tabla, y su valor correspondiente.

La Ventaja es que el objeto solo contendra propiedades, ningun metodo o evento, por lo tanto sera mas que liviano. y pueden ser facilmente pasado como parametro a otras aplicaciones nuestras variables.

El Codigo Es Este:

Define Class oEntorno As Session
  Procedure LeerConf
  Lparameters cArchivo, cCampo, cValor
  If Type('cArchivo')<>'C' Or Type('cCampo')<>'C' Or Type('cValor')<>'C' Or ;
    Len(Alltrim(cArchivo))= 0 Or Len(Alltrim(cCampo))= 0 Or Len(Alltrim(cValor))= 0
    Wait Window 'Error del Programador!'
    Return .F.
  Endif
  Local uValor, cCad, nCiclo, oProp, cAlias
  Use In Select('Origen')
  Use (cArchivo) Alias Origen Again In 0 Shared
  cCad=',.*+/- =()[]{}<>^&%$#"!@)(¿?'+"'"
  Select Distinct (&cCampo) As cOpcion, (&cValor) As uValor From Origen Into Cursor CurProp Readwrite
  If Reccount('CurProp')>0
    Update CurProp Set cOpcion =Chrtran(Alltrim(CurProp.cOpcion), cCad, Replicate('_',Len(cCad)))
    oProp = Createobject("Empty")
    Select CurProp
    Scan
      AddProperty(oProp, Alltrim(CurProp.cOpcion),'')
      Do Case
      Case Alltrim(Upper(transform(curprop.uValor)))='.NULL'
        cCad = [Store .Null. to Oprop.]+Alltrim(CurProp.cOpcion)
      Case Type(Evaluate("curprop.uValor"))='L'
        cCad = [Store ]+ Upper(Transform(CurProp.uValor))+[ to Oprop.]+Alltrim(CurProp.cOpcion)
      Case Getwordcount(CurProp.uValor,'/-')=3 And Len(Alltrim(CurProp.uValor))=10
        cCad = [Store {^] +Alltrim(CurProp.uValor)+ [} to Oprop.]+Alltrim(CurProp.cOpcion)
      Case Type(Evaluate([curprop.uValor]))='N'
        cCad = [Store ]+ Upper(Transform(CurProp.uValor))+[ to Oprop.]+Alltrim(CurProp.cOpcion)
      Otherwise
        cCad = [Store "]+ Upper(Transform(CurProp.uValor))+[" to Oprop.]+Alltrim(CurProp.cOpcion)
      Endcase
      &cCad
    Endscan
  Endif
  Use In Select('CurProp')
  Use In Select('Origen')
  Return oProp
  Endproc
Enddefine

Los parametros que deben pasarse son:

  • La ruta al Archivo que leeremos
  • El Nombre del Campo que contendra los nombres de las propiedades
  • El Nombre del Campo que contendra los valores

Para Usarlo, suponiendo que Nuestra Tabla es: "C:\configuravariables.dbf" y la estructura es:

Id Numeric(2), Nombre char(16), Valor Char(200)

Hariamos

Set Library to oEntorno.prg  && donde oEntorno.prg es el nombre con que grabaron esta rutina
Local oEnt
Public oGlobal
oEnt = CreateObject("oEntorno")
oGlobal = oEnt.LeerConf("C:\configuravariables.dbf","Nombre","Valor")

y a partir de este momento, oGlobal ya tiene tantas propiedades como registros en la tabla.

NOTA: Ambos campos tienen que ser Caracter. La rutina intenta detectar el tipo de dato que es, para las variables de tipo Date, debe ir almacenada en el formato AAAA/MM/DD. Acepta registros con campo =Null para los valores.

Saludos desde Guatemala.

Jorge Mota

18 de noviembre de 2015

Conocer si un DLL esta registrada antes de Instanciarla

Una forma de evitar que un error se produzca si tu DLL o Servidor COM no estuviara registrado en la PC de producción.

Suele suceder que si algún componente externo de tu aplicación no este registrado y al momento de querer instanciarlo via CREATEOBJECT(), arrojandonos el Error #1733 "No se encuentra la definición de clase ..."

El codigo para evitarlo es relativamente sencillo. Utilizando la clase Registry que está incluido en Visual FoxPro dentro de las Fox Foundation Classes (FFCs).

loRegistry = NEWOBJECT("Registry",HOME(1)+"ffc\registry.vcx")
IF loRegistry.Iskey("zipit.cgzipfiles")
   *** Hacer lo propio...
ELSE
    Messagebox("No está registrado el componente de Compresión")
END

Espero les sea de utilidad.

Espartaco Palma Martínez

16 de noviembre de 2015

Trucos de WSH

Siempre es interesante conocer algunas de las funciones del WSH, esto brinda un poco mas de dinamica a nuestras aplicaciones.

OBTENER NOMBRE DEL PC, DOMINIO Y USUARIO
WshNetwork = CreateObject('WScript.Network')
lcMessage='Domain = ' + WshNetwork.UserDomain + CHR(13)
lcMessage=lcMessage+ 'Computer Name =' + WshNetwork.ComputerName+CHR(13)
lcMessage=lcMessage+ 'User Name = ' + WshNetwork.UserName
MESSAGEBOX(lcMessage)
OBTENER INFORMACION SOBRE LAS UNIDADES DE CD
LOCAL strComputer
Local lcString
strComputer = '.'
lcString = ''
objWMIService = Getobject('winmgmts:'+ 'impersonationLevel=impersonate}!\\' + strComputer + '\root\cimv2')
colItems = objWMIService.ExecQuery('Select * from Win32_CDROMDrive')
For Each objItem In colItems
    lcString = lcString + 'Description: '+objItem.Description+Chr(13)
    lcString = lcString + 'Name: '+objItem.Name+Chr(13)
    lcString = lcString + 'Manufacturer:' +objItem.manufacturer+Chr(13)
    lcString = lcString + 'Media type: '+objItem.mediaType+Chr(13)
    lcString = lcString + 'PNP Device ID:' + objItem.PNPDeviceID +Chr(13)
Next
Messagebox(lcString)
MAPEAR UNA UNIDAD DE RED
oNet = CreateObject('WScript.Network')    
oNet.MapNetworkDrive('I','\\myserver\myFiles',.T.,'mike','password')
DESCONECTAR DE UNA UNIDAD DE RED
WshNetwork = CreateObject('WScript.Network')
WshNetwork.RemoveNetworkDrive('E')
CONFIGURAR UNA IMPRESORA POR DEFAUL
oNet = CreateObject('WScript.Network')    
oNet.SetDefaultPrinter('\\ServerName\PrinterName')
OBTENER EL ESPACIO LIBRE EN DISCO
objFSO = CreateObject('Scripting.FileSystemObject')
objDrive = objFSO.GetDrive('C:')
MESSAGEBOX('Available space: ' + chr(13)+TRANSFORM(objDrive.AvailableSpace,'999,999,999,999,999'+' kb' ))
COMO COPIAR UN ARCHIVO DEUN LUGAR A OTRO
FSO = CreateObject('Scripting.FileSystemObject')
FSO.CopyFile('c:\COMPlusLog.txt','c:\x\')
COMO CREAR UNA CARPETA
fso = createobject('Scripting.FileSystemObject')
fldr = fso.CreateFolder('C:\MyTest')
COMO BORRAR UNA CARPETA
fso =createobject('Scripting.FileSystemObject')
fldr = fso.DeleteFolder('C:\MyTest')
DETERMINAR SI UNA CARPETA EXISTE
fso =createobject('Scripting.FileSystemObject')
? fso.FolderExists('C:\MyTest')
COMO CREAR UN ARCHIVO
fso = CreateObject('Scripting.FileSystemObject')
f1 = fso.CreateTextFile('c:\testfile.txt', .T.)
COMO CREAR UN ARCHIVO Y ESCRIBIR EN EL
fso = CreateObject('Scripting.FileSystemObject')
tf = fso.CreateTextFile('c:\testfile.txt', .t.)
tf.WriteLine('Testing 1, 2, 3.') 
tf.WriteBlankLines(3) && Skip three lines
tf.Write ('This is a test.') 
tf.Close
MODIFY FILE 'c:\testfile.txt'
COMO CREAR UN ICINO EN EL ESCRITORIO
Shell = CreateObject('WScript.Shell')
DesktopPath = Shell.SpecialFolders('Desktop')
link = Shell.CreateShortcut(DesktopPath+'\test.lnk')
link.Arguments = '1 2 3'
link.Description = 'test shortcut'
link.HotKey = 'CTRL+ALT+SHIFT+X'
link.IconLocation = 'app.exe,1'
link.TargetPath = 'c:\blah\app.exe'
link.WindowStyle = 3
link.WorkingDirectory = 'c:\blah'
link.Save()
COMO CREAR UNA ENTRADA EN EL REGISTRY DE WINDOWS
oSh = CreateObject('WScript.Shell')
key =  'HKEY_CURRENT_USER\'
oSh.RegWrite( key + 'WSHTest\','testkeydefault')
oSh.RegWrite(key + 'WSHTest\string1', 'testkeystring1')
oSh.RegWrite( key + 'WSHTest\string2', 'testkeystring2', 'REG_SZ')
oSh.RegWrite( key + 'WSHTest\string3', 'testkeystring3', 'REG_EXPAND_SZ')
oSh.RegWrite( key + 'WSHTest\int', 123, 'REG_DWORD')
COMO REMOVER LA ENTRADA
oSh = CreateObject('WScript.Shell')
oSh.RegDelete('HKCU\\Software\\ACME\\FortuneTeller\\MindReader')
oSh.RegDelete('HKCU\\Software\\ACME\\FortuneTeller\\')
oSh.RegDelete ('HKCU\\Software\\ACME\\')

Se que les seran de alguna utilidad.

Disfrutenlas.

Mauricio Henao Romero

11 de noviembre de 2015

Buffer y bloqueos en Visual FoxPro - Resumen

Artículo original: Buffering and Locking in Visual FoxPro - an Overview
http://weblogs.foxite.com/andykramek/archive/2005/10/18/948.aspx
Autor: Andy Kramek
Traducido por: Ana María Bisbé York


¿Dónde comenzamos?

Parece que trabajar con datos en buffer en Visual FoxPro causa mucha confusión. Creo que es en gran medida debido a la confusión en la implementación del buffer y en parte también, por problemas de nomenclatura (por estándares convencionales) asociados al tema.

Por ejemplo, para definir buffer para un archivo DBF de Visual FoxPro, que es una tabla, tenemos que utilizar la función CURSORSETPROP(), que está excesivamente sobrecargada. ¿Por qué no tener una función separada, sin ambigüedades, " SetBufferMode()"? Para confirmar una transacción pendiente el comando es END TRANSACTION. ¿Por qué no "COMMIT" como en el resto de los lenguajes de bases de datos - una opción que incluso es más peculiar ya que el estándar "ROLLBACK" se utiliza para revertir una transacción?

Además, quizás debido a que Visual FoxPro implementa el bloqueo de los registros (lo contrario a "Página"), el hecho de controlar la colocación y liberación de los bloqueos está inseparablemente enlazada con el buffer. Por ejemplo, para permitir buffer de filas, (que solamente opera sobre un sólo registro) SET MULTILOCKS debe estar en "ON". De acuerdo con el archivo Ayuda, esta configuración solamente determina la capacidad de Visual FoxPro para bloquear múltiples registros en la misma tabla - y que de todas formas, está limitado a la actual sesión de datos. Esto no parece ser muy lógico y no es realmente sorprendente que la gente esté totalmente confusa.

¿Qué significa "buffer"?

El principio es realmente muy simple. Cuando hace cambios en algún dato, esos cambios no es escriben en la tabla fuente, en lugar van a un área de almacenaje (el "Buffer") hasta el momento en que le indica a Visual FoxPro que deben ser guardados en el almacén permanente o eliminarlos. Esta área de almacenaje es lo que, en la actualidad, ve en Visual FoxPro cuando se utiliza una tabla de buffer, en realidad un cursor actualizable basado en la tabla original. Todos los cambios se han hecho sobre este cursor y sólo se escriben en la tabla cuando se utiliza el comando "update" adecuado.

Estrategias de buffer

La estrategia de buffer determina cuándo y cómo se almacenan en la tabla los cambios que se encuentran en el buffer. Existen tres opciones:

  • No se emplea buffer: Esta vía es solamente una opción para las versiones anteriores a Visual Foxpro versión 3.0 y es además, el comportamiento predeterminado actualmente para las tablas de Visual FoxPro. Cualquier cambio hecho en una tabla va directamente e inmediatamente a la tabla original. No hay posibilidad de "deshacer" sin que se haya programado explícitamente - utilizando Scatter y Gather, por ejemplo. Se establece al asignar "1" como parámetro a CursorSetProp()
  • Buffer de filas: Los cambios no se han enviado a la tabla original a menos que ocurran una de estas dos cosas. O hay una llamada explícita a la función TableUpdate(), o el puntero del registro se mueve dentro de la tabla original. Vea que CUALQUIER movimiento del puntero del registro, aunque sea iniciado por una tabla que esté en buffer de filas, siempre causa un TableUpdate() "implícito". Establezca 2 ó 3 como parámetro para CursorSetProp().
  • Buffer de tabla: Los cambios nunca se envían automáticamente a la tabla original, a menos que exista una llamada a un comando TableUpdate() o TableRevert() siempre debe afectar los cambios que estén guardados en el buffer. Intentar cerrar una tabla con buffer mientras tiene cambios no cometidos provoca que Visual FoxPro genere un error en la versión 3.0; pero este comportamiento cambió en versiones anteriores por tanto, los cambios pendientes simplemente se pierden. (No hay error; ni advertencia de que los cambios se van a perder). Se establece asignando 4 ó 5 como parámetro para CursorSetProp().

Hasta este punto se está preguntando, por qué hay DOS parámetros posibles para cada una de las estrategias que implementan buffer. La respuesta es, como se indica en la introducción, debido a que hay dos estrategias de bloqueo.

Estrategias de bloqueo

Visual FoxPro necesita bloquear el registro físico en la tabla mientras se realizan los cambios en su contenido y existen dos vías con las que se puede establecer el bloqueo automático (opuesto al uso explícito de las funciones RLock()/FLock())

  • Bloqueo pesimista: El registro se bloquea en cuanto un usuario comienza a hacer cambios (El bloqueo en realidad ocurre en cuanto se oprime cualquier tecla válida). Esto evita que cualquier otro usuario pueda hacer o guardar cambios sobre este registro hasta tanto el usuario actual haya completado sus cambios y liberado el registro tanto guardando o revirtiendo los cambios.
  • Bloqueo optimista: Un intento de bloqueo del registro se hace solamente cuando los cambios se comienzan a enviar a la tabla. Esto significa que incluso mientras un usuario hace cambios en el dato, el registro permanece disponible a otros usuarios, los que también podrían, y posiblemente guardarán, cambios en el mismo registro mientras el primer usuario están aun trabajando en el.

Modos de buffer

El modo de buffer para una tabla es, por tanto, la combinación específica de las estrategias de Buffer y Bloqueo que se aplican. Existe un total de 5 modos de buffer para una tabla como se ilustra en la Tabla 1.

Tabla 1. Modos de Buffer en Visual FoxPro

ModoBloqueoBufferComentarios
1PesimistaNingunoÚnica opción para FP2.x, predeterminado para tablas VFP
2PesimistaFilaEl bloqueo se establece por el evento KeyPress. El movimiento del puntero de registro obliga a Guardar
3OptimistaFilaEl bloqueo se establece por TableUpdate(). El movimiento del puntero de registro obliga a Guardar
4PesimistaTablaEl bloqueo se establece por el evento KeyPress. Guardar se debe iniciar explícitamente
5OptimistaTablaEl bloqueo se establece por el evento TableUpdate(). Guardar se debe iniciar explícitamente

Al trabajar con Visual FoxPro, debemos ser cuidadosos al distinguir entre estrategias individuales, que se especifican directamente para Buffer y Bloqueo, y el modo buffer que resulta de la combinación de ellos. Desafortunadamente, como hemos visto, Visual FoxPro es de por sí, menos cuidadoso con esta distinción.

¿Qué significa todo esto cuando creamos formularios enlazados a datos?

He aquí donde las cosas comienzan a ponerse un poco más complejas (y no es solamente por la nomenclatura). Consideremos una situación normal donde las tablas se agregan al formulario por el entorno de datos nativo. El formulario tiene una propiedad llamada "Buffermode" que tiene tres valores posibles:

  • 0 Ninguno (predeterminado)
  • 1 Pesimista
  • 2 Optimista

Vea que esto se refiere en realidad a las opciones para la estrategia de bloqueo y no tiene nada que ver con el buffer ¡¡ para nada !! El hecho por el cual el formulario determina la estrategia buffer es por sus tablas todas por si mismo, basadas en su utilización. Si un formulario utiliza dos tablas que tienen una relación de uno a muchos, muestran el lado "muchos" de la relación de diferentes formas.

Si la tabla "muchos" se utiliza como origen de datos para el control grid, para el formulario, se abre con buffer de tabla. Sin embargo, si la tabla "muchos" se emplea para enlazar con controles que solamente muestran una fila de la tabla cada vez entonces, el buffer para la tabla va a ser lo mismo que para "una" tabla para toda de configuración de la propiedad Buffermode del formulario.

Si crea un pequeño formulario de prueba y ejecuta todas sus opciones para la propiedad Buffermode de un formulario, puede ver que incluso con la propiedad BufferMode establecida en 0-(Ninguna) el cursor creado por Visual FoxPro está aun abierto en modo buffer de fila cuando las tablas se abren desde el entorno de datos del formulario. Es como si para el formulario "No buffer" y "Bufer de filas" fuera lo mismo.

Sin embargo, este NO es el caso si las tablas se abren directamente con el comando USE. Si las tablas se abren explícitamente en el método Load(), entonces la propiedad Buffermode no tiene impacto en lo absoluto, y las tablas se abren en dependencia de los parámetros apropiados en la Sesión de datos. Para formularios que corren en la sesión predeterminada de datos (DataSession = 1) se utilizan los parámetros especificados en la ventana Opciones. Vea que en la ventana Opciones, las opciones, en realidad establecen la configuración del modo de bufer. Tiene las 5 opciones que se corresponden con lo 5 modos definidos antes, en la Tabla 1, y los que utilizan los mismos identificadores que la función CursorSetProp(). Si el formulario se ejecuta en sesión privada de datos, entonces, las tablas abiertas con el comando USE se configuran en dependencia de la configuración establecida para esa sesión de datos y, de forma predeterminada, no serán alojadas en buffer.

¿Aun confundido?

Lo más que puedo decir es que la situación es realmente como sigue:

  • Para las tablas abiertas por el entorno de datos del formulario, no importa si el formulario tiene Datassesion con valor Default o Private. Las tablas siempre tienen almacenamiento en buffer, al menos en modo Optimista de filas.
  • La propiedad BufferMode del formulario en realidad determina la estrategia de bloqueo, no el modo de buffer; pero solo para tablas que han sido abiertas en el entorno de datos del formulario.
  • En la sesión de datos actual, las tablas que han sido abiertas explícitamente tienen ambas estrategias: buffer y bloqueo establecidas en dependencia de la ventana Opciones.
  • En la sesión Privada de datos, las tablas que han sido abiertas explícitamente tienen sus estrategias de buffer y bloqueo establecidas de acuerdo a los parámetros que se han aplicado para esta sesión (Predeterminado = "No Buffering")
  • Estos resultados se pueden verificar estableciendo varias opciones en un formulario sencillo y mostrando el resultado que se obtiene por CURSORGETPROP("BUFFERING")

Entonces, ¿cómo debemos establecer el buffer para un formulario?

La respuesta corta , como siempre, es "depende". Si utiliza el entorno de datos del formulario para abrir las tablas entonces, puede normalmente dejar la opción al Visual FoxPro. En caso contrario puede simplemente emplear la función CursorSetProp() en su código para configurar cada tabla como sea necesario. De cualquier manera necesita ser consciente de las consecuencias para que pueda programar sus actualizaciones rápidamente.

Utilizar BufferModeOverride

La clase dataenvironment posee una propiedad para cada tabla (o, mejor dicho, "cursor") llamado "BufferModeOverride". Esta propiedad va a fijar el modo del buffer para esa tabla (y sólo esa tabla) a una de las estas seis opciones - si, es correcto, SEIS opciones, no cinco - veamos:

  • 0 Ninguno
  • 1 (Predeterminado) Utilizar la configuración del formulario.
  • 2 Búffer pesimista de fila
  • 3 Buffer optimista de fila
  • 4 Buffer pesimista de tabla
  • 5 Buffer optimista de tabla

Existen dos puntos sobre los que hay que prestar atención. Primero, Vea que mientras los números del 2 al 5 se corresponden con los parámetros de CursorSetProp() y son aquellos mismos que están disponibles desde el diálogo Opciones, el valor requerido para la configuración "ninguno" ahora es 0 en lugar de 1. He aquí otra inconsistencia en la configuración del buffer. Segundo, vea que el valor predeterminado para esta propiedad es "1 - Utiliza la configuración de formularios". Cuando se refiere a configuración de formularios, se refiere, por supuesto a la propiedad "BufferMode", la que como ya hemos visto en realidad se encarga de definir la estrategia de bloqueo a aplicar. No existe configuración del formulario para controlar buffer !!.

Habiendo dicho esto, la configuración de la propiedad BufferModeOverride va a asegurar que la tabla se abre utilizando el modo buffer que usted ha especificado. Al menos esta propiedad ha sido nombrada correctamente, sobreescribe todo lo demás y fuerza la tabla a un modo especial de buffer en todas las situaciones.

Utilizar CursorSetProp()

Independientemente de cómo fue abierta la tabla, siempre puede utilizar la función CursorSetProp() para cambiar el modo buffer de una tabla. Sin embargo, si está utilizando el entorno de datos del formulario para abrir sus tablas, entonces, tiene dos opciones en dependencia de si su formulario utiliza sesión privada de datos o sesión actual de datos. En el primer caso, puede establecer simplemente el modo requerido en la ventana Opciones y olvidarse de ello. Todas las tablas se van a abrir siempre con esa configuración y siempre sabrá en qué estado se encuentra. Si utiliza una sesión privada de datos entonces, necesita hacer dos cosas. Primero, necesita asegurarse de que el entorno de datos está configurado para soportar buffer. Algunos parámetros de entorno se limitan a la sesión de datos actual y puede que necesite cambiar el comportamiento predeterminado o alguno, o todos los siguientes (vea el tema SET DATASESSION en el archivo ayuda para una lista completa de parámetros afectados):

  • SET MULTILOCKS - Debe ser ON para permitir buffer, predeterminado OFF
  • SET DELETED - Predeterminado OFF
  • SET DATABASE - "No database" es el predeterminado en una sesión Privada
  • SET EXCLUSIVE - El predeterminado es OFF para una sesión Privada
  • SET LOCK - Predeterminado OFF
  • SET COLLATE - Predeterminado es "MACHINE"
  • SET EXACT - Predeterminado es OFF

Lo segundo que necesita es especificar explícitamente el modo buffer de cada tabla utilizada en la función CursorSetProp() con el parámetro apropiado porque la configuración en la ventana Opciones no se puede aplicar a la sesión privada de datos.

Entonces, ¿qué modo de buffer debo utilizar en mis formulario?

Para nosotros la respuesta es sencilla. Siempre debe utilizar estrategia de buffer de tabla con bloqueo optimista (es decir modo de buffer 5). La razón es simplemente que, con la excepción de creación de un índice, no hay nada que pueda hacer en otro modo que no pueda hacer en este modo. Mientras el buffer de filas puede ser utilizado en desarrollo, no creemos que tienen cabida en una aplicación en funcionamiento. La razón es simplemente que existe muchas formas en las que puede ser desencadenada la función TableUpdate() implícito (causado por el movimiento del puntero de registro) y no todos los que están por debajo de nuestro control directo. Por ejemplo, la función KeyMatch() está definida en el archivo Ayuda, como;

Busca una clave de índice en una etiqueta o un archivo de índice.

Parece suficientemente inofensivo - seguramente buscar un archivo índice no puede causar problemas. Pero una nota (en la sección Observaciones justo al final del tema) indica que:

KEYMATCH( ) devuelve el puntero de registro al registro donde estaba originalmente antes de ejecutar KEYMATCH( ).

Aquí se bloquea! Seguramente "devuelve el puntero de registro" implica que mueve el puntero del cursor - lo que en efecto - hace. La consecuencia es que si está utilizando el buffer de filas y quiere verificar por clave duplicados utilizando keymatch(), puede realizar inmediatamente cualquier cambio pendiente. (Por supuesto, en la versión 6 o después pueden siempre utilizar en su lugar IndexSeek(). Sin embargo, el mismo problema surge cuando muchos de los comandos y funciones que operan sobre una tabla - especialmente el más viejo introducido en FoxPro antes los días de buffer (e.g. CALCULATE, SUM y AVERAGE).

Aun más importante, el hecho de que configure una tabla para buffer de tabla no lo previene de que la tabla como si en realidad hubiera buffer de filas. Ambas funciones TableUpdate() y TableRevert(). Esto puede significar que tiene que escribir un poco más de código; pero puede evitar muchos problemas.

Cambiar el modo de buffer de la tabla

Hemos dicho, al inicio de la última sesión, que puede utilizar siempre CursorSetProp() para establecer o cambiar el modo de buffer de una tabla. Esto es cierto; pero si la tabla ya ha tenido asignado un modo buffer, puede no ser tan sencillo porque al cambiar el estado de buffer forzaría a Visual FoxPro a verificar el estado de cualquier buffer existente.

Si la tabla tiene buffer de filas, y tiene cambios no confirmados, Visual FoxPro los envía y permite cambiar el modo. Si el destino tiene buffer de tablas, y trata de cambiar su modo de buffer mientras existen cambios no confirmados, Visual FoxPro se queja y manda el error 1545 ("El búfer de tablas para el alias "nombre" contiene cambios no confirmados"). Esto es un problema porque no puede indexar una tabla, o hacerla libre una tabla o cursor transactable, los que mientras la tabla tiene buffer, por tanto es la única vía de hacer estas cosas, temporalmente, a buffer de filas. Por supuesto, la solución es siempre suficiente - asegúrese sólo de que no hay cambios pendientes antes de que intente cambiar el modo de buffer.

Controlar datos en buffer en Visual FoxPro

Artículo original: Handling buffered data in Visual FoxPro
http://weblogs.foxite.com/andykramek/2005/12/27/handling-buffered-data-in-visual-foxpro

Autor: Andy Kramek
Traducido por: Ana María Bisbé York


Un par de meses atrás escribí sobre buffer; pero deliberadamente dejé fuera de la discusión dos funciones vitales mientras trabajaba con buffer- nombradas TableUpdate() y TableRevert(). Estas son la base mediante la cual usted, el desarrollador, controla la transferencia de los datos entre el buffer de Visual FoxPro y el origen de datos originales. La función TableUpdate() toma los datos pendientes desde el buffer y los actualiza en la tabla original, mientras TableRevert() refresca los buffers para releer el dato desde el origen de datos. La realización exitosa de otras funciones da como resultado un buffer 'limpio' lo que significa que, para Visual FoxPro, los buffer y el origen de datos son sincronizados.

Controlar el alcance de las actualizaciones

Debido a que Visual FoxPro admite tanto buffer de Filas y de Tablas, ambas funciones de traspaso de datos pueden operar "sólo sobre el registro actual" o sobre "todos los registros modificados". El primer parámetro que se pasa a la función determina el alcance de la operación y ufff tenemos otra confusión posible aquí. Las funciones operan en modo significativamente diferente y tienen tipos diferentes de valores devueltos.

En la versión 3.0 de Visual FoxPro; ambas TableUpdate() y TableRevert() aceptarían solamente un parámetro lógico para determinar el alcance del cambio que están controlando. Pasar un valor de .T., significa que todos los cambios pendientes fueron procesados, mientras .F. restringe las operaciones al registro actual solamente, sin importar el modo de buffer empleado.

TableRevert() devuelve el número de filas que fueron revertidos y no pueden realmente "fallar" - sin que fuera un problema físico, como perdiendo una conexión de red. En una fila de tabla en buffer, o cuando específicamente el alcance como .F., el valor devuelto, por tanto siempre 1.

TableUpdate() devuelve un valor lógico que indica si la modificación especificada es exitosa, independientemente del alcance. En otras palabras, un valor devuelto de .T., indica que todos los registros en el alcance han sido actualizados con éxito. Sin embargo, el comportamiento cuando se utiliza un parámetro lógico para determinar el alcance y la actualización falla por cualquier razón, es que no se genera un error y la función devuelve un valor de .F. dejando el puntero en el registro en que falla.

Si está actualizando un solo registro, esto es muy sencillo; pero si está actualizando múltiples registros en una tabla, y un registro no puede ser actualizado, esto significa que cualquier actualización posterior no puede ser verificada. Pero, después de solucionar el conflicto para el registro que ha fallado, no hay garantía que re-intentar la actualización que no va a fallar al registro más cercano. Esto puede ser un problema!

El comportamiento de TableUpdate() fue, por tanto, modificado en la versión 5 para aceptar incluso un parámetro numérico o lógico para el alcance, donde 0 es equivalente para .F. y 1 para .T. El nuevo comportamiento, que puede ser solamente especificado al pasar "2" como parámetro de alcance, específicamente dirigidos los problemas de actualización de múltiples registros.

Al utilizar buffer de tablas, al llamar TableUpdate() con un parámetro de alcance de "2" Visual FoxPro intenta actualizar todos los registros que tienen cambios pendientes. Sin embargo, si un registro no puede ser actualizado, en lugar de parar, la función registra el número de registro que ha fallado en una matriz (cuyo nombre puede ser especificado como el cuarto parámetro) y continúa tratando de actualizar otros registros cambiados. La función devuelve .F. si cualquier registro falla la actualización; pero al menos tratará de actualizar todos los registros disponibles. La matriz de salida contiene una lista de los números de registros para aquellos registros fallan la actualización.

El segundo parámetro para TableUpdate()

Una diferencia mayor entre la sintaxis de TableUpdate() y TableRevert() es que el anterior puede solamente tomar un parámetro extra lógico, en la segunda posición en la lista de parámetros. Esto controla la forma en la que se comportan las actualizaciones cuando encuentran un conflicto.

De forma predeterminada, Visual FoxPro debe rechazar un cambio cuando un conflicto entre el buffer de datos y se detecta el dato original (vea debajo un debate completo del conflicto y la resolución). Al especificar un valor lógico .T. como el segundo parámetro puede forzar una actualización a que sea aceptada incluso en situaciones cuando debería fallar. Naturalmente esto es algo que deseará hacer de forma predeterminada; pero existen, como veremos luego, situaciones donde este comportamiento no es solamente deseado, sino esencial.

Especificar la tabla a ser actualizada o revertida

Ambas funciones TableUpdate() y TableRevert() operan sobre una tabla al mismo tiempo. El comportamiento predeterminado es que, a menos que específicamente, se determine lo contrario; actuará en la tabla en el área de trabajo seleccionada. Si no hay una tabla abierta en el área de trabajo, hay un error (No se encuentra el alias - Error 13). Ambos, sin embargo, pueden actuar sobre una tabla abierta disponible en la sesión de datos actual y puede aceptar cualquiera de los nombres de ALIAS (el tercer parámetro para TableUpdate(), segundo para TableRevert()) o un número de área de trabajo.

No recomendamos el uso del número del área de trabajo en esto, o cualquiera, situación donde está especificando una tabla diferente que la seleccionada. Tal y como hemos podido ver esta funcionalidad está incluida, sólo por compatibilidad hacia atrás y no tiene lugar en el entorno VFP. Existen dos razones para evitar el uso del área de trabajo. Primeramente, hace su código dependiente de tablas específicas estén abiertas en áreas específicas de trabajo - lo que es aun mayor limitación si piensa cambiar! En segundo lugar, no tiene control real sobre las tablas abiertas en VFP, los cursores o vistas cuando utiliza de cualquier forma el Entorno de datos del formulario. Entonces, liberar el número del área de trabajo en lugar del alias, es una estrategia muy arriesgada e innecesaria.

El único momento que recomendamos el uso del número de área de trabajo es cuando guardamos el área de trabajo actual guardando el valor devuelto de la función SELECT(0). Utilizar el número del área de trabajo en este caso asegura que el área de trabajo actual está vacía, o que las tablas que contienen se cierren durante cualquier operación que esté haciendo, puede aun devolverlo sin error.

Conclusión

Existe mucha funcionalidad y flexibilidad oculta dentro de TableUpdate() y TableRevert(). Al utilizar buffer, necesita tener cuidado de, exactamente cuáles de varias combinaciones de sus parámetros, puede pasarles para asegurar que está utilizando la combinación correcta que necesita. Mientras TableRevert() es bastante simple, TableUpdate() es más compleja y por eso, en la Tabla 2 que muestro debajo se brinda un resumen de algunas combinaciones "prácticas" de parámetros para TableUpdate().

Tabla 2. Opciones de TableUpdate()

Parámetros

AlcanceFuerzaTablaSalidaAcción
0 ó .F..F.  Intenta actualizar la fila actual del alias actual
0 ó .F..T.  Fuerza la actualización del registro actual solamente del alias actual
0 ó .F..F./.T.Alias Intenta forzar todo los registros disponibles de las alias especificadas
1 ó .T..F.  Intenta actualizar la fila actual sólo del alias especificado
1 ó .T..T.   Fuerza la actualización de todos los registros disponibles del alias actual
1 ó .T..F./.T.Alias Intenta actualizar la fila actual sólo del alias especificado. Se detiene en un fallo
2.F.AliasMatrizIntenta actualizar todos los registros disponibles del alias especificado. Nota los fallos; pero no se detiene.
2.T.  Fuerza la actualización de todos los registros disponibles del alias actual/especificado
2.F./.T.Alias MatrizIntenta/Fuerza la actualización de todos los registros disponibles del alias especificado. Nota los fallos; pero no se detiene.

Detectar y solucionar conflictos

La sección anterior trataba sobre los mecanismos para actualizar una tabla con buffer y en mi artículo anterior, recomendé que el Buffer de Tabla optimista debe ser la opción normal para la mayoría de las aplicaciones. Entonces, el próximo problema está, en asegurarse de que los cambios no son la causa de un conflicto de actualización cuando guarda. Uno de los problemas inherentes al utilizar bloqueo optimista en un entorno multiusuario es que es posible para más de un usuario hacer cambios al mismo registro al mismo tiempo. Puede preguntarse ¿porqué este tipo de cosa pudiera ser posible alguna vez?- ¿ está seguro de que dos usuarios no pudieran actualizar el mismo registro al mismo tiempo?

En la práctica, existen muchas situaciones donde puede ocurrir legítimamente. Considere la situación en una Orden de Ventas en el procesamiento del sistema. Cuando se coloca una orden para un elemento, el "stock disponible" actual debe reajustarse para reflejar la reducción. Si dos usuarios, que son controlados por dos operadores simultáneamente, incluyen el mismo elemento en sus órdenes, existe un gran posibilidad de que surja un conflicto. Obviamente, esto puede no ocurrir si el sistema utiliza bloqueo pesimista; pero esto tiene otras consecuencias, generalmente indeseables. En este caso, el segundo operador, que trata de acceder al elemento en cuestión, recibirá un mensaje que el registro está en uso por alguien y no puede hacer modificaciones - no es mucha ayuda ! Aún más, el bloqueo pesimista puede ser utilizado cuando una tabla Visual FoxPro es utilizada directamente como origen de dato - no puede bloquear de forma pesimista una vista de cualquier tipo (ni para datos locales o remotos).

Al utilizar buffer, Visual FoxPro hace una copia de todos los datos como e recuperan de la tabla física, cuando se pide una actualización, se compara esa copia con el estado actual del dato. Si no hay cambios, la actualización es permitida, en otro caso, la actualización se genera un error de conflicto (#1585 para las vistas, #1595 para tablas).

El rol de OldVal() y CurVal()

La base de toda la detección del conflicto está en dos funciones nativas, OldVal() y CurVal(), las que acceden a los cursores intermedios creados por Visual FoxPro cuando utiliza datos en buffer. Como indican sus nombres, OldVal() devuelve el valor de un campo tal y como era cuando el usuario final leyó el dato del origen, mientras CURVAL() devuelve el estado actual del dato en la tabla origen. Estas dos funciones operan desde el nivel de campo y, aunque ambas pueden aceptar una expresión que evalúa una lista de campos, son más usadas para devolver valores de campos individualmente para evitar el problema de solucionar diferentes tipos de datos.

Aquí hay un problema al utilizar CurVal() para verificar el estado de la vista. Visual FoxPro realmente mantiene un nivel adicional de buffer para una vista, la cual, a menos que sea refrescada inmediatamente antes de verificar el valor del campo, puede causar CurVal() que devuelve la respuesta incorrecta.

Entonces, ¿Cómo puedo realmente detectar el conflicto?

Antes de entrar en la discusión de cómo detectar un conflicto, permítanme ser claro sobre la definición de Visual FoxPro sobre lo que es un conflicto. Como he comentado antes, en la discusión sobre buffering, Visual FoxPro realmente hace dos copias de un registro, cada vez que el usuario accede al dato. Una copia está disponible como cursor modificable y es donde un usuario hace los cambios. La otra copia guarda el estado original. Antes de permitir la actualización, Visual FoxPro compara este cursor original con el dato guardado actualmente en el disco. Ocurre un conflicto cuando estas dos versiones del dato no coincide exactamente, y existen dos formas en las que puede ocurrir. La primera, y más obvia, es debido a que el usuario actual hace cambios a un registro y trata de guardar esos cambios después de que otro ha cambiado y guardado el mismo registro.

La segunda es menos obvia y ocurre cuando un usuario hace un cambio; pero entonces los cambios van a sus valores originales. El resultado es que no cambia cuando en realidad hace; pero cuando tratan de "guardar" el registro Visual FoxPro va a ver aun esto como un conflicto debido a que los valores OldVal() y CurVal(), en realidad son diferentes. Para evitar este tipo de error puede simplemente confiar en GetFldState(); pero debe comparar expresamente los valores en el buffer actual con estos en OldVal().

Entonces, habiendo evitado la posibilidad de conflictos cuando el usuario actual no ha hecho ningún cambio; pero sencillamente no trata de confirmar el registro, existen básicamente dos estrategias que puede adoptar para detectar conflictos. La primera es el proceder "Trate y vea". Esto significa simplemente que no trata y detecta conflictos potenciales; pero sólo atrapa el resultado de una llamada para TableUpdate() y cuando falla, determina porqué.

El segundo proceder es "Cinturones y tirantes" en el que cada campo cambiado se verifica individualmente y cualquier conflicto se soluciona antes de intentar actualizar la tabla original. Mientras esto parece más defensivo, y por tanto "mejor" en realidad oculta un problema. En el tiempo que esto ocurre (aunque sea muy pequeño) para verificar todos los cambios contra sus valores actuales, otro usuario puede cambiar exitosamente el mismo registro que está verificando. Entonces, a menos que también haya bloqueado explícitamente el registro antes de comenzar a verificar los valores, la actualización real pudiera fallar. Debido a que queremos realmente evitar explícitamente la colocación de bloqueos, necesita incorporar exactamente la misma verificación del resultado de TableUpdate(), y brinda el mismo control del fallo, lo que es mucho más simple precisamente la estrategia "Intenta y veremos".

Por tanto, a menos que tenga una razón para sobreescribir cambios pre-validados, recomendamos fuertemente que permita que Visual FoxPro detecte los conflictos y justamente atrápelo para aquellos errores que han surgido.

De acuerdo, entonces, habiendo detectado un conflicto de actualización, ¿qué puedo hacer sobre esto?

He aquí cuatro estrategias básicas para actualizar conflictos. Puede escoger una, o combine más de una en una aplicación en dependencia de la situación real:

[1] El usuario actual siempre gana - Esta estrategia es apropiada solamente en aquellas situaciones en las que se asume que son correctos los datos del usuario que está actualmente intentando guardar. Típicamente esto debería ser implementado en la base del ID del usuario, el que es en realidad está haciendo el guardado y debería implementar una regla de negocio que cierta información de una gente es más útil que otra.

Un ejemplo podría ser tener una aplicación donde un operador hable al usuario podría tener derechos de sobreescritura para contactar información para el cliente (en la base que la persona realmente habla al cliente es más probablemente capaz de obtener los detalles correctos). El conflicto puede surgir en este caso cuando un administrador está actualizando un detalle de cliente desde un dato en archivo o última orden, mientras un operador tiene detalles nuevos, directamente desde el cliente. La implementación de esta estrategia en Visual FoxPro es muy sencilla. Simplemente establezca el parámetro "FORCE", (el segundo) en la función TableUpdate() a ".T." y reintente la actualización.

[2] El usuario actual siempre pierde - Esto es exactamente lo contrario a la anterior. Al usuario actual se le permite solamente guardar los cambios siempre que no hay otro usuario que haya hecho cambios. Por el contrario, será implementado normalmente en base al ID del usuario y podría reflejar la probabilidad que este usuario en particular es propenso a trabajar con información "histórica" en lugar de información "actual". La implementación en Visual FoxPro es también muy sencilla. Los cambios del usuario actual se revierten, se recarga la información original y el usuario tiene que hacer cualquier cambio que necesite, una vez más. Esta es, probablemente la estrategia que es adoptada más frecuentemente - pero usualmente en base global.

[3] El usuario actual gana a veces - Esta estrategia es la más compleja de las cuatro a implementar; pero es en la actualidad, bastante frecuente. El principio básico es que cuando ocurre un conflicto de actualización, usted determina si alguno de los campos, que el usuario actual ha cambiado, van a afectar los cambios hechos por otro usuario. Si no, el registro del usuario actual es actualizado automáticamente (utilizando el valor de CURVAL()), entonces esto provoca que es negado el conflicto y la actualización se reintenta. Sin embargo, debido a no puede cambiar los valores devueltos por OldVal(), necesita forzar la segunda actualización.

Incidentalmente, esta estrategia también está dirigida al problema de cómo controlar el conflicto de actualización "falso positivo". Esto ocurre cuando existen discrepancias entre los valores del disco y aquellos en el buffer de usuario, pero los actuales cambios del usuario, no crean en realidad un conflicto con cualquier otro cambio que se haya hecho. Claramente, esta situación no es en realidad un conflicto; pero necesita ser controlada.

Aunque no es trivial, la implementación en Visual FoxPro es relativamente fácil. Primero, la función CURVAL() es utilizada para determinar cómo actualizar el buffer de usuario actual de tal forma que no va sobreescribir los cambios hechos por otro usuario. Entonces, la actualización se aplica utilizando el segundo parámetro FORCE en el TableUpdate() para decirle a Visual FoxPro que ignore el conflicto que van a surgir porque OldVal() y CurVal() no corresponden.

[4] El usuario actual decide - Este es el caso de "Coge todo". El conflicto no falla bajo ninguna regla de negocio reconocida, así que la única solución es preguntarle al usuario cuya acción de guardar desencadena el conflicto que es lo que desea hacer. La idea básica es que usted muestre al usuario que ha desencadenado el conflicto con una lista de valores - aquellos que acaba de entrar) y el valor que hay ahora en la tabla ( es decir con el cual alguien ha cambiado el valor original). El usuario puede entonces decidir si hay que forzar o revertir sus propios cambios. Las herramientas básicas para la implementación de esta estrategia han sido discutidos en secciones anteriores. Todo lo requerido es determinar qué cambios traen conflicto y los presentan al usuario como vía para que el usuario pueda decidir en un campo a campo qué hacer, en base al conflicto. En la práctica, esta estrategia en general se combina con la estrategia [3] mostrada antes, así al usuario sólo se le presenta una lista de campos donde hay un conflicto en los campos que han modificado por ellos mismos.

En el próximo artículo de esta serie, veré el diseño e implementación de una clase que controle un conflicto que puede ser arrastrada a un formulario.

7 de noviembre de 2015

Creando un servidor COM de subproceso múltiple (Parte 3 de 3)

Tercera y última parte del artículo "Creando un servidor COM de subproceso múltiple" escrito por Antonio Muñoz de Burgos y Caravaca (eMans).


Parte 3 de 3

ANEXOS


READ,@...Get/Says
Comandos y funciones de menús, emergentes y barras.
MESSAGEBOX() y WAIT WINDOW


GENERAN ERRORES EN TIEMPO DE EJECUCIÓN

;@…BOX ;@…CLASS ;@…CLEAR
;@…EDIT ;@…FILL ;@…GET
;@…MENU ;@…PROMPT ;@…SAY
;@…SCROLL ;@…TO _ALIGNMENT
_ASSIST _BEAUTIFY _BOX
_CALCMEM _CALCVALUE _CONVERTER
_COVERAGE _CUROBJ _DBLCLICK
_DIARYDATE _FOXDOC _GALLERY
_GENMENU _GENPD _GENSCRN
_GETEXPR _INDENT _LMARGIN
_PADVANCE _PBPAGE _PCOLNO
_PCOPIES _PDRIVER _PDSETUP
_PECODE _PEJECT _PEPAGE
_PLENGTH _PLINENO _PLOFFSET
_PPITCH _PQUALITY _PSCODE
_PSPACING _PWAIT _RMARGIN
_RUNACTIVEDOC _SCCTEXT _SPELLCHK
_STARTUP _TABS _THROTTLE
_TRANSPORT _WRAP ACCEPT
ACTIVATE MENU ACTIVATE POPUP ACTIVATE SCREEN
ACTIVATE WINDOW AGETCLASS() AMOUSEOBJ()
ANSITOOEM() ASELOBJ() ASSERT
ASSIST BAR() BARCOUNT()
BARPROMPT() BROWSE CALL
CHANGE CLEAR DEBUG CLEAR GETS
CLEAR MACROS CLEAR MENUS CLEAR POPUPS
CLEAR PROMPT CLEAR READ CLOSE DEBUGGER
CLOSE FORMAT CLOSE MEMO CNTBAR()
CNTPAD() COL() CREATE
CREATE CLASS CREATE CLASSLIB CREATE COLOR SET
CREATE FORM CREATE LABEL CREATE MENU
CREATE PROJECT CREATE QUERY CREATE REPORT
CREATE SCREEN DEACTIVATE MENU DEACTIVATE POPUP
DEACTIVATE WINDOW DEBUG DEBUGOUT
DEFINE BAR DEFINE BOX DEFINE MENU
DEFINE PAD DEFINE POPUP DEFINE WINDOW
EDIT   FKLABEL()
FKMAX() GETBAR() GETCOLOR()
GETCP() GETDIR() GETEXPR()
GETFILE() GETFONT() GETPAD()
GETPICT() GETPRINTER() HELP
HIDE MENU HIDE POPUP HIDE WINDOW
IMESTATUS() INPUT KEYBOARD
LOAD LOCFILE() MCOL()
MDOWN() MENU MENU TO
MENU() MESSAGEBOX() Comandos MODIFY
MOUSE MOVE POPUP MOVE WINDOW
MRKBAR() MRKPAD() MROW()
MWINDOW() OBJNUM() OBJVAR()
OEMTOANSI() ON BAR() ON ESCAPE
ON EXIT Commands ON KEY ON KEY LABEL
ON PAD ON PAGE ON READERROR
ON SELECTION BAR ON SELECTION MENU ON SELECTION PAD
ON SELECTION POPUP PAD() PLAY MACRO
POP KEY POP MENU POP POPUP
POPUP() PRMBAR() PRMPAD()
PROMPT() PUSH KEY PUSH MENU
PUSH POPUP PUTFILE() RDLEVEL()
READ READ MENU READKEY()
REGIONAL RELEASE BAR RELEASE MENUS
RELEASE PAD RELEASE POPUPS RELEASE WINDOWS
RESTORE MACROS RESTORE SCREEN RESTORE WINDOW
ROW() SAVE MACROS SAVE SCREEN
SAVE WINDOWS SCROLL SHOW GET(S)
SHOW MENU SHOW OBJECT SHOW POPUP
SHOW WINDOW SIZE POPUP SIZE WINDOW
SKPBAR() SKPPAD() SUSPENDER
VARREAD() WAIT WBORDER()
WCHILD() WCOLS() WEXIST()
WFONT() WLAST() WLCOL()
WLROW() WMAXIMUM() WONTOP()
WOUTPUT() WPARENT() WREAD()
WROWS() WTITLE() WVISIBLE()
XMINIMUM() ZOOM WINDOW  


NO GENERAN ERRORES EN TIEMPO DE EJECUCIÓN

 

DOEVENTS    
SET ASSERTS SET BELL SET BORDER
SET BROWSEME SET BRSTATUS SET CONSOLE
SET COLOR SET CLEAR SET CLOCK
SET COVERAGE SET CONFIRM SET CURSOR
SET CPDIALOG SET DEBUGOUT SET DEBUG
SET DEVELOPMENT SET DELIMITERS SET DISPLAY
SET DOHISTORY SET ESCAPE SET ECHO
SET EVENTLIST SET EVENTTRACKING SET FORMAT
SET FUNCTION SET HELP SET INTENSITY
SET MARK OF SET MACDESKTOP SET MACKEY
SET MARGIN SET MESSAGE SET NOTIFY
SET ODOMETER SET PALETTE SET PDSETUP
SET READBORDER SET REFRESH SET RESOURCE
SET SAFETY SET SKIP OF SET STICKY
SET STATUS SET SYSMENU SET TALK
SET TRBETWEEN SET TYPEAHEAD SET VIEW
SET WINDOW SYS(1037) SYS(18)
SYS(103) SYS(2002) SYS(1270)
SYS(2017) SYS(4204) SYS(2016)

 

Entradas en el Registro del Sistema
(enlace del documento: Creando un Servidor COM)

Cuando registramos nuestro componente, se crean varias claves en el registro del Sistema, con las imágenes expuestas podemos ver como quedan en el registro del Sistema.

Podemos ver la Biblioteca en tiempo de ejecución que será usada en la llave: Foxruntime, la ruta donde se encuentra registrado nuestro componente, etc.

Estas entradas se registran en HKEY_CLASSES_ROOTCLSID{aquí GUID generado al compilar}

Si realizas compilaciones donde vuelvas a generar el GUID, es conveniente que previamente quites las entradas del registro, de forma contraria te encontraras que tienes el registro del Sistema con un montón de basura.
(regsvr32 /u MiComponente.dll)


Este el fichero miComponenteWeb.VBR que se ha generado al compilar el componente del ejemplo.

VERSION=1.0.0

HKEY_CLASSES_ROOTmicomponenteweb.MiComponenteWeb = micomponenteweb.MiComponenteWeb
HKEY_CLASSES_ROOTmicomponenteweb.MiComponenteWebNotInsertable
HKEY_CLASSES_ROOTmicomponenteweb.MiComponenteWebCLSID = {C735E044-08DD-4F98-A0A3-6561B4EC10DD}
HKEY_CLASSES_ROOTCLSID{C735E044-08DD-4F98-A0A3-6561B4EC10DD} = micomponenteweb.MiComponenteWeb
HKEY_CLASSES_ROOTCLSID{C735E044-08DD-4F98-A0A3-6561B4EC10DD}ProgId = micomponenteweb.MiComponenteWeb
HKEY_CLASSES_ROOTCLSID{C735E044-08DD-4F98-A0A3-6561B4EC10DD}VersionIndependentProgId = micomponenteweb.MiComponenteWeb
HKEY_CLASSES_ROOTCLSID{C735E044-08DD-4F98-A0A3-6561B4EC10DD}InProcServer32 = micomponenteweb.dll
HKEY_CLASSES_ROOTCLSID{C735E044-08DD-4F98-A0A3-6561B4EC10DD}InProcServer32"ThreadingModel" = Apartment
HKEY_CLASSES_ROOTCLSID{C735E044-08DD-4F98-A0A3-6561B4EC10DD}TypeLib = {3371A7EA-3BDA-4101-84AC-51A32B9F9E3B}
HKEY_CLASSES_ROOTCLSID{C735E044-08DD-4F98-A0A3-6561B4EC10DD}Version = 1.0
HKEY_CLASSES_ROOTCLSID{C735E044-08DD-4F98-A0A3-6561B4EC10DD}Foxruntime = VFP7T.DLL
HKEY_CLASSES_ROOTINTERFACE{5E398B91-C7E1-41DD-B66F-DE22CED073C9} = MiComponenteWeb
HKEY_CLASSES_ROOTINTERFACE{5E398B91-C7E1-41DD-B66F-DE22CED073C9}ProxyStubClsid = {00020424-0000-0000-C000-000000000046}
HKEY_CLASSES_ROOTINTERFACE{5E398B91-C7E1-41DD-B66F-DE22CED073C9}ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046}
HKEY_CLASSES_ROOTINTERFACE{5E398B91-C7E1-41DD-B66F-DE22CED073C9}TypeLib = {3371A7EA-3BDA-4101-84AC-51A32B9F9E3B}
HKEY_CLASSES_ROOTINTERFACE{5E398B91-C7E1-41DD-B66F-DE22CED073C9}TypeLib"Version" = 1.0


; TypeLibrary registration
HKEY_CLASSES_ROOTTypeLib{3371A7EA-3BDA-4101-84AC-51A32B9F9E3B}
HKEY_CLASSES_ROOTTypeLib{3371A7EA-3BDA-4101-84AC-51A32B9F9E3B}1.0 = micomponenteweb Type Library
HKEY_CLASSES_ROOTTypeLib{3371A7EA-3BDA-4101-84AC-51A32B9F9E3B}1.0win32 = micomponenteweb.dll
HKEY_CLASSES_ROOTTypeLib{3371A7EA-3BDA-4101-84AC-51A32B9F9E3B}1.0FLAGS = 0
 

IMÁGENES DEL REGISTRO



Bibliografía y/o documentación adicional:

Biblioteca MSDN & Resource KIT.
Manual de Referencia de Visual FoxPro.


Antonio Muñoz de Burgos y Caravaca
www.emans.com (Web realizada en vFoxPro)
Sevilla - España
Manifestando el apoyo a la comunidad de desarrolladores de MS Visual FoxPro.
 

Todas las marcas aquí mencionadas, están registradas por sus respectivos fabricantes.

Creando un servidor COM de subproceso múltiple (Parte 2 de 3)

Segunda parte del artículo "Creando un servidor COM de subproceso múltiple" escrito por Antonio Muñoz de Burgos y Caravaca (eMans).


Parte 2 de 3

Tenemos algunos comandos que tienen un funcionamiento diferente cuando se ejecuta en servidores de automatización y su funcionamiento fuera de ellos, es decir trabajando normalmente.

SET DEFAULT

El directorio de trabajo de un servidor se controla mediante el Sistema Operativo, por lo tanto si variamos de posición todos los objetos se verán afectados.

Si al iniciar nuestra aplicación establecemos el directorio de trabajo con los comandos SET DEFAULT o CD, como es lógico y deseado afecta a todo el proceso, donde tendríamos que tener cuidad,o es si creamos instancias y en una ellas utilizamos algunos de estos comandos, ya que afectará al proceso donde estén cargados los objetos y a los subprocesos.

SET PROCEDURE

El comando SET PROCEDURE es exclusivo de cada subproceso, pero predeterminada Visual FoxPro ejecuta implícitamente dicho comando cuando se realiza la instancia de un servidor COM (.exe o.dll) de esta forma se consigue tener acceso a cualquiera de los procedimientos que tengamos en nuestro servidor.

Por lo tanto debemos tener mucho cuidado si algunos de nuestros subprocesos ejecutamos el comando SET PROCEDURE TO, ya que estaríamos produciendo una ruptura dentro de la capacidad de acceso a procedimientos del servidor, ya que estamos estableciendo al ruta a ninguna, si bien cierto, que si se crea un nuevo objeto en el mismo subproceso se restablece automáticamente.

SET CLASS

Este comando tiene un tratamiento similar que SET PROCEDURE, ya que también se guarda en un almacén local de subprocesos, debemos de tener cuidado si hemos inicializado librerías de clases con todo el conjunto de bibliotecas que hay en el servidor COM (.exe o .dll), y que en algún subproceso ejecutemos el SET CLASSLIB TO, para eliminar clases de forma independiente podemos usar el comando RELEASE CLASSLIB.


Los ejemplos mostrados son orientativos, para entender el funcionamiento y la forma de como aplicarlos, aunque los ejemplos son la base de inicio, el código debe estructurarse en función al tipo de desarrollo y objetivo deseado.

Obteniendo valores para la página Web .ASP.

Podemos obtener los valores de nuestro componente, de distintas formas:

[* 1] Por medio de propiedades tipo ARRAY
[* 2] Retornando directamente código HTML, JavaSript, XML, etc.
[3] Generando ficheros.

Definición de la clase: MiComponenteWeb.prg
*
DEFINE CLASS MiComponenteWeb AS Session OLEPUBLIC
*
****************************************************
* LA DEFINICIÓN DE ESTA PROPIEDAD, NOS SERVIRÁ PARA
* RECOGER VALORES DESDE LA PÁGINA ASP.
* ESTA SERÍA UNA DE LAS FORMAS.
****************************************************
*
DIMENSION paValores[1,1]
*
FUNCTION Init( )
*
******************
* INICIALIZACIÓN.
******************
*
PUBLIC ARRAY gaValores[1,1]
*
STORE SPACE( 0 ) TO THIS.paValores, gaValores
*
*************************************************
* AQUÍ EN INIT PODEMOS DEFINIR LOS COMANDOS SET
* DE LA FORMA DESEADA PARA EL COMPORTAMIENTO
* DE LOS MISMOS DENTRO DE TODO EL PROCESO.
*************************************************
*
RETURN DODEFAULT( )
*
*******************************************************
* SI ES NECESARIO, AQUÍ PUEDES UTILIZAR LOS OTROS
* EVENTOS, MÉTODOS y PROPIEDADES DE LA CLASE SESSION.
*******************************************************
*
FUNCTION Ejecutar( tcComando, tcFicheroLibreria )
*
**************************************************
* NUESTRA FUNCIÓN EJECUTAR, SERÁ LA INTERMEDIARIA
* PARA LA EJECUCIÓN DE NUESTRO CÓDIGO.
**************************************************
*
* PARÁMETROS:
*
* tcComando:
*
* Comando directo o función que deseamos ejecutar y parámetros si corresponde.
*
* tcFicheroLibreria:
*
* Es nuestra librería, que contiene la función que deseamos ejecutar.
* El nombre de la función a ejecutar vendrá dado en el parámetro tcComando.
*
******************************************
* DEFINICIÓN e INICIALIZACIÓN DE VARIABLES
* LOCALES, UTILIZADAS EN ESTA FUNCIÓN.
******************************************
*
LOCAL lnIndi, lnColumna, luRetVal
*
STORE 0 TO lnIndi, lnColumna
STORE SPACE( 0 ) TO luRetVal
*
IF PARAMETERS( ) <> 0
*
IF .NOT. EMPTY( tcFicheroLibreria )
*
***************************************************
*
[* A] ESTABLECEMOS EL DIRECTORIO POR DEFECTO,
* DEPENDIENDO DE LA POSICIÓN DESEADA,
* EN NUESTRO CASO SIEMPRE PARTIMOS DE LA
* RAÍZ DEL SERVIDOR WEB.
*
*
[* B] Y A PARTIR DEL DIRECTORIO POR DEFECTO,
* ASIGNAMOS LAS RUTAS RELATIVAS.
* DE TAL FORMA QUE A PARTIR DE ESTAS ASIGNACIONES
* EN TODO EL PROCESO UTILIZAMOS RUTAS RELATIVAS.
* LO ESTABLECEMOS CON EL COMANDO SET PATH.
*
*
Nota: También se puede realizar esta asignación
* en la FUNCION Init( ) del componente.
*
***************************************************
*
*
[* A] DIRECTORIO POR DEFECTO [RAÍZ]
*
SET DEFAULT TO
( SUBSTR( JUSTPATH( tcFicheroLibreria ), 1, RAT( "", JUSTPATH( tcFicheroLibreria ), 2 ) ) )
*
*
[* B] PATH RELATIVOS AL RAÍZ.
*
SET PATH TO datos, modulos
*
***************************************************
* ABRIMOS NUESTRO FICHERO DE PROCEDIMIENTOS.
* LA LIBRERÍA CON LA FUNCIÓN, QUE VAMOS A EJECUTAR.
***************************************************
*
SET PROCEDURE TO &tcFicheroLibreria
*
ENDIF
*
*********************************************************
* EJECUTAMOS EL COMANDO o LA FUNCIÓN.
*
* ASIGNADA EN : tcComando
* y CONTENIDA EN : tcFicheroLibreria
*
* AQUÍ ES DONDE SE PROCEDE A EJECUTAR NUESTRO CÓDIGO.
*********************************************************
*
* SI LA FUNCIÓN EJECUTADA RETORNA ALGÚN TIPO DE CÓDIGO
* SE REALIZA LA ASIGNACIÓN A
luRetVal
* ESTA SERÍA OTRA FORMA DE RETORNAR CÓDIGO A LA PÁGINA .ASP
*
luRetVal = &tcComando
*
************************************************************
* UNA VEZ QUE HEMOS EJECUTADO EL CÓDIGO DESEADO.
* RESTAURAMOS EL ENTORNO, EN RELACIÓN A LOS PROCEDIMIENTOS.
************************************************************
*
IF .NOT. EMPTY( tcFichPrograma )
*
CLEAR PROGRAM & tcFicheroLibreria
*
ENDIF
*
***********************************************
*
[* 1] OBTENIENDO VALORES POR MEDIO DE
* PROPIEDADES.
*
* ACTUALIZAMOS LAS PROPIEDADES DEL OBJETO.
* QUE SE UTILIZAN PARA INTERCAMBIO DE DATOS.
*
*
Nota: Podemos crearnos tantas propiedades,
* según las necesidades y casos.
***********************************************
*
DECLARE THIS.paValores[ ALEN( gaValores, 1 ), ALEN( gaValores, 2 ) ]
*
FOR lnIndi = 1 TO ALEN( gaValores, 1 )
*
FOR lnColumna = 1 TO ALEN( gaValores, 2 )
*
THIS.paValores[ lnIndi, lnColumna ] = gaValores[ lnIndi, lnColumna ]
*
ENDFOR
*
ENDFOR
*
ENDIF
*
************************************************************
*
[* 2] RETORNANDO CÓDIGO EN DISTINTOS FORMATOS.
*
* CON LA VARIABLE luRetVal, NUESTRA FUNCIÓN PUEDE RETORNAR
* VALORES y/o CÓDIGO QUE PUEDA SER APLICADO POR ASP o QUE
* SEA ENTENDIBLE DE FORMA DIRECTA POR EL NAVEGADOR WEB.
************************************************************
*
RETURN luRetVal
*
ENDDEFINE
*

Una vez que tenemos el código de nuestro componente, procedemos a compilarlo (ver formas de compilación), y podemos ver las entradas que se producen en el registro del Sistema.

Cuando realizamos la compilación, nos encontraremos con los siguientes ficheros:

Un fichero de biblioteca de tipos (.TLB)

La biblioteca .TBL es un fichero binario que contiene una relación de todas las clases publicadas en nuestro componente (Servidor de automatización), con todos sus eventos, métodos y propiedades.

Un fichero de registro (.VBR)

En el fichero de registro podemos ver todos los identificadores globales que se han generado al compilar nuestro componente, este tipo de ficheros es igual que los ficheros de registro.REG, lo único que se excluye es la ruta de acceso al código.


Nota: Estos ficheros se incluyen por compatibilidad o para NT 4, ya que la información necesaria se incluye en el componente, esto es válido e interpretados por Sistemas Windows 2000 o superiores, por lo que no son necesarios.


Llamada de la DLL desde una página .ASP


En la Web (www.emans.com) tenemos una opción que es ver todas las noticias de PortalFox.

Una forma de realizar dicha opción en .ASP, hubiera sido; el que todo programador de .ASP realiza, es decir escribiría todo o casi toda la codificación en .ASP, utilizando su lenguaje VBA (Visual Basic Script), la conexión y manipulación de la base de datos (Visual FoxPro) se tendría que realizar por medio de ADO y utilizando VBA o por múltiples .DLL

Pues bien, la creación de esta .DLL en Visual FoxPro es justamente para evitar todo eso, de forma que todo lo codifiquemos en Visual FoxPro y sin necesidad de tener amplios conocimientos de .ASP y/o VBA.

El único código que tenemos que tener en .ASP es la instancia del componente y ejecutar nuestra función en Visual Foxpro que es la que se encargará de hacer todo el proceso de recuperación de datos y en otros casos la manipulación de la información, actualizaciones, inserciones, borrados, etc.

Incluso si utilizamos la versión 8 de Visual FoxPro, podríamos utilizar el CursorAdapter, para acceder a otros motores de bases de datos una forma sencilla y simple, se da por entendido que también se puede acceder a datos Visual FoxPro de forma nativa, también por medio de OLEDB.

Si utilizas la versión 8 como es lógico, lo que corresponde es generarse librerías de clases bases para el acceso a datos, basándonos en el CursorAdapter, trabajar con proveedores OLEDB, de forma tal que tengamos una capa para cambiar fácilmente al tipo de motor de base de datos a tratar.

Si realizas una buena abstracción de cada una de las capas, podrás reutilizar y sin cambiar la codificación, las clases tanto para este tipo de aplicaciones o para aplicaciones de escritorio en entorno multiusuarios, realmente esta es una de las grandezas de las nuevas clases que aporta la nueva versión 8 de Visual FoxPro.

Nota: Si realmente lo que deseas es llegar a crear un sistema lo más escalable posible, utiliza el proveedor OLEDB de Visual FoxPro, no trabajes de forma nativa con datos vFox, ya que en un futuro un cambio sería más traumático y costoso, lo que en resumen implica que tu desarrollo no es escalable, de esta forma empezaras a obviar cierto tipo de código y formas de estructuras que no se corresponderán a los aplicativos puramente cliente/servidor.
Esto es un comentario que debes tenerlo en cuenta, que no se puede detallar en unas líneas, pero de momento no se corresponde de forma directa al tema que estamos tratando.


Definición de la página: TodasNoticiasPortalFox.asp

Estructura base de una página .html, puedes utilizar cualquier editor web como puede ser el FrontPage o simplemente utilizar el bloc de notas.

Nota: Este ejemplo es como explotamos la información, no es el código de la captura de la información desde PortalFox, por medio del backend.php existente en PortalFox, este código lo puedes ver directamente desde dicha opción, pulsado el icono de mostrar código fuente del proceso de la opción

<html>
<head>
<meta name="eMans" content="Antonio Muñoz de Burgos y Caravaca">
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<title>PortalFox Noticias</title>
</head>
<body>
<%
'
'----------------------------------------------------------------
' TODO LO QUE ESTE ENCERRADO ENTRE <% %> SERÁ
' PROCESADO EN EL SERVIDOR.
' ES EL IDENTIFICADOR DE ASP.
'
'----------------------------------------------------------------
' INICIO ASP VER TODAS LAS NOTICIAS
' REGISTRADAS EN PORTAL_FOX.
'----------------------------------------------------------------
'
'----------------------------------------------------------------
' DECLARACIÓN DE VARIABLES.
'----------------------------------------------------------------
'
DIM lcRetorno
'
lcRetorno = ""
'
' CREAMOS EL OBJETO BASADO EN NUESTRO COMPONENTE.
'
SET oPasarelaVFP =Server.CREATEOBJECT( "pasarela_ea.pasarela" )
'
' La función Ejecutar: tiene dos parámetros:
'
' (1) Función que deseamos ejecutar.
' (2) En que librería se encuentra ubicada, fisicamente.
'
'----------------------------------------------------------------
' EJECUTAMOS LA FUNCIÓN:
NoticiasPortalFox
' SI NUESTRA FUNCIÓN UTILIZARA PARÁMETROS, SOLAMENTE
' TENDRÍAMOS QUE INDICARLA ENTRO LOS PARÉNTESIS DE
' LA MISMA
'----------------------------------------------------------------
'
lcRetorno=oPasarelaVFP.Ejecutar("NoticiasPortalFox()", "c:ejemplosprgfuncionesWeb.prg" )
'
' LA FUNCIÓN DEVUELVE CÓDIGO HTML.
'
Response.Write( lcRetorno )
'
SET oPasarelaVFP = Nothing
'
%>
</body>
</html>


Definición de la librería de funciones: funcionesWeb.prg

Aquí, en este fichero es donde definimos las funciones de nuestra Web.

Nota:
se pueden utilizar tantas librerías de funciones como se deseen, que se a su vez tengan definiciones de clases, etc.

La función NoticiasPortalFox, es código totalmente vFoxPro, por lo tanto este código no es nada extraño, ya que los pasos que se realiza son los de cualquier función, con la excepción del retorno de datos; que en este caso se realiza generando el código .HTML correspondiente, pero podríamos retornar .XML, DHTML, JavaScript, un objeto para sea analizado y procesado desde .ASP o una mezcla de diferentes lenguajes y objetos.

FUNCTION NoticiasPortalFox( ) contenida en el fichero funcionesWeb.prg

(1)
Declaración de variables locales de la función.

(2) Llamada a otras librerías en el caso de ser necesaria.

SET PROCEDURE TO misFuncionesGenerales, etc. ADDITIVE.

(3) Llamada a INCLUDES en caso de ser necesaria.

#INCLUDE "miIncludeWeb.h"

Entendemos en esto casos que no es necesario utilizar su ruta,
ya que en el módulo ejecutar hemos establecido el SET PATH.

(4) Apertura de la base de datos.

IF DBUSED( "miBaseDatos" )
SET DATABASE TO "miBaseDatos"
ELSE
OPEN DATABASE "miBaseDatos.dbc" SHARED NOUPDATE
ENDIF

(5) Apertura de la tabla en donde se guardan los datos de las noticias,
en este caso dicha tabla almacena el título y el link a portalFox con
la noticia correspondiente.

USE "miTablaNoticias" IN 0 SHARED

(6) Generamos una página .HTML que es la que retornamos a la página .ASP

SELECT( "miTablaNoticias" )
SCAN ALL
TEXT TO lcRetVal TEXTMERGE NOSHOW ADDITIVE
Código HTML en base al diseño que deseamos retornar.
Para insertar valores de variables, en este caso valores
de los campos de nuestra tabla.
<<MiCampo1>> ETC.
ENDTEXT
ENDSCAN

(7) Cerramos la tabla.

USE IN ( "miTablaNoticias" )

(8) Cerramos la base de datos.

SET DATABASE TO "miBaseDatos"
CLOSE DATABASES

(9) Quitamos las librerías cargadas.

RELEASE PROCEDURE "misFuncionesGenerales"

(10) Retornamos los datos.

RETURN lcRetVal


Validando en el lado del Servidor o en el Cliente.

Todo dependerá de cada caso, no existe una regla fija, pero si hay casos que deben de estar claros, con el sistema mostrado podríamos tener la intención de realizar validaciones en el lado del servidor, como el controlar los campos obligatorios de una ficha de entrada, por ejemplo si se encuentran vacíos.

Eso sería tan simple, como pasar los valores a nuestra .DLL y por medio de una función en Visual FoxPro, comprobar su valores y retornar lo que corresponda en caso de no ser correcto.

Pero estaríamos cargando el Servidor sin tener una verdadera razón, ya que esto se podría procesar de forma simple desde el cliente, recordemos que el cliente esta utilizando un navegador para este tipo de aplicación, pues tenemos alternativas muy sencillas como puede ser el escribir una función genérica en JavaScript, que se encargue de realizar este tipo de validaciones siempre que corresponda.

El proceso sería el siguiente;

Realizar la validación en el lado del cliente, por medio de JavaScript.

Si todo es correcto; ejecutar la acción correspondiente
por medio de la .DLL por ejemplo la inserción de registros en la tabla,
donde podemos utilizar el sistema de buffer para mantener la integridad
de nuestros datos para inserciones en más de una tabla en cascada.

Sino es correcto, damos el mensaje directamente en el Cliente por
medio de la función JavaScritp, y no ejecutamos el proceso deinsersión
de datos.


El código valido para este tipo de comprobación sería el siguiente y podríamos aplicarlo a todos los formularios de entrada de datos.

Para estos casos, lo lógico sería tener una librería de funciones en JavaScript, que se cargue en el inicio de nuestra página .HTML y/o .ASP.

La forma de realizar la llamada de dicha librería sería de la siguiente forma;

<html>
<head>
<SCRIPT LANGUAJE="JavaScript1.2" src="miLibreriaBasesEnJavaScript.js" type="text/javascript"></SCRIPT>
</head>
...


Definiendo un función en nuestra librería miLibreriaBasesEnJavaScript.js

Pasemos a crear una función genérica para validar la entrada de datos, en este caso la función se encargará de comprobar que los campos no pueden tener valor vacío.

Nota: Esta función se ejecuta en el lado del Cliente (en el navegador)

//
// ***********************************
// [INICIO] Chequea campos obligatorios.
// ***********************************
//
// PARÁMETROS: toForm : Objeto formulario
//
// Pasamos el objeto formulario, de forma que
// chequeamos los campos que hemos establecido
// como obligatorios en la propiedad objOBLID
//
// Nota: JavaScript distingue entre mayúsculas y minúsculas.
//
function chequeaCamposOBL(toForm)
{
//
// VARIABLE QUE INFORMARÁ DE LOS ERRORES.
//
var lcMsg = ""
var lnIndice = 0
//
// TOTAL DE OBJETOS DENTRO DEL FORMULARIO.
//
for ( lnIndice = 0; lnIndice <= (toForm.elements.length-1); lnIndice++)
{
//
// SOLAMENTE CHEQUEAMOS AQUELLOS CAMPOS QUE
// HEMOS ESTABLECIDO COMO OBLIGATORIOS.
//
if ( toForm.elements[lnIndice].objOBLID != null &&
toForm.elements[lnIndice].objOBLID != "" &&
toForm.elements[lnIndice].objOBLID != "undefined" )
{
//
// CHEQUEAMOS EL VALOR DEL OBJETO.
//
if ( toForm.elements[lnIndice].value == "" || toForm.elements[lnIndice].value == null )
{
//
// FORMAMOS LA EXPRESIÓN DEL MENSAJE.
//
lcMsg = lcMsg +
"El campo [" +
toForm.elements[lnIndice].objOBLID +
"] no puede estar vacio." +
"r" + "n"
}
//
}
}
//
// COMPROBACIÓN PARA RETORNO DE VALOR.
//
if ( lcMsg != "" )
{
//
// CAMPOS OBLIGATORIOS NO RELLENADOS.
//
lcMsg = "ATENCIÓN : Campos obligatorios" + "r" + "n" + "n" + lcMsg
//
alert( lcMsg )
//
return false
}
else
{
//
// SECUENCIA DE CAMPOS OBLIGATORIOS CORRECTA.
//
return true
}
}
//
// ********************************
// [FIN] Chequea campos obligatorios.
// ********************************
//

En el caso de no rellenar los datos, en el cliente se obtendría el mensaje informándole de todos los campos que son obligatorios y que no están rellenos:

Parte 2 de 3


Bibliografía y/o documentación adicional:

Biblioteca MSDN & Resource KIT.
Manual de Referencia de Visual FoxPro.


Antonio Muñoz de Burgos y Caravaca
www.emans.com (Web realizada en vFoxPro)
Sevilla - España
Manifestando el apoyo a la comunidad de desarrolladores de MS Visual FoxPro.

Todas las marcas aquí mencionadas, están registradas por sus respectivos fabricantes.