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

4 de diciembre de 2021

TRY ... CATCH ... FINNALY

A partir de Visual FoxPro 8 existe una nueva estructura de control TRY...CATCH...FINALLY para capturar errores o excepciones que ocurren en tiempo de ejecución.

La estructura TRY...CATCH...FINALLY comienza con una cláusula TRY que marca el inicio de un bloque TRY. En este bloque se puede especificar un grupo de cláusulas que pueden producir un error en tiempo de ejecucion. Si el programa completa el bloque TRY sin generar ninguna excepción, este salta el bloque CATCH y busca el bloque FINALLY y ejecuta las sentencias de ese bloque. Si el bloque FINALLY no existe, el programa ejecuta la primera sentencia despues de la clausula ENDTRY que marca el final de la estructura TRY...CATCH...FINALLY

Capturando el error

Cuándo el error ocurre, el código lanza (THROW) una excepción, el programa ejecuta la primera sentencia del bloque CATCH que maneja dicha excepción.

El programa examina las sentencias CATCH en la orden que estas aparecen para ver si una de ellas pueden manejar la excepción. Si el programa encuentra una sentencia CATCH que maneja la excepción, el programa ejecuta el código correspondiente.

La sentencia CATCH puede contener las clausuals opcionales TO y WHEN. Uno puede especificar una variable de memoria en la cláusula TO para almacenar una referencia a un objeto Exception (nuevo en Visual FoxPro 8), que se crea sólo cuando una excepción ocurre. Si se quiere establecer una condición para ejecutar un bloque CATCH, se puede especificar una expresión en la cláusula WHEN que se debe evaluar Verdadero (.T.) antes de que el bloque CATCH se ejecute.

Las sentencias CATCH trabajan similarmente como una sentencia DO CASE en el que la cláusula WHEN debe evaluar a una expresión lógica. Si las cláusulas TO y WHEN no existen, la sentencia CATCH se evalúa como CATCH WHEN .T. (Verdadero).

Después que el programa ejecuta las sentencias en un bloque CATCH, no vuelve a la sentencia TRY, y no busca otras declaraciones CATCH. El programa busca el bloque FINALLY, si este existe. Si no existe el programa ejecuta la sentencia inmediatemente despues de la clausula ENDTRY. El bloque FINNALY se ejecuta se haya o no generado una excepción.

Sintaxis y ejemplo

Esta es la sintaxis de la estructura de control TRY...CATCH...FINALLY

TRY
   [tryCommands] 
[CATCH [TO VarName] [WHEN lExpression] 
   [catchCommands]]
[THROW eUserExpression]
[EXIT]
[FINALLY 
   [finallyCommands]] 
ENDTRY

Un breve ejemplo del uso de TRY...CATCH...FINALLY

CLEAR
LOCAL ln AS Integer, lc AS Character
LOCAL lo AS Exception 
TRY
  ? "-- TRY --"
  SELECT 0
  USE "TablaNoExistente" ALIAS "MiTabla"
  GO BOTT
  ln = MiTabla.nCampo
  lc = MiTabla.cCampo
  ? ln
  ? lc
CATCH TO lo	&& objeto Exception
  ? "-- CATCH --"
  lo.UserValue = "Aquí hay un error"
  ? ' Comment: ', lo.COMMENT
  ? ' Details: ', lo.Details
  ? ' ErrorNo: ', lo.ErrorNo
  ? ' LineContents: ', lo.LineContents
  ? ' LineNo: ', lo.LINENO
  ? ' Message: ', lo.MESSAGE
  ? ' Procedure: ', lo.PROCEDURE
  ? ' UserValue: ', lo.UserValue
  ? ' StackLevel: ', lo.StackLevel
FINALLY
  ? "-- FINALLY --"
  IF USED("MiTabla")
    USED IN "MiTabla"
  ENDIF
ENDTRY
? "-- ENDTRY --"

Prioridad de los manejos de errores

Si un error ocurre en el método de un objeto que se llama en un bloque TRY, Visual FoxPro sigue el procedimiento del manejo del error para ese objeto particular. Este protocolo proporciona una manera para mantener una encapsulation y control de los componentes.

Por ejemplo, si la línea MiForm.Refresh() en un bloque TRY genera un error, si el método Error existe para manejar el error, entonces el método Error toma la precedencia. Si ningún método Error existe, entonces la sentencia CATCH procura manejar el error.

Veamos algunos ejemplos de estas prioridades:

Ejemplo 1: La clase MiClase posee un método Error, y este toma precedencia al generarse un error.

CLEAR
lo = CREATEOBJECT("MiClase")
lo.MiMetodo1()

DEFINE CLASS MiClase AS CUSTOM

  PROCEDURE MiMetodo1
    TRY
      THIS.MiMetodo2()
    CATCH TO loErr
      ? "-- CATCH --"
      ? "ErrorNo:", loErr.ErrorNo
      ? "LineNo:", loErr.LINENO
      ? "Procedure:", loErr.PROCEDURE
      ? "Message:", loErr.MESSAGE
    ENDTRY
  ENDPROC

  PROCEDURE MiMetodo2
    ? PROGRAM()
    *-- La variable luY no existe. El evento ERROR maneja este error.
    luX = luY
  ENDPROC

  PROCEDURE ERROR(nError, cProcedure, nLine)
    ? "-- ERROR --"
    ? "ErrorNo:", nError
    ? "LineNo:", nLine
    ? "Procedure:", cProcedure
    ? "Message:", MESSAGE()
  ENDPROC

ENDDEFINE

Ejemplo 2: La clase MiClase no posee un método Error, en esta caso el manejo de error lo maneja la claúsula CATCH.

CLEAR
lo = CREATEOBJECT("MiClase")
lo.MiMetodo1()

DEFINE CLASS MiClase AS CUSTOM

  PROCEDURE MiMetodo1
    TRY
      THIS.MiMetodo2()
    CATCH TO loErr
      ? "-- CATCH --"
      ? "ErrorNo:", loErr.ErrorNo
      ? "LineNo:", loErr.LINENO
      ? "Procedure:", loErr.PROCEDURE
      ? "Message:", loErr.MESSAGE
    ENDTRY
  ENDPROC

  PROCEDURE MiMetodo2
    ? PROGRAM()
    *-- La variable luY no existe y 
    *-- el método ERROR no existe. CATCH maneja este error.
    luX = luY
  ENDPROC

ENDDEFINE

Mas información

Pueden ver mas información de la estructura de control TRY...CATCH...FINNALY en:

Fox.Wikis: http://fox.wikis.com/wc.dll?Wiki~TryCatch


4 de septiembre de 2021

Configurando una aplicación usando JSON

Título original: Application Configuration using JSON
https://doughennig.blogspot.com/2020/10/application-configuration-using-json.html
Autor: Doug Hennig
Traducido por Luis María Guayán


Después de ver una la presentación de Andrew MacNeill sobre JSON, me inspiré para ver nfJSON, un proyecto VFPx de Marco Plaza. Este gran proyecto agrega soporte JSON a las aplicaciones VFP. Lo que modificó mi interés fue la capacidad de convertir una cadena JSON en un objeto VFP y viceversa con una sola línea de código.

Mi primer pensamiento fue usar esto para los parámetros de configuración. Casi todas las aplicaciones necesitan parámetros de configuración: es mejor leer los parámetros como la información de conexión de la base de datos, ubicaciones de archivos, configuración de correo electrónico, etc. desde una fuente de configuración en lugar de "hardcodearlos" en la aplicación. He usado el Registro de Windows, archivos DBF, INI y XML en varias ocasiones, pero todos requieren codificar manualmente la lectura y escritura entre la fuente y los objetos VFP que contienen la configuración. Con nfJSON, es solo una línea de código.

He creado una clase contenedora llamada SFConfiguration. Solo tiene tres métodos:

  • Load devuelve un objeto con propiedades que coinciden con los pares de nombre/valor en el JSON contenido en el archivo de configuración especificado. Si el archivo no existe o está vacío (como la primera vez que se ejecuta la aplicación), llama a GetDefaultSettings (que se describe a continuación) para obtener la configuración predeterminada.
  • Save guarda las propiedades del objeto de configuración especificado en el archivo especificado en el nombre de archivo pasado o en la propiedad cSettingsFile si no se pasa un nombre de archivo.
  • GetDefaultSettings devuelve JSON para la configuración predeterminada. Puede usar esto de dos maneras: subclase SFConfiguration y anular GetDefaultSettings para devolver el JSON deseado, o establecer la propiedad oSettings en un objeto de configuración que contenga la configuración predeterminada.

A continuación, se muestra un ejemplo del uso de esta clase para obtener la configuración del correo electrónico:

loConfig = createobject('SFConfiguration')
loConfig.cSettingsFile = 'email.json'
loConfig.oSettings     = createobject('EmailSettings')
loSettings = loConfig.Load()
* loSettings contiene la configuración de correo electrónico del usuario;
* si email.json no existe, la configuración de la clase 
* EmailSettings se utiliza como predeterminada.
* Después de que el usuario ingrese la configuración deseada en algún 
* cuadro de diálogo, guárdelos usando:
loConfig.Save(loSettings)

define class EmailSettings as Custom
    Email      = 'dhennig@stonefield.com'
    MailServer = 'mail.stonefield.com'
    Port       = 25
    UseSSL     = .F.
    UserName   = 'dhennig'
    Password   = 'mypw'
enddefine

Así es como se ve el objeto de configuración:

Así es como se ve el JSON guardado:

Aquí hay otro ejemplo, esta vez usando una subclase de SFConfiguration para lo mismo:

loConfig = createobject('SFEmailConfiguration')
loConfig.cSettingsFile = 'email.json'
loSettings = loConfig.Load()
* loSettings contiene la configuración de correo electrónico del usuario; 
* si email.json no existe, la configuración de la clase 
* SFEmailConfiguration a continuación se usa como predeterminada.

define class SFEmailConfiguration as SFConfiguration
    function GetDefaultSettings
        text to lcSettings noshow
            {
                "email":"dhennig@stonefield.com",
                "mailserver":"mailserver.stonefield.com",
                "password":"mypw",
                "port":25,
                "username":"dhennig",
                "usessl":false
            }
            endtext
            return lcSettings
    endfunc
enddefine

Aquí está el código para SFConfiguration. Requiere nfJSONRead.prg y nfJSONCreate.prg, que puede obtener del repositorio nfJSON de GitHub:

define class SFConfiguration as Custom
    cSettingsFile = ''
        && the name and path for the settings file
    cErrorMessage = ''
        && the text of any error that occurs
    oSettings     = ''
        && a settings object

* Cargue la configuración del archivo especificado en el parámetro 
* o en This.cSettingsFile y devuelva un objeto de configuración. 
* Si el archivo no existe (como la primera vez que nos llaman), 
* se retorna un objeto de configuración que contiene 
* la configuración predeterminada.

    function Load(tcSettingsFile)
        local lcSettingsFile, ;
            lcSettings, ;
            loSettings, ;
            loException as Exception
        try
            lcSettingsFile = evl(tcSettingsFile, This.cSettingsFile)
            if not empty(lcSettingsFile) and file(lcSettingsFile)
                lcSettings = filetostr(lcSettingsFile)
            endif not empty(lcSettingsFile) ...
            if empty(lcSettings)
                lcSettings = This.GetDefaultSettings()
            endif empty(lcSettings)
            loSettings = nfJSONRead(lcSettings)
            This.cErrorMessage = ''
        catch to loException
            This.cErrorMessage = loException.Message
            loSettings = NULL
        endtry
        This.oSettings = loSettings
        return loSettings
    endfunc

* Guarde la configuración en el objeto especificado en el 
* archivo especificado en el parámetro o This.cSettingsFile.

    function Save(toSettings, tcSettingsFile)
        local lcSettingsFile, ;
            lcSettings, ;
            loException as Exception
        lcSettingsFile = evl(tcSettingsFile, This.cSettingsFile)
        if not empty(lcSettingsFile)
            try
                lcSettings = nfJSONCreate(toSettings, .T.)
                strtofile(lcSettings, lcSettingsFile)
                This.cErrorMessage = ''
            catch to loException
                This.cErrorMessage = loException.Message
            endtry
        else
            This.cErrorMessage = 'Settings file not specified.'
        endif not empty(lcSettingsFile)
        return empty(This.cErrorMessage)
    endfunc

