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.
Propiedad | Descripción |
---|---|
UserTyped | Texto del pad del menú del que procede el hit. |
MenuItem | Texto del elemento del menú seleccionado por el usuario. |
ValueType | Un 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.