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