* Obtiene el conjunto de configuraciones predeterminado como una 
* cadena JSON; anule esto en una subclase si es necesario.

    function GetDefaultSettings
        local lcSettings
        if vartype(This.oSettings) = 'O'
            lcSettings = nfJSONCreate(This.oSettings, .T.)
        else
            lcSettings = '{"text":"some text"}'
        endif vartype(This.oSettings) = 'O'
        return lcSettings
    endfunc
enddefine

28 de mayo de 2021

Extender el IDE de VFP 9 con MENUHIT y MENUCONTEXT

Original en Inglés: Extending the VFP 9 IDE with MENUHIT and MENUCONTEXT
https://doughennig.com/papers/Pub/200409dhen.pdf
Autor: Doug Hennig
Traducido por: Ana María Bisbé York


Uno de los temas claves en VFP 9 es la extensibilidad. Puede extender el Diseñador de informes a través de eventos de informes y el motor de informes a través de la clase ReportListener. Y ahora, puede extender incluso el IDE, específicamente las llamadas de menú contextual y de sistema. Este mes, Doug Hennig muestra cómo personalizar el IDE de VFP 9 IDE de forma que nunca antes pensó posible.

VFP ofrece vías para entrar en varios aspectos del entorno de desarrollo interactivo (IDE), y lo hace mejor con cada nueva versión. FoxPro 2.x nos permitía sustituir la funcionalidad de ciertos componentes Xbase al cambiar el nombre de las aplicaciones que apuntan a variables de sistema, tales como _GENSCRN y _GENMENU. VFP 3 nos proporcionó generadores, con los que creamos formularios y clases.

VFP 6 añadió ganchos de proyecto (hooks), permitiendo recibir eventos cuando los usuarios realizan varias acciones en el Administrador de proyectos, tales como agregar o eliminar un archivo. La novedad más importante en VFP 7 fue IntelliSense, con un procedimiento conducido por datos que pudimos extender fácilmente. VFP 8 añadió Toolbox personalizable y otras mejoras del IDE.

En VFP 9, sin embargo, Microsoft ha superado las ligas anteriores sobre el IDE. Debido a que el Diseñador de informes provoca eventos de informe, podemos cambiar completamente la apariencia y comportamiento de sus diálogos. La nueva clase base ReportListener nos permite recibir eventos en la medida que se ejecuta el informe, proporcionando posibilidades como son rotación de etiquetas, formateo dinámico y generación dinámica de objetos por ejemplo, gráficos. Pronto podremos reaccionar a eventos de Windows a través de las mejoras en BINDEVENT(), esta característica no está disponible al momento de este escrito; pero tendremos mejor acceso a eventos del sistema operativo Windows, incluidos aquellos relacionados con el IDE de VFP donde están involucradas ventanas con hWnds.

