18 de noviembre de 2004

Saber si un número es par

Tres funciones distintas para saber si un número entero es par.

1. Utilizando el operador %
FUNCTION EsPar(tnEntero)
  RETURN tnEntero % 2 = 0
ENDFUNC
2. Utilizando el operador ^
FUNCTION EsPar(tnEntero)
  RETURN -1 ^ tnEntero = 1
ENDFUNC
3. Utilizando BITTEST()
FUNCTION EsPar(tnEntero)
  RETURN NOT BITTEST(tnEntero,0)
ENDFUNC
Nota del editor: Las funciones 1. y 2. fueron tomadas del Grupo de Noticias de VFP en español, de respuestas enviadas por Luis María Guayán y Dario David Puccio respectivamente. La función 3. fue tomada de la ayuda de VFP de la función BITTEST().

5 de noviembre de 2004

Ventajas y desventajas del uso de formularios basados en VCX o SCX

Autor: Drew Speedie
Traducido por: Ana María Bisbé York


Fragmento de la sesión VFP Tips and Tricks presentada por el autor en la Conferencia DevEssentials 2004
Existen ventajas y desventajas para crear/guardar formularios VFP en .SCXs o .VCXs. He aquí algunas de ellas:


Formularios basados en VCX: VENTAJAS
  • Fácilmente subclaseable
  • Muchos formularios pueden estar contenidos en un fichero VCX/.VCT (desventaja para desarrollo en grupos)
  • Posibilidad de utilizar propiedades y métodos protegidos (Protected) y ocultos (Hidden)
  • Puede sustituir una subclase DataEnvironment definida por el usuario (programa .PRG) en tiempo de ejecución.
  • Control completo sobre la apertura de tablas/vistas
Formularios basados en VCX: DESVENTAJAS
  • No puede utilizar DataEnvironment visual nativo
  • Como no puede utilizar DataEnvironment visual nativo, debe llenar manualmente las propiedades ControlSources y RecordSources
  • Más dificultad para ejecutar/probar, son necesarios 2 ó 3 comandos
  • No existe una vía directa para devolver un valor de un formulario modal
  • No tiene disponible el Asistente de formulario (de todas formas no recomendamos utilizar asistentes nativos...)
  • Si varios formularios están contenidos en una librería .VCX/.VCT, el desarrollo en equipo se hace más difícil, debido a que es más complicado que más de un desarrollador trabajen con formularios diferentes guardados en el mismo archivo .VCX
Formularios basados en SCX: VENTAJAS
  • Puede utilizar DataEnvironment visual nativo
  • Si se utiliza DataEnvironment, puede llenar ControlSources y RecordSources a través de los cuadros desplegables
  • Es fácil de ejecutar/probar, con un comando o con la opción Ejecutar en la barra de herramientas estándar.
  • Una vía simple, directa para devolver un valor de un formulario modal.
  • Puede sustituir una subclase DataEnvironment definida por el usuario (programa .PRG) en tiempo de ejecución.
  • Tiene disponible el Asistente de formulario (de todas formas no recomendamos utilizar asistentes nativos...)
  • El desarrollo en equipo es menos problemático porque cada formulario está siempre guardado en su propio archivo .SCX/.SCT
Formularios basados en SCX: DESVENTAJAS
  • Un poco más difíciles de subclasear; la intención es utilizarlo en el nivel instanciado.
  • Sólo un formulario por archivo .SCX/.SCT (muchos archivos individuales), pero puede ser una ventaja en el desarrollo de proyectos en equipo (vea arriba)
  • No se pueden utilizar propiedades y métodos protegidos (Protected) y ocultos (Hidden)
  • No existe control real sobre la apertura de tablas/vistas en el DataEnvironment (aunque, si lo desea, lo puede realizar en el Form.Load(); pero perderá los beneficios de utilizar un DataEnvironment nativo, mientras tiene otros ventajas de formularios basados en SCX)
