Artículo: Cuadros de diálogo (Parte II) »
JUAN F. RUIZ F. - SEP 26, 2006 (03:13:47 PM)
El funcionamiento del formulario es muy sencillo, en el campo rotulado "Carpeta Raiz" deberemos indicar la ruta completa de la carpeta que mostrará el cuadro de diálogo, en el campo con la etiqueta "Filtros" indicaremos los tipos de ficheros que va a mostrar el cuadro de diálogo siguiendo este formato :
"Cadena 1|*.ext1 |... | Cadena n|*.extn_1;*.extn_2;*.extn_3 | ... | Todos los ficheros|*.*"
En el campo con el rótulo "Extensión por defecto" sencillamente indicaremos la extensión que queremos que se muestre en el cuadro de diálogo cuando se abra.
Los campos "Carpeta" y "Ficheros" son rellenados automáticamente con lo que hayamos seleccionado en los cuadros de diálogo que hayamos abierto.
Nota : Cuando damos al botón "Cancelar" del cuadro de diálogo se nos mostrará una advertencia de que se ha producido un error. Esto no es realmente así, lo que ocurre es que es un valor más que puede retornar la función. Estoy viendo como se puede solucionar esto ( si alguien encuentra el método que me lo diga y lo pondré aquí con sus créditos correspondientes ).
La explicación
Bien, en esta última página del artículo os voy a explicar cómo se ha realizado la gestión de los cuadros de diálogo.
Si observamos con atención la biblioteca de scripts "BSDialogos" de la base de datos de ejemplo podemos ver como se realizan un montón de declaraciones de constantes así como dos estructuras definidas por el usuario. ¿Esto porqué? Bueno, son las estructuras de datos que necesitan las funciones del API para realizar su cometido y debemos recordar que todas ellas fueron desarrolladas en C y ensamblador por lo que los typedef's así como las constantes son el 'equivalente' a las struct y const del C ...
Tambien podemos ver más abajo como se realizan seis declaraciones de funciones que nos permitirán llamar a las funciones del API. Veamos, por ejemplo, la declaración que nos permite acceder a la función GetOpenFileName() del API :
Declare Function GetOpenFileName Lib "comdlg32.dll" _
Alias "GetOpenFileNameA" (pOpenfilename As OPENFILENAME) As Long
Comprobamos que se está declarando una función que se llama GetOpenFileName(), la cual podrá encontrar en la librería "comdlg32.dll", que tiene un alias por el cual nos podemos referir a la función como "GetOpenFileNameA" y que recibe como parámetro un puntero a una variable de tipo OPENFILENAME ( tipo definido por el usuario que especificamos anteriormente ). La función devolverá un entero largo.
Os preguntaréis porqué definimos un alias de la función si ya la nombramos antes ...
Una de las razones por las que se utilizan alias en las llamadas a las funciones del API es que éstas, al estar creadas en C, tienen nombres 'sensibles al caso' o lo que es lo mismo, distinguen entre mayúsculas y minúsculas; aspecto éste que no es contemplado por LotusScript que pasa todo el nombre de la función a mayúsculas.
De esta forma cuando en LotusScript llamemos a la función GETOPENFILENAME lo que hará el código es llamar a la función definida en su alias asociado, es decir, GetOpenFileNameA().
Otra razón de usar alias es por la posibilidad de que el nombre de la función en C no sea un identificador válido en LotusScript ( por hacer uso de ciertos caracteres especiales ).
Aunque normalmente en el alias se indica el nombre de la función puede haber otras ocasiones en que nos refiramos a una función por el número ordinal de la posición de la función en la librería. Para ello usamos el símbolo de la almohadilla seguido de un número.
En cuanto a los argumentos debemos tener bastante cuidado puesto que los tipos de datos en C difieren un poco de los utilizados en LotusScript y existen ciertas 'normas' en la forma de pasar argumentos a la función.
Por defecto, LotusScript pasa los argumentos a las funciones externas por referencia. Si necesitamos que alguno de los argumentos se pase por valor deberemos anteponer la palabra clave "ByVal" antes del argumento requerido y esto solo en el caso de que LotusScript pueda convertir el valor al tipo de datos que espera la función de C en ese argumento.
Los arrays, las variables typedef, y los objetos definidos por el usuario deberán ser pasadas por referencia. Tambien debemos recordar que no podemos pasar listas como argumentos a funciones de C.
Los objetos de Notes pueden ser pasados por referencia ( pasando una referencia al manejador de la instancia ) o por valor ( pásandole directamente el manejador de la instancia ) utilizando la palabra clave "ByVal". No se podrán utilizar paréntesis en los argumentos.
Si queremos que no se hagan restriciones sobre tipos de datos podemos pasar un argumento como de tipo "Any". Los argumentos de tipo "Any" se pasarán siempre por referencia, independientemente del tipo de dato real que contengan. De esta forma podremos pasar una variable variant conteniendo un array o una lista a la función C si la declaramos como de tipo "Any".
Cuando pasamos cadenas por valor, LotusScript en realidad crea una cadena terminada con el caracter NULL ( que es lo que espera la función de C ) y le pasa un puntero a esa cadena creada. Si estamos pasando un puntero a cualquier otro tipo de dato que no sea una cadena, entonces deberemos pasar el parámetro por referencia.
Debemos tener cuidado cuando trabajamos con una variable de cadena que recibe un valor de la función C del API. Si la función en C asigna un valor que es más largo que el tamaño reservado para la cadena, sobreescribirá memoria no perteneciente a la cadena lo que puede ocasionar que el programa realize una operación no permitida o incluso colgar / resetear el equipo.
Para asegurarnos de que la variable de cadena tenga espacio suficiente disponemos de dos métodos :
1 - Asignarle un valor que al menos sea tan largo como la cadena que será devuelta antes de pasarle la variable a la función. Declararemos la variable con el suficiente tamaño en el caso de que sea una cadena de tamaño fijo. Por ejemplo, si la longitud máxima para la cadena es 255, entonces podremos usar la función String() para colocar 255 caracteres NULL en una cadena de longitud variable :
Dim sParam As String
sParam$ = String(255, 0)
2 - Declarar una variable de cadena de longitud fija de esta forma :
Dim sParam As String * 255
Normalmente, las funciones del API, en el caso de que reciban argumentos de cadena para guardar valores devuelven un b>entero indicando la cantidad de caracteres que ha escrito en ella. De esta forma, si hemos usado una variable de cadena de tamaño variable, podremos usar este valor de retorno para eliminar los NULL's sobrantes del final de la cadena. Por ejemplo :
Dim VenTitulo As String, VenLongitud As Integer
VenTitulo = String(255, 0)
VenActiva% = GetActiveWindow()
VenLongTitulo% = GetWindowText(VenActiva%, VenTitulo$, 255)
VenTitulo$ = Left(VenTitulo$, VenLongTitulo%)
En el caso de que la función del API no devolviera la cantidad de caracteres escritos en la cadena, podremos extraer la porción izquierda de la cadena hasta el primer caracter NULL con el código siguiente :
CadenaDesdeC$ = Left(CadenaDesdeC$, Instr(CadenaDesdeC$,Chr$(0)) -1)
En cuanto a las funciones en sí, normalmente se limitan a colocar los valores pasados como argumentos en los sitios adecuados y llamar a la función del API para luego devolver el resultado aunque debo indicar que extendí las funciones originales para que puedieran devolver una selección de ficheros echa por el usuario.
Para ello, en el caso de la función DlgAbrirFicheroExt() debía tratar lo devuelto por la función GetOpenFileName() de forma que pudiera tener la lista de ficheros seleccionada y además la ruta en la que se seleccionaron. Para ello recorro en un bucle el miembro fStructure.lpstrFile de la estructura fStructure en busca de caracteres NULL.
El conjunto de caracteres desde el principio hasta el primer NULL es la ruta donde se encuentran los ficheros seleccionados. El resto de elementos que encuentre serán los nombres de los ficheros. Cada vez que encuentro un NULL lo sustituyo por un asterisco. El bucle termina cuando encuentra dos caracteres NULL seguidos ya que esto indicaría que hemos llegado a la 'zona de relleno' de la cadena.
Una vez separados todos los elementos mediante asteriscos lo único que hago es recorrer de nuevo la cadena en busca de ellos e ir guardando cada uno de los elementos en un vector de cadenas añadiendo a cada elemento ( que es un nombre de fichero ) la ruta obtenida anteriormente.Por último devuelvo este vector ( o un error en el caso de que se produzca ).
Aunque parezca complicado en un primer momento trabajar con el API de Windows desde LotusScript no debemos desalentarnos y solo hace falta poner un poco de cuidado en su uso para conseguir funcionalidades y resultados que serían muy complicados de obtener ( o imposible ) de otra forma.