(Nota de la traductora: Sobre las nuevas posibilidades que ofrece VFP 9.0 para control de eventos a través de BINDEVENT() ver "Windows Event Binding Made Easy" en: https://doughennig.com/papers/Pub/200501dhen.pdf, o su versión en español "Enlazar eventos de ventana", que aparece publicada en: https://comunidadvfp.blogspot.com/2016/04/enlazar-eventos-de-ventana.html)

Este artículo está enfocado, sin embargo, a penetrar en los hits de menú (esto es, cuando el usuario selecciona un elemento desde la barra de menú del sistema de VFP) y los menús contextuales del sistema (aquellos que se muestran por el clic derecho en varios lugares, tales como la ventana Propiedades o el Diseñador de Bases de datos). Comenzaré examinando cómo hacer esto, seguido de algunos ejemplos prácticos.

MENUHIT

Para atrapar hit de menú, creo un registro script en la tabla IntelliSense con TYPE = "S" (por "script"), ABBREV = "MENUHIT", y el código para ejecutar en el campo memo DATA. (La tabla IntelliSense se indica por la variable de sistema _FOXCODE; de forma predeterminada es FOXCODE.DBF en el directorio HOME(7)) El código debe aceptar un objeto como parámetro. Este objeto tiene varias propiedades; pero las importantes, en lo que concierne a MENUHIT, se muestran en la Tabla 1.

PropiedadDescripción
UserTypedTexto del pad del menú del que procede el hit.
MenuItemTexto del elemento del menú seleccionado por el usuario.
ValueTypeUn valor devuelto a VFP – en blanco significa que continúa con el comportamiento predeterminado y "V" o "L" significan evitar el comportamiento predeterminado (similar a utilizar NODEFAULT en código de clases).
Tabla 1. Las propiedades del parámetro de objeto pasado al script MENUHIT proporcionan información sobre el hit de menú.

He aquí un ejemplo sencillo (tomado de SimpleMenuHit.PRG, incluido en el archivo Download de este mes):

text to lcCode noshow
lparameters toParameter
wait window 'Pad: ' + toParameter.UserTyped + ;
  chr(13) + 'Bar: ' + toParameter.MenuItem
endtext
delete from (_foxcode) where TYPE = 'S' and ;
  ABBREV = 'MENUHIT'
insert into (_foxcode) ;
    (TYPE, ABBREV, DATA) ;
  values ;
    ('S', 'MENUHIT', lcCode)

Esto muestra simplemente el texto del pad y la barra para el elemento de menú seleccionado y ejecuta el comportamiento predeterminado para ese elemento. Esto es bueno para pruebas; pero en código "real" probablemente desee deshabilitar el comportamiento predeterminado y sustituirlo por el suyo propio. En este caso, establezca la propiedad ValueType del objeto parámetro a "V" o "L". (En caso de que se sorprenda porqué hay dos valores posibles, es debido a que esos valores se emplean para otros propósitos por el controlador de IntelliSense y el equipo de VFP decidió utilizar el mismo mecanismo para MENUHIT. No tiene importancia cuál de ello se utilice.)

He aquí un ejemplo, tomado de DisableExit.PRG, que inhabilita la función Exit en el menú File (Archivo) (es posible que desee utilizarlo para hacerle la broma del tonto de Abril a un compañero de trabajo) (Nota de la traductora: April fool Persona a la que burlan tradicionalmente el 1ro. de Abril en los Estados Unidos.)

text to lcCode noshow
lparameters toParameter
if toParameter.UserTyped = 'File' and ;
  toParameter.MenuItem = 'Exit'
  toParameter.ValueType = 'V'
endif toParameter.UserTyped = 'File' ...
endtext
delete from (_foxcode) where TYPE = 'S' and ;
  ABBREV = 'MENUHIT'
insert into (_foxcode) ;
    (TYPE, ABBREV, DATA) ;
  values ;
    ('S', 'MENUHIT', lcCode)

Mientras esto parece correcto, al seleccionar Exit desde el menú File, VFP aun existe. Además de establecer ValueType igual a "V" o "L", algunas funciones requieren que el código MENUHIT devuelva .T. para evitar el comportamiento predeterminado. Añade RETURN .T. justo antes del ENDIF para evitar que Exit cierre VFP.

Otro aspecto: ¿Qué pasa si está empleando una versión local de VFP, por ejemplo la versión en Alemán? En ese caso, al comprobar que "File" y "Exit" no van a funcionar porque estos textos no aparecen en el menú. Esto es algo que tiene que tener en cuenta si distribuye sus códigos script para MENUHIT para otros desarrolladores.

Enseguida hablamos del MENUHITs

Mientras estamos en el tema de la distribución de scripts de MENUHIT a otros, ¿Qué ocurre si tiene scripts que hacen algo realmente bueno y otros que hacen otras cosas y quiere utilizar ambos? El problema es que puede haber un solo registro MENUHIT (si existe más de uno, VFP va a utilizar sólo el primero que encuentre en la tabla IntelliSense). Por esta razón, pienso que lo mejor es tener el registro MENUHIT que delegue en otra cosa en lugar de configurar la personalización del IDE directamente.

La manera más sencilla de hacer esto es agregar registros adicionales a la tabla IntelliSense y hacer que el registro MENUHIT los utilice para configurar las tareas actuales. Aunque esto es arbitrario, me parece que un registro con TYPE = "M" servirá para esto. Debido a que "M" se interpreta como "menú" y no hay un tipo de registro actualmente utilizado en la tabla. Por ejemplo, para controlar la función Exit, puede agregar un registro con TYPE = "M", ABBREV = "Exit", y el código a ejecutar en DATA.

Para hacer este trabajo, necesitamos un registro estándar MENUHIT. El código para este registro se muestra en la tabla para el registro con TYPE = "M" y ABBREV se iguala al texto de la barra que seleccione el usuario, y si existe, ejecuta el código en el campo memo DATA. He aquí el código para controlarlo:

lparameters toParameter
local lnSelect, ;
  lcCode, ;
  llReturn
try
  lnSelect = select()
  select 0
  use (_foxcode) again shared order 1
  if seek('M' + padr(upper(toParameter.MenuItem), ;
    len(ABBREV)))
    lcCode = DATA
  endif seek('M' ...
  use
  select (lnSelect)
  if not empty(lcCode)
    llReturn = execscript(lcCode, toParameter)
    if llReturn
      toParameter.ValueType = 'V'
    endif llReturn
  endif not empty(lcCode)
catch
endtry
return llReturn

Ejecute StandardMenuHit.PRG para instalar este código en la tabla IntelliSense. Existen algunas cosas interesantes sobre este código. Primero, yo utilizo normalmente ORDER <nombre de etiqueta> en lugar de ORDER <n> para establecer el orden en una tabla. Sin embargo, la tabla IntelliSense es inusual: Si abre la tabla y hace ATAGINFO() para recuperar información sobre los índices de la tabla, verá que existen dos etiquetas, ambas marcadas como Primary y ambas sin nombre de etiquetas. Entonces es necesario emplear ORDER 1 u ORDER 2 para establecer el orden para esta tabla.

Lo segundo a tener en cuenta es que este código está envuelto en una estructura TRY para prevenir cualquier error, tales como problemas con la apertura de tablas o errores que puedan existir en el código en otros registros.

El tercer aspecto es que este código no va a verificar el texto del pad que corresponde al elemento del menú seleccionado, sino solamente el texto de la barra de menú. Esto es debido a que he decidido hacer un SEEK por razones de rendimiento y la etiqueta utilizada es UPPER(TYPE + ABBREV). Teniendo en cuenta que es una tabla pequeña, podría probablemente conseguirlo colocando el texto para el pad en una columna EXPANDED y usando LOCATE FOR TYPE = "M" AND ABBREV = toParameter.MenuItem AND EXPANDED = toParameter.UserTyped para asegurarse de que ha sido encontrado el registro exacto.

Como conclusión de estos aspectos, Microsoft no ha decidido si iba a existir algo como un registro estándar MENUHIT en la tabla IntelliSense de forma predeterminada. Si no, van a proporcionar una forma sencilla para agregar como un registro o a través de los ejemplos Solution. (Los ejemplos Solution son ejemplos que muestran varias de las características de VFP, y a los que se accede fácilmente desde el Administrador de Panel de tareas).

Una característica final de MENUHIT. Si el código en el campo memo DATA tiene cualquier error de compilación, no se obtendrán mensajes de error de VFP – VFP simplemente ignora el código y aplica el comportamiento predeterminado. Esto pudiera ser molesto, por supuesto, ya que puede no estar totalmente seguro de por qué su código falla al hacer lo que espera.

¿Para qué nos sirve esto?

Muy bien, podemos entrar en un elemento del menú de VFP. ¿Para qué podemos utilizarlo?

Lo primero que vino a mi mente fue sustituir los diálogos para New Property (Nueva propiedad) y New Method (Nuevo método). Ya que ahora tenemos la posibilidad de preservar la capitalización de letras para propiedades y métodos de usuarios (vea mi artículo de junio 2004 en la revista FoxTalk 2.0, "MemberData and Custom Property Editors"), En lugar de tener el control de VFP lo introduzco en los diálogos New Nueva propiedad y Nuevo método. De esta forma evito pasos extras: saltar al editor de MemberData y cambiar allí el tipo de letra. Además, siempre me molestó tener que hacer clic en el botón Add (Agregar) para agregar una nueva propiedad o método y luego hacer clic en el botón Close (Cerrar) para cerrar el diálogo. Frecuentemente agrego solamente una propiedad o método cada vez, Deseaba tener un botón que hiciera ambas cosas, Agregar la propiedad y Cerrar el diálogo.

(Nota de la traductora:. El artículo mencionado por el autor fue traducido al español y aparece publicado bajo el título "MemberData y los editores de propiedades de usuario" en: https://comunidadvfp.blogspot.com/2017/06/memberdata-y-los-editores-de.html)

Debido a que el enfoque dado por MENUHIT nos permite ahora atrapar los elementos de menú New Property (Nueva propiedad) y New Method (Nuevo método), para los menús Form (Formulario) y Class (Clase), debíamos ser capaces de crear un diálogo que se comporte exactamente como deseamos. La Figura 1 muestra un diálogo con las siguientes características:

  • Actualiza automáticamente la propiedad _MemberData (agregando la propiedad si es necesario), entonces la capitalización para cada letra será utilizada (incluso para los métodos Access y Assign si fuesen creados también) y el nuevo miembro se mostrará en la ficha Favorites (Favoritos) si la opción se selecciona en este diálogo.
  • No es una ventana modal. Lo que significa que se puede mantener abierta, agregar otras propiedades o métodos, activar otras ventanas, retornar a esta, y agregar nuevos miembros.
  • Se puede anclar la ventana. Intente anclarla con la ventana Propiedades. Es fantástico !!
  • Se puede redimensionar y se almacena su tamaño y posición en su archivo de recursos (FOXUSER).
  • Tiene un botón Add & Close (Agregar y cerrar) para realizar ambas tareas en un solo clic.
  • El valor predeterminado de la propiedad se establece automáticamente al valor basado en el tipo de dato de la propiedad (si utiliza notación húngara). Por ejemplo lTest debe ser lógica, entonces .F. es su valor predeterminado. Para nTest su valor predeterminado es 0.
  • Oculta en lugar de inhabilitar los controles no aplicables. Debido a que este diálogo se emplea para ambos elementos de menú New Property (Nueva propiedad) y New Method (Nuevo método), en ambos menús Form (Formulario) y Class (Clase), algunas opciones pueden no ser aplicables para una instancia dada.
  • No permite nombres no válidos en el momento de ser introducidos en lugar de comprobarlo al hacer Clic en los botones Add (Agregar) o Add & Close (Agregar y cerrar).
  • Los botones Add (Agregar) se activan sólo si se introduce un nombre.

Figura 1

No veremos el código que contiene NewPropertyDialog.APP. No es complicado, en su núcleo llama a los métodos AddProperty y WriteMethod del objeto que está siendo editado en el Diseñador de clase o formulario para agregar una nueva propiedad o método. Estos dos métodos aceptan dos nuevos parámetros en VFP 9: la visibilidad (1 para Public, 2 para Protected, ó 3 para Hidden) y la descripción de la nueva propiedad o método.

Para utilizar los diálogos sustituidos, haga DO NewPropertyDialog.APP para registrarlo en la tabla IntelliSense. Agrega el registro MENUHIT que ha sido descrito antes y dos registros (uno para "New Property" y uno para "New Method") que tienen el mismo código en DATA que utiliza la clase NewPropertyDialog. En ese código, "<path>" representa la ruta (path) a NewPropertyDialog.APP en su sistema (la APP agrega automáticamente la ruta correcta al ejecutarla).

lparameters toParameter
local llReturn, ;
  llMethod, ;
  llClass
try
  llMethod = toParameter.MenuItem  = 'New Method'
  llClass  = toParameter.UserTyped = 'Class'
  release _oNewProperty
  public _oNewProperty
  _oNewProperty = newobject('NewPropertyDialog', ;
    'NewProperty.vcx', ;
    '<path>NewPropertyDialog.app', llMethod, llClass)
  _oNewProperty.Show()
  llReturn = .T.
catch
endtry
return llReturn

Ahora, puede seleccionar New Property o New Method desde los menús Form o Class, tendrá el nuevo menú en lugar del nativo.

MENUCONTEXT

Además de entrar en la selección desde el menú de sistema de VFP puede además atrapar menús contextuales como el que se muestra cuando se hace clic derecho en la ventana Propiedades. Esto se ha hecho utilizando el mismo mecanismo que MENUHIT, excepto el campo ABBREV en la tabla IntelliSense contiene "MENUCONTEXT” en lugar de "MENUHIT".

Como con MENUHIT, el código para el registro MENUCONTEXT debe aceptar un parámetro de objeto. En este caso, existen tres propiedades de interés: Item, una matriz de los textos mostrados en el menú contextual; ValueType, que tiene el mismo propósito y así lo hace para MENUHIT; y el ID para el menú contextual.

Algunos de los valores para MenuItem son "24446" para el menú contextual en la ventana Comandos, "24447" para el menú contextual del Administrador de proyectos, y "24456" para el menú contextual de la ventana Propiedades. ¿Cómo he descubierto estos valores? He creado un registro MENUCONTEXT con el código que mostró simplemente el valor para toParameter.MenuItem, y luego hice clic derecho en varios lugares. Como con MENUHIT, debe establecer ValueType como "V" o "L" y devuelve .T. para evitar comportamiento nativo (mostrar el menú contextual).

MENUCONTEXT no es tan fácil de usar cómo MENUHIT, por varias razones. Primero, al cambiar el contenido de la matriz Ítems, que no cambia mostrado por el menú. Por ejemplo, este código no parece tener ningún efecto sobre el menú contextual del todo:

lparameters toParameter
toParameter.Items[1] = 'My Bar'

De forma similar, el siguiente código no resulta en una nueva barra en el menú:

lparameters toParameter
lnItems = alen(toParameter.Items) + 1
dimension toParameter.Items[lnItems]
toParameter.Items[lnItems] = 'My Bar'

Segundo, no hay forma de cambiar lo que ocurre cuando es seleccionada una barra. Por ejemplo, puede desear que la barra Propiedades en el acceso directo a la ventana Comando para mostrar su diálogo en lugar del nativo. El problema es que ninguna de las propiedades del objeto parámetro especifica qué hacer cuándo es escogido un elemento del menú, que es generado dentro del menú como tal.

Entonces, parece que la única forma que podemos cambiar el menú es utilizar nuestro propio menú contextual y que evitan el comportamiento nativo. Sin embargo, hay una complicación con este proceder: Tal y como tiene que sustituir el menú entero con el suyo propio, ¿cómo le dice a VFP que utilice el comportamiento nativo cuando algunos de sus elementos de menú son seleccionados? En el momento que se realiza este escrito no parece haber ninguna forma para hacer esto, entonces yo sospecho que MENUCONTEXT será utilizado sólo moderadamente.

Sustituir el menú contextual del Administrador de proyecto

Incluso con estas características, tratemos de hacer un ejemplo. En mi artículo de Mayo 2000 de la revista FoxTalk, "Zip it, Zip it Good," presenté una utilidad que compactaba todos los archivos basados en tablas en un proyecto (tales como archivos VCX, SCX, y FRX) y otras utilidades que cierran (zips) todos los archivos referenciados en un archivo ZIP. En aquel artículo, se llamaban las utilidades desde una barra de herramientas mostrada por un gancho al proyecto. Vamos ahora a colocarlos en un menú contextual que lo hará disponible para cualquier objeto.

MENUCONTEXT tiene el mismo conflicto potencial que con MENUHIT, entonces, vamos a utilizar la misma solución: un registro "standard" que delegue simplemente en otro registro para un menú contextual particular. De hecho, el código para este registro estándar es exactamente el mismo que para el registro MENUHIT (ejecuta StandardMenuContext.PRG para crear el registro MENUCONTEXT. Sin embargo, en lugar de colocar el texto de la barra en el campo ABBREV para el registro controlador, colocaremos el ID del menú. (Puede ser que quiera el propósito del menú, como "ventana de comandos", en el campo EXPANDED, para hacer obvio el objetivo de ese registro.)

Después de crear el registro MENUCONTEXT, he creado un registro con TYPE = "M", ABBREV = "24447" (el ID para el menú contextual del Administrador de proyecto), y el siguiente código en DATA:

lparameters toParameter
local loMenu
loMenu = newobject('_ShortcutMenu', ;
  home() + 'ffc\_menu.vcx')
loMenu.AddMenuBar('\<Pack Project', ;
  'do PACKPROJ with _vfp.ActiveProject.Name')
loMenu.AddMenuBar('\<Zip Project', ;
  'do ZIPPROJ with _vfp.ActiveProject.Name')
loMenu.AddMenuBar('Project \<Info...', ;
  'keyboard "{CTRL+J}" plain')
loMenu.ShowMenu()

Este código utiliza las FFC (FoxPro Foundation Classes) la clase _ShortcutMenu para proporcionar el menú contextual. He descrito esta clase en mi artículo de Febrero 1999 de la revista FoxTalk, "A Last Look at the FFC." ("Una última mirada a las FFC."). Ejecute ProjectMenu.PRG para crear este registro en la tabla IntelliSense.

Al hacer clic derecho en el Administrador de proyecto, el nuevo menú personalizado muestra tres barras (vea la Figura 2): Pack Project, que llama a PACKPROJ.PRG que el nombre del archivo de proyecto actual; Zip Project, que llama a ZIPPROJ.PRG con el nombre del archivo de proyecto actual; y Project Info, que utiliza el comando KEYBOARD para invocar el diálogo Project Info desde el menú Project (Proyecto). (La función anterior muestra que puede reproducir la funcionalidad de una barra en el menú contextual nativo as long as existe una función de sistema que lo llama.)

Figura 2

Resumen

Las nuevas ventajas MENUHIT y MENUCONTEXT nos permiten entrar en el IDE de VFP mucho más que antes. Además para utilizar los ejemplos que he presentado en este artículo, puedo ver reemplazos para los diálogos Edit Property/Method y las funciones New, Import, y Export en el menú File. Estoy seguro de que puede pensar que las funciones del IDE que desea implementar de forma diferente en VFP 9; por favor, háganme saber que ideas tiene para esta novedad tan útil.

Download

https://doughennig.com/Papers/Pub/200409dhensc.zip

17 de mayo de 2021

Dígitos significativos en Visual FoxPro

Artículo original: Significant digits in Visual FoxPro
http://www.foxpert.com/foxpro/knowlbits/files/knowlbits_201102_1.html
Autor: Christof Wollenhaupt
Traductor: Luis María Guayán

Steve Bodnar hizo una pregunta interesante en Twitter: ¿Por qué obtiene resultados diferentes para las siguientes dos líneas?

? Round(512.9250000000,2) && 512.92
? Round(512.925000000,2) && 512.93

Nuestra educación matemática nos dice que no debería importar con cuántos ceros se rellene el número. El resultado tiene que ser el mismo. Las computadoras comúnmente no comparten la opinión de los usuarios, ni usan la misma base que los humanos. Vivimos en un mundo decimal (a excepción de unas pocas medidas no métricas, algunos países simplemente se niegan a darse por vencidos), las computadoras viven en un mundo binario. Lo que es un número casi perfecto para usar, 512,925, es de hecho 512,924999999999 para la computadora.

Visual FoxPro, como la mayoría de los otros lenguajes, usa números de punto flotante para expresar fracciones. Esos números no tienen una precisión fija, a diferencia de los números decimales que usamos. Trabajar con números de punto flotante de 15 dígitos sería engorroso rápidamente, ni coinciden con lo que usamos en Visual FoxPro en realidad. Nadie quiere una factura con la cantidad indicada hasta el millonésimo de centavo.

Visual FoxPro encontró varias soluciones a lo que es básicamente un problema de presentación. Por un lado, tenemos comandos oscuros que casi nadie puede explicar correctamente, como SET DECIMALS y SET FIXED.

Para simplificar las cosas, hasta el punto de que ya no vemos lo que sucede detrás de escena, Visual FoxPro realiza un seguimiento de la cantidad de dígitos que tiene cada valor flotante. Esta es una información adicional que no forma parte del valor numérico en sí.

512,9250000000 y 512,925000000 no son los mismos valores. Ambos tienen el mismo valor de coma flotante 512,924999999999. El primero tiene una precisión de 13, el segundo tiene una precisión de 12. Con un número como este, es bastante fácil averiguar cuántos lugares significativos tiene el número. Solo cuente sus dígitos. Pero, ¿qué hay de usar este número en cualquier tipo de cálculo? ¿Cómo sabe Visual FoxPro cuántos lugares digitales tendrá el resultado? Cuando se ejecuta el siguiente código:

? 512.925000000 font "Courier new"
? 512.925000000+0 font "Courier new"
? 512.925000000*1 font "Courier new"

Ud. notará que el número sigue siendo el mismo (afortunadamente, de lo contrario tendríamos cosas más serias de las que preocuparnos), sin embargo, sigue desplazándose hacia la derecha. ¿Porque es eso?

Cuando Visual FoxPro imprime un valor en la pantalla, lo hace de acuerdo con la precisión almacenada con el valor. No hay forma de pedir directamente a Visual FoxPro esos valores.

Alguien, probablemente un aprendiz deficiente hace 25 años, especificó para cada operación por cuántos dígitos el resultado podría variar de los valores que participan en la operación. El simple hecho de imprimir el número no tiene ningún efecto, por lo tanto, se imprime alineado con el borde izquierdo. Cuando agrega dos números, nunca puede agregar más de un dígito a la izquierda. 9 + 9 es 18. Por lo tanto, la operación de suma deja espacio para un desbordamiento potencial y agrega un dígito. Visual FoxPro examina ambos lados del separador decimal por separado. Agregar un número 4,1 y 1,4 da como resultado un número 5,4, que es el número máximo de dígitos en cada lado más uno para el desbordamiento. Las multiplicaciones combinan el número de dígitos:

? 1.11*1.1 font "Courier new"
? 1.11*1.10 font "Courier new"
? 01.11*01.10 font "Courier new"

Las multiplicaciones también dejan espacio para el carácter del signo. En la primera línea multiplicamos un valor de 1,2 y 1,1. El resultado tiene un total de 6 dígitos significativos: uno para el signo, dos para los números enteros y tres para la fracción. Al agregar ceros, cambia la precisión de la expresión. Sumar cero a la fracción da como resultado cuatro lugares decimales (2 + 2), pero no mueve el número a la derecha. Sin embargo, agregar ceros al número entero da como resultado que la parte del número entero ahora sea cuatro en lugar de dos dígitos. El número entero se desplaza dos caracteres a la derecha.

Teniendo esto en cuenta, resulta fácil comprender el problema del redondeo. La última pieza que necesita saber, como señaló amablemente Jody Meyer en Twitter, es que Visual FoxPro usa una precisión interna de 15 dígitos. ROUND(), como cualquier otra operación, cambia el número de lugares significativos de su resultado. Si pasa un valor entero, obtiene el mismo valor más dos lugares decimales.

Comencemos con el segundo valor del ejemplo de Steve para mostrar cómo funciona Visual FoxPro. El operando tiene 12 posiciones decimales. ROUND() agregará otros 2. El resultado será un valor de punto flotante con 14 lugares significativos. Cada valor que participa en la operación se redondea a esa precisión. 512,924999999999, el valor almacenado con un máximo de 15 lugares significativos, se redondea a 512,92500000000, un valor con 14 dígitos. Por lo tanto, el resultado de la operación ROUND() es 512,93.

La primera línea, aunque tiene 13 dígitos. Sumar los dos dígitos de las operaciones ROUND() da como resultado un enorme número de 15 dígitos. Esto maximiza el número de dígitos que admite Visual FoxPro. Siguiendo el mismo patrón, el valor de 512,924999999999 se redondea a 15 dígitos. En otras palabras, permanece sin cambios. El tercer lugar decimal es ahora un cuatro en lugar de un cinco. Como resultado, Visual FoxPro redondea hacia abajo a 512,92.


8 de mayo de 2021

Lecciones de malabarismo

Con tanto tiempo en casa, tengo que mantenerme ocupado.

Esto es totalmente inútil a menos que quieras aprender a hacer malabares.

Cuando estaba en la escuela, aprendí a hacer malabares, un pariente me enseñó. Aquí están las 3 lecciones para practicar.

Tony Vignone
FL, USA

PUBLIC oForm
oForm = CREATEOBJECT('form1')
oForm.SHOW(1)
DEFINE CLASS form1 AS FORM
  HEIGHT = 369
  WIDTH = 461
  DOCREATE = .T.
  AUTOCENTER = .T.
  CAPTION = "Lecciones de malabarismo"
  ad = 0
  NAME = "form1"

  ADD OBJECT b1 AS SHAPE WITH ;
    TOP = 252, ;
    LEFT = 36, ;
    HEIGHT = 24, ;
    WIDTH = 24, ;
    CURVATURE = 99, ;
    BACKCOLOR = RGB(0,128,255), ;
    NAME = "b1"

  ADD OBJECT b2 AS SHAPE WITH ;
    TOP = 252, ;
    LEFT = 372, ;
    HEIGHT = 24, ;
    WIDTH = 24, ;
    CURVATURE = 99, ;
    BACKCOLOR = RGB(255,0,0), ;
    NAME = "b2"

  ADD OBJECT b3 AS SHAPE WITH ;
    TOP = 252, ;
    LEFT = 408, ;
    HEIGHT = 24, ;
    WIDTH = 24, ;
    CURVATURE = 99, ;
    BACKCOLOR = RGB(128,255,128), ;
    NAME = "b3"

  ADD OBJECT Line1 AS LINE WITH ;
    BORDERWIDTH = 3, ;
    HEIGHT = 0, ;
    LEFT = 12, ;
    TOP = 276, ;
    WIDTH = 444, ;
    NAME = "Line1"

  ADD OBJECT Command1 AS COMMANDBUTTON WITH ;
    TOP = 318, ;
    LEFT = 108, ;
    HEIGHT = 25, ;
    WIDTH = 60, ;
    FONTSIZE = 9, ;
    CAPTION = "Malabar 1", ;
    TABSTOP = .F., ;
    BACKCOLOR = RGB(255,179,179), ;
    NAME = "Command1"

  ADD OBJECT Command2 AS COMMANDBUTTON WITH ;
    TOP = 318, ;
    LEFT = 192, ;
    HEIGHT = 25, ;
    WIDTH = 60, ;
    CAPTION = "Malabar 2", ;
    TABSTOP = .F., ;
    BACKCOLOR = RGB(255,179,179), ;
    NAME = "Command2"

  ADD OBJECT Command3 AS COMMANDBUTTON WITH ;
    TOP = 318, ;
    LEFT = 276, ;
    HEIGHT = 25, ;
    WIDTH = 60, ;
    CAPTION = "Malabar 3", ;
    TABSTOP = .F., ;
    BACKCOLOR = RGB(255,179,179), ;
    NAME = "Command3"

  ADD OBJECT Spinner1 AS SPINNER WITH ;
    ALIGNMENT = 2, ;
    HEIGHT = 25, ;
    KEYBOARDHIGHVALUE = 6, ;
    KEYBOARDLOWVALUE = 0, ;
    LEFT = 420, ;
    SPINNERHIGHVALUE =   6.00, ;
    SPINNERLOWVALUE =   0.00, ;
    TABSTOP = .F., ;
    TOP = 318, ;
    WIDTH = 37, ;
    CONTROLSOURCE = "Thisform.ad", ;
    NAME = "Spinner1"

  ADD OBJECT Label2 AS LABEL WITH ;
    FONTNAME = "Tahoma", ;
    FONTSIZE = 8, ;
    ALIGNMENT = 2, ;
    CAPTION = "Para con X, FIN, Barra Espaciadora", ;
    HEIGHT = 13, ;
    LEFT = 154, ;
    TOP = 348, ;
    WIDTH = 169, ;
    NAME = "Label2"

  ADD OBJECT Label1 AS LABEL WITH ;
    FONTNAME = "Tahoma", ;
    FONTSIZE = 8, ;
    WORDWRAP = .T., ;
    ALIGNMENT = 2, ;
    CAPTION = "Ajustar tiro", ;
    HEIGHT = 31, ;
    LEFT = 372, ;
    TOP = 318, ;
    WIDTH = 40, ;
    NAME = "Label1"

  ADD OBJECT Label3 AS LABEL WITH ;
    FONTSIZE = 8, ;
    WORDWRAP = .T., ;
    ALIGNMENT = 2, ;
    BACKSTYLE = 0, ;
    CAPTION = "Practica en este orden", ;
    HEIGHT = 48, ;
    LEFT = 36, ;
    TOP = 309, ;
    WIDTH = 48, ;
    NAME = "Label3"

  ADD OBJECT Label4 AS LABEL WITH ;
    FONTNAME = "Wingdings 3", ;
    FONTSIZE = 14, ;
    CAPTION = "u", ;
    HEIGHT = 25, ;
    LEFT = 84, ;
    TOP = 321, ;
    WIDTH = 18, ;
    NAME = "Label4"

  ADD OBJECT LblMsg AS LABEL WITH ;
    FONTSIZE = 8, ;
    ALIGNMENT = 2, ;
    BACKSTYLE = 0, ;
    CAPTION = "No mires tus manos. Mira hacia adelante.", ;
    HEIGHT = 16, ;
    LEFT = 96, ;
    TOP = 301, ;
    WIDTH = 252, ;
    NAME = "lblMsg"

  ADD OBJECT Image1 AS IMAGE WITH ;
    HEIGHT = 25, ;
    LEFT = 24, ;
    TOP = 278, ;
    WIDTH = 61, ;
    NAME = "Image1"

  ADD OBJECT Image2 AS IMAGE WITH ;
    HEIGHT = 25, ;
    LEFT = 382, ;
    TOP = 278, ;
    WIDTH = 61, ;
    NAME = "Image2"

  PROCEDURE INIT
    DECLARE Sleep IN kernel32 INTEGER dwMilliseconds
    WITH THISFORM

      PUBLIC nspeed
      nspeed = 100
      PUBLIC x1,x2,x3,y1,y2,y3
      PUBLIC esclist
      PUBLIC i1,i2,i3
      PUBLIC w,b1p,b2p,b3p
      PUBLIC side1,side2,side3,msg1,msg2
      w = 30  &&30*12
      esclist = "6,120,32"
      side1 = "L"
      side2 = "R"
      side3 = "R"
      msg1 = "No mires tus manos. Mira hacia adelante."
      msg2 = "Tira debajo de uno entrante para que no choquen"
      SET CURSOR OFF
      .image1.PICTUREVAL = .handpicL()
      .image2.PICTUREVAL = .handpicR()
    ENDWITH
  ENDPROC

  PROCEDURE DESTROY
    SET CURSOR ON
  ENDPROC

  PROCEDURE Command1.CLICK
    WITH THISFORM
      nspeed = 70
      IF .b1.TOP < 252
        .RESET()
        sleep(1000)
      ELSE
        .RESET()
      ENDIF

      LOCAL i
      x1 = .ad
      FOR i=1 TO 30*8
        .throw1()
        IF INLIST(INKEY(),&esclist) THEN  && END
          .RESET()
          EXIT
        ENDIF
      NEXT
    ENDWITH
  ENDPROC

  PROCEDURE Command2.CLICK
    WITH THISFORM
      nspeed = 50
      IF .b1.TOP < 252
        .RESET()
        sleep(1000)
      ELSE
        .RESET()
      ENDIF

      .lblMsg.CAPTION = msg2
      LOCAL i
      x1 = 0
      b1p = 0
      DO WHILE b1p = 0
        .throw1(0.6)
      ENDDO
      x2 = w-.ad
      FOR i=1 TO 30*8
        .throw2()
        .throw1()
        IF INLIST(INKEY(),&esclist) THEN  && END
          .RESET()
          EXIT
        ENDIF
      NEXT
    ENDWITH
  ENDPROC

  PROCEDURE Command3.CLICK
    WITH THISFORM
      nspeed = 25
      IF .b1.TOP < 252
        .RESET()
        sleep(1000)
      ELSE
        .RESET()
      ENDIF

      .lblMsg.CAPTION = msg2
      LOCAL i
      x1 = 0
      b1p = 0
      DO WHILE b1p = 0
        .throw1(.2)
      ENDDO
      x2 = w
      b1p = 0
      DO WHILE b1p = 0
        .throw2()
        .throw1(.6)
      ENDDO
      x3 = w-.ad
      FOR i=1 TO 30*12
        .throw3()
        .throw2()
        .throw1()
        IF INLIST(INKEY(),&esclist) THEN  && END
          .RESET()
          EXIT
        ENDIF
      NEXT
    ENDWITH
  ENDPROC

  PROCEDURE throw1
    LPARAMETER npart
    npart = EVL(npart,0.2)
    WITH THISFORM
      y1 = MAX(0,(x1-.ad)*(w-x1))
      .b1.LEFT = 36+12*x1
      .b1.TOP = 252-y1
      sleep(nspeed)
      IF x1 = INT(npart*w) THEN
        b1p = 1
      ENDIF
      IF side1 = "L" THEN
        x1 = x1+1
        IF x1 = w THEN
          side1 = "R"
        ENDIF
      ELSE
        x1 = x1-1
        IF x1 = .ad THEN
          side1 = "L"
        ENDIF
      ENDIF
    ENDWITH
  ENDPROC

  PROCEDURE throw2
    WITH THISFORM
      y2 = MAX(0,x2*(w-.ad-x2))
      .b2.LEFT = 36+12*x2
      .b2.TOP = 252-y2
      sleep(nspeed)
      IF x2 = INT(.8*w) THEN
        b2p = 1
      ENDIF
      IF side2 = "L" THEN
        x2 = x2+1
        IF x2 = w-.ad THEN
          side2 = "R"
        ENDIF
      ELSE
        x2 = x2-1
        IF x2 = 0 THEN
          side2 = "L"
        ENDIF
      ENDIF
    ENDWITH
  ENDPROC

  PROCEDURE throw3
    WITH THISFORM
      y3 = MAX(0,x3*(w-.ad-x3))
      .b3.LEFT = 36+12*x3
      .b3.TOP = 252-y3
      sleep(nspeed)
      IF side3 = "L" THEN
        x3 = x3+1
        IF x3 = w-.ad THEN
          side3 = "R"
        ENDIF
      ELSE
        x3 = x3-1
        IF x3 = 0 THEN
          side3 = "L"
        ENDIF
      ENDIF
    ENDWITH
  ENDPROC

  PROCEDURE RESET
    WITH THISFORM
      .b1.LEFT = 26
      .b2.LEFT = 372
      .b3.LEFT = 408
      STORE 252 TO .b1.TOP,.b2.TOP,.b3.TOP
      side1 = "L"
      side2 = "R"
      side3 = "R"
      .lblMsg.CAPTION = msg1
    ENDWITH
  ENDPROC

  PROCEDURE handpicL
    LOCAL un
    *     1...5...10....5...20....5...30....5...40....5...50....5...60....5...70....5...80....5...90....5..100'
    un =    'FFD8FFE000104A46494600010101012C012C0000FFE1009245786966000049492A000800000002000E010200590000002600'
    un = m.un+'000098820200090000008000000000000000566563746F7220696C6C757374726174696F6E206F6620612070616C6D207570'
    un = m.un+'206F75747265616368696E672068616E6420676573747572652C2069736F6C61746564206F6E207768697465206261636B67'
    un = m.un+'726F756E642E0000626C61636B7265640000FFDB0043000A07070807060A0808080B0A0A0B0E18100E0D0D0E1D1516111823'
    un = m.un+'1F2524221F2221262B372F26293429212230413134393B3E3E3E252E4449433C48373D3E3BFFDB0043010A0B0B0E0D0E1C10'
    un = m.un+'101C3B2822283B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B'
    un = m.un+'3B3B3B3B3B3BFFC00011080016003303012200021101031101FFC4001F000001050101010101010000000000000000010203'
    un = m.un+'0405060708090A0BFFC400B5100002010303020403050504040000017D010203000411051221314106135161072271143281'
    un = m.un+'91A1082342B1C11552D1F02433627282090A161718191A25262728292A3435363738393A434445464748494A535455565758'
    un = m.un+'595A636465666768696A737475767778797A838485868788898A92939495969798999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7'
    un = m.un+'B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE1E2E3E4E5E6E7E8E9EAF1F2F3F4F5F6F7F8F9FAFFC4001F0100030101'
    un = m.un+'010101010101010000000000000102030405060708090A0BFFC400B511000201020404030407050404000102770001020311'
    un = m.un+'04052131061241510761711322328108144291A1B1C109233352F0156272D10A162434E125F11718191A262728292A353637'
    un = m.un+'38393A434445464748494A535455565758595A636465666768696A737475767778797A82838485868788898A929394959697'
    un = m.un+'98999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE2E3E4E5E6E7E8E9EAF2F3'
    un = m.un+'F4F5F6F7F8F9FAFFDA000C03010002110311003F00F63620726B32E35491837D9235655FF968E4E0FD00EB59C902A01E6C6A'
    un = m.un+'7E6E371CFE95688F9368C01818F415C92AEDAD343A634527A9565D66770639E7F2D8F4585305BD81C935368B61702F5AF648'
    un = m.un+'7ECE850AED6FBD2127AB7FF5F9AA92DAEC6DCF18652796001AB114925B465ADEEA555FEEB8DC31F422A233D6F3349434B40E'
    un = m.un+'887D296B9F6D52F72163916427D23E9FF8F556FB7EA1792BDB4734924A07DD83680BFEF3F6AE9F6CBA1CFEC5EED9D4F14561'
    un = m.un+'C3A46ADE52EED6E543DD5630C07E2DC9FC68AD2E4D97728CD6BA9B49E53B5B1FF8137F854C965A8C49BCB5BE3D0337F85145'
    un = m.un+'70F2A3AEECAD26A72C0712C487D76B1A92D2E66D4D8C76E91A05EA64E7F414515296A53DAE599344BA9CED9B50DB1F758A32'
    un = m.un+'33ED9CD6CD9D9C1636EB05BC61117D3BFB9F7A28AEBA492392726F727A28A2B5323FFFD9'

    RETURN STRCONV(un,16)
  ENDPROC

  PROCEDURE handpicR
    LOCAL un
    *     1...5...10....5...20....5...30....5...40....5...50....5...60....5...70....5...80....5...90....5..100'
    un =    'FFD8FFE000104A46494600010101012C012C0000FFE1009245786966000049492A000800000002000E010200590000002600'
    un = m.un+'000098820200090000008000000000000000566563746F7220696C6C757374726174696F6E206F6620612070616C6D207570'
    un = m.un+'206F75747265616368696E672068616E6420676573747572652C2069736F6C61746564206F6E207768697465206261636B67'
    un = m.un+'726F756E642E0073626C61636B72656400FFFFDB0043000A07070807060A0808080B0A0A0B0E18100E0D0D0E1D1516111823'
    un = m.un+'1F2524221F2221262B372F26293429212230413134393B3E3E3E252E4449433C48373D3E3BFFDB0043010A0B0B0E0D0E1C10'
    un = m.un+'101C3B2822283B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B'
    un = m.un+'3B3B3B3B3B3BFFC00011080016003303012200021101031101FFC4001F000001050101010101010000000000000000010203'
    un = m.un+'0405060708090A0BFFC400B5100002010303020403050504040000017D010203000411051221314106135161072271143281'
    un = m.un+'91A1082342B1C11552D1F02433627282090A161718191A25262728292A3435363738393A434445464748494A535455565758'
    un = m.un+'595A636465666768696A737475767778797A838485868788898A92939495969798999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7'
    un = m.un+'B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE1E2E3E4E5E6E7E8E9EAF1F2F3F4F5F6F7F8F9FAFFC4001F0100030101'
    un = m.un+'010101010101010000000000000102030405060708090A0BFFC400B511000201020404030407050404000102770001020311'
    un = m.un+'04052131061241510761711322328108144291A1B1C109233352F0156272D10A162434E125F11718191A262728292A353637'
    un = m.un+'38393A434445464748494A535455565758595A636465666768696A737475767778797A82838485868788898A929394959697'
    un = m.un+'98999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE2E3E4E5E6E7E8E9EAF2F3'
    un = m.un+'F4F5F6F7F8F9FAFFDA000C03010002110311003F00F66A2B99B84D5749324D35E5C4B175F3906E03FDE43D3EA38A747ABEA0'
    un = m.un+'515CC91BAB747550C3F422B275527666DEC9B574CD7D5ED25BED364821601DB070C786C1CE0FB1AC186F26D33F76E25B361F'
    un = m.un+'76265CA37D074FC8D5B37D753E50DE155C7263400FE7CD537815A4DA8AF2B9FBC5F93F99AC2A544DDE3B9B53834AD2D8D0B6'
    un = m.un+'D5EF9CEE78A1914765CA1FE6456B5B5D457516F8C9F4653D54FA115896F118536923E83B524D1C6D20CA2973DFA1E9EB442B'
    un = m.un+'496FA8A74A2F63A2A2B94366E4E4A7FE3D455FD63C89F61E6755D6B1AE7C3ABF6933D85C1B466E5902E509FA64628A2B7945'
    un = m.un+'495998464E2F420B9B4BEB084CEF24132AF5F94AB7F5ACF8F5A131C470F3DF71C514571D48A8CAC8EDA6F9A3765D861D42F9'
    un = m.un+'498FC841EEE7FC2A096C75281B7E6DB3D321DBFC28A29F246D71733BD8B51E9DAC4881C4D6A011D32DFE1451455AA71239E4'
    un = m.un+'7FFFD9'

    RETURN STRCONV(un,16)
  ENDPROC

ENDDEFINE

Publicado en el foro de Foxite por Tony Vignone: FOR FUN ONLY

17 de abril de 2021

Breve Reseña Sobre Sesiones de Datos Públicas Y Privadas

Si bién antiguamente usábamos cualquier tabla desde cualquier programa (lo que luego se denominó "Sesión de datos Pública") ahora es más frecuente, aunque no imprescindible, usar "Sesión de datos Privada", que permite abrir otra copia las mismas tablas como si se tratara de otra sesión de VFP (como cuando abrís VFP 2 veces o más).

Qué tipo de Sesión de Datos quieras usar dependerá de cómo pretendas encarar tu proyecto. Voy a intentar hacer un breve resúmen de ventajas y desventajas de cada caso.

1) Ventajas de la Sesión de datos Pública

  • Las tablas son visibles desde todos los módulos del sistema (PRG, SCX, VCX, MNX, FRX, etc.)
  • El hecho de que se las tablas se abran una sola vez implica un cierto ahorro de memoria RAM, ya que por cada tabla abierta se guardan en memoria datos como: Indice actual, tipo de ordenamiento del índice, puntero de registro, etc.
  • La sesión, al ser Pública, implica usar sólo una copia en memoria RAM de los seteos (comandos SET ON/OFF) que son afectados por las sesiones de datos (en la ayuda del comando SET dice si es afectado o no por la sesión de datos)
  • Es compatible con el modo de funcionamiento de FoxPro 2.X, lo que permitiría mantener código heredado de esa versión

2) Desventajas de la Sesión de datos Pública:

  • En cada programa tenés que hacer tu propio manejo de los punteros de las tablas para que cuando abrís un programa nuevo o cambiás a otro, cada uno restaure el puntero del registro al lugar correspondiente.
  • Si desde un procedimiento llamás a otro procedimiento que cambia el puntero de una tabla o el orden de un índice que estabas usando y no lo restaurás, es muy probable que le ocaciones problemas al procedimiento original, pudiéndose llegar a cancelar el programa
  • No es recomendable para objetos COM
  • Los comandos SET ON/OFF afectan a todos los módulos por igual
  • Si la misma tabla se abre varias veces, se debe hacer con alias distintos (USE MiTabla AGAIN SHARED ALIAS ...)
  • Se dificulta hacer código genérico para manejar tablas, ya que hay que agregar manejo de punteros, índices, etc.
  • El bloqueo / desbloqueo de registros (LOCK / RLOCK / FLOCK) es más complejo de manejar, ya que si un módulo bloquea uno o más registros, otro módulo puede desbloquear esos mismos registros pudiendo provocar un problema de seguridad y/o coherencia de datos entre módulos

3) Ventajas de la Sesión de datos Privada (Objeto Session / Form / Reporte):

  • Varios comandos SET ON/OFF son dependientes de la sesión en la que se los usa (Ej: SET DELETED),lo que permite tener distintos valores entre sesiones de datos privadas (cada sesión puede tener un valor distinto para el mismo comando, sin afectar a las demás)
  • Una misma tabla se puede abrir con el mismo alias en distintas sesiones privadas
  • Si la misma tabla se abre en distintas sesiones, cada sesión mantiene automáticamente el puntero de registro de las tablas y el orden de los índices
  • Es más fácil hacer código genérico para manejar tablas, ya que el mantenimiento de punteros de registro e índices es automático
  • Es recomendable para hacer objetos COM
  • El bloqueo / desbloqueo de registros (LOCK / RLOCK / FLOCK) es más fácil de manejar, ya que si una sesión bloquea uno o más registros de una tabla, sólo esa sesión podrá desbloquear esos mismos registros, lo que beneficia a la seguridad y/o coherencia de datos entre módulos
  • Una sesión de datos Privada se comporta comosi fuera una nueva Sesión de VFP (como abrir 2 ó mas veces VFP)

4) Desventajas de la Sesión de datos Privada (Objeto Session / Form / Reporte):

  • El mantenimiento independiente de los punteros de registro, índices, valores de los comandos SET ON/OFF, etc. por cada sesión implica un mayor uso de memoria RAM por cada una de las tablas abiertas y de los comandos SET ON/OFF. Ej: Si se usa una tabla con un indice en 3 sesiones privadas distintas, en realidad es como si se estuvieran usando 6 archivos distintos.
  • El código heredado de versiones de FoxPro 2.X no funcionará del modo esperado, ya que no estaba preparado para este esquema de trabajo por Sesiones