Existe una vía de tomar lo mejor de ambos mundos, particularmente para tomar las ventajas de poder agregar cursores a un formulario basado en .SCX DataEnvironment, donde los nombres de tablas y nombres de campos están disponibles en varias interfaces del Diseñador de formularios:
  1. Crea un formulario basado en .SCX
  2. Agregue cursores al DataEnvironment – estos cursores nunca serán abiertos en tiempo de ejecución, así sean tablas/vistas cuando estás utilizando objetos de negocio o DataEnvironment de usuario.
  3. Establecer la propiedad DataEnvironment.AutoCloseTables a .F.
  4. Establecer la propiedad DataEnvironment.AutoOpenTables a .F.
  5. Establecer manualmente el entorno de datos:
    • Agregar código manual en el Form.Load()
    • Agregar código en el Form.Load() para el lazo entre el DataEnvironment (ignorado en tiempo de ejecución) y ejecutar el código equivalente al objeto Cursor y sus propiedades en los objetos Relation.
    • Implementar su objeto DataEnvironment sustituto/de usuario
    • Liberar el control de los datos a su objeto de negocio
Esta técnica permite aprovechar todas las ventajas de los formularios basados en .SCX, mientras acomoda todas las consideraciones que típicamente fueron creadas para formularios basados en .VCX. Incluso en un desarrollo n-capas, si su capa intermedia proporciona cursores a la capa de presentación, puede agregar los cursores al DataEnvironment donde están disponibles en tiempo de diseño, y establecer simplemente la propiedad Alias al alias proporcionado en la capa intermedia.

Espero que les haya resultado de utilidad.

Saludos,

Ana María Bisbé York

2 de noviembre de 2004

Menú dinámico

Hola a Todos, esto se me ocurrió hace tiempo y recién lo he podido implementar, mi idea era generar un menú que se encuentre definido en una tabla dbf y luego llamarlo desde un formulario de nivel superior, para que no se necesite compilar nuestra aplicación si queremos cambiar algo en el menú, ojalá les sirva de utilidad.

****************************************
* MODO DE USO :
* COPIAR EL TEXTO EN UN PRG EN
* CUALQUIER CARPETA EJ: MENU.PRG
* DO MENU.PRG
****************************************
* INDICACIONES :
* EL CODIGO SE ENCARGA DE CREAR UN FORM DE NIVEL SUPERIOR, GENERA EL
* MENU Y HACE LA LLAMADA AL MISMO LA PRIMERA VEZ QUE SE EJECUTA EL
* CODIGO, VERIFICA SI EXISTE LA TABLA DE MENU, EN LA CUAL SE DEFINEN LOS
* NIVELES DE PROFUNDIDAD DE CADA OPCION DEL MENU, LO CUAL LO PUEDES
* MANEJAR SEGUN REQUERIMIENTO LAS OPCIONES DE LOS MENUS NO VAN A
* FUNCIONAR AL NO EXISTIR LOS FORMULARIOS EN LA RUTA, ESO LO DEBES
* CAMBIAR SEGUN REQUERIMIENTO.

IF !FILE("MENU.DBF")
  CREATE TABLE MENU (MODULO C(2),;
    CODIGO C(14),;
    DESCRIPC C(50),;
    COMMAND C(50))
  SELECT MENU
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01000000000000","Tablas","")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01010000000000","Clientes","DO FORM CLIENTES.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01020000000000","Proveedores","DO FORM PROVEEDORES.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01030000000000","Maestras","ACTIVATE POPUP P010301")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01030100000000","Trabajadores","ACTIVATE POPUP P01030101")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01030101000000","Empleados","DO FORM EMPLEADOS.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01030102000000","Obreros","ACTIVATE POPUP P0103010201")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","01030102010000","Obreros Clasificados","DO FORM CLASIFICA.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02000000000000","Procesos","")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02010000000000","Calcular Encuesta","DO FORM CALCULAR.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020000000000","Tipos De Empleados","ACTIVATE POPUP P020201")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020100000000","Obreros","DO FORM OBREROS.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020100000000","Empleados","DO FORM EMPLEADOS.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020100000000","Contratistas","ACTIVATE POPUP P02020101")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020101000000","Luz Del Sur","ACTIVATE POPUP P0202010101")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020101010000","Electricistas","ACTIVATE POPUP P020201010101")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020101010100","Juan Perez","DO FORM JUAN.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("01","02020101010200","Jose Chavez","DO FORM JOSE.SCX")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("02","03000000000000","Ayuda","")
  INSERT INTO MENU(MODULO,CODIGO,DESCRIPC,COMMAND) VALUES ("02","03010000000000","Acerca De","do form acercade.scx")
  INDEX ON modulo+codigo TAG ORDEN
  CLOSE ALL
