Delphi paso a paso (V): Controles (III) Por Vladimir Algara Como ya se anunció en el número anterior de Algoritmo, en este artículo se abordará la forma de manipular las funcionalidades de los ListBox y así solucionar muchas de las necesidades que se presentan a la hora de perfilar una aplicación. Recordando lo visto hasta ahora acerca de ListBox, saber que partimos de una colección de datos almacenados en forma de tabla, que podemos rellenar a nuestro gusto (manualmente o leyendo los datos almacenados en un archivo). Esta fuente de la que obtener los datos, como veremos más adelante, no se va a quedar anclada aquí, todo lo contrario, se va a caracterizar por su diversificación. Una vez relleno el ListBox, podía ser ordenado o restaurado según el original, así como aplicarle cualesquiera de las propiedades y acciones comunes a los controles, a saber, inhabilitar, redimensionar, ocultar, etc. Resumiendo, no hemos visto gran cosa de lo controles tipo ListBox. A partir de que se conozcan más formas de rellenar controles de tipo ListBox, se irán viendo más funcionalidades y más ventajas, y ése, concretamente, es el cometido del presente artículo, por lo tanto, me voy a dejar de zarandajas y me meteré en el meollo de la cuestión, porque ya lo dijo..., bla, bla, bla..., etc.,...bla, rebla. Tipos de ListBox Los ListBox, o sus hermanos los ComboBox, se encuentran en la carpeta Standard y los iconos que los representan (no seleccionados y seleccionados) son los de la figura 1. Figura 1. Iconos para ListBox y ComboBox El nombre (Name) que por defecto se da a un control de tipo ListBox es ListBox1, ListBox2, etc., y esto será lo primero que iremos cambiando, pues ello permitirá una mejor localización e identificación posterior (en el ejemplo final vamos a manipular hasta un total de tres listas deslizantes). Existen otros tipos de ListBox, diferentes a los que vienen en la paleta Standard, de ellos vamos a ver dos, uno destinado a mostrar un árbol de los directorios del disco y otro para ver los archivos de un directorio específico. Con ambos dos y con uno estándar, construiremos una ventana en la que se podrá teclear el nombre y/o la extensión de los archivos que queremos visualizar (será en un control de edición). De acuerdo al nombre escrito allí se almacenará en un ListBox de la clase TFileListBox la relación de todos los archivos que cumplan la condición de la máscara, para facilitar la navegación por los distintos directorios se recurrirá a un ListBox de la clase TFileListBox y para tener una relación de los archivos que se vayan seleccionando se utilizará un ListBox de la clase TListBox (el estándar). Dado que este ListBox final va a tener un filtro de todos aquellos archivos que han merecido nuestra atención, si partimos de que los archivos a tratar van a ser BitMaps, podemos completar el ejemplo haciendo que el BitMap elegido se visualice por pantalla. 1 Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS Para resumir, y como soy consciente de que la explicación escrita no ha quedado nada clara, muestro en la figura 2 la pantalla a la que quiero llegar. Figura 2. Pantalla ejemplo de ListBox Nota: Los distintos elementos disponibles son: Un control de edición donde especificar el tipo de archivos que se quieren visualizar (opción por defecto *.bmp), un ListBox con la estructura de directorios de nuestra unidad, otro ListBox con los archivos que cumplen la máscara (*.bmp) del directorio elegido (existe también una parte donde se especifica en qué directorio nos encontramos -en verde en la ventana-), otro ListBox con los elementos elegidos en el anterior y una pantalla de visualización del bitmap elegido. Propiedades de los ListBox Como viene siendo habitual, hacemos un rápido recorrido por eventos y propiedades no vistos por el momento, y que están íntimamente ligados a los ListBox. En primer lugar se van a comentar las propiedades de los controles TFileListBox y a continuación los de TDirectoryListBox. Propiedades de TFileListBox FileEdit. Es una propiedad (dato) con la que se asocia, de manera automática, un ListBox a un control de edición. El objetivo de esta operación es que, a medida que nos vamos moviendo por el ListBox o seleccionamos alguno de sus datos, el contenido del control de edición se amolda a la operación realizada en el ListBox, quedando ambos controles, pues, sincronizados. Dado que a medida que añadimos controles en una ventana éstos se van registrando, si pinchamos sobre la propiedad comentada (FileEdit) aparecerá una relación de los controles de edición disponibles hasta el momento, nuestra misión es elegir alguno de ellos y echarlo a andar; nada más sencillo. FileType. Tipos de archivos a visualizar según sus atributos. Se trata de una estructura en la que se puede elegir entre los distintos tipos de la tabla 1. 2 Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS Valor ftReadOnl y ftHidden ftSystem ftVolumeID Significado Atributo de Sólo Lectura Atributo de Oculto Atributo de Archivo del Sistema Muestra la etiqueta del volumen seleccionado ftDirectory Atributo de directorio ftArchive Atributo de Archivo ftNormal Archivo sin atributos Tabla 1. Tipos de archivos según los atributos Mask. Máscara por defecto de los archivos que se van a mostrar en el ListBox. Si se deja en blanco no se produce filtrado y, por lo tanto, se manipulan todos los archivos. Para nuestro ejemplo se puede rellenar con *.bmp. ExtendedSelect. En un ListBox se pueden elegir un elemento o varios (para ello hay que modificar la propiedad MultiSelect), y se puede hacer sin más que ir pinchando aquellos que queramos seleccionar o deseleccionar, o bien, siguiendo las técnicas estándar de Windows, en las que pulsando la tecla [Ctrl] se eligen elementos dispersos y presionando [Shift] un rango de ellos. ExtendedSelect permite definir cómo se va a comportar este modo de selección. MultiSelect. Permite que se pueda elegir más de un ítem entre los de una relación. La opción por defecto es false. Propiedades de TDirectoryListBox DirLabel. De la misma manera que un control de tipo TFileListBox es posible asociarlo a un control de edición (a través del dato FileEdit), un control de tipo TDirectoryListBox se puede asociar a un control de tipo TLabel (texto fijo en pantalla). Como antes, el objetivo de esta operación es tener ambos controles sincronizados, y que la operación producida en el primero repercuta en el segundo. En el ejemplo de la figura 2 se ha incluido el texto dentro de un recuadro verde, con la intención de que quede más bonito, objetivo que no se consigue. FileList. A través de esta propiedad se permite otra sincronización más. En este caso el control objeto de sincronización es uno del tipo TFileListBox. Dado que un TFileListBox posee una máscara de entrada (visto más arriba), si sincronizamos un TDirectoryListBox con un TFileListBox, éste se rellenará automáticamente cada vez que naveguemos por aquél. Obsérvese lo fácil que resulta realizar una ventana que vaya dando cuenta de cada uno de nuestros movimientos por los directorios, todo sin trazar una línea de código. Ejemplo de visualización de BitMaps Los pasos a seguir para obtener la ventana del ejemplo han sido: 1.- Pinchamos en los controles TLabel para poner el literal deseado en cada uno y, en el caso del que está enmarcado en la caja verde, para cambiar su fuente (Arial, 8 Bold y color 3 Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS clWhite) y su color de fondo (clGreen). 2.- Elegimos los controles de edición TEdit para habilitar un lugar donde especificar la máscara de los archivos a visualizar. Si bien este control lo podríamos asociar al ListBox de archivos, no lo haremos, pues la funcionalidad que se le dará será, a mi juicio, más interesante que el mero hecho de la sincronización. Dado que al ubicar un control de edición éste se rellena automáticamente con el literal Edit1 modificaremos su propiedad Text para dejarlo en blanco o, siguiendo un criterio que más interesante, lo rellenaremos con *.bmp. 3.- Elegimos un control TFileListBox. Se puede comprobar que se permite su asociación con cualquier control de edición presente en la ventana, y en concreto con el que hemos puesto en el paso 2. 4.- Elegimos un control TListBox que se rellenará cuando elijamos, haciendo doble clic, desde el ListBox del paso 3. 5.- Elegimos un control TDirectoryListBox y lo asociamos mediante su propiedad DirLabel con el control de edición de la caja verde (a todo esto, la caja verde se hace con un control de tipo TShape, que se encuentra en la paleta Additional), y asociamos su propiedad FileList con el ListBox del paso 3. ¡Buf!. 6.- Sin trazar una sola línea de código ya tenemos mucho. Se puede poner en marcha y ver el resultado de las distintas componentes de la ventana. 7.- Para completar un poco la aplicación y para que no sea Delphi el único que trabaje, se van a añadir un par de botones, uno de cerrar ventana y otro para visualizar el BitMap del ListBox del paso 4 (también se podrá visualizar haciendo doble clic sobre el ListBox). Para la ubicación y manipulación de archivos gráficos existe un control que se encarga de ellos, este control es de la clase TImage. Un control de este tipo permite, entre otras cosas, ajustar el BitMap a su tamaño o sólo visualizar, en el área definida, aquella porción que quepa. Alguna de las propiedades más importantes de los controles TImage, o al menos aquellos que vamos a manejar en este ejemplo, son: - AutoSize. Obvia el tamaño prefijado del control TImage, de tal forma que si el archivo gráfico a visualizar es mayor que el área definida, ésta se ignora y el BMP ocupa el total de su tamaño. Esto puede provocar, incluso, que ni la ventana pueda albergarlo, por lo que, automáticamente, aparecerán en dicha ventana las barras de desplazamiento oportunas. - Center. Centra la imagen en el área definida, si no la sitúa en el ángulo superior derecho. - Picture. Mediante esta propiedad se asigna al control la imagen que se quiere visualizar. Dado que lo que se pretende hacer es volcar el contenido de una imagen seleccionada por nosotros, en este ejemplo se dejará en blanco dicha propiedad Picture, y se asignará, en tiempo de ejecución, de la siguiente forma: Imagen.Picture.LoadFromFile( <Archivo> ); - Stretch. Obliga a que el BMP se ajuste al tamaño fijado o, de no caber, se visualice sólo lo que quepa en el área definida. Esta propiedad, que en principio es muy potente, puede hacer que un BMP de gran tamaño quede reducido y distrosionado excesivamente al ubicarlo en un recinto excesivamente pequeño. En nuestro caso lo vamos a dejar a false, cediendo el control del tamaño a otro control auxiliar. 4 Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS Precisamente porque los archivos gráficos pueden ser enormes o pequeños, apaisados u horizontales, alargados, etc., sería bueno reservar esa famosa zona de pantalla donde ubicar el archivo, independientemente del formato de éste. Para este cometido se va a recurrir a un control del tipo TScrollBox, y dentro de él ubicaremos la imagen (TImage). La operativa a seguir es ubicar, en primer lugar, un TScrollBox y después un TImage dentro de él. Esto hace que el control TImage quede constreñido al control padre y que, en ningún caso, se pueda escapar de su recinto. Las propiedades que nos pueden interesar en los controles de tipo TScrollBox son: - - AutoScroll. Pone, de manera automática y cuando sea necesario, barras de desplazamiento vertical y horizontal. Poniéndolo a true conseguimos que, cuando la imagen sea menor que el recuento definido, aparezca en su totalidad, y cuando no, podamos desplazarlos para verla en su totalidad. - BorderStyle: Tipo de marco que se va a utilizar, según los valores bSingle (tal como aparece en la figura 2) y bNone (sin marco). Una vez ubicado el control TImage dentro de la caja TScrollBox hacemos que aquél se ajuste al tamaño total de éste, lo cual se puede consigue con la propiedad Align de TImage. Asignando a Align la constante alClient forzamos que toda la imagen se amolde al tamaño de la caja, pero existe un pero; si visualizamos la imagen en tiempo de ejecución no vamos a conseguir que aparezcan las mencionadas barras de desplazamiento. Estas barras sólo aparecen cuando la propiedad Align está a alNone. Por lo tanto, en primera instancia, podemos elegir alCliente (lo cual provocará que el editor de ventanas se encargue de colocar y redimensionar TImagen a los contornos de la caja) e inmediatamente después cambiarlo a alNone, para conseguir el efecto deseado en tiempo de ejecución. 8.- Accedemos a los eventos del botón de visualización, concretamente a OnClick, para que al pulsar sobre el Botón de Ver se muestre el contenido del archivo gráfico elegido. A este procedimiento se le ha llamado VerBmp y su contenido es el del fuente 1. En él se inicializa la variable nPos, la cual servirá para determinar qué elemento del ListBox "estándar" (el de la clase TListBox) se ha elegido. Una vez que se tiene la posición en nPos la utilizamos para extraer el ítem concreto, todo ello a través del dato Items del ListBox (consistente en una cadena de caracteres organizada en forma de tabla o array) ListBox2.Items[nPos] Esto hará que dispongamos del nombre del archivo y su ruta, que es lo que efectivamente se guarda en la lista deslizante. // --- Fuente 1 --------------------------------------------------------procedure TForm1.VerBmp(Sender: TObject); var nPos: integer; begin nPos := ListBox2.ItemIndex; Imagen.Picture.LoadFromFile( ListBox2.Items[nPos] ); end; 5 Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS Si partimos de que no sólo queremos visualizar el BMP cuando pulsemos el botón de Ver, sino también al hacer doble clic en el ListBox, habrá que manipular el evento OnDblClick de dicho ListBox. Dado que la operación que hay que realizar en este caso es idéntica a la que se hace cuando pulsamos el botón de Ver, basta con asociar el mismo procedimiento VerBMP ya existente, y que es el que está definido en el fuente 1. - Ambos llaman al mismo procedimiento, por lo que en el OnClick del botón Ver y el OnDblClick del ListBox aparecerá la llamada a VerBmp. 9.- Accedemos a los eventos del control de edición, para que cada vez que allí se escriba algo, ese algo se asuma como máscara del TFileListBox. El evento que hay que manipular es OnChange del control de edición, donde ensayaremos lo que se ve en le fuente 2. Por medio del dato Text del control de edición (en nuestro caso EditDir) accedemos al contenido del control (lo que hay escrito en ese preciso instante). Con ello modificamos la máscara actual, lo cual se hace alterando el contenido del dato Mask de TFileListBox (en nuestro caso BMPListBox). // --- Fuente 2 --------------------------------------------------------procedure TForm1.CambiarMascara(Sender: TObject); begin BMPListBox.Mask := EditDir.Text; end; 10.- Por último, queda habilitar un método que permita transferir el dato seleccionado en el ListBox de archivos al ListBox estándar. Comúnmente esta operación se realiza tras hacer doble clic en el elemento deseado, por lo que, nuevamente, habrá que manipular el evento OnDblClick, esta vez del TFileListBox. Si al procedimiento lo llamamos Exportar, bastará con escribir esto en la casilla asociada a OnDblClick y pulsar [Intro]; en la porción de código que aparecerá seguidamente añadimos las líneas que nos interesan. Como habíamos comentado en las líneas de más arriba, los elementos de un ListBox se almacenan en el dato Items, pero cuando manipulamos un ListBox de archivos está disponible otro dato adicional llamado FileName, donde se almacena la unidad, directorio, nombre y extensión del archivo (toda la ruta). Bastará con decir: BMPListBox.FileName; Para disponer del dato que nos interesa. Como vimos en el artículo anterior, para añadir datos a un ListBox se había de recurrir al método Add, pasándole como parámetro la cadena a añadir, por lo tanto: cCadena := BMPListBox.FileName; ListBox2.items.Add( cCadena ); Donde cCadena habrá que inicializarla. En caso de no querer utilizar una variable intermedia, bastará con sustituirlo por su valor efectivo. 6 Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS Ya que estamos en el procedimiento invocado cada vez que se hace doble clic en el ListBox de archivos, podría ser aquí, también, donde se habilitara o no el botón de VerBMP. Como sabemos, este botón se encarga de mostrar el BMP seleccionado en el ListBox, pero si ocurriera que ese ListBox no contuviese aún ningún elemento, intentaría ver "algo" que aún no existe, provocando un error de programa. Para evitar esta situación se puede inhabilitar inicialmente el boton de Ver, y sólo habilitarlo en el método Exportar que estamos perfilando. PBVer.Enabled := true Resumiendo, el procedimiento Exportar quedará como se ve en el fuente 3 // --- Fuente 3 --------------------------------------------------------procedure TForm1.Exportar(Sender: TObject); begin ListBox2.items.Add( BMPListBox.FileName ); PBVer.Enabled := true end; Obsérvese en el fuente 3 cómo no se realiza ningún control sobre el elemento que se va a volcar; esto quiere decir que podremos transferir un mismo archivo tantas veces como doble clic hagamos en el TFileListBox. Este control se puede realizar recorriéndose el ListBox de archivos y comprobando su existencia, del resultado de dicha verificación dependerá que el elemento se transfiera o no. Sabemos que los elementos del ListBox se almacenan en el dato Items, y que éste pertenece a la clase TString, por lo que se podrá hacer uso de cualquiera de sus métodos y datos. Uno de ellos es IndexOf, encargado de buscar una cadena dentro de un TString. Cuando IndexOf encuentra la cadena buscada, devuelve la posición que ocupa (siendo 0 la primera de ellas, 1 la segunda y así sucesivamente). Cuando IndexOf devuelve -1 quiere decir que la cadena buscada no se encuentra. Será en este caso, y no en otro, cuando habremos de transferir la información. Si vemos el contenido del fuente 4, que no es más que una ligera modificación sobre el fuente 3 para tener en cuenta esta eventualidad, sabremos qué quiero decir. // --- Fuente 4 --------------------------------------------------------procedure TForm1.Exportar(Sender: TObject); begin if ListBox2.Items.IndexOf( BMPListBox.FileName ) = -1 then begin ListBox2.items.Add( BMPListBox.FileName ); PBVer.Enabled := true end end; Conclusión 7 Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS Concluir con Delphi es muy sencillo, pero también muy repetitivo. Se trata de un lenguaje y una arquitectura que con poquísimas líneas de código permite realizar grandes cosas. En este ejemplo, sin ir más lejos, se lanzó un programa en el que interactuaban una serie de controles, todo ello gestionado desde el editor; fue después, tratando de encontrarle las cosquillas, cuando tuvimos que escribir la friolera de 9 líneas de código, de las cuales, si omitimos la variable (que solo está puesta por dar algo más de claridad al código), los begin y los end, se nos quedan en 5. Esto, en mi tierra, es hacer las cosas bien. 8 Algoritmo. La revista para el programador de sistemas de bases de datos. http://www.eidos.es - © Grupo EIDOS