Seguramente hay más cosas para agregar en cada caso, pero esto es para dar una idea.

Saludos

Fernando D. Bozzo

25 de diciembre de 2020

La "Guía del hacker para Visual FoxPro" y el "Archivo de Ayuda de Visual FoxPro 9 SP2" ahora en línea

Guía del hacker para Visual FoxPro

Los autores del libro "Hacker's Guide to Visual FoxPro", Tamar Granor, Ted Roche, Della Martin y Doug Hennig, anunciaron que ahora el libro es de código abierto y se encuentra en línea al alcance de todos en el sitio: https://hackfox.github.io

La mayoría de los desarrolladores de Visual FoxPro considera que HackFox es la "biblia", porque va más allá de la ayuda de VFP, y describe cómo funciona realmente VFP. Aconseja que comandos y funciones usar, cuáles evitar y la mejor manera de trabajar.

HackFox se ha demorado un poco, ya que no tuvo actualizaciones después de VFP 7. Hacerlo de código abierto permitirá que prospere a medida que la comunidad actualizan los temas existentes y agregan nuevos temas para VFP 8 y VFP 9.


Archivo de Ayuda de Visual FoxPro 9 SP2

También otro archivo que se encuentra ahora en línea, es el Archivo de ayuda de Microsoft Visual FoxPro 9 SP2, VFPX Edition v.1.08 en el sitio: https://www.vfphelp.com/help/index.htm

Es el mismo archivo actualizado y mejorado por la comunidad a través del Proyecto VFPx que se encuentra para descarga en formato .CHM en el sitio: https://github.com/VFPX/HelpFile


17 de diciembre de 2020

AddProperty() no necesita comprobar la existencia de la propiedad

Puede no ser obvio, pero el método AddProperty() (y la función ADDPROPERTY() agregada en VFP 8.0) es bastante "lista" para no colisionar o para no generar un error cuando usted añade con .AddProperty() una propiedad que ya exista.

En vez de un código semejante a esto:

IF NOT PEMSTATUS(THIS,"AlgunaPropiedad",5)
  THIS.AddProperty("AlgunaPropiedad", AlgunValor)
ENDIF

usted puede escribir esta sola línea de código:

THIS.AddProperty("AlgunaPropiedad", AlgunValor)

Con lo cual VFP agrega la propiedad "AlgunaPropiedad" si no existe ya, y le asigna "AlgunValor" a ésta.

VFP Tips & Tricks - Drew Speedie

1 de noviembre de 2020

Cargar un número grande de archivos mediante ADIR()

La función ADIR() es una manera práctica de cargar todos los archivos (o de un subconjunto de todos los archivos) de una carpeta específica, en un Array. En la mayoría de los usos típicos de ADIR(), el número de archivos y por lo tanto el tamaño del Array, no es una cuestión a discutir.