ENDIF
IF USED('TABMENU')
  USE IN TABMENU
ENDIF

oForm = CREATEOBJECT('MyFormSample')
oForm.SHOW
READ EVENTS

DEFINE CLASS MyFormSample AS FORM
  NAME = 'FrmPrincipal'
  CAPTION = 'Formulario Principal'
  SHOWWINDOW = 2
  WINDOWSTATE = 2
  cNameMenuMP = ""
  PROCEDURE LOAD
    =MESSAGEBOX("ejecutando load")
    THISFORM.Previo()
      \LPARAMETER oFormRef,getMenuName
      \
      \LOCAL cMenuName
      \IF TYPE("m.oFormRef") # "O" OR LOWER(m.oFormRef.BaseClass) # 'form' OR m.oFormRef.ShowWindow # 2
      \   RETURN
      \ENDIF
      \m.cMenuName = IIF(TYPE("m.getMenuName")="C",m.getMenuName,SYS(2015))
      \IF TYPE("m.getMenuName")="L" AND m.getMenuName
      \   m.oFormRef.Name = m.cMenuName
      \ENDIF
      \DEFINE MENU (m.cMenuName) IN (m.oFormRef.Name) BAR
      \*
    SELECT TABMENU
    nNumberMenuPrin = 0
    SELECT TABMENU
    SCAN FOR INT(VAL(SUBSTR(CODIGO,3)))=0
      nNumberMenuPrin = nNumberMenuPrin + 1
      cDescripcion = ALLTRIM(TABMENU.descripc)
      =MESSAGEBOX("Creando Menú " + cDescripcion)
      cNamePad = "PAD" + PADL(ALLTRIM(STR(nNumberMenuPrin)),3,'0')
      \DEFINE PAD <<cNamePad>> OF (m.cMenuName) PROMPT "<<cDescripcion>>" COLOR SCHEME 4 KEY ALT+T, ""
    ENDSCAN
    SELECT LEFT(CODIGO,2) AS GRUPO FROM TABMENU GROUP BY 1 ORDER BY 1 INTO CURSOR _CrsOpciones
    nMenuTotal = RECCOUNT("_CrsOpciones")
    IF USED('_CrsOpciones')
      USE IN _CrsOpciones
    ENDIF
    nNumberMenuPrin = 0
    SELECT TABMENU
    SCAN FOR INT(VAL(SUBSTR(CODIGO,3)))=0
      nNumberMenuPrin = nNumberMenuPrin + 1
      cDescripcion = ALLTRIM(TABMENU.descripc)
      cNamePad   = "PAD" + PADL(ALLTRIM(STR(nNumberMenuPrin)),3,'0')
      cNamePopup = "P" + LEFT(TABMENU.CODIGO,2) &&& + PADL(ALLTRIM(STR(nNumberMenuPrin)),2,'0')
      \ON PAD <<cNamePad>> OF (m.cMenuName) ACTIVATE POPUP <<cNamePopup>>
    ENDSCAN

    * DEFINIENDO POPUS POR NIVELES
    nBarNivel = 0
    SELECT TABMENU
    GO TOP
    FOR nNiveles = 2 TO 6
      nSubStrCero = ((nNiveles+1)+nNiveles)
      nLeft = nSubStrCero-1
      nLeftNivelFactor = IIF(nNiveles=2,2,nNiveles*2)
      FOR nMenuPrin = 1 TO nMenuTotal
        SELECT * FROM TABMENU WHERE LEFT(TABMENU.CODIGO,2) = PADL(ALLTRIM(STR(nMenuPrin)),2,'0') AND ;
          INT(VAL(SUBSTR(TABMENU.CODIGO,nSubstrCero))) = 0 AND ;
          RIGHT(LEFT(TABMENU.CODIGO,nLeft),2) <> '00';
          INTO CURSOR _CrsConsultaSQL
        IF RECCOUNT('_CrsConsultaSQL')>0
          IF USED('_CrsConsultaSQL')
            USE IN _CrsConsultaSQL
          ENDIF
          nBarNivel = 0
          lEsPrimero = .T.
          SELECT TABMENU
          SCAN FOR LEFT(TABMENU.CODIGO,2)=PADL(ALLTRIM(STR(nMenuPrin)),2,'0') AND ;
              INT(VAL(SUBSTR(TABMENU.CODIGO,nSubstrCero))) = 0 AND ;
              RIGHT(LEFT(TABMENU.CODIGO,nLeft),2) <> '00'
            cTagNivelNamePad = LEFT(TABMENU.codigo,nLeftNivelFactor) &&& SUBSTR(TABMENU.codigo,3,2)
            IF lEsPrimero
              cPadNameNivel = "P" + cTagNivelNamePad
              \
              \DEFINE POPUP <<cPadNameNivel>> MARGIN RELATIVE SHADOW COLOR SCHEME 4
              lEsPrimero = .F.
            ENDIF
            cTagBar = ALLTRIM(TABMENU.DESCRIPC)
            nBarNivel = nBarNivel + 1
            \DEFINE BAR <<nBarNivel>> OF <<cPadNameNivel>> PROMPT "<<cTagBar>>"
          ENDSCAN
        ELSE
          IF USED('_CrsConsultaSQL')
            USE IN _CrsConsultaSQL
          ENDIF
        ENDIF
        nBarNivel = 0
        SELECT TABMENU
        SCAN FOR LEFT(TABMENU.CODIGO,2)=PADL(ALLTRIM(STR(nMenuPrin)),2,'0') AND ;
            INT(VAL(SUBSTR(TABMENU.CODIGO,nSubstrCero)))=0 AND ;
            RIGHT(LEFT(TABMENU.CODIGO,nLeft),2)<>'00'
          cTagBar = ALLTRIM(TABMENU.DESCRIPC)
          cTagCom = ALLTRIM(TABMENU.COMMAND)
          nBarNivel = nBarNivel + 1
          IF "ACTIVATE POPUP"$cTagCom
            \ON BAR <<nBarNivel>> OF <<cPadNameNivel>> <<cTagCom>>
          ELSE
            \ON SELECTION BAR <<nBarNivel>> OF <<cPadNameNivel>> <<cTagCom>>
          ENDIF
        ENDSCAN
      NEXT
    NEXT
    \ACTIVATE MENU (m.cMenuName) NOWAIT
    SET TEXTMERGE TO
    SET TEXTMERGE OFF
    COMPILE (THISFORM.cNameMenuMP+".MPR")
  ENDPROC
  PROCEDURE Previo
    SET TEXTMERGE ON
    cFileMenu = CURDIR() + "MENU.DBF"
    IF !FILE(cFileMenu)
      =MESSAGEBOX("No existe el archivo de Menú ...!"+CHR(13)+cFileMenu,16,"Error al cargar Form")
      RETURN .F.
    ENDIF
    USE (cFileMenu) IN 0 SHARED ALIAS TABMENU ORDER ORDEN
    THISFORM.cNameMenuMP = GETENV("TEMP")+"\"+"_" + RIGHT(SUBSTR(SYS(2015), 3),3) + RIGHT(SUBSTR(SYS(2015), 3),3) + RIGHT(SUBSTR(SYS(2015), 3),1)
    SET TEXTMERGE TO (THISFORM.cNameMenuMP+".MPR") NOSHOW
  ENDPROC
  PROCEDURE INIT
    DO (THISFORM.cNameMenuMP+".MPX") WITH THISFORM,.F.
    THISFORM.RESIZE()
  ENDPROC
  ADD OBJECT CmdSalir AS MyButtonQuit  WITH ;
    CAPTION = "\<Salir"
ENDDEFINE


DEFINE CLASS MyButtonQuit AS COMMANDBUTTON
  HEIGHT = 30
  WIDTH = 130
  TOP = 50
  LEFT = 100
  NAME = 'CmdSalir'
  CAPTION = '\<Salir'

  PROCEDURE CLICK
    IF MESSAGEBOX("¿Está seguro que desea Salir?",36,THISFORM.CAPTION) = 6
      CLEAR EVENTS
      THISFORM.RELEASE
    ENDIF
  ENDPROC
ENDDEFINE

PD: Se reciben sugerencias y comentarios. Muchas Gracias.

Jesús Rojas Cárdenas (Lima - Perú)