31 de marzo de 2005

Obtener y ajustar el volumen de los altavoces

Dos funciones para obtener y ajustar el volumen de los altavoces mediante llamados a la API de Windows.

Para obtener el volumen de los altavoces utilizamos función GetVolume() que retorna una cadena Hexadecimal, donde los 4 primeros caracteres (sin el "0x") es el correspondiente al altavoz derecho, y los siguientes 4 corresponden al altavoz izquierdo.

En el siguiente ejemplo transformamos la cadena retornada para obtener el porcentaje de volumen de cada altavoz.

lcHex = GetVolume()
*-- % volumen altavoz izquierdo
lnIzq = CEILING(BITAND(EVALUATE(lcHex),0xFFFF)*100/0xFFFF)
*-- % volumen altavoz derecho
lnDer = CEILING((BITAND(EVALUATE(lcHex),0xFFFF0000)/0x10000)*100/0xFFFF)

? lcHex    && cadena hexadecimal
? lnIzq    && porcentaje volumen altavoz izquierdo
? lnDer    && porcentaje volumen altavoz derecho

*---------------------------------------------------
* FUNCTION GetVolume()
*---------------------------------------------------
* Toma el valor de volumen de los altavoces de la PC
* RETORNO: Caracter (cadena de caracteres hexadecimal)
* USO: GetVolume()
*---------------------------------------------------
FUNCTION GetVolume()
LOCAL ln
  DECLARE INTEGER waveOutGetVolume IN Winmm ;
    INTEGER wDeviceID, ;
    INTEGER @ dwVolume
    ln = 0x0000
    =waveOutGetVolume(0,@ln)
    RETURN TRANSFORM(ln,";@0")
ENDFUNC
*---------------------------------------------------

Para ajustar el volumen de los altavoces utilizamos la función SetVolume() pasandole como parámetro el porcentaje de cada altavoz (izquierdo y derecho).

En el siguiente ejemplo ajustamos el volumen al 75% cada altavoz.

? SetVolume(75,75)

*---------------------------------------------------
* FUNCTION SetVolume(tnIzq, tnDer)
*---------------------------------------------------
* Configura el volumen de los altavoces de la PC
* PARAMETROS:
*   tnIzq = Porcentaje de volumen altavoz izquierdo
*   tnDer = Porcentaje de volumen altavoz derecho
* RETORNO: Logico .T. si pudo configurar
* USO: SetVolume(50,50)
*---------------------------------------------------
FUNCTION SetVolume(tnIzq, tnDer)
  LOCAL lnVol
  DECLARE INTEGER waveOutSetVolume IN Winmm ;
    INTEGER wDeviceID, ;
    INTEGER dwVolume
  tnIzq = MAX(0,MIN(tnIzq,100))
  tnDer = MAX(0,MIN(tnDer,100))
  lnVol = EVALUATE("0x" + ;
    RIGHT(TRANSFORM(tnDer*0xFFFF/100,";@0"),4) + ;
    RIGHT(TRANSFORM(tnIzq*0xFFFF/100,";@0"),4))
  RETURN 0 = waveOutSetVolume(0,lnVol)
ENDFUNC
*---------------------------------------------------

Nota: El control de volumen con las funciones de la API waveOutSetVolume y waveOutGetVolume de este ejemplo solo trabajan para el tipo Onda (Wave) y no sobre el control de volumen general del sistema.

Luis María Guayán

28 de marzo de 2005

Número de día del año

Rutina para obtener el número de día del año de una fecha.
*-- Ejemplo de uso:
? DoY()                 && Hoy
? DoY(DATE(2005,2,5))   && 5 de Febrero
? DoY(DATE(2004,12,31)) && Año bisiesto

*------------------------------------------------
* FUNCTION DoY(tdFecha)
*------------------------------------------------
* Retorna el número de día del año.
* (Número de días a partir del 1° de Enero)
*------------------------------------------------
FUNCTION DoY(tdFecha)
  IF EMPTY(tdFecha)
    tdFecha = DATE()
  ENDIF
  RETURN tdFecha - DATE(YEAR(tdFecha),1,1) + 1
ENDFUNC
*------------------------------------------------
Luis María Guayán

21 de marzo de 2005

Números consecutivos para nuestras tablas

Muchas veces necesitamos crear números consecutivos para nuestras tablas, como ser Números de Facturas, Albaranes, Remitos, Clientes, Ordenes, etc. Una forma de hacerlo es tener una tabla con los últimos números generados, y una función que genere el siguiente número consecutivo para cada una de nuestras necesidades.

La tabla de números consecutivos (Ids) puede ser una tabla libre o una tabla incluida en nuestra base de datos, y solo contendrá dos campos, uno con el nombre de la tabla (cTabla C(30)) y el otro con el último número utilizado (cId I) y un índice por el campo cTabla.

La creación de la tabla y el índice lo realizamos por única vez con el siguiente código:
CREATE TABLE Ids ;
  (cTabla C(30) NOT NULL, ;
  nId I NOT NULL)
INDEX ON UPPER(cTabla) TAG cTabla
Por cada tabla que necesitemos llevar un número consecutivo, insertamos un registro con el nombre de la tabla e inicializamos el contador en 0 (cero):
INSERT INTO Ids (cTabla, nId) VALUES ("Facturas", 0)
INSERT INTO Ids (cTabla, nId) VALUES ("Albaranes", 0)
INSERT INTO Ids (cTabla, nId) VALUES ("Clientes", 0)
INSERT INTO Ids (cTabla, nId) VALUES ("Ciudades", 0)
La función que nos generará el nuevo número consecutivo (NuevoId()), puede estar ubicada en un archivo de procedimientos de nuestra aplicación, o puede ser un procedimiento almacenado de nuestra base de datos. El código de la función es el siguiente:
FUNCTION NuevoID(tcAlias)
  LOCAL lcAlias, lnId, lnAreaAnt, lcReprAnt
  lnId = 0
  lnAreaAnt = SELECT()
  lcReprAnt = SET('REPROCESS')
  SET REPROCESS TO AUTOMATIC
  lcAlias = UPPER(ALLTRIM(tcAlias))
  IF NOT USED("Ids")
    USE Ids IN 0
  ENDIF
  SELECT Ids
  IF SEEK(lcAlias, "Ids", "cTabla")
    IF RLOCK()
      REPLACE nId WITH nId + 1 IN Ids
      lnID = Ids.nId
      UNLOCK
    ENDIF
  ENDIF
  SELECT (lnAreaAnt)
  SET REPROCESS TO lcReprAnt
  RETURN lnID
ENDFUNC
Cada vez que necesitemos un nuevo número consecutivo de una tabla, invocamos la función pasando el nombre de la tabla o su alias como parámetro:
lnNuevoId = NuevoId("Facturas")
El valor retornado por la función NuevoId() es del tipo Entero. Si por ejemplo el código de cliente de nuestra tabla Clientes es del tipo caracter de 4 y justificado con 0s (ceros) a la izquierda, transformamos el número retornado de la siguiente manera:
lcCodCli = TRANSFORM(NuevoId("Clientes"), "@L 9999")
Espero que este breve código sea de utilidad para Uds.

Hasta la próxima.

Luis María Guayán

14 de marzo de 2005

Leyendo Servidores SQL Server con el API ODBC

Existen diversas formas de saber cuales son los Servidores SQL Server disponibles, una de ellas, es la que comento a continuación; utilizando el API del ODBC.

*
*********************************************************************************************
* Ejemplo básico, de como leer servidores disponibles de SQL Server por medio del ODBC API. *
*********************************************************************************************
*

* REFERENCIA GENERAL: MSDN ODBC Programmer's Reference
*
= ServidoresSQLDisponibles( )
*

PROCEDURE ServidoresSQLDisponibles( )
  *
  * Constantes.
  *
  #DEFINE V_SQL_HANDLE_ENV      1
  #DEFINE V_SQL_HANDLE_DBC     2
  #DEFINE V_SQL_ODBC_VERSION  200  #DEFINE V_SQL_ODBC3              3
  #DEFINE V_SQL_DATO                99  #DEFINE V_TRAMA_TAMANO        2048  #DEFINE V_SQL_DRIVER             "DRIVER=SQL SERVER"
  *
  **********************
  * Declaraciones API. *
  **********************
  *
  * REFERENCIA API:
http://msdn.microsoft.com/library/en-us/odbc/htm/odbcSQLBrowseConnect.asp
  *
  DECLARE SHORT SQLBrowseConnect IN ODBC32 ;
  INTEGER ConnectionHandle, ;
  STRING InConnectionString, ;
  INTEGER StringLength1, ;
  STRING @ OutConnectionString, ;
  INTEGER BufferLength, ;
  INTEGER @ StringLength2Ptr
  *
  * REFERENCIA API:
http://msdn.microsoft.com/library/en-us/odbc/htm/odbcsqlallochandle.asp
  *
  DECLARE SHORT SQLAllocHandle IN ODBC32 ;
  INTEGER HandleType, ;
  INTEGER InputHandle, ;
  INTEGER @ OutputHandlePtr
  *
  * REFERENCIA API:
http://msdn.microsoft.com/library/en-us/odbc/htm/odbcsqlfreehandle.asp
  *
  DECLARE SHORT SQLFreeHandle IN ODBC32 ;
  INTEGER HandleType, ;
  INTEGER Handle
  *
  * REFERENCIA API:
http://msdn.microsoft.com/library/en-us/odbc/htm/odbcsqlsetenvattr.asp
  *
  DECLARE SHORT SQLSetEnvAttr IN ODBC32 ;
  INTEGER EnvironmentHandle, ;
  INTEGER Attribute, ;
  INTEGER ValuePtr, ;
  INTEGER StringLength
  *
  **********************
  * CÓDIGO DE EJEMPLO. *
  **********************
  *
  LOCAL lnIndi, lnIOHandle, lnCX, lnLongitudSalida
  LOCAL lcEntrada, lcSalida
  LOCAL ARRAY laServidores[1]
  *
  STORE 0                                         TO lnIOHandle, lnCX, lnLongitudSalida
  STORE V_SQL_DRIVER                    TO lcEntrada
  STORE SPACE( V_TRAMA_TAMANO )TO lcSalida
  *
  TRY
    *
    IF SQLAllocHandle( V_SQL_HANDLE_ENV, lnIOHandle, @lnIOHandle ) == 0
      *
      IF ( SQLSetEnvAttr(lnIOHandle, V_SQL_ODBC_VERSION, V_SQL_ODBC3, 0 ) ) == 0
        *
        IF SQLAllocHandle( V_SQL_HANDLE_DBC, lnIOHandle, @lnCX ) == 0
          *
          IF ( SQLBrowseConnect( lnCX, @lcEntrada, LEN( lcEntrada ), @lcSalida, V_TRAMA_TAMANO, @lnLongitudSalida ) ) == V_SQL_DATO
            *
           FOR lnIndi = 1 TO ALINES( laServidores, STREXTRACT(lcSalida, '{', '}'), .T., ',' )
              *
              ?laServidores[ lnIndi ]
              *
            ENDFOR
            *
          ENDIF
          *
        ENDIF
        *
      ENDIF
      *
    ENDIF
   *
  CATCH TO loException
  FINALLY
    *
    IF lnCX <> 0
      *
      SQLFreeHandle( V_SQL_HANDLE_DBC, lnCX )
      *
    ENDIF
    *
    IF lnIOHandle <> 0
      *
      SQLFreeHandle( V_SQL_HANDLE_ENV, lnCX )
      *
    ENDIF
    *
  ENDTRY
  *
ENDPROC
*
REFERENCIA GENERAL: MSDN ODBC Programmer's Reference
 

Antonio Muñoz de Burgos y Caravaca
www.emans.com  (Web realizada en vFoxPro)
eMans AC (Administrador Corporativo para SQL Server 2000&2005)
Sevilla - España
Manifestando el apoyo a la comunidad de desarrolladores de MS Visual FoxPro.

3 de marzo de 2005

Dos formas de agregar un objeto a otro

Hay dos formas de agregar un objeto a otro: usando AddObject / NewObject o como propiedad...

Los métodos AddObject y NewObject de los objetos son para agregar nuevos objetos (y sólo objetos) a objetos existentes.

En cambio las asignaciones con "=" o con AddProperty se realizan como asignaciones de propiedades, aunque las mismas puedan contener objetos.

No sé si hay un motivo teórico para esta distinción entre agregar como objeto o como propiedad, pero hay varios motivos prácticos, cada uno con sus pros y contras:

AGREGAR UN OBJETO A OTRO OBJETO

usando AddObject o NewObject
  • Ventajas:
  • - Permite que el objeto interior (contenido) pueda referirse al objeto exterior (padre) usando la sintaxis THIS.PARENT - Permite a varios objetos setear u obtener datos entre sí en cualquier dirección (hijo a padre, padre a hijo, hijo a hermano, etc.) - Los objetos agregados forma parte de una colección dentro del padre, que puede recorrerse fácilmente por programa.
  • Desventajas:
  • - Hay objetos que pueden dar problemas con esta forma de juntar objetos, por ejemplo los basados en SESSION, ya que el objeto agregado (hijo) estará en la sesión del objeto padre. - Aumenta el acoplamiento, el padre puede acceder a la funcionalidad del hijo y el hijo puede acceder a la funcionalidad del padre
AGREGAR UN OBJETO A OTRO OBJETO COMO PROPIEDAD

usando AddProperty, "=" o STORE
  • Ventajas:
  • - Todos los objetos son compatibles con esta forma de agregación, incluyendo los basados en SESSION, ya que cada uno mantiene su sesión predeterminada - Disminuye el acoplamiento porque el objeto padre puede acceder al objeto hijo, pero el objeto hijo no puede acceder al objeto padre. Esto permite a que el objeto hijo sea funcionalmente independiente del objeto padre.
  • Desventajas:
  • - No permite que el objeto interior (contenido) pueda referirse al objeto exterior (padre) usando la sintaxis THIS.PARENT - Los objetos agregados no forman parte de ninguna colección dentro del padre, por lo que para recorrerlos hay que buscarlos primero.
Observaciones: - No hay diferencias de performance entre ambas formas de unir objetos - Elegir entre una forma u otra puede deberse a varios motivos, pero dos motivos mutuamente excluyentes son: Usar clases SESSION y querer la referencia THIS.PARENT

Fernando D. Bozzo