Sin embargo, en aquellos escenarios donde el número de archivos que Ud. desea cargar es muy grande (mas de 10.000), existen estos problemas:

1) En versiones de VFP anteriores a VFP 9.0, ADIR() estaba limitado a cerca de 12.800 filas, debido al límite de 64.000 items máximos de un Array. Si la carpeta específicada contiene mas de 12.800 archivos, ADIR() genera un error.

2) VFP 9.0 elimina la limitación de 64.000 items, pero cuando usted intenta cargar y después procesar las filas del Array, el rendimiento sufre porque el Array entero se carga en memoria.

El siguiente código muestra una manera de solventar el problema usando la función SYS(2000) que carga un archivo a la vez en un cursor y a) Trabaja en cualquier versión de VFP y b) Consume muy poca memoria, porque solamente una fila del Array a la vez, se almacena en memoria.

CLEAR 
LOCAL xx
*
*  create 12,000 files with the ".TST" extension,
*  the limit when using VFP 8.0 or lower -- feel
*  free to increase this number dramatically in 
*  VFP 9 and higher
*
FOR xx = 1 TO 12000
  STRTOFILE("File " + TRANSFORM(m.xx),"File"+TRANSFORM(m.xx)+".TST",0)
ENDFOR
*
*  try it the ADIR() way
*
start = SECONDS()
CREATE CURSOR FileList (FileName C(60), ;
                        FileSize I, ;
                        LastModD D, ;
                        LastModT C(8), ;
                        FileAttr C(10))
LOCAL laDir[1]
ADIR(laDir,"*.TST")
INSERT INTO FileList FROM ARRAY laDir
end = SECONDS()
? end-start, RECCOUNT("FileList")
USE IN FileList
*
*  try it the SYS(2000) way
*
start = SECONDS()
CREATE CURSOR FileList (FileName C(60), ;
                        FileSize I, ;
                        LastModD D, ;
                        LastModT C(8), ;
                        FileAttr C(10))
LOCAL lcFile
lcFile = SYS(2000,"*.TST")
DO WHILE NOT EMPTY(m.lcFile)
    ADIR(laFile, m.lcFile)
    INSERT INTO FileList FROM ARRAY laFile
    lcFile = SYS(2000,"*.TST",1)
ENDDO
end = SECONDS()
? end-start, RECCOUNT("FileList")
USE IN FileList
ERASE *.TST
RETURN

VFP Tips & Tricks - Drew Speedie

6 de octubre de 2020

Tip: Use _TALLY

_TALLY es una variable de ambiente de VFP, ha estado ahí desde que yo recuerde, su uso puede ser bastante útil cuando se está trabajando con datos. Se utiliza para saber el número de registros que han sido afectados o creados.

En lo particular, utilizo mucho _TALLY para saber si ha sido creado un cursor con SELECT-SQL:

SELECT iID, cClave, cNombre, cTelefono ;
  FROM Empleado ;
  WHERE iID = lnIDEmpleado ;
  INTO CURSOR cEmpleado

IF _TALLY > 0 
  ** Se creó el cursor, hubo datos
  ** por lo que podremos procesar cEmpleado
ELSE
  ** Mensaje al usuario?
ENDIF

Está documentado en la ayuda del producto con qué otros comandos se puede utilizar, no estaría de más darle un vistazo e implementarlo en donde sea conveniente.

Como nota adicional, cabe mencionar que _TALLY no funcionará con cadenas enviadas vía ODBC u OLEDB (hacia un servidor de bases de datos?), ya que le resulta imposible saber (por éste método) cuántos registros han sido afectados.

Espartaco Palma Martínez

3 de agosto de 2020

Agregar registro IFND en IntelliSense

El programa IFND_FoxCode.PRG agrega un registro "IFND" a nuestra tabla IntelliSense record que expandira en en un control IF Not Default().

En el editor de métodos o programa ingrese:

IFND{SPACE}

y este registro IntelliSense expadirá esto a:

IF NOT DODEFAULT()
   RETURN .F.
ENDIF
*
*  IFND_FoxCode.PRG
*  Agrega un registro "IFND" a nuestra tabla IntelliSense table para
*  que cuando ingrese:
*    IFND{SPACE}
*  esto se expanda a:
*    IF NOT DODEFAULT()
*      RETURN .F.
*    ENDIF
*
CLEAR ALL
CLOSE ALL
CLEAR
USE (_FOXCODE) IN 0 AGAIN ALIAS UpdateFoxCode
SELECT UpdateFoxCode
**************************************************
LOCATE FOR UPPER(ALLTRIM(Abbrev)) == "IFND"
**************************************************
IF NOT FOUND()
  APPEND BLANK
  REPLACE TYPE WITH "U", ;
    Abbrev WITH "IFND",;
    CASE WITH "U", ;
    SAVE WITH .T., ;
    Cmd WITH "{}", ;
    USER WITH "Mi registro IFND"
  ACTIVATE SCREEN
  ? PROGRAM() + " acaba de agregar el registro 'IFND'"
ENDIF
REPLACE DATA WITH ;
  "*  IF NOT DODEFAULT(), RETURN .F., ENDIF" + CHR(13) + CHR(10) + ;
  "LPARAMETERS oFoxcode" + CHR(13) + CHR(10) + ;
  "IF NOT oFoxcode.Location = 10" + CHR(13) + CHR(10) + ;
  [   RETURN "IFND"] + CHR(13) + CHR(10) + ;
  "ENDIF" + CHR(13) + CHR(10) + ;
  [oFoxcode.ValueType = "V"] + CHR(13) + CHR(10) + ;
  "TEXT TO myvar TEXTMERGE NOSHOW" + CHR(13) + CHR(10) + ;
  "IF NOT DODEFAULT()" + CHR(13) + CHR(10) + ;
  "  RETURN .F." + CHR(13) + CHR(10) + ;
  "ENDIF" + CHR(13) + CHR(10) + ;
  "ENDTEXT" + CHR(13) + CHR(10) + ;
  "RETURN myvar + chr(13) + [~]"
USE IN UpdateFoxCode
RETURN

VFP Tips & Tricks - Drew Speedie

20 de julio de 2020

ExecScript()

La nueva función ExecScript() en VFP 7.0 permite que usted ejecute una secuencia de código "al vuelo".

El ejemplo de abajo también hace uso de la nueva sintaxis TEXT TO para crear la cadena de comandos.

Simule esto es su código PRG o método:

*
* código normal del PRG o método aquí ...
* 

*
* cree una cadena de comandos "al vuelo" 
*
TEXT TO lcCode NOSHOW
LOCAL lcOutput,xx
lcOutput = SPACE(0)
FOR xx = 1 to 10
  lcOutput = lcOutput + ;
  "Line " + TRANSFORM(xx) + ;
  CHR(13) + CHR(10)
ENDFOR
RETURN lcOutput
ENDTEXT
*
* ejecute los comandos aquí 
*
LOCAL lcRetVal
lcRetVal = EXECSCRIPT(lcCode)
*
* continúe ejecutando el PRG o método... 
* 

Solamente tenga cuidado que ExecScript() es relativamente lento.

VFP Tips & Tricks - Drew Speedie

20 de junio de 2020

DISPLAY MEMORY LIKE <Skeleton>

Los desarrolladores de VFP estan familiarizados con estos comandos: DISPLAY MEMORY y LIST MEMORY, sin embargo ambos comandos soportan la cláusula opcional LIKE <Skeleton>

Dado que la lista completa de variables de memorias existentes es tipicamente mas de una pantalla de información, es muy cómodo poder especificar un subconjunto de una o mas variables de memoria:

DISPLAY MEMORY LIKE X*
DISPLAY MEMORY LIKE _*
DISPLAY MEMORY LIKE MemvarName

Si todo lo que Ud. necesita es consultar solo el valor de una variable de memoria, Ud. tipicamente solo comprueba esta directamente con:

? lcMemvar

Sin embargo, cuando usted desea comprobar todos los valores de un Array, es bueno poder hacer:

DISPLAY MEMORY LIKE laMyArray

Aquí estan un par de ejemplos usando Arrays creados con funciones de VFP:

APRINTERS(laPrinters)
DISPLAY MEMORY LIKE laPrinters
AFONT(laFonts)
DISPLAY MEMORY LIKE laFonts

VFP Tips & Tricks - Drew Speedie

15 de mayo de 2020

MESSAGEBOX() ejecuta un TRANSFORM() implícito del texto

El primer parámetro aceptado por MESSAGEBOX() es automáticamente transformado (TRANSFORM()) por VFP 7 y superior.

Así que usted puede hacer lo siguiente sin tener que usar TRANSFORM() con el texto del mensaje ni preocuparse de del tipo de datos:

messagebox(datetime()) 
messagebox(123.45) 
messagebox(date()-date(1999,10,01)) 
messagebox(.f.) 
messagebox(.NULL.)

VFP Tips & Tricks - Drew Speedie

20 de abril de 2020

TRANSFORM() un poquito mas lento que ALLTRIM(STR()), DTOC() y TTOC()

La función TRANSFORM() fue agregada a VFP hace ya algunas versiones, y es mucho mas conveniente que las equivalentes ALLTRIM(STR()), DTOC() y TTOC().

Sin embargo, es también un poquito más lenta como lo demuestra el siguiente código:

CLEAR
LOCAL lnNumber, ldDate, ltTime, lnStart, lnEnd, xx
lcString = "This is a test"
ldDate = DATE()
ltTime = DATETIME()
lnNumber = 123.456

lnStart = SECONDS()
FOR xx = 1 TO 10000
  TRANSFORM(m.lnNumber)
ENDFOR
lnEnd = SECONDS()
? "TRANSFORM(m.lnNumber)", lnEnd-lnStart

lnStart = SECONDS()
FOR xx = 1 TO 10000
  ALLTRIM(STR(m.lnNumber,7,3))
ENDFOR
lnEnd = SECONDS()
? "ALLTRIM(STR(m.lnNumber,7,3))", lnEnd-lnStart
?

lnStart = SECONDS()
FOR xx = 1 TO 10000
  TRANSFORM(m.ldDate)
ENDFOR
lnEnd = SECONDS()
? "TRANSFORM(m.ldDate)", lnEnd-lnStart

lnStart = SECONDS()
FOR xx = 1 TO 10000
  DTOC(m.ldDate)
ENDFOR
lnEnd = SECONDS()
? "DTOC(m.ldDate)", lnEnd-lnStart
?

lnStart = SECONDS()
FOR xx = 1 TO 10000
  TRANSFORM(m.ltTime)
ENDFOR
lnEnd = SECONDS()
? "TRANSFORM(m.ltTime)", lnEnd-lnStart

lnStart = SECONDS()
FOR xx = 1 TO 10000
  TTOC(m.ltTime)
ENDFOR
lnEnd = SECONDS()
? "TTOC(m.ltTime)", lnEnd-lnStart

Sin embargo, a menos que Ud. este codificando un ciclo apretado, la conveniencia de TRANSFORM() compensa lejos su pérdida de rendimiento. TRANSFORM() es solo un poquito mas lento que DTOC() y TTOC(). La diferencia de rendimiento es mayor al comparar TRANSFORM() con ALLTRIM(STR()) para convertir números a cadenas.

VFP Tips & Tricks - Drew Speedie

22 de febrero de 2020

Instalar versiones nuevas de ejecutables de VFP

Instalar versiones nuevas de ejecutables de VFP.

Autor: Mike Lewis
Texto original:
-- Installing new copies of VFP executables --
http://www.ml-consult.co.uk/foxst-30.htm
Traducido por: Ana María Bisbé York


¿Cómo instalar el archivo EXE actualizado sin forzar a los usuarios a cerrar su aplicación?

¿Alguna vez ha necesitado instalar una copia nueva de un archivo .EXE después que la aplicación empezó a funcionar? Si le ha ocurrido, sabrá que no puede simplemente copiar el nuevo archivo sobre el existente. Si trata de hacerlo mientras los usuarios están corriendo la aplicación, Windows reportará “Violación de archivos compartidos”. Su única solución es esperar a que todos hayan finalizado su sesión, lo cual puede ser inconveniente.

Andew Connor, el IT Manager en Mids & Horsey Ltd en el Reino Unido, ha llegado a una simple solución a este problema. Andrew sugiere que suministre un pequeño lanzador de programa el que busca el EXE con la versión más reciente. Una vez encontrado, toma el control del fichero que hasta ahora ejecuta la aplicación.

El programa lanzador es un sencillo programa de VFP. Es completamente genérico, no necesita saber el nombre del archivo EXE o del directorio que lo contiene. Sin embargo, es necesario seguir normas sencillas para nombrar los archivos.

Nombrar los archivos

El programa lanzador debe ser compilado a un archivo EXE, su nombre debe ser igual al de la aplicación principal. Este es el archivo que los usuarios van a lanzar cuando deseen correr la aplicación.

El nombre del archivo EXE actual de la aplicación debe contener además dos dígitos numéricos para la versión. Es decir, si la aplicación se llama Ventas, el programa lanzador se llamará Ventas.EXE. El archivo EXE principal de la aplicación puede llamarse entonces Ventas01.EXE, Ventas02.EXE, y así sucesivamente. Cada vez que desee distribuir una nueva versión del EXE principal, solo necesita cambiar el número de la versión.

Sus números de versión pueden ser cualquiera que decida, no es necesario que sean consecutivos o en orden ascendente. Pero deben tener exactamente dos dígitos.

Asegúrese de colocar el lanzador en el mismo directorio que el archivo ejecutable principal. Luego configúrelo para que los usuarios ejecuten el lanzador cuando deseen correr la aplicación.

El código

Aquí está el código para el programa lanzador. Puede pegarlo en un archivo de programa PRG y luego compilarlo y generar un .EXE

* Programa Lanzador (genérico).
LOCAL lcExecPath, lcFileName, lcSkeleton, lnFileCount
LOCAL lcExe, ltLatest, lnI
LOCAL ARRAY laFiles(1)
* Toma la ruta del directorio del archivo ejecutable
lcExecPath = JUSTPATH(SYS(16))
* Establece este directorio como predeterminado (Default)
SET DEFAULT TO (lcExecPath)
* Toma la raíz del nombre del archivo ejecutable
lcFileName = JUSTSTEM(SYS(16))
* Crea una matriz con los nombres de los EXEs posibles
lcSkeleton = lcFileName+"??.EXE"
&& lcSkeleton es un archivo comodín
&& para ADIR()
lnFileCount = ADIR(laFiles,lcSkeleton)
* Busca el archive EXE más reciente
lcEXE = ""
ltLatest = {}
FOR lnI = 1 TO lnFileCount
  IF FDATE(laFiles(lnI,1),1) > ltLatest
    ltLatest = FDATE(laFiles(lnI,1),1)
    lcExe = laFiles(lnI,1)
  ENDIF
ENDFOR
* Lanza la ejecución del EXE más reciente.
IF NOT EMPTY(lcExe)
  DO (lcEXE)
ENDIF

Como puede ver, el programa lanzador crea un arreglo que contiene los nombres de todos los archivos EXE que cumplan con la convención de nombre. Luego toma el control del más reciente de estos archivos.

Actualizar la aplicación

A partir de ahora, cuanto desee distribuir una nueva versión de su aplicación, puede copiar el EXE actualizado dentro del directorio donde están los ejecutables. Esto se puede hacer incluso si hay usuarios trabajando en la aplicación debido a que tendrá diferente número de versión que el archivo existente. La próxima vez que el usuario llame la aplicación, el lanzador encontrará automáticamente la versión correcta. En caso necesario, puede borrar la versión anterior.

Mike Lewis Consultants Ltd. Mayo 2003

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

28 de abril de 2018

Incluir una imagen centrada en la pantalla (revisado)

Me vi en la necesidad de incluir una imagen en el escritorio (pantalla) de VFP, para ello Luis María nos ofrece una solución.

_SCREEN.ADDOBJECT("oImg", "Image") 
_SCREEN.oImg.PICTURE = "miImagen.png" 
_SCREEN.oImg.TOP = (_SCREEN.HEIGHT- _SCREEN.oImg.HEIGHT)/2 
_SCREEN.oImg.LEFT = (_SCREEN.WIDTH - _SCREEN.oImg.WIDTH)/2 
_SCREEN.oImg.VISIBLE = .T. 

La imagen debe estar en un directorio donde VFP pueda encontrarla, mejor aún si se incluye en el ejecutable.

Esta solución me presentó algunos problemas.

El primer problema es que tarda en cargarse la imagen, entonces recurrí a un comado del viejo FoxPro que muestra la imagen de inmediato.

CLEAR
@0,0 SAY 'miImagen.png' BITMAP CENTER

Este comando en VFP9 muestra una imagen de los formatos más utilizados, no sólo BMP; pero tiene el inconveniente que si se redimensiona la pantalla a un tamño menor de la imagen, esta quedará permanentemente recortada, por cual se hace necesario utilizar el objeto Image, que se sobrepone a la imagen cargada con el comando SAY.

El segundo problema es que al redimensionar la pantalla, la imagen no se redimensiona, pero VFP ofrece una solución muy sencilla que es la propiedad ANCHOR. Anclando relativamente todos los bordes de la imagen a la Pantalla se obtiene un buen resultado. Para esto se incluye la siguiente línea:

_SCREEN.oImg.anchor = 240

Anclar de forma fija los bordes de la Imagen a la Pantalla (ANCHOR = 15), crea problemas con el siguiente punto de mantener el aspecto de la imagen.

Finalmente deseaba que la imagen conservara su aspecto sin importar el tamaño, para lo cual el objeto Image tiene la propiedad STRETCH, agregamos la siguiente línea:

_SCREEN.oImg.stretch = 1

El código completo queda así:

CLEAR
@0,0 SAY 'miImagen.png' BITMAP CENTER
_SCREEN.ADDOBJECT("oImg", "Image") 
_SCREEN.oImg.PICTURE = "miImagen.png" 
_SCREEN.oImg.TOP = (_SCREEN.HEIGHT- _SCREEN.oImg.HEIGHT)/2 
_SCREEN.oImg.LEFT = (_SCREEN.WIDTH - _SCREEN.oImg.WIDTH)/2 
_SCREEN.oImg.anchor = 240
_SCREEN.oImg.stretch = 1
_SCREEN.oImg.VISIBLE = .T. 

Este código se debe colocar en el programa Principal.

Espero que les sea de utilidad.

Germán Giraldo

28 de noviembre de 2017

Algunos consejos para el uso del depurador

Artículo original: Debugging Tip
http://rickschummer.com/blog/2006/05/debugging-tip.html
Autor: Rick Schummer
Traducido por: Ana María Bisbé York


En la Conferencia GLGDW hubo una sesión en panel sobre las mejores prácticas y consejos para depurar. Yo fui uno de los que debió participar; pero Doug Hennig necesitó mi PC para su presentación sobre desarrollo de aplicaciones verticales. Entonces, he pensado mostrar mis mejores prácticas por aquí, aunque creo que Dan Freeman las mencionó brevemente durante la sesión.

Un desarrollador Visual FoxPro puede realizar un gran trabajo de configuración del depurador al configurar la ventana Examinar, colocando exactamente los puntos de interrupción que necesita para una aplicación o módulo y seleccionando ciertos eventos para que sean corridos con traza. La configuración puede cambiar en dependencia de la aplicación o un módulo específico en una aplicación. Podemos eliminar expresiones desde la ventana Examinar y colocar en ella nuevas ya que estamos verificando varios módulos, podemos activar y desactivar puntos de ruptura, y podemos mover los eventos que estamos verificando hacia y desde la lista. Otra vía en la que podemos guardar la configuración exacta del módulo y llamarlo después la configuración sin necesidad de reconfigurar las expresiones o puntos de ruptura.

Esto se puede lograr sólo desde el marco de depuración (Debug frame) Utilizando el menú, puede seleccionar Archivo - Guardar Configuración ... para crear un archivo. Este archivo guarda un diálogo de forma predeterminada en la carpeta raíz de VFP. Para recuperar una configuración anterior Menú - Archivo - Cargar Configuración ... El contenido del archivo se guarda en texto ASCII. He aquí un ejemplo:

DBGCFGVERSION=4
WATCH=_screen
WATCH=set("deleted")
WATCH=set("path")
WATCH=thisform
WATCH=curdir()
WATCH=recno()
WATCH=eof()
WATCH=_vfp.ActiveProject
BPMESSAGE=OFF
BREAKPOINT BEGIN
TYPE=2
CLASS= 
LINE=0
EXPR=EOF("curReport")
DISABLED=0
EXACT=0
BREAKPOINT END

BREAKPOINT BEGIN
TYPE=3
CLASS= 
LINE=0
EXPR="MAIN"$PROGRAM()
DISABLED=1
EXACT=0
BREAKPOINT END

EVENTWINDOW=ON
EVENTFILE=
EVENTLIST BEGIN
Activate, Deactivate
EVENTLIST END

Puede manipular el contenido de forma segura con un editor de texto y recargar la configuración. Haga copias de este archivo si le preocupa perder esta configuración.

Lo que es realmente bueno es que estos archivos se pueden crear por programación. Vea que las expresiones de la ventana Examinar, son sencillamente líneas y pueden estar en cualquier lugar del archivo. Por tanto se pueden agregar sencillamente con esta sencilla línea de código:

STRTOFILE("WATCH=ALIAS()", "MyDebugSettings.DBG", 1)

Puede además organizar las expresiones en el archivo. Algo que me molesta de la ventana Examinar es que cada nueva expresión se agrega al final de la lista. Puede agregarlas al inicio utilizando el siguiente código:

lcFileContents = FILETOSTR("MyDebugSettings.DBG")
STRTOFILE("WATCH=ALIAS()" + CHR(13) + lcFileContents, "MyDebugSettings.DBG", 1)

Una vez que la agregue, tiene que cargar nuevamente el archivo de configuración. Una desventaja es que pierde las posibilidades que ofrece IntelliSense para la ventana Examinar.

Cada vez que comento a alguien sobre esta posibilidad, siempre hay quien dice que nunca escuchó de que existía.


26 de octubre de 2017

Propuesta de Código Óptimo

Ahora estoy implementando una rutina, bajo un código que quiero que sea el óptimo en resultados, en tamaño y en tiempo. Expongo aquí las pautas y algunas de las limitaciones con las que me he encontrado de momento:

Supongamos que para un Conjunto Dado C tenemos N elementos, estos N elementos tienen todos por un lado un valor (podria ser un importe, p.ej) y tienen todos también un coste derivado de la relación entre C para llegar a cada undo de los elementos N.

Prentendo poder pasar unos valores CMin y CMax para los que las suma de valores de Subconjuntos del Conjunto C esten entre esos dos parámetros y de todos los posibles resultados encontrar el que me de un valor cuya suma de Costes del Subconjunto resultante sea la mínima posible de entre todas las posibilidades.

Voy a explicarlo en un ejemplo que quedará muchisimo más claro que todo este rollo raro:

Supongamos que B son Bancos y que el Conjunto C (BSCH) es el conjunto de recibos (Elementos) que deseo/puedo llevar a ese banco para descontar en N58.

Para cada Recibo N, tengo guardados 2 valores: 1º el importe del recibo, 2º el coste que me supone (aplicando los % y comisiones pactadas con el banco tratado y que tengo en una tabla de Cond. Bancarias).

CMin es un importe mínimo que yo debo llevar al banco, y del que no puedo bajar porque tengo por ejemplo, unos pagos que sé que hay que cubrir.

CMax es un importe máximo porque tengo la linea de descuento muy saturada y sólo me permite enviarles ese importe, con lo que no puedo llevar recibos que sumados superen este parametro. (con un relativo margen razonable, exacto es imposible).

Se Trata de Tratar todos Los Recibos enviados para este Banco y buscar sus posibles sumas combinatorias entre TODOS ellos y encajar ese importe entre los dos valores CMin y CMax obteniendo un Subconjunto Resultante tal que el coste de los intereses y comisiones bancarios sea el mínimo posible de todas las opciones que cumplan estos requisitos.

Notas:

Tengo desarrollados dos métodos.

  1. En método iterativo, es decir la programación habitual de todos nosotros, en general y la más rapida de pensar, pero no me acaba de encajar bien los resultados en cuanto al mejor coste posible.
  2. Lo tengo hecho por métodos de programación avanzada (y muy teoricos) por los teoremas de Dijkstra, Ford y Floyd... resumiendo... Backtraking de Seguimiento de Arboles y diagramas con el objetivo de obtener los caminos mínimos entre los distintos nodos del grafo (árbol). Este segundo la verdad es que me funciona muy bien al menos de momento, pero y parece ser el óptimo en resultados.... pero de momento me hallo en algún apurillo técnico... os lo resumo:
    1. El orden resultante de operaciones (sin optimizar demasiado)
    2. Es de 2^n és decir 2 a la n, siendo n el nº de recibos. o sea que para n = 4 recibos, 6 recibos, 8, recibos, 16 recibos y 50 recibos me da un total de operaciones:

      2^4 = 16 operaciones

      2^6 = 64 operaciones

      2^8 = 256 operaciones

      2^16 = 65536 operaciones

      2^25 = 33554432 operaciones

      2^50 = 1125899906842624 operaciones

      Quereis que os diga el problema o SALTA A LOS PUT... Ojos.... Perdonad !!!! :(

      Es decir, que sera la mar de óptimo y pijo... pero al menos por el momento me es totalmente inviable... Alguien no conoce alguna empresa que lleve más de 50 recibos a descontar N58, o da igual, 25 que ya ronda los 33 millones de operaciones ????

      Bueno, cabe decir que esto es para acojonar un poco, pq. hay mejoras relativamente fáciles de implementar que mejoran en algo el resultado, pero aún así ya aviso que no lo suficiente... Si estoy en algunas otras que creo que me lo pueden rebajar bastante y sino he pensado en la posibilidad de mezclar partes de los dos códigos (1º Iterativo y 2º Recursivo) y creo que por ahí tb. podria funcionar mejor (pq. así, ya es para dejarlo ahora mismo).

    3. Este problema... es peor que el anterior....
    4. Ehhh... continuad leiendo... pq. a ver si me equivoco y tiene fácil solución...

      Uso Técnicas de Backtracking y recursividad, eso significa llamadas dentro de la función X a la misma Función X, que pasa ??? que si no logro hallar un resultado o cortar (podar) el árbol de llamadas, llega un momento en que me salta un error de OverFlow por Anidamiento... no recuerdo cual es el máximo de llamadas anidadas permitidas en Vfp. 6.0 pero Toni Planas (Buen Compañero mío, saludos REY !!!!) me comento que le suena o cree que son 27... por lo que tengo el problema que aún solucionando lo de las combinaciones que seria resolver el tiempo de ejecución, estaria delante del problema técnico de que si no resuelvo (por bueno) o corto (por malo) el proceso de cada posibilidad antes de llegar a este "suspuesto" limite técnico... me "petará" por desborde.

La pregunta es ??? es evitable modif. algun parametro o set ??? pasa en Vfp. 8 o en Vfp. 9... y en ASP (pq. seguramente lo acabaré traduciendo) ???

Os pongo un ejemplo (con nº's totalmente aleatorios, pero para que veais el funcionamineto):

CMax = 9999999 (sin limite), pero CMin=15000

Conjunto C (Vector)   && Valores aleatorios, son sólo para pruebas !!!!

Pos  Imp   Coste
[1] 8,727 (20.40)
[2] 9,147 (22.15)
[3] 1,142 ( 6.34)
[4] 4,111 (11.67)
[5] 3,152 (35.36)
[6] 2,164 (19.09)
******************************************************************************************************************************
[1]     0 ( 0.00)+[2]     0 ( 0.00)+[3]     0 ( 0.00)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 17,874 * (42.55)
[1]     0 ( 0.00)+[2]     0 ( 0.00)+[3]     0 ( 0.00)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 21,985 * (54.22)
[1]     0 ( 0.00)+[2]     0 ( 0.00)+[3] 3,152 (35.36)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 21,026 * (77.91)
[1]     0 ( 0.00)+[2]     0 ( 0.00)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 16,410 * (69.18)
[1]     0 ( 0.00)+[2]     0 ( 0.00)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 15,990 * (67.43)
[1]     0 ( 0.00)+[2] 2,164 (19.09)+[3]     0 ( 0.00)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 20,038 * (61.64)
[1]     0 ( 0.00)+[2] 2,164 (19.09)+[3]     0 ( 0.00)+[4] 4,111 (11.67)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 15,422 * (52.91)
[1]     0 ( 0.00)+[2] 2,164 (19.09)+[3]     0 ( 0.00)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 15,002 * (51.16)
[1]     0 ( 0.00)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 23,190 * (97.00)
[1]     0 ( 0.00)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 18,574 * (88.27)
[1]     0 ( 0.00)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 18,154 * (86.52)
[1] 1,142 ( 6.34)+[2]     0 ( 0.00)+[3]     0 ( 0.00)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 19,016 * (48.89)
[1] 1,142 ( 6.34)+[2]     0 ( 0.00)+[3]     0 ( 0.00)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 23,127 * (60.56)
[1] 1,142 ( 6.34)+[2]     0 ( 0.00)+[3] 3,152 (35.36)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 22,168 * (84.25)
[1] 1,142 ( 6.34)+[2]     0 ( 0.00)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 17,552 * (75.52)
[1] 1,142 ( 6.34)+[2]     0 ( 0.00)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 17,132 * (73.77)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3]     0 ( 0.00)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 21,180 * (67.98)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3]     0 ( 0.00)+[4] 4,111 (11.67)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 16,564 * (59.25)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3]     0 ( 0.00)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 16,144 * (57.50)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4]     0 ( 0.00)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 15,605 * (82.94)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 15,185 * (81.19)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 19,716 * (94.61)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 19,296 * (92.86)
******************************************************************************************************************************
[1]     0 ( 0.00)+[2]     0 ( 0.00)+[3]     0 ( 0.00)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 17,874 *(42.55)

Notas: Cabe decir que en este ejemplo ya se ve una de las mejoras, pero no siempre sale así de rapido, aunque es bueno que tengamos este.

Los elementos, se deben procesar (TODOS CON TODOS, no es necesario un orden concreto, siempre que su suma supere CMIN y por debajor de CMAX (más o menos, este valor es más de referencia)

Mejoras propuestas (algunas ya implementadas, otras aún no):

  1. Ordenar los Elementos de de C por Importe ASC, de esta forma, hallando el 1er elemento que supere CMIN, podemos obtener un primer SubConjC que seran todos los valores que de por si ya superen CMin y de ellos buscar el de mín. coste con lo que obtendriamos un primer candidato a falta de procesar ahora ya con otro metodo los que han quedado por debajo de CMIN.
  2. Si al vector que nos ha quedado ahora con los elementos de C cuyo importe sea menor q CMIN y por tanto necesitaremos sumarlos con otro/s de este vector para cubrir el minimo requerido. Si ordenamos estos por Importe DESC... las primeras sumas llegaran mucho más rapido al mín. que si lo hacemos al revés... y de esta forma ya empezamos a tener unos costes mín. que nos permitiran (ver. Punto 3)
  3. Teniendo almacenado el Subconjunto que de momento nos de el coste mín. y la suma de costes de esos elementos nos dará ese valor... si procesamos otra posibilidad, y aunque no la hayamos terminado, en algun momento su mín ya supera el que nosotros tenemos como óptimo... no hace falta continuar... pq. no lo mejorariamos... ok ???

Buenos... Mentes !!!! Os pido, si teneis ganas... porque es un poco galimatias... que penseis en mejoras o si de existir en limitaciones técnicas... sabéis de formas de eviatrlas o de versiones que las amplien...

Y por dudas, ya sabeis... un mensaje y hablamos...

Grácias a todos y un fuerte abrazo.....

VIsual FoxPro ForEver !!!

Josep Mª Picañol