Académique Documents
Professionnel Documents
Culture Documents
Apndice A
Introduccin
En vista de que algunos de los que leis este Curso Bsico de Programacin en Visual Basic, (clsico, es decir para versiones anteriores a la versin de .NET), no tenis el entorno de desarrollo de VB por aquello de que trabajis tambin con el Visual Basic incluido en el Office o porque ya no podis conseguir "legalmente" el Visual Basic, ya que no se sigue vendiendo, he decidido crear un apndice en el cual te explico cmo instalar, configurar y, sobre todo, usar el entorno de desarrollo integrado de Visual Basic 5.0 CCE (en adelante IDE, Integrated Development Environment). Si bien lo aqu tratado es sobre el IDE de VB5 CCE, en la mayora de los casos ser igualmente til y vlido para los IDEs de VB5 y VB6, en el caso de VB4 la cosa vara, pero me imagino que a estas alturas sern pocos los que tengan ese entorno de desarrollo, ni decir tiene que el VB3 o anteriores son cosas de la historia.
Si no lo sabes, te lo explico o aclaro ahora, el VB5 CCE no sirve para crear ejecutables, slo sirve para crear controles ActiveX (OCX), pero esto no es impedimento para usarlo, ya que, aunque no se puedan generar los ejecutables (EXE), si que se pueden probar todos los proyectos, bueno, casi todos, ya que no se podrn usar las aplicaciones que tengan acceso a bases de datos de DAO, sin embargo, puedes crear proyectos de acceso a datos con ADO sin el data control (por ejemplo el mostrado en la entrega 41). Nota: Si vas a usar proyectos creados con VB6, por ejemplo los zips de cdigo de algunas de las entregas de este curso, seguramente tendrs que quitar algunos de los valores incluidos en el fichero de proyecto (vbp), por regla general, puedes borrar los que hay a partir de VersionProductName. Si no quitas esos valores del vbp, el VB5CCE te dar error y no cargar el proyecto.
Nota del 19/Oct/2006: Segn parece el VB5CCE ya no est, es decir, est desaparecido totalmente. As que, te recomiendo que te vayas olvidando del VB "clsico" y te pases al .NET. En .NET tienes el Visual Basic 2005 Express Edition que es totalmente gratuito y operativo al 100%, adems de que es lo que debes aprender, porque el Visual Basic 6.0 (y anteriores) ya no lo soporta Microsoft e incluso ni est en venta, as que... te recomiendo que mires el curso que tengo de Visual Basic .NET que es lo que ahora debes ir aprendiendo y tambin te recomiendo que empieces a ver la seccin de WinFX porque ser lo que en un futuro prximo se empezar a usar. Este es el link del curso de Visual Basic .NET (vlido para todas las versiones de Visual Basic para la plataforma .NET) Por supuesto, lo que aprendas con este curso bsico de Visual Basic 6 y anteriores te ser de utilidad para el .NET, ya que las instrucciones siguen siendo las mismas, al menos las que estn relacionadas con el lenguaje en s, los For, If, etc.
Nota del 15/Jun/06: Como es costumbre en Microsoft, algunas cosas desaparecen (como esa pgina) y otras cambian de sitio. Por suerte, esta vez aunque la pgina del VB5CCE ha desaparecido, al menos queda el instalador y la ayuda, que gracias a ngel Rosario de Repblica Dominicana, ahora puedes bajar. Estos son los links a la fecha de esta nota: El programa de instalacin: http://download.microsoft.com/msdownload/sbn/vbcce/vb5c cein.exe Y esta es la ayuda: http://download.microsoft.com/download/5/2/5/5253a7ea1310-40d8-b762-625c2019310e/ccehelp.exe
Es posible que esa direccin no est disponible, bien porque la hayan cambiado de sitio (cosa habitual en el sitio de Microsoft) o bien porque lo hayan quitado definitivamente... cosa que tambin puede ocurrir. Una vez que ests en la pgina de descarga del VB5CCE, tendrs varios links, uno de ellos es el que se baja el programa instalador, pero tambin habr otro link para la ayuda y documentacin. El programa de instalacin del VB5CCE se llama: vb5ccein.exe 7.13 MB El programa de instalacin de la ayuda se llama: ccehelp.exe 2.77 MB Tambin hay varios ficheros con documentacin, pero aqu no lo vamos a tratar, aunque te recomiendo que tambin te los descargues, ya que, aunque estn en ingls, de algo te servirn... que despus te puedes quejar de que no trato todos los temas...
el directorio VB5CCE de Archivos de Programas (Program Files en sistemas Windows en ingls, como el que yo uso). Si quieres tener un acceso directo a la ayuda, busca el fichero vb5.hlp en el directorio de instalacin.
Iniciar el VB5CCE
Una vez que tienes instalado el VB5CCE es hora de usarlo, as que, busca el acceso directo a Visual Basic 5.0 CCE (vb5cce.exe) y dejemos que empiece el espectculo... Nada ms empezar, te mostrar un cuadro de dilogo como el mostrado en la figura 1:
Este cuadro de dilogo te permite indicar el tipo de proyecto que quieres crear, si marcas la opcin Don't show this dialog in the future, este cuado no se mostrar al iniciar el VB5CCE. Los tres tipos de proyectos que permite crear son: ActiveX Control (Control ActiveX), un control para poder usarlo en tus aplicaciones e incluso en una pgina WEB. Standard EXE (EXE estndar), un programa normal y corriente. CtlGroup (Grupo de proyectos), por defecto crea un control ActiveX y un EXE estndar.
Empecemos seleccionando un ejecutable normal (Standard EXE), pero recuerda que aunque podamos seleccionarlo, no te permitir crear el ejecutable (compilar en formato EXE). El aspecto del IDE del VB5CCE ser parecido al mostrado en la figura 2.
Figura 2, El IDE del VB5 CCE despus de haberlo instalado y casi sin configurar
Ahora vamos a configurar un poco el IDE. Lo que aqu te voy a contar es como yo suelo configurarlo, pero si a ti no te gusta, eres libre de dejarlo como quieras. Nota: Si instalas el VB5CCE y ya tienes instalado el VB5 normal (en cualquier versin), la configuracin que ya tuvieras en el VB5 se mostrar en el IDE de VB5CCE. Esto quiere decir que no hay problemas de que instales el VB5CCE si ya tienes otra versin de VB. En la figura 2 tenemos varias ventanas acopladas. Arriba est la barra de herramientas con los mens y otros "comandos" (los botones debajo de los mens). A la izquierda tenemos la barra de herramientas con los controles que podemos aadir a los formularios. A la derecha, en la parte superior tenemos el explorador de proyectos. Debajo de este tenemos la ventana de propiedades. Debajo (aunque no se muestra en esta figura), suele aparecer la ventana con la posicin
del formulario, pero yo suelo cerrarla. Abajo est la ventana Inmediate, (permite ejecutar comandos directos), sta se puede mostrar seleccionndola del men View (por defecto no se muestra), en ese mismo men puedes mostrar algunas de las ocultas, por ejemplo la de Form Layout Windows que es la que te permite posiciona o ver dnde se posicionar el formulario. Por ltimo, en el centro tendremos las ventanas de diseo de formularios/controles, as como las ventanas de cdigo. stas se pueden posicionar como queramos y esa posicin ser recordada cada vez que abramos el proyecto. La informacin sobre el tamao y posicin se guarda en un fichero con la extensin vbw.
Figura 3, Opciones, ficha Editor En la ficha Editor, lo primero que debes seleccionar es Require Variable Declaration, (la he sealado para que no tengas excusas de saber dnde est esa opcin), esto har que en los nuevos proyectos creados se aada a cada mdulo (formulario, mdulo BAS, clase, etc.) la instruccin Option Explicit, ya sabrs que si quieres llevarte bien conmigo, tendrs que usar siempre esa instruccin, de esta forma te evitars muchos problemas, adems de que as siempre tendrs que declarar todas las variables que vayas a usar en tus proyectos.
En la ficha General te recomiendo que asignes 60 tanto a Width como Height de Grid Units, esto te permitir posicionar mejor los controles en el formulario, ya que as estarn los puntos ms juntos (el valor por defecto es 120). Tambin puedes quitar la marca de Compile On Demand ya que esto har que siempre se compile todo el cdigo y te asegures de que lo probado es lo que tienes escrito... realmente no hace falta, pero yo siempre lo tengo as...
De la ficha Environment te recomiendo que selecciones Save Changes del frame (marco) When a programa starts:, de esta forma se guardarn los cambios realizados en el cdigo... nunca se sabe si se quedar colgado el IDE o no!
En la ficha Advanced no vamos a hacer cambios, pero te la muestro para que sepas que en esta ficha puedes indicar si quieres que se te avise cuando se hagan cambios en un fichero que est compartido por varios proyectos o si prefieres usar el estilo SDI en lugar del MDI, es decir que cada ventana sea independiente en lugar de estar todas "dentro" del entorno de desarrollo (IDE). En la figura 7 tienes una captura de cmo quedara el IDE de VB5CCE en modo SDI.
De la lista de la izquierda (Categories) selecciona Edit, en el cuadro de la derecha se mostrarn las opciones relacionadas con la edicin. Selecciona Comment Block (esta opcin te permite comentar el bloque de cdigo seleccionado) y arrstralo con el ratn hasta la barra de herramientas, (vers que el cursor del ratn cambia para que sepas dnde vas a soltar el elemento seleccionado), sultalo en la posicin que quieras y haz lo mismo con las opciones Uncomment Block, (quitar el comentario al bloque de texto seleccionado), Toggle Bookmark (una banderita azul), (pone/quita una marca), Next Bookmark, (ir a la siguiente marca), Previous Bookmark, (ir a la marca anterior), Clear All Bookmarks (quitar todas las marcas). Con las marcas (bookmarks) puedes marcar una parte del cdigo, (se indicar con una banderita en la barra derecha del cdigo, tal como puedes ver en la figura 9), y despus navegar entre las distintas marcas que tengas en el cdigo del proyecto. El problema es que cuando cierras el proyecto esas marcas desaparecen... cosa que en el IDE de VB .NET han mejorado o al menos permanecen.
10
Figura 10, Configurando la barra de herramientas En la figura 10 puedes ver esto de colocar las opciones en la barra de herramientas. Si quieres poner una lnea de separacin entre las distintas opciones (o comandos), simplemente arrastra hacia la derecha el icono (o botn) que est a la derecha de donde quieras dejar esa separacin. Por ejemplo, si quieres dejar una separacin entre el
11
comando de quitar comentarios y el de asignar marcador, tendrs que arrastrar la banderita azul un poco hacia la derecha. Por otro lado, si quieres quitar alguno de los que ya hay (o hayas aadido), simplemente arrstralos desde la barra hasta el cuadro de dilogo. Ahora aade el resto de comandos. De las categora Format selecciona Lock Controls y de Tools, selecciona Add Procedure..., una vez hecho todo esto, el look de la barra de herramientas ser el que te muestro en la figura 11.
Figura 11, El aspecto de la barra de herramientas Fjate que algunas opciones slo estarn disponibles (habilitadas) cuando se puedan usar.
Bueno, dejemos aqu esta primera parte del Apndice A, en otra ocasin veremos para que sirven algunas de las opciones de los mens y de los botones de las barras de herramientas, as como algunas otras configuraciones y, sobre todo, cmo agregar controles a un formulario, cmo aadir nuevos elementos al proyecto, cmo aadir nuevos proyectos (para poder crear multi-proyectos), etc., pero eso ser en otra ocasin. Nos vemos. Guillermo
12
Introduccin
En esta segunda parte del Apndice A del Curso Bsico, vamos a ver cmo aadir controles a nuestros formularios y cmo configurar esos controles, cambiar los valores de las propiedades, etc. Esto es algo que hace aos tendra que haber hecho, lo s, pero yo es que antes era un poco ms inocente que ahora, y pensaba que los lectores de este curso se iban a tomar un poco de ms inters en aprender estas cosas usando la informacin (o documentacin) que acompaaba al Visual Basic... y creo que hace algn tiempo as era... antes la gente no se me quejaba tanto como ahora... seguramente porque los lectores eran menos numerosos... y, posiblemente, (Guille, creo que deberas decir "presuntamente", para que nadie se sienta "acusado"), porque ahora la gente tiene ms oportunidad de "conseguir" gratuitamente el Visual Basic, (es que hay "viejos VisualBasikeros" que son muy caritativos, no te creas otra cosa), y es posible que no se acompae "ningn" tipo de documentacin... Espero que nadie se de por aludido con el comentario anterior, y si es as, echando mano del refranero: el que se rasca, es porque algo le pica... (que malo soy!) La cuestin es que, sea como sea, aqu estn estos apndices, los cuales intentarn "ayudarte" en la medida de lo posible a que aprendas "mejor" este querido, (al menos por mi), lenguaje de programacin. En este caso seguiremos usando el Visual Basic 5.0 Control Creation Edition (VB5CCE para los amigos), ya que, al ser un entorno (IDE) gratuito, todos tendris la oportunidad de poder "jugar" con l. En la primera parte de este Apndice A te expliqu cmo conseguirlo, cmo instalarlo y como configurar algunas de las opciones y barras de herramientas, as que... si an no te lo has ledo, deberas hacerlo antes de seguir. Te recuerdo o aclaro que todo lo que voy a explicar aqu, estar basado en el Visual Basic 5.0 Control Creation Edition, pero tambin ser vlido para el Visual Basic 5.0 e incluso para el Visual Basic 6.0, pero en algunos casos no ser "tan evidente" para la versin 4.0, aunque en estas fechas, esa versin ya es ms difcil de encontrar, as que... si tienes el VB4, intenta amoldarte a las explicaciones. Y si no tienes ninguna de esas versiones, ya que trabajas con el Visual Basic de Office, pues... bjate el VB5CCE y prueba con lo que aqu voy a explicar o bien, intenta amoldarte a como el Office te presente el diseador de formularios... La cuestin es que si lo aqu dicho no se adapta al 100% a lo que tu tienes... que te lo curres! Gracias.
13
Al crear un nuevo proyecto de tipo EXE, tendremos un formulario llamado Form1. Por ahora vamos a dejar ese nombre para que sea ms fcil de comprender todo lo que voy a explicar. Vamos a configurar el formulario para que sepas "adaptarlo" a tu gusto. Para poder configurar el formulario, tendremos que tenerlo a la vista. Un formulario se compone de dos partes: Lo que mostrar y el cdigo. Para poder aadir controles al formulario o para cambiar la apariencia del mismo, tendremos que tenerlo a la vista, es decir, tendremos que mostrar el "diseador de formularios". Cuando queramos usar el diseador de formularios, tendremos que seleccionar el formulario de la ventana de proyectos y pulsar la combinacin de teclas Maysculas+F7 (Shift+F7) o bien pulsar en el icono con forma de formulario que hay en la ventana de proyectos, (ver figura 1).
Figura 1 Para mostrar la ventana del cdigo, podemos pulsar F7 o bien en el icono que est a la izquierda del que muestra el formulario, (ver figura 1) Una vez que tenemos el formulario en modo de diseo, podemos pulsar F4 para mostrar la ventana de propiedades. En esta ventana (ver figura 2) se mostrarn las propiedades que podemos cambiar mientras estamos "diseando" el formulario, es decir, no se mostrarn todas las propiedades, ya que algunas de ellas puede que no sean "configurables" en tiempo de diseo, es decir, que no afecten al aspecto visual. Tambin hay que tener en cuenta de que, (como comprobaremos en un momento), en la ventana de propiedades se mostrarn las propiedades del "objeto" que est seleccionado. En este caso, se mostrar sin problemas las propiedades del formulario por la sencilla razn de que no tenemos ningn otro "objeto" dentro del formulario (ni en la aplicacin).
14
Figura 2, Ventana de propiedades Como podemos comprobar, la ventana de propiedades est dividida en tres partes: -La parte superior nos indica el "objeto" que tenemos seleccionado, en este caso es el formulario. Podemos usar esa lista desplegable para seleccionar el objeto que queremos configurar. Ah se nos indica el nombre del objeto, el cual estar en negrita, y el tipo de objeto se mostrar en letra normal. -La parte del centro nos muestra las propiedades, en esa ventana tenemos dos fichas (o solapas), para mostrar la informacin de forma alfabtica (Alphabetic) o bien por categoras (Categorized). En la columna de la izquierda est el nombre de la propiedad y en la de la derecha est el valor. -En la parte inferior tenemos la descripcin de la propiedad que tenemos actualmente seleccionada. En el caso de la figura 2, la propiedad seleccionada es Caption y abajo nos dice (en ingls) que esa propiedad sirve para mostrar el texto en la barra de ttulo. Si quisiramos que en la barra de ttulos del formulario se mostrara otra cosa distinta a lo que actualmente se muestra, simplemente tendramos que escribir en la casilla que est a la derecha de la propiedad el texto que queremos mostrar. Las propiedades ms importantes (o las ms usadas) del formulario las iremos viendo con algo de detalle en este o en otros "apndices". Lo importante es que sepas cmo poder cambiar los valores y, si te atreves, que vayas probando por tu cuenta y riesgo... y como resulta que ya sabes cmo cambiar (y dnde) cambiar esos valores, ahora no tienes excusa. Para muestra un botn: La propiedad AutoRedraw nos puede servir cuando queremos mostrar las pruebas hechas en las primeras entregas del curso, en las que tenamos que incluir "Show" al principio del evento Form_Load para que se pudiera ver lo que se "escribe" en el formulario. Si queremos darle un valor verdadero (True) a esa propiedad, simplemente seleccionaremos ese valor de la casilla que est a la derecha de la propiedad y asunto arreglado.
15
Tambin decirte que en las versiones ms recientes de Visual Basic hay ms propiedades que en las versiones anteriores, por ejemplo, en la versin 5 tenemos algunas propiedades que no estaban disponibles en la versin 4. Tal es el caso de la propiedad que le indica a Windows dnde debe situar el formulario cuando se muestre. Si buscamos la propiedad StartUpPosition, tenemos unos valores a elegir, por defecto tendr un valor de 3 - Windows Default, lo cual quiere decir que ser el Windows el que posicione el formulario (o la ventana), pero si elegimos el valor 2 CenterScreen, el formulario se posicionar en el centro de la pantalla. Si seleccionamos el valor 0 - Manual, el formulario se posicionar en la posicin que manualmente le indiquemos en las propiedades Left y Top. Por ltimo, si elegimos 1 - CeterOwner se centrar en el formulario que es "el propietario", esto lo veremos cuando se trate el manejo de ms de un formulario. Pero dejemos las propiedades del formulario, (en otra ocasin intentaremos hacer un repaso de algunas de ellas y el efecto que conseguiremos al aplicar distintos valores), y sigamos viendo otras cosas.
16
Figura 3 Si posicionas el ratn encima de los iconos, te mostrar un ToolTip (una cajita emergente) con el nombre del control. La etiqueta es la A que hay en la segunda fila de controles, la caja de textos es el que est a la derecha, tambin en la segunda fila y el botn es el que est a la derecha en la tercera fila. Para aadir un control, podemos hacer doble pulsacin (doble-click) sobre el elemento de la barra de herramientas y se aadir al formulario, posicionndose en el centro del mismo y teniendo el tamao predeterminado. Tambin podemos pulsar una vez, y a continuacin "dibujarlo" en el formulario, dndole el tamao que queramos y posicionndolo tambin en el sitio que queramos. Por ahora vamos a elegir el primer mtodo y despus cambiaremos el tamao y posicin. Haz doble-click en la etiqueta, esto aadir una etiqueta al formulario, el nombre de ese objeto se llamar Label1, para cambiar la posicin de la etiqueta Label1, podemos seleccionarla en el formulario y moverla con el ratn. Vamos a situarla en la parte superior izquierda, para que deje el centro del formulario libre, ya que el resto de los controles se posicionarn en el mismo sitio y si dejamos la etiqueta ah y aadimos nuevos controles, los nuevos controles ocultarn la etiqueta. A continuacin aadiremos una caja de textos, por tanto haz doble-click en el icono correspondiente y una vez que est incluido en el formulario, lo posicionaremos en la parte superior, a la izquierda de la etiqueta. El nombre que tendr esa caja de textos ser Text1. Por ltimo aadiremos un botn, as que pulsa en el icono correspondiente y djalo donde lo ha posicionado el diseador de formularios, el nombre que tendr ser Command1. El aspecto del formulario con estos controles ser el mostrado en la figura 4.
17
Figura 4 Fjate en los nombres de los controles, que son los mismos que los "textos" mostrados. El diseador de formularios de Visual Basic siempre va nombrando los controles usando el mismo "algoritmo", (dicho as parece hasta algo importante, je, je), osea: usando un nombre "reducido" del tipo de control seguido de un nmero, el cual se va incrementando cada vez que aadimos un nuevo control del mismo tipo. Si aadimos una nueva etiqueta (control Label), qu nombre tendra? (la respuesta dentro de un ratillo).
18
2. La segunda forma de cambiar el tamao es usando la ventana de propiedades y usar las propiedades Height y Width, las cuales cambiarn respectivamente la altura y anchura del control.
Figura 5
Figura 6
Vamos a cambiar el tamao de los controles para que tengan los que aqu te indico: La etiqueta (Label1), tendr un tamao de 1245 x 255 (ancho x alto) La caja de textos (Text1), tendr un tamao de 2895 x 315 El botn (Command1), tendr el tamao 1245 x 375 Para cambiar la posicin de los controles, como puedes imaginarte, tambin podemos hacerlo de dos formas, arrastrando el control hasta la nueva posicin o bien asignando valores a las propiedades Left y Top, las cuales representarn los valores izquierdo y superior respectivamente. Posiciona la etiqueta en la posicin 180, 150 (izquierda, arriba), la caja de textos en la posicin 1560, 120 y el botn djalo en el mismo sitio que estaba: 1740, 1350. Nota: Como te coment en la primera parte del Apndice A, podemos cambiar el espaciado de puntos del grid (o rejilla) del diseador de formularios, en aquella ocasin te dije que usaras 60 x 60, pero yo suelo usar 30 x 30, por tanto para que los controles se "puedan mover" en tiempo de diseo a las posiciones que te indico, es posible que tengas que cambiar esos valores, (ver la figura 4 de la primera parte del Apndice A). Todos estos cambios son en tiempo de diseo, es decir, mientras estamos "diseando" el formulario, ya que esos ajustes de tamao tambin podemos hacerlo en tiempo de ejecucin, es decir, cuando pulsemos F5 para ejecutar la aplicacin. El sitio habitual para hacer esos cambios en tiempo de ejecucin es en el evento Load del formulario o bien en el evento Resize del mismo. Aunque, la verdad sea dicha, no es comn cambiar el tamao de los controles, salvo en contadas ocasiones, por ejemplo, cuando queremos adaptarlos al nuevo tamao del formulario, etc., pero ese tema no lo vamos a ver, al menos ahora, ya que, aunque sea de paso, se toc en la entrega veintinueve. Las propiedades Left, Top, Height y Width tambin las podemos usar en tiempo de ejecucin para asignar un nuevo valor en cualquiera de ellas, Tambin quiero "recordarte" que existe un mtodo en casi todos los controles (incluso en los formularios) que sirve para cambiar la posicin y/o el tamao de los controles de una vez (es decir con una sola instruccin); ese mtodo es: Move y se usa de la siguiente forma: <control>.Move Izquierda, Arriba, Ancho, Alto El nico valor requerido es el primero (posicin izquierda), pero si se especifica cualquiera de los otros tres, los anteriores tambin se deben especificar; aunque los siguientes no tenemos la obligacin de indicarlos, adems de que seguirn conservando los valores que ya tuvieran. Por ejemplo, si slo queremos cambiar la posicin del control, podemos indicar los dos primeros parmetros de ese procedimiento. Pero si adems de la posicin queremos cambiar el alto, estamos obligados a indicar tambin el alto... ya que no
19
podemos dejar ese "hueco". Como consejo, decirte que es ms "rpido" usar el mtodo Move que asignar individualmente las propiedades. Otra cosa, la posicin siempre se considera relativa a la posicin superior izquierda del formulario, la cual estar indicada por 0,0. El formulario a su vez estar posicionado relativamente a la pantalla (Screen), considerndose igualmente que la posicin superior izquierda es la posicin 0,0.
20
Figura 7, El men Format De ese mismo men Format, podemos seleccionar Align (alinear) para que estn alineados a la izquierda, por tanto, selecciona la opcin Lefts de Format>Align, (se supone que los dos controles siguen estando seleccionados). Otra cosa que podemos hacer es pegar el segundo control al primero, de forma que estn "juntitos", pero no revueltos, para ello, selecciona Format>Vertical Spacing>Remove y los dos controles estarn juntos, uno debajo de otro. Por ahora vamos a dejar los controles "pegados". Nota: Decirte que cuando seleccionamos controles para usar las opciones del men Format, el ltimo que se haya seleccionado ser el que "tenga el mando" y el resto se adaptar a ese ltimo control seleccionado. Una vez que hemos cambiado el segundo textbox de sitio, en el centro del formulario tendremos el botn y "debajo" estar la etiqueta, como resulta que la etiqueta es ms alta que el botn, debajo del botn sobresaldr la etiqueta, as que, podemos seleccionarla, pulsar la tecla Control y pulsar sobre la otra etiqueta y haremos lo mismo que con la caja de textos, Format>Make Sime Size>Both y a continuacin Format>Align>Lefts y por ltimo: Format>Vertical Spacing>Remove. Truco: Tambin puedes seleccionar esa etiqueta usando la ventana de propiedades, de la lista desplegable, selecciona Label2 y ahora pulsa la tecla Control, etc., es decir, si no puedes seleccionarla en el formulario, porque est oculta, por ejemplo, siempre nos queda el recurso de seleccionarla desde la ventana de propiedades. Ahora tendremos las dos etiquetas y las dos cajas de textos del mismo tamao y con la misma posicin izquierda, aunque demasiado pegados con sus compaeros, as que ahora, manualmente, vamos a cambiar la posicin. Baja un poquito la segunda caja de textos y posiciona la etiqueta 30 puntos ms bajo que la caja de textos, (que es una distancia "conveniente"), es decir si el valor de la propiedad Top de la segunda caja de textos (Text2) tiene un valor de 510, la segunda etiqueta (Label2) debera tener 540.
21
Ahora vamos a aadir una nueva etiqueta y una caja de textos, pero usando otro "truco". En este caso, tambin queremos que tanto la etiqueta como la caja de textos tengan el mismo tamao que las dos ltimas que acabamos de aadir, por tanto, selecciona la etiqueta y la caja de textos que est a su derecha; ya sabes cmo hacerlo: selecciona la etiqueta y manteniendo pulsada la tecla Control, pulsa en la caja de texto, aunque tambin puedes hacerlo de otra forma: primero pulsa en cualquier parte del formulario para quitar cualquier seleccin que hubiera, a continuacin, con el ratn, selecciona la etiqueta y la caja de textos, (de la misma forma que seleccionaras varios objetos en cualquier aplicacin, incluso en el explorador de Windows, ver la figura 8)
Figura 8 Una vez que tenemos los dos controles seleccionados, copiaremos los dos controles, bien mediante el men Edit>Copy, bien mediante el men contextual (pulsando con el botn secundario del ratn) y seleccionando Copy (Copiar). Ahora vamos a pegar lo que hemos copiado, ya sabrs cmo... (ver la figura 9)
Figura 9 Al pulsar en pegar, se nos mostrar un cuadro de dilogo preguntndonos si queremos crear un array de controles, (ver la figura 10), pulsa en el botn NO, ya que no queremos crear ningn array de controles. Primero te preguntar por si quieres crear el array de Label2 y despus de Text2 (aunque puede que el orden sea al revs, pero eso no es
22
importante). El tema de los arrays de controles, lo vimos en la segunda parte de la entrega 11.
Figura 10 Esto har que se "peguen" dos nuevos controles, los cuales tendrn el mismo tamao que los copiados, incluso estarn posicionados "relativamente" a la misma distancia, pero se pegarn en la parte superior del formulario, como estarn los dos seleccionados, podemos arrastrarlos hasta que estn en la posicin que queramos, en este caso, los posicionaremos debajo de los anteriores. Si necesitas ajustar las posiciones, ya sabes que puedes recurrir al men Format y posicionarlos correctamente. Un detalle a tener en cuenta, es que los controles tendrn como nombre Label3 y Text3, pero mostrar el mismo "texto" que los dos que se usaron para la copia. Para cambiar el texto mostrado, tendrs que asignar un valor a la propiedad Caption de la etiqueta y para cambiar el texto mostrado en la caja de textos, tendrs que cambiar la propiedad Text.
Nota: Como puede ser que te haya ocurrido, sobre todo si no tienes mucha prctica con el ratn o bien porque tu ratn sea muy sensible, es posible que al "intentar" hacer un click en un control, realmente hayas hecho un doble-click, si es eso lo que te ha ocurrido, vers que se muestra la ventana de cdigo con el evento "predeterminado" de ese control, no te preocupes, simplemente cierra la ventana de cdigo y vuelve a intentarlo.
Ahora ya sabes dnde est el men Format, as que... juega un poquito con las distintas opciones y practica, por ejemplo centrando los controles en el formulario, etc.
Ahora que ests entretenido "jugueteando" con las opciones del men Format, vamos a dejar aqu esta segunda parte del Apndice A, entre otras cosas, porque te he explicado lo que quera explicarte: cmo agregar controles a un formulario, poder cambiarle el tamao y posicin, as como explicarte cmo cambiar otras propiedades... adems de que ya se ha hecho un poquito larga... (je, je... es una excusa como otra cualquiera). En otra ocasin veremos ms cosillas que podemos hacer con el IDE de Visual Basic, as como otras cosas relacionadas con los controles y formularios. Espero que estos apndices vayan rellenando las lagunas que se hayan quedado en las entregas "normales". No te voy a adelantar ms cosas que contendrn estos apndices, pero si te dir que no
23
se quedarn en cosas relacionadas con el IDE (entorno de desarrollo), pero... como siempre, tendrs que esperar para poder saber de qu tratarn... mientras tanto, sigue practicando y leyendo cosas sobre el Visual Basic. Nos vemos. Guillermo
Compilar el proyecto
24
Introduccin
Y en este apndice, (por no llamarlo entrega, que tambin poda llamarlo as, pero... he preferido que sea un apndice, por aquello de que en realidad es algo "aparte" del curso en s), quiero explicarte los tipos de proyectos que podemos crear con Visual Basic 6.0 (cuando digo 6.0 tambin es vlido para el 5.0, incluso para el 4.0, aunque en este ltimo ya no se utiliza, adems de que no permite crear todos los tipos de proyectos que veremos en este apndice). Tambin te explicar cmo compilar esos diferentes tipos de proyectos y cmo configurarlos, ya que an hay gente que se "queja" de que no saben a ciencia cierta para que sirven las diferentes opciones que tenemos en la configuracin de los proyectos de Visual Basic. Antes de seguir, te recomiendo que le eches un vistazo a los dos apndices anteriores, en el primero (el apndice A.1) te explico cmo crear nuevos proyectos usando Visual Basic 5.0 Crontrol Creation Edition (la versin gratuita de VB5); en el segundo (apndice A.2) te explico cmo aadir controles a los formularios y esas cosillas. Te recomiendo que te los leas, porque lo mismo piensas que en este te explicar lo mismo... pero no, lo que aqu veremos es cmo configurar y compilar los distintos tipos de proyectos que podemos crear con Visual Basic 6.0.
Nota: Todas las capturas y explicaciones de este apndice sern de la versin empresarial de Visual Basic 6.0 en espaol, pero lo explicado ser igualmente vlido para el resto de versiones y ediciones, ya que solo
25
trataremos de los 4 tipos principales de proyectos que podemos crear con Visual Basic.
Nota: Si la pantalla mostrada en la figura 1 no se muestra al iniciar el Visual Basic, tendrs que seleccionar Nuevo proyecto del mens de Archivo.
26
Empezaremos creando uno del tipo EXE estndar, que ser el ms comn, es decir, el que nos permite crear un .EXE de toda la vida. Siempre que se crea el nuevo proyecto de tipo EXE tendremos un formulario en la ventana del Explorador de proyectos, que no te voy a explicar para que sirve, ya que est bien explicado en los dos apndices anteriores. Ahora lo que veremos es cmo "configurar" las propiedades de este proyecto.
Figura 2. Configurar las propiedades del proyecto Al seleccionar esa opcin tendremos a la vista un cuadro de dilogo como el de la figura 3.
27
Junto a esa lista tenemos una lista desplegable con el Objeto inicial, es decir, si tenemos varios formularios, podemos indicar cual ser el que se usar como formulario inicial de la aplicacin. Tambin podemos seleccionar Sub Main, si es que queremos que la aplicacin empiece por el mtodo Main. Ese mtodo o procedimiento debe estar declarado en un mdulo de tipo BAS, por ahora no hablaremos de este mtodo pero lo har en otra ocasin, por ejemplo para crear una aplicacin de VB6 que no tenga formularios.
28
Debajo de estas dos opciones, tenemos el Nombre del proyecto, este nombre ser el mismo que el mostrado en el Explorador de proyectos, por tanto podemos cambiarlo desde estas opciones o bien desde el mencionado explorador. Como puedes comprobar, el nombre que muestra es el predeterminado: Proyecto1.
Si queremos que nuestra aplicacin de VB6 tenga su propia ayuda, podemos indicar el nombre del fichero en la casilla Nombre del archivo de Ayuda, y si no sabemos el "path" en el que se encuentra, podemos usar el botn con los res puntos suspensivos que hay a continuacin. Tambin podemos indicar el "ID" del contenido de la ayuda, con idea de que sea ese "id" el que se use cuando el usuario pulse en F1. Los tipos de ficheros que soporta el Visual Basic 6.0 son ayudas con extensin .HLP y las que tienen extensin .CHM.
Nota: La forma de crear ficheros de ayuda para VB y cmo usarlas estn explicadas en mi sitio, (y desde el ndice principal puedes acceder a ellas), aunque es posible que algn da de estos las resuma y las incluya en el curso bsico, ya veremos...
La casilla con la Descripcin del proyecto en realidad solo se utiliza (o es til) para los proyectos de tipo ActiveX, pero no es mala costumbre rellenarla siempre.
Los checkbox que hay abajo a la izquierda sirven para diferentes cosas, aunque dependiendo del tipo de proyecto estarn habilitadas o no, en este caso, est habilitada y seleccionada la de Actualizar controles ActiveX, esto vale por si tenemos aadido algn control ActiveX (.ocx) en nuestro proyecto y si se detecta que hay una nueva versin, se actualizar el proyecto para usar esa versin. Por regla general lo podemos dejar marcado.
Las opciones del grupo Modelo de subprocesos solo estar disponible para proyectos ActiveX.
Propiedades Generar
La segunda ficha de las propiedades del proyecto (ver figura 4) es la de Generar el proyecto.
29
Figura 4. Propiedades Generar del proyecto En esta ficha tenemos los datos del Nmero de versin, que sern las que se muestren cuando pulsemos en las propiedades del ejecutable que creemos desde VB6. Si marcamos la casilla que hay debajo: Incremento automtico, cada vez que creemos un nuevo ejecutable, se incrementar automticamente el valor de Revisin. Yo normalmente lo tengo desmarcado, ya que aado mis nmeros de revisiones, etc., de forma manual, pero si lo dejas marcado, nunca se te olvidar cambiar ese valor, que siempre es conveniente para saber que esta versin es diferente a las anteriores.
En el grupo Aplicacin tenemos el Ttulo del proyecto y el formulario que se usar para el icono de la aplicacin. En la lista desplegable que hay junto a Icono se mostrarn los formularios que tengamos y el icono que tenga el seleccionado ser el que se use para nuestra aplicacin. Por defecto estar el formulario usado como inicio de la aplicacin.
En el grupo de Informacin de la versin tenemos varias opciones en Tipo, segn las vayamos seleccionando, podemos escribir el valor en la casilla que hay bajo Valor. Los valores de tipo son los siguientes: Tipo Comentarios Derechos de autor Marcas comerciales legales Descripcin Descripcin larga de la aplicacin El copyright Las marcas comerciales de los productos usados con esta aplicacin
30
Como te comentaba, estos son los valores que se muestran en Windows cuando mostramos las propiedades del ejecutable. La versin, descripcin corta y el copyright son los datos que se muestran en la parte superior de la solapa Versin. El resto de datos se muestran al seleccionar las opciones que hay en la parte inferior de la solapa Versin, tal como puedes ver en la figura 5.
En Argumentos de la lnea de comandos podemos indicar lo que queramos, de forma que ese valor sea el que se pueda interceptar mediante la funcin Command$ para simular lo que pasara si el usuario de nuestra aplicacin la indiciara indicando algo a continuacin del nombre del ejecutable, ya sea desde una ventana de "DOS" o bien porque le ha agregado opciones despus del nombre del ejecutable en un acceso directo. Por ejemplo, si la aplicacin se inicia as: proyecto1.exe pepito.doc, el valor de la lnea de comandos ser "pepito.doc" y ese valor ser el que se indique en Command$.
31
Si se escriben ms cosas, por ejemplo: proyecto1.exe Erase una vez que se era, todo lo que haya despus del nombre del ejecutable ser el contenido de la lnea de comandos.
La casilla Argumentos de compilacin condicional sirve para crear "constantes de compilacin", las constantes de compilacin son las que podemos usar con: #If CONSTANTE = VALOR Then. Esas constantes se tendrn en cuenta mientras compilamos el ejecutable, es decir, no son valores que podamos cambiar una vez que el .exe est creado. La forma de indicarlo en esa casilla sera de esta forma: NOMBRE = VALOR : NOMBRE2 = VALOR, es decir, las usamos como si hiciramos una asignacin y si queremos definir ms de una, las separamos con dos puntos. Esto es equivalente a definir esas constantes con la instruccin #Const NOMBRE = VALOR en todos los ficheros de nuestro proyecto.
Por ltimo, la casilla Quitar informacin de controles ActiveX no usados, nos viene muy bien si hemos aadido "componentes" (controles .ocx) a la barra de herramientas de controles, pero que no hemos aadido en ninguno de los formularios, por tanto, cuando compilemos no se harn referencia a esos controles no usados.
Propiedades Compilar
En esa ficha de las configuraciones del proyecto tenemos las opciones para eso, para compilar nuestra aplicacin. Las opciones disponibles son las mostradas en la figura 6, y te las detallo un poco a continuacin.
32
La primera opcin es Compilar a P-Code, seleccionndola haremos que el compilador de VB cree un ejecutable en el que se utiliza "pseudo cdigo". Esta compilacin lo que hace es crear un ejecutable ms pequeo, ya que prcticamente todo lo que contiene son llamadas a las funciones y cdigo ejecutable del "runtime" de Visual Basic. En la mayora de los casos, esta ser la opcin ms conveniente, sobre todo si lo que nos interesa es que nuestro ejecutable no ocupe mucho.
El grupo de Compilar a cdigo nativo nos da varias opciones que veremos ahora, la diferencia entre la compilacin a cdigo nativo y el P-Code es que en la compilacin a cdigo nativo se utiliza un compilador "de verdad", es decir, en lugar de usar funciones que llaman a las que estn en el runtime, se sustituyen por cdigo mquina, si bien el runtime de VB sigue utilizndose, ya que no todo el cdigo usado se incluye en el ejecutable. Al compilar en modo nativo, el fichero ejecutable suele ser mucho ms grande que el de P-Code, tambin suele tardar ms en compilar, dependiendo de la cantidad de cdigo que tengamos en el proyecto. Y a pesar de lo que puedas pensar no siempre el resultado de compilar a cdigo nativo suele resultar en un ejecutable ms rpido, independientemente de como hayas seleccionados las opciones extras de compilar a cdigo nativo. Cuando seleccionamos la opcin de compilar a cdigo nativo, tenemos otras sub opciones que nos sirven para "afinar" dicha compilacin, las tres primeras nos permiten optimizar para un cdigo ms rpido, lo cual suele ir acompaado de un ejecutable an ms "pesado". La segunda opcin es optimizar para cdigo reducido, como te puedes imagina, en este caso se "opta" por reducir el tamao del ejecutable aunque ello vaya en detrimento de la velocidad, por ltimo, sin optimizacin, pues eso, no se optimiza ni para cdigo rpido ni para reducido... ni fu, ni fa!
33
Optimizar para Pentium Pro(tm), pues eso, optimiza el cdigo para los Pentium Pro, en otros Pentium tambin funcionar, pero puede que el rendimiento sea menor... o eso dice la documentacin, en fin... Lo de Generar informacin para depuracin simblica, es por si queremos generar un fichero .pdb para poder usar desde Visual Studio (o Visual C++) para depurar el cdigo una vez compilado. Al pulsar en el botn Optimizaciones avanzadas, nos mostrar el cuadro de dilogo que podemos ver en la figura 7, yo prcticamente nunca he usado estas opciones, y como puedes comprobar en esa figura, tampoco se recomienda su uso si realmente no sabemos lo que hacemos, as que... mi recomendacin es que no "trastees" aqu, y si lo vas a hacer, lete lo que dice la ayuda... ya que yo no te lo voy a explicar... je, je, es que si yo no lo uso, pues... el que quiera usarlo que se busque la vida ;-))) No, en serio, no te recomiendo que uses estas opciones salvo que antes hayas comprobado "el efecto" que puede producir.
La opcin final de la pantalla de configuracin de compilacin (figura 6), la que indica Direccin base de la DLL, y que solo estar activada en los proyectos de tipo DLL ActiveX y control ActiveX (un control ActiveX, en realidad es una DLL con la extensin .ocx). Esta opcin incluye un valor que por defecto es: &H11000000, ese valor ser la direccin de memoria en la que se cargar esa DLL, la recomendacin es que siempre usemos una diferente para nuestras DLLs, si al cargar la DLL en memoria, esa direccin est ocupada se utilizar otra, si no est ocupada, se usar la que indiquemos. Yo por regla general le doy el valor que empieza por &H57nnnnnn, por tanto, te rogara que usaras otra distinta, as si en tu equipo hay una DLL que yo he hecho y una tuya, cada una usar su "segmento" de memoria.
34
Propiedades Componente
La ficha Componente solo estar disponible cuando creemos proyectos de tipo ActiveX, en la figura 8 vemos esa ficha si tenemos seleccionado un proyecto de tipo EXE ActiveX.
Figura 8. Opciones de Componente de nuestro proyecto actual En el grupo Modo de inicio podemos seleccionar Independiente o Componente ActiveX. Estas opciones solo est disponibles cuando el proyecto es de tipo EXE ActiveX. Para comprender bien debes saber cmo funcionan los EXE ActiveX, y no creo que ese este el lugar idneo para explicarte como "furula" este tipo de proyecto, pero te explicar un poco, para que no andes demasiado perdido (o creas que quiero escurrir el bulto, je, je).
Qu es un EXE ActiveX?
Un EXE ActiveX es un componente ActiveX (o componente COM), en realidad todo lo que creemos con VB6 son componentes ActiveX/COM, (ver la entrega 47 para ms detalles), pero es un componente diferente, ya que nos permite usarlo de dos formas diferentes: como un ejecutable normal que es capaz de proporcionar "objetos automatizados" y tambin se puede usar como un control ActiveX, en el sentido de que simplemente proporciona esos objetos de automatizacin pero no se "ve" que se est ejecutando (o en funcionamiento). Los ejemplos "clsicos" de componentes EXE ActiveX son las aplicaciones como Word o Excel, que tanto pueden hacer de componente ActiveX como de una aplicacin normal y corriente. Pues bien, si seleccionamos la opcin Independiente, le estamos indicando al
35
compilador de VB que nuestra idea ser usar este ejecutable como un EXE normal que es capaz de proporcionar objetos de automatizacin, es decir, se ejecutar de forma visible pero si la "referenciamos" desde otro proyecto podremos usar las clases que expone pblicamente. Por otra parte, si seleccionamos Componente ActiveX, la aplicacin se iniciar de forma "oculta", y solo proporcionar esas clases pblicas que hayamos indicado en dicho ejecutable.
En el cuadro Compatibilidad de la versin tenemos tres opciones y se utilizan para los componentes ActiveX (sean del tipo que sean), estas opciones indicarn cmo tratar el compilador el cdigo generado. Normalmente cuando creamos un componente ActiveX, ste debe estar registrado en el registro del sistema operativo, si no lo est, no podremos usarlo. Pues bien, el propio Visual Basic se encarga de hacer ese registro, (pero solo en nuestro equipo), y dependiendo de como tengamos seleccionada estas opciones actuar de una forma u otra. Si seleccionamos Sin compatibilidad, querr decir que cada vez que compilemos el componente crear una nueva entrada en el registro, bueno, que crear un nuevo valor que identifica a este componente. Esta opcin solo debemos seleccionarla si la queremos empezar "en limpio" un componente, y no ser opcin que habitualmente seleccionaremos. Si seleccionamos Compatibilidad de proyecto le estamos indicando que siempre que se compile actualice el resto de proyectos en el que estemos usando dicho componente, de forma que podamos seguir usndolos sin problemas y sin tener que andar cambiando las referencias de los proyectos que usan el componente. Todo esto es para cuando creamos un componente ActiveX y aadimos proyectos normales que lo utilicen, para ms detalles sobre todo esto, lete la entrega 47. Por ltimo tenemos la opcin de Compatibilidad binaria, en la que adems hay una caja de textos, en la que estar el componente que hemos creado. La compatibilidad binaria significa que siempre se mantendr la misma clave del registro de Windows, esto quiere decir que si modificamos el cdigo "interno" de las clases que tenemos en ese componente, esos cambios se intentarn manejar como compatibles con las versiones anteriores, de forma que si ya tenemos distribuido el componente, esos cambios no afecten a las aplicaciones que lo utilicen. Esto en realidad es ms "peliagudo" de lo que en este pequeo comentario pueda explicarte, y como seguramente ser motivo de una entrega propia, no me voy a enrollar demasiado, solo decirte que el propio Visual Basic nos avisar si "rompemos" esa compatibilidad binaria, dndonos opciones para solucionar el "problema". En la caja de textos indicaremos el componente previamente compilado con el que queremos mantener esa compatibilidad binaria. Y si quieres saber ms, lete esto.
Y la ltima opcin de este dilogo es la ficha Depurar, de esta ficha, pues tampoco te voy a comentar nada, ni siquiera te voy a poner una captura, porque... si no voy a comentarte
36
Compilar el proyecto
Para terminar, veamos cmo compilar el proyecto o proyectos que tengamos. Como ya sabes, en el entorno de Visual Basic podemos tener ms de un proyecto abierto, normalmente tendremos varios cuando estemos creando una DLL ActiveX o un Control ActiveX y queramos probarlo en un proyecto normal, tal como te coment en la entrega 47. En cualquier caso, para compilar ese o esos proyectos lo haremos mediante la opcin Generar <nombre proyecto>... si es un proyecto independiente, bien el nico que tengamos abierto o el que tengamos seleccionado si es que tenemos ms de uno. En caso de que tengamos varios proyectos, podemos seleccionar la opcin Generar grupo de proyectos... que como vemos en la figura 9, estar deshabilitado si no tenemos varios proyectos cargados.
Figura 9. Las opciones para generar el proyecto est en el men de Archivo Dependiendo del tipo de proyecto que tengamos cargado en el IDE de Visual Basic, la extensin ser .exe para los proyectos EXE estndar y EXE ActiveX, .dll para el tipo de proyecto DLL ActiveX o bien .ocx para el tipo de proyecto Control ActiveX. Al pulsar en esa opcin nos mostrar un cuando de dilogo como el de la figura 10, en el que indicaremos el nombre que queremos usar y dnde guardarlo. Bueno, ese cuadro de
37
dilogo solo se mostrar si ya hemos compilado (o generado) nuestra aplicacin como mnimo una vez, ya que la primera vez la generar directamente usando como nombre el mismo nombre del proyecto y como path el del propio proyecto.
Si pulsamos en el botn Opciones... nos mostrar el cuadro de dilogo de propiedades del proyecto pero solo con las solapas (o fichas) Generar y Compilar.
Pues esto es todo por hoy... que para mi ha sido ayer y hoy, ya que como soy tan "pamplinas", pues tardo una eternidad en escribir cuatro cosillas para que tu lo tengas ms fcil... ;-)))) Pero no me quejo... o casi... je, je. Hasta la prxima!
A estas alturas no es fcil hacer un curso de introduccin a la programacin con Visual Basic. Si fuese a raz de la primera o segunda versin, incluso con la tercera, sera ms fcil. Pero ahora est ms crudo, por aquello de que soporta 16 y 32 bits y te enfocas en una u otra direccin o... Pero como el ttulo indica, esto ser una cosa para introducir al que quiere empezar a programar, incluso para el que viene de otro lenguaje, aunque seguramente, sobre todo al principio, se encontrar con conceptos demasiados bsicos. Como este curso no se publica sobre algo que ya est terminado, ir hacindolo sobre la marcha, espero tus indicaciones sobre si voy demasiado rpido o si realmente me estoy pasando en cuanto a 'simpleza'. De cualquier forma, me gustara or tu comentario y tus impresiones, eso me animar a seguir y sobre todo a terminar. Bueno, manos a la obra. A ver como me sale el tema, (al final no he encontrado los apuntes que ya tena hechos, de cuando daba clases a nios y a no tan nios, de esto hace ya 10 aillos de nada) Lo advierto, a los que saben algo y a los que lo saben todo (o casi todo), lo que viene a continuacin es super bsico. Y pensando tambin en los que por una razn u otra no tienen unos conocimientos que a otros les parecer absurdo, s que an hay gente que no tienen ni 'repajolera' idea de lo que es una expresin, una variable y ni que decir tiene sobre las matrices, los nmeros binarios o la notacin hexadecimal... Que es una variable? En cualquier programa siempre necesitaremos hacer clculos, usar informacin, procesarla y mostrarla. En la mayora de los casos, necesitaremos un lugar temporal en el cual guardar parte de esa informacin, incluso toda. Todos los lenguajes, y el Basic no iba a ser menos, nos permiten guardar datos en la memoria, para que cuando los necesitemos, podamos tomarlos, modificarlos y volverlos a guardar para usarlos ms tarde. Si no seguimos unas normas o usamos unas reglas, de poco nos iba a servir el guardar esa informacin, si despus no podemos almacenarla en ningn sitio, pero ese ser tema de otro captulo... ahora centrmosno en la memoria. La memoria? Espero que este concepto lo tengas claro, pero si no es as, ah va un poco de 'rollo': La memoria es el lugar donde el ordenador almacena de forma temporal los programas y parte de la informacin que necesita o utiliza. As pues, los lenguajes de programacin usan tambin esa memoria para guardar informacin propia del lenguaje y del programa que queramos realizar. El programa una vez que se est ejecutando puede necesitar tambin guardar informacin, aunque esta informacin slo estar disponible mientras se est ejecutando. Esas posiciones o lugares de la memoria donde los programas pueden almacenar informacin son las variables. El que se llamen de esta forma es porque podemos hacer que el contenido de ese lugar de almacenamiento vare, es como si tuvisemos una serie de cajas y en ellas pudisemos guardar cosas, con la salvedad de que en cada caja slo puede haber una cosa a la vez; aunque tambin veremos cmo hacer que el contenido pueda variar y que vare dependiendo de lo que contena antes... Veamos un ejemplo: Imagnate que quieres guardar tu nombre en una variable, para ello tendramos que 'guardar' el nombre en la memoria, es decir asignar a una variable un valor. En este caso nuestro nombre. Para ello el lenguaje de programacin pone a nuestra disposicin unos lugares donde almacenar nuestro nombre, pero nos impone una serie de reglas de conducta. Si queremos guardar en una de nuestras cajas una hoja, por lo menos tendremos una caja con el tamao adecuado y que tenga una 'forma', para que el papel no vuele al menor soplo de viento.
39
Esta es la regla bsica para poder usar esos lugares de almacenamientos (variables): Llamar a esas posiciones de memoria con un nombre. Simple, verdad? En principio, es todo el requisito necesario: que le demos un nombre al sitio en el que queremos guardar la informacin. Por tanto, si queremos guardar un nombre, (el nuestro, por ejemplo), en la memoria, podramos llamarlo nombre. Y ahora tendremos que seguir otra norma de conducta (o funcionamiento), que en este caso el lenguaje Basic nos dicta: Para guardar en una variable (posicin de memoria) algo, debes hacerlo de la siguiente manera: ---Pon el nombre con el que quieres llamar a esa parte de la memoria, ---a continuacin pones el signo igual (=) y ---despus lo que quieras guardar. Por tanto para guardar Guillermo en la variables nombre, tendramos que hacer (o casi): Nombre = Guillermo Pero esto poda llevar a confusin, ya que el Basic no nos dice nada sobre cmo debemos llamar (o si lo prefieres, cmo hay que escribir) el nombre de una variable, por tanto Guillermo tambin podra ser una variable, (es que el Basic, a pesar de que llevo tantos aos bregando con l, no sabe que ese es mi nombre!!!). As pues, cuando queramos guardar en una variable una palabra, una frase, nombre o cualquier tipo de informacin alfabtica, tendremos que indicarlo poniendo dicha informacin dentro de comillas dobles, el ejemplo quedara as: Nombre = "Guillermo" Ahora no hay confusin posible, hemos seguido lo que el Basic nos ha dicho: variable, signo igual, valor a almacenar. Si queremos guardar un nmero en una variable, la cosa es ms simple: Numero = 7 Te ests enterando? Pero, que ocurre si quisiramos repetir 7 veces a Guillermo? Podras hacer esto, multiplicar a Guillermo por 7 (como no hay bastante con uno...) Paliza = "Guillermo" * 7 Pero el Basic te dira que eso no est bien, no porque Guillermo (yo) no sea un paliza, sino porque te dira que no coinciden los tipos (Type Mismatch) Que son los tipos? Los distintos tipos de datos. (que esfuerzo mental...) Los datos pueden ser, bsicamente, de dos tipos: Numricos: slo nmeros y Alfanumricos: cualquier cosa, letras y/o nmeros, pero es tratada como si fuesen palabras, frases, etc. Para el Basic 7 y "7" son dos tipos de datos diferentes. El primero es el nmero 7 y en el segundo caso, es el literal (o palabra) "7" As que cuando veas algo entrecomillado, piensa que no es un nmero, sino una palabra (ms vulgarmente llamada cadena de caracteres o string en ingls) Hemos visto que no podemos multiplicar una palabra (cadena) por un nmero, pero si podemos multiplicar una variable por un nmero (siempre que la variable sea numrica, por supuesto) Segn esto, el Basic debera permitir hacer esto: Guillermo = 5 Paliza = Guillermo * 7 El Basic tomara el 5 y lo almacenara en una variable numrica llamada Guillermo. Despus se encuentra con: Paliza = Guillermo * 7 y aqu lo que hace es evaluar la expresin que est despus del signo igual, lo calcula y el resultado lo guarda en la variable que est a la izquierda del signo de asignacin (=)
40
Expresin? Expresin es cualquier cosa que el Basic tenga que 'desglosar' para poder entenderla, incluso a veces ni eso... Por ejemplo cuando el Basic se encuentra con 5 * 2 tiene que 'evaluar' lo que significa, para poder hacer el clculo, de esta forma sabr que tenemos una operacin en la cual queremos multiplicar dos nmeros, una vez que ha evaluado nuestra intencin de multiplicar esos dos nmeros, efectuar el clculo y almacenar el resultado en... si no le decimos dnde, lo har en una memoria que tiene para esas cosas, pero si no le indicamos que debe hacer con ese resultado, nos dar un error... Si le decimos simplemente: 5 * 2 El Basic no sabr que hacer con el resultado de esta 'expresin' (que por cierto es 10) y nos dir: o te espabilas o lo tienes crudo conmigo. As que lo ms juicioso sera decirle: vale, vale, gurdalo en una variable, as que: Resultado = 5 * 2 guardara un 10 en la variable Resultado. Tambin podramos decirle que nos mostrara el resultado, en lugar de guardarlo en una variable, y aqu llega nuestra primera instruccin: Print. Con ella le decimos al Basic que lo imprima (o sea que los muestre, ms adelante veremos dnde), segn lo dicho, haciendo esto: Print 5 * 2, el Basic dira que muy bien y mostrara un 10 Pero, volvamos al Paliza del Guillermo, es decir al ejemplo de Paliza = Guillermo * 7 Si quisiramos mostrar el valor de Paliza, tendramos que hacer algo como esto: Print Paliza, y nos mostrara 35, ya que el valor de Paliza sera 35, porque el contenido de Guillermo es 5 y 5 * 7 es 35 (y sin calculadora!!!) Veamos si es cierto que Guillermo vale 5. Haciendo Print Guillermo, mostrar un 5. Antes de seguir 'imaginando' las cosas, vamos a verla en funcionamiento. Es decir vamos a probar que todo esto es cierto. Carga el Visual Basic (si es que an no lo has hecho). Te crear un Form nuevo, que estar vaco. Cirralo y muestra la ventana de cdigo. Mostrar la parte de las declaraciones Generales del Formulario. Si tiene escrito Option Explicit, (estar en la parte superior), brralo, ms adelante te explicar para que sirve. Ahora sitate en Form (seleccinalo de la lista desplegable que est a la izquierda), te mostrar: Private Sub Form_Load() End Sub Sitate en medio, es decir, en una lnea en blanco despus del Private... y escribe el ejemplo, quedara as: Private Sub Form_Load() Show Guillermo = 5 Paliza = Guillermo * 7 Print Paliza End Sub
41
Pulsa F5, para ejecutar el programa, y vers que se escribe el 35. Bien, ya tienes una forma de mostrar datos. Ahora veamos otros ejemplos, antes debes parar el programa, para ello cierra el Form, pulsando en el botn que tiene una X, o bien pulsa en el botn detener de la barra de herramientas del VB. Sitate de nuevo en el cdigo del Form_Load, escribe despus de la lnea del Print, lo siguiente: Print Guillermo Pulsa de nuevo F5 y vers que ahora adems del 35, hay un 5 debajo. El valor de la variable Guillermo. Pero, que ocurrira si cambisemos el valor de Guillermo? Aade estas lneas a continuacin de la anteriores, para que quede de esta forma: Private Sub Form_Load() Show Guillermo = 5 Paliza = Guillermo * 7 Print Paliza Print Guillermo Guillermo = 10 Print Guillermo Print Paliza End Sub Despus de pulsar F5, te mostrar los siguientes valores (cada nmero en una lnea), 35, 5, 10, 35 Esperabas que el ltimo fuese 70? Fjate que cuando asignamos a Paliza el contenido de Guillermo, ste era 5, por tanto el Basic evalu la expresin 5 * 7 y almacen el resultado (el 35). Una vez almacenado el resultado, el Basic se olvid de dnde haba sacado ese 5. Si queremos que se 'actualice' el valor de Paliza, tendremos que indicrselo de nuevo al Basic, para que vuelva a evaluar la expresin y hacer la correspondiente asignacin. Para ello, pon en medio de los dos ltimos prints la siguiente asignacin: Paliza = Guillermo * 7 Esta vez, al ejecutar el programa, mostrar un 70, que ser el nuevo contenido de Paliza. Ya para terminar, borra todo lo anterior y escribe: (por supuesto debes detener el programa...) Private Sub Form_Load() Show Nombre = "Guillermo" Print Nombre End Sub
42
Pulsa F5 y vers que se muestra el contenido de la variable Nombre, es decir Guillermo. Prueba ahora con esto otro (es un clsico): Print "Hola Mundo" Y para rematar, y de camino ver otra posibilidad del Print, escribe en lugar del Print Nombre: Print "Hola " ; Nombre El punto y coma, se usa para indicarle al Basic que se deben mostrar las cosas una a continuacin de la otra. Ahora te habr mostrado: Hola Guillermo, fjate que despus de hola y antes de cerrar las comillas hay un espacio. Bien, creo que con esto es suficiente por hoy... o por ahora. Si tienes algn comentario, hazlo pulsando en este link. As sabr que opinin te merece este primer captulo del cursillo/tutorial o como prefieras llamarlo. Nos vemos, (espero que pronto).
Nota: En todos los FORM_LOAD debers poner SHOW al principio para que se muestre lo que se imprime.
Ya lo he cambiado para que no te quejes, pero los que empezaron el curso antes del 4 de Sep'98, lo tuvieron ms crudo...
43
es que el Visual Basic nos permite controlar un poco mejor el tema de las variables que queremos usar. Ese control lo da la instruccin: Option Explicit. Si ponemos esto en la parte destinada a las declaraciones de cualquier mdulo, nos obligar a declarar las variables que vamos a usar en dicho mdulo. En el ltimo ejemplo de la entrega anterior, tenamos una variable llamada Nombre, en la cual almacenamos un nombre, por tanto podramos haberle avisado a Visual Basic que reservara espacio para una variable, y para hacerlo usamos la instruccin DIM, con sta le indicamos que nos guarde un "cachillo" de la memoria para nuestro uso: Dim Nombre Tambin nos ofrece una variante con respecto al "basic clsico" y es, precisamente, el tipo de datos variant. Con este tipo podemos asignar a una variable cualquier tipo de dato. Desde un nmero hasta una cadena de caracteres, pasando por cualquier tipo de objeto que Visual Basic pueda manejar (ms o menos). Los que hayan tenido experiencias anteriores, con Basic u otro lenguaje, sabrn que cada variable debe ser del tipo de datos que queramos asignarle. En VB por supuesto esto tambin es posible y recomendable. Ya vimos que los tipos de datos podan ser numricos o de caracteres. Pero dentro de los numricos, tenemos cuatro tipos bsicos: enteros, enteros largos, simples y dobles. Cada uno de ellos tienen unas capacidades determinadas, adems de ocupar ms memoria o menos, (ahora lo veremos), lo ms importante es que los nmeros enteros y entero largo slo admiten nmeros enteros (de ah sus nombres), es decir que no admiten decimales. Sin embargo los otros dos si admiten decimales. Estas capacidades puedes encontrarlas en el manual del basic o en la ayuda, lo que a mi me interesa que sepas es cmo poder indicarle al Visual Basic que reserve ese espacio de memoria para un tipo determinado. Ya te he dicho que el espacio que ocupa en memoria es diferente para cada uno de estos tipos, veamos en la siguiente tabla cmo declararlos y cuanto ocupa: Tipo Entero Entero Largo Simple Doble Espacio ocupado Tipo de declaracin 2 bytes 4 bytes 4 bytes 8 bytes Integer Long Single Double Ejemplo Dim Numero As Integer Dim Numero As Long Dim Numero As Single Dim Numero As Double
En el caso de las variables que van a guardar nombres (cadenas de caracteres), se deben declarar como String y el espacio que ocupa ser 4 bytes ms un byte por cada caracter que tenga, en el caso de VB de 32 bits realmente ocupar 2 bytes por cada caracter que tenga. La longitud mxima de una variable del tipo String ser de aproximadamente 32.000 caracteres y la forma de declararla ser: Dim Cadena As String Una vez declarada podemos asignarle la cantidad de caracteres que queramos (sin pasarnos) y cuantas veces queramos. Hay que tener en cuenta que en cualquier variable slo se queda el ltimo valor asignado. Ya lo vimos en la entrega anterior, pero vamos a refrescarlo un poco: Dim Nmero As Integer Nmero = 5 Print Nmero
44
Nmero = 12 Print Nmero En este ejemplo, el ltimo valor almacenado en Nmero es el 12. El 5 que tena en un principio se perdi. Pero, que tengo que hacer para sumarle una cantidad al valor almacenado en una variable? Es decir, cmo incrementar el valor de una variable numrica? La respuesta la tienes en cmo se manejan las expresiones y las asignaciones a las variables. Como ya vimos anteriormente, al asignar a una variable una expresin, primero se calcula la expresin y una vez obtenido el resultado, se asigna a la variable. Recuerdas lo que ocurra con la variable Paliza? Vamos a verlo de nuevo, pero usando otros nombres menos "cachondos" Dim N As Integer Dim M As Integer N = 10 M = N * 3 Print M El resultado de este programa sera 30, que es lo que resulta de multiplicar 10 por 3. Cuando se asigna a la variable M el valor de N (que es 10) multiplicado por 3, el VB toma el contenido de N y lo multiplica por 3. Una vez que sabe la solucin, asigna ese valor, en este caso 30) a la variable que hay a la izquierda del signo igual. Sabiendo esto, podramos simplificar la cosa y hacer los siguiente: N = N * 3 Print N Tambin obtendramos 30. Ya que cuando el Basic calcula la expresin de la derecha del signo igual, N vale 10, una vez obtenido el resultado del clculo lo asigna a la variable de la izquierda del signo de asignacin, sin importarle lo ms mnimo de que variables es y como en este caso hemos usado la misma, pues se queda el ltimo valor, perdindose el que originalmente estaba "guardado". Esto es til cuando necesitamos "contar" de forma secuencial, para as incrementar el valor de una variable. Ya veremos alguna "utilidad" para estos casos. Ahora vamos a ver cmo podemos manejar las cadenas de caracteres, es decir las variables de tipo String. Con estas variables ocurre lo mismo que con las numricas, pero la nica operacin que podemos realizar es la suma. Realmente una suma en una cadena de caracteres es "pegar" dos cadenas en una sola. Por ejemplo si hacemos esto: N = 3 + 2. El valor obtenido es 5 y eso es lo que se guarda en N. Sin embargo con los strings hacer esto: Cadena = "A" + "B", se guardar "AB", es decir se unirn las dos cadenas en una sola. Para este tipo de operacin se recomienda usar mejor el signo &. Que entre otras cosas le indica al Visual Basic que lo que pretendemos hacer es unir dos cadenas, no sumarlas. Aunque "tericamente" no se pueden sumar cadenas, slo con-catenarlas, veremos cmo
45
podemos llegar a producir "problemillas" de entendimiento entre el VB y nuestras "mentes poderosas". Como te he comentado al principio el tipo de datos Variant acepta de todo, nmeros, nombres, etc. Si no le indicamos de forma correcta al VB cual es nuestra intencin, podemos confundirle y hacer que el resultado de algo que nosotros dbamos "por hecho", al final se convierte en un pequeo caos para nuestras pobres mentes. Vamos a verlo con un par de ejemplos, en estos casos: (al no indicarle de que tipo son las variables, el Basic entiende que nuestra intencin es usar el tipo Variant) Dim Num1 Dim Num2 Num1 = 5 Num2 = 3 Num1 = Num1 + Num2 Print Num1 Que imprimir? Prubalo y saldrs de duda. Bueno, imprimir un 8. Ahora veamos este otro ejemplo: Dim Num1 Dim Num2 Num1 = "5" Num2 = "3" Num1 = Num1 + Num2 Print Num1 Fjate que lo que vara es slo las comillas. El resultado en este caso es 53, es decir ha unido las dos cadenas. Ahora quita las comillas del 5, para dejarlo as: Dim Num1 Dim Num2 Num1 = 5 Num2 = "3" Num1 = Num1 + Num2 Print Num1 Alehop! Que ha pasado? Pues que ha impreso un 8, es decir ha "pasado" de que el tres sea una cadena de caracteres y lo ha tomado por un nmero... En esta ocasin, slo vamos a cambiar la lnea de la asignacin para dejarla de esta forma:
46
Num1 = Num1 & Num2 El resultado ser 53. Porque le hemos indicado que una las dos cadenas, por tanto al encontrase con esta "operacin" ha considerado al nmero 5 como una cadena, en lugar de un nmero. Cambia ahora la asignacin del Num2, para que sea: Num2 = 3 Vuelve a mostrar 53, el signo & tiene unos poderes enormes... y a pesar de ser dos nmeros la nica operacin que puede realizar es la concatenacin de cadenas, por tanto el tipo Variant se convierte por arte de magia en cadena. Pero fjate si es "fuerte" el poder de conviccin que tiene este operador, que aunque cambiemos el tipo de las variables, sigue "convenciendo" al basic que tipo de operacin debe hacer. Esto no debera ocurrir as, pero ocurre. Dim Num1 As Integer Dim Num2 As Integer Num1 = 5 Num2 = 3 Num1 = Num1 & Num2 Print Num1 Sigue mostrando 53, aunque en este caso debera producir un error, ya que un Integer no es una cadena. As que "cuidadn" con las operaciones que realizamos. Ya que si aades esta lnea: Print Num1 * 2 Vers que realmente Num1 tiene guardado un nmero y el resultado ser: 106 A dnde nos lleva todo esto? A que debemos usar los signos (operadores) de forma adecuada. Y si nuestra intencin es sumar nmeros, empleemos el signo +, en caso de que queramos unir cadenas de caracteres, usaremos el & Para rematar esta segunda entrega, vamos a usar un textbox para que se puedan introducir datos y empecemos a manejar los eventos, mejor dicho empecemos a "habituarnos" a los eventos. Aade al form dos Label, un TextBox y un botn de comandos. El aspecto ser algo parecido al de la siguiente figura:
47
Aade el siguiente cdigo y despus ejecuta el programa, ya sabes F5. Escribe algo en el cuadro de texto y pulsa en el botn. Private Sub Form_Load() Label2 = "" Text1 = "" End Sub
Private Sub Command1_Click() Label2 = "Hola " & Text1 End Sub Cuando pulsas F5, se produce el evento Form_Load, por tanto se asigna al Label2 y al Text1 una cadena vaca, con lo cual borramos el contenido anterior, que es el que se muestra en la Figura. Hasta que no pulsemos el botn mostrar, no ocurrir nada y el programa estar esperando a que ocurra algo. Una vez pulsado el botn, se produce el evento Click del Command1 y se hace lo que se indica en su interior, que es tomar lo que hay en la caja de texto y unirla a la palabra Hola, para asignarla al Label2. Ahora, imagnate que quieres mostrar el nombre en maysculas. Lo nico que tendras que hacer es lo siguiente: Private Sub Command1_Click() Label2 = "Hola " & UCase(Text1) End Sub Lo que se ha hecho es decirle al VB que convierta en maysculas lo que ya est en Text1. Esa es la "utilidad" del UCase. Pero y si quisiramos que conforme se va escribiendo se vayan convirtiendo los caracteres a maysculas? Aqu entraran ms instrucciones/funciones del Visual Basic, as cmo otro de los eventos que pone a nuestra disposicin, en este caso el evento que se produce cada vez que se modifica el contenido del textbox: Change, escribe lo siguiente: Private Sub Text1_Change() Text1 = UCase(Text1) End Sub Prubalo y vers lo que ocurre. Queda "guay" verdad? Pero no es lo que nosotros pretendamos. Vamos a intentar remediarlo y de camino vemos nuevas instrucciones/propiedades, en este caso del TextBox.
48
Private Sub Text1_Change() Text1 = UCase(Text1) Text1.SelStart = Len(Text1) End Sub La lnea que se ha aadido (realmente la habrs tecleado t), lo que le indica al Visual Basic es que haga lo siguiente: Calcula la longitud del contenido del Text1, (Len cuenta los caracteres de una cadena y lo devuelve como nmero), SelStart es una propiedad del TextBox que entre otras cosas, le indica la posicin en la que se insertar el siguiente caracter que se escriba o bien nos puede indicar la posicin actual del cursor. Por tanto obliga a poner el cursor, (el palico ese que parpadea y que nos indica que podemos escribir), al final de la ltima letra que contiene el Text1. Ahora ya sabes que cada vez que "cambie" el Text1, se produce un evento Change. Pero hay otra forma de hacer esto mismo y es controlando cada tecla que se pulsa. Esto lo podemos "controlar" en el evento KeyPress, el cual se produce cada vez que se pulsa una tecla. Borra el procedimiento anterior y escribe este otro: Private Sub Text1_KeyPress(KeyAscii As Integer) Dim s As String s = UCase(Chr(KeyAscii)) KeyAscii = Asc(s) End Sub Ahora han entrado dos nuevas funciones en accin: Chr, la cual convierte un nmero en una cadena... realmente convierte un cdigo ASCII en la letra que representa (busca en la ayuda ASCII y lete lo que dice en las opciones que te muestra). Por otra parte Asc hace lo contrario, es decir convierte una letra en el cdigo ASCII. Y lo que nosotros hacemos es: convertir el cdigo de la tecla pulsada, representado por la variable KeyAscii, en una cadena, la pasamos a maysculas y despus la volvemos a asignar a la variable, para "engaar" al Visual Basic y as hacerle pensar que realmente hemos tecleado una letra en maysculas. Bueno, aqu voy a dejar la cosa, pues creo que con esto es te puedes ir "entreteniendo". Nos vemos, espero en la prxima entrega. Y ya sabes que espero que me mandes tus comentarios, por lo menos para saber que hay alguien interesado en seguir este curso "super-bsico".
Bien, despus de un mes y pico, seguimos con la tercera entrega del curso "super-bsico" de programacin con Visual Basic. Si quieres ver las entregas anteriores, pulsa en los siguientes links: este para la Primera y este otro para la Segunda. Esta entrega la voy empezar con recomendaciones e instrucciones del buen hacer en Visual Basic, espero que sigas algunas, preferiblemente todas, estas normas. Ya has visto cmo maneja el Visual Basic las variables, si a esta "libertad" (aunque ms bien es libertinaje), le aadimos que no nos obliga a nada, es decir el VB nos est diciendo: "puedes usar las variables para lo que quieras, cmo quieras (o casi) y cuando quieras" Y esto en principio podra parecer una buena cosa, pero realmente es un mal hbito, que muchos de los que vens del BASIC, ya tenis formado y creo que ahora sera un buen momento para empezar a cambiar. Lo primero que debes hacer es ir al men Herramientas (Tools) y en Opciones (Options) marca la casilla que indica "Requerir declaracin de variables" (Require Variable Declaration), esto aadir a cada nuevo mdulo (FRM, BAS o CLS) la siguiente instruccin: Option Explicit, de esta forma tendrs la obligacin de declarar cada una de las variables que uses en el programa. Y tu preguntars: Para que obligar a que se declaren las variables? La respuesta es bien sencilla: para que las declares... (algunas veces me asombro de la lgica tan aplastante de mis comentarios) Bromas aparte, es recomendable que declares las variables que vayas a usar y te dira ms: no slo es bueno declarar las variables, sino que mejor an es declararlas del tipo adecuado. Ya vimos que hay diferentes tipos de variables, no slo de tipos genricos como podran ser para almacenar caracteres y nmeros, sino que dentro de las numricas hay varios tipos, y cada uno de ellos tiene una razn de ser. En mis tiempos del BASIC normalito, es decir del MS-DOS, no exista esta obligacin de declarar "forzosamente" las variables y cuando estabas escribiendo un programa (proyecto que lo llaman ahora), grande, acababas "inevitablemente" usando ms variables de la cuenta porque ya no recordabas si la variable "i" o "j" estaba siendo usada a nivel global o no... (yo es que con el despiste que gasto, me vea creando las variables "ii", "j2", etc., para no "meter la pata") y esto no era lo peor, al fin y al cabo lo nico que ocurra era que estaba "desperdiciando" memoria, por no tener un control de las variables que estaba usando; lo malo era que se podan escribir errneamente los nombres de las variables de forma que al final, el programa no funcionaba bien porque al escribir un nombre de variable, habamos cambiado el nombre... era frustrante y algunas veces te volvas loco buscando el fallo... La ventaja de usar el Option Explicit, es que si escribes mal una variable, el VB te avisa... bueno, algunas veces te avisa, sobre todo cuando se encuentra con la variable "mal escrita". Aqu viene la segunda recomendacin del da: cuando ejecutes un programa, hazlo con Control+F5, de esta forma se hace una compilacin completa y "ms o menos" exhaustiva del cdigo, avisndote cuando hay algo que no "cuadra", con el VB3 no haba problemas, ya que siempre se haca la compilacin completa, pero desde el VB4 se puede pulsar F5 y hasta que no llega al procedimiento actual, no comprueba si todo lo que hay en l est correcto. As que para "curarte en salud" procura hacer la compilacin completa La tercera recomendacin no es obligatoria, siempre que sigas la que voy a dar despus, esta es una norma que tambin he usado desde mis tiempos de MS-DOS (aunque reconozco que ltimamente no la pongo en prctica, ya que hago lo que despus
50
comentar en la cuarta recomendacin). En todos los mdulos, antes slo eran BAS, pona al principio la siguiente lnea: DEFINT A-Z de esta forma le indicaba al BASIC que mi intencin era usar todas las variables del tipo Integer (entero), (realmente despus usaba del tipo que me daba la gana, pero mi primera intencin era no complicarme la vida con la mayora de las variables), cuando quera usar una variable diferente de Integer, le indicaba "explcitamente" de que tipo era y as me obliga a usar la mayora de ellas de este tipo que a la larga es o era el ms usado, ya que para hacer bucles (ya te explicar en un ratillo que es eso de los bucles y cmo hacerlos en VB) y otros clculos "normales", era ms que suficiente y en la mayora de los casos: ms rpido. En Basic, y por supuesto todava en Visual Basic, aunque cada vez va a menos, se puede indicar el tipo de una variable de varias formas, al declararlas con Dim, vimos que se haca de la siguiente forma: Dim unNumero As Integer Dim unNumeroLargo As Long Dim otroNumero As Single Dim masNumeros As Double Dim unNombre As String Dim multiUso As Variant Cada una de estas variables es de un tipo distinto, las cuatro primeras numricas, la quinta para almacenar cadenas de caracteres y la ltima del tipo por defecto del VB: Variant que como su nombre indica (aunque en ingls), es Variante y puede almacenar prcticamente cualquier cosa, objetos incluidos, (ya veremos los objetos en otra ocasin). Lo del tipo por defecto, es siempre que no se haya especificado un tipo determinado para todas las variables, por ejemplo usando el DEFINT A-Z, el tipo por defecto ya no es Variant, sino Integer. Al grano, "quesnoche", a lo que iba era que adems de declarar las variables de esta forma, tambin se puede hacer de de esta otra: Dim unNumero% Dim unNumeroLargo& Dim otroNumero! Dim masNumeros# Dim unNombre$ En el caso de Variant no existe un caracter especial para indicar que es de ese tipo, as que cuando quieras usar una variable Variant, tendrs que declararla como en el primer ejemplo. An queda otro carcter para otro tipo de datos numrico, el tipo Currency que se puede declarar con @. Este tipo ocupa 8 bytes y permite guardar nmeros de tipo moneda, es decir nmeros no enteros, pero con un nmero determinado y fijo de decimales, ahora no recuerdo, pero en la ayuda o en los manuales podrs ver la "retaila" de nmeros que cada tipo admite.
51
Para terminar con las recomendaciones de hoy, voy a indicarte algo que debes tener en cuenta cuando declaras variables y que an los ms expertos caen en la trampa. Adems de declarar las variables con Dim, poniendo cada declaracin en una lnea, cosa que por otro lado queda bastante claro y es como suelo hacerlo, aunque ltimamente estoy volviendo a coger malos hbitos... ser la edad? Tambin se pueden declarar ms de una variable con un mismo DIM, vamos a verlo con un ejemplo: Dim Numero As Integer, NumeroLargo As Long, otroNum As Single, Nombre As String, Numerazo As Double por supuesto tambin valdra de esta otra forma: Dim Numero%, NumeroLargo&, otroNum!, Nombre$, Numerazo# Y si me apuras, tambin de esta otra: Dim Numero%, NumeroLargo As Long, otroNum As Single, Nombre$, Numerazo# Pero sea como fuere, en todos los ejemplos se ha especificado el tipo que queremos asignar. Por supuesto tambin podremos declarar variables de esta forma: Dim unaVariable, otraVariable, terceraVariable Pero, surge esta pregunta de que tipo son estas tres variables? (al menos se espera que te surja...) La respuesta es bien sencilla, si se ha entendido toda la "retahila" que te he soltado anteriormente: Sern del tipo Variant o del especificado con el DEFINT A-Z (es decir Integer) Voy a suponer que la tercera recomendacin no la ests poniendo en prctica, por tanto seran del tipo Variant. Pero fjate que podras caer en el error, sobre todo si has programado algo en C, de pensar que esta lnea: Dim Numero, otroNumeroInt, elTercero As Integer o esta otra: Dim Numero As Integer, otroNumeroInt, elTercero estn declarando tres nmeros Integer y no es as, lo que se est declarando sera, en el primer caso: Numero y otroNumeroInt como Variant y elTercero como entero. en el segundo caso slo Numero sera del tipo entero y las otras dos variables del tipo Variant. Sera "ideal" que fuese como aparenta, pero el VB no hace estas "virgueras", ni incluso en la versin 5. Por tanto, cuando declares variables, fjate bien de que tipo son las que ests declarando, para no llevarte sorpresas, sobre todo con los redondeos y errores de desbordamiento... Un desbordamiento se produce cuando asignamos a un nmero un valor mayor del que est capacitado para almacenar, as si un entero slo acepta valores de +/- 32767 (realmente acepta hasta -32768), al asignarle un valor de 40000, nos dir que "turur" y dar error. En cuanto a que tipo de variable usar en cada caso, tendrs que tener en cuenta que quieres hacer. Normalmente en los bucles se suelen usar variables enteras, bien Integer, si sabemos que no nos vamos a pasar de 32767, bien Long Integer que puede almacenar un valor de dos mil y pico millones... (quien los tuviera, aunque fuese en calderilla!) Vamos a ver un ejemplo (al fin algo de cdigo se escucha entre el pblico...), con este cdigo podrs comprobar la velocidad de los bucles con los distintos tipos de variables y
52
as poder comprobar cual es la ms adecuada. Crea un nuevo proyecto y asigna unos cuantos Labels (6 en total) y un botn. Cuando ejecutes este programilla, puedes ir tranquilamente a tomar caf, porque se tomar su tiempo... En teora nos mostrar el tiempo que emplea en hacer unos bucles con tipos diferentes de datos. Para que sea fiable, debers especificar unos valores altos, ya que con nmeros pequeos no es demasiado fiable, e incluso con nmeros altos tampoco... la cosa era poner algo de cdigo para "rematar" el captulo de hoy... En la prxima entrega explicar las instrucciones que se han usado y en algunos casos, explicar hasta el por qu de usarlas. O sea esto es lo que se dice un programa intil que adems de consumir recursos del sistema y hacernos perder el tiempo, no vale para nada... (es que despus de probarlo, me he dado cuenta de que o todos los formatos son prcticamente igual de rpidos o yo he estado "engaado" durante todo este tiempo...) Option Explicit Private Sub Command1_Click() Dim nInt As Integer Dim nLng As Long Dim nSng As Single Dim nDob As Double Dim nCur As Currency Dim nVar As Variant Dim timer1#, timer2 As Double Const minBucle = 1, maxBucle = 10 Command1.Caption = "Calculando..." timer1 = Timer For nInt = minBucle To maxBucle Contar CInt(nInt), Label1 Next timer2 = CDbl(Timer - timer1) Label1 = "Duracin con Integer: " & timer2 DoEvents timer1 = Timer For nLng = minBucle To maxBucle Contar CInt(nLng), Label2 Next timer2 = CDbl(Timer - timer1)
53
Label2 = "Duracin con Long: " & timer2 DoEvents timer1 = Timer For nSng = minBucle To maxBucle Contar CInt(nSng), Label3 Next timer2 = CDbl(Timer - timer1) Label3 = "Duracin con Single: " & timer2 DoEvents timer1 = Timer For nDob = minBucle To maxBucle Contar CInt(nDob), Label4 Next timer2 = CDbl(Timer - timer1) Label4 = "Duracin con Double: " & timer2 DoEvents timer1 = Timer For nCur = minBucle To maxBucle Contar CInt(nCur), Label5 Next timer2 = CDbl(Timer - timer1) Label5 = "Duracin con Currency: " & timer2 DoEvents timer1 = Timer For nVar = minBucle To maxBucle Contar CInt(nVar), Label6 Next timer2 = CDbl(Timer - timer1) Label6 = "Duracin con Variant: " & timer2 DoEvents Command1.Caption = "Calcular" End Sub Private Sub Contar(valor As Integer, etiqueta As Control)
54
Dim i As Integer Dim unDoble As Double Const elMaximo = 1000& For i = 1 To elMaximo unDoble = unDoble + 1 etiqueta.Caption = valor * elMaximo + unDoble DoEvents Next End Sub Te espero en la prxima entrega. Y como te digo y seguir dicindote: espero que me mandes tus comentarios, para saber si slo lo estis leyendo t y alguien ms... es que si no me dices nada, me aburro y acabar por dejarlo...
55
Y la cosa es que antes de hacer este ejemplo, yo crea que algunos tipos de datos eran ms rpidos que otros, (y a pesar de que el ejemplo demuestra, o casi, lo contrario, sigo creyndolo...). La cosa es que en 32 bits un Long debera ser ms rpido que el resto. Y los enteros ms rpidos que los de coma flotante... voy a probarlo en un 386 sin copro, a ver... ahora vuelvo... Ya puestos a probar, he probado, aqu se demuestra lo "morro" (o cabezn) que soy, y en esta tabla (pulsando en el link), tienes los diferentes valores en distintos equipos y con distintas versiones de VB. Vers que sorpresilla te llevas... Lo has visto? Dejar este tema, que ya es mucho lo que le he dedicado, vamos a ver el programa y as cambiamos de tercio... Para introducir cdigo en cualquiera de los eventos de los controles o del formulario, lo nico que tienes que hacer es seleccionar el control y el evento que queremos codificar de las listas desplegables, en el mdulo de cdigo, pulsando en Cdigo en la ventana en la que se muestra los mdulos y formularios que forma un proyecto. En la lista de la izquierda seleccionamos el control y en el de la derecha nos mostrar todos los eventos soportados por VB para ese control. Si sabemos el nombre del control y el del evento, podemos teclearlo directamente o bien si copiamos cdigo de otro sitio, simplemente con pegarlo, se va a su sitio. En el caso de querer aadir al cdigo, una funcin o procedimiento se puede hacer de varias formas, lo acabo de decir, pero lo repito un poco ms claro: 1. Directa: Escribir el cdigo directamente, con lo cual se crear un nuevo "apartado" en la lista de las funciones/ procedimientos. En caso de que no sea un evento soportado por los controles de nuestro formulario, se mostrar en la lista de la izquierda, estando seleccionada en la derecha "General" 2. Copiar/Pegar: Pues eso, si copias una funcin/procedimiento y lo pegas en la ventana de cdigo... 3. Por men de VB: Segn las distintas versiones de VB, ser un men u otro, debers especificar el nombre del procedimiento o la funcin, marcando la casilla correspondiente. En VB4/VB5 vers que aparte de los Procedimientos (Sub) y las Funciones (Function) hay tambin Propiedades (Property), estas las veremos en otra ocasin. Tambin vers que puedes declararlas Pblicas o Privadas. Esto no es posible en VB3, al menos en los procedimientos y funciones de los formularios. En otra ocasin veremos todas estas cosas y con ejemplos, que es lo que "mola". Bueno, toda esta "retahla" vena a cuento de cmo introducir cdigo en los eventos de los controles o del formulario y cmo crear nuestras propias instrucciones (esto es lo que ms me gust del QuickBasic 4.0, poder crear mis propias instrucciones (subs) y funciones). Ya es hora de coger el listado de la entrega anterior y "destriparlo". Vamos a ver cada cosa por separado, que aunque parezca que es mucho cdigo, realmente est "repetido", o casi...
56
Option Explicit Esto nos obliga a declarar todas las variables que usemos en el mdulo, ponerlo es una recomendacin, incluso te la impondra como norma. Para que salga de forma automtica en cada nuevo mdulo, selecciona del men Tools/Advanced la opcin Declare variables required (o algo parecido, que viene a significar Requiere la declaracin de variables) Siguiendo nuestro recorrido por el cdigo, no encontramos con: Private Sub Command1_Click() Lo de Private significa que slo se puede acceder a este procedimiento desde dentro del mdulo en el que est declarado. Es decir no se puede llamar desde otro form o mdulo BAS. Sub indica que es un procedimiento, no una funcin, ni una propiedad. Los Subs actan como instrucciones propias del lenguaje. Las funciones tambin, pero devuelven un valor, mientras que los subs no devuelven nada, lo que cogen se los quedan ellos, aunque en su momento veremos que tambin nos pueden dar algo a cambio. Command1_Click, este es el nombre que habr que usar para acceder a l desde cualquier punto de ste mdulo. Los parntesis sin nada dentro, indica que este procedimiento no recibe parmetros; los parmetros lo veremos dentro de un "ratillo" Toda esta lnea es la descripcin del procedimiento y cuando se le llame, bien desde nuestro propio cdigo, bien porque se pulse sobre el botn, se ejecutar todo lo que est dentro de l. El final del procedimiento est marcado por End Sub. Las lneas con DIM indican que estamos declarando las variables y lo que se especifica despus del AS es el tipo de variable, los cinco primeros son de cada uno de los tipos numricos soportados (otro da veremos otro tipo cuasi-numrico), el sexto es Variant, el multi-uso, el que vale para todo. Veamos ahora que es lo que se hace con esta lnea: Dim timer1#, timer2 As Double Aqu he declarado dos variables del tipo Double. Al separarlas con comas no hay que repetir la palabra DIM, pero s el tipo de cada variable. Ya vimos en la entrega anterior que algunos tipos de variables se podan indicar mediante unos caracteres especiales, (estos tipos son los heredados de versiones anteriores al Visual Basic 2, en esa versin, se introdujo el tipo Variant), en este caso # es lo mismo que Double, por tanto se podra haber hecho tambin de cualquiera de estas tres formas: Dim timer1#, timer2# Dim timer1 As Double, timer2# Dim timer1 As Double, timer2 As Double Ahora fjate que esta otra no hara la misma tarea: Dim timer1, timer2 As Double Esto funcionara con lenguajes como el C, (realmente el tipo se pone delante), pero en Basic no declara las dos variables como Double. La segunda variable si es Double, pero la primera es del tipo por defecto, en nuestro caso Variant. As que mucho ojito con las declaraciones de las variables. En algn sitio, no voy a decir dnde, porque lo mismo fue un "lapsus" del autor, pero deca que de esta forma declaraban tres variables de tipo Integer: Dim i, j, k As Integer Sigamos nuestra andadura, ahora veamos esta declaracin/asignacin: Const minBucle = 1, maxBucle = 10
57
Aqu lo que se declaran son dos constantes, stas a diferencia de las variables, no pueden cambiar de valor, de ah su nombre, por tanto permanecern siempre con el mismo valor. Cuando se declara una constante, no es necesario especificar el tipo, VB se encarga de adivinarlo y usar el tipo adecuado, realmente lo que hace es sustituir estas "palabras" por el valor que hay despus del signo igual. En caso de hacer esto: cNombre = "Una palabra". Visual Basic sabe que es una cadena de caracteres y cada vez que se encuentre con cNombre lo sustituir por "Una palabra". Ahora viene la explicacin del "por qu" usar constantes. Adems de "esclarecer" los listados, los hace ms fciles de comprender, tambin nos permite modificar un valor en un slo sitio, con lo que ganamos en "confianza", al asegurarnos de que no omitiremos alguno de los sitios dnde tengamos o queramos cambiar el valor antiguo por uno nuevo. En nuestro ejemplo, minBucle y maxBucle se usan en seis partes diferentes del procedimiento, si quisieras probar con otros valores, tendras que cambiar en seis sitios esos valores, pero al declararlos como constantes, slo cambiando el valor asignado, tenemos todo el trabajo hecho. Esto adems de ser ms fiable y legible, nos puede ahorrar algn que otro quebradero de cabeza y si adems le aadimos que no ocupan espacio extra, salvo en la tabla de smbolos, una vez compilado el programa slo se "compilarn" las constantes usadas. Sin embargo con las variables no ocurre esto, ya que aunque no se usen, ocupan memoria. Un inciso, esto de explicar tan detalladamente los listados, no va a ser norma, ya que al final todos nos aburriramos, slo lo har cuando lo crea conveniente o bien si lo solicitas, en este caso, no tendr ms remedio que cumplir tus deseos... Command1.Caption = "Calculando..." Cambiamos el texto mostrado en el botn, para avisarnos de que est haciendo algo... timer1 = Timer Asignamos el valor de la funcin Timer a la primera de las dos variables que usaremos para calcular el tiempo empleado por cada uno de los bucles. Esta funcin devuelve el nmero de segundos transcurridos desde la media noche. For nInt = minBucle To maxBucle Esto es un bucle, que se repetir desde minBucle (1) hasta maxBucle (10) y la variable nInt es la que llevar la cuenta o la que se usar para saber el valor actual del bucle. Deberamos ver primero la declaracin del procedimiento Contar, para entender lo que hace la lnea que viene despus del For. Private Sub Contar(valor As Integer, etiqueta As Control) Declaramos un procedimiento privado llamado Contar (acta como una instruccin ms del VB, ya que no representa a ningn control ni evento), entre los parntesis estn declarados los dos parmetros que espera recibir en la llamada, el primero es un nmero entero y el segundo (separado por una coma), un Control, que puede ser cualquier control de VB. El resto del procedimiento ahora no es demasiado significativo Ahora veamos esta lnea: Contar CInt(nInt), Label1 Contar es el nombre del procedimiento y a continuacin se deben indicar los parmetros que espera recibir. En este caso no sera necesario CINT ya que lo que hace esta funcin es convertir el nmero que se pone dentro de los parntesis en un nmero entero y como resulta que nInt es un nmero entero... pues no hay nada que convertir! El segundo parmetro es el control Label1, ya sabes que tenemos 6 etiquetas en nuestro programa desde Label1 a Label6
58
Cuando llegue estos datos al procedimiento Contar, valor tomar lo que valga nInt y etiqueta se aduear de Label1. Next Contina repitiendo el bucle hasta que se alcance el valor mximo, realmente el Next lo que hace es lo siguiente: nInt = nInt + 1 Es nInt menor o igual que maxBucle? Si la respuesta es SI, sigue con lo que haya despus de la lnea FOR, en caso negativo contina con la lnea siguiente al Next (realmente en la siguiente instruccin despus del Next, ya veremos esto en otra ocasin) timer2 = CDbl(Timer - timer1) Asignamos a la segunda variable que usamos para el clculo del tiempo la diferencia entre el nuevo valor de los segundos transcurridos desde la media noche (Timer) y el valor que tena timer1, es decir cuantos segundos... antes de empezar el bucle. El CDBL es una fucin que devuelve un valor Doble. Es decir hace la resta y ese valor resultante lo convierte en doble. Label1 = "Duracin con Integer: " & timer2 Esta asignacin realmente es Label1.Caption, si se omite la propiedad, Visual Basic usa la que tiene por defecto, que segn los manuales, suele ser la propiedad que se usa con ms frecuencia. En este caso el Caption, es decir lo que se muestra en la etiqueta. DoEvents Esta es una instruccin "controvertida" y a la que muchos programadores no les hace demasiada gracia usar, no porque no tenga su utilidad, sino porque hay que saber realmente lo que hace y tener cuidado cuando la usamos, ya que algunas veces puede crear errores o confusin... realmente no es tan drstico, pero casi... DoEvents, detiene la ejecucin del programa y devuelve el control a Windows, para que ejecute los mensajes que tiene pendientes en la cola de mensajes... ? no te preocupes si no te enteras, es as y punto. por qu la uso? Pues para dar tiempo a que se vea el contenido del Label; prueba a quitarla y vers lo que ocurre, o debera ocurrir... que ya a estas alturas no me sorprendera nada que se mostrara... El resto del programa es idntico a este bucle, pero usando distintas variables y las dems etiquetas. El caso es que Contar espera una variable de nmero entero y un control, en el caso del control es obvio que se estn poniendo las distintas etiquetas y en el caso del nmero se convierte a entero, porque eso es lo que espera nuestra instruccin y si no lo hacemos as, se quejar. Ya slo queda ver una lnea del procedimiento Contar: etiqueta.Caption = valor * elMaximo + unDoble unDoble contar desde 1 hasta elMaximo, en cada vuelta del bucle, se asignar al caption de la etiqueta pasada al procedimiento y el DoEVents que viene a continuacin se encargar de que se muestre ese contenido. Bueno, tambin se asigna valor * elMaximo, es decir que cuando valor valga 1, se estar mostrando 1000 + unDoble, realmente para hacer un contador se tendra que haber usado lo siguiente: etiqueta.Caption = (valor -1) * elMaximo + unDoble, para que mostrara desde el 1, en lugar de empezar desde el 1001. Una vez que Contar termina, por el End Sub, vuelve el control al bucle que lo llam y se ejecuta la siguiente instruccin. Por tanto Contar se llamar tantas veces como dure el bucle en el que se encuentra.
59
Creo que queda todo ms o menos claro y aunque este cdigo no es muy til por s, ha servido para ver algunas cosillas del VB. Para terminar vamos a ver una serie de cambios y a ver si adivinis que es lo que hace... as os servir de ejercicio, cosa que algunos me habis pedido, pero que an no es el momento de hacerlos. En las declaraciones generales aade esta declaracin: Dim Contando As Integer En Contar, aade lo siguiente despus del DoEvents: If Contando = 0 Then Exit For Al principio del Command1_Click, aade estas lneas: If Contando Then Command1.Caption = "Calcular" Contando = 0 DoEvents Exit Sub End If Contando = 1 En cada uno de los bucles, pon esto despus de llamar a Contar... If Contando = 0 Then Exit Sub Y antes del End Sub aade esto otro: Command1.Caption = "Calcular" Contando = 0 Bueno, ah dejo esto y como ejercicio podras aadir dos controles TextBox para especificar los valores de maxBucle y elMaximo, de forma que segn los valores introducidos, que por defecto deben ser 10 y 1000, se usen los que especifiques en cada TextBox y se tengan en cuenta cuando pulsas (haces click) en el botn. Como pista te dir que las variables usadas y/o declaradas dentro de un procedimiento son slo visibles dentro de ese procedimiento. No te quejars del "pedazo" de pista que te he dado... A disfrutar y hasta la prxima entrega... cuando? Eso ni se sabe... Y ya sabes que espero tus comentarios sobre el cursillo este de marras... Nos vemos.
Hoy no voy a pasar lista, porque me imagino que los que faltan estarn aprovechando el puente este de Santiago o estarn preparando las maletas para irse de vacaciones... que suerte! Quin pudiera estar en la costa! con las playas "abarrotadas" de gente... je, je... yo no estoy de vacaciones, pero tengo la playa a menos de 50 metros de mi casa... ...que no se te pongan los dientes largos y lmpiate las babas... para tu consuelo te dir que an no he pisado la arena de la playa... O 8-| Vale, vale, tambin puedes irte al campo con los mosquitos... se nota que me gusta ms la playa? y que conste que la sierra que tenemos por esta zona es una maravilla... no, no soy del Patronato de Turismo... Bueno, una vez dado un "repaso" veraniego, vamos al cdigo, que es lo que te ha trado a esta pgina. Hoy no tengo nada que rectificar de la entrega anterior... salvo varios errores tipogrficos... De todas formas si lees estas entregas y hay posteriores, sera conveniente que fueses a la siguiente y te leyeras el principio, ya que aparte de poner algn comentario "chorra", suelo dar un repaso a los "errores" de la entrega anterior. Ahora viene el contenido real de la quinta entrega. No voy a dar la solucin al problema/ejercicio planteado en la entrega anterior, voy a dejar que la deduzcas. Para que tengas base suficiente, te voy a contar un poco cmo funciona el Visual Basic y por extensin todas las aplicaciones de Windows. En la segunda entrega creamos un programa que mostraba un form en el que tenamos una caja de texto (TextBox), un botn (CommandButton) y dos etiquetas (Label). Cuando, despus de pulsar F5 o CRTL+F5, se ejecuta la aplicacin, de lo nico que podemos tener certeza es que se ejecutar el cdigo que se encuentra en el procedimiento Form_Load. Este procedimiento (sub) en concreto es lo que se llama un evento, y se produce cada vez que el formulario (form) se carga (load) en la memoria. Antes de entrar en detalles del porqu podemos tener la certeza de que ese cdigo se va a ejecutar, tengo que seguir con mi 'ponencia' de cmo funcionan las aplicaciones Windows. Se dice, (otros lo dicen y yo me lo creo), que Windows es un sistema o entorno operativo 'dominado' por los eventos. Esto ya lo dej caer al principio de la segunda entrega. En Windows cada vez que movemos el ratn, pulsamos una tecla, tocamos una ventana o cualquiera de los controles que estn en ellas, se produce un evento. Cuando se 'dispara', (los anglosajones usan 'fire' para indicar que se produce el evento), uno de estos eventos, Windows le dice al form, (de forma bastante rpida, aunque en algunas ocasiones no tanto como nos hubiera gustado), que se ha movido el ratn y que el ratn ha pasado por encima de tal ventana o de ese control y as con todas las cosas. La verdad, no es de extraar que de vez en cuando falle el sistema, es que no para de disparar!!! y algunos disparos se le puede escapar y... ...que chiste ms malo, verdad? Ya pensabas que el comentario ese del 'fire' era porque "el Guille se cree que est traduciendo un artculo de VB Online"... Lo que quiero dejar claro es que a diferencia de los lenguajes que funcionan en MS-DOS, en Windows no podemos 'predecir' cual ser el cdigo que se va a ejecutar. No debes 'planificar' tu programa dando por sentado que... "despus de esto el usuario 'tiene' que hacer esto otro y as yo podr hacer una comprobacin para..." NO ! Aqu (en Windows) no existe la programacin lineal, no des nunca por hecho que esto es lo que va
61
a ocurrir..., porque casi con toda seguridad no ocurrir! Veremos cmo podemos 'controlar' que algunas cosas se hagan cuando nosotros queramos, pero esto ser slo cuando el usuario de nuestra aplicacin realice una 'accin' que estaba prevista; tambin codificaremos para que se ejecute parte del cdigo cuando el usuario no haga lo que habamos previsto que hiciera. Pero eso lo iremos viendo poco a poco... Todo programa de Windows tiene un punto de entrada; en el caso de Visual Basic, puede ser bien un formulario o bien un procedimiento de un mdulo BAS, (de debe llamarse obligatoriamente Main); los mdulos los veremos en otra ocasin. Normalmente las aplicaciones suelen tener ms de un formulario y algn que otro mdulo. Pero tenga uno o ciento, siempre hay un nico punto de entrada (o de inicio). Por regla general suele ser un formulario. En nuestro ejemplo slo tenemos un form en el proyecto, por tanto no hay duda alguna de cual ser el punto de entrada del programa. Perdona si me extiendo en esto, pero tanto si t lo sabes como si no, creo que t ahora lo sabes... (es difcil esto de escribir una cosa para tanta gente con distintos niveles...) Cuando Windows inicia el programa, 'debe' cargar el formulario en la memoria. Y desde el momento que se prepara para hacerlo, ya est con los 'tiritos' y mandando 'mensajes' a la ventana (todo formulario es una ventana), pero no slo le est avisando a la nuestra que la accin ha empezado, sino que lo hace prcticamente a los cuatro vientos; si otra aplicacin quiere enterarse de lo que ocurre en Windows, slo tiene que conectarse a la 'mensajera' de ste entorno operativo y leer las 'noticias'... pero esto ya es complicarse la vida y todava no nos la vamos a complicar tanto... o ms... (pensar alguno despus de respirar aliviado...) Lo que ahora interesa es saber que el 'evento' Form_Load se produce cuando esta ventana pasa del anonimato a la vida pblica, aunque no la veamos, estar en la memoria de Windows y se producir el primer evento del que tenemos 'certeza', por tanto este es un buen sitio para poner cdigo de inicializacin. Realmente el Form_Load no es lo primero que puede ocurrir al iniciarse un formulario, pero por ahora vamos a pensar que s; porque sino esta entrega no la termino hasta despus del verano... que me gusta darle vueltas a las cosas!!! Ahora que se ha cargado el form en la memoria... que ocurrir? Pues, si el usuario se va a tomar unas caas: nada. Slo ocurrir algo cuando interactuemos con el form, es decir, le demos razones a Windows para 'pegar tiritos'. Nuestra aplicacin, (te recuerdo que tena, entre otras cosas, un textbox y un botn), esperar a que se produzca algunas de las acciones para las que est preparada. Y la pregunta es que es lo que puede ocurrir? Para saber 'todas' las cosas que pueden ocurrir en nuestra ventana, (no recuerdo si has pulsado F5 o no), finaliza el programa y muestra la ventana de cdigo. En la parte superior de la ventana hay dos listas desplegables, la de la izquierda tiene todos los controles, (en otra ocasin veremos que no siempre es as), que tenemos en el form, incluido ste, adems de uno especial que se llama General. Pulsa en la lista de la izquierda, para que se despliegue y te mostrar esto:
62
Estos son los cinco controles, incluyendo el form, que pueden recibir mensajes de Windows. Pulsa en cualquiera de ellos. En la lista de la derecha estn todos los procedimientos (eventos) soportados por el control de la izquierda. Cada control mostrar los eventos que VB y/o Windows permite que se produzcan. Estos ocurrirn por distintos motivos, cada uno tiene su 'tarea', nos pueden avisar de que se ha pulsado el ratn, que se acaba de pulsar una tecla, que se ha modificado lo que antes haba, etc. Una llamada a la precaucin. Los eventos son 'procedimientos' y no slo se puede llegar a ellos porque se produzca una accin del usuario o del propio Windows y si me apuras, incluso de Visual Basic... Nosotros podemos 'provocarlos' cmo? simplemente haciendo una llamada al SUB que queramos o actuando en el control de manera que ocurra alguno de los eventos soportados. Por ejemplo, en el Form_Load tenemos que se asignan cadenas vacas tanto al Label2 como al Text1: Label2 = "" Text1 = "" Cuando VB asigna una cadena vaca (o cualquier otra cosa), al Label2 est borrando el contenido del 'Caption' de esta etiqueta y asignando algo nuevo, es decir lo est cambiando. En nuestro programa no ocurre nada, salvo que se borra lo que all hubiera, pero realmente se est produciendo el evento Label2_Change, porque hemos cambiado el contenido. VB sabe que no hemos escrito cdigo para manejar esta situacin, as que... hace la vista gorda y simplemente cambia el contenido del Label2.Caption sin hacer nada ms. Pero al asignar una cadena vaca al Text1, tambin se borra el contenido y se produce un Change, slo que en este caso, al no tener Caption, lo que se borra es el Text; de todas formas sea como fuere, se produce el evento Text1_Change. Nuestro querido amigo Visual Basic, sabe que hemos escrito cdigo para este evento y sabe que tiene que hacer lo que all se dice... En este caso, nuestro cdigo se ejecuta, pero realmente no hace nada de inters o por lo menos nada que se pueda apreciar de forma visual. No voy a explicar para que sirve ese cdigo, porque ya lo hice en su da, lo que espero es que hoy lo entiendas mejor... Cmo? que no sabes de qu cdigo estoy hablando... pues del ejemplo de la segunda entrega, creo que ya lo dije al principio... a ver si prestamos ms atencin y dejamos de pensar en las vacaciones... estos nios! El cdigo interesante es el que se ejecuta cuando se pulsa en el botn: Label2 = "Hola " & Text1 Aqu se asigna al Caption del Label2 lo que hay despus del signo igual, en este caso acta 'casi' como una variable. Y ya sabrs que antes de asignar un valor a una variable, se procesa todo lo que est despus del signo igual, por tanto, Visual Basic tomar el contenido del Text1, es decir lo que se haya escrito y lo unir (&) a la palabra "Hola ", una vez hecho esto, lo almacena en el Caption del Label2. Y si en lugar de guardarlo en un control label, lo asignramos a una variable... y si en lugar de escribir nuestro nombre, escribisemos un nmero... y si la variable en la que guardamos ese nmero se llamara, por ejemplo, maxBucle o elMaximo... Pues que tendramos una parte resuelta de la tarea esa que puse como ejercicio en la entrega anterior. Pero, este form de la segunda entrega no nos sirve. Tendremos que cargar el de la vez pasada y aadirle un par de cajas de textos y un par de etiquetas, para que indique lo que se debe introducir en cada textbox; el aspecto sera este:
63
Pero nos encontramos con un problema: cmo puedo asignar un valor a maxBucle, si las constantes no pueden cambiar de valor? Fcil, convirtela en variable. Pero debes recordar la pista que di al final: "Las variables declaradas dentro de un procedimiento son solamente visible dentro de ese procedimiento". De este tipo de variables se dice que son locales. Alumno: Que significa esto? Guille: Que slo pueden usarse dentro del procedimiento en el que se han DIMensionado o declarado. A: Vale, "mu bonito" y que pasa? G: Esto... que no pueden usarse en otro sitio... Recuerdas la recomendacin de usar Option Explicit? Pues gracias a Option Explicit, se solucionan el 99% de los fallos 'involuntarios' con las variables... y no exagero!!! Es super-fcil escribir de forma incorrecta el nombre de una 'bariable' y no vengas con el cuento de que a t nunca te ocurre, porque no me lo voy a creer... bueno, de ti si, pero no todos son tan minuciosos como t... (para que nadie se sienta ofendido/a, quiero que veas en esto que acabo de poner... la intencin que tiene, es decir que me dirijo a ms de un "ti"... ya s que no eres tan torpe como para no haberlo 'captado', pero con esta aclaracin me quedo ms tranquilo.) Segn cmo y dnde se declare una variable, su 'visibilidad' o rea de cobertura, ser diferente... tambin se puede usar la palabra mbito... es que como en las emisoras de radio se habla de la cobertura... pues... Una variable puede tener estas coberturas: --Privada o Local a nivel de procedimiento (Sub, Function, etc.) --Privada o Local a nivel de mdulo (FRM, BAS, etc.) --Pblica o Global a nivel de aplicacin (en este tipo hay una forma especial de usar las variables que veremos en otra ocasin) Explicando los dos primeros puntos. Cuando declaramos o dimensionamos una variable 'dentro de' un procedimiento, sta slo es visible en ese procedimiento; fuera de l no es conocida y cualquier uso que se intente hacer de ella, producir un error... si has sido obediente y has usado el Option Explicit. En caso de que no hayas puesto la 'obligacin' de declarar todas las variables, te llevars una sorpresa de que no tiene el valor esperado. A: JA! G: No te lo crees? Vale. Vamos a comprobarlo. Abre un proyecto nuevo, pon un textbox y un botn.. Abre la ventana de cdigo, borra el Option Explicit.
64
En el Form_Load haz esta asignacin: Incredulo = "No me lo creo" En el Command1_Click escribe esto otro: Text1 = Incredulo Pulsa F5 y haz click en el botn... Que ha pasado? (Tengo que comprobarlo, para no meter la pata, pero se supone que el texto se borrar sin poner nada...) Bien, eso ocurre porque la variable usada en el Form_Load no tiene nada que ver con la usada en el Command1_Click. Con esto comprobamos o demostramos que podemos tener variables diferentes con el mismo nombre. La nica condicin es que no pueden estar juntas, aunque hay un truco para juntarlas sin que ellas se enteren... En este ltimo ejemplo, nuestra intencin es tener una variable que sea 'conocida' en todo el form. Cuando necesitemos variables con un mbito a nivel de mdulo, tenemos que declararla o dimensionarla en la seccin de las declaraciones generales; ya sabes, en la lista izquierda de la ventana de cdigo seleccionas General y en la de la derecha Declarations ( Declaraciones). Muestra la ventana de cdigo y en General/Declaraciones escribe: Option Explict 'retornamos a las buenas costumbres Dim Incredulo As String Pulsa F5 para ejecutar el programa, pulsa en el botn y... AHORA SI! Tenemos una variable que puede ser 'vista' en todo el form. Ya puedes usar 'Incredulo' donde quieras, ahora siempre ser la misma variable y contendr lo ltimo que se le haya asignado. A partir de la versin 4 del VB, entra en juego una nueva palabra, 'Private', que suele usarse en las declaraciones de las variables a nivel de mdulo, de esta forma es ms fcil entender la intencin de la misma; por tanto la declaracin anterior podemos hacerla tambin as: Private Incredulo As String Hay veces, sobre todo si ya has programado antes en MS-DOS, que usamos variables como a, b, c, i, j, k...etc., (al menos yo estoy acostumbrado a llamar i, j, k a las variables que uso en los bucles FOR), cuando hago un bucle dentro de un procedimiento uso i como variable ndice, (o variable 'contadora'), pero que ocurre si esta 'costumbre' quiero aplicarla en varios procedimientos? Pues que dimensiono una variable i en cada uno de ellos y aqu no ha pasado nada!!! Usar el mismo nombre de variable en distintos procedimientos Como indica el encabezado de este nuevo prrafo, cosa que ya he comentado antes, podemos tener distintas variables con el mismo nombre en distintos procedimientos; para ello, slo hay que dimensionarlas y el VB las almacenar en distintas posiciones de la memoria para que no se 'mezclen'. En la entrega anterior, tenamos un procedimiento llamado Contar que se usaba para eso, contar... En este ejemplo vamos a usar un sub tambin llamado contar, para ver en accin todo esto que estoy diciendo. Sitate en la parte General/Declarations de la ventana de cdigo y escribe o "copia/pega" lo siguiente: Private Sub Contar() Dim i As Integer
65
For i = 1 To 2 Print "i en contar= "; i Next End Sub Ahora en el Form_Load, escribe esto otro: Dim i As Integer Show For i = 1 To 3 Print "i en el Form_Load= "; i Contar Next Ejecuta el programa y ... Has visto lo que ocurre... A pesar de que ambas variables tienen el mismo nombre, son diferentes. La variable i del Form_Load no es la misma que la variable i de Contar. Cuando usamos variables locales es como si cambisemos el nombre y se llamaran NombreProcedimiento_Variable. S que puede ser una forma 'rebuscada' de explicarlo, pero as te haces una idea. Todas las variables declaradas en un procedimiento, slo son visibles en ese procedimiento. Si has usado QuickBasic o el compilador Basic de Microsoft (que usaba el QuickBasic Extended QBX), esto ya exista y tambin exista la forma de hacer que una variable declarada en un procedimiento, fuese visible fuera de l; para ello declarabas la variable como Shared (compartida); pero en VB eso NO EXISTE. La nica forma de compartir una variable es declarndola en la seccin General de las declaraciones. Prueba ahora esto. Sustituye el procedimiento Contar por este otro: Private Sub Contar(j As Integer) Dim i As Integer For i = 1 To j Print "i en contar= "; i Next End Sub Aqu hacemos que Contar reciba un parmetro y el valor que recibe lo usamos como el lmite final del bucle For, es decir contar desde UNO hasta el valor de j. Sustituye la llamada a Contar del Form_Load por esta: Contar i Le damos a Contar el parmetro i. Por tanto cada vez que se llame a este procedimiento le estamos diciendo que i es el valor mximo que tomar el bucle For que tiene dentro. Cmo reaccionar? Se confundir? ...
66
No, no voy a dejarlo para la siguiente entrega, es tan obvio que lo voy a explicar ahora mismo: Al procedimiento Contar le da igual que se use una variable llamada i o cualquier otra, incluso un nmero. Lo nico que necesita y espera, es recibir un valor numrico (del tipo Integer) y lo asignar a la variable j. Por tanto no ocurrir nada extrao. Ejecuta el programa y fjate en lo que ocurre. S que lo has deducido, eso est bien... vas aprendiendo... 8-) Cmo? Que t an no lo has captado? Pues dmelo... (mejor no me lo digas y repsalo de nuevo...) Otra cosa sera pretender usar una variable declarada a nivel de mdulo, dentro del procedimiento, que tuviese el mismo nombre. Si te has quedado 'con la copla', tu mismo sabrs la respuesta... Efectivamente! Si dentro de un procedimiento tenemos una variable dimensionada con el mismo nombre de una declarada a nivel de mdulo o a nivel global, (para usarla en cualquier sitio de la aplicacin), tendr 'preferencia' la variable local... sta 'tapar', ocultar o como prefieras decirlo a cualquier otra variable del mismo nombre... En la prxima entrega veremos ms casos y cosas de las variables. Comprobaremos cmo usarlas a nivel Global. Pero por ahora vamos a terminar con el programa que tenamos planteado en la entrega anterior: Aunque realmente deberas saber cmo solucionarlo... Lo que seguramente no sabrs, es cmo hacer que estas variables tomen el valor... De acuerdo, lo explicar. Carga el ejemplo de la cuarta entrega. Hay varias soluciones a este problema; una sera usar variables locales, esta decisin no 'justifica' el 'pedazo' de pista que te di... pero esto es lo que hay. La lnea Const minBucle = 1, maxBucle = 10. Debe quedar as: Const minBucle = 1 Dim maxBucle As Integer Esto har que maxBucle deje de ser una constante y pase a ser una variable, con lo cual podremos asignarle cualquier valor. Pero, cmo le asignamos el valor? Vamos a dar por sentado que lo que se escriba en el Text1 ser el valor que debe tener maxBucle; entonces lo nico que haremos es asignar a maxBucle lo que se escriba en el Text1... Vale, pero dnde? Pues... por ejemplo, despus de la declaracin, as que en la siguiente lnea al Dim maxBucle... escribe los siguiente: maxBucle = Text1 Esto en teora no dara problemas, al menos en condiciones normales, ya que el contenido de un textbox es del tipo Variant y ya vimos que un Variant puede almacenar cualquier cosa, por tanto si es un nmero 'intentar' convertir al tipo de la variable que recibir el valor. Esto no siempre funciona, sobre todo si el contenido del Text1 no es numrico. Por ahora vamos a hacerlo simple, si el usuario (en este caso t), escribe algo no numrico lo vamos a considerar CERO... o casi... Cambia la asignacin anterior por esta otra... ALTO !!! Antes de hacerlo, prubalo e intenta escribir una palabra en lugar de un nmero... que ocurre?
67
Pues que VB no se complica la vida y te dice que 'nones'... (realmente dice Type Mismatch... Error de Tipos, es decir que lo que has escrito no es capaz de convertirlo a Integer)... as que escribe esto otro: maxBucle = Val(Text1) Con esto lo que hacemos es convertir el contenido del Text1 a un VALor numrico y lo asignamos en la variable... Problemas? Que el valor sea mayor del que se puede guardar en un Integer, pero eso ya no es asunto de esta entrega... Ahora nos queda convertir elMaximo en variable y asignarle el valor que hay en el Text2. Efectivamente! hacemos lo mismo, slo que esta vez dentro del procedimiento Contar, por tanto la declaracin Const elMaximo = 1000&, la tienes que quitar y poner estas dos lneas: Dim elMaximo As Integer elMaximo = Val(Text2) Aqu el nico inconveniente es que esta asignacin se hace cada vez que se entra en este procedimiento... y eso, amigo mo, no es un buen estilo de programacin... Sobrecargamos de forma innecesaria al procesador... ten en cuenta que la conversin a nmero y la asignacin se ejecuta cada vez que se entra en Contar!!! Lo mejor para este caso sera declarar elMaximo como variable a nivel de mdulo. Por tanto, borra el Dim elMaximo... del sub Contar y colcalo en la parte de las declaraciones generales del form. Ahora... dnde asignamos el valor para evitar la sobre-carga? Ya que tenemos la variable a nivel de mdulo, sta ser 'vista' en todos los procedimientos del formulario, por tanto lo lgico sera hacerlo en el Command1_Click, ya que cuando nos interesa a nosotros saber cuanto tenemos que contar es precisamente cuando pulsamos en el botn... Pero... dnde exactamente?, despus de Contando = 1 Bien, ahora est la cosa mejor... haz tus pruebas y si an no lo tienes claro... pregntame, (te digo lo de antes: mejor no me preguntes y repsalo todo de nuevo... ) Prcticas y ejercicios Quieres algo para practicar? Este ejercicio se lo pona a mis alumnos, cuando daba clases de BASIC, hace ya unos 10 aos o ms... y consista en pedir el nombre, pedir la edad y mostrar el nombre tantas veces como aos tengamos... Claro que con el BASIC del MS-DOS era ms directo y se sabia cuando se deba empezar a mostrar el nombre, para solventar esto, se mostrar el nombre 'edad' veces cuando se pulse en un botn. El aspecto del form sera algo as:
No creo que sea complicado, as que vamos a complicarlo un poco ms: Mostrar el nombre 'edad' veces, dentro de un label, para ello el label deber ocupar la parte izquierda del form.
68
Y una tercera versin, sera lo mismo que esta ltima, pero cada vez que se muestre el nombre se haga en una lnea diferente. La pista: En la segunda entrega vimos de pasada el CHR. Pues decirte que si aadimos a una variable un CHR(13), lo que hacemos es aadirle un retorno de carro, es decir lo que venga despus se mostrar en la siguiente lnea... siempre que se 'concatene'. Tambin existe una constante definida en VB4 o superior que es vbCrLf, esto es un retorno de carro (Cr) y un cambio de lnea (Lf) Que te diviertas!
Como ya es costumbre al final de cada entrega, espero tus comentarios y opiniones sobre el curso, adems de aceptar crticas, que seguro que algo habr que no entiendes o que no te gusta como est explicado. Con esto acaba el tema por ahora... no, no se acaba el curso, no te alarmes; la prxima entrega ser... pues, es que... yo creo que... si no... un da de estos, pero seguro que en el caluroso mes de Agosto. Nos vemos.
He preferido poner la solucin a los ejercicios en una pgina separada, as creo que ser mejor. Solucin al primero, preguntar el nombre, preguntar la edad y mostrar el nombre "edad" veces: Tenemos dos TextBoxes: Text1 y Text2, un botn: Command1, el cdigo sera:
Private Sub Command1_Click() Dim i As Integer Dim j As Integer Dim Nombre As String j = Val(Text2) Nombre = Text1 For i = 1 To j Print Nombre
69
El segundo es un poco ms complicado, pero no tanto, espero. Slo hay que asignar el nombre al Label, suponiendo que fuese Label3, sera algo como esto: Private Sub Command1_Click() Dim i As Integer Dim j As Integer Dim Nombre As String j = Val(Text2) Nombre = Text1 For i = 1 To j Label3 = Label3 & Nombre Next End Sub Por ltimo, para que cada nombre se muestre en una lnea diferente, hay que aadirle a continuacin un retorno de carro y cambio de lnea, en VB4 hay una constante definida para ello: vbCrLf, en el VB3 habra que declararla de esta forma: Dim vbCrLf As String vbCrLf = Chr$(13) & Chr$(10) Este sera el cdigo: Private Sub Command1_Click() Dim i As Integer Dim j As Integer Dim Nombre As String j = Val(Text2) Nombre = Text1 For i = 1 To j Label3 = Label3 & Nombre & vbCrLf Next End Sub Espero que te hayas apuntado un 10, pero si no has logrado hacerlos bien, no te preocupes, poco a poco irs quedndote con la copla.
70
En la otra forma de usarlo, las instrucciones se ponen en las siguientes lneas y podemos escribir tantas lneas como queramos. Todas se intentarn procesar... Esto para el caso de que al evaluar la expresin se cumpla como verdadero. En las ocasiones en las que no se cumple la expresin se har lo siguiente, segn la forma de usar el IF/THEN: --En el primer mtodo, se procesan las instrucciones que hay en la siguiente lnea y se contina a partir de ah el programa. --En el segundo caso, se busca END IF y se contina por la siguiente lnea...
71
Ms adelante, en otra entrega, veremos otras formas de usar IF...THEN... Ahora voy a explicar un poco esto de la evaluacin de las expresiones. El IF espera encontrar un valor CERO (FALSO) o un valor distinto de cero (por extensin VERDADERO) En If Contando = 0 Then Exit For, la expresin es Contando = 0, aqu no se est usando la asignacin, sino que se est evaluando si el contenido de la variable Contando es igual a cero; en caso de que sea cierto, es decir que Contando valga cero, (algunas veces me maravillo de mi lgica aplastante...), se devuelve un valor Verdadero (TRUE). Por otra parte si Contando NO vale cero, se devolver un valor Falso (FALSE, CERO). En otros lenguajes se usa un smbolo distinto para el igual, segn sea una asignacin o una comparacin. En Pascal (Delphi) la asignacin es := y la comparacin es = En C, C++, la asignacin es = y la comparacin == (esto del == puede 'sonarte' si has hecho algo con JavaScript) En el caso de If Contando Then, al no haber una expresin que evaluar, lo que se comprueba es si el contenido de la variable es cero o no es cero, en caso de que sea NO CERO, se cumple la condicin y se procesa todo lo que viene a continuacin; pero si la variable vale CERO, el IF lo interpretar como FALSO y pasa a ejecutar lo que haya a continuacin de End If. Por tanto, (volviendo por fin al listado), la primera vez que se pulse en el botn Command1 y se compruebe esto:
If Contando Then
No se cumplir la condicin, ya que el contenido de Contando es cero, y se pasar a lo que hay despus del End If, es decir a: Contando = 1 y se contina con el programa como antes de aadir todas estas cosas... Pero, si pulsas de nuevo en el botn, se vuelve a procesar lo que hay dentro de Command1_Click, (esto es posible porque al usar DoEvents, permitimos que Windows siga recibiendo y procesando 'tiritos'), pero cuando entramos por segunda vez, Contando vale 1 (uno), (ya que al ser una variable a nivel de mdulo conserva el ltimo valor que hayamos asignado), y esta vez al evaluar: If Contando Then si se cumple, as que se procesan las lneas que vienen a continuacin... entre ellas est Contando = 0 y DoEvents que vuelve a permitir a Windows que las otras cosas que antes de pulsar en el botn continen ejecutndose, (esto se hace de forma asncrona, es decir, Windows se da por enterado y da los avisos (mensajes) cuando quiera), pero contina con la siguiente instruccin sin esperar a que esos mensajes se terminen de procesar, entonces se encuentra con el Exit Sub que lo manda fuera del evento... (Si no te ha quedado demasiado claro, no te preocupes veremos ms de esto a lo largo del curso...) En caso de que hayamos pulsado el botn cuando an no haba terminado todo lo que este Sub estaba haciendo, se contina igual que si se hubiese hecho PAUSA y despus PLAY. Con la salvedad de que si VB se encuentra con alguno de los If Contando = 0 Then Exit Sub, dejar de procesar y se saldr del procedimiento... Esta no es la mejor forma de cancelar una tarea ya iniciada, pero algo es algo... Tambin es posible que al pulsar por segunda vez en el botn, se estuviese dentro del Sub
72
Contar, en este caso, tambin se evaluara la expresin y se saldra del procedimiento... as que tambin dejara de procesarse todo. Cuando pulsemos por tercera vez... iniciaremos el proceso de nuevo...
Bueno, ahora si que puedo dar por terminada la Quinta Entrega. Nos vemos.
Solucin de los ejercicios de la Quinta Entrega Pulsa este link para ver la solucin de los ejercicios de la quinta entrega (aunque el nombre la pgina sea: basico06_sol) Como hemos visto en el apndice de la entrega anterior, la instruccin IF... THEN... nos permite tomar decisiones segn el valor de una variable o el resultado de una expresin. En esta entrega veremos como sacarle rendimiento a esta instruccin. Pero antes de entrar en detalles, veamos cmo podemos decirle al Basic que haga las cosas. En realidad vamos a ver la forma en que se le puede decir que las haga... Forma de especificar las instrucciones en Visual Basic Las instrucciones en Basic no tienen porqu estar cada una en una lnea. Se pueden escribir varias instrucciones en la misma lnea, pero separando cada una de ellas con el signo : (dos puntos). Cuando VB encuentra los dos puntos, deja de 'interpretar' la instruccin y pasa a la accin, una vez traducido a su lenguaje interno, toma lo que hay despus del signo : y sigue su camino en busca de ms instrucciones o el final de la lnea. Vemoslo de forma prctica: Nombre = "Pepe" : Print Nombre Esta lnea tiene dos instrucciones: una asignacin y una instruccin Print. Podemos poner cuantas instrucciones queramos, separadas con los dos puntos. Pero, (siempre hay un pero), si una de las instrucciones es el IF/THEN la cosa puede cambiar...
73
Ya vimos que IF comprueba la expresin que viene a continuacin, si es cierta, ENTONCES procesa lo que haya despus de THEN. En caso de ser en la misma lnea, interpretar todas las instrucciones que estn a continuacin; en caso de ser un bloque IF... THEN... END IF, ejecutar todo lo que est dentro de ese bloque. Ahora bien, si la expresin es falsa pasa a la siguiente lnea, tanto si es o no un bloque. En el caso del bloque la siguiente lnea a interpretar ser la que est despus de END IF. En los tiempos del BASIC interpretado de MS-DOS, era habitual encontrar las lneas con varias instrucciones separadas por dos puntos. En mi caso, cuando empec a usar el QuickBasic 2.0 y al poder usar bloques IF... THEN... END IF, fui dejando a un lado el "mogolln" de instrucciones en la misma lnea... Ahora, salvo en contadas excepciones, escribo cada instruccin en una lnea. Y te recomiendo que hagas lo mismo, tu cdigo ganar en claridad y si alguna vez vuelves a verlo, te ser ms fcil de entender. Despus de este pequeo respiro, veamos cmo estara formada una lnea de VB para usar con un IF... THEN... [instrucciones:] IF <expresin> THEN <instrucciones si es cierto [:ms instrucciones...]> A continuacin de THEN podemos incluir cuantas instrucciones queramos, separadas por dos puntos. Estas slo se ejecutarn cuando la expresin sea cierta. Si el resultado de la expresin es falso, se obvia 'todo' lo que hay despus de THEN y se pasa a la siguiente lnea. Espero que lo hayas asimilado y que no te indigestes con lo siguiente... Pero, (...), existe otra instruccin que PUEDE acompaar al IF... THEN... y es para los casos en los cuales el resultado de la expresin sea FALSO. Si, ya s que dije que cuando es falso se pasa a la siguiente lnea, pero eso es cuando no se usa la clusula ELSE. Con sta, la definicin de la instruccin "tomadora de decisiones" quedara as: IF <expresin> THEN <si se cumple> ELSE <si no se cumple> Tanto en <si se cumple> como en <si no se cumple> pondremos tantas instrucciones como queramos, (separadas por dos puntos). Pero no te recomiendo que lo hagas, es preferible, al menos para darle "claridad" a nuestro cdigo, usar el bloque: IF <expresin> THEN <si se cumple> ELSE <si no se cumple> END IF S que esto puede ocupar ms lneas de cdigo, pero nuestro "coco" lo agradecer, ya que es ms fcil de comprender, sino veamos un ejemplo:
74
IF numero > limite THEN Print "tu nmero es grande" ELSE Print "OK, McKey!" END IF Ahora vemoslo en una sla lnea: IF numero > limite THEN Print "tu nmero es grande" ELSE Print "OK, McKey!" En este ejemplo, an queda claro, pero lo podramos complicar con ms instrucciones para ambos casos, es decir, para cuando la expresin es cierta y tambin cuando es falsa. En los tiempos del BASIC que venan incorporados con los ordenadores, cada lnea del programa haba que numerarla, ya que todo lo que se escriba sin nmero de lnea, se ejecutaba inmediatamente; al igual que ocurre con lo que se escribe en la ventana Inmediate del Visual Basic. Los nmeros de lneas se usaban, adems de porque era obligatorio, para cambiar el orden de ejecucin, sobre todo despus de una comparacin. De esta forma, an sin tener bloques IF/THEN/ELSE/END IF, se podan simular. Cmo se lograba? Usando una instruccin que muchos creen que es "indecente, antisocial, etc."
75
30 A = A + 1 40 IF A <= 10 THEN GOTO 20 'Con el Commodore este programa se sola escribir as: 10 A=1 20 PRINTA:A=A+1:IFA<=10GOTO20 'Sin ESPACIOS NI NADA... TODO APELMAZADO... que ms daba usar el GOTO? Imagine there's no heaven... (es que est sonando J. Lennon... un momento...) En este ejemplo, es obvio que podramos sustituirlo con: 10 For A = 1 To 10 20 30 Next El NEXT hace lo mismo que la asignacin y la comparacin. Pero hay otras maneras, para ello existe una serie de instrucciones que funcionan de manera similar, veamos otros ejemplos con ms instrucciones para hacer bucles, seguir usando los nmeros de lnea por aquello de la "nostalgia", pero salvo en el primer ejemplo, en los dems no es necesario. 10 A = 1 20 While A <= 10 30 40 Print A A = A + 1 Print A
50 Wend El WHILE/WEND ya casi ni se usa, porque tienen un sustituto ms verstil, ahora lo veremos, pero el uso sera: WHILE <expresin> <instrucciones si se cumple> WEND Es decir, MIENTRAS la expresin sea cierta, repite todo lo que haya hasta el WEND. Por supuesto podemos ponerlo todo en una sola lnea: 10 A = 1 : While A <= 10 : Print A : A = A + 1 : Wend Pero esto tampoco es recomendable, queda algo "difuso"... El WEND funciona como IF A <=10 THEN GOTO xxx, con la ventaja que evitamos el GOTO y lo que hace es comprobar si la expresin que hay tras el WHILE es cierta o no, en caso de que sea verdadero el resultado, (ya sabes, distinto de CERO), se ejecutar todo lo que
76
hay entre WHILE y WEND. Estas instrucciones ya existan en el GWBASIC (el basic de los PCs) Hay que tener cuidado con esto de que la expresiones evalan el cero como FALSO y cualquier otro valor como VERDADERO, por ejemplo: A = 1 While A Print A A = A + 1 Wend Wend o While 1 Print A A = A + 1
En ambos casos el bucle sera "infinito", realmente se detendra en un momento dado, ya que llegara a "desbordarse" el valor mximo y en ese momento el programa acabara por producirse un error... pero prueba esto y vers: While 1 Print "Hola Mundo" Wend Esto nunca finalizar, salvo que pulses CRTL+BEAK (o INTERrumpir), para detener el programa.
Ms instrucciones para hacer bucles Con el QuickBasic 3.0, (yo no llegu a tenerlo, pero creo que fue ah dnde se introdujo), entr en funcionamiento una nueva forma de hacer bucles: DO... LOOP El ltimo ejemplo podramos escribirlo as: Do Print "Hola Mundo" Loop Pero la "gracia" de este tipo de bucle es que podemos usar dos nuevas clusulas para evaluar cuanto durar el bucle. La primera es WHILE y funciona igual que en WHILE/WEND A = 1 Do While A <= 10 Print A A = A + 1
77
Loop La ventaja es que WHILE se puede poner tanto despus de DO como a continuacin de LOOP. Si lo usamos como DO WHILE <expresin>... la forma de actuar es igual que WHILE/WEND, es decir, se evala la expresin y slo en caso de que sea cierta, se ejecuta lo que est dentro del bucle, es decir entre DO y LOOP. Pero si evaluamos la expresin en LOOP, se ejecutar todo lo que hay tras el DO, como mnimo una vez y se seguir repitiendo si se cumple la condicin. De esta forma, como he dicho, se ejecutar el contenido del bucle, como mnimo una vez. Veamos un ejemplo: A = 1 Do Print A A = A + 1 Loop While A <= 10 El problema es que si A, en lugar de valer 1, tiene un valor superior a 10, tambin se ejecutar, al menos, una vez. A = 11 : Do : Print A: A = A + 1: Loop While A <= 10 Que mal queda en una sola lnea, verdad? Pero con DO/LOOP tambin puede usarse UNTIL, en este caso, el bucle se repetir HASTA que se cumpla la expresin A = 1 Do Until A > 10 Print A A = A + 1 Loop Fjate que la expresin ha cambiado de <= (menor o igual) a > (mayor), ya que ahora se evala de esta forma: Hasta que A sea mayor que diez, REPITE todo hasta LOOP. Por supuesto tambin podemos usarlo despus del LOOP: A = 1 Do Print A A = A + 1 Loop Until A > 10
78
Aqu hago la misma aclaracin que antes, si el valor inicial de A es ms de 10 se ejecutar como mnimo una vez. Realmente para contar de forma secuencial y prcticamente para casi todo tipo de bucle, no es necesario hacer los bucles con DO/LOOP, ya que FOR/MEXT lo hace bastante bien. Sigamos con estos bucles, pero en lugar de contar de menor a mayor, vamos a contar " pa trs", es decir de mayor a menor... quin sabe, lo mismo necesitas hacer un programa que cuente al revs... A = 10 Do While A >= 1 Print A A = A - 1 Loop Cuando se muestre el 1, A=A-1 se convertir en A = 0 y la comparacin A >= 1 no se cumplir, por tanto dejar de repetirse el bucle, pero esto tambin se puede hacer con FOR/NEXT: For A = 10 To 1 Print A Next El nico inconveniente es que NO SE REPITE NI UNA VEZ... Por qu? Porque si no se le indica lo contrario, FOR/NEXT siempre cuenta de forma ascendente y cuando ve que A debe ir de 10 hasta 1 y que eso no es ascendente... pasa de ejecutar el bucle. Esto es una cosa a tener en cuenta, FOR siempre evala los valores del bucle que tiene que hacer y si no est entre los valores que debe, no se ejecuta ni una sola vez. En este caso debe empezar por DIEZ y llegar hasta UNO, as que se da cuenta de que ya ha terminado... incluso sin haber empezado... que listo es el FOR! Para que el FOR cuente hacia atrs, necesitamos un nuevo peldao (esto en ingls quedara "clavado"), en la escala evolutiva del FOR/NEXT (ah queda eso!!!) Ya sin coas, se necesita la palabra STEP para indicarle que no queremos ir de uno en uno de forma ascendente, en nuestro ejemplo lo usaramos as: For A = 10 To 1 Step -1 Print A Next De esta forma contar desde 10 hasta 1, restando uno en cada repeticin. Pero, que hacer si queremos usar otros valores? Simplemente ponerlo despus de STEP, por ejemplo: For A = 10 To 1 Step -1 For A = 1 To 10 Step 3, etc, etc.
79
Insisto, todo esto est muy bien, pero en la prctica usaremos otras cosas adems de contar de forma lineal, con incrementos o sin ellos... habr veces que queramos salir de un bucle. Ya lo hemos visto, por ejemplo Exit Sub sala del procedimiento, recuerdas el Exit For? Para salir de los bucles podremos Exit y a continuacin For, Do, etc. Pero NO podremos salir de un bucle WHILE/WEND. Ya veremos ejemplos para estos casos y otros que surgirn ms adelante. Bien, creo que ya hemos dado demasiadas vueltas con tanto bucle, para terminar: los ejercicios esos que tanto os gustan. 1.) Haz un programa que al pulsar en un botn (CommandButton) haga un bucle entre dos valores que habrs introducido por medio de dos cajas de textos (una para el inicio y otra para el final) 2.) Otro que tenga una tercera caja de textos y que el valor introducido en ella sea el incremento. 3.) Como tercer ejercicio, una vez terminado el bucle, que muestre en un Label las veces que se ha repetido. Por ejemplo, si hacemos un bucle del uno al diez de uno en uno, se repetir diez veces; pero si lo hacemos de dos en dos, se repetir cinco veces... Como pista, decirte que no tendrs que hacer ninguna comparacin para obtener el resultado, la solucin es tan SIMPLE que seguramente la descartars "porque no puede ser tan fcil" Por supuesto, me gustara que los bucles los hicieras tanto con FOR/NEXT y DO/LOOP. Ya puestos, podras hacerlo con el WHILE/WEND e incluso con el GOTO... Feliz programacin! Y seguimos con la costumbre esta de pedirte tus comentarios sobre el curso. Sobre todo necesito saber si realmente est claro y entendible... ya que si no no valdr para mucho el montn de horas que le dedico a cada una de las entregas... si, aunque no te lo creas, le dedico ms de 5 horas a cada entrega... Si no pasa nada raro, seguramente en este mismo mes habr una nueva entrega. Nos vemos. Guillermo
80
el bus, el metro, cuando llames a un 906 y sobre todo cuando te tomes unas copas con los amigos... a ver si al final entre todos me compris una casita de verdad... Vamos a ver las soluciones de los ejercicios de la sexta entrega, para ver que nivel llevas, porque me imagino que te has enterado de todo lo que pona, ya que ltimamente no recibo preguntas ni dudas ni quejas porque algo estuviera mal... salvo algn que otro despistadillo que ha empezado por la primera entrega, hace los ejercicios y me "mailea" dicindome que no le muestra nada... y eso que lo he puesto al final de la primera entrega y en letra GORDA pa que se vea bien... Esto pasa por bajarse las pginas, guardarlas en el disco duro y despus leerla al montn de das... Bueno, despus del rapapolvo... todo para que se te olvide que esta entrega iba a estar el mes pasado... SOLUCIONES A LOS EJERCICIOS DE LA SEXTA ENTREGA: 1.) Private Sub Command1_Click() Dim A As Integer Dim B As Integer Dim i As Integer A = Val(Text1) B = Val(Text2) For i = A To B Print i Next Show End Sub ' El bucle tambin se puede hacer de esta forma: i = A Do While i <= B Print i i = i + 1 Loop ' Y de esta tambin i = A While i <= B Print i i = i + 1 Wend 2.)
81
Private Sub Command1_Click() Dim A As Integer Dim B As Integer Dim C As Integer Dim i As Integer A = Val(Text1) B = Val(Text2) C = Val(Text3) For i = A To B Step C Print i Next Show End Sub ' Otra forma de solucionarlo i = A Do While i <= B Print i i = i + C Wend 3.) ' Aadir estas lneas: Dim D As Integer '... '...For D = D +1 '... Next Label1 = "Nmero de repeticiones: " & D Y eso es todo, no pongo otras posibles formas de obtener los resultados, porque tu mismo habrs comprobado si estn bien o no.
Ahora empezamos con la entrega real, es decir la sptima. Ya disponemos de instrucciones suficientes para empezar a "profundizar" en las cosas ms difciles... o casi. Ya sabes que hay que usar Option Explicit en todos los mdulos de cdigo... Esto no es obligatorio, pero si no quieres que perdamos la amistad, usalo. Gracias. Tambin cmo usar las variables y los diferentes tipos, puedes hacer bucles, tomar
82
decisiones, realmente es ms correcto decir: hacer que VB tome decisiones, ya sabes cmo pedir datos al usuario y tambin cmo mostrarlos. Sabes crear tus propias instrucciones... Jo! Cuanto sabes! Me tienes "anonadado" Pero an no sabes una cosa: cmo crear Funciones Qu son las funciones? Para simplificar, te dir que una funcin es un procedimiento (como el Sub), que puede devolver un valor. Normalmente se usa para que lo devuelva, aunque veremos que no siempre es necesario; de todas formas, cuando necesites un procedimiento que no necesite devolver un valor, usa el Sub. Cmo declarar/codificar una funcin? mbito Function Nombre ([parmetros]) As Tipo Dnde mbito puede ser Public o Private, dependiendo de la "cobertura" o visibilidad. Ya sabes, Private slo es visible en el propio mdulo en el que se encuentra definido y Public es en todo el proyecto, incluso fuera de l... Los parmetros son los valores que esta funcin puede necesitar para "cumplir" su misin. stos son opcionales, es decir puede tenerlos o no, incluso si tiene, puede ser uno o varios, para declarar varios parmetros hay que separarlos por comas... Los corchetes, que se suelen usar en los manuales, la ayuda, etc, sirven para indicar que son opcionales, pero no se te ocurra ponerlos en ninguna funcin!, ya que no forman parte del lenguaje Basic... El tipo es para saber que tipo de dato devolver la funcin. El valor devuelto por una funcin lo podemos usar para asignarlo a una variable: a = MiFuncin() o incluirlo en una expresin: If MiFuncin() + 15 > LoQueSea Then La ventaja real frente a los Subs es la posibilidad de devolver un valor, imaginate que quieres crearte tu propio procedimiento para averiguar si un determinado archivo existe... si lo hace como funcin podras devolver un valor cero para indicar que no existe el archivo y un valor distinto de cero indicara que el archivo en cuestin existe. Por tanto, podramos usarlo de esta forma: If Existe(NombreArchivo) Then ... Ya que estamos puestos, veamos cmo hacer esta funcin de forma simple y as te explico una cosa muy importante de toda funcin: poder devolver el valor! Public Function Existe(sArchivo As String) As Integer Existe = Len(Dir$(sArchivo)) End Function Para devolver un valor, ste se asigna a una variable que tiene el msmo nombre que la funcin. Ya vimos que LEN devuelve el nmero de caracteres de la cadena que ponemos entre los parntesis; si, LEN tambin es una funcin, pero incluida en el propio Visual Basic. Dir$ es otra funcin del VB que devuelve el nombre de un archivo, (slo el nombre), o una cadena vaca, en caso de que no haya ninguno en la direccin pasada por el parmetro que se ha usado. Para saber ms de esta funcin, as como de otras, puedes buscar en la ayuda... Hemos visto que en las expresiones usamos unos operadores para hacer las comparaciones, aqu tienes los seis posibles: = igual, > mayor que, < menor que, >= mayor o igual, <= menor o igual y <> distinto. Recuerda que el signo igual funciona de forma diferente, segn se use en un expresin o
83
en una asignacin. Pero adems de estos signos, podemos usar en nuestras expresiones unos operadores lgicos, estos son: AND, OR y NOT Podramos desear hacer una comparacin y comprobar si varias cosas se cumplen, por ejemplo: If A>10 And Len(Nombre)<>0 Then ... Para que esta expresin se cumpla, deben ser ciertas las dos condiciones, es decir que A sea mayor que 10 "y" que la longitud de Nombre sea distinta de cero. Podemos usar tantas condiciones como queramos, sin pasarnos demasiado para que la cosa funciones mejor. Aqu las dos condiciones deben cumplirse, pero en este otro ejemplo: If A>10 Or Len(Nombre)<>0 Then ... cumplindose cualquiera de las dos, se acepatara como vlido. Cuando el If se procesa, se toma todo lo que hay entre IF y THEN y se considera como una sla expresin. Si quieres puedes asignar a una variable el resultado de una expresin, el valor devuelto siempre ser 0 (cero) en caso de que no se cumpla todo lo expuesto y -1 cuando sea cierta. Para manejar estos valores de Cierto (-1) y Falso (0), Visual Basic tiene un tipo especial llamado Boolean, los valores que puede aceptar una variable de este tipo son: True (verdadero) y False (falso). Veamos un ejemplo: Dim b As Boolean, i As Integer Dim a As Integer, Nombre As String Show a = 15 Nombre = "Guille" b = (a > 10 And Len(Nombre) <> 0) i = (a > 10 Or Len(Nombre) <> 0) Print "Valor de B "; b Print "Valor de i "; i Te has fijado en el detalle? B vale True (verdadero), sin embargo i vale -1. Pero para el caso los dos valores significan lo mismo: si estas expresiones se hubiesen usado en una comparacin, las dos hubiesen devuelto un valor verdadero. El tercer operador lgico (Not) sirve para negar algo, es decir invertir el valor contenido en una variable, o casi... If Not A>10 Then ... Parece lgico el resultado, verdad?, si no se cumple que A sea mayor que diez, ser cierto; comprobmoslo: Dim A As Integer 'recuedas que tienes que poner Show?
84
A = 5 If Not A > 10 Then Print A; "no es mayor que 10" End If BINGO! Funciona! "Mu" bonito, pero... que es lo que ocurre? Se toma A > 10 y se procesa, como A no es mayor que 10, se devuelve un valor falso (0) y despus se hace Not 0 que da como resultado -1 (verdadero), por tanto se cumple la condicin. Ya vimos que el valor devuelto por una variable se puede usar en una comparacin, si es cero se toma como falso y si es distinto de cero, como verdadero. Prueba ahora esto: A = 0 If Not A Then Print A; "es cero" Else Print A; "es distinto de cero" End If Tambin funciona, ya que Not 0 es -1, por tanto el If lo da por cierto, si cambiamos el valor incial de A por un valor distinto de cero: A = 5 If Not A Then Print A; "es cero" Else Print A; "es distinto de cero" End If Que ha pasado aqu? Simple, que no es lo que esperbamos... Cuando hicimos Not 0 era evidente, ya que se convierte en -1, pero Not 5 no se convierte en cero, sino en: -6 y ya sabes que el IF considera como verdadero todo lo que no sea cero. No quiero entrar en detalles de porqu ocurre esto, slo decirte que la responsable de todo es la notacin binaria... los ceros y unos que dicen que es el lenguaje nativo de los cacharros estos... talvez ms adelante tratemos un poco de la notacin binaria, pero no por ahora... recuerdo que en mis tiempos del GwBasic la usaba bastante, incluso tena rutinas para "representar" en ceros y unos un nmero... Vale, para que te entretengas probando... (este link es para el programa completo Dec2Bin.zip 1.75 KB)
Private Function Dec2Bin(sNumDec As String) As String
85
' Recibe una cadena que ser un nmero decimal ' Devuelve ese nmero representado por ceros y unos ' Dim i As Integer Dim lngNum As Long Dim sTmp As String lngNum = Val(sNumDec) sTmp = "" For i = MaxBits - 1 To 0 Step -1 If lngNum And 2 ^ i Then sTmp = sTmp & "1" Else sTmp = sTmp & "0" End If Next Dec2Bin = sTmp End Function ' Long, por si las moscas ' Cadena temporal
Ahora puedes comprobar porqu NOT 5 da como resultado -6, usa esta rutina para probarlo, si escribes 5, te mostrar: 00000101 y si escribes -6 lo que muestra es: 11111010, fijate que ha cambiado todos los ceros por unos y viceversa. Eso es lo que hace el NOT, invertir los valores binarios y como un valor binario slo puede ser 0 1, no se complica demasiado la vida. Prueba a escribir el valor 0 y el valor -1 y conviertelo a notacin binaria, fijate lo que el Visual Basic normalmente ve. Prueba con el tema de la notacin binaria, as sabrs realmente cmo funciona todo esto de las comparaciones (por dentro). Lo que nunca falla es completar la expresin, por ejemplo si haces esto: If Not A<>0 Then Print A; "es CERO" Else Print A; "NO es CERO" End If Esto siempre funcionar de la forma esperada. Pero sera ms fcil, o al menos ms inteligible, hacerlo as: If A = 0 Then Print A; "es CERO"
86
Else Print A; "NO es CERO" End If Es que algunas veces se puede uno complicar la vida ms de lo necesario... Cuando quieras comprobar un valor devuelto por cualquier expresin, puedes hacerlo asignndolo a una variable o bien mostrando el valor: Print Not A Cuando se usa AND pra evaluar varias partes de una expresin hay que tener presente que siempre se procesan todas las condiciones y finalmente se decide si es cierto o no el valor devuelto. Esto que parece lgico, algunas veces puede llevar a confusin e incluso producir efectos no deseados en el programa. Prueba con esta nueva versin de la funcin Existe. En un form debes poner una etiqueta Label1. Private Function Existe(Archivo As String) As Integer Existe = Len(Dir$(Archivo)) If Existe Then Label1 = Archivo & " Si existe" Else Label1 = Archivo & " No existe" End If ' Esto es ms corto, pero talvez menos evidente: ' Label1 = Archivo & IIf(Existe, " Si", " No") & " existe" DoEvents End Function Private Sub Form_Load() Dim A As Integer, Nombre As String Show Label1 = "" Nombre = "C:\Autoexec.BIN" A = 5 If A > 10 And Existe(Nombre) Then Print A; "mayor de 10 y " & Nombre & " existe" Else Print A; "no es mayor de 10 o " & Nombre & " no existe" End If
87
End Sub En el ejemplo comprobars que a pesar de que la segunda parte de la comparacin no se cumpla, a no ser que tengas en tu disco C un archivo que se llame as, el caption del Label se ha cambiado. Es decir que se ha procesado la segunda parte de la expresin a pesar de que la primera A>10 es FALSA. Imagnate que en lugar de ser una funcin rpida, hubiese sido otra cosa que tardara un poquito ms de la cuenta... Para casos como estos, (la verdad es que no son demasiado habituales), deberas hacerlo as: Private Sub Form_Load() Dim A As Integer, Nombre As String Show Label1 = "" Nombre = "C:\Autoexec.BIN" A = 5 If A > 10 Then If Existe(Nombre) Then Print A; "mayor de 10 y " & Nombre & " existe" Else Print A; "es mayor de 10 pero " & Nombre & " no existe" End If Else Print A; "no es mayor de 10 o " & Nombre & " no existe" End If End Sub Talvez sea ms largo y haya que usar ms cdigo, pero en ocasiones es ms "resultn". Usando este nuevo "estilo", slo se comprobar si existe el archivo cuando A sea mayor que diez. Lo que debes sacar en claro de todo esto es que despus de un THEN puedes "anidar" ms expresiones IF...THEN...ELSE. Incluso se puede usar en una sola lnea, slo que el resultado "visual" del cdigo no es tan "presentable"... If A > 10 And Existe(Nombre) Then Print A; "mayor de 10 y " & Nombre & " existe" Else Print A; "no es mayor de 10 o " & Nombre & " no existe" Aunque podramos usar el caracter _ que se puede usar en VB para separar lneas largas, pero es como si estuviese toda en la misma lnea, as que la lnea anterior, se quedara as:
88
If A > 10 And Existe(Nombre) Then _ Print A; "mayor de 10 y " & Nombre & " existe" _ Else _ Print A; "no es mayor de 10 o " & Nombre & " no existe" Fjate que a pesar de aparentar que es un BLOQUE IF, no tiene el END IF del final, esto es porque yo lo he "estructurado" de esa forma, no porque sea lo mismo. El uso de _ es slo esttico y para VB todo se trata de una misma lnea, por tanto tendr un lmite de caracteres posibles a usar, el lmite que VB le ponga, que creo que es 1024... pero no me hagas demasiado caso... Antes he mencionado la palabra "anidacin", sta se usa para indicar que una serie de instrucciones estn dentro de otras. En este caso hemos anidado dos IF... THEN, pero lo ms habitual es hacerlo con los bucles (FOR, DO, etc.), vemoslo: Dim i%, j%, c% For i = 1 To 10 For j = 1 To 10 c = c + 1 Next Next Print c Lo que debes saber, o al menos tener en cuenta, es que cuando anidamos varios bucles, lo externos empiezan antes (elemental querido Watson), pero los internos finalizan primero (...) y hasta que no lo hagan, no podrn continuar los de fuera. En el ejemplo, por cada repeticin del bucle i, se completa un bucle j. Por eso el valor de c es 100 (10*10) Esto, en ocasiones, puede ralentizar el programa, y dar la impresin de que el programa se ha quedado "colgado", prueba a poner otro bucle dentro del j y cambia los valores mximo de los dos bucles internos a 1000, te recomiendo que la variable c sea LONG y que te sientes... No hace falta que hagas la prueba, es una chorrada... Lo que interesa es que dentro de un proceso cualquiera y por supuesto tambin en los bucles, podramos necesitar que el Visual Basic nos mostrara alguna indicacin de que est "ocupado", por ejemplo cambiando la forma del cursor del ratn, como hacen otros programas, incluso el propio VB cuando est "atareado". Para ello tendremos que cambiar la propiedad MousePointer para que muestre el reloj de arena: MousePointer = vbHourGlass ' vbHourglass es igual a 11, por si tienes usas el VB3 '... lo que sea MousePointer = vbDefault ' 0 si usas VB3 Pero algunas veces el cursor no se cambia... para asegurarnos que cambie, usa el DoEvents despus de asignar el valor para el reloj de arena. De esta forma permitimos que Windows procese sus mensajes (recuerdas?) y as tiene ocasin de cambiar el puntero del ratn.
89
Bueno, hasta aqu llega esta entrega. No hay ejercicios, slo te pedira que revisaras la ayuda y te leyeras lo que all pone referente a las instrucciones que vamos viendo... aunque me imagino que tendrs otras cosas que hacer... El caso es que no hay ejercicios y ya est. Siguiendo con la costumbre todas las entregas anteriores, te pido tus comentarios sobre esta entrega y el curso en general, ms que nada para saber si voy bien encaminado y no me pierdo, lo que pretendo es que se entienda y que aprendas... Ya me contars. Nos vemos en la prxima entrega, que espero que no sea tan tardona como esta...
90
total en la variable VecesDia. Pero la cosa se va complicando, verdad? No lo crees? Pues toma un poco ms de complicacin: Cmo haras para saber la hora en que ms veces te has rascado la cabeza? No te doy la solucin... Es demasiado largo y seguramente hasta complicado como para ponerlo como ejemplo para que salgas de tu incredulidad... Ahora bien, si quieres hacerlo, hazlo, pero despus no me preguntes si est bien o no... sera una prdida de tiempo, ya que, como vas a ver dentro de muy pocas lneas, hay una forma de hacerlo bastante ms simple. Toda esta retahla, es para explicarte la forma con que el Basic, y otros lenguajes, nos facilita la vida en tareas de este tipo... realmente no creo que haya nadie que tenga una utilidad de este tipo, ya que no es til esto de saber cuantas veces nos rascamos la cabeza por no comprender algo... ni an cuando ese algo sea lo que yo escribo... Un poco de historia (la ma) La primera vez que me top con los ARRAYS (de eso va esta entrega), fue con un programa de dados, en mi querido VIC-20. Lo copi de un libro ingls que tena 50 juegos para el Vic-20, saba que funcionaba, pero no saba cmo... Durante un montn de tiempo, (a lo mejor fue una o dos semanas, es que antes los das duraban ms que ahora), us mtodos parecidos, sin saber porqu funcionaba... slo saba que funcionaba y simplemente lo aceptaba. Despus cay en mis manos un libro, esta vez en espaol, y me puse a leer la parte que explicaba esto de las "matrices", antes no eran arrays... la verdad es que lo le como 20 veces o ms... y pareca que nunca iba a lograr asimilarlo, es que soy bastante duro de mollera y antes, que era ms joven, calculo que unas 14 veces ms joven que ahora, tena la cabeza ms dura. En aquella ocasin si que me hubiese venido bien el programilla este de rascarme la cabeza... Esto es lo que deca, el susodicho libro: "El lenguaje BASIC permite definir otro tipo de variables numricas: A(1), A(2)...A(N) se llaman variables con ndice y estn formadas por un nombre de variable, [...], seguido de un nmero natural entre parntesis. [...] el conjunto ordenado de estas variables se llama lista. [...]" Ahora que lo he vuelto a leer, casi lo entiendo; pero para un pobre cateto ignorante como yo, aquello sonaba a chino. Y si tu lo entiendes, me alegro por ti... Como te he comentado, el prrafo ese est sacado de un libro que fue de los pocos que tuve, al menos en castellano, de los ingleses slo me interesaban los listados, que era lo nico que prcticamente traan. La cosa es, que a pesar de eso, hasta aprend un poco, a duras penas, sobre todo porque en aquellos tiempos no tena a quin preguntarle, ni quin me explicara algunas de las muchas dudas que tena... y si no tena ms dudas era porque tampoco haba profundizado demasiado. Pero lo que he sacado en claro es que para aprender a programar hay que practicar, practicar y seguir practicando... es como todo, cuanto ms practicas... o terminas por aburrirte o aprendes... Vamos al tema, pero sin teoras, las teoras se las dejo a los eruditos... ellos saben cmo explicar "bien" las cosas... Y que conste que no tengo nada en contra de las teoras, lo que ocurre, al menos a mi, es que prefiero entender las cosas de forma prctica, que soy "mu" torpe yo y si no lo veo funcionar, no me entero... En el ejemplo ese de las horas, nos vendra muy bien que pudisemos usar otra variable para la hora de nuestro "picor" y hacer algo como: HoradelPicor = HoradelPicor + 1, dnde "delPicor" sera la hora en que nos rascamos la cabeza, as si "delPicor" es 22, incrementar Hora22. S, lo reconozco, una vez intent
91
hacerlo, crea que se poda hacer as: delPicor = 22 HoradelPicor = HoradelPicor + 1 Pero el basic no haca lo que yo quera, ni siquiera me daba error, si en aquellos tiempos hubiese existido el Option Explicit, me habra percatado de muchas cosas antes de tiempo... Por suerte para todos, existen los ARRAYS (o variables con ndice) y realmente la forma de hacerlo es casi como yo crea, lo nico que cambiaba era la forma... total, por un par de parntesis... Hora(delPicor) = Hora(delPicor) +1 Con esto le decimos al Basic: coge lo que hay guardado en la variable que est en la posicin delPicor del array Hora... Vale, captado. Me estoy lanzando y an no te he presentado a los arrays. Un Array es una serie de variables que tienen el mismo nombre y para acceder a cualquiera de esas variables, usamos un nmero (ndice) para indicar cual de esas variables es la que nos interesa... Te has enterado de que estoy hablando de variables o tengo que decirlo ms veces? Vale, admito que tampoco he sido demasiado claro, es que realmente no es tan fcil de asimilar, pero en cuanto lo veas con algunos ejemplos, seguro que lo "asimilas". El basic, que es "mu" listo, cuando ve esto: Hora(delPicor), dice: "Cuanto vale lo que est dentro del parntesis?" (en caso de que sea una expresin en lugar de un nmero o una variable, la evaluara primero y usara el resultado), una vez que sabe cuanto vale lo que est dentro del parntesis... "ahora cojamos, del array, el contenido de la variable que est en esa posicin" (para el basic un array no es ms que una serie de variables que tienen el mismo nombre y lo nico que vara es "la direccin" en la que est guardado el valor. O sea que maneja los arrays de la misma forma que a las variables simples, pero ampliando nuestros horizontes "variabilsticos". Ahora fjate cmo podemos sumar las veces que nos hemos rascado la cabeza a lo largo del da: For i= 1 to 24 vecesDia = vecesDia + Hora(i) Next Cada vez que i cambia de valor, (en este caso tomando valores desde 1 hasta 24), el Basic usa una variable diferente del array Hora, cuando i vale 1 est usando Hora(1) y cuando i vale 22, usa Hora(22). Lo de saber a que hora te has rascado ms veces la cabeza te lo dejo como ejercicio, slo te dir que si haces esto: masVeces = Horas(HoraMasVeces) sabrs cuantas veces te has rascado a la hora HoraMasVeces... Los elementos de un array se comportan como variables normales, pudiendo usarlas en expresiones y en cualquier otro sitio en el que podamos usar una variable, realmente uno de los pocos sitios donde no puede usarse es como ndice de un bucle FOR, pero por lo dems, en cualquier parte. Imagnate que en la posicin 22 del array Hora, es decir en Hora(22) tenemos guardado un valor 15, al hacer esto: Print Hora(22) * 10 mostrara 150, porque el VB ha sustituido Hora(22) por su valor y lo ha multiplicado por 10, es como si internamente hubiese hecho: Print 15*10.
92
Y tu dirs: esto mismo es lo que hace con las dems variables... Efectivamente, ya que es una variable, especial, pero una variable al fin y al cabo. Sigamos imaginando... suponte que quieres guardar en una variable a que horas te pones a leer, cada da, las pginas del Guille y que sabes que sern tres veces diarias, lo hay masoquistas... Podras hacer algo como esto: Vez(1) = 20: Vez(2) = 22: Vez(3) = 23 '...(de nueve a diez vas a cenar) con lo cual tendras un array con tres ndices. El valor de cada una de las variables del array "Vez", sera la hora en que "guilleas" ... y si quieres incrementar los "rascones" de la hora que est en la posicin H, (que puede ser 1, 2 3): Ahora = Vez(H) ' Si H vale 1, Ahora sera igual a 20 Hora(Ahora) = Hora(Ahora) + 1 Pero esto podras hacerlo ahorrndotela variable Ahora, sera as: Hora(Vez(H)) = Hora(Vez(H)) + 1 Complicado? Pues si... que quieres que te diga... pero dejmoslo estar... Veamos cmo podemos usar en nuestros programas este tipo especial de variables. Antes yo las llamaba: "variables dimensionadas", entre otras cosas porque era nicamente cuando necesitaba usar DIM, al menos si iba a usar ms de 10 "posiciones", aunque tambin puede ser que lo leyera en algn sitio, no importa... Para decirle al Basic que vas a usar tres variables en el array Vez, hay que hacerlo de esta forma: Dim Vez(3) Ahora lo que necesitamos es un array para guardar los picores de las 24 horas del da: Dim Hora(24) Bueno, ya sabes casi todo lo que tienes que saber de los arrays... ahora cmprate un buen libro "terico" y estdiatelo... o lete lo que dice el manual del Visual Basic... que tambin puede valer. An sigues por ah...? Bueno, ya que insistes, te explicar algunas cosillas ms... Los arrays son variables, algo especiales, pero variables al fin y al cabo. Por tanto, podemos tener arrays numricas, de carateres y en definitiva de cualquier tipo que el Basic permita, lo nico que tenemos que hacer es indicrselo al reservar memoria: Dim Vez(3) As Integer Dim Amigos(1000) As String Dim Salario(1000) As Currency Cuando declaramos un array, el Basic reserva memoria para cada una de las variables que vamos a usar, o casi, ya que en realidad reserva una posicin ms, no por nada en especial, sino porque empieza a contar desde cero; por ejemplo en el array Vez, el ndice ms bajo que podramos usar es el 0 y el ms alto el 3. Esto ha sido as desde siempre... aunque en un intento de "cambiar" las cosas, un listillo dijo: "El Basic debera empezar a contar desde uno" y se sacaron una nueva instruccin de la manga, desde mi punto de vista lo podran haber hecho mejor, pero como mis "preferencias" no las tuvieron en cuenta... (tampoco tuve la oportunidad, la verdad sea dicha...), el caso es que dijeron: OPTION BASE 1 para que el ndice menor de un array sea UNO y OPTION BASE 0 para empezar por CERO, esta ser la predeterminada. Por tanto si usamos este cdigo: Option Base 1
93
Dim Vez(3) As Integer Crea un array con tres variables (del 1 al 3) Ms adelante, otro listillo, (este fue un poco ms inteligente), dijo: "Y si el usuario pudiera decidir el valor menor y el mayor del ndice de una array"... "pues que bien", contest otro... Y as fue. Imagnate que tu slo te rascas la cabeza de 10 a 23, puedes dimensionar as el array Hora: Dim Hora(10 To 23) As Integer De esta forma slo "reservas" la memoria que necesitas... Ya ves que todo son facilidades, aunque hay una cosa "muy" importante que hay que tener en cuenta: Si pretendes acceder a una posicin del array que no est reservada, el Visual Basic te avisa de que hay un error y detiene (termina) el programa. Esto es lo nico grave, pero si tu aplicacin tiene informacin importante pendiente de guardar, o se encontraba en medio de un proceso largo de clculo... realmente si que ser grave... en otra ocasin veremos cmo detectar los errores y poder "manejarlos" para que no nos dejen en la "estacada"... Por tanto si declaras un array que reserva tres posiciones, siempre consecutivas, no podremos acceder a ninguna posicin anterior o posterior a las que tenemos declarada. Pero... y si necesito ms espacio? Lo nico que tienes que hacer es re-dimensionar el Array: ReDim Vez(5) As Integer Problemas? Si, que ya has perdido lo que antes haba almacenado... Cuando yo empec con el Basic (otra batallita?, habr que seguirle la corriente...), no exista el ReDim. Pero si exista una forma de conseguir esto mismo. El truco consista en "borrar" el array creado y volver a dimensionarlo... tambin recuerdo que me dio muchos quebraderos de cabeza adaptar mi cdigo (escrito en el intrprete GwBasic... la GW ser Gates, William?) a un compilador... eso de usar varias veces el DIM no lo digera bien... pero eso es otra historia, que seguramente no contar... Para borrar un array de la memoria, hay que usar ERASE seguido por el nombre del array, por ejemplo: Erase Hora Con esto conseguimos lo mismo que con ReDim Vez(5) As Integer: Erase Vez Redim Vez(5) As Integer Pero, y si no quisiramos perder los valores anteriores... Pues copia los datos en otro Array temporal, borras el primero, lo vuelves a dimensionar con el nuevo nmero de elementos y a continuacin copias los datos del array temporal en el array que acabas de redimensionar, despus borras el array temporal, ya que no lo necesitars... No, no es necesario tantas cosas, pero esto es lo que haba que hacer con el VB antes de la versin 3 y con el QuickBasic antes de la versin 4, ahora slo hars esto: ReDim Preserve Vez(5) Adems cuando se ReDimensiona un array no hace falta volver a especificar el tipo de dato, ya que tomar el mismo que se us inicialmente al declararlo. La ventaja del ReDim, con o sin Preserve, (podra haber hecho un chiste malo con esto del Preserve, pero me abstengo...), es que puedes ampliar o reducir el nmero de variables de un array... supn que despus de dimensionar Vez a cinco, lo piensas mejor y decides que con dos veces es suficiente, pues nada, haces esto: Redim Preserve Vez(2) y ya est. Lo importante es que slo reserves la memoria que vas a necesitar.
94
En la siguiente entrega veremos ms cosas de los arrays, as como algunas otras instrucciones, pero no te voy a adelantar nada, no sea que despus no est lo que tengo pensado y te mosquees. Ahora vamos al apartado de los ejercicios, que adems del que te dije casi al principio, te voy a poner otro ms: 1.) Tienes un array con un nmero cualquiera de elementos, averigua cual de las variables de ese array es la que tiene el valor mayor. 2.) La que tiene el valor menor y que no sea cero. Como ves no te quiero que te esfuerces demasiado. Ya sabes, si hay algo que no hayas terminado de comprender, no dudes en comentrmelo, de esta forma no seguir enrollndome con cosas que no hayan quedado "super claras". Esto ser en la prxima entrega, que espero sea dentro de muy poquito, (aprovechando las vacaciones virtuales estas que tengo), as que si vas a comentarme algo, hazlo pronto. Nos vemos.
95
'Poner este cdigo en el Form_Load Dim Hora(24) As Integer Dim i As Integer, Mayor As Integer 'Llenar el array con nmeros... '(en esta entrega veremos cmo hacerlo de forma aleatoria) '... 'Comprobar cual es el mayor For i = 1 To 24 If Hora(i) > Mayor Then Mayor = Hora(i) End If Next Print "El nmero mayor es:"; Mayor Un poco de explicacin, ya que no creo que sea suficiente con ensear la solucin. El problema que te has podido encontrar es, seguramente, la forma de asignar valores al array, aunque siempre queda el recurso de poder "llenarlo manualmente"; pero eso lo veremos en esta misma entrega. La cuestin es comparar el contenido de cada una de las horas con la variable que guardar el nmero mayor. Al principio esta variable, como ya deberas saber, tiene el valor CERO, as que cuando se haga la comparacin If Hora(i)>Mayor Then, si la variable que est en la posicin "i" del array "Hora" tiene un valor mayor que cero, se cumplir la condicin y se pasar a asignar ese valor a la variable que contendr el nmero mayor de los 24 que tenemos en el array. El bucle contina y cada vez que se cumpla la condicin de que el contenido de Hora(i) es mayor que el que tenemos en la variable Mayor, se asignar este y as hasta que se termine de dar vueltas... Si no te has enterado... preprate para la solucin del segundo ejercicio. El segundo: 'Los mismos comentarios iniciales que el primero Dim Hora(24) As Integer Dim i As Integer, Menor As Integer ' For i = 1 To 24 If Hora(i) Then If Menor = 0 Then Menor = Hora(i) Else If Hora(i) < Menor Then Menor = Hora(i) 'Si el contenido de Hora(i) es menor 'lo asignamos como menor 'Slo si no vale cero 'Si an no tiene un valor 'se lo asignamos
96
End If End If End If Next Print "El nmero menor es "; Menor Este est ms o menos explicado en los comentarios, pero voy a dejrtelo un poco ms claro: La cuestin consiste en comprobar primero si el contenido del elemento "i" del array "Hora" tiene un valor distinto de cero, (si vale cero no lo tendremos en cuenta), lo siguiente que se hace es comprobar si el contenido de "Menor" vale cero, si es as, quiere decir que an no le hemos asignado ningn valor, por tanto le asignamos lo que tenga Hora(i). En posteriores comprobaciones lo que se hace es averiguar si el valor guardado en el elemento del array es menor que el que tenemos en nuestra variable "Menor" y si es as, quiere decir que tenemos un nmero ms pequeo, por tanto lo asignamos para que siempre "Menor" tenga el nmero menor (valga la redundancia). Pero y si quisiramos tener en cuenta tambin el CERO... Pues que tendramos que hacerlo de otra forma, ya que esta es slo para el caso expuesto... te dejo que lo pienses, pero no es demasiado difcil, incluso ms simple que esta solucin, lo que ocurre es que entran en juego pequeos detalles que seguramente veremos en esta entrega... Ahora vamos a empezar la Novena Entrega. No voy a empezar, o mejor dicho, no voy a continuar con los arrays, pero no te preocupes que slo ser un pequeo alto en el camino, lo que veremos primero es algo que nos va a facilitar hacer pruebas con los arrays... se trata... (redoble de tambores) de:
Nmeros Aleatorios
En algunos casos vamos a necesitar generar nmeros aleatorios, (nmeros sacados al azar, al menos en teora...), y si no necesitas usar nmeros aleatorios, vamos a usarlos en algunos de los ejercicios, as que voy a explicar cmo va esto: La funcin que se usa para generar nmeros aleatorios es: RND Esta funcin devuelve un nmero que ser mayor o igual a CERO y menor que UNO. Creo que se representa as: 0<RND<1, pero si no es as, da igual o algn "experto" me lo dir. Lo que interesa saber es que nunca llegar a valer uno y que puede ser igual a cero. Si hacemos esto: x = Rnd * 6 El valor X nunca llegar a 6, en la mayora de los casos se suelen quitar los decimales usando INT, en este caso, haciendo x = Int(Rnd * 6), x podr valer 0, 1, 2, 3, 4 5 y si hacemos esto otro: x = Int(Rnd * 6) + 1. Los valores sern de 1 a 6. Si queremos valores del 65 al 90 la expresin sera esta: x = Int(Rnd * 26) + 65. Ya que Int(Rnd * 26) producir un nmero que estar entre el 0 y el 25 (ambos inclusives), al sumarle 65... pues eso, estar en el rango deseado. Este ejemplo nos sirve para cuando necesitemos obtener un cdigo ASCII de una letra de la A a la Z, (en maysculas), ya que los cdigos ASCII de las letras son: A=65, B=66... Z=90 en caso de que sean maysculas, para obtener los valores en minsculas slo hay que aadirle 32 y ya los tendremos porque a=97, b=98... z=122. En estos rangos se excluye la ee, tanto maysculas como minsculas y las vocales acentuadas... es que los seores que crearon esta norma (American Standard Code for Interchange Information, o algo parecido y se suele pronunciar ASKI), eran de los USA y all no usan esas letras... recuerdo mis tiempos de comics, cuando Alex Nio se llamaba Alex Nino, al menos en los crditos de los comics USA...
97
El problema de los nmeros aleatorios, es que no son tan aleatorios, es decir cada vez que se inicia el programa produce el mismo nmero, (al menos en los basics anteriores, he ledo que ahora el VB4 no genera la misma secuencia cada vez que se inicia el programa, eso lo veremos despus), vamos a ver un ejemplo para que lo compruebes, esto lo he comprobado con el VB2 (que es el que tengo en el porttil que me he llevado a casa para escribir las entregas, hasta que mi jefe me lo pida... que ser en pocos das, puede ser), se supone que en los dems funcionar igual, pero como te digo para el VB4 hay un "truco" que veremos despus... cuando lo compruebe, je. Escribe esto en el Form_Load del nuevo proyecto que habrs tenido que crear para probar... si no lo has hecho, ya tardas... Private Sub Form_Load Show 'Aqu debe estar esto, sino no se ver nada... recuerdas? Print Rnd * 25 End Sub A m me ha mostrado 17.63869, ejectalo varias veces y vers que siempre muestra el mismo nmero... Existe una forma de solucionar esta falta de "aleatoriedad" y es cambiando la "semilla" que se usa como base para la imPLANTAcin de nmeros aleatorios, para ello se usa Randomize seguido de un nmero, pero si el nmero es el mismo... no conseguimos nada... Prueba poniendo Randomize 5 despus del Show y antes del Print, prueba a ejecutarlo varias veces, a mi me ha mostrado 8.143144 todas las veces que lo he ejecutado. El problema es que al usar un nmero "fijo" como semilla para la generacin de nuevos nmeros, el nmero producido siempre es el mismo, esto est bien para hacer pruebas fijas o cuando queremos "drnosla" de mago con los colegas que saben menos que nosotros, ya que podemos "predecir" los nmeros que mostrar en una secuencia seguida... con este "truco" dejaba "alucinado" a los chavales a los que le daba clases... slo tena que memorizar una serie de nmeros y se quedaban "alucinados" cuando les deca el que iba a salir... claro que despus tena que poner "pies en polvorosa" cuando les explicaba "la trampa". Bueno, al tema, que no es plan de contar batallitas... El VB nos proporciona una funcin que devuelve el nmero de segundos transcurridos desde la media noche (TIMER) y usando esta funcin como "semilla" lograremos producir nmeros que "casi" ser aleatorios... al menos sern ms difciles de "pronosticar", cambia el Randomize 5 por Randomize Timer y vers que ya no se produce el mismo nmero cuando ejecutes varias veces el programa...salvo que hagas "trampas" cambiando la hora del equipo... En VB4 y superior, se puede hacer esto mismo poniendo Randomize -1, de esta forma la "semilla" es diferente cada vez que se ejecuta, pero prefiero usar el Timer ya que es ms "compatible". Que tal un jueguecito para practicar? Hay que hacer un programa que genere un nmero entre uno y cien y hay que intentar adivinarlo... Si el nmero que damos es mayor o menor, que el VB nos avise y cuando acertemos que nos lo comunique y termine el programa... Para que VB se comunique, te voy a decir cmo hacerlo... Para preguntarte el nmero y guardarlo en la variable N, haz esto: N = Val(InputBox("Escribe un nmero entre 1 y 100")). El InputBox muestra una pantalla preguntando y devuelve lo que escribamos o una cadena vaca si pulsamos en cancelar, el Val convierte esa cadena en nmero. Para avisar que el nmero N, (el que nosotros le decimos al VB), es menor o mayor, cambiar xxxx por lo que corresponda: MsgBox "El nmero " & CStr(N) & " es xxxx"
98
De esta forma se mostrar un cuadro de dilogo indicando si vamos bien encaminados o no... Ya mejoraremos o ampliaremos este "ejercicio" para hacer ms cosas, incluso que el ordenador averige el nmero... que sin ningn tipo de "suerte" lo adivinar en 5 6 veces, lo mismo que tu debers hacer si sigues algunas normas o trucos... slo decirte lo de "divide y vencers" (no s porqu me ha dado ahora por esa cita...) En la prxima entrega veremos ms cosas sobre los arrays... Si te atreves podras hacer los siguientes cambios al "problemilla" planteado anteriormente: 1. Comprobar que el nmero introducido en el InputBox est entre 1 y 100, en caso de que no sea as, volver a preguntar. 2. Si se escribe CERO mostrar el nmero que el VB haba "pensado" y terminar. 3. Cuando lo acertemos que nos indique en cuantos intentos lo hemos conseguido. 4. Un programa que sea al revs, es decir: que nosotros pensemos un nmero del 1 al 100 y el VB intente adivinarlo, para ello deber mostrarnos un nmero y nosotros indicarle si lo ha acertado. 5. Otro igual, pero indicndole si nuestro nmero es Menor, Mayor o es correcto... habr que darle las mismas oportunidades... (este es el que tiene el "truco" del divide y vencers... En los casos 4 y 5 que muestre tambin el nmero de intentos que le ha llevado solucionarlo... La pista para que el ordenador sepa si es menor o mayor es usar el MsgBox, pero como funcin: If MsgBox("Mi nmero es: " & CStr(x) & Chr$(13) & "He acertado?", 4) = 6 Then MsgBox "Lo he acertado en " & CStr(v) & " veces." Exit Do '... De esta forma mostrar un cuadro de dilogo con dos opciones "SI" y "NO", el nmero 4 es el encargado de eso. El valor devuelto ser 6 si se pulsa en SI y 7 si se pulsa en NO. Esto en VB4 se podra hacer as: If MsgBox("Mi nmero es: " & CStr(x) & vbCrLf & "He acertado?", vbYesNo) = vbYes Then MsgBox "Lo he acertado en " & CStr(v) & " veces." Exit Do '... Con lo cual, aunque sea en ingls, es ms intuitivo. Esto de los MsgBox lo veremos en una entrega "especial"
99
Las soluciones estn en este link... no quiero que ests esperando las soluciones hasta la prxima entrega... para que veas que algunas veces soy un poco "ms considerado" Bueno, creo que esta entrega no debera tener demasiadas dudas... en los manuales o la ayuda viene explicado esto de los nmeros aleatorios, pero si quieres dejarme algn comentario, hazlo e intentar aclararte cualquier duda... pero no lo hagas sobre las soluciones, ya que las obtienes en el link del prrafo anterior. Que lo "randomices" bien... Nos vemos.
100
Dim n As Integer Dim x As Integer Randomize Timer x = Int(Rnd * 100) + 1 Do Do n = Val(InputBox$("Escribe un nmero del 1 al 100")) Loop While n < 1 Or n > 100 If n = x Then Exit Do If n < x Then MsgBox "El nmero " & CStr(n) & " es menor." Else MsgBox "El nmero " & CStr(n) & " es mayor." End If Loop MsgBox "Lo has acertado." 2. Si se escribe CERO mostrar el nmero que el VB haba "pensado" y terminar. Dim n As Integer Dim x As Integer Randomize Timer x = Int(Rnd * 100) + 1 Do Do n = Val(InputBox$("Escribe un nmero del 1 al 100")) If n = 0 Then MsgBox "Mi nmero era el " & CStr(x) Unload Me End End If Loop While n < 1 Or n > 100 If n = x Then Exit Do If n < x Then MsgBox "El nmero " & CStr(n) & " es menor." Else
101
MsgBox "El nmero " & CStr(n) & " es mayor." End If Loop MsgBox "Lo has acertado." 3. Cuando lo acertemos que nos indique en cuantos intentos lo hemos conseguido. Dim n As Integer Dim x As Integer Dim v As Integer Randomize Timer x = Int(Rnd * 100) + 1 Do Do n = Val(InputBox$("Escribe un nmero del 1 al 100")) If n = 0 Then MsgBox "Mi nmero era el " & CStr(x) 'Si est en el Form_Load 'End 'Si est en un procedimiento Exit Sub End If Loop While n < 1 Or n > 100 v = v + 1 If n = x Then Exit Do If n < x Then MsgBox "El nmero " & CStr(n) & " es menor." Else MsgBox "El nmero " & CStr(n) & " es mayor." End If Loop MsgBox "Lo has acertado en " & CStr(v) & " veces." 4. Ahora al revs, es decir: que nosotros pensemos un nmero del 1 al 100 y el VB intente adivinarlo, para ello deber mostrarnos un nmero y nosotros indicarle si lo ha acertado.
102
Dim x As Integer Dim v As Integer Randomize Timer Do x = Int(Rnd * 100) + 1 v = v + 1 If MsgBox("Mi nmero es: " & CStr(x) & Chr$(13) & "He acertado?", 4) = 6 Then MsgBox "Lo he acertado en " & CStr(v) & " veces." Exit Do End If Loop 5. Otro igual, pero indicndole si nuestro nmero es Menor, Mayor o es correcto... habr que darle las mismas oportunidades... (este es el que tiene el "truco" del divide y vencers... Dim x As Integer Dim v As Integer Dim a As Integer Dim z As Integer a = 1 z = 100 'En este caso no necesitamos nmeros aleatorios Do x = (z - a) / 2 + a v = v + 1 If MsgBox("Mi nmero es: " & CStr(x) & Chr$(13) & "He acertado?", 4) = 6 Then MsgBox "Lo he acertado en " & CStr(v) & " veces." Exit Do Else If MsgBox("Entonces... " & CStr(x) & " es mayor?", 4) = 6 Then 'El nmero del ordenador es mayor 'debe estar entre x-1 y a z = x - 1 Else
103
'El nmero del ordenador es menor 'debe estar entre x+1 y z a = x + 1 End If End If Loop
Ahora a esperar a la dcima entrega que ser dentro de poquito... un mes, un ao... quin sabe?
Arrays Multidimensionales
En algunas ocasiones hasta los arrays se quedan cortos... al menos los arrays simples o unidimensionales, (una sola dimensin), si en el ejemplo de la octava entrega, el de los rascamientos, quisieras saber las veces que te has rascado cada da del mes... tendramos otra vez el problema, podramos usar un array para cada da del mes: Dia1(24), Dia2(24)... pero de nuevo tendramos complicaciones para algunos clculos... Por suerte para nosotros, existe otra forma de usar los arrays. En nuestro caso nos servira el que los arrays tuviesen dos dimensiones, al estilo de una tabla con filas para cada da del mes y columnas para cada una de las horas del da en cuestin, para hacer esto, dimensionaremos un array de esta forma: Dim Dias(31, 24) As Integer Para guardar o recuperar un valor lo haremos de la misma forma que con un array simple, pero especificando dos valores separados por una coma: Dias(DiaMes, HoraDia) = 1 y por supuesto, podemos usarlo con bucles FOR: For Dia = 1 To 31 For Hora = 1 To 24 RascadasMes = RascadasMes + Dias(Dia, Hora) Next Next
104
Despus que estos dos bucles terminen, la variable RascadasMes tendr el total de veces que nos hemos rascado cada uno de los das del mes que estamos procesando. Si queremos almacenar las rascadas de cada da de cada mes de un ao, No Problem! Dim Meses(12, 31, 24) As Integer De esta forma solucionaramos en problema, ya que al aadir una tercera dimensin, podemos usar esta para cada uno de los meses, por ejemplo, el total de veces que nos hayamos rascado a las 22 horas del da 30 del mes 9 (septiembre), estara en: Meses(9, 30, 22) Reconozco que este ejemplo de las rascada no es til, pero por lo menos hemos visto cmo usar los arrays. Recuerda que los arrays pueden ser de cualquier tipo: Integer, String, Double, etc.
105
Erase Dias Dim Dias(12, 31, 24) El problema es que perdemos los datos... cosa que, en caso de necesidad, podramos solucionar copiando los datos a otra variable y volviendo a asignarla al nuevo array dimensionado... Pero muchos de estos problemas se solucionan con las colecciones y el uso del tipo Variant, as como con los objetos o clases definidas por nosotros... pero eso ser ms adelante... todava hay muchas otras cosas "esenciales" que aprender y conceptos que siempre debes tener en cuenta... que poco a poco estoy intentando recalcar para que tu "coco" vaya asimilndolos... espero conseguirlo. Nota: Hay que tener en cuenta que la nica dimensin que podemos redimensionar en un array multidimensional es la ltima.
106
Dim i As Integer Dim j As Integer Randomize Timer list1.Clear 'Asignar los valores '...Escribe aqu tu cdigo... 'Clasificar For i = 1 To MaxCadenas For j = 1 To i - 1 'para ordenar de forma descendente: 'If cadena(i) > cadena(j) Then If cadena(i) < cadena(j) Then 'intercambiar los valores sTmp = cadena(i) cadena(i) = cadena(j) cadena(j) = sTmp End If Next Next list1.Clear For i = 1 To MaxCadenas list1.AddItem cadena(i) Next Creo que el procedimiento es lo suficientemente "simple" como para que lo entiendas... verdad? Lo que debes "observar" en este mtodo es que cada uno de los elementos del bucle i se compara con todos los anteriores, de forma que si alguno anterior es "mayor" se intercambien las posiciones... Supn que en la posicin cadena(1) tienes almacenado "HOLA" y en la posicin 2 est la palabra "AMIGO" La condicin se cumplir cuando la variable i valga 2 y j valga 1, quedando por tanto en el orden correcto. Lo que debes saber de las cadenas de caracteres es que cuando se hace una comparacin el Visual Basic comprueba los valores ASCII de las letras que componen la palabra, en este caso la letra A est antes que la H, as que A es menor que H. Tambin debers saber que los nmeros estn antes que las letras, por tanto si una cadena de caracteres empieza por una cifra del 0 al 9, se ordenar antes que la "A" y que la "a" estar despus que la Z Si quieres saber los valores ASCII de los caracteres "ms o menos" stndard, haz este bucle:
107
Ahora los ansiados ejercicios, (realmente ha sido cortita esta entrega verdad?) Para los ejercicios, usando este trozo para guardar nmeros aleatorios en un array unidimensional, espero que no tengas problemas para guardarlos en un array multidimensional. T = Int(Rnd * 31) + 20 'Nmero de rascadas, T valdr de 20 a 50 For i = 1 To T H = Int(Rnd * 23) + 1 'H valdr de 1 a 23 Horas(H) = Horas(H) + 1 Next Los ejercicios usando este ejemplo: 1. Saber que hora tiene el valor mayor y a que hora empezaste a rascarte (es decir la primera hora del array que contiene un valor) 2. Que hora fue la ltima en que te arrascaste (no necesita explicacin...) 3. Modificar el ejemplo anterior para que el nmero de veces que te rascas valga (aleatoriamente) de 100 a 1000 y saber tambin cual de estas horas tiene el valor menor (en caso de que haya varios, slo tienes que averiguar uno de ellos) Para que no te compliques mucho la vida, decirte que con un par de lneas, puedes averiguar el mayor o el menor... no sea que quieras hacer un mogolln de comparaciones. A disfrutarlo! Esta entrega no da ms de s, no es que haya querido hacerla deprisa y corriendo, es que realmente lo "bsico" est aqu explicado, si quieres profundizar ms, ya sabes dnde buscar informacin... en los libracos esos que venan con tu VB. Si an as, piensas que no te has enterado... repsatela unas diecisis veces ms... Nos vemos. 10/Ene/98: Las soluciones de esta entrega.
108
[repeticin del final de la entrega 10] Ahora los ansiados ejercicios, (realmente ha sido cortita esta entrega verdad?) Para los ejercicios, usando este trozo para guardar nmeros aleatorios en un array unidimensional, espero que no tengas problemas para guardarlos en un array multidimensional. T = Int(Rnd * 31) + 20 'Nmero de rascadas, T valdr de 20 a 50 For i = 1 To T H = Int(Rnd * 23) + 1 'H valdr de 1 a 23 Horas(H) = Horas(H) + 1 Next Los ejercicios usando este ejemplo: 1. Saber que hora tiene el valor mayor y a que hora empezaste a rascarte (es decir la primera hora del array que contiene un valor) 2. Que hora fue la ltima en que te arrascaste (no necesita explicacin...) 3. Modificar el ejemplo anterior para que el nmero de veces que te rascas valga (aleatoriamente) de 100 a 1000 y saber tambin cual de estas horas tiene el valor menor (en caso de que haya varios, slo tienes que averiguar uno de ellos) Para que no te compliques mucho la vida, decirte que con un par de lneas, puedes averiguar el mayor o el menor... no sea que quieras hacer un mogolln de comparaciones.
Las soluciones:
109
Primero: He hecho un pequeo cambio al ejemplo que se usara, para que tambin se incluya la HORA CERO, esta sera una de las formas de conseguirlo:
'Ejercicio 1 Dim T%, i%, H%, Horas%(0 To 23) Randomize T = Int(Rnd * 31) + 20 'Nmero de rascadas, T valdr de 20 a 50 For i = 1 To T H = Int(Rnd * 24) 'H valdr de 0 a 23 Horas(H) = Horas(H) + 1 Next
Dim ValorMayor%, HoraValorMayor%, HoraInicioRascada% 'Este valor ser para saber que no se ha asignado Const NoAsignado = -1 ValorMayor = 0 HoraValorMayor = NoAsignado HoraInicioRascada = NoAsignado 'Las horas van de 0 a 23 For H = 0 To 23 'Si esta hora tiene algn valor, hacer las comprobaciones If Horas(H) Then ' '===Para saber la primera hora de la rascada=== ' 'Si la hora de inicio no se ha asignado If HoraInicioRascada = NoAsignado Then 'Asignar esta hora HoraInicioRascada = H End If ' '===Para saber la hora que tiene el valor mayor===
110
' 'Si el valor actual es mayor... If Horas(H) > ValorMayor Then ValorMayor = Horas(H) HoraValorMayor = H End If End If Next 'Para mostrar los valores: 'HoraValorMayor ser la hora que tiene el valor mayor 'El valor mayor se puede conseguir as: ' ValorMayor ' Horas(HoraValorMayor) Esta forma dar error si no hay ninguna hora con el valor mayor ' 'HoraInicioRascada ser la hora en la que empezaste a rascarte... ' Lo nico que hay que notar es lo siguiente: Ya que las horas empezarn por CERO, debemos asignar un valor "inexistente" a los valores de las variables que contendrn las horas de inicio y de mayor valor... Por qu? Porque el Basic asigna automticamente el valor cero a las variables numricas y como estas variables pueden tener un valor de CERO (0) a Veintitrs (23), el valor por defecto puede ser un valor vlido... as que asignndole -1 (menos uno), nos aseguramos que no tendr un valor que pueda confundirnos... lo captas? si no es as... ya te enterars cuando te ocurra... ;-)
El segundo: Usando la parte de asignacin de las horas, es lo mismo que para saber la primera hora, pero usando otra variable. 'Ejercicio 2 Dim UltimaHora As Integer 'Este valor ser para saber que no se ha asignado Const NoAsignado = -1 UltimaHora = NoAsignado 'Las horas van de 0 a 23
111
For H = 0 To 23 'Si esta hora tiene algn valor, hacer las comprobaciones If Horas(H) Then ' '===Para saber la ultima hora de la rascada=== ' 'Si la hora actual es mayor que la asignada If H > UltimaHora Then 'Asignar esta hora UltimaHora = H End If End If Next Esta solucin no tiene mayor inconveniente, una vez comprendida la primera solucin.
El tercero: Para que el nmero aleatorio que se asigna est entre 100 y 1000, cambia la asignacin a T de esta forma: T = Int(Rnd * 901) + 100 Recuerda que Int(Rnd * 901) produce un valor que va desde 0 a 900 ambos inclusive, as que sumndole 100 tendremos un valor de 100 a 1000. Para averiguar la hora con el menor valor, usaremos dos variables, una para guardar el valor menor y otra para que "recuerde" a que hora ocurri eso... Aunque habrs comprobado que eso de averiguar el valor menor no es tan "lgico" como parece... La explicacin es que al asignar el valor CERO a una variable cuando se inicializa, este valor puede ser menor que cualquiera que se haya asignado, este problema no existe si se asignan valores negativos, pero como ese no es el caso... Para solucionarlo, se pueden hacer varias cosas, aqu explico las dos que creo que cubren todas las posibilidades: 1. Si HoraValorMenor an no se ha asignado, la primera hora que se compruebe, ser la hora con el menor valor. 2. Cuando es la "primera" hora que se comprueba, ese debe ser el valor menor hasta el momento. Esto mismo se puede hacer para el valor mayor, si sabemos que pueden asignarse
112
valores negativos... ya que si as fuera, al tener un valor cero, ste sera mayor que cualquier nmero negativo...
'Ejercicio 3 Dim T%, i%, H%, Horas%(0 To 23) Randomize T = Int(Rnd * 901) + 100 'Nmero de rascadas, T valdr de 100 a 1000 For i = 1 To T H = Int(Rnd * 24) 'H valdr de 0 a 23 Horas(H) = Horas(H) + 1 Next Dim ValorMenor%, HoraValorMenor% 'Este valor ser para saber que no se ha asignado Const NoAsignado = -1 ValorMenor = 0 HoraValorMenor = NoAsignado 'Las horas van de 0 a 23 For H = 0 To 23 'Si esta hora tiene algn valor, hacer las comprobaciones If Horas(H) Then ' '===Para saber la hora que tiene el valor menor=== ' 'NOTA: 'Para que FUNCIONE, hay que hacer una de estas dos cosas: ' '1- Si no se ha asignado el valor menor... If HoraValorMenor = NoAsignado Then ValorMenor = Horas(H) HoraValorMenor = H End If '2- Si es el primer valor, ser el menor...
113
If H = 0 Then ValorMenor = Horas(H) HoraValorMenor = H End If ' 'hasta que se demuestre lo contrario... If Horas(H) < ValorMenor Then ValorMenor = Horas(H) HoraValorMenor = H End If End If Next Para Terminar: Bien, ya tenemos todas las soluciones, ahora pongmoslo todo junto y veamos para que sirve eso de NoAsignado... 'Todos los ejercicios Dim T%, i%, H%, Horas%(0 To 23) Randomize T = Int(Rnd * 901) + 100 'Nmero de rascadas, T valdr de 100 a 1000 For i = 1 To T H = Int(Rnd * 24) 'H valdr de 0 a 23 Horas(H) = Horas(H) + 1 Next Dim ValorMayor%, HoraValorMayor%, HoraInicioRascada% Dim UltimaHora% Dim ValorMenor%, HoraValorMenor% 'Este valor ser para saber que no se ha asignado Const NoAsignado = -1 ValorMayor = 0 HoraValorMayor = NoAsignado HoraInicioRascada = NoAsignado UltimaHora = NoAsignado ValorMenor = 0
114
HoraValorMenor = NoAsignado 'Las horas van de 0 a 23 For H = 0 To 23 'Si esta hora tiene algn valor, hacer las comprobaciones If Horas(H) Then ' '===Para saber la primera hora de la rascada=== ' 'Si la hora de inicio no se ha asignado If HoraInicioRascada = NoAsignado Then 'Asignar esta hora HoraInicioRascada = H End If ' '===Para saber la ultima hora de la rascada=== ' 'Si la hora no se ha asignado If H > UltimaHora Then 'Asignar esta hora UltimaHora = H End If ' '===Para saber la hora que tiene el valor mayor=== ' 'Si el valor actual es mayor... If Horas(H) > ValorMayor Then ValorMayor = Horas(H) HoraValorMayor = H End If ' '===Para saber la hora que tiene el valor menor=== ' 'NOTA: 'Para que FUNCIONE, hay que hacer una de estas dos cosas: ' '1- Si no se ha asignado el valor menor...
115
If HoraValorMenor = NoAsignado Then ValorMenor = Horas(H) HoraValorMenor = H End If '2- Si es el primer valor, ser el menor... If H = 0 Then ValorMenor = Horas(H) HoraValorMenor = H End If ' 'hasta que se demuestre lo contrario... If Horas(H) < ValorMenor Then ValorMenor = Horas(H) HoraValorMenor = H End If End If Next 'Mostrar los datos: Dim sTmp As String sTmp = "Estos son los valores:" & vbCrLf If HoraInicioRascada <> NoAsignado Then sTmp = sTmp & "Hora de inicio de rascada: " & CStr(HoraInicioRascada) & vbCrLf End If If UltimaHora <> NoAsignado Then sTmp = sTmp & "ltima hora de rascada: " & CStr(UltimaHora) & vbCrLf End If If HoraValorMenor <> NoAsignado Then sTmp = sTmp & "La hora en la que empezaste a rascarte fue: " & CStr(HoraValorMenor) & vbCrLf End If If HoraValorMayor <> NoAsignado Then sTmp = sTmp & "La hora en la que terminaste de rascarte fue: " & CStr(HoraValorMayor) & vbCrLf
116
117
PuntoY(N) = y: PuntoX(N) = x Y si lo que pretendemos es averiguar la posicin del punto A, lo sabramos as: y = PuntoY(A): x = PuntoX(A) Simplemente estaramos usando un array para la fila (PuntoY) y otro para la columna (PuntoX), para simplificar todo esto, podemos crear un tipo en el cual tendramos almacenado la posicin de cada punto, para ello, hay que hacer una declaracin de la siguiente forma: Type tPunto X As Integer Y As Integer End Type cada vez que necesitemos una variable de este Nuevo tipo, tendremos que declararla como cualquier otra variable: Dim unPunto As tPunto Mu bonito, pero cmo asignamos los valores? De una forma muy especial, para acceder a cada uno de los datos que puede almacenar nuestra variable tendremos que especificar el nombre de la variable, un punto y a continuacin la variable interna que nos interese... Vemoslo: unPunto.X = 100 unPunto.Y = 20 Para saber el valor guardado en la X de nuestra variable lo sabramos as: columna = unPunto.X Siempre usando el punto despus de la variable interna, esto puedes encontrrtelo en algunos libros o manuales, usando la expresin: "para acceder a un miembro de una estructura de datos definida por el usuario..." pero el significado, al final, es el mismo... Bien, ahora si queremos crear un array para guardar los mil puntos esos a los que me refera al principio: Dim Puntos(1000) As tPunto Para almacenar la posicin del punto N:
118
Puntos(N).X = x: Puntos(N).Y = y Y seguro que ahora sabrs como obtener la posicin del punto A. Pero tambin podemos almacenar el punto actual en una variable normal de este tipo y asignar ese valor a un elemento del array: Dim PuntoActual As tPunto PuntoActual.X = un_valor: PuntoActual.Y = otro_valor Puntos(N) = PuntoActual
Distintos tipos de variables en un tipo definido Los tipos definidos, no slo sirven para "mezclar" variables del mismo tipo, sino que puedes tener variables de varios tipos, incluso variables de tipos definidos... S, un verdadero lo... Espero que despus de leerte esta entrega y con los ejemplos que veremos en las prximas, (cuando le toque el turno al manejo de ficheros), se te aclararn las dudas.
Otro ejemplo clsico Este es uno de los ms usados para este tipo especial de variables y la verdad es que es tambin el ms usado, enseguida sabrs porqu. Situacin: Datos: El Tipo: Tener los datos de todos los colegas y otros que no lo son tanto. Nombre y apellidos, direccin, telfono fijo, telfono mvil, direccin e-mail, URL y cualquier otra cosa que se te ocurra. Para un caso como este, (simplificando un poco), podramos usar este tipo definido: Type tColega Nombre Apellidos Direccion Poblacion Edad As String As String As String As String As Integer As Long 'Esto por si
Fjate en el ltimo "campo" del tipo, es una chorrada (aunque ms de uno no pensar as), pero es para que veas que se pueden usar nombres de variables "super-largos",
119
hasta 40 caracteres! Es decir, que si te gust Mary Poppins, podras tener una variable que se llamara: MeGustaSupercalifragilisticoespialidoso. Ya en serio, no es conveniente el uso de nombres tan largos, no hagas caso de la propaganda esa que te dicen que es mejor usar nombres descriptivos, ya que es una lata tener que escribirlos!!! En caso de que te de el "punto" de escribir nombres largos, puedes hacerlo, incluso puedes usar ms de 40 letras, slo que las primeras 40 tienen significado... es decir que si al supercali... ese le aades ms letras, slo reconocer las 40 primeras. Por ejemplo: SupercalifragilisticoespialidosoChitiChitiBangBangVolando SupercalifragilisticoespialidosoChitiChitiBangBangParado Para el VB ser la variables (un momento que cuente las letras): SupercalifragilisticoespialidosoChitiChi De todas formas sigo pensando que es algo tedioso eso de escribir nombres tan largos... Otro caso de las variables, creo que este an no lo hemos visto, es este: Cuando vayamos a usar este tipo de variables para guardar los datos en ficheros (dentro de un par de entregas ya los estars usando), es conveniente "definir" la longitud mxima de las cadenas de caracteres, por ejemplo: Reservar 20 caracteres para el nombre 50 para la direccin, en este caso la declaracin de las variables se haran as: Dim Nombre As String * 20
Dim Direccion As String * 50 Y si estn en un tipo definido: Type tFijos Nombre End Type La ventaja de hacerlo as: al tener una longitud fija, podemos acceder a cualquier registro haciendo unos pequeos clculos... aunque de esto se encarga de forma automtica el propio Basic y como he dicho antes: lo veremos ms despus. As String * 20 Direccion As String * 50
Vamos con algunos ejemplos. Ya tenemos definido el tipo tColega, si queremos usarlo slo hay que DIMensionar una variable para que sea de ese tipo: Dim unColega As tColega Y para guardar el nombre de ese colega:
120
unColega.Nombre = "Pepito" Con los dems campos se hara igual. Ahora, se nos presenta la situacin de que tenemos, por poner un ejemplo, 50 colegas; as que vamos a reservar espacio para todos ellos: Dim misColegas(1 To 50) As tColega Para almacenar el nombre del colega nmero uno: misColegas(1).Nombre = "Maria de las Mercedes" Para mostrarlos, simplemente hacemos un bucle que recorra este array y asunto concluido: For i = 1 To 50 Print misColegas(i).Nombre, misColegas(i).Apellidos, ...etc. Next Que quieres imprimir de forma aleatoria uno de los 50 nombres, digamos para gastarle una inocentada, pues haces esto: Print misColegas(Int(Rnd * 50) + 1).Nombre Ya sabes, si no lo sabias, ahora lo sabrs, que el ndice de un array, el numrico ese que se pone dentro de los parntesis, puede ser cualquier expresin numrica, que de como resultado un valor que est dentro de los lmites de la cantidad de variables que tiene ese array... S, es que si ese valor no est "dentro" de los elementos que tienes dimensionados, te "regaar" el VB dicindote: Index Out of Range (o sea: T'as pasao, colega)
Un alto en el camino. Toma papel y lpiz, porque esto es una nueva instruccin. Ya has visto en el ejemplo de imprimir los 50 nombres, que cada vez que accedes a uno de los campos (o variables internas) del tipo definido, tienes que usar el nombre de la variable el punto y despus el campo. Pues a partir del VB4, este asunto se ha simplificado y no slo para los tipos definidos, ya vers, en un futuro no muy lejano, calculo que antes del ao 2010, que se puede usar en todas las situaciones en las que "algo" tenga otros "algos" dentro de l y haya que acceder por medio del punto... Si no lo captas, no te preocupes, ya te enterars bien...
121
La palabra mgica es: WITH No te voy a hacer una presentacin formal de esta instruccin, ya tienes el manual del VB o la ayuda y all seguro que estar bien "definida", vamos a ver cmo usarla en el ejemplo este que nos traemos entre manos: For i = 1 To 50 With misColegas(i) Print .Nombre, .Apellidos, .Direccion, ...etc. End With Next Ves que fcil! Hasta he puesto otro de los campos... De esta forma no tienes que repetir el nombre de la variable, el Visual ya sabe que te ests refiriendo a misColegas(i), porque esa es la variable que has usado despus de With. Esto mismo se puede usar con cualquier objeto del VB, los tipos definidos no son objetos, pero se parecen, en unas cuantas de miles de entregas ms, te enterars del porqu... Por ejemplo para asignar varias de las propiedades de un TextBox llamado Text1: With Text1 .SelStart = 0 .SelLength = Len(.Text) End With Este mismo ejemplo sin With, como lo tendran que hacer con el VB3, sera esto: Text1.SelStart = 0 Text1.SelLength = Len(Text1.Text) Como comprobars, est ms claro si se usa el With Adems, se pueden anidar varios Withs... unos dentro de otros, pero siempre el PUNTO har referencia al ltimo que se ha puesto, esta situacin ni la voy a "ejemplificar" ya que cuando le toque el turno, le tocar...
Bueno, vamos a dejarlo por ahora; en la siguiente parte de esta entrega, usaremos todo esto en un pequeo programa en el cual se introducirn los datos y todas esas cosas... Pero eso ser maana (hoy realmente, pero ms tarde) En seguida nos vemos.
122
1.- Poder modificar uno de los colegas. Este es el cdigo a insertar en el Command3_Click:
Cdigo para modificar un colega El truco ser primero mostrar slo un colega y a continuacin, modificar los campos que queramos, el cdigo del Command2_Click quedara as, para que al escribir un nmero en el Text7, sea este colega el que se muestre, en caso de que el nmero introducido sea menor que 1 o mayor que el nmero actual de colegas, se mostrarn todos.
123
124
Cuando inicias el VB, se crea un nuevo proyecto con un form por defecto, as que ese paso lo puedes conseguir simplemente cargando el Visual Basic. Una vez que tienes esto, te mostrar una pantalla como esta:
Pantalla de inicio del VB4 A la izquierda est la barra con los controles que se pueden usar, pulsa (doble click) en el que tiene la A, esto situar una etiqueta llamada Label1, en el centro del form... sitala en la esquina superior izquierda, para ello plsala con el botn izquierdo del ratn y arrstrala hasta arriba y a la izquierda... Pulsa otras cinco veces... y ve colocndolas debajo de la anterior, es decir una debajo de otra... Ahora debes pulsar el que est al lado de la etiqueta: (textbox) y haz la misma operacin, pero los sita junto a cada una de las etiquetas anteriores. Una vez terminado todo el proceso, debers tener seis etiquetas y seis cajas de texto. Por ltimo pulsa en el botn que est debajo de la caja de texto y los colocas en la parte inferior derecha, pulsa de nuevo en el mismo objeto y lo pones justo al lado del anterior, al final debes tener algo como esto:
125
El form de prueba, con todos los controles Hay una forma ms rpida de hacerlo... y que adems te permite, si quieres, crear arrays de controles. Sera pulsando en el Label una vez, a continuacin en el TextBox. Los seleccionas y le das a Edicin/Copiar, tambin con el botn derecho del ratn. Cmo los seleccionas para poder copiarlo? Pulsa el Label, pulsa la tecla Control, dejando pulsada la tecla control, pulsa en el TextBox, vers que se quedan los dos "resaltados", ahora suelta la tecla Control, en el men de Edicin, selecciona Copiar (o Copy si tienes la edicin inglesa). Ya estn copiados en la memoria del VB, ahora en el men Edicin selecciona Pegar (Paste en guiri), te mostrar un mensaje de que si quieres crear un array del Label1, pulsa Si o No, dependiendo de que quieras crear ese array o no, en el Label haz lo que quieras, pero cuando te pregunte si quieres crearlo del TextBox, dile que no... Sita los nuevos controles debajo de los anteriores y repite la operacin, pero en esta ocasin slo tienes que volver a pegar... ya que an siguen copiados en memoria. Ahora ocurrirn dos cosas, dependiendo de si le dijiste que SI o NO a la creacin de arrays del Label, en caso de haberle dicho SI, slo preguntar si quieres crear un array de TextBox1; por otro lado, si le contestaste No, te preguntar de nuevo si quieres crear el array de Label1 y despus te interrogar sobre el TextBox1... Reptelo hasta que tengas 6 controles de cada en el form y despus haces lo de los botones... Esto lo practicas unas 500.000 veces y acabas por cogerle el "tranquillo"... 8-) Un detalle: el texto que te mostrar ser Label1 y Text1, pero los nombres de los controles sern diferentes, sino me crees, ve pulsando cada uno de ellos y busca la propiedad Name de la ventana esa que hay a la derecha, la que pone Properties - Form1. Lo que interesa es que tengas los controles mostrados en la figura anterior y que los TextBoxes tengan los nombres Text1, Text2... hasta Text6, los botones deben llamarse Command1 y Command2.
126
Ahora abre el panel de cdigo, para hacer esto... cosa que a estas alturas ya deberas saber, es pulsando en el botn "View Code", ese que est en la ventana del proyecto, s, esa... la de la esquina superior derecha. Escribe esto:
Con esto acabamos de declarar el tipo definido, una constante con el nmero mximo de colegas que por ahora queremos tener, un array para almacenar los datos de esos colegas y una variable que ir llevando la cuenta de los colegas que tenemos actualmente. Volvamos al form, pulsa en el botn Command1 y en la ventana de propiedades busca la que pone Caption, selecciona el texto Command1 y escribe esto: "Nuevo Colega", ahora pulsa en el Commad2 y cambia el caption por "Mostrar". En cada uno de los Labels, empezando por el de arriba, escribe el nombre de los campos de que se componen nuestro tipo definido, en el ltimo, puedes poner algo ms corto... por ejemplo: Veces. El siguiente cdigo lo pones en el Form_Load, para que se ejecute cuando inicies el proyecto.
127
Cdigo que se ejecutar al pulsar en el Command1 Y por ltimo, escribe esto en el Command2_Click:
128
Cdigo que se ejecutar al pulsar en Command2 Con esto, ya puedes escribir los datos correspondientes y despus pulsas en Nuevo Colega, los datos escritos se asignarn a cada uno de los campos del tipo definido, como estamos usando un array, hay que especificar el nmero en el que queremos insertar esos datos, la variable Colega se va incrementando y en caso de que pulsemos en nuevo colega y ya tengamos el nmero mximo, nos mostrar un mensaje indicndonos que ya no hay espacio para ms. El botn de mostrar los datos, lo que hace es que va mostrando cada uno de los colegas que tenemos... te los tienes que ver todos... as que... prepara el cuerpo para los ejercicios de esta entrega, ah van: 1. Poder modificar uno de los colegas. Pista: Aade un nuevo TextBox, un nuevo CommandButton, en el caption del botn escribes: "Modificar" y el nmero que introduzcas en el nuevo TextBox ser el colega a modificar. 2. Mostrar los colegas a partir de un nmero determinado , por ejemplo, si en ese TextBox escribes 5, mostrar desde el 5 hasta el ltimo introducido, puedes usar el mismo botn Mostrar, de forma que si pones 0 1, te muestre todos. Las soluciones estn en este link, (es que como no lo haga ahora, se me olvida...), pero no hagas trampas e intntalo primero... ya sabes que slo t sabrs si ests jugando limpio.
129
Y hasta aqu ha llegado esta undcima entrega, espero que no te impacientes hasta la siguiente, pero para que te vayas haciendo el cuerpo, vamos a tratar el manejo de ficheros, es decir guardar y recuperar datos del disco... As que atento y espero, como es costumbre, tu comentario sobre esta entrega o sobre el curso en general... pero no me hagas trampas y aproveches el link para hacerme consultas... que el consultorio del Dr. Guille est cerrado temporalmente... ;-) Nos vemos. Guillermo
130
Dim PuntoY(1000) As Integer, PuntoX(1000) As Integer Cuando necesitemos dibujar el punto N en X,Y, haramos algo como esto: PuntoY(N) = y: PuntoX(N) = x Y si lo que pretendemos es averiguar la posicin del punto A, lo sabramos as: y = PuntoY(A): x = PuntoX(A) Simplemente estaramos usando un array para la fila (PuntoY) y otro para la columna (PuntoX), para simplificar todo esto, podemos crear un tipo en el cual tendramos almacenado la posicin de cada punto, para ello, hay que hacer una declaracin de la siguiente forma: Type tPunto X As Integer Y As Integer End Type cada vez que necesitemos una variable de este Nuevo tipo, tendremos que declararla como cualquier otra variable: Dim unPunto As tPunto Mu bonito, pero cmo asignamos los valores? De una forma muy especial, para acceder a cada uno de los datos que puede almacenar nuestra variable tendremos que especificar el nombre de la variable, un punto y a continuacin la variable interna que nos interese... Vemoslo: unPunto.X = 100 unPunto.Y = 20 Para saber el valor guardado en la X de nuestra variable lo sabramos as: columna = unPunto.X Siempre usando el punto despus de la variable interna, esto puedes encontrrtelo en algunos libros o manuales, usando la expresin: "para acceder a un miembro de una estructura de datos definida por el usuario..." pero el significado, al final, es el mismo... Bien, ahora si queremos crear un array para guardar los mil puntos esos a los que me refera al principio:
131
Dim Puntos(1000) As tPunto Para almacenar la posicin del punto N: Puntos(N).X = x: Puntos(N).Y = y Y seguro que ahora sabrs como obtener la posicin del punto A. Pero tambin podemos almacenar el punto actual en una variable normal de este tipo y asignar ese valor a un elemento del array: Dim PuntoActual As tPunto PuntoActual.X = un_valor: PuntoActual.Y = otro_valor Puntos(N) = PuntoActual
Distintos tipos de variables en un tipo definido Los tipos definidos, no slo sirven para "mezclar" variables del mismo tipo, sino que puedes tener variables de varios tipos, incluso variables de tipos definidos... S, un verdadero lo... Espero que despus de leerte esta entrega y con los ejemplos que veremos en las prximas, (cuando le toque el turno al manejo de ficheros), se te aclararn las dudas.
Otro ejemplo clsico Este es uno de los ms usados para este tipo especial de variables y la verdad es que es tambin el ms usado, enseguida sabrs porqu. Situacin: Datos: El Tipo: Tener los datos de todos los colegas y otros que no lo son tanto. Nombre y apellidos, direccin, telfono fijo, telfono mvil, direccin e-mail, URL y cualquier otra cosa que se te ocurra. Para un caso como este, (simplificando un poco), podramos usar este tipo definido: Type tColega Nombre Apellidos Direccion Poblacion Edad As String As String As String As String As Integer As Long 'Esto por si
VecesQueLeHeMandadoUnMailYNoContesta
132
Fjate en el ltimo "campo" del tipo, es una chorrada (aunque ms de uno no pensar as), pero es para que veas que se pueden usar nombres de variables "super-largos", hasta 40 caracteres! Es decir, que si te gust Mary Poppins, podras tener una variable que se llamara: MeGustaSupercalifragilisticoespialidoso. Ya en serio, no es conveniente el uso de nombres tan largos, no hagas caso de la propaganda esa que te dicen que es mejor usar nombres descriptivos, ya que es una lata tener que escribirlos!!! En caso de que te de el "punto" de escribir nombres largos, puedes hacerlo, incluso puedes usar ms de 40 letras, slo que las primeras 40 tienen significado... es decir que si al supercali... ese le aades ms letras, slo reconocer las 40 primeras. Por ejemplo: SupercalifragilisticoespialidosoChitiChitiBangBangVolando SupercalifragilisticoespialidosoChitiChitiBangBangParado Para el VB ser la variables (un momento que cuente las letras): SupercalifragilisticoespialidosoChitiChi De todas formas sigo pensando que es algo tedioso eso de escribir nombres tan largos... Otro caso de las variables, creo que este an no lo hemos visto, es este: Cuando vayamos a usar este tipo de variables para guardar los datos en ficheros (dentro de un par de entregas ya los estars usando), es conveniente "definir" la longitud mxima de las cadenas de caracteres, por ejemplo: Reservar 20 caracteres para el nombre 50 para la direccin, en este caso la declaracin de las variables se haran as: Dim Nombre As String * 20
Dim Direccion As String * 50 Y si estn en un tipo definido: Type tFijos Nombre End Type La ventaja de hacerlo as: al tener una longitud fija, podemos acceder a cualquier registro haciendo unos pequeos clculos... aunque de esto se encarga de forma automtica el propio Basic y como he dicho antes: lo veremos ms despus. As String * 20 Direccion As String * 50
133
Ya tenemos definido el tipo tColega, si queremos usarlo slo hay que DIMensionar una variable para que sea de ese tipo: Dim unColega As tColega Y para guardar el nombre de ese colega: unColega.Nombre = "Pepito" Con los dems campos se hara igual. Ahora, se nos presenta la situacin de que tenemos, por poner un ejemplo, 50 colegas; as que vamos a reservar espacio para todos ellos: Dim misColegas(1 To 50) As tColega Para almacenar el nombre del colega nmero uno: misColegas(1).Nombre = "Maria de las Mercedes" Para mostrarlos, simplemente hacemos un bucle que recorra este array y asunto concluido: For i = 1 To 50 Print misColegas(i).Nombre, misColegas(i).Apellidos, ...etc. Next Que quieres imprimir de forma aleatoria uno de los 50 nombres, digamos para gastarle una inocentada, pues haces esto: Print misColegas(Int(Rnd * 50) + 1).Nombre Ya sabes, si no lo sabias, ahora lo sabrs, que el ndice de un array, el numrico ese que se pone dentro de los parntesis, puede ser cualquier expresin numrica, que de como resultado un valor que est dentro de los lmites de la cantidad de variables que tiene ese array... S, es que si ese valor no est "dentro" de los elementos que tienes dimensionados, te "regaar" el VB dicindote: Index Out of Range (o sea: T'as pasao, colega)
Un alto en el camino. Toma papel y lpiz, porque esto es una nueva instruccin.
134
Ya has visto en el ejemplo de imprimir los 50 nombres, que cada vez que accedes a uno de los campos (o variables internas) del tipo definido, tienes que usar el nombre de la variable el punto y despus el campo. Pues a partir del VB4, este asunto se ha simplificado y no slo para los tipos definidos, ya vers, en un futuro no muy lejano, calculo que antes del ao 2010, que se puede usar en todas las situaciones en las que "algo" tenga otros "algos" dentro de l y haya que acceder por medio del punto... Si no lo captas, no te preocupes, ya te enterars bien... La palabra mgica es: WITH No te voy a hacer una presentacin formal de esta instruccin, ya tienes el manual del VB o la ayuda y all seguro que estar bien "definida", vamos a ver cmo usarla en el ejemplo este que nos traemos entre manos: For i = 1 To 50 With misColegas(i) Print .Nombre, .Apellidos, .Direccion, ...etc. End With Next Ves que fcil! Hasta he puesto otro de los campos... De esta forma no tienes que repetir el nombre de la variable, el Visual ya sabe que te ests refiriendo a misColegas(i), porque esa es la variable que has usado despus de With. Esto mismo se puede usar con cualquier objeto del VB, los tipos definidos no son objetos, pero se parecen, en unas cuantas de miles de entregas ms, te enterars del porqu... Por ejemplo para asignar varias de las propiedades de un TextBox llamado Text1: With Text1 .SelStart = 0 .SelLength = Len(.Text) End With Este mismo ejemplo sin With, como lo tendran que hacer con el VB3, sera esto: Text1.SelStart = 0 Text1.SelLength = Len(Text1.Text) Como comprobars, est ms claro si se usa el With Adems, se pueden anidar varios Withs... unos dentro de otros, pero siempre el PUNTO har referencia al ltimo que se ha puesto, esta situacin ni la voy a "ejemplificar" ya que cuando le toque el turno, le tocar...
135
Bueno, vamos a dejarlo por ahora; en la siguiente parte de esta entrega, usaremos todo esto en un pequeo programa en el cual se introducirn los datos y todas esas cosas... Pero eso ser maana (hoy realmente, pero ms tarde) En seguida nos vemos.
136
El inconveniente es, como ya he repetido, que para acceder a un dato en concreto, se deben "leer" todos los anteriores. Esta inconveniencia del acceso secuencial se arregla usando el acceso aleatorio. Con este tipo de acceso, puedes leer del fichero el dato almacenado en cualquier posicin, sin tener que leer primero todos los anteriores... Pero, no todo es perfecto... tambin tiene un "pequeo" inconveniente... que los datos guardados en un fichero aleatorio deben ocupar el mismo espacio, o sea que sean de la misma longitud... Si guardas cadenas de caracteres, todas ocuparn el mismo espacio en el disco, aunque unas tengan ms caracteres "vlidos" que otras... Tambin se pueden mezclar nmeros y cadenas de caracteres... pero eso tiene tambin sus "inconvenientes" o mejor dicho su "truco" para poder usarlo sin armar un KAOS... Ya que estamos con los distintos tipos de acceso, te dir as por encima de que va el tipo Binario, ste es un poco especial y permite leer la informacin almacenada de la forma que queramos, todo depender de la longitud de la variable que usemos para acceder al fichero... todo estas cosas quedarn explicadas y aclaradas en esta o en prximas entregas... Bien, ya sabes que tipos de ficheros puedes manejar con el Visual Basic, ahora vamos a ver como hacerlo, por supuesto, con las instrucciones correspondientes para poder hacerlo, ya que de eso se trata... o es que esperabas poder acceder a la informacin de los ficheros sin usar instrucciones del VB? Cada vez que quieras abrir un fichero, tienes que usar un nmero de "canal" por el que VB nos suministrar la informacin, este canal. El canal se indica por medio de un nmero de 1 a 255. Gracias a este nmero, el Basic se comunica con el sistema operativo para acceder a los datos. El Basic nos facilita la tarea de conseguir ese nmero, con idea de que no usemos una lnea que est en uso... La instruccin, en realidad es una funcin, para conseguir un nmero de canal libre, es: Freefile. Esta funcin devuelve un nmero entero, el cual se almacenar en una variable y as podremos usarlo para el manejo de los datos almacenados. NumFic = Freefile Una vez que conozcamos un canal por el que poder acceder, tendremos que abrirlo: Open "Prueba.txt" For Output As NumFic Con esta lnea, abrimos el fichero Prueba.txt de forma secuencial para poder escribir en l. Una vez que tenemos una "va" de comunicacin, podremos escribir informacin usando una versin un poco maquillada de la instruccin Print... El maquillaje es el nmero de canal con el que podemos acceder al fichero: Print #NumFic, "Lo que sea" #NumFic es el nmero de fichero (o canal) por el que accedemos al fichero abierto y despus de ese nmero, usamos una coma y a continuacin lo que queremos guardar en el fichero. Cuando hayamos acabado de guardar cosas, tendremos que cerrar el fichero que hemos abierto, para poder liberar ese canal abierto y as poder usarlo en otra ocasin, esto se consigue con el comando Close: Close NumFic Es importante esto de cerrar el fichero abierto, ya que en ese momento es cuando el Basic guarda la informacin que an tiene "temporalmente" almacenada en una memoria intermedia que usa para que el acceso a datos sea, al menos en teora, ms rpido. A esta memoria intermedia se le llama "buffer". El VB la usa para ir guardando la informacin que vamos a grabar fsicamente en el disco, antes de grabarla, la guarda ah y cuando est llena, la escribe en el disco y la libera, esto se consigue con el close, para asegurarnos que todo lo que tenga que estar guardado, realmente lo est. El valor de este
137
bfer para los ficheros secuenciales y aleatorios puede ser de 32767 bytes como mximo, antes con el Basic del DOS el valor por defecto era de 128 bytes y el mximo de 255 caracteres, pero esto hace tiempo que cambi y ahora incluso, (al menos en el acceso de 32 bits), aunque en la ayuda no lo indique as, puede ser mayor que todo eso... Ya tendremos ocasin de comprobarlo. Todo esto est muy bien, pero si quieres especificar esa longitud... cmo y/o dnde se especifica? Ahora sabrs cmo y dnde. Para ello vamos a ver cmo se usa al completo la orden OPEN y sus posibilidades de uso. Open RutaAcceso [For Modo] [Access acceso] [tipo de bloqueo] As [#]nmerofichero [Len=longitudregistro] Lo que est entre corchetes son parmetros opcionales. Fjate en el detalle que FOR Modo est entre corchetes, esto significa que si no se especifica el modo, el Visual Basic entiende que quieres acceder de forma aleatoria. La explicacin de cada uno de estos parmetros los tienes en la ayuda, as que si no quieres esperar a que los explique todos, vete a la ayuda y le echas un vistazo. Yo empezar a explicarte lo que ahora necesitas saber y poco a poco iremos viendo las distintas posibilidades... Pero si no quieres esperar... ya sabes... echa mano del F1 y accede a la explicacin de la ayuda o del manual... Vamos a ver lo que nos interesa de esta instruccin: El path completo, o a medias, de dnde queremos que se almacene el fichero o el lugar en el que est almacenado. Por ejemplo: C:\Datos\Un directorio\Prueba.txt .\Algo\Prueba.txt, siempre que en el directorio actual haya un directorio que se llame "Algo" o simplemente Prueba.txt (esto le indicar que estar en el directorio actual) Output, para ficheros de salida, es decir para guardar los datos. Si el fichero existe, lo borrar (sobrescribir) y si no existe, lo crear. Modo Input, para leer los datos de un fichero ya existente. Append, como el Output, pero aadiendo la informacin al final del fichero, si este ya existe. Random, para acceso aleatorio. Binary, para acceso binario.
RutaAcceso
As NmeroFichero
Aqu se indica el nmero de fichero (canal) por el que accederemos a la informacin. El signo de nmero (#) es opcional. Y NmeroFichero, puede ser una variable o una constante.
138
Las otras opciones ya las veremos, ahora nos centraremos en las cosas que son ms fciles, siempre hay tiempo para complicarse la vida, as que nos la complicaremos ms adelante, cuando ya tengamos un poco de idea de todo este folln... RutaAcceso, a estas alturas deberas saber de que va todo esto del PATH, pero si no lo sabes, te lo explico por encima: un path es una ruta de acceso a un fichero... comor? Pues eso, si quieres guardar la informacin en el disco, tendrs que saber en que parte del disco la quieres guardar, incluso en que disco quieres almacenarla. Y lo ms importante, cmo vas a llamar el sitio en el que se guardar. Esto es un poco como las variables, si quieres tener distintas cosas en la memoria del Basic, usas distintos nombres de variables, pues lo mismo con los ficheros, usando distintos nombres de ficheros puedes tener informacin diferente almacenada en el disco. Para empezar, debes saber que tienes que usar un nombre en el que almacenar la informacin que quieres "conservar", para despus poder acceder a ella en el momento que la necesites. La ventaja de esto con respecto a los nombres de las variables es que puedes usar distintas partes del disco para guardar esa informacin, aunque el nombre "real" del fichero sea el mismo... A ver, si quieres guardar los rascones esos que te dabas en las entregas anteriores, puedes decirle al Basic que quieres usar un fichero que se llame: rascones. Pero suponte que quieres tener todos los rascones de todos los meses del ao almacenados en distintos ficheros, uno para cada mes. Podras hacer algo como esto: darle a cada fichero un nombre diferente o bien usar la "extensin" del fichero para cada uno de los meses... Por ejemplo: rascones.ene para enero, rascones.dic para los de diciembre... etc. Y si quieres que esos datos se guarden en el disco A, pues slo tienes que decirle que el fichero se llama: A:\rascones.ene Si tienes la intencin de guardar cada grupo de ficheros en carpetas (directorios) diferentes, tambin puedes indicarselo en la ruta esta de acceso: C:\Datos\A1998\rascones.ene Por supuesto para poder hacer esto ltimo debes tener un disco C (quin no lo tiene?), un directorio A1998 que est dentro de otro llamado Datos que est a su vez en el directorio raz del mencionado disco C. En caso que no se especifique la ruta completa, el Visual Basic usar el directorio actual para acceder al fichero. Debes saber que el visual crear el fichero indicado, pero si no existen los directorios o no puede tener acceso a ellos, dar error y no abrir el fichero. Que error? El nmero 76: Path not found (No se ha encontrado la ruta de acceso) Hay ms errores, muchos, pero estos ya te los irs encontrando y en su momento veremos cmo poder detectarlos. Veremos tambin cmo crear las rutas esas de acceso, en caso de que no existan, para as asegurarnos que existen antes de guardar la informacin en el disco... pero todo a su debido tiempo... Ahora lo que vamos a ver es unos ejemplos de cmo guardar informacin y despus poder "leerla", ya que esto es lo ms bsico y lo que en principio debemos saber.
Cmo guardar la informacin? Ya te he dicho antes de que con Print se puede guardar la informacin en el disco, veamos cmo: Print #NumFic, Nombre Print #NumFic, 125
139
Tambin podemos guardar esta misma informacin as: Print #NumFic, Nombre, 125 Es decir que si quieremos guardar varias cosas con una misma instruccin, lo haremos usando una coma como separador. De esta forma cada cosa que est separada se guardar en el disco en "lneas" distintas.
Cmo leer la informacin? Para poder leer la informacin, adems de abrir el archivo para lectura modo INPUT, hay que usar una de estas instrucciones: Input #NumFic, Variable Tambin con: Line Input #NumFic, variable. La diferencia entre el Input y el Line Input la veremos dentro de un ratillo. Antes tendremos que ver cmo acceder a ese nombre y a ese nmero que antes hemos guardado... Input #NuFic, unNombre, unNumero Un detalle que debes tener en cuenta es que si el Nombre que guardamos tiene alguna coma, puede que no accedas a los datos como pretendas... Vamos a verlo con un ejemplo. Crea un nuevo proyecto en el VB y aade dos botones (CommandButton), escribe este cdigo y pruebas: Private Sub Command1_Click() Dim Nombre$, Num% Dim NumFic% Nombre = "Prez, Pepito" Num = 22 NumFic = FreeFile Open "C:\Prueba.txt" For Output As NumFic Print #NumFic, Nombre, Num Close NumFic End Sub Private Sub Command2_Click() Dim Cadena$, Numero% Dim nF% nF = FreeFile Open "C:\Prueba.txt" For Input As nF Input #nF, Cadena, Numero
140
Close nF MsgBox "Cadena= " & Cadena & vbCrLf & _ "Nmero= " & Numero End Sub Ahora pulsa en F5 y dale primero al botn Command1, despus le das al Command2 y vers que no te muestra lo esperado. En lugar de mostrar: Cadena= Prez, Pepito Nmero= 22 Te ha mostrado: Cadena= Prez Nmero= 0 Que ha ocurrido? En primer lugar, decirte que esto mismo con el Basic del MS-DOS hubiese dado un error, pero debido a como maneja el VB las variables, se ha tragado lo que ha encontrado... Que ha encontrado? Pues que tiene que asignar a Cadena la "palabra" Prez y a la variable Numero el "nmero" Pepito... que al no ser un nmero, le ha dado el valor cero... Esto es debido a que cuando INPUT lee los datos espera una coma o el final de lnea para "distinguir" entre los diferentes datos a asignar a las variables indicadas. Vale, dirs, pongmoslo en distintas lneas y as los leer correctamente: Input #nF, Cadena Input #nF, Numero Pero con esto no lo solucionars, prubalo y vers que tengo razn. De gente desconfiada est el mundo lleno! ...no te he dicho que dara el mismo resultado... HUM! Bien, cmo solucionarlo? Lo has adivinado? Pues eso mismo, usando el Line Input... Pero con Line Input no se pueden especificar ms de una variable en la misma instruccin... as que ponlas en dos lneas. Line Input #nF, Cadena Line Input #nF, Numero OPS! Que ha ocurrido? Si has pulsado simplemente F5, te habr dado un error al pulsar en el segundo botn... Y si has pulsado Control+F5, te habr indicado, con el mismo error, que los tipos no coinciden, (si usas la versin inglesa: Type Mismatch) Esto es debido a que Line Input slo puede leer cadenas de caracteres, mejor dicho slo se pueden usar variables de tipo string (cadena), ya que esta instruccin lee todo lo que hay en la lnea actual del fichero abierto, hasta el final de la lnea.
141
Cmo solucionarlo? Usando una variable intermedia o simplemente usando el INPUT normal para leer el nmero. Veamos cmo sera de las dos formas: Line Input #nF, Cadena Input #nF, Numero Dim sTmp$ Line Input #nF, Cadena Line Input #nF, sTmp Numero = Val(sTmp) Ahora si que tendremos el resultado correcto: Cadena= Prez, Pepito Nmero= 22 EXACTO! Tampoco nos ha mostrado esto... Por qu? Muy sencillo, realmente no es sencillo, sino que despus de que me haya ocurrido como dos millones de veces, resulta hasta lgico... 8-( Si miras el contenido del fichero C:\Prueba.txt, te dars cuenta de que el contenido de este fichero es: Prez, Pepito 22 Entre Pepito y el 22 hay un tabulador, Chr$(9). Esto es debido a que Print x, y muestra los valores en distintas "posiciones" de tabulacin, lo mismo ocurre cuando se guarda en el disco... Para solucionar todo esto y hacer que la cosa funcione bien, te aconsejo que cada dato lo guardes con distintas instrucciones Print, de esta forma cada dato se guarda en distintas lneas del fichero. As que cambia el cdigo del Command1, para que en lugar de un slo Print, haya dos: Print #NumFic, Nombre Print #NumFic, Num Ahora todo debe funcionar bien. Vale, prubalo si no te fas... Tena yo razn? Pues claro, ...ya que lo he comprobado antes... ;-) Si no sabemos el tipo de datos que tenemos almacenado, lo mejor es usar la instruccin Line Input y as nos curamos en salud, pero si sabemos que, por ejemplo, todos los datos son numricos y se han almacenado sin usar comas... Mejor un ejemplo: En este caso vamos a guardar en un array una serie de nmeros aleatorios, los vamos a guardar en un fichero y despus los leeremos para asignarlos en otro array y los mostraremos en un label. Para hacerlo, crea un nuevo proyecto, el anterior lo puedes borrar ya que es de una inutilidad total. Aade un Label que ocupe prcticamente todo el Form, salvo la parte de abajo, en la que pondrs dos botones. Pega el cdigo este que te pongo, pulsa F5 y primero pulsa en el Command1, para despus pulsar en el Command2
142
Private Sub Command1_Click() Dim Numeros(1 To 10) As Integer Dim i% Dim nFic% Randomize 'Asignamos los valores For i = 1 To 10 Numeros(i) = Int(Rnd * 100) + 1 Next 'Abrimos el fichero nFic = FreeFile Open "C:\Prueba.txt" For Output As nFic For i = 1 To 10 Print #nFic, Numeros(i) Next Close nFic Label1 = "Nmeros guardados en el disco" End Sub
Private Sub Command2_Click() Dim MasNumeros(1 To 10) As Integer Dim i% Dim nFic% 'Abrimos el fichero para leer los datos nFic = FreeFile Open "C:\Prueba.txt" For Input As nFic For i = 1 To 10 Input #nFic, MasNumeros(i) Next Close nFic 'Asignamos estos nmeros al label: Label1 = "" For i = 1 To 10
143
Label1 = Label1 & MasNumeros(i) & vbCrLf Next End Sub Este es un ejemplo sencillo de cmo asignar datos a un array, guardarlos en el disco y despus leerlos. Y hasta aqu hemos llegado... Como ejercicio, haz un programa que al pulsar en un botn, te pida diez nombres, los guarde en un fichero y despus pulsando en otro botn los muestre en un label. No es necesario que uses un array para guardar los datos, pero podras hacer dos versiones, con y sin un array. Como pista te recordar que la funcin InputBox puede servirte para esto de preguntar, ya que el valor que devuelve es la cadena de caracteres introducida en la caja de dilogo que muestra. Esto del InputBox ya lo vimos en la novena entrega, o en las soluciones, pero te explico brevemente cmo funciona: variable$=InputBox("Escribe un nombre") Facil, verdad? Pues esa es toda la pista que te voy a dar. Ahora se "legal" y no veas las soluciones de esta entrega hasta que lo hayas hecho t. En la pgina de las soluciones tienes un "extra", as que aunque sepas cmo hacerlo... te pasas a verla... vale? Espero que te haya resultado instructiva esta entrega, a pesar de haberte dejado con la miel en la boca, pero as son las cosas y no es plan de darlo todo de golpe. Para cuando la siguiente entrega? Ah!, misterios de la vida... eso ni se sabe. As que permanece a la escucha y ya vers cuando... no quiero prometer que ser pronto, que despus me regaas... as, que... a esperar! Si hay algo que no entiendas o simplemente quieres hacer algn comentario sobre esta entrega o cualquier otro tipo de peloteo o lo que te de la gana decirme sobre el curso bsico, usa este link... Pero no lo aproveches para las consultas... que te conozco rosco! Nos vemos pronto. Guillermo
Como ejercicio, haz un programa que al pulsar en un botn, te pida diez nombres, los guarde en un fichero y despus pulsando en otro botn los muestre en un label. No es necesario que uses un array para guardar los datos, pero podras hacer dos versiones, con y sin un array. Solucin 1: Sin usar Array: Private Sub Command1_Click() Dim i% Dim nFic% Dim Nombre$ 'Solucin 1, sin array nFic = FreeFile 'Abrimos el fichero para almacenar los datos Open "C:\Prueba.txt" For Output As nFic For i = 1 To 10 Nombre = InputBox("Escribe el nombre nmero " & CStr(i)) Print #nFic, Nombre Next Close nFic End Sub Private Sub Command2_Click() Dim i% Dim nFic% Dim Nombre$ 'Solucin 1, sin array Label1 = "" nFic = FreeFile 'Abrimos el fichero para leer los datos Open "C:\Prueba.txt" For Input As nFic For i = 1 To 10 Input #nFic, Nombre Label1 = Label1 & Nombre & vbCrLf Next Close nFic End Sub
145
Solucin 2: Usando un Array: Private Sub Command1_Click() Dim i% Dim nFic% Dim losNombres(1 To 10) As String 'Solucin 1, sin array 'Primero preguntamos los nombres For i = 1 To 10 losNombres(i) = InputBox("Escribe el nombre nmero " & CStr(i)) Next 'Ahora los guardamos nFic = FreeFile Open "C:\Prueba.txt" For Output As nFic For i = 1 To 10 Print #nFic, losNombres(i) Next Close nFic End Sub
Private Sub Command2_Click() Dim i% Dim nFic% Dim variosNombres(1 To 10) As String 'Solucin 2, con array nFic = FreeFile Open "C:\Prueba.txt" For Input As nFic 'Leer los diez nombres For i = 1 To 10 Line Input #nFic, variosNombres(i) Next Close nFic
146
'mostrar los nombres Label1 = "" For i = 1 To 10 Label1 = Label1 & variosNombres(i) & vbCrLf Next End Sub Fjate que con esta segunda forma, si usas un array a nivel de mdulo o global, puedes mostrar los datos cuando quieras, leyndolos una vez y despus mostrndolos en cualquier ocasin, hasta que asignes nuevos datos. Lo mismo ocurre al pedir esos nombres y despus guardndolos en el disco cuando quieras. En el siguiente listado, puedes ver un ejemplo (simple) de esto que te digo. Fjate que a la hora de leer los nombres, primero se comprueba si existe el fichero, esto ya lo vimos en la entrega del IF...THEN Option Explicit Dim losNombres(1 To 10) As String
Private Sub cmdGuardar_Click() Dim i% Dim nFic% 'Ahora los guardamos nFic = FreeFile Open "C:\Prueba.txt" For Output As nFic For i = 1 To 10 Print #nFic, losNombres(i) Next Close nFic Label1 = "Datos guardados correctamente" End Sub
147
Dim nFic% 'Nos aseguramos que exista el fichero: If Len(Dir$("C:\Prueba.txt")) Then nFic = FreeFile Open "C:\Prueba.txt" For Input As nFic 'Leer los diez nombres For i = 1 To 10 Line Input #nFic, losNombres(i) Next Close nFic Label1 = "Datos ledos correctamente" Else Label1 = "No existe el fichero de nombres" End If End Sub
Private Sub cmdMostrar_Click() Dim i% 'mostrar los nombres Label1 = "" For i = 1 To 10 Label1 = Label1 & losNombres(i) & vbCrLf Next End Sub
Private Sub cmdPreguntar_Click() Dim i% Label1 = "" 'Borramos el contenido anterior For i = 1 To 10 losNombres(i) = ""
148
Next 'Tambin se puede hacer as: (esto es ms rpido) 'ReDim losNombres(1 To 10) 'Preguntamos los nombres For i = 1 To 10 losNombres(i) = InputBox("Escribe el nombre nmero " & CStr(i)) Next End Sub
149
aplicar a las funciones; ya que estamos en ello, y a ttulo de curiosidad, si no quieres usar el valor devuelto por una funcin, cosa que se hace muchas veces con las llamadas al API de Windows, debers hacerlo anteponindole a la funcin la instruccin CALL. Esto lo veremos cuando empecemos con el API, que, aunque te parezca que es un tema avanzado, lo empezaremos a ver muy pronto. Ahora veamos cmo usar esas dos funciones: variable = LOF(#canal) Esta funcin devuelve, en bytes, el tamao del fichero abierto con el canal indicado dentro del parntesis. LOF es la abreviatura de: Length Of File (longitud del fichero). Por tanto si queremos saber la longitud de un fichero, lo abrimos, asignamos a una variable el valor devuelto por LOF y despus hacemos lo que tengamos que hacer... Que slo quieres averiguar la longitud, pues lo cierras y ya est, por ejemplo: Dim nFic As Integer Dim sFic As String Dim tamFic As Long sFic = "C:\Autoexec.bat" nFic = Freefile Open sFic For Input As nFic tamFic = LOF(nFic) Close nFic MsgBox "El tamao de " & sFic & vbCrLf & "es de " & tamFic & " bytes" La verdad es que si lo que pretendes es saber la longitud de un fichero, puedes usar la funcin FileLen, sta se usa poniendo el nombre del fichero entre los parntesis y tambin devuelve el tamao en bytes: (Tanto una funcin como la otra devuelven un valor LONG) Dim sFic As String sFic = InputBox("Nombre del fichero:", "Mostrar tamao", "C:\Autoexec.bat") If Len(sFic) Then MsgBox "El tamao de " & sFic & vbCrLf & "es de " & FileLen(sFic) & " bytes" End If Este trozo de cdigo te preguntar, (usando InputBox), el nombre de un fichero, por defecto te mostrar C:\Autoexec.bat y si se ha escrito algo, mostrar el tamao en bytes. Fjate que el valor devuelto por una funcin no slo se puede asignar a una variable, sino
150
que tambin se puede usar directamente. Si, ya s que lo he dicho en otras ocasiones, pero lo repito para que no te quede duda. Ahora veamos cmo "funciona" la funcin INPUT: Esta funcin devuelve una cadena con el contenido de un fichero que est abierto... realmente devuelve el nmero de caracteres que le indiquemos y esos caracteres los tomar del fichero abierto con el "canal" indicado, veamos cmo usarla: cadena = Input$(numCar, #canal) El signo dlar ($), lo uso para saber que estoy trabajando con una funcin que devuelve un valor de cadena... La mayora de este tipo de funciones del Visual Basic, devuelven indistintamente un valor variant o string, para obligarle a que devuelva una cadena, debemos ponerle al final el signo $, ni que decir tiene que esto slo es vlido para las funciones que devuelvan cadenas de caracteres, no para las que devuelvan otro tipo de datos... Pero no te preocupes de este tema, ya que el VB se encarga de hacer lo que tenga que hacer para que obtengas lo que tienes que obtener... Que lo!!! Ahora vamos a ver cmo podemos leer todo el contenido de un fichero y asignarlo en una variable de cadena: Dim nFic As Integer Dim sFic As String Dim tamFic As Long Dim sContenido As String sFic = InputBox("Nombre del fichero:", "Mostrar fichero", "C:\Autoexec.bat") If Len(Dir$(sFic)) Then nFic = FreeFile Open sFic For Input As nFic tamFic = LOF(nFic) sContenido = Input$(tamFic, nFic) Close nFic MsgBox sContenido End If Fjate que uso un MsgBox para mostrarlo, en caso de que el contenido del fichero sea "demasiado" grande, el botn Aceptar no se ver... tienes dos opciones: pulsar Intro o ESC, de esta forma quitars el mensaje de la pantalla, a pesar de que no tenga "visible" el botn Aceptar. Un par de detalles, la funcin Input$() devuelve una cadena de caracteres, el tamao mximo de caracteres que admite estar delimitado por el sistema operativo y sobre todo por la versin del VB, en 32 bits este tamao "casi" no tiene lmite, pero en 16 bits, ser de 64 KB como mximo. El otro detalle es que la comprobacin que se hace para saber si existe el fichero, no es a
151
prueba de "manazas". Me explico: si el nombre que se indica en sFic no existe y/o el path indicado tampoco, no pasa nada, todo funcionar bien; pero si le indicas la unidad A, o cualquier otra, cuando no hay disco insertado, la cosa deja de funcionar y te mostrar un error. Pero todo esto tiene solucin, ahora mismo te dir cual es, pero antes voy a desglosarte el funcionamiento de Len(Dir$(...)) Fjate que aqu se usan dos funciones: Len(Cadena) Dir$(sFichero) Esta funcin devuelve el nmero de caracteres de la cadena indicada Esta otra, lo que devuelve es el nombre del primer fichero que coincida con la "especificacin" indicada en sFichero, en caso de que no haya coincidencias, devolver una cadena vaca.
Por tanto, si no existe el fichero, Dir$ devuelve una cadena vaca y la longitud de una cadena vaca es CERO, as que en la comparacin, If Len(Dir$(sFic)) Then Visual Basic sustituir Len(Dir$(sFic)) por el valor devuelto, recuerda que para IF un cero significa FALSO y cualquier otro valor ser VERDADERO, insisto en este punto, ya explicado anteriormente, para que se te quede claro. Lo que quizs no sepas es que en Dir$(sFic), sFic puede contener signos comodines (? y/o *), para indicar cualquier tipo de fichero. Esto se suele usar tambin en las rutinas de bsqueda que se hacen en las bases de datos, as que mejor que te vayas enterando cmo usarlo y "buscar" por ah informacin, ya que aqu te voy a explicar un poco el significado, para que lo puedas usar al indicar los ficheros. Como "aadido", decirte que en el sistema de bsqueda del Windows 95, tambin puedes buscar ficheros o contenidos, usando estos comodines. Ejemplos: (las pruebas se pueden hacer desde una ventana del MS-DOS, usando el comando DIR) La interrogacin (?) se usa para indicar que no nos importa el carcter que haya en esa posicin. El asterisco (*) sirve para que nos devuelva todos los que tengan los caracteres anteriores a este signo, pero que los restantes no los tenga en cuenta.
Dir Dato??98.txt
Mostrar todos los ficheros que empiecen por Auto y que la extensin sea .bat Todos los ficheros que empiecen por Auto, sin importar ni la extensin ni nada, ya que al usar .* se indica que cualquier cosa es vlida. Es recomendable usar la segunda forma. Mostrar todos los ficheros que la primera letra sea A y la tercera T, como se usan *, esto indica que nos da igual lo que haya despus de la T, por tanto si tuvisemos ficheros llamados: Arte.txt, Autoexec.bat, Automovil.doc, Arial.ttf, nos devolvera los tres primeros, porque coinciden en lo indicado. Nos devolver todos los ficheros que tengan en las 4 primeras letras la palabra dato y en las posiciones 7 y 8 el nmero 98, la extensin ser txt. Adems en las posiciones 5 y 6 podr tener cualquier cosa, pero deber tener 8 caracteres. As que Dato0198.txt, dato1298.txt sera nombre encontrados, pero no lo sera: data0198.txt ni dato0298.doc, en el primer caso, porque
152
empieza con la palabra data y en el segundo porque la extensin no es txt Una vez visto, por encima, esto de los signos comodines, vamos a ver cmo hacer nuestra forma de comprobar si existe un ficheros, algo ms fiable y segura. Para ello, vamos a crear una funcin que se llame: Existe y esta funcin devolver FALSO (cero) si el fichero no existe y en caso de que exista, devolver VERDADERO (-1) Esta funcin podremos usarla de cualquiera de estas dos formas: If Existe(unFichero) Then 'Si existe, hacer lo que corresponda End If If Not Existe(unFichero) Then 'El fichero en cuestin no existe End If Para poder "detectar", o interceptar, los posibles errores que se produzcan al intentar comprobar si existe el fichero, usaremos esto: On [Local] Error Resume Next Con estas instrucciones, se le indica al Visual Basic que en caso de que se produzca un error, "pase" de ese error y contine con la siguiente instruccin. Esto est bien, pero... cmo sabremos que se ha producido el error? ya que, si continua... pues... eso, no se detiene... Respuesta: Usando la funcin ERR. Esta funcin devuelve el nmero del error que se haya producido, o cero si no se produjo error. A partir del VB4, esta funcin se puede sustituir por la propiedad Number del objeto Err, por tanto Err.Number tambin nos dir que nmero de error se ha producido, ten en cuenta que Number es la propiedad por defecto de este objeto. Pero como quiero que este curso bsico sea lo ms genrico posible y no se incline slo por el VB4 o superior... pues intentar usar funciones que sean compatibles... aunque tampoco prometo nada, as que te recomiendo que dejes de lado las versiones de 16 bits o al menos la versin 3 del VB, porque esta "consideracin" que estoy teniendo puede que cambie... de hecho seguramente pondr cosas que slo estarn disponibles a partir de la versin 4... y cuando la cosa vaya avanzando ms, incluso slo cosas de la versin 5... aunque para esas fechas ya estar en el mercado el VB7, por lo menos... El caso es que yo estoy acostumbrado a usar ERR, as que... eso es lo que hay... je, je. Y ya sin ms rodeos, veamos cmo quedara la funcin Existe, si te fijas es casi igual que la que puse hace ya algunas entregas, pero usando la deteccin de errores: Private Function Existe(ByVal unFichero As String) As Boolean On Local Error Resume Next
153
Existe = Len(Dir$(unFichero)) If Err Then Existe = False End If Err = 0 On Local Error GoTo 0 End Function Te explico cada una de las lneas: Private Function Existe(ByVal unFichero As String) As Boolean Esta es la declaracin de la funcin, el Private es por si lo quieres usar en un form, o en cualquier otro mdulo, pero que slo sea visible para el cdigo de ese mdulo. Si quieres que est visible en todo el proyecto, cosa recomendable para este tipo de funciones, cambia el Private por Public y escribe el cdigo en un mdulo BAS. As podrs usarlo en cualquier form o mdulo que tengas en tu proyecto. Veamos porqu he declarado el parmetro de esta forma: Byval unFichero As String Este es el parmetro que le pasamos a la funcin, es decir: el fichero o especificacin que queremos comprobar si existe. Lo de especificacin es porque puedes usar esta funcin para saber si existen archivos que empiecen por la A, asignando al parmetro los comodines que creas necesarios: If Existe("C:\A*.*") Then comprobar si en el directorio raiz de la unidad C hay archivos que empiecen por la letra A. El ByVal le indica al Visual que use una copia del parmetro, con lo cual evitaremos que el cdigo de nuestra funcin lo modifique; ya que al usar una copia, no tenemos acceso al original, esto tambin acelera el manejo de nuestra funcin. El As Boolean del final, es el tipo de dato que devolver nuestra funcin, tambin se podra usar Integer, de esta forma, si ests usando el VB3, podrs usar la funcin, sin mayor problema. On Local Error Resume Next Esta es la instruccin que pone en marcha la deteccin de errores. Local es para indicarle al VB que slo est operativa dentro de esta funcin, sirve para que, en caso de que externamente haya otro mecanismo de deteccin de errores, el que est operativo sea el que acabamos de declarar... cuando abandonemos la funcin seguir funcionando la otra rutina que hubiera. Existe = Len(Dir$(unFichero)) Asigna a Existe la cantidad de caracteres devueltos por la funcin DIR. Al ser del tipo Boolean, el VB automticamente asigna Verdadero o Falso. En el caso de que la funcin se haya declarado como Integer, un cero indica que es falso y cualquier otro valor que es verdadero, por tanto la cosa funcionar igualmente independientemente del tipo devuelto por esta funcin. Seguro? Veamos un ejemplo:
154
If Not Existe("algo.txt") Then MsgBox "El fichero indicado no existe" End If Prueba esto cambiando el tipo de dato devuelto por la funcin Existe, cuando lo pongas como Integer, siempre te dir que no existe el fichero... La explicacin de porqu ocurre esto, ya lo vimos anteriormente, y es porque NOT Un_Nmero, devuelve un nmero, no un cero... Y recuerda que, cuando Existe es del tipo Integer, devuelve el nmero de caracteres... Si quieres que slo devuelva 0 -1 para que sea igual que False/True, podras hacer esto otro: Existe = Len(Dir$(unFichero)) <> 0 De esta forma se evalua la expresin y en caso de que sea cierto que el resultado es distinto de cero, se asignar un -1, en cualquier otro caso se asignar un cero y ahora si que actuar igual que si el tipo devuelto fuese boolean. If Err Then Ahora comprobamos si se produjo algn error al intentar buscar el fichero en cuestin. En el supuesto de que se produzca un error, se pasar a la lnea despus del IF, asignando FALSE al valor que devolver la funcin. Como sabrs o te habrs imaginado, el valor que debe devolver una funcin se asigna al nombre de la funcin, en otros lenguajes se usa RETURN valor, por ejemplo en C o en el JavaScript... Por tanto, Existe = False, slo se asignar cuando se produzca un error. Err = 0 Esta es una de esas recomendaciones "obligatorias" que yo hara siempre que uses el Resume Next, en este caso no es necesario porque el cdigo de la funcin termina ah, pero si hubiese ms cdigo, ese valor de error seguira asignado a Err y cualquier otra comparacin posterior al objeto Err, podra "alterar" el buen funcionamiento de nuestro programa. (Recuerda que en VB4 o posterior, realmente se asigna a la propiedad Number del objeto Err) On Local Error Goto 0 Esta ltima lnea "libera" la deteccin de errores de esta funcin, en nuestro caso, tampoco es necesaria, ya que al finalizar un procedimiento, cualquier rutina de deteccin de errores se elimina. Esto, al igual que lo dicho anteriormente, sirve si queremos dejar de detectar errores en las siguientes lneas de nuestra rutina. Como ya no hay ms lneas de cdigo, la verdad es que no es necesaria, pero suelo usarla siempre, costumbres que tiene uno de saber cuando dejo de interceptar los errores. Bien, ya tenemos nuestra funcin a prueba de usuarios inexpertos o con malas intenciones... tambin para los olvidadizos... Por qu me miras? Es que nunca has intentado acceder a la unidad A cuando an no has insertado un disquete? Pues yo s... je, je...
155
Vamos a probarla. Crea un nuevo proyecto, agrega un mdulo bas. Asegrate que tiene Option Explicit al principio, ya sabes que esto es para que cualquier error tipogrfico al escribir una variable, no obligar al VB a crearla, sino que nos avisar de que esa variable no existe, es decir, sirve para obligarnos a declarar todas las variables que vayamos a usar. Ahora copia y pega o escrbela nuevamente, la declaracin de la funcin Existe, asegrate que sea pblica y no privada. Cierra el mdulo y asegrate que el Form1 est visible y en modo de diseo, es decir que se vea el Form. Aade un Label, un TextBox y un CommandButton. Modifica el caption del label para que contenga este texto: Fichero a comprobar: En el caption del botn escribe: Comprobar. Sita los controles para que tengan un aspecto "agradable" y escribe lo siguiente en el Command1_Click: Private Sub Command1_Click() If Existe(Text1.Text) Then MsgBox "Si existe el fichero: " & Text1.Text Else MsgBox "NO EXISTE el fichero: " & Text1.Text End If End Sub Prueba a escribir en el TextBox signos comodines para comprobar que todo funciona, por ejemplo: *.bas Igualmente escribe algn nombre que sepas que no existe... y para salir de dudas, intenta acceder a un fichero de la unidad A, pero sin poner un disco... Todo correcto, verdad? Pues me alegro, de que as sea... No, no te preocupes que no hay "gato" encerrado... bueno, s, los tres que tengo en mi casa, pero esos no tienen nada que ver con el programa... No te voy a poner ningn ejercicio en esta entrega, ya lo har en la siguiente, as que paciencia y no desesperes, que ya la tengo escrita en papel, por tanto puede que maana mismo est en lnea... es que si contino, se me va a pasar la hora y quiero que sea hoy da diez el da que publique esta entrega... chorradillas que se le ocurren a uno...
Como es habitual, y las buenas costumbres no hay que perderlas, si quieres hacer algn comentario sobre esta entrega o sobre el curso bsico en general... o tienes ese porttil que me hara tan feliz... y me lo quieres regalar, claro, usa este link Te repito por ensima vez que no aproveches el link para las consultas... que ya nos vamos conociendo... a pesar de que no des la cara... Nos vemos. Guillermo
156
Haz dobleclick en el Command1 y escribe: Private Sub Command1_Click() Dim sTmp As String On Local Error Resume Next
157
sTmp = Dir$(Text1.Text) If Err = 0 Then Do While Len(sTmp) List1.AddItem sTmp sTmp = Dir$ Loop End If Err = 0 End Sub Ejecuta el programa y escribe *.* en el TextBox, pulsa en el botn y te mostrar en el listbox todos los archivos del directorio actual. Vamos a ver que es lo que hace el cdigo: sTmp = Dir$(Text1.Text) Con esto, guardamos en sTmp el primer fichero que coincida con lo que hemos escrito en el Text1. If Err = 0 Then Si no se produce un error... Do While Len(sTmp) ...se entra en el bucle, pero slo si el contenido de sTmp no es una cadena vaca. Recuerda que DIR$ devuelve una cadena vaca si no se ha encontrado un fichero que coincida con lo indicado... List1.AddItem sTmp Esto aade al ListBox un nuevo elemento con el contenido de sTmp sTmp = Dir$ Fjate que DIR$ se usa sin indicarle nada ms, salo de esta forma si quieres que siga comprobando si hay ms ficheros que coincidan con la especificacin indicada la vez anterior que se le pas un parmetro. Si el contenido del TextBox tena algn signo comodn, Dir$ devolver el siguiente fichero que coincida, en caso de que no queden ms ficheros "coincidentes", devolver una cadena vaca. Loop Repite el bucle si se cumple la condicin que pusimos despus de DO WHILE, es decir: continuar si la longitud, nmero de caracteres, de sTmp NO ES CERO. Prueba, sin cerrar el programa, con varias cosas, por ejemplo: *.vbp, *.bas, etc. Que pasa? Si ests indicando varias cosas que buscar, y las encuentra, te dars cuenta que el listbox se va llenando... es decir, adems de lo nuevo, que estar al final, sigue lo anterior... Cmo solucionarlo? Borrando el contenido del listbox. 'Repetir mientras haya ficheros 'Lo aadimos a la lista 'Asignar el siguiente fichero
158
Para borrar el contenido del listbox, usa esto: List1.Clear Dnde debo ponerlo? En nuestro ejemplo, yo lo pondra justo despus del On Local Error..., o antes, da igual, ya que lo que se pretende es que se borre al hacer CLICK en el botn. Por supuesto no lo pongas dentro del DO...LOOP, ya que no servira para lo que queremos... puesto que se borrara continuamente... S, ya s que no eres tan torpe como para hacerlo, pero... Un detalle que puede que sea simple, pero que en nuestro ejemplo nos ahorrara pulsaciones. Si al pulsar INTRO se simulara el CLICK del botn, nos ahorrara el tener que "desplazarnos" a ese botn para que muestre lo que ha encontrado y si queremos seguir mostrando ms cosas, el tener que desplazarnos nuevamente al TextBox. Para conseguir esto, todo lo que tenemos que hacer, es indicarle al VB que el botn sea un botn "por defecto". Muestra el form, y pulsa una vez en el botn, te mostrar la ventana de propiedades de este control, busca la propiedad DEFAULT y mrcala como TRUE, vers que el botn ahora est remarcado con un borde negro. Ejecuta de nuevo el programa, escribe cualquier cosa en el TEXT1 y pulsa INTRO, ahora te mostrar los ficheros hallados, pero el cursor permanece en el TextBox, listo para poder escribir una nueva especificacin... Ahora vamos a los ejercicios: 1.- Modifica el ejemplo para que en lugar de guardar los ficheros hallados en un listbox, lo haga en un array. 2.- Una vez hecho esto, aade al listbox todos los ficheros hallados... mejor dicho, para que no hagas trampas, aade al listbox el contenido del array, es decir todos y cada uno de los ficheros previamente asignados. 3.- Aade otro botn al form y al pulsar en l, que guarde en un fichero, (por ejemplo: hallados.txt), todos los ficheros hallados, es decir los que estn en el array. Creo que con esto, ya tienes para entretenerte un rato. Como pista, ya sabes el tipo de pistas que doy..., te dir que la asignacin al array, puedes hacerla de dos formas: UNA: Usando un nmero mximo de ficheros a asignar. DOS: Usando un nmero variable, es decir que se aadan slo la cantidad de ficheros hallados. Y si haces los ejercicios de las dos formas posibles, mejor an. Si ves que te atrancas y no sabes por dnde hincarle el diente, aunque sea postizo, chale un vistazo a la entrega ocho y a la entrega doce, en ellas encontrars la solucin a estos ejercicios... bueno, la solucin, lo que se dice la solucin: no, pero si unas verdaderas auto-pistas para poder solucionarlos. Y como soy un poco "diablillo", no te voy a poner un link a las soluciones... al menos hasta que ponga la siguiente entrega, as que... a rabiar unos das! Es que algunas veces... Soy malo! Pues nada, a esperar... que de seguro ser poco, pero mientras tanto...
159
Y recuerda que espero tu comentario, no me preguntes si lo ests haciendo bien o no, que de eso se encargar la pgina con las soluciones, slo pregntame algo que no hayas entendido..., de esta entrega, claro, no de otra cosa que no est en el curso... que casi de seguro estar en otra de las secciones que hay en mis pginas...
160
If nFic < MaxFicheros Then nFic = nFic + 1 'Lo aadimos al array sFicheros(nFic) = sTmp End If sTmp = Dir$ Loop End If Err = 0 End Sub 'Asignar el siguiente fichero
Usando un nmero variable de ficheros. En esta ocasin usaremos el Redim Preserve para hacer hueco en el array que guardar los nombres de los archivos.
Private Sub Command1_Click() Dim sTmp As String Dim sFicheros() As String Dim nFic As Integer On Local Error Resume Next sTmp = Dir$(Text1.Text) nFic = 0 If Err = 0 Then Do While Len(sTmp) nFic = nFic + 1 'Adecuar el tamao del array a los ficheros leidos ReDim Preserve sFicheros(nFic) 'Lo aadimos al array sFicheros(nFic) = sTmp sTmp = Dir$ Loop End If Err = 0 End Sub 'Asignar el siguiente fichero 'Repetir mientras haya ficheros
En el segundo ejercicio, hay que guardar el contenido del array, en este caso, el array debe estar declarado a nivel de mdulo, ya que un array declarado dentro de un procedimiento es local a ese procedimiento y por tanto no estar disponible fuera de el. Si no lo haces as cada uno de los arrays que uses (y dimensiones) en cada SUB ser slo
161
visible en ese procedimiento... Por tanto el Dim sFicheros() As String debes ponerlo en la parte de las declaraciones del form. Este cdigo debers agregarlo despus de asignar todos los ficheros al array, justo despus del Loop, para que est dentro del IF que comprueba que no se haya producido error..
Dim i As Integer 'Guardar el contenido del array Open "prueba.txt" For Output As 1 For i = 1 To nFic Print #1, sFicheros(i) Next Close 1
Fijate que no he usado el Freefile para "buscar" un canal libre. En lugar de eso he usado el nmero 1. Te lo digo por dos razones, la primera es para que no lo confundas con la letra L minscula y la segunda es para que sepas que se pueden usar constantes, aunque no te lo recomiendo, pero como en este caso, se con toda seguridad de que mi aplicacin no tiene abierto ningn otro fichero, puedo permitirme el lujo de hacerlo as, de forma directa. El tercer ejercicio, no debera tener mayor problema, todo lo que hay que hacer es un bucle que asigne al listbox cada uno de los ficheros del array:
'Borrar el contenido del listbox List1.Clear 'Agregarle cada uno de los ficheros del array For i = 1 To nFic List1.AddItem sFicheros(i) Next
Este cdigo aadelo justo despus de guardar los datos en el disco, aunque tambin puedes ponerlo despus, siempre que est despus del Loop, cualquier sitio es bueno. Fijate que a pesar de que selecciones distintos tipos de ficheros, sin cerrar el programa, por supuesto, estos no se incrementan en la lista, no slo por el List1.Clear, sino porque al hacer Redim Preserve el nmero de elementos del array se adapta al valor de nFic y este valor empieza siempre por cero, as que siempre se tendr en el array el nmero correcto de ficheros. La asignacin que hago para ponerlo a cero, no es necesaria, ya que cuando se dimensiona una varible numrica, sta variable contiene inicialmente un valor cero. Pero imaginate que no haces esa asignacin o que quieres asegurarte que el contenido del array se "libere" antes de empezar a asignarle datos... para ello tendras que usar: ERASE sFicheros, con esta instruccin borramos el contenido del array. En el caso de que el
162
nmero de elementos del array fuese fijo, a lo que se llama un array esttico, simplemente se borrara el contenido del array, pero seguira existiendo el array con las 50 "dimensiones" creadas. Si, por el contrario, el array es dinmico, es decir que podemos cambiar el tamao del mismo, lo que hacemos es "eliminarlo" de la memoria, por tanto necesitaremos dimensionarlo (o REdimensionarlo) para poder usarlo nuevamente. Bueno, ahora a esperar a la siguiente entrega... hasta entonces... un saludo.
163
El Textbox grande (Text2) tiene la propiedad Multiline a True, de esta forma se ir mostrando todo el contenido conforme lo vayamos rellenando. Los nombres de los otros controles sern Label1, Text1, cmdAbrir para el botn de Abrir y cmdGuardar para el de guardar. El problema de los textbox es que no soportan ms de 64KB, aunque en teora si, pero en la prctica lo que soportan son unos 32000 caracteres, que en la versin de 32 bits suponen esos 64KB, por si no lo sabes en Windows de 32 bits cada carcter est representado por dos bytes. Para solventar ese "pequeo" inconveniente, vamos a dar por hecho de que slo admite 32000 caracteres, de esta forma tambin nos servir el programa en VB de 16 bits. A este proyecto hay que agregarle un mdulo BAS que tenga la funcin Existe que vimos en la entrega trece, si no quieres aadir un nuevo mdulo, simplemente copia y pega esa funcin en este formulario. Si la funcin la usas desde un mdulo BAS, debe ser pblica, si la pegas en este formulario, puede ser privada. En el formulario tambin puede ser pblica, pero lo que nos interesa es que pueda accederse desde el form, as que no es necesario declararla de esa forma. Ahora aade el siguiente cdigo para el botn abrir: Private Sub cmdAbrir_Click() 'Abrir Dim i As Long, tamFic As Long
164
If Existe(Text1.Text) Then Text2.Text = "" i = FreeFile Open Text1.Text For Input As i tamFic = LOF(i) Text2.Text = Input$(tamFic, i) Close i End If End Sub Esta rutina no est hecha a prueba de fallos, pero no te preocupes que ya lo hars... como ejercicio? efectivamente! Este es el cdigo del botn guardar: Private Sub cmdGuardar_Click() 'Guardar Dim i As Long i = FreeFile Open Text1.Text For Output As i Print #i, Text2.Text Close i End Sub Fjate que cdigo ms corto... as da gusto hacer programas! Aunque tampoco est preparado para cualquier impedimento... Vamos a aadirle una serie de mejoras, entre ellas el que avise, al guardar, si es que vamos a sobrescribir un fichero existente. Otra mejora sera comprobar si se ha modificado el contenido del Text2 y avisar antes de abrir un nuevo fichero. La tercera mejora que se me ocurre es comprobar que no se abra un fichero con ms caracteres de los "permitidos"... aunque este lo dejar para que lo hagas t. La razn de poner estas mejoras por separado, es decir, contrtelo antes de hacerlo, es para darte la oportunidad de que lo hagas por tu cuenta... Ahora no recuerdo si tienes la base suficiente para hacerlo, pero... podra ser... comprubalo por tu cuenta. Antes de darte la solucin a dos de estas tres mejoras, voy a contarte un "rollito" para que as no se vean las soluciones... no te preocupes que no te voy a contar una de mis batallitas, slo voy a "ampliar" tus conocimientos... (que bien te ha quedado eso Guille)
165
Cuando usamos un textbox multiline, es decir en el que podemos escribir varias lneas de texto, el Visual nos da la posibilidad de poder usarlo de varias formas, si no le decimos nada, conforme vayamos escribiendo, se ir mostrando el texto en la siguiente lnea, a esto se le llama wordrap o algo as, que viene a significar que no se corten las palabras al cambiar de lnea... para hacer esto mismo en el BASIC normalito del MS-DOS, haba que crear una rutina para comprobar cuando haba que mandar una palabra a la siguiente lnea... dejemos los viejos tiempos y continuemos con los nuevos... Pero si lo que quieres es que cada lnea escrita se quede en la misma lnea hasta que pulses intro, debers indicrselo al Visual Basic, dicindole que slo aada al textbox un scroll horizontal, prubalo y decide... Cmo aadirle los scrollbars... no pienses que tienes que usar los controles que el Visual tiene para eso, slo debes modificar la propiedad ScrollBars del control Text2. Tienes varias opciones: 0- None 1- Horizontal 2- Vertical 3- Both Ningn scrollbar Slo el scroll horizontal, para cambiar de lnea debes pulsar Intro Slo el scroll vertical, esto es como sin scrolls, pero nos permite navegar hacia abajo. Ambos scrolls, pues eso, una mezcla de los dos.
Si los compruebas, vers al asignar a esta propiedad el valor 0 y 2, el resultado es el mismo, al menos en lo que se refiere a la hora de escribir en l, ya que el resultado visual es diferente; a lo que me refiero es que el texto se ajustar automticamente haya o no un intro para separar cada lnea, la diferencia, es que con el scroll vertical, podemos navegar fcilmente hacia la parte no visible del texto escrito. Cuando especificamos el Scroll horizontal, tanto con el valor 1, como con el 3, ya te dars cuenta de que cada lnea est separada, siempre que hayas pulsado Intro para cambiar de lnea. Tambin puedes usar Shift+Intro o Control+Intro para efectuar un cambio de lnea. Para comprobarlo, haz el textbox ms pequeo, al menos en anchura, por ejemplo ajstalo al tamao del Text1 y escribe varias lneas, pulsando en algunas Intro y otras escribiendo bastante texto... Luego decide que tipo de scroll prefieres. Una cosa que debes saber es que esta propiedad es de slo lectura, al menos en tiempo de ejecucin, o sea que no puedes cambiar que scrolls deben mostrarse una vez que el programa est en funcionamiento. Me imagino que te habrs fijado que en el Notepad (Bloc de Notas) que incluye el Windows, existe una opcin para ajustar las lneas automticamente, es decir para usar los dos scrolls o slo el vertical. Cmo se consigue este efecto si no se puede cambiar la propiedad ScrollBars? Pues... usando dos TextBoxes, uno de ellos con la propiedad ScrollBars a 2 (Vertical) y el otro asignando el valor 3 (Both) Para probarlo, debers crear un array del Text2, para ello, cpialo y vuelve a pegarlo, te preguntar si quieres tener una matriz de este control, contstale que s. Al Text2(0), el que tiene el valor Index igual a CERO, asgnale la propiedad ScrollBars a 2 y al otro, el valor 3. Sitalos en la misma posicin del form, para que uno est encima del otro, no te preocupes de cual est encima, eso lo controlaremos nosotros. Aade un nuevo botn, escribe en el Caption: Ajustar lneas y dale el nombre cmdAjustar. Declara una variable a nivel de mdulo para saber cual es el TextBox que est activo: Dim QueText2 As Integer En el Form_Load escribe esto: Text2(0).Zorder para que se ponga encima del otro TextBox. En el evento Click del nuevo botn aade este cdigo:
166
Private Sub cmdAjustar_Click() Dim QueText2Ant As Integer 'Valor del TextBox actual QueText2Ant = QueText2 'Nuevo valor QueText2 = QueText2 + 1 'si nos pasamos... volvemos al principio If QueText2 > 1 Then QueText2 = 0 'asignar al nuevo textbox el contenido Text2(QueText2).Text = Text2(QueText2Ant).Text 'Lo hacemos visible Text2(QueText2).ZOrder 'borramos el contenido anterior Text2(QueText2Ant).Text = "" End Sub Ahora tendrs que cambiar el cdigo usado para leer y escribir, simplemente cambia el Text2.Text por Text2(QueText2).Text y asunto arreglado, ya que el text que estar visible es el que indica esa variable. Ejecuta el programa, escribe algo en el textBox, preferiblemente alguna lnea larga y pulsa Intro para crear otras, pulsa en el botn de ajustar lneas y vers el efecto. Si quieres tener esta posibilidad en este programa, debers recordar de cambiar cualquier uso de Text2 por Text2(QueText2), ya que si no lo haces, el Visual Basic se encargar de recordrtelo... Bueno, creo que el rollito este ha sido ms largo de lo que tena previsto, pero espero que haya valido la pena. Vamos a ver las soluciones a las mejoras... las soluciones las voy a dar suponiendo que no tienes esta posibilidad de usar los dos TextBoxes para el ajuste de lnea, es que sino, me va a romper todo el esquema que tena previsto y no es plan... Para saber cuando se va a sobrescribir el fichero, lo que hay que hacer es comprobar primero si ese fichero ya existe y despus de comprobarlo, avisar con un MsgBox si se quiere sobre-escribir, en caso negativo simplemente no se guarda el contenido del TextBox en el fichero. Vamos a ver el cdigo necesario, este deber estar en el botn de Guardar... (elemental mi querido Watson) Private Sub cmdGuardar_Click() 'Guardar
167
Dim i As Long Dim SobreEscribir As Boolean 'Se asigna el valor Verdadero, por si no existe SobreEscribir = True 'Si ya existe, preguntar If Existe(Text1.Text) Then If MsgBox("ATENCIN, el fichero ya existe." & vbCrLf & _ "Quieres sobreescribirlo?", vbQuestion + vbYesNo) = vbNo Then 'Hemos contestado que no, as que... SobreEscribir = False End If End If 'Si no existe o se quiere sobreescribir... If SobreEscribir Then i = FreeFile Open Text1.Text For Output As i Print #i, Text2.Text Close i End If End Sub Fjate en el MsgBox, al usar & _ es para que el VB pueda mostrar en lneas distintas lo que se debera escribir en una misma. Despus usamos vbQuestion para que muestre la interrogacin y vbYesNo es para que nos muestre los botones SI / NO. Si pulsamos en Si, no se cumple la condicin y si pulsamos en NO, si que se cumple, por tanto asignamos un valor falso a la variable SobreEscribir para que en la siguiente comparacin no se cumpla y no se guarde el contenido del Text2. Al principio le he dado el valor VERDADERO a la variable que decide si se debe sobrescribir o no, esto lo he hecho porque si no existe el fichero, no nos preguntar y si no le damos de "arrancada" el valor Verdadero, nunca lo tendr, ya que la nica asignacin que hacemos es la darle un valor FALSO, en caso de que no queramos guardar el contenido. Como habrs podido comprobar, para poder guardarlo con otro nombre, debers escribir ese nombre en el Text1. Para la segunda mejora, necesitaremos una variable a nivel de mdulo, as que aade esta declaracin en la seccin de las declaraciones generales del formulario: Dim Modificado As Boolean Como recordars de la segunda entrega, en los TextBoxes hay un evento, CHANGE, que se "dispara" cada vez que cambia el contenido de la propiedad Text de estos controles.
168
Usaremos este evento para saber cuando se ha cambiado el contenido del Texto escrito. Aade este cdigo al formulario: Private Sub Text2_Change() Modificado = True End Sub De esta forma, cada vez que se cambie el contenido de este control, se cambiar tambin el valor de esa variable y as podremos saber si se ha cambiado o no. Esto lo comprobaremos en el procedimiento Abrir, de forma que si se ha modificado, nos pregunte si queremos guardarlo antes de abrir uno nuevo. El cdigo, ms o menos sera algo as: If Modificado Then If MsgBox("El texto se ha modificado..." & vbCrLf & _ "Quieres guardarlo?", vbQuestion + vbYesNo) = vbYes Then 'Guardarlo '... End If End If Realmente no sera tan simple. Ahora lo veremos al completo. Con esto de comprobar si est modificado se nos presentan dos problemas: El primero es que al no conservar el nombre del fichero abierto anteriormente, o con el que acabamos de guardar lo que hayamos escrito antes de intentar abrir otro, no sabremos con que nombre guardarlo, ya que al usar el contendido del Text1, ste puede cambiar y seguramente no conseguiramos nuestro objetivo. Por suerte, la solucin a este inconveniente es tan simple como la de usar una variable, a nivel de mdulo, para guardar el nombre del fichero abierto o guardado por ltima vez. Nota: Fjate que uso variables a nivel de mdulo para algunas cosas, de esta forma estas variables, como ya deberas saber, estarn disponibles en cualquier parte del mdulo actual, en este caso: el formulario. Aade esta declaracin en la parte general de las declaraciones del formulario: Dim sFichero As String Ahora modifica el cdigo para que podamos usar esta variable: Private Sub cmdAbrir_Click() 'Abrir Dim i As Long, tamFic As Long
169
Dim sTmp As String If Modificado Then If MsgBox("El texto se ha modificado..." & vbCrLf & _ "Quieres guardarlo?", vbQuestion + vbYesNo) = vbYes Then 'Conservar el nombre actual sTmp = Text1.Text 'y asignar el anterior Text1.Text = sFichero 'guardarlo... cmdGuardar_Click 'Volvemos a dejar el Text1 como estaba Text1.Text = sTmp End If End If 'Asignamos el nombre del fichero sFichero = Text1.Text If Existe(sFichero) Then Text2.Text = "" i = FreeFile Open sFichero For Input As i tamFic = LOF(i) Text2.Text = Input$(tamFic, i) Close i End If End Sub Fjate en las asignaciones que hay que hacer antes de guardar el contenido del Text2, esto es necesario, ya que en ese evento se usa el contenido del Text1, para saber en que fichero se debe guardar. Bien, ya tenemos una parte resuelta, la otra es que una vez que la variable Modificado ha tomado el valor TRUE no lo suelta. Este valor debera de dejar de valer verdadero cuando lo guardemos, as que aade al final del procedimiento que guarda el fichero, esta asignacin: Modificado = False Realmente debers ponerla despus del Close i y dentro de la comparacin que decide si se debe guardar o no el contenido del Text2. Tambin tendremos que asignar en este procedimiento el valor de la variable sFichero, para que al asignarse en el evento Abrir su valor al TextBox, ste tome el que tuviera esa
170
variable cuando se guard un fichero. Esto debers hacerlo una vez que se guarde el fichero, es decir, si no hemos cancelado la grabacin. sFichero = Text1.Text Aunque pueda parecer que realmente no tiene sentido el uso de esta variable, ya que tanto en Guardar como en Abrir se le asigna el contenido del Text1, lo tiene en el caso de querer abrir uno nuevo, antes de haber guardado los cambios, si no se tuviera esta variable, no conservaramos el nombre del fichero, antes de haber modificado el contenido del Text1 para asignar un nuevo nombre... Aunque parte de este "come-coco" se solucionara con el uso de un cuadro de dilogo que preguntara el nombre del fichero a abrir o a guardar, de esta forma no sera necesario dejar siempre un TextBox para que se pueda escribir el nombre. An as, necesitaramos una variable para conservar el nombre del fichero... Bien, vamos a probar esto... a ver si funciona bien... Como podrs comprobar, no est todo lo "refinado" que quisiramos... Despus de abrir un fichero y sin haber modificado el contenido del Text2, intenta abrir otro, te dir que el texto se ha modificado... Por qu ocurre esto? Cuando asignamos al Text2 el contenido del fichero, estamos "cambindolo", por tanto se produce el evento Change y se asigna el valor de la variable Modificado, as que tambin tendremos que asignar un valor FALSE a esta variable despus de abrir el fichero y asignarlo al Text2, es decir al final del procedimiento Abrir. De esta forma slo se activar la "alarma" cuando realmente se modifique el texto. Este valor asignarlo justo despus de cerrar el fichero o despus de haber asignado al Text2 el contenido del fichero. Para finalizar, un par de cosillas para mejorar. Cuando un form se abre, cosa que ocurre automticamente al iniciarse este programa, se produce el evento Load, este ahora no nos interesa, el que nos interesa es el que se produce cuando el form se cierra, cosa que tambin ocurre al cerrar la aplicacin, es decir el evento Unload. En este evento tambin se puede poner una comprobacin para que, en caso de no haber guardado el contenido del Text2, tengamos la oportunidad de poder guardarlo antes de cerrar definitivamente el form. As que, aade este cdigo en el evento Form_Unload: Private Sub Form_Unload(Cancel As Integer) If Modificado Then If MsgBox("El texto se ha modificado..." & vbCrLf & _ vbYes Then "Quieres guardarlo?", vbQuestion + vbYesNo) = 'Asignar el anterior
171
Text1.Text = sFichero 'guardarlo... cmdGuardar_Click End If End If Set Form1 = Nothing End Sub Aunque en el caso de que no hayamos usado ningn nombre, el contenido de sFichero, no sea adecuado, as que tambin podramos tener un valor por defecto en esta variable, que sera el que se mostrase al iniciarse el programa, por tanto vamos a aadir un valor por defecto a esta variable y al Text1, esto lo haremos en el evento Form_Load: Private Sub Form_Load() sFichero = "Prueba15.txt" Text1.Text = sFichero End Sub Fjate que al final del Form_Unload he puesto Set Form1 = Nothing, esto se suele usar cada vez que descarguemos un formulario, para asegurarnos que se libere la memoria que pudiera estar ocupando... de esto ya veremos ms cuando nos topemos con las clases y la creacin de objetos... piensa que cada control y formulario es un objeto y cuando un objeto no se usa, debemos indicarle al Visual que se deshaga de l... Pero esto lo veremos en otra ocasin.
Ahora los ejercicios: 1.- Como ya he comentado, los TextBoxes no soportan todos los caracteres que quisiramos, para redondear, digamos que soportan 32000. Esto es casi cierto en VB de 16 bits, realmente admiten 32767 (32 KB). En 32 bits en teora soportan 64 KB, pero como el entorno de 32 bits usa caracteres de 2 bytes, estos 64 kas se quedan en 32. Para 16 bits, existe una funcin del API de Windows que permite asignar 64 KB a un TextBox, en 32 bits no tiene ningn efecto ya que, como he repetido ms de una vez... admite 64 KB. Para no liarte ms de lo que ya puedas estar, vamos a dar por sentado que slo se pueden asignar a un TextBox 32000 caracteres, que en 32 bits no es lo mismo que 32000 bytes... (je, que me gusta liar al personal) Lo que tienes que hacer es que a la hora de abrir un fichero, compruebes que no tenga ms de 32000 caracteres y en caso de que el fichero los tenga, no abrirlo. 2.- En este segundo ejercicio, lo que vas a hacer es leer slo 32000 caracteres del fichero que tenga ms de esos... as al menos podrs editar esa parte de los ficheros grandes,
172
cosa que no es recomendable, sobre todo si lo guardas, ya que podras "cargarte" un fichero que puede ser necesario... El que avisa... Aunque, para curarte en salud, podras impedir que se guardase un fichero del cual no se hayan ledo todos los caracteres que tuviese... por tanto, un tercer ejercicio: 3.- Si el fichero abierto tiene ms de 32000 caracteres, leer slo esta cantidad y usar un "flag" para impedir que se guarde... Bueno, espero que esta entrega te haya sido provechosa y que seas capaz de completar los ejercicios, la solucin de los cuales te pongo en este link, adems en la pgina de las soluciones tendrs el cdigo para usarlo con los dos Textboxes para permitir el ajuste de lnea. Y como suele ser costumbre al terminar una entrega, ya sabes que espero tu comentario, sobre todo para preguntarme cosas que no hayas entendido de esta entrega, para que si lo considero oportuno, aclararlo en una prxima. Por supuesto que tambin puedes usar ese link para darme "nimos" a que contine con el curso... o si te sobra ese porttil y me lo quieres regalar..., pero, por favor, no lo uses para hacerme consultas... despus no te quejes si no te las contesto, que algunos aprovechis el rollo ese del peloteo para soltar alguna consultilla... que ya nos vamos conociendo...
Nos vemos.
'Asignamos el nombre del fichero sFichero = Text1.Text If Existe(sFichero) Then Text2.Text = "" i = FreeFile Open sFichero For Input As i tamFic = LOF(i)
173
If tamFic <= 32000 Then Text2.Text = Input$(tamFic, i) End If Close i 'Una vez abierto, no est modificado Modificado = False End If En este caso, lo nico que hay que hacer es comprobar si el tamao es menor o igual al mximo indicado, si es as, abrir el fichero, por tanto, slo tendremos que poner la parte que asigna al Text2 el contenido del fichero, dentro de la comparacin.
2.- En caso de que sea mayor de 32000 caracteres, leer slo los 32000 primeros. La solucin es casi como la anterior, bueno, casi, es decir tendremos que hacer una comparacin y en caso del que sea cierta, asignar a la variable que indica el nmero de caracteres a leer el valor mximo que queremos. 'Asignamos el nombre del fichero sFichero = Text1.Text If Existe(sFichero) Then Text2.Text = "" i = FreeFile Open sFichero For Input As i tamFic = LOF(i) ' If tamFic > 32000 Then tamFic = 32000 End If Text2.Text = Input$(tamFic, i) Close i 'Una vez abierto, no est modificado Modificado = False End If Por tanto comprobamos si el contenido de tamFic es mayor de 32000, en caso de ser cierto, se cumple la condicin, asignamos este valor a esa variable, de esta forma slo se leern esos caracteres.
174
3.- Si el tamao es mayor de 32000 no poder guardarlo (y como extra: no poder editarlo) Para conseguir esto, debemos aprovechar el cdigo usado en la segunda solucin, de forma que si es mayor de 32000, podamos asignar un flag, para saber que no se debe guardar. Tambin podramos evitar que se modificase el contenido del TextBox, esto se consigue asignando la propiedad Locked a True. De esta forma no se podr escribir en el TextBox. Por supuesto que en caso contrario se debera permitir la escritura, ahora veremos cmo hacer estas cosas. Veamos las cosas por partes: 'Asignamos el nombre del fichero sFichero = Text1.Text If Existe(sFichero) Then Text2.Text = "" i = FreeFile Open sFichero For Input As i tamFic = LOF(i) 'Nos aseguramos que se pueda editar Text2.Locked = False Text2.ForeColor = vbWindowText If tamFic > 32000 Then tamFic = 32000 'Si el tamao no es el leido, 'no permitir escribir Text2.Locked = True 'si adems cambiamos el color... mejor Text2.ForeColor = vbGrayText End If Text2.Text = Input$(tamFic, i) Close i 'Una vez abierto, no est modificado Modificado = False En esta primera solucin, an no evitamos que se pueda guardar, pero al menos cambiamos el color del texto y evitamos que se pueda modificar el contenido, para ello he cambiado el valor de la propiedad ForeColor, usando dos constantes predefinidas, la primera es para asignar el color normal del texto y la segunda es para asignar el color Gris cuando no podamos escribir en el TextBox, la ventaja de usar estas constantes, al menos con el VB5, es que si cambias los colores, usando el panel de control de Windows, estos se usan de forma automtica al asignarlos con estas constantes.
175
Ahora pasemos a ver cmo evitar que se guarde, cuando se abra parcialmente un fichero. Para ello necesitaremos otra variable a nivel de mdulo: Dim PoderGuardar As Boolean Con esta indicaremos que podemos o no guardar el contenido del TextBox. Se asignar al abrir el fichero y se comprobar al guardarlo. Veamos al completo los procedimientos de Abrir y Guardar: Private Sub cmdAbrir_Click() 'Abrir Dim i As Long, tamFic As Long Dim sTmp As String If Modificado Then If MsgBox("El texto se ha modificado..." & vbCrLf & _ vbYes Then "Quieres guardarlo?", vbQuestion + vbYesNo) = 'Conservar el nombre actual sTmp = Text1.Text 'y asignar el anterior Text1.Text = sFichero 'guardarlo... cmdGuardar_Click 'Volvemos a dejar el Text1 como estaba Text1.Text = sTmp End If End If 'Asignamos el nombre del fichero sFichero = Text1.Text If Existe(sFichero) Then Text2.Text = "" i = FreeFile Open sFichero For Input As i tamFic = LOF(i) 'Nos aseguramos que se pueda editar Text2.Locked = False Text2.ForeColor = vbWindowText 'y que se pueda guardar PoderGuardar = True If tamFic > 32000 Then tamFic = 32000
176
'Si el tamao no es el leido, 'no permitir escribir Text2.Locked = True 'si adems cambiamos el color... mejor Text2.ForeColor = vbGrayText 'No permitir que se guarde PoderGuardar = False End If Text2.Text = Input$(tamFic, i) Close i 'Una vez abierto, no est modificado Modificado = False End If End Sub
Private Sub cmdGuardar_Click() 'Guardar Dim i As Long Dim SobreEscribir As Boolean 'Slo se ejecuta el cdigo si se puede guardar If PoderGuardar Then 'Se asigna el valor Verdadero, por si no existe SobreEscribir = True 'Si ya existe, preguntar If Existe(Text1.Text) Then If MsgBox("ATENCIN, el fichero ya existe." & vbCrLf & _ vbYesNo) = vbNo Then "Quieres sobrescribirlo?", vbQuestion +
'Hemos contestado que no, as que... SobreEscribir = False End If End If 'Si no existe o se quiere sobrescribir... If SobreEscribir Then i = FreeFile
177
Open Text1.Text For Output As i Print #i, Text2.Text Close i 'Ya hemos guardado las modificaciones Modificado = False sFichero = Text1.Text End If End If End Sub
Pues creo que esto es todo... Una cosa que me gustara que hicieras, al menos as aprenderas ms, es que intentaras entender las soluciones, no te limites a copiarlas, sin saber el porqu de su uso... por supuesto que no slo hay una solucin y si la que tu has encontrado, sirve para lo mismo, pues mejor an. Aqu tienes el cdigo para usar dos controles Text2, chale un vistazo a las comparaciones realizadas para poder saber si se ha modificado y esas cosas, ya que ahora vas a trabajar con dos controles, como si slo fuese uno... Que lo disfrutes y prueba a experimentar otras posibilidades...
178
179
Dim unColega As t_colegas Y abrimos el fichero: Open "miscolegas.dat" For Random As nFic Len = Len(unColega) El Len(unColega), le est indicando al VB que la longitud de cada registro es la que tenga ese tipo de datos. No siempre hay que hacerlo as, podramos asignar a una variable esa longitud y usarla para abrir el fichero: lenColega = Len(unColega) Open "miscolegas.dat" For Random As nFic Len = lenColega Lo que quiero dejar claro es que al Visual Basic no le importa con qu variable accedemos al fichero, sino que longitud tendr esa variable, o si lo prefieres, cual ser la longitud de cada registro. Por tanto, si sabemos que nuestro tipo de datos, (o lo que es lo mismo: cada registro), tiene 82 bytes de longitud, podramos usar un string con esa longitud para acceder al fichero en cuestin y... Toc, toc! Guille!!! S? No crees que te ests precipitando? Yo? Por qu? Porque ests hablando de acceder as o asao a los datos y an no has explicado cmo leer o escribir esos datos... Pues... s... es cierto... je... Si no fuera por el otro Guille... en fin... Esto..., cuando quieras leer el contenido de un registro en particular del fichero abierto, usa esta instruccin: Get #canal, num_registro, variable_de_datos Para escribir usa esta otra: Put #canal, num_registro, variable_de_datos Es decir GET para leer y PUT para escribir, en esto los ingleses lo tienen ms fcil, ya que al menos a ellos esas dos palabras tienen algo de sentido... Despus de cualquiera de estas instrucciones se indica el nmero de fichero ( #canal), seguido por el nmero de registro al que queremos acceder, (num_registro), y por ltimo la variable, (variable_de_datos), que usamos para leer, (GET), los datos del disco o almacenarlos en l (PUT) Un detalle que tienes que tener siempre presente al definir un tipo de datos para acceder a los ficheros aleatorios: asegurate de que todas las cadenas contenidas en ese tipo, sean de longitud fija; lo mismo si dentro del tipo usas arrays, estos deben ser de ndices constantes, es decir que estn definidos al crear el tipo. Todas estas "precauciones" se consiguen con datos "estticos", es decir que permanecen
180
sin cambios, al menos en lo que a tamao se refiere. Los "dinmicos" son los que pueden cambiar "dinmicamente" de tamao. Aclaremos estos puntos antes de continuar. Una variable declarada de esta forma: Dim cadenaEstatica As String * 50 Siempre tendr 50 caracteres, incluso si hacemos esta asignacin: cadenaEstatica = "" Con esto no eliminamos las 50 celdas de memoria, sino que la rellenamos de espacios... Prueba con este cdigo: Crea un nuevo proyecto y aade esto en el Form_Load: Private Sub Form_Load() Dim cadenaEstatica As String * 50 Show cadenaEstatica = "Hola" Print cadenaEstatica & "Pepe" cadenaEstatica = "" Print cadenaEstatica & "Pepe" Print Print "Longitud de la cadena:"; Len(cadenaEstatica) Print "Asc(cadenaEstatica) ="; Asc(cadenaEstatica) End Sub El resultado sera este:
Es decir que una cadena esttica (o de longitud fija), siempre tendr el nmero de caracteres que le indicamos al declararla. Sin embargo una cadena dinmica slo tendr los caracteres que le asignemos. Prueba este cdigo: Private Sub Form_Load()
181
Dim cadenaDinamica As String Show cadenaDinamica = "Hola" Print cadenaDinamica & "Pepe" cadenaDinamica = "" Print cadenaDinamica & "Pepe" Print Print "Longitud de la cadena:"; Len(cadenaDinamica) Print "Asc(cadenaDinamica) ="; Asc(cadenaDinamica) End Sub Habrs obtenido algo parecido a esto:
O sea "Illegal Function Call", este error lo da al intentar conseguir el cdigo ASCII de la cadena... pero como la cadena est vaca... pues no hay nada que obtener, por tanto: Error al canto!!! Aparte del error, te puedes fijar que "HolaPepe" sale junto y que la longitud de la variable es cero. Cuando asignamos una cadena vaca a un string dinmico, lo dejamos "seco", es decir sin nada en el interior... Lo mismo ocurre con los arrays, ya sean de caracteres o numricos. Los estticos siempre conservan el nmero de elementos, incluso cuando se eliminan, (al menos eso es lo que parece), con Erase. Aunque esto ya lo vimos, no est de ms recordarlo: Al "eliminar" con Erase un array dinmico, lo eliminamos de la memoria, pero en los estticos, simplemente ponemos el contenido a cero, (o a cadenas vacas si son de
182
cadenas dinmicas). Prubalo: Dim arrayEstaticoInteger(1 To 10) As Integer Dim arrayEstaticoString(1 To 5) As String Dim arrayDinamicoInteger() As Integer Dim arrayDinamicoString() As String Cuando se declaran los arrays con el nmero de elementos que va a contener, siempre con constantes, estos arrays son estticos, porque siempre contendrn ese nmero de elementos. Sin embargo los dinmicos, se declaran sin decirles cuantos elementos van a contener y despus se usa Redim o Redim Preserve para cambiar el nmero de elementos. Veamos un ejemplo de un tipo definido con un array esttico: Private Type t_colegas2 Nombre Edad email(1 To 3) End Type Aqu hemos definido un "campo" email con un array de tres elementos. Recuerda que aunque los tipos definidos permitan tanto cadenas de longitud variable como arrays dinmicos, si ese tipo se va a usar para acceder a datos de un fichero aleatorio, su longitud siempre tiene que ser fija y coincidir con la longitud que se indic a la hora de abrir ese fichero... si no tienes esta "precaucin"... no acceders con "fiabilidad" a los datos contenidos en ese fichero... despus no digas que no te advert... De todas formas, vamos a comprobarlo... Escribe este cdigo en un form: 'Este cdigo en las declaraciones del form Option Explicit Private Type t_colegas Nombre Edad email End Type Dim unColega As t_colegas As String * 30 As Integer As String * 50 As String * 30 As Integer As String * 50
183
Private Type t_colegaDinamico Nombre Edad email End Type Dim unColegaDinamico As t_colegaDinamico Private Sub Form_Load() Dim nFic As Integer nFic = FreeFile Open "miscolegas.dat" For Random As nFic Len = Len(unColega) 'Asignamos los datos With unColega .Nombre = "Pepe" .Edad = 22 .email = "pepe22@mundo.net" End With Put #nFic, 1, unColega 'ahora lo hacemos con el colega dinmico With unColegaDinamico .Nombre = unColega.Nombre .Edad = unColega.Edad .email = unColega.email End With 'Aqu dar error: Put #nFic, 2, unColegaDinamico Close End Sub Ejecuta el programa y te encontrars con un "bonito" error nmero: 59 Bad Record Length o La longitud de registro es incorrecta, si usas la versin castellana del Visual Basic. Lo que nos quiere decir este error es que no puedes grabar datos de una longitud diferente a la especificada a la hora de abrir el fichero. Pero... por qu? si, aparentemente, la longitud es la misma... Pues eso, que slo es en apariencia... si en la ventana "Inmediato" escribes esto, saldrs As String As Integer As String
184
de dudas: Print len(uncolega); len(uncolegadinamico) El resultado ser este: 82 10 En el primer caso, el del coleguilla normal, la longitud es la esperada: 82 Pero en nuestro amigo dinmico, la longitud es 10, independientemente de que "en teora" tenga almacenados esos 82 caracteres del "esttico". Lo que ocurre es que al ser dinmico, el contenido de las variables de caracteres se almacenan en otro sitio y lo nico que hay en esas variables es un "puntero" a la direccin de memoria en la que estn los datos almacenados... al menos ahora sabemos que una variable de cadena de caracteres tiene una longitud de 4 bytes... creo que eso ya lo vimos en la entrega nmero dos o tres... dnde hablamos de lo que ocupa cada variable... los enteros (Integer) ocupan 2 bytes, independientemente de cmo est declarada la variable... lo mismo ocurre con los otros datos numricos. Como te deca, si sabemos que la longitud del registro es de 82 caracteres, podemos definir una cadena de esa longitud y usarla para acceder a los datos. Probemos esto otro: Dim unaCadena As String * 82 '... unaCadena = "Prueba de una cadena" Put #nFic, 2, unaCadena '... Ahora si que funcionar. Pero si esa variable est definida como String normal, no funcionar... Resumiendo: para acceder a los registros de un fichero abierto como Random, las variables usadas deben tener definida la longitud igual que el tamao especificado a la hora de abrirlo. Un poco de complicacin: Vamos a ver un ejemplo, "ilustrativo" ms que prctico. Ya he comentado que con una cadena de longitud fija podemos acceder a un registro. Ahora comprobars lo "cmodo" que es usar los tipos definidos. Pero este ejemplo lo pongo para que sepas cmo se almacenan los datos numricos en una cadena que se va a usar para guardar datos en un fichero de acceso aleatorio. Hemos "comprobado" que un Integer se almacena en dos bytes... por tanto lo que se guarda en el disco es precisamente eso: dos bytes. Cuando se usa un tipo definido, no te tienes que preocupar de cmo se almacena, simplemente asignas el valor a la variable numrica y del resto se ocupa el VB. Viendo el ejemplo anterior, es fcil almacenar un entero, si usamos un tipo definido, pero cmo lo haremos si la variable en la que se almacenar los datos es una cadena de longitud fija? Para ello debemos "desglosar" los datos en distintas partes: Para el Nombre usaremos los primeros 30 bytes de la cadena, para la edad los dos
185
siguientes, es decir: bytes 31 y 32, para el email desde la posicin 33 hasta la 82, o sea desde la 33 con 50 bytes de longitud. Si escribes esto: Dim unaCadena As String * 82 Dim elNombre As String * 30 Dim laEdad As String * 2 Dim elEmail As String * 50 Dim nFic As Integer Dim unaCadena As String * 82 Show nFic = FreeFile Open "miscolegas.dat" For Random As nFic Len = Len(unColega)
elNombre = "Pepe de 23 aos" laEdad = 23 elEmail = "Pepe23@mundo.net" unaCadena = elNombre & laEdad & elEmail Put #nFic, 2, unaCadena Close nFic No conseguirs el resultado esperado... en lugar de 23 aos, tendr: 13106 !!! Dudo mucho que llegue a conseguir esa edad... ni an siendo un gnomo... Si queremos guardar un nmero debemos convertirlo antes... el problema es que "NO HAY FUNCIONES DE CONVERSIN" Antes si que las haba... cuando digo antes, me refiero al Basic del MS-DOS. Para cada tipo de dato numrico existan un par de funciones, una para convertir el nmero en una cadena y el otro para convertir la cadena en un nmero... Y no me estoy refiriendo a las funciones que convierten los nmeros en letras y las cadenas en nmeros... aunque pueda parecerte una contradiccin... no es lo mismo convertir un nmero en una cadena normal que en una cadena para almacenar en el disco como si de un nmero se tratase... A saber, STR o CSTR convierten un nmero en una cadena, es decir que si el nmero es 23, lo convierten en "23" en el caso de CSTR y en " 23" si se usa STR, fjate en el espacio que hay antes del 2 en el segundo caso. Pero cuando se debe convertir un nmero en una cadena de 2 bytes... la cosa cambia... Y no te confundas... no pienses que si usas CSTR lo tienes solucionado... porque en el
186
ejemplo este de "23" est claro... pero y si el nmero es 1998? Tendramos una cadena de 4 caracteres... Como te deca, en los tiempos del MS-DOS, existan las funciones MKI$ y CVI para convertir los nmeros enteros; MKL$ y CVL para los enteros largos, MKS$ y CVS para los "singles" y MKD$ y CVD para los Double. No busques estas funciones... porque no estn... Aunque podemos fabricrnoslas... pero, como no es plan de "romperse" el coco con una chorradilla de estas... slo vamos a usar el ejemplo de los nmeros enteros. Cmo se usaran? En el ejemplo anterior haramos esto: elNombre = "Pepe de 23 aos" laEdad = Mki(23) elEmail = "Pepe23@mundo.net" unaCadena = elNombre & laEdad & elEmail Put #nFic, 2, unaCadena La funcin se hara as: Private Function Mki(ByVal unInteger As Integer) As String Dim sLoByte As String Dim sHiByte As String Dim iChar As Integer iChar = unInteger \ 256 sHiByte = Chr$(iChar) iChar = unInteger Mod 256 sLoByte = Chr$(iChar) 'Devolvemos el valor Mki = sLoByte & sHiByte End Function Que complicacin verdad? Pues como estas... muchas ms... pero eso antes, en el MS-DOS... desde que hay "mogolln" de espacio de almacenamiento... he perdido de vista muchas de estas cosas de "manejo" de nmeros a "bajo nivel"... Pero antes haba que ahorrar cuantos bytes se pudieran... La cuestin es que se divide el nmero en dos partes, la alta que se consigue dividiendo el entero entre 256 y la baja, que es el resto que queda... despus de haberlo dividido por 256... que de eso es de lo que se encarga el MOD... Despus se convierten esos valores en dos bytes usando el CHR$... se unen... y sin necesidad de agitarlo... conseguimos el "batido" que queremos... por tanto, lo asignamos
187
al valor que devolver la funcin... (recuerda que una funcin devuelve un valor cuando se asigna ese valor a su nombre...) Bien... un lo... verdad? Pues ahora ms los... Cmo se asigna esto al revs? Es decir: Cmo se convierte una cadena de 2 bytes en un entero? Con los tipos definidos no hay que hacer nada... ya sabes, que hace la conversin automticamente. Pero si usamos una cadena de longitud fija... ya es otro cantar... Vamos a ver la declaracin de la funcin "equivalente" al CVI del viejo Basic del MS-DOS: Private Function Cvi(ByVal unaCadena As String) As Integer Dim sLoByte As String Dim sHiByte As String sLoByte = Left$(unaCadena, 1) sHiByte = Right$(unaCadena, 1) 'Devolvemos el valor Cvi = Asc(sLoByte) + Asc(sHiByte) * 256 End Function No voy a entrar en detalles, as que paso a un ejemplo "completo" para ver estos resultados. Si vas a comprobar cmo "trabajan" alguna de estas funciones usando "puntos de interrupcin", es decir que quieres que el VB se detenga en una lnea determinada, (para ello debers posicionarte en la lnea, que no debe ser un comentario, y pulsar F9, al llegar a esa lnea el Visual se detiene), debers cambiar la propiedad Autoredraw del form y ponerla a TRUE, de esta forma el contenido del Form (el que se imprime dentro del Form_Load), se mantiene... 'Esto escribelo en el Form_Load Dim nFic As Integer Dim unaCadena As String * 82 Dim laEdad23 As Integer Show laEdad23 = 23 nFic = FreeFile Open "miscolegas.dat" For Random As nFic Len = Len(unColega)
188
'Asignamos los datos With unColega .Nombre = "Pepe" .Edad = 22 .email = "pepe22@mundo.net" End With Put #nFic, 1, unColega
Dim elNombre As String * 30 Dim laEdad As String * 2 Dim elEmail As String * 50 elNombre = "Pepe de 23 aos" laEdad = Mki(laEdad23) elEmail = "Pepe23@mundo.net" unaCadena = elNombre & laEdad & elEmail Put #nFic, 2, unaCadena Get #nFic, 1, unColega With unColega Print Print "Colega 1:" Print .Nombre Print .Edad Print .email End With 'Si se lee as, no hay problemas Get #nFic, 2, unColega With unColega Print Print "Colega 2:" Print .Nombre Print .Edad Print .email End With
189
'De esta manera est la cosa ms complicada... Get #nFic, 2, unaCadena Print Print "Colega 2:" Print Mid$(unaCadena, 1, 30) Print Cvi(Mid$(unaCadena, 31, 2)) Print Mid$(unaCadena, 33, 50) Observa que aunque el segundo dato se haya guardado usando una cadena de longitud fija, (unaCadena), se puede leer tanto con un tipo definido, si es de la misma longitud, claro, como con la cadena de longitud fija. El problema es que tenemos que "desglosar" el contenido de esa cadena... Recuerda que te coment que cada uno de los "campos" del registro ocupa un espacio determinado... o sea que tienen una longitud fija... por eso hay que usar el Mid$, para tomar cada uno de los "trozos" de esa cadena. El Mid$ funciona de la siguiente forma: Mid$ (cadena, posicin_de_inicio, numero_de_caracteres) Lo que significa que devolver el nmero de caracteres indicados por numero_de_caracteres empezando por la posicin: posicin_de_inicio. En caso de que no se especifique el nmero de caracteres, devolver toda la cadena desde la posicin indicada hasta el final de la misma. Y ya que estamos con estas funciones... voy a explicarte las dos usadas en la funcin CVI: Left$ y Right$ El Left$ toma de una cadena los caracteres que se le indiquen, empezando por la izquierda. Right$ hace lo mismo, pero empieza a contar desde la derecha. Unos ejemplos: Left$("Hola Mundo", 4) devolver "Hola", es decir los 4 primeros caracteres. Right$("Hola Mundo", 4) devolver "undo", porque son los cuatro caracteres que hay a la derecha... El Left$ se puede sustituir por Mid$ de la siguiente forma: Mid$("Hola Mundo", 1, 4) es decir: empezando por el primer caracter, dame cuatro... Sin embargo esto otro: Mid$("Hola Mundo", 4) nos dar: "a Mundo", o lo que es lo mismo: desde la posicin cuarta, todo lo que haya en la cadena. Y si quieres imprimir cada uno de los caracteres de una cadena, prueba esto: Dim i As Integer Dim unaCadena As String
190
unaCadena = "Hola Mundo" For i = 1 To Len(unaCadena) Print Mid$(unaCadena, i, 1) Next Lo que se hace aqu es un bucle para cada uno de los caracteres de la cadena, esto se sabe con LEN, que devuelve la longitud de una cadena y con el Mid$, vamos tomando todos los caracteres de uno en uno. Ya que le decimos que nos muestre el caracter que est en la posicin i... slo muestra uno, porque esa es la cantidad que le indicamos que nos devuelva... UF! Creo que algunas veces se me va la olla... y se me olvidan cosas que... creyendo que he explicado... las uso... En fin... la cuestin es que ahora sabes unas instrucciones nuevas... Y ya que estamos... Veamos cmo se puede usar tambin MID$, lo que ocurre es que esta "funcin" tambin es una "instruccin", al igual que ocurra con INPUT, segn se use delante o detrs del signo igual, se convierte en funcin o en instruccin. Ya sabes que una funcin devuelve un valor y se puede usar en una expresin, pero una instruccin "hace" algo y no devuelve ningn valor ni se puede usar en una expresin. En el ejemplo de guardar los registros, se usaron tres variables para almacenar los datos en el fichero: Dim elNombre As String * 30 Dim laEdad As String * 2 Dim elEmail As String * 50 elNombre = "Pepe de 23 aos" laEdad = Mki(laEdad23) elEmail = "Pepe23@mundo.net" unaCadena = elNombre & laEdad & elEmail Put #nFic, 2, unaCadena Pues esto mismo se puede hacer de esta otra forma: Mid$(unaCadena, 1, 30) = "Prueba de una cadena" Mid$(unaCadena, 31, 2) = Mki(laEdad23) Mid$(unaCadena, 33, 50) = "pepe23@mundo.net" Put #nFic, 2, unaCadena
191
Es decir, al igual que la funcin con el mismo nombre, la instruccin MID$ sustituye en "unaCadena" los caracteres indicados por el inicio y la longitud por los que estn despus del signo igual... Creo que me he pasado... bueno, esto al final viene bien... ya que as tenemos otra entrega "medio" preparada... ya que de la que tena prevista de 7 pginas "manuscritas" slo he usado 2 y poco ms... Para completar esta "extraa" entrega... extraa por "no prevista"... vamos a "rematarla" con unos ejercicios, que en este caso sern de manejo de cadenas con las funciones que acabamos de ver... Los ejercicios: 1- Haz que se muestren los caracteres de una cadena al revs. Por ejemplo, si la cadena es "Hola Mundo", se deber imprimir: "odnuM aloH" Que es til si queremos hacer carteles para que se vean al derecho al mirar por el retrovisor del coche... (por decir algo) 2- Usando esta misma cadena, es decir "Hola Mundo", divdela en las dos palabras. Se supone que deber servirte para cualquier frase, as que no hagas nada "fijo". Lo que tienes que hacer es devolver en una cadena todos los caracteres hasta el primer espacio y el resto en otra cadena. Para esto, si quieres, puedes usar el Left$ y el Mid$... al menos para la primera palabra. Y ya est... no te voy a complicar ms la vida... que ya vendrn complicaciones posteriores. Pulsando en este link tienes las soluciones.
Para terminar, slo pedirte disculpas por haberme/haberte "liado"... pero... eso es lo que hay... 8-)
Y como no es plan de perder la costumbre de pedirte que hagas un comentario de la entrega que acaba de terminar... aqu tienes el link para que me hagas saber que te ha parecido... A estas alturas no tendra que recordarte que ese link slo es para hacer comentarios de esta entrega o del curso en general... as que no te voy a decir que no aproveches el link para hacerme consultas... Nos vemos. Guillermo
1- Haz que se muestren los caracteres de una cadena al revs. Dim unaCadena As String Dim i As Integer unaCadena = "Hola Mundo" Cls For i = Len(unaCadena) To 1 Step -1 Print Mid$(unaCadena, i, 1); Next Print Lo que se hace es un bucle "al revs", es decir: empezando por el ltimo caracter... avanzando hasta el primero. De eso se encarga el STEP -1
2- Usando esta misma cadena, es decir "Hola Mundo", divdela en las dos palabras. ' Dim unaCadena As String Dim i As Integer Dim palabra1 As String Dim palabra2 As String unaCadena = "Hola Mundo" Cls For i = 1 To Len(unaCadena) If Mid$(unaCadena, i, 1) = " " Then palabra1 = Mid$(unaCadena, 1, i) 'Tambin as: 'palabra1 = Left$(unaCadena, i)
193
palabra2 = Mid$(unaCadena, i + 1) 'Realmente deberamos abandonar el bucle 'Por la tremenda: 'Exit For 'Terminando el bucle: 'i = Len(unaCadena) End If Next Print palabra1 Print palabra2 En este segundo ejemplo, si no abandonamos el bucle, bien usando EXIT FOR, (algunos me odiarn por esto), bien asignando a la variable i el valor mximo que puede tener: Len(unaCadena); en el caso de que la variable tenga ms de un espacio, se asignar a palabra2 la ltima palabra y a palabra1 todo lo anterior. Si se abandona el bucle, en ese mismo caso: palabra1 tendr la primera palabra y palabra2 todo el resto. Para comprobarlo, cambia la asignacin por esta otra: unaCadena = "Hola Mundo desastroso" Y comprueba lo que te digo... Ms adelante veremos otras formas de "obtener" cada una de las palabras de una cadena...
Ya slo queda esperar a la prxima entrega... en la que "remataremos" lo del acceso aleatorio ese... Nos vemos. Guillermo
194
Realmente no es que se me resista, es que por el camino van surgiendo nuevas cosillas que es bueno que sepas y como este curso no est enfocado para que lo aprueben en ningn plan nacional de estudios... pues eso... Lo que si que me interesa saber es que no te pierdes con tantos desvaros... es decir, que sigues la lnea... aunque eres libre de poder desviarte y salirte por la tangente para mirar los manuales y la ayuda del Visual Basic, que aunque no te lo creas, tambin sirve para aprender... ahora, lo que pasa es que hay que saber comprenderla... y de eso creo que es de lo que de una forma u otra me estoy encargando yo... si no lo consigo, malo y si lo consigo... mejor para todos. Siguiendo con esos desvaros, vamos a empezar esta entrega con el manejo de las cadenas, que, aunque ya vimos un poco de ella en la entrega anterior, no estar de ms saber algo ms. Ten en cuenta que la mayora de las veces vamos a manipular cadenas... y no es plan de que nos pillen "atados" sin saber que hacer... (chiste malo, lo reconozco...) Seguramente no me voy a enrollar demasiado en este tema, digo "seguramente" porque no s en que acabar esta entrega... as que paciencia y disfruta y aprende con lo que se diga en ella. Ahora vamos al tema. Ya hemos visto, cmo tomar caracteres de una cadena desde el principio (LEFT), desde el final (RIGHT) y desde cualquier punto (MID); tambin podemos saber cuantos caracteres tiene una cadena (LEN) e incluso convertir un nmero en cadena (CSTR) o una cadena en nmero (VAL). Tienes que recordar que el VB tiene como tipo de variable por defecto el Variant, por tanto estas funciones realmente devuelven un tipo Variant, dentro de este tipo hay una serie de subtipos, que realmente son los tipos que vimos al principio de este curso, es decir Integer, Long, String, etc. A que viene este nuevo lo Guille? A que es conveniente que lo sepas, nada ms, pero, al menos por ahora no te preocupes por ello, el VB se encarga de hacer sus conversiones, aunque nosotros podemos obligarle a que lo haga, para ello deberemos usar esas funciones aadindole al final el signo dlar ($), ya sabes que ese signo es el que se emplea para indicar que una variable es de cadena. Por tanto LEFT$, MID$ y RIGHT$ devolvern siempre una cadena, mientras que LEFT, MID y RIGHT lo que devuelven es un Variant de subtipo String... un lo... pero ya te digo que no debes preocuparte. Nota del 24/Ago/2003: Repasando esta entrega quiero hacer un inciso, que aunque en el prrafo anterior ha quedado "medio-claro", quiero que se clarifique totalmente, ya que en ese prrafo no te dije que el rendimiento ser mejor si lo que pretendes es usar cadenas de caracteres. Me estoy refiriendo a que uses las funciones acabadas con el signo dlar ($), de esa forma le ests "confirmando" al Visual Basic que quieres usar cadenas de caracteres y no un dato del tipo Variant con "sub-tipo" String. Un Variant siempre es ms "pesado" que un tipo String, (lo de pesado quiere decir que necesitar de ms "tratamiento" por parte del VB). Hay un montn de funciones para esto del manejo y conversin de las cadenas, en esta entrega veremos algunas de ellas, las que usaremos ms a menudo. Si eres de espritu inquieto, (por decir algo), puedes echar mano de la ayuda del Visual Basic y buscar las distintas funciones e instrucciones que manejan o manipulan las cadenas... la verdad que te puedes entretener bastante con ellas... aunque no te fes de todo lo que veas y te diga la ayuda si no lo compruebas por ti mismo... que al menos uno de los ejemplos est
195
equivocado, pero me imagino que se deber a la traduccin... en fin... por no fiarte, no te fes ni de lo que yo te diga, siempre comprueba las cosas, as saldrs de dudas... y si no sales de dudas, pues... peor "pa t" Creo que voy a complicarte un poco ms la vida, pero no te espantes, no va a ser en vano. Adems de los tipos de datos que hemos vistos hasta ahora, hay otro que sirve para esto de las cadenas, o casi, ya que se podra usar para manejar cada uno de los bytes de una cadena, fijate que he dicho "bytes" y no caracteres, ya que si trabajas en un entorno de 32 bits, cada caracter de una cadena se compone de dos bytes, si ests usando el VB de 16 bits slo hay un byte por cada uno de los caracteres. El tipo en cuestin es BYTE y en ese tipo slo se puede almacenar un valor que va desde 0 a 255, es decir un byte, la lgica algunas veces es aplastante... As que, si le echas un vistazo a la ayuda, te encontrars con algunas funciones que tienen al final una letra B, eso significa que devuelve valores tipo Byte, as por ejemplo LENB, devolver el nmero de bytes que tiene una cadena. Prueba esto, vers que si lo usas con el VB de 16 bits te devuelve una cosa distinta a lo que hara en 32 bits: Print Len("Hola Mundo") Print LenB("Hola Mundo") Lo que se mostrar, (recuerda de ponerle un Show, si escribes este cdigo en el evento Load del form), ser 10 y 20 si usas el VB de 32 bits, (recuerda que el VB5 slo es de 32 bits), pero en 16 bits (VB3 VB4-16), mostrar en ambos casos 10. Vamos a practicar algunas cosillas con esto de los Bytes (o con datos de tipo Byte). Ya sabes que puedes crear una array de cualquier tipo de datos, incluso de tipos definidos, as que tambin podemos crear arrays del tipo Byte, pero este tipo de arrays tienen una particularidad. Una cadena (variable de tipo string), realmente es un array del tipo Byte, esto nos permite manejar las cadenas de caracteres como si fuesen arrays de bytes o mejor an usar los arrays del tipo byte, como si fuesen cadenas. Vamos a comprobarlo, pon estas lneas de cdigo en el evento Load de un form: Show 'para que se muestre Dim aByte() As Byte aByte = "Hola Mundo" Print aByte Como comprobars se muestra Hola Mundo... igual que si lo hubieses asignado a una cadena de caracteres. En el VB4-16 te habr dado un error, para evitar ese error y que se muestre el contenido,
196
puedes asignarlo a una cadena e imprimir esa cadena, tambin puedes asignarlo a un Label y se mostrar el contenido de ese array como si fuese una cadena. Esto mismo, por supuesto tambin es aplicable al Visual Basic de 32 bits. Aclaracin para los que trabajis con 16 bits: Para no complicar mucho las cosas, si ests usando 16 bits, comprueba lo que aqu se diga y saca tus propias conclusiones. A pesar de que an hay sistemas funcionando de 16 bits y gente que por tanto programa para esos sistemas, creo que el enfoque adecuado de este curso debera concentrarse en los sistemas de 32 bits, de todas formas, la mayora de las cosas "realmente" vlidas servirn tanto para 16 como para 32 bits. Pero voy an ms lejos, dentro de poco, no s realmente en cuantas entregas, pero el enfoque se ir concentrando en el Visual Basic 5 y posteriores, (cuando los haya), ya que en el VB5 se han introducido cosas realmente interesantes para la programacin enfocada a objetos, (que pronto empezaremos a tocar), que aunque la mayora de esas cosas sean vlidas para el VB4, en algn momento dejar de serlo y slo sern aplicables al VB5 y posteriores... Quiero dejar esto claro, para que despus no haya sorpresas... de todas formas, recalco que esto es un curso bsico y como tal, servir para prcticamente cualquier versin de VB, aunque mi "despiste" seguramente me llevar a tocar cosas slo aplicables en el VB5... aunque eso ser seguramente en la segunda parte de este curso... que la habr, de eso puedes tener casi un 100% de certeza. Pero si pruebas esto: Print Len(aByte) El Visual Basic te dir que "nanai de la china", osease que no puedes saber cuantos caracteres tiene un array de bytes... Al menos usando Len... ni an usando LenB, as que si quieres lo compruebas, pero te dar el mismo error. Ahora bien, para saber los elementos que contiene un array, vimos que existen dos funciones: LBOUND y UBOUND, la primera para saber el valor del primer elemento y la ltima para saber el ltimo elemento del array. Por tanto podemos usar estas dos funciones para averiguar la longitud de esa cadena: Print UBound(aByte) Esto nos dar el nmero mximo de bytes de este array... s, muestra 19 (en 32 bits), en el entorno de 16 bits nos informar de que el elemento mayor de esta matriz es de 9; ya sabes que para 32 bits cada caracter son dos bytes, por tanto 10 caracteres son 20 bytes. El porqu de que muestre uno menos es porque empieza a contar por CERO, es decir que el elemento inferior de este array ser cero, lo podemos comprobar con esto otro: Print LBound(aByte) Tanto en VB de 16 cmo de 32 bits mostrar cero.
197
Si te preguntas que pasara si se aadiera a las declaraciones generales del Form la instruccin: Option Base 1, puedes comprobarlo, pero no cambiar los resultados mostrados, los arrays de Byte siempre empiezan por cero, independiente del valor que indiquemos en el Option Base. Esto es una afirmacin a medias, me explico, si a un array de bytes le asignas el contenido de una cadena, el elemento inferior siempre ser CERO, pero puedes dimensionar una matriz de este tipo de la misma forma que lo haras con cualquier otra de cualquier otro tipo de datos. Lo que ocurre es que deberas tener la precaucin de que el nmero de elementos fuese par si lo que pretendes es que contenga "algo parecido" a una cadena. Vamos a seguir probando un poco ms, escribe este cdigo: Dim aByte() As Byte aByte = "Hola" Dim i& Print aByte For i = LBound(aByte) To UBound(aByte) Print aByte(i); Next Print En el VB de 16 bits, te mostrar esto: 72 111 108 97, sin embargo en 32 bits el resultado sera este otro: 72 0 111 0 108 0 97 0 (si, ya se que he dicho que no iba a decir nada de los 16 bits, pero...) Con esto, se demuestra, ms o menos, eso de que cada caracter de 32 bits ocupa dos bytes, el primero es el normal y el segundo es el extendido... Para "rematar" esto de los arrays de bytes, una ltima prueba, si usas el VB4-16 debers modificarlo un poco, ya que slo tienes un byte por cada caracter y realmente es menos "instructivo" que si ests usando 32 bits. 'Se supone que tienes estas declaraciones de las variables i y a: 'Dim a$, i& 'Los valores deben ser pares ReDim aByte(-10 To 19) Dim j% j = 65 For i = LBound(aByte) To UBound(aByte) Step 2 aByte(i) = j aByte(i + 1) = 0 j = j + 1
198
Next Print aByte Print LBound(aByte), UBound(aByte) a = aByte Print a Aqu hemos usado un array de bytes con ndices que van de -10 a 19, recuerda que de -10 a -1 van 10 y de 0 a 19 van 20, por tanto sern 30 los bytes que tiene este array, es decir 15 caracteres, que van desde A (65) hasta O (letra o mayscula, 79), por eso se imprimen estos quince caracteres: ABCDEFGHIJKLMNO. Para terminar con estas pruebas, escribe esto a continuacin de lo anterior: Print LeftB(a, 6) Print RightB(a, 6) Print MidB(a, 5, 6) Esto ser lo que habr mostrado (en 32 bits): ABC, MNO y CDE Fjate que el MidB empieza por un nmero impar, si lo hicieras por uno par, el VB te mostrara una serie de interrogaciones... porque no sabe que es lo que quieres hacer con eso... ms o menos... Si te preguntas que es lo que ocurrira si en lugar de MidB(a, 5, 6) escribieras esto otro: MidB(a, 0, 6), te encontraras con un error del Visual Basic, ya que el argumento desde, debe ser un valor 1 hasta la longitud de la cadena. Bien, ya sabes algo ms, el resto que veamos tratar de las funciones de cadenas normales y corrientes, cualquier otra prueba con bytes la haces "por libre", cosa que te recomiendo para que te vayas acostumbrando a ellas, pero no te compliques demasiado, ya que no son tan frecuentes como debieran y siempre tendrs a mano la ayuda para poder "recordar" que estn ah.
199
i = 10 a = CStr(i) Print "i=" & a a = Str$(i) Print "i=" & a Como vers, en el primer caso se mostrar: i= 10, en el segundo: i=10, es decir que Str$ le aade el espacio que tiene los nmeros positivos. Si el valor de i fuese -10 mostrara lo mismo en ambos casos. Pero esa no es la nica diferencia, si estamos trabajando con variables (o datos) que contengan decimales, CStr utilizar el decimal que est seleccionado en la configuracin regional y Str$ siempre usar el punto como separador decimal. ' Cuidado con los decimales (24/Ago/03) Dim d As Double d = 123.456 a = CStr(d) Print "d= " & a a = Str$(d) Print "d= " & a
Cambiar a maysculas / minsculas: Para hacer esto, usaremos las funciones: LCase y UCase, la primera convierte la cadena a minsculas y la segunda a maysculas: a = "Un niito clido" Print LCase(a) Print UCase(a) Aunque sea una chorradilla de frase, es para que compruebes que los caracteres acentuados y la ee tambin se convierten. En los nmeros, esto de la conversin no tiene sentido, por tanto no les afectar esto de la conversin y permanecern iguales.
Quitar los espacios del principio y del final: Las funciones usadas para hacer esto, son tres: LTrim$, RTrim$ y Trim$ La primera quita los espacios del principio, la segunda los espacios en blanco del final y la tercera los quita tanto del principio como del final. Esta ltima funcin es lo mismo que si hiciramos esto: Ltrim$(RTrim$(unaCadena)), que
200
es lo que haca yo antes de que el Visual Basic incluyese esta funcin... Vamos a ver un ejemplo: a = " Un ejemplo "
Print "<" & LTrim$(a) & ">" Print "<" & RTrim$(a) & ">" Print "<" & Trim$(a) & ">" El resultado ser el siguiente: <Un ejemplo >, < Un ejemplo>, <Un ejemplo> Es decir: se comprueba que actan como se esperaba...
Averiguar el cdigo de los caracteres: Ya sabes que cada "letra" que puede tener una cadena de caracteres est representada por un cdigo, as la A es el cdigo 65, la Z es el 90, la a es el 92 y la z es el 132. El cdigo del espacio es 32. Para averiguar los cdigos de los caracteres y para asignar a una cadena cualquier cdigo, convertido en el correspondiente caracter, se usan las funciones Chr$ y Asc. La primera devuelve el caracter correspondiente al nmero que se le pasa como parmetro. La segunda hace la operacin inversa, nos dice cual es el cdigo del caracter que se le ha indicado. Veamos un ejemplo: Print Asc("Z") Print Chr$(90) El primero nos dir que 90 es el cdigo de la Z maysculas y en el segundo caso, nos confirma que el caracter representado por 90 ser la Z. Si en ASC se le pasa una cadena con ms de una caracter, slo nos devuelve el valor del primero: Print Asc("Hola") Imprimir 72, es decir el valor de la H mayscula.
Llenar una cadena con una cantidad de caracteres: Para esto, podemos usar un bucle y en cada iteracin del mismo incrementar el contenido de una cadena o bien podemos usar dos de las funciones que el Visual Basic pone a nuestra disposicin: Space$ y String$ La primera devuelve la cantidad de espacios que se le indique, si queremos 10 espacios haremos esto: a = Space$(10) A la segunda se le pasan dos parmetros, el primero indicando la cantidad de caracteres que queremos y el segundo el cdigo de ese caracter: a = String$(10, 65) nos dar diez
201
letras A maysculas. Tambin podemos pasarle en el segundo parmetro una cadena, por tanto el ejemplo anterior se podra escribir as: a = String$(10, "A") Si la cadena que le pasamos tiene ms de un caracter, nos devuelve slo el primer caracter de esa cadena: a = String$(10, "Pepe") Es decir slo 10 letras P.
Comparar el contenido de dos cadenas: Ya sabes que las cadenas se pueden comparar igual que se hacen con los nmeros: usando el signo igual. If "Hola" = "hola" Then Es un ejemplo "chorra", pero se puede hacer. Esta comparacin debera devolver FALSE, ya que la H inicial en una de las cadenas est en maysculas y en la otra en minscula. Si no le hemos indicado nada al VB, nos dir que son diferentes, pero podemos indicarle al Visual que al comparar las cadenas, ignore las diferencias entre las maysculas y minsculas. Para ello tendremos que agregar en la parte general del mdulo, donde est el Option Explicit, la siguiente instruccin: Option Compare Text De esta forma, las comparaciones realizadas siempre sern independiente de que sean maysculas o minsculas, incluso si estn mezcladas las maysculas con las minsculas. Por defecto el Visual Basic hace lo que se llama una comparacin binaria, es decir que comprueba el cdigo de cada uno de los caracteres que componen las cadenas; tambin se le puede indicar esto para que todas la comparaciones realizadas sean siempre en modo "binario", aunque no hace falta indicarlo, ya que es el valor por defecto, pero si quieres hacerlo, para saber que ests haciendo las comparaciones de esa forma, aade esta lnea: Option Compare Binary Las comparaciones tambin se pueden efectuar para saber cual es mayor o menor, el VB se guiar por el cdigo de cada caracter comparado, debers tener en cuenta que los nmeros estn antes que las letras, por tanto "1234" sera menor que "ABCD" y "VWXYZ" ser mayor que "DEFGH" Pero adems de efectuar las comparaciones de esta forma, existe una funcin que sirve precisamente para eso: StrComp Esta funcin permite que las comparaciones se hagan de distinta forma: Usando el valor por defecto, es decir el indicado en Option Compare, usando siempre comparacin binaria, (distingue entre maysculas y minsculas) y usando siempre comparacin "textual", (no hace distincin entre maysculas y minsculas) Los parmetros que usa esta funcin son los siguientes: cadena1, cadena2 y opcionalmente el tipo de comparacin a realizar. Si este ltimo parmetro no se especifica, se efectuar la comparacin segn se indique en Option Compare. En otro caso, los valores pueden ser: 0 (comparacin binaria) 1 (comparacin textual). Existe otro valor que es usado para las bases de datos de Access, pero que nunca lo he llegado a probar... slo lo he visto cuando he leido la ayuda. Los valores que devolver esta funcin sern: 0 si las dos cadenas son iguales,
202
-1 si la primera cadena es menor que la segunda 1 si la primera cadena es mayor que la segunda. Debes tener en cuenta que "HOLA" es menor que "hola", si la comparacin es binaria, lo mismo con "Hola", ya que la H mayscula tiene un cdigo menor que la h minscula. Veamos un ejemplo: Dim b As String a = "Hola" b = "hola" Print "Usando el Option Compare: "; StrComp(a, b) Print "Usando comparacin textual: "; StrComp(a, b, vbTextCompare) Print "Usando comparacin binaria: "; StrComp(a, b, vbBinaryCompare) Los valores de las constantes vbTextCompare y vbBinaryCompare ya estn definidos por el VB y son 1 y 0 respectivamente.
Asignar valores e incrementar el contenido de una cadena: Muchas de las cosillas que estamos viendo en esta entrega, ya se han usado en otras entregas, pero as tendrs una referencia ms a mano... adems de servirte de recordatorio, por si lo has olvidado. La concatenacin consiste en aadir a una cadena el contenido de otras cadenas o caracteres. Como sabrs si quieres incrementar el valor de una variable numrica, haces esto: Num = Num + 1 El basic interpretar primero la expresin, o lo que haya despus del signo igual y el resultado lo asignar a la variable que est a la izquierda del signo igual. Lo mismo hace con las cadenas de caracteres, lo que ocurre es que slo permite la suma. Aunque desde hace tiempo que Microsoft no recomienda esta forma de concatenar las cadenas, ya que el Visual Basic puede "malintepretarlo" y recomienda que se use el signo & (ampersand, creo que se llama). Usando ese signo, el VB sabe exactamente lo que debe hacer. Por tanto si haces esto: a = "Hola" b = "mundo" a = a & " " & b Print a Te mostrar Hola mundo, es decir ha guardado en "a" lo que ya haba adems de un espacio y el contenido de "b"
203
Este tipo de concatenacin tambin puedes hacerlo en los controles Label y Text o en cualquiera que disponga de alguna propiedad en la que puedas guardar una cadena. Por ejemplo el Caption de un label (que es la propiedad por defecto y por tanto se puede omitir). Si tienes un Label2 en el form, podras hacer esto: Label2 = "Hola" Label2 = Label2 & " " & b
Y se mostrar en ese label lo mismo que antes se imprimi. Esto mismo se puede hacer indicando que se asigna a la propiedad Caption de esa etiqueta: Label2.Caption = "Hola" Label2.Caption = Label2.Caption & " El resultado sera el mismo. Nota: Siempre que quieras "sumar" cadenas, deberas usar el signo & en lugar de +, ya que el primero es evidente que "suma" cadenas (concatena), mientras que el segundo, aunque tambin "suma", debera usarse slo para "sumar" nmeros. " & b
Averiguar la posicin de una cadena dentro de otra: Para esto usaremos la funcin Instr, esta funcin espera dos parmetros como mnimo, aunque permite cuatro: Instr( [posInicio,] Cadena1, Cadena2[, tipo_comparacin]) Las dos cadenas, son las que usar para devolver la posicin de la segunda cadena dentro de la primera. Si hacemos esto: Print Instr("Hola mundo", "mundo") nos mostrar un SEIS, ya que "mundo" est en la posicin sexta, al menos ah es dnde empieza. Si se especifica posInicio, empezar a comprobar a partir de esa posicin inclusive, por tanto: Print Instr("Hola mundo", "o") imprimir 2 Print Instr( 6, "Hola mundo", "o") imprimir 10, ya que busca una letra "o" a partir de la posicin 6. El ltimo parmetro: tipo_comparacin es para realizar comparaciones binarias o de texto, como ya hemos visto anteriormente, si este parmetro tiene la misma funcin que en StrComp. Es decir si no se especifica, la comparacin se hace segn el Option Compare y si se indica, se hace segn lo indicado. Si especificas este parmetro, debers indicar tambin la posicin de inicio, sino el VB te dir que los tipos de datos no coinciden. Un ejemplo: Print InStr("Hola mundo", "o") Print InStr(6, "Hola mundo", "o")
204
Print InStr("Hola mundo", "O") Print InStr(1, "Hola mundo", "O", vbBinaryCompare) Print InStr(1, "Hola mundo", "O", vbTextCompare) Dependiendo de que tengamos Option Compare Text el tercer caso imprimir un cero (si no tenemos la comparacin textual) o un 2 si tenemos esa opcin de comparacin En los dems casos, siempre devolver el mismo resultado. Pero no te confundas, devuelven lo mismo, porque sabemos que debe devolverlo, ya que se especifica como segunda cadena una constante que representa a una "o" minscula y en la primera cadena tenemos dos de esa letra, otro gallo cantara si tanto la primera como la segunda cadena fuesen variables y no sabemos lo que va a contener. Cuando sea imprescindible saber la posicin de una cadena dentro de otra, teniendo en cuenta el que haya que distinguir o no entre las maysculas y minsculas, es recomendable que uses el tipo de comparacin que quieres realizar. Si no lo haces, al menos recuerda o comprueba que tipo de comparacin se realizarn por defecto en ese mdulo. Bien, creo que ya tienes a tu disposicin casi todas las funciones de manejo de cadenas que puedas necesitar. Ahora toca que practiques un poco con ellas para que le vayas cogiendo el "tranquillo" y te las aprendas. Una cosa que quiero aclarar es que no pretendo hacer con este curso un "sustituto" de la ayuda o los manuales del Visual Basic, por eso te recomiendo siempre que las instrucciones con las que te encuentres en algunos de los ejemplos, las busques en la ayuda o los manuales, lo mismo que para saber "manejarte" en el entorno de Visual Basic. S que esto ya lo he dicho antes, o al menos debera haberlo dicho, pero lo hago de nuevo para que no esperes un "diccionario" de todas las instrucciones que tiene el Visual Basic. Lo que pretendo siempre es que te enteres de cmo se usan esas instrucciones e incluso cuando usar una en lugar de otra... en fin... si ves que alguna que otra vez se me va la "olla", me lo dices e intentar solucionar ese despiste. Ejercicios? Te podra poner muchos... pero slo voy a ponerte uno y con ese tendrs "pa rato" Escribe una funcin que devuelva la posicin de una cadena dentro de otra, pero comprobando esa posicin por el final. Es decir que Instr("Hola Mundo","o") devolvera 2, pero con nuestra funcin devolvera 10. Podramos llamarla RInstr y se usara as: RInstr("Hola mundo","o") Haz una segunda versin en la cual se le pase como primer parmetro la posicin por la que se empezar a comprobar, de esta forma: RInstr2(6,"Hola mundo","o") nos dar un valor 2 En las soluciones veremos cmo podemos indicar parmetros opcionales en nuestros procedimientos y crearemos una nica funcin que se use igual que Instr, es decir pasando como primer parmetro opcional la posicin de inicio de la bsqueda.
205
206
que ser opcionales. Entonces cmo indicar que el parmetro opcional sea el primero? Pues... ms o menos fcil... aunque con truco. Cuando un parmetro es opcional, se puede usar la funcin IsMissing para comprobar si se ha especificado o no ese parmetro. Esto es cierto siempre que el parmetro opcional sea de tipo Variant sin un valor por defecto. En el VB4 todos los parmetros opcinales deben ser Variant y sin valores por defecto, pero en VB5 (y posteriores) los parmetros opcionales pueden ser del tipo que queramos, adems de poder tener un valor por defecto, es decir, si no se especifica, se le da un valor "predeterminado". Ahora vamos a usar slo los parmetros sin valores predeterminados, pero ms adelante seguramente lo usaremos. Sabiendo que con IsMissing podemos averiguar si se ha especificado o no el parmetro en opcional, haremos lo siguiente: Si se especifican los tres parmetros, el primero ser la posicin por la que se empezar a comprobar. Si no se especifica el ltimo parmetro, engaaremos al Visual Basic, dicindole que los dos parmetros introducidos son la primera y la segunda cadena... Ya ves que slo es echarle un poco de "cabeza" al asunto... Vamos a ver la declaracin de esta funcin: Private Function RInstr(ByVal v1 As Variant, ByVal v2 As Variant, Optional ByVal v3 As Variant) As Long Dim i As Long Dim sTmp As String Dim s1 As String Dim s2 As String Dim posIni As Long If IsMissing(v3) Then 'Si no se especifican los tres parmetros s1 = CStr(v1) s2 = CStr(v2) posIni = Len(s1) Else posIni = CLng(v1) s1 = CStr(v2) s2 = CStr(v3) End If 'Valor inicial de la bsqueda, si no se encuentra, es cero RInstr = 0 'Siempre se empieza a buscar por el final 'la posicin por la que empezar 'la primera cadena (segundo parmetro) 'la segunda cadena (tercer parmetro) 'La primera cadena 'la segunda cadena 'el ltimo caracter de la cadena
207
For i = posIni - Len(s2) + 1 To 1 Step -1 'Tomar el nmero de caracteres que tenga la segunda cadena sTmp = Mid$(s1, i, Len(s2)) 'Si son iguales... If sTmp = s2 Then 'esa es la posicin RInstr = i Exit For End If Next End Function Ahora quedara hacer una funcin que comtemple las mismas opciones que tiene el InStr normal, es decir que se pueda especificar un cuarto parmetro que nos indique si la comparacin se hace de una forma u otra. Que te parece esto como un ejercicio nuevo? Saba que contestara de forma afirmativa... je, je... Pues intntalo... y si quieres ver la solucin, selecciona el contenido de la caja negra esa que hay al final y vers...
Nota: He quitado lo de la seleccin porque no siempre funciona... al menos ahora no se ve el texto al seleccionarlo...
' Private Function RInstr(ByVal v1 As Variant, ByVal v2 As Variant, _ Optional ByVal v3 As Variant, _ Optional ByVal v4 As Variant) As Long Dim i As Long Dim sTmp As String Dim s1 As String
208
Dim s2 As String Dim posIni As Long If IsMissing(v3) Then 'Si no se especifican los tres parmetros s1 = CStr(v1) s2 = CStr(v2) posIni = Len(s1) Else posIni = CLng(v1) s1 = CStr(v2) s2 = CStr(v3) End If 'Valor inicial de la bsqueda, si no se encuentra, es cero RInstr = 0 'Siempre se empieza a buscar por el final For i = posIni - Len(s2) + 1 To 1 Step -1 'Tomar el nmero de caracteres que tenga la segunda cadena sTmp = Mid$(s1, i, Len(s2)) 'Si se especifica el tipo de comparacin If Not IsMissing(v4) Then 'Se usa StrComp con ese parmetro If StrComp(sTmp, s2, v4) = 0 Then 'esa es la posicin RInstr = i Exit For End If Else 'Aqu tambin se podra usar 'If StrComp(sTmp, s2) = 0 Then 'Si son iguales... If sTmp = s2 Then 'esa es la posicin RInstr = i Exit For End If 'la posicin por la que empezar 'la primera cadena (segundo parmetro) 'la segunda cadena (tercer parmetro) 'La primera cadena 'la segunda cadena 'el ltimo caracter de la cadena
209
A diferencia del Visual Basic, no dar error si se especifica el cuarto parmetro y no se especifican los dems, por la sencilla razn de que si slo se especifican 3, el cuarto nunca llega a tener un valor... (elemental mi querido Guille). De lo que tendrs que tener "precaucin", al menos si quieres que funcione todo bien, es de indicar los valores que se esperan... ya que sino es as, no conseguirs tu propsito... o al menos no el deseado.
210
Ya hemos visto, cmo tomar caracteres de una cadena desde el principio (LEFT), desde el final (RIGHT) y desde cualquier punto (MID); tambin podemos saber cuantos caracteres tiene una cadena (LEN) e incluso convertir un nmero en cadena (CSTR) o una cadena en nmero (VAL). Tienes que recordar que el VB tiene como tipo de variable por defecto el Variant, por tanto estas funciones realmente devuelven un tipo Variant, dentro de este tipo hay una serie de subtipos, que realmente son los tipos que vimos al principio de este curso, es decir Integer, Long, String, etc. A que viene este nuevo lo Guille? A que es conveniente que lo sepas, nada ms, pero, al menos por ahora no te preocupes por ello, el VB se encarga de hacer sus conversiones, aunque nosotros podemos obligarle a que lo haga, para ello deberemos usar esas funciones aadindole al final el signo dlar ($), ya sabes que ese signo es el que se emplea para indicar que una variable es de cadena. Por tanto LEFT$, MID$ y RIGHT$ devolvern siempre una cadena, mientras que LEFT, MID y RIGHT lo que devuelven es un Variant de subtipo String... un lo... pero ya te digo que no debes preocuparte. Nota del 24/Ago/2003: Repasando esta entrega quiero hacer un inciso, que aunque en el prrafo anterior ha quedado "medio-claro", quiero que se clarifique totalmente, ya que en ese prrafo no te dije que el rendimiento ser mejor si lo que pretendes es usar cadenas de caracteres. Me estoy refiriendo a que uses las funciones acabadas con el signo dlar ($), de esa forma le ests "confirmando" al Visual Basic que quieres usar cadenas de caracteres y no un dato del tipo Variant con "sub-tipo" String. Un Variant siempre es ms "pesado" que un tipo String, (lo de pesado quiere decir que necesitar de ms "tratamiento" por parte del VB). Hay un montn de funciones para esto del manejo y conversin de las cadenas, en esta entrega veremos algunas de ellas, las que usaremos ms a menudo. Si eres de espritu inquieto, (por decir algo), puedes echar mano de la ayuda del Visual Basic y buscar las distintas funciones e instrucciones que manejan o manipulan las cadenas... la verdad que te puedes entretener bastante con ellas... aunque no te fes de todo lo que veas y te diga la ayuda si no lo compruebas por ti mismo... que al menos uno de los ejemplos est equivocado, pero me imagino que se deber a la traduccin... en fin... por no fiarte, no te fes ni de lo que yo te diga, siempre comprueba las cosas, as saldrs de dudas... y si no sales de dudas, pues... peor "pa t" Creo que voy a complicarte un poco ms la vida, pero no te espantes, no va a ser en vano. Adems de los tipos de datos que hemos vistos hasta ahora, hay otro que sirve para esto de las cadenas, o casi, ya que se podra usar para manejar cada uno de los bytes de una cadena, fijate que he dicho "bytes" y no caracteres, ya que si trabajas en un entorno de 32 bits, cada caracter de una cadena se compone de dos bytes, si ests usando el VB de 16 bits slo hay un byte por cada uno de los caracteres. El tipo en cuestin es BYTE y en ese tipo slo se puede almacenar un valor que va desde 0 a 255, es decir un byte, la lgica algunas veces es aplastante... As que, si le echas un vistazo a la ayuda, te encontrars con algunas funciones que tienen al final una letra B, eso significa que devuelve valores tipo Byte, as por ejemplo LENB, devolver el nmero de bytes que tiene una cadena. Prueba esto, vers que si lo usas con el VB de 16 bits te devuelve una cosa distinta a lo que hara en 32 bits:
211
Print Len("Hola Mundo") Print LenB("Hola Mundo") Lo que se mostrar, (recuerda de ponerle un Show, si escribes este cdigo en el evento Load del form), ser 10 y 20 si usas el VB de 32 bits, (recuerda que el VB5 slo es de 32 bits), pero en 16 bits (VB3 VB4-16), mostrar en ambos casos 10. Vamos a practicar algunas cosillas con esto de los Bytes (o con datos de tipo Byte). Ya sabes que puedes crear una array de cualquier tipo de datos, incluso de tipos definidos, as que tambin podemos crear arrays del tipo Byte, pero este tipo de arrays tienen una particularidad. Una cadena (variable de tipo string), realmente es un array del tipo Byte, esto nos permite manejar las cadenas de caracteres como si fuesen arrays de bytes o mejor an usar los arrays del tipo byte, como si fuesen cadenas. Vamos a comprobarlo, pon estas lneas de cdigo en el evento Load de un form: Show 'para que se muestre Dim aByte() As Byte aByte = "Hola Mundo" Print aByte Como comprobars se muestra Hola Mundo... igual que si lo hubieses asignado a una cadena de caracteres. En el VB4-16 te habr dado un error, para evitar ese error y que se muestre el contenido, puedes asignarlo a una cadena e imprimir esa cadena, tambin puedes asignarlo a un Label y se mostrar el contenido de ese array como si fuese una cadena. Esto mismo, por supuesto tambin es aplicable al Visual Basic de 32 bits. Aclaracin para los que trabajis con 16 bits: Para no complicar mucho las cosas, si ests usando 16 bits, comprueba lo que aqu se diga y saca tus propias conclusiones. A pesar de que an hay sistemas funcionando de 16 bits y gente que por tanto programa para esos sistemas, creo que el enfoque adecuado de este curso debera concentrarse en los sistemas de 32 bits, de todas formas, la mayora de las cosas "realmente" vlidas servirn tanto para 16 como para 32 bits. Pero voy an ms lejos, dentro de poco, no s realmente en cuantas entregas, pero el enfoque se ir concentrando en el Visual Basic 5 y posteriores, (cuando los haya), ya que en el VB5 se han introducido cosas realmente interesantes para la programacin enfocada a objetos, (que pronto empezaremos a tocar), que aunque la mayora de esas cosas sean vlidas para el VB4, en algn momento dejar de serlo y slo sern aplicables al VB5 y posteriores... Quiero dejar esto claro, para que despus no haya sorpresas... de todas formas, recalco que esto es un curso bsico y como tal, servir para prcticamente cualquier versin de VB, aunque mi "despiste" seguramente me llevar a tocar cosas slo aplicables en el
212
VB5... aunque eso ser seguramente en la segunda parte de este curso... que la habr, de eso puedes tener casi un 100% de certeza. Pero si pruebas esto: Print Len(aByte) El Visual Basic te dir que "nanai de la china", osease que no puedes saber cuantos caracteres tiene un array de bytes... Al menos usando Len... ni an usando LenB, as que si quieres lo compruebas, pero te dar el mismo error. Ahora bien, para saber los elementos que contiene un array, vimos que existen dos funciones: LBOUND y UBOUND, la primera para saber el valor del primer elemento y la ltima para saber el ltimo elemento del array. Por tanto podemos usar estas dos funciones para averiguar la longitud de esa cadena: Print UBound(aByte) Esto nos dar el nmero mximo de bytes de este array... s, muestra 19 (en 32 bits), en el entorno de 16 bits nos informar de que el elemento mayor de esta matriz es de 9; ya sabes que para 32 bits cada caracter son dos bytes, por tanto 10 caracteres son 20 bytes. El porqu de que muestre uno menos es porque empieza a contar por CERO, es decir que el elemento inferior de este array ser cero, lo podemos comprobar con esto otro: Print LBound(aByte) Tanto en VB de 16 cmo de 32 bits mostrar cero. Si te preguntas que pasara si se aadiera a las declaraciones generales del Form la instruccin: Option Base 1, puedes comprobarlo, pero no cambiar los resultados mostrados, los arrays de Byte siempre empiezan por cero, independiente del valor que indiquemos en el Option Base. Esto es una afirmacin a medias, me explico, si a un array de bytes le asignas el contenido de una cadena, el elemento inferior siempre ser CERO, pero puedes dimensionar una matriz de este tipo de la misma forma que lo haras con cualquier otra de cualquier otro tipo de datos. Lo que ocurre es que deberas tener la precaucin de que el nmero de elementos fuese par si lo que pretendes es que contenga "algo parecido" a una cadena. Vamos a seguir probando un poco ms, escribe este cdigo: Dim aByte() As Byte aByte = "Hola" Dim i& Print aByte
213
For i = LBound(aByte) To UBound(aByte) Print aByte(i); Next Print En el VB de 16 bits, te mostrar esto: 72 111 108 97, sin embargo en 32 bits el resultado sera este otro: 72 0 111 0 108 0 97 0 (si, ya se que he dicho que no iba a decir nada de los 16 bits, pero...) Con esto, se demuestra, ms o menos, eso de que cada caracter de 32 bits ocupa dos bytes, el primero es el normal y el segundo es el extendido... Para "rematar" esto de los arrays de bytes, una ltima prueba, si usas el VB4-16 debers modificarlo un poco, ya que slo tienes un byte por cada caracter y realmente es menos "instructivo" que si ests usando 32 bits. 'Se supone que tienes estas declaraciones de las variables i y a: 'Dim a$, i& 'Los valores deben ser pares ReDim aByte(-10 To 19) Dim j% j = 65 For i = LBound(aByte) To UBound(aByte) Step 2 aByte(i) = j aByte(i + 1) = 0 j = j + 1 Next Print aByte Print LBound(aByte), UBound(aByte) a = aByte Print a Aqu hemos usado un array de bytes con ndices que van de -10 a 19, recuerda que de -10 a -1 van 10 y de 0 a 19 van 20, por tanto sern 30 los bytes que tiene este array, es decir 15 caracteres, que van desde A (65) hasta O (letra o mayscula, 79), por eso se imprimen estos quince caracteres: ABCDEFGHIJKLMNO. Para terminar con estas pruebas, escribe esto a continuacin de lo anterior: Print LeftB(a, 6) Print RightB(a, 6)
214
Print MidB(a, 5, 6) Esto ser lo que habr mostrado (en 32 bits): ABC, MNO y CDE Fjate que el MidB empieza por un nmero impar, si lo hicieras por uno par, el VB te mostrara una serie de interrogaciones... porque no sabe que es lo que quieres hacer con eso... ms o menos... Si te preguntas que es lo que ocurrira si en lugar de MidB(a, 5, 6) escribieras esto otro: MidB(a, 0, 6), te encontraras con un error del Visual Basic, ya que el argumento desde, debe ser un valor 1 hasta la longitud de la cadena. Bien, ya sabes algo ms, el resto que veamos tratar de las funciones de cadenas normales y corrientes, cualquier otra prueba con bytes la haces "por libre", cosa que te recomiendo para que te vayas acostumbrando a ellas, pero no te compliques demasiado, ya que no son tan frecuentes como debieran y siempre tendrs a mano la ayuda para poder "recordar" que estn ah.
215
d = 123.456 a = CStr(d) Print "d= " & a a = Str$(d) Print "d= " & a
Cambiar a maysculas / minsculas: Para hacer esto, usaremos las funciones: LCase y UCase, la primera convierte la cadena a minsculas y la segunda a maysculas: a = "Un niito clido" Print LCase(a) Print UCase(a) Aunque sea una chorradilla de frase, es para que compruebes que los caracteres acentuados y la ee tambin se convierten. En los nmeros, esto de la conversin no tiene sentido, por tanto no les afectar esto de la conversin y permanecern iguales.
Quitar los espacios del principio y del final: Las funciones usadas para hacer esto, son tres: LTrim$, RTrim$ y Trim$ La primera quita los espacios del principio, la segunda los espacios en blanco del final y la tercera los quita tanto del principio como del final. Esta ltima funcin es lo mismo que si hiciramos esto: Ltrim$(RTrim$(unaCadena)), que es lo que haca yo antes de que el Visual Basic incluyese esta funcin... Vamos a ver un ejemplo: a = " Un ejemplo "
Print "<" & LTrim$(a) & ">" Print "<" & RTrim$(a) & ">" Print "<" & Trim$(a) & ">" El resultado ser el siguiente: <Un ejemplo >, < Un ejemplo>, <Un ejemplo> Es decir: se comprueba que actan como se esperaba...
Averiguar el cdigo de los caracteres: Ya sabes que cada "letra" que puede tener una cadena de caracteres est representada por un cdigo, as la A es el cdigo 65, la Z es el 90, la a es el 92 y la z es el 132. El cdigo del espacio es 32. Para averiguar los cdigos de los caracteres y para asignar a una cadena cualquier cdigo,
216
convertido en el correspondiente caracter, se usan las funciones Chr$ y Asc. La primera devuelve el caracter correspondiente al nmero que se le pasa como parmetro. La segunda hace la operacin inversa, nos dice cual es el cdigo del caracter que se le ha indicado. Veamos un ejemplo: Print Asc("Z") Print Chr$(90) El primero nos dir que 90 es el cdigo de la Z maysculas y en el segundo caso, nos confirma que el caracter representado por 90 ser la Z. Si en ASC se le pasa una cadena con ms de una caracter, slo nos devuelve el valor del primero: Print Asc("Hola") Imprimir 72, es decir el valor de la H mayscula.
Llenar una cadena con una cantidad de caracteres: Para esto, podemos usar un bucle y en cada iteracin del mismo incrementar el contenido de una cadena o bien podemos usar dos de las funciones que el Visual Basic pone a nuestra disposicin: Space$ y String$ La primera devuelve la cantidad de espacios que se le indique, si queremos 10 espacios haremos esto: a = Space$(10) A la segunda se le pasan dos parmetros, el primero indicando la cantidad de caracteres que queremos y el segundo el cdigo de ese caracter: a = String$(10, 65) nos dar diez letras A maysculas. Tambin podemos pasarle en el segundo parmetro una cadena, por tanto el ejemplo anterior se podra escribir as: a = String$(10, "A") Si la cadena que le pasamos tiene ms de un caracter, nos devuelve slo el primer caracter de esa cadena: a = String$(10, "Pepe") Es decir slo 10 letras P.
Comparar el contenido de dos cadenas: Ya sabes que las cadenas se pueden comparar igual que se hacen con los nmeros: usando el signo igual. If "Hola" = "hola" Then Es un ejemplo "chorra", pero se puede hacer. Esta comparacin debera devolver FALSE, ya que la H inicial en una de las cadenas est en maysculas y en la otra en minscula. Si no le hemos indicado nada al VB, nos dir que son diferentes, pero podemos indicarle al Visual que al comparar las cadenas, ignore las diferencias entre las maysculas y minsculas. Para ello tendremos que agregar en la parte general del mdulo, donde est el Option Explicit, la siguiente instruccin:
217
Option Compare Text De esta forma, las comparaciones realizadas siempre sern independiente de que sean maysculas o minsculas, incluso si estn mezcladas las maysculas con las minsculas. Por defecto el Visual Basic hace lo que se llama una comparacin binaria, es decir que comprueba el cdigo de cada uno de los caracteres que componen las cadenas; tambin se le puede indicar esto para que todas la comparaciones realizadas sean siempre en modo "binario", aunque no hace falta indicarlo, ya que es el valor por defecto, pero si quieres hacerlo, para saber que ests haciendo las comparaciones de esa forma, aade esta lnea: Option Compare Binary Las comparaciones tambin se pueden efectuar para saber cual es mayor o menor, el VB se guiar por el cdigo de cada caracter comparado, debers tener en cuenta que los nmeros estn antes que las letras, por tanto "1234" sera menor que "ABCD" y "VWXYZ" ser mayor que "DEFGH" Pero adems de efectuar las comparaciones de esta forma, existe una funcin que sirve precisamente para eso: StrComp Esta funcin permite que las comparaciones se hagan de distinta forma: Usando el valor por defecto, es decir el indicado en Option Compare, usando siempre comparacin binaria, (distingue entre maysculas y minsculas) y usando siempre comparacin "textual", (no hace distincin entre maysculas y minsculas) Los parmetros que usa esta funcin son los siguientes: cadena1, cadena2 y opcionalmente el tipo de comparacin a realizar. Si este ltimo parmetro no se especifica, se efectuar la comparacin segn se indique en Option Compare. En otro caso, los valores pueden ser: 0 (comparacin binaria) 1 (comparacin textual). Existe otro valor que es usado para las bases de datos de Access, pero que nunca lo he llegado a probar... slo lo he visto cuando he leido la ayuda. Los valores que devolver esta funcin sern: 0 si las dos cadenas son iguales, -1 si la primera cadena es menor que la segunda 1 si la primera cadena es mayor que la segunda. Debes tener en cuenta que "HOLA" es menor que "hola", si la comparacin es binaria, lo mismo con "Hola", ya que la H mayscula tiene un cdigo menor que la h minscula. Veamos un ejemplo: Dim b As String a = "Hola" b = "hola" Print "Usando el Option Compare: "; StrComp(a, b) Print "Usando comparacin textual: "; StrComp(a, b, vbTextCompare) Print "Usando comparacin binaria: "; StrComp(a, b, vbBinaryCompare)
218
Los valores de las constantes vbTextCompare y vbBinaryCompare ya estn definidos por el VB y son 1 y 0 respectivamente.
Asignar valores e incrementar el contenido de una cadena: Muchas de las cosillas que estamos viendo en esta entrega, ya se han usado en otras entregas, pero as tendrs una referencia ms a mano... adems de servirte de recordatorio, por si lo has olvidado. La concatenacin consiste en aadir a una cadena el contenido de otras cadenas o caracteres. Como sabrs si quieres incrementar el valor de una variable numrica, haces esto: Num = Num + 1 El basic interpretar primero la expresin, o lo que haya despus del signo igual y el resultado lo asignar a la variable que est a la izquierda del signo igual. Lo mismo hace con las cadenas de caracteres, lo que ocurre es que slo permite la suma. Aunque desde hace tiempo que Microsoft no recomienda esta forma de concatenar las cadenas, ya que el Visual Basic puede "malintepretarlo" y recomienda que se use el signo & (ampersand, creo que se llama). Usando ese signo, el VB sabe exactamente lo que debe hacer. Por tanto si haces esto: a = "Hola" b = "mundo" a = a & " " & b Print a Te mostrar Hola mundo, es decir ha guardado en "a" lo que ya haba adems de un espacio y el contenido de "b" Este tipo de concatenacin tambin puedes hacerlo en los controles Label y Text o en cualquiera que disponga de alguna propiedad en la que puedas guardar una cadena. Por ejemplo el Caption de un label (que es la propiedad por defecto y por tanto se puede omitir). Si tienes un Label2 en el form, podras hacer esto: Label2 = "Hola" Label2 = Label2 & " " & b
Y se mostrar en ese label lo mismo que antes se imprimi. Esto mismo se puede hacer indicando que se asigna a la propiedad Caption de esa etiqueta: Label2.Caption = "Hola" Label2.Caption = Label2.Caption & " El resultado sera el mismo. " & b
219
Nota: Siempre que quieras "sumar" cadenas, deberas usar el signo & en lugar de +, ya que el primero es evidente que "suma" cadenas (concatena), mientras que el segundo, aunque tambin "suma", debera usarse slo para "sumar" nmeros.
Averiguar la posicin de una cadena dentro de otra: Para esto usaremos la funcin Instr, esta funcin espera dos parmetros como mnimo, aunque permite cuatro: Instr( [posInicio,] Cadena1, Cadena2[, tipo_comparacin]) Las dos cadenas, son las que usar para devolver la posicin de la segunda cadena dentro de la primera. Si hacemos esto: Print Instr("Hola mundo", "mundo") nos mostrar un SEIS, ya que "mundo" est en la posicin sexta, al menos ah es dnde empieza. Si se especifica posInicio, empezar a comprobar a partir de esa posicin inclusive, por tanto: Print Instr("Hola mundo", "o") imprimir 2 Print Instr( 6, "Hola mundo", "o") imprimir 10, ya que busca una letra "o" a partir de la posicin 6. El ltimo parmetro: tipo_comparacin es para realizar comparaciones binarias o de texto, como ya hemos visto anteriormente, si este parmetro tiene la misma funcin que en StrComp. Es decir si no se especifica, la comparacin se hace segn el Option Compare y si se indica, se hace segn lo indicado. Si especificas este parmetro, debers indicar tambin la posicin de inicio, sino el VB te dir que los tipos de datos no coinciden. Un ejemplo: Print InStr("Hola mundo", "o") Print InStr(6, "Hola mundo", "o") Print InStr("Hola mundo", "O") Print InStr(1, "Hola mundo", "O", vbBinaryCompare) Print InStr(1, "Hola mundo", "O", vbTextCompare) Dependiendo de que tengamos Option Compare Text el tercer caso imprimir un cero (si no tenemos la comparacin textual) o un 2 si tenemos esa opcin de comparacin En los dems casos, siempre devolver el mismo resultado. Pero no te confundas, devuelven lo mismo, porque sabemos que debe devolverlo, ya que se especifica como segunda cadena una constante que representa a una "o" minscula y en la primera cadena tenemos dos de esa letra, otro gallo cantara si tanto la primera como la segunda cadena fuesen variables y no sabemos lo que va a contener. Cuando sea imprescindible saber la posicin de una cadena dentro de otra, teniendo en cuenta el que haya que distinguir o no entre las maysculas y minsculas, es recomendable que uses el tipo de comparacin que quieres realizar. Si no lo haces, al menos recuerda o comprueba que tipo de comparacin se realizarn por defecto en ese mdulo. Bien, creo que ya tienes a tu disposicin casi todas las funciones de manejo de cadenas que puedas necesitar.
220
Ahora toca que practiques un poco con ellas para que le vayas cogiendo el "tranquillo" y te las aprendas. Una cosa que quiero aclarar es que no pretendo hacer con este curso un "sustituto" de la ayuda o los manuales del Visual Basic, por eso te recomiendo siempre que las instrucciones con las que te encuentres en algunos de los ejemplos, las busques en la ayuda o los manuales, lo mismo que para saber "manejarte" en el entorno de Visual Basic. S que esto ya lo he dicho antes, o al menos debera haberlo dicho, pero lo hago de nuevo para que no esperes un "diccionario" de todas las instrucciones que tiene el Visual Basic. Lo que pretendo siempre es que te enteres de cmo se usan esas instrucciones e incluso cuando usar una en lugar de otra... en fin... si ves que alguna que otra vez se me va la "olla", me lo dices e intentar solucionar ese despiste. Ejercicios? Te podra poner muchos... pero slo voy a ponerte uno y con ese tendrs "pa rato" Escribe una funcin que devuelva la posicin de una cadena dentro de otra, pero comprobando esa posicin por el final. Es decir que Instr("Hola Mundo","o") devolvera 2, pero con nuestra funcin devolvera 10. Podramos llamarla RInstr y se usara as: RInstr("Hola mundo","o") Haz una segunda versin en la cual se le pase como primer parmetro la posicin por la que se empezar a comprobar, de esta forma: RInstr2(6,"Hola mundo","o") nos dar un valor 2 En las soluciones veremos cmo podemos indicar parmetros opcionales en nuestros procedimientos y crearemos una nica funcin que se use igual que Instr, es decir pasando como primer parmetro opcional la posicin de inicio de la bsqueda.
Y siguiendo la "sana" costumbre, te pido tu opinin sobre esta entrega. Nos vemos. Guillermo P.S. Aqu tienes las soluciones a esta entrega
221
RInstr1 = 0 For i = Len(s1) To 1 Step -1 sTmp = Mid$(s1, i, Len(s2)) If sTmp = s2 Then RInstr1 = i Exit For End If Next End Function Este tiene poco que explicar. Se hace un bucle desde la ltima posicin de la primera cadena Len(s1) hasta el principio, ya que lo que necesitamos es encontrar la posicin de s2 empezando a "mirar" desde el final. A continuacin guardamos en sTmp un trozo de cadena que va desde la posicin actual y que contiene tantos caracteres como tenga la segunda cadena, si te fijas, el bucle se podra hacer tambin de esta forma: For i = Len(s1) - Len(s2) + 1 To 1 Step -1 Ya que no nos sirve comparar el ltimo caracter con la cadena s2 cuando esta ltima tiene ms de un caracter, por ejemplo. Esto es inapreciable en cadenas cortas, pero en otras ms largas, acortar el tiempo del bucle. Para el segundo caso, hay que saber que los procedimientos (sub y function) permiten parmetros opcionales, estos se indican con la palabra Optional delante de los parmetros que vayan a ser opcionales. La nica pega es que cuando un parmetro es opcional los que le siguen tambin tienen que ser opcionales. Entonces cmo indicar que el parmetro opcional sea el primero? Pues... ms o menos fcil... aunque con truco. Cuando un parmetro es opcional, se puede usar la funcin IsMissing para comprobar si se ha especificado o no ese parmetro. Esto es cierto siempre que el parmetro opcional sea de tipo Variant sin un valor por defecto. En el VB4 todos los parmetros opcinales deben ser Variant y sin valores por defecto, pero en VB5 (y posteriores) los parmetros opcionales pueden ser del tipo que queramos, adems de poder tener un valor por defecto, es decir, si no se especifica, se le da un valor "predeterminado". Ahora vamos a usar slo los parmetros sin valores predeterminados, pero ms adelante seguramente lo usaremos. Sabiendo que con IsMissing podemos averiguar si se ha especificado o no el parmetro en opcional, haremos lo siguiente: Si se especifican los tres parmetros, el primero ser la posicin por la que se empezar a comprobar. Si no se especifica el ltimo parmetro, engaaremos al Visual Basic, dicindole que los dos parmetros introducidos son la primera y la segunda cadena... Ya ves que slo es echarle un poco de "cabeza" al asunto... Vamos a ver la declaracin de esta funcin:
222
Private Function RInstr(ByVal v1 As Variant, ByVal v2 As Variant, Optional ByVal v3 As Variant) As Long Dim i As Long Dim sTmp As String Dim s1 As String Dim s2 As String Dim posIni As Long If IsMissing(v3) Then 'Si no se especifican los tres parmetros s1 = CStr(v1) s2 = CStr(v2) posIni = Len(s1) Else posIni = CLng(v1) s1 = CStr(v2) s2 = CStr(v3) End If 'Valor inicial de la bsqueda, si no se encuentra, es cero RInstr = 0 'Siempre se empieza a buscar por el final For i = posIni - Len(s2) + 1 To 1 Step -1 'Tomar el nmero de caracteres que tenga la segunda cadena sTmp = Mid$(s1, i, Len(s2)) 'Si son iguales... If sTmp = s2 Then 'esa es la posicin RInstr = i Exit For End If Next End Function Ahora quedara hacer una funcin que comtemple las mismas opciones que tiene el InStr normal, es decir que se pueda especificar un cuarto parmetro que nos indique si la comparacin se hace de una forma u otra. Que te parece esto como un ejercicio nuevo? Saba que contestara de forma afirmativa... je, je... 'la posicin por la que empezar 'la primera cadena (segundo parmetro) 'la segunda cadena (tercer parmetro) 'La primera cadena 'la segunda cadena 'el ltimo caracter de la cadena
223
Pues intntalo... y si quieres ver la solucin, selecciona el contenido de la caja negra esa que hay al final y vers...
A ver si ya terminamos en la prxima entrega el acceso aleatorio a los ficheros. Nos vemos. Guillermo
Para ver la solucin, selecciona todo el contenido del siguiente cuadro y vers... Nota: He quitado lo de la seleccin porque no siempre funciona... al menos ahora no se ve el texto al seleccionarlo...
' Private Function RInstr(ByVal v1 As Variant, ByVal v2 As Variant, _ Optional ByVal v3 As Variant, _ Optional ByVal v4 As Variant) As Long Dim i As Long Dim sTmp As String Dim s1 As String Dim s2 As String Dim posIni As Long If IsMissing(v3) Then 'Si no se especifican los tres parmetros s1 = CStr(v1) s2 = CStr(v2) posIni = Len(s1) Else posIni = CLng(v1) s1 = CStr(v2) s2 = CStr(v3) End If 'la posicin por la que empezar 'la primera cadena (segundo parmetro) 'la segunda cadena (tercer parmetro) 'La primera cadena 'la segunda cadena 'el ltimo caracter de la cadena
224
'Valor inicial de la bsqueda, si no se encuentra, es cero RInstr = 0 'Siempre se empieza a buscar por el final For i = posIni - Len(s2) + 1 To 1 Step -1 'Tomar el nmero de caracteres que tenga la segunda cadena sTmp = Mid$(s1, i, Len(s2)) 'Si se especifica el tipo de comparacin If Not IsMissing(v4) Then 'Se usa StrComp con ese parmetro If StrComp(sTmp, s2, v4) = 0 Then 'esa es la posicin RInstr = i Exit For End If Else 'Aqu tambin se podra usar 'If StrComp(sTmp, s2) = 0 Then 'Si son iguales... If sTmp = s2 Then 'esa es la posicin RInstr = i Exit For End If End If Next End Function
A diferencia del Visual Basic, no dar error si se especifica el cuarto parmetro y no se especifican los dems, por la sencilla razn de que si slo se especifican 3, el cuarto nunca llega a tener un valor... (elemental mi querido Guille). De lo que tendrs que tener "precaucin", al menos si quieres que funcione todo bien, es de indicar los valores que se esperan... ya que sino es as, no conseguirs tu propsito... o al menos no el deseado.
Ya es hora de seguir con los ficheros de acceso aleatorio, despus del alto en el camino para ver cmo se manejan las cadenas de caracteres en Visual Basic, an no hemos visto todas las funciones, pero si las ms comunes. No te preocupes... no voy a continuar en esta entrega con esas funciones... ya le llegarn el turno... Por fin! Habrs exclamado... y con razn... pero son cosas que debes saber... y como de lo que se trata es de saber ms y/o mejor, pues... nunca estn de ms... No recuerdo exactamente en que punto me qued en las explicaciones sobre el acceso aleatorio de la entrega diecisis, as que... si ves que me repito, pues... salud! y a seguir adelante... Una cosa que hay que tener superclarsimo en esto del acceso aleatorio, es que debemos usar tipos definidos para acceder a los datos del fichero... para que complicarnos con funciones conversoras de datos, si el Visual lo hace de forma automtica por nosotros? Por tanto, te aconsejo que "siempre" uses variables definidas, ya que no hay un motivo vlido para no usarlas. En los siguientes ejemplos, vamos a usar el tipo definido que usamos en la entrega diecisis, el del colega... En este primer caso, vamos a guardar en un registro determinado el contenido de tres cajas de textos, cada una de ellas para cada uno de los campos del tipo definido: Private Sub cmdGuardar_Click() Dim unColega As t_colega Dim nFic As Long Dim numColega As Long nFic = FreeFile Open "colegas.dat" For Random As nFic Len = Len(unColega) 'Sacar un valor aleatorio numColega = Rnd * 25 + 1 Label1(3) = "nmero de colega: " & CStr(numColega) With unColega .Nombre = Text1 .Edad = Val(Text2) .email = Text3 End With 'Guardar los datos en el disco Put #nFic, numColega, unColega
226
Close nFic End Sub Aqu vemos los pasos que normalmente se realizan con cualquier tipo de fichero... --Se asigna a una variable un nmero de fichero libre (ya sabes: el canal por el cual VB se comunicar con el disco) --Se abre el fichero en cuestin, recuerda que las extensiones que uses son a tu antojo, no hay necesidad de usar un tipo de extensin especfica, salvo que hagas un programa que "entienda" los datos contenidos en ese tipo de extensin y puedas abrir los ficheros con slo hacer doble click... pero esto es un tema para ms adelante... --Se asigna a la variable el dato a guardar y --Se guarda... Este ejemplo no sera prctico, ya que puede que salga el mismo nmero ms de una vez y se perderan los datos anteriores. Porque debes saber que, cuando se guarda informacin en un registro determinado, ste funciona de la misma forma que las variables... o casi, es decir: cuando se guarda un nuevo valor, el que hubiera antes "desaparece". Una cosa que debes saber, aunque me imagino que lo habrs comprobado al ejecutar el programa, y si no ha sido as, no te preocupes... si te sirve de consuelo, tarde unos mesesillos en "detectar" esto que te voy a explicar ahora... Puedes acceder a cualquier registro de un fichero aleatorio, incluso si antes no has guardado nada. De la misma forma, puedes guardar informacin en el registro 7 aunque antes no hayas guardado en ninguno de los 6 anteriores. El problema? Que si lees informacin de un registro en el que no has guardado informacin anteriormente... puedes encontrarte con "basura", y de hecho la encontrars... Por qu? Porque accedes a una parte del disco que, posiblemente tena guardada alguna otra informacin... aprovecho esto, para decirte que, cuando borras un fichero del disco, este fichero no se borra, al menos no se borra la informacin que contena. Cmo solucionar este problemilla? Hay varios mtodos, el que yo normalmente usaba, (ahora casi no trabajo con ficheros de acceso aleatorio), era guardar informacin "vaca" en unos cuantos registros y cuando esos estaban ocupados, guardaba otro puado y as. Algunas veces, si saba que el fichero iba a tener un nmero limitado de registros, los grababa todos con datos vacos, es decir cadenas con slo espacios y nmeros con valor cero. En otras ocasiones tena un campo del registro al que le asignaba un valor, si al leer el registro, tena ese valor "predeterminado", quera decir que ya contena informacin vlida, si no era as, pona los campos con valores "vacos" y as evitaba la basura. Si quieres comprobarlo... as de paso me sirve para que veas un ejemplo de cmo acceder a los datos del disco y mostrarlo en unas cajas de texto. Private Sub cmdLeer_Click() Dim unColega As t_colega Dim nFic As Long
227
Dim numColega As Long nFic = FreeFile Open "colegas.dat" For Random As nFic Len = Len(unColega) 'Sacar un valor aleatorio numColega = Rnd * 25 + 1 Label1(3) = "nmero de colega: " & CStr(numColega) 'leer ese registro Get #nFic, numColega, unColega With unColega Text1 = .Nombre Text2 = .Edad Text3 = .email End With Close nFic End Sub Si has probado el ejemplo de guardar, para poder conseguir datos con contenido "basura", debers probar algunas veces ms que las que hayas probado antes... por qu? porque estamos usando nmeros aleatorios y al no existir un "Randomize", la secuencia de nmeros aleatorios siempre es la misma cada vez que ejecutas el programa... lo recuerdas? Bien, aparte del asuntillo este de la "basura", no tiene ningn misterio esto del acceso aleatorio... pero an no hemos terminado, no queda mucho para que te toque "trabajar", pero todava tienes un respiro... si a que te explique ms cosas se puede llamar respiro... En el tercer ejemplo que vamos a ver, se van a mostrar todos los colegas que tenemos guardados en el fichero. Se supone que los datos los hemos guardado de forma ordenada, no como en los ejemplos anteriores, ya que no tiene ningn motivo guardar a los colegas aleatoriamente... no sea que cojan complejo de bola de bingo... Ya te he comentado que la longitud de todos los registros de un fichero aleatorio tienen que ser iguales... y ahora lo podrs comprobar... que algunas veces no hay que fiarse de todo lo que yo diga... El nmero de registros es el resultado de dividir la longitud del fichero por la longitud de cada registro... numRegistros = Lof(nFic) \ Len(unColega) Fjate que uso \ para dividir. Las divisiones realizadas con este signo dan como resultado nmeros enteros, mientras que el habitual / realiza una divisin de coma flotante... es decir que puede tener decimales. Como un registro no puede estar formado por "nosecuantos caracteres y pico", en este
228
caso es recomendable el uso de la divisin de nmeros enteros, entre otras cosas porque tambin es ms rpida... Por tanto este cdigo nos informara del nmero de registros y hara un bucle entre todos los registros que tiene el fichero. ' nFic = FreeFile Open "colegas.dat" For Random As nFic Len = Len(unColega) numRegistros = Lof(nFic) \ Len(unColega) For i = 1 To numRegistros Get #nFic, i, unColega 'Mostrar el contenido del registro '... Next Close nFic Podemos sustituir el Get #nFic, i, unColega por esto otro: Get #nFic, , unColega. Cuando no se indica el nmero de registro, tanto en Get como en Put, Visual Basic usa el registro actual. Cada vez que se accede a un registro determinado, el Basic lo "marca" como registro actual, de esta forma sabe cual debe ser el siguiente registro al que debe acceder si no se le indica ninguno. Si hacemos esto: Get #nFic, 15, unColega, al hacer esto otro: Put #nFic, , unColega, se estar guardando en el nmero 16. Es decir, el VB mantiene un "puntero" al siguiente registro. El bucle anterior podra haber quedado as: For i = 1 To numRegistros Get #nFic, , unColega 'Mostrar el contenido del registro '... Next De todas formas, siempre suelo especificar el nmero de registro al que quiero acceder, as me parece que estoy ms seguro de dnde se leer o escribir el registro. Uno de los problemas que tiene este tipo de ficheros es que estamos "atados" a la longitud del registro. Que ocurre si nos d el "punto" de quitar, aadir o cambiar la longitud de alguno de los campos? Pues que lo tenemos ms bien chungo... estaremos metidos en un pequeo lio... Una vez que hemos definido el tamao del registro, y tenemos datos en el fichero, cualquier cambio que hagamos, dar como resultado algo no esperado... Pero, todo tiene arreglo... aunque en este caso, nos lo tendremos que "currar" nosotros. Tendremos que fabricarnos alguna rutina que se encargue de esta conversin.
229
Y ese podra ser el ejercicio de esta entrega: Realizar una pequea utilidad que convierta un fichero de acceso aleatorio con registros de una longitud conocida, en otro con registros de otra longitud o con campos de longitud diferente al original. Y digo "longitud conocida", porque si no sabemos la longitud de cada registro, incluso de cada campo de ese registro, poco podremos hacer... Un detalle importante es que para acceder a los ficheros abiertos como "random", slo podemos hacerlo con variables de longitud fija, ya sean tipos definidos o cadenas. Se podra pensar que haciendo esto: Dim unaCadena As String 'Asignamos 83 espacios a esta cadena unaCadena = Space$(83) nFic = FreeFile Open "colegas.dat" For Random As nFic Len = Len(unaCadena) '... Get #nFic, 1, unaCadena Pues no! Ya que la variable unaCadena no tiene longitud fija y el Visual Basic necesita que lo sea. Otra cosa es que hagamos esto otro: 'Esta cadena siempre tendr este nmero de caracteres Dim unaCadena As String * 83 nFic = FreeFile Open "colegas.dat" For Random As nFic Len = Len(unaCadena) '... Get #nFic, 1, unaCadena Ahora si que funcionara bien la cosa, ya que el VB tiene la informacin que necesita... La verdad es que hecho de menos una instruccin que tena el Basic del MS-DOS y era que podas definir una serie de variables para acceder a los registros, incluso podas declarar una array... y cada uno de los elementos del array con la longitud de cada uno de los campos... Pero eso ya no est, as que... hay que usar los tipos definidos que al fin y al cabo es la mejor opcin para este tipo de ficheros. Para el ejercicio, se supone que tenemos un fichero con registros declarados de esta forma: Private Type t_Colega
230
Nombre As String * 30 Edad email End Type Y lo queremos convertir en registros que tengan esta otra: Private Type t_Colega Nombre As String * 30 Edad email URL End Type Por supuesto, queremos que los registros que haya sigan conservando los datos que tuviesen... sino, que clase de rutina conversora sera? Acurdate de lo que digo en muchas ocasiones, no pienses en complicarte la vida, siempre procura ir a lo simple, ya que en la mayora de las ocasiones ese es el camino correcto... Cuando veamos la siguiente entrega, o al menos cuando veamos los ficheros de acceso binario, (mi intencin es que sea en la prxima entrega, pero ya sabes...), crearemos otra utilidad de conversin ms flexible que esta, es decir, una rutina que convierta un fichero a un formato que pueda ser definido por el usuario en tiempo de ejecucin. Aunque no sea lo habitual, y normalmente una entrega termina cuando te pongo los ejercicios, vamos a ver un ejemplo completo para introducir y mostrar datos en un fichero de acceso aleatorio. Cmo? Que quieres hacerlo t? Pues vale, me parece estupendo... Desde luego, es que tengo unos alumnos que no me merezco... 8-) No te quejes... y no digas que t no has dicho nada... yo he odo voces que me decan: queremos hacerlo nosotros! Y eso es lo que hay... Para esta aplicacin vamos a usar tres cajas de texto en los que introduciremos los valores a guardar en cada campo, con sus correspondientes labels para indicarnos los nombres de esos campos. Existir otro label/textbox para indicarnos el nmero del registro actual, es decir en el que se almacenarn los datos o del que se leern esos datos. Un par de botones para Guardar y Leer... Te resulta familiar? Pues as es... algo parecido a lo que ya vimos en la entrega nmero once cuando se usaron arrays de tipos definidos... El aspecto del "programilla" sera algo as: As Integer As String * 50 As String * 128 As Integer As String * 50
231
No he aadido nada para mostrar todos los registros... pero ya tendremos tiempo de hacerlo. Bueno, pues hasta aqu hemos llegado... Las soluciones de la entrega 18 estn en este link.
Y a pesar de parecer un poco pesado, realmente me gustara recibir tu opinin sobre esta entrega. Nos vemos. Guillermo
232
Private Sub cmdConvertir_Click() Dim unColega As t_Colega, unColega2 As t_Colega2 Dim nFic As Long, nFic2 As Long Dim numColegas As Long Dim i As Long 'abrir el fichero original nFic = FreeFile Open "colegas.dat" For Random As nFic Len = Len(unColega) 'abrir el fichero de destino nFic2 = FreeFile Open "colegas2.dat" For Random As nFic2 Len = Len(unColega2) numColegas = LOF(nFic) \ Len(unColega) For i = 1 To numColegas 'leer el registro Get #nFic, i, unColega 'Asignar los nombres del nuevo tipo With unColega unColega2.Nombre = .Nombre unColega2.Edad = .Edad unColega2.email = .email unColega2.URL = "" End With 'Guardar el nuevo registro Put #nFic2, i, unColega2 Next Close nFic2 Close nFic
233
'Si quieres eliminar el fichero anterior y cambiarle el nombre 'hazlo despus de cerrar los ficheros End Sub Este es el listado completo del segundo ejercicio: '-----------------------------------------------------------------'Ejercicio de la entrega 18 ' 'Guillermo 'guille' Som, 1998 '-----------------------------------------------------------------Option Explicit 'Tipo para usar en el fichero Private Type t_Colega Nombre As String * 30 Edad As Integer email As String * 50 End Type 'Esta variable se usar para acceder a los datos Dim m_unColega As t_Colega 'Nmero de registros del fichero Dim m_numColegas As Long 'Nmero del colega actual, usado cuando se edita, etc. Dim m_elColega As Long 'Esta variable guardar el fichero a usar Dim m_sFicColegas As String 'Esta se usar como FLAG para saber si hemos cambiado 'el registro actual Dim m_Modificado As Boolean (26/Abr/98)
Private Sub cmdGuardar_Click() Dim nFic As Long 'Slo si el nmero del colega es el indicado en Text4
234
'de esta forma slo se guardar cuando se pulse en 'Nuevo o en Leer If m_elColega = Val(Text4) Then nFic = FreeFile Open m_sFicColegas For Random As nFic Len = Len(m_unColega) With m_unColega .Nombre = Text1 .Edad = Val(Text2) .email = Text3 End With 'Guardar los datos en el disco Put #nFic, m_elColega, m_unColega Close nFic 'Ajustar el nmero de colegas m_numColegas = CuantosColegas() m_Modificado = False 'Posicionar el cursor en el nmero de registro Text4.SetFocus End If End Sub
Private Sub cmdLeer_Click() Dim nFic As Long 'No se comprueba si se ha modificado el registro actual 'esto habra que tenerlo en cuenta... lo he dejado preparado 'con la variable m_Modificado 'Te dejo que hagas las comparaciones pertinentes... '... 'Slo leer si no se est aadiendo uno nuevo If m_elColega <= m_numColegas Then m_elColega = Val(Text4)
235
'Pero que no se lea un valor "no vlido" If m_elColega > 0 And m_elColega <= m_numColegas Then nFic = FreeFile Open m_sFicColegas For Random As nFic Len = Len(m_unColega) 'leer ese registro Get #nFic, m_elColega, m_unColega 'quitarle los espacios "extras", ya que al ser 'de longitud fija, los espacios en blanco tambin 'se mostrarn en la caja de texto 'Para comprobarlo, quita el Trim$ y vers lo que 'ocurre cuando el nombre tiene menos caracteres... With m_unColega Text1 = Trim$(.Nombre) Text2 = .Edad Text3 = Trim$(.email) End With Close nFic m_Modificado = False Text1.SetFocus Else 'si el nmero no es vlido... Text4.SetFocus m_elColega = 0 End If End If End Sub
236
'slo si no se est introduciendo uno nuevo If m_elColega <> m_numColegas + 1 Then m_elColega = m_numColegas + 1 Text4 = m_elColega 'Limpiar el contenido de las cajas de texto Text1 = "" Text2 = "" Text3 = "" 'Limpiar tambin la variable el registro actual, 'aunque realmente no es necesario... With m_unColega .Nombre = "" .Edad = 0 .email = "" End With m_Modificado = False 'Posicionar el cursor en el campo del nombre Text1.SetFocus End If End Sub
Private Sub Form_Load() 'asignamos el path del fichero de colegas: m_sFicColegas = App.Path & "\Colegas.dat" 'Esta asignacin fallar si el path es el directorio raiz 'por tanto se debera comprobar de esta forma: If Right$(App.Path, 1) = "\" Then m_sFicColegas = App.Path & "Colegas.dat" Else m_sFicColegas = App.Path & "\Colegas.dat" End If 'Tambin de esta otra forma... algo menos "clara" m_sFicColegas = App.Path & _ IIf(Right$(App.Path, 1) = "\", "", "\") & _
237
"Colegas.dat" 'Inicialmente leer el nmero de registros 'lo pongo en una funcin para usarlo cuando se necesite, 'sin tener que repetir el proceso, aunque corto, pero... m_numColegas = CuantosColegas() 'Borrar el contenido de los TextBox Text1 = "" Text2 = "" Text3 = "" Text4 = "" 'Para empezar no se ha modificado m_Modificado = False End Sub
Private Function CuantosColegas() As Long 'Esta funcin se encarga de informarnos del nmero de registros 'que tiene el fichero 'Usarlo slo cuando queremos saber esta informacin y 'no necesitamos mantener el fichero abierto 'si no existe el fichero, se producir un error On Local Error Resume Next CuantosColegas = FileLen(m_sFicColegas) \ Len(m_unColega) If Err Then CuantosColegas = 0 End If Label1(4) = "Nmero de colegas:" & CuantosColegas Err = 0 End Function
238
'Por si se qued o estaba el fichero abierto... Close Set Form1 = Nothing End Sub
Private Sub Text1_Change() 'Si en lugar de usar tres TextBox distintos se usara un array 'sera ms cmodo, ya que slo se pondr esta asignacin 'en un slo evento Change. ' m_Modificado = True End Sub Private Sub Text2_Change() m_Modificado = True End Sub Private Sub Text3_Change() m_Modificado = True End Sub
A ver si la prxima entrega no se hace de rogar demasiado, que ya estamos casi a punto de acabar con esto del acceso a los ficheros... Nos vemos. Guillermo
239
yankinglish... pero no es plan... en fin, tendr que seguir buscando las teclas y despus de pulsar una tecla mirar para el papel... Bueno, menos rollo, a ver si soy capaz de terminarlo pronto, para ir a ponerme como un salmonete, que hoy es da de playa, adems de que es fiesta local y esas cosas pa que los catetillos podamos ir a darnos un remojn a la playa, con una buena torta de San Juan... Me acuerdo yo que antes... vale, vale!, no hace falta que grites..., lo dejo, pero que sepas que te pierdes lo que iba a decir...
240
Ya vimos en el acceso aleatorio que el clculo de la posicin de cada registro podamos dejarlo de forma automtica, es decir que sea el propio VB el que "decida" la posicin. Realmente el VB no decide nada, ya que es una caracterstica de GET y PUT, si no se le indica la posicin, usa la "predeterminada" y esa posicin se ajusta automticamente cada vez que se lee o escribe informacin, el clculo se hace tomando la ltima posicin y aadindole la longitud del dato. En el caso de los ficheros aleatorios esa posicin es "ficticia" (o relativa), ya que el VB convierte la posicin real dentro del fichero en nmero de registros... Pero ahora no estamos con el acceso aleatorio, sino con el binario y con este tipo de acceso, trabajamos con posiciones "reales", es decir que si hacemos esto: Get nFic, 3, A$ Leeremos caracteres desde la posicin tres del fichero, el nmero de caracteres ledos estar indicado por la longitud de la variable A$ Como ya coment antes, la ventaja es que no estamos obligados a leer un nmero determinado de caracteres y el inconveniente es que hay que saber lo que estamos haciendo y se puede convertir en un inconveniente si no lo usamos de la forma adecuada. Pero, vamos a demostrar esto que acabo de decir. Crea un nuevo proyecto, asignale a la propiedad AutoRedraw del form el valor TRUE, de esta forma no habr problemas a la hora de imprimir, (y ver lo impreso), en el formulario. Esto del Autoredraw es til cuando nuestro form quede oculto por otra ventana, nunca perder lo que hayamos imprimido en l. Aade un commandbutton y escribe el siguiente cdigo: Private Sub Command1_Click() Dim nFic As Integer Dim sFic As String Dim sCadena As String 'sCadena tiene 20 caracteres sCadena = "Prueba de una cadena" sFic = "binarios_19.dat" nFic = FreeFile Open sFic For Binary As nFic Put nFic, , sCadena Close nFic 'leer los datos guardados nFic = FreeFile Open sFic For Binary As nFic Get nFic, , sCadena Print sCadena Close nFic End Sub
241
Ejecuta la aplicacin (pulsando F5), pulsa en el Command1, y vers que todo funciona bien. Ahora aade lo siguiente antes del Get nFic, , sCadena: sCadena = Space$(5) Y ejecuta de nuevo el programa. Como vers slo se han leido los cinco primeros caracteres de lo que se guard anteriormente. Es decir slo mostrar Prueb, porque la cadena usada para leer tiene esa cantidad de caracteres. Este es un detalle que debers recordar, as que apntatelo. La ventaja es que podemos guardar y leer distintos tipos de datos mezclados. Por ejemplo, si sabemos que tenemos un tipo definido y despus una cadena de caracteres, podemos mezclarlo. Pero es importante que a la hora de leer los datos, leamos la cantidad "justa" de caracteres, y en el orden correcto. Vamos a ver esto que acabo de decir. Borra el cdigo anterior o crea un nuevo proyecto y aade un command y el siguiente cdigo: 'Esto en la parte General del form Option Explicit Private Type t_colega Nombre As String * 30 Edad As Integer End Type
Private Sub Command1_Click() Dim nFic As Integer Dim sFic As String Dim sCadena As String Dim unColega As t_colega unColega.Nombre = "Guille" unColega.Edad = 40 'sCadena tiene 20 caracteres sCadena = "Prueba de una cadena" sFic = "binarios_19.dat" nFic = FreeFile Open sFic For Binary As nFic Put nFic, , unColega Put nFic, , sCadena Close nFic
242
'leer los datos guardados nFic = FreeFile Open sFic For Binary As nFic Get nFic, , unColega Get nFic, , sCadena 'mostramos los datos leidos Print unColega.Nombre, unColega.Edad Print sCadena Close nFic End Sub Si inviertes el orden de las variables a la hora de leer, pues causas un pequeo desastre, ya que no lees lo que esperas leer. Osea que no uses esto del acceso binario "al voleo", sino pensndolo bien. Entonces, cuando es conveniente usar el acceso binario? Siempre que queramos acceder a un fichero del que estimemos que puede que no sea del tipo ASCII, es decir un fichero que pueda contener cualquier clase de caracteres. Normalmente los ficheros ASCII, (o los usados habitualmente para acceso secuencial), terminan cuando se encuentra un cdigo EOF (caracter ASCII nmero 26) o cuando ya no hay ms caracteres en el fichero; sin embargo con el acceso binario slo se "acaban" cuando no quedan ms caracteres que leer del fichero. Por supuesto que si un fichero se ha guardado usando un tipo de acceso, puede abrirse usando otro tipo de acceso, aunque estos casos no son recomendables, salvo que sepamos lo que hacemos... Veamos un nuevo ejemplo. Ya sabes, borra el cdigo usado anteriormente o crea un nuevo proyecto. Private Sub Command1_Click() Dim nFic As Integer Dim sFic As String Dim sCadena As String sFic = "binarios_19.dat" nFic = FreeFile Open sFic For Binary As nFic Put nFic, , "Prueba de una cadena" Put nFic, , vbCrLf 'Se guarda una segunda cadena Put nFic, , "Segunda cadena" Put nFic, , vbCrLf Close nFic
243
'leer como secuencial nFic = FreeFile Open sFic For Input As nFic Do While Not EOF(nFic) Line Input #nFic, sCadena Print sCadena Loop Close nFic End Sub Como habrs comprobado, se ha ledo todo lo que haba en el fichero, incluso cosas que haba de pruebas anteriores. Ahora engaemos al VB y hagamos que piense que un fichero se ha acabado antes de que se acabe de forma "real". Sustituye el cdigo del Command1 por este otro: Private Sub Command1_Click() Dim nFic As Integer Dim sFic As String Dim sCadena As String Dim sEOF As String * 1 sEOF = Chr$(26) sFic = "binarios_19.dat" nFic = FreeFile Open sFic For Binary As nFic Put nFic, , "Prueba de una cadena" Put nFic, , vbCrLf 'Aadimos un cdigo de fin de fichero Put nFic, , sEOF 'Se guarda una segunda cadena Put nFic, , "Segunda cadena" Put nFic, , vbCrLf Close nFic 'leer como secuencial nFic = FreeFile Open sFic For Input As nFic
244
Do While Not EOF(nFic) Line Input #nFic, sCadena Print sCadena Loop Close nFic End Sub Ahora slo se ha mostrado lo guardado antes del cdigo almacenado en la variable sEOF. Pero el fichero contina teniendo lo que antes tena. Lo que ocurre es que cuando se abre un fichero secuencial y el VB se encuentra con el cdigo 26, piensa que se debe haber terminado el fichero en cuestin. Ahora vamos a leerlo como binario... Por supuesto, sabiendo lo que se ha guardado y cmo se ha guardado. Private Sub Command1_Click() Dim nFic As Integer Dim sFic As String Dim sCadena As String Dim sEOF As String * 1 Dim sCRLF As String * 2 sEOF = Chr$(26) sCRLF = vbCrLf 'sCadena tiene 20 caracteres sCadena = "Prueba de una cadena" sFic = "binarios_19.dat" nFic = FreeFile Open sFic For Binary As nFic Put nFic, , sCadena Put nFic, , sCRLF Put nFic, , sEOF 'Se guarda una cadena de 15 caracteres Put nFic, , "Segunda cadena" Put nFic, , sCRLF Close nFic 'leer los datos guardados nFic = FreeFile Open sFic For Binary As nFic
245
'Se leen slo 5 caracteres de los 20 guardados sCadena = Space$(5) Get nFic, , sCadena Print sCadena sCadena = Space$(15) 'Leemos los caracteres que quedaron pendientes, 'ya que sCadena slo ley 5 de los 20 caraceteres que tena Get nFic, , sCadena 'tambin leemos los caracteres "extras" que se guardaron Get nFic, , sCRLF Get nFic, , sEOF Print sCadena Get nFic, , sCadena Print sCadena Close nFic End Sub Bien, ahora ya hemos conseguido leer todo, pero fjate en el detalle de que hemos tendo que leer los cdigos "extras" que se guardaron, es decir el retorno de carro y el de fin de fichero. Si no lo hubieramos hecho... pruebalo y lo compruebas... Habrs observado una lnea de ms y un caracter extrao antes de "Segunda cade"... creo que no hace falta que te explique el porqu... verdad? Normalmente con el acceso binario podemos leer todos los caracteres que haya en un fichero. Se suele usar cuando no sabemos la estructura de ese fichero y tenemos alguna forma de "interpretar" lo que leemos, aunque esto ltimo no se aprende en ningn curso y no hay regla fija. En la mayora de las ocasiones que uso el acceso binario, es cuando quiero leer informacin de un fichero para buscar algo en concreto. Ese fichero puede ser un ejecutable, una DLL o cualquier otro tipo de fichero. Si de antemano se que es un fichero secuencial, seguramente no lo leera como binario, ya que el tiempo de acceso y lectura de un fichero secuencial es menor que uno abierto como binario. Osea que se lee antes uno abierto con For Input que con For Binary. Esta diferencia en el tiempo de acceso es apreciable sobre todo cuando se manejan muchos ficheros... Hablando de leer todo el contenido de un fichero, ya sabes que existe un funcin llamada Input$, a la que se le indica el nmero del fichero abierto y la cantidad de caracteres que queremos leer y nos lo devuelve para que podamos asignarlo a una variable de cadena. El fichero que hemos creado con el ltimo ejemplo no es realmente un fichero ASCII, ya que contiene caracteres binarios, es decir, caracteres que no estn en el rago ASCII del 32 al 255 (en algunos casos hasta el cdigo 127). Veamos la reaccin del VB ante un caso "no deseado", es decir leer lo que no debemos leer: 'Se supone que has probado los ejemplos anteriores y que existe el fichero indicado
246
Private Sub Command1_Click() Dim nFic As Integer Dim sFic As String Dim sCadena As String sFic = "binarios_19.dat" nFic = FreeFile ' Open sFic For Input As nFic sCadena = Input$(LOF(nFic), nFic) Close nFic Print sCadena End Sub Aqu lo que se pretenda era leer el fichero de una vez. Pero como se ha abierto el fichero como secuencial, el VB ha detectado que se la ledo un cdigo de fin de fichero, precisamente antes de que se acabe el fichero y nos avisa con un magnfico mensaje de error. Esto se soluciona, bien abriendo el fichero secuencial, pero usando EOF(nFic) para comprobar si hemos alcanzado el final del fichero o bien abriendo el fichero en modo binario y usando la funcin Input$. Private Sub Command1_Click() Dim nFic As Integer Dim sFic As String Dim sCadena As String sFic = "binarios_19.dat" nFic = FreeFile ' Open sFic For Binary As nFic sCadena = Input$(LOF(nFic), nFic) Close nFic Print sCadena End Sub Ahora puedes observar que se lee TODO lo que hay en el fichero, ya que al abrirlo en modo binario, no se tiene en cuenta ningn caracter especial y se lee hasta dnde se le indique.
247
Creo que es suficiente por hoy. Hay ms cosas, en cuanto al acceso a los ficheros, pero no vamos a alargar esta entrega, que puede ser que te empaches con tantas cosas y no es bueno.
Los ejercicios Recuerdas uno de los ejercicios de la entrega 18? Consista en cambiar el formato de un fichero de acceso aleatorio en otro con los campos en otro formato (longitud de los campos). Pues bien, con el acceso aleatorio slo era posible si conocamos de antemano el tamao del registro nuevo (y, por supuesto, la longitud de cada campo). Ahora con lo que sabes del acceso binario y unas cuantas miles de lneas de cdigo, podrs hacerlo para convertir cualquier fichero a un nuevo formato y as poder usarlo de forma genrica. Es decir, tenemos un fichero con una serie de campos y queremos cambiar la estructura de ese fichero y, por supuesto, traspasar la informacin existente al nuevo formato. El usuario de esta utilidad, tendr que saber la estructura del fichero de origen y tambin saber la nueva estructura a la que quiere convertir el susodicho fichero de datos. Para no complicar demasiado la cosa, vamos a usar slo 10 campos, pero se podran permitir ms... El aspecto del form en tiempo de diseo sera el siguiente: No te preocupes por los "campos" que faltan en el destino, enseguida te explico cmo hacer que aparezcan, al menos a la hora de ejecutar el programa. Esto te servir para otras veces en las que necesites crear controles en tiempo de ejecucin y posicionarlos adecuadamente, o casi... Veamos el aspecto del form y despus veremos el cdigo usado para "crear" esos controles:
248
El cdigo: Private Sub Form_Load() Dim i As Integer 'Crear los controles de destino '(empezamos por UNO porque el control CERO ya est creado) For i = 1 To 9 'Cargarlos en memoria Load lblDest(i) Load txtDestTam(i) 'Asignarles la posicin y hacerlos visible With txtDestTam(i) .Visible = True .Top = txtDestTam(i - 1).Top + .Height + 45 lblDest(i).Top = .Top - 15 lblDest(i).Visible = True lblDest(i) = "Campo " & i + 1 & ":" End With Next
249
'Borrar el contenido de los TextBoxes For i = 0 To 9 txtTam(i).Text = "" txtDestTam(i).Text = "" Next End Sub La cuestin est en que al pulsar en el botn de convertir el fichero, antes se hayan asignado los valores correspondientes. La informacin que hay que pasarle a la aplicacin de cada campo, es la siguiente: tamao de cada campo. Lo que hay que saber es el tamao de los nmeros al guardarlo en disco, para ello echale un vistazo a la ayuda del VB y te dir lo que ocupa cada tipo, aunque creo que en alguna de las primeras entragas ya lo vimos. Para muestra, un botn: Los Integers ocupan dos bytes, los Longs cuatro bytes, etc. La cuestin es que se asignen los valores de forma correcta, sino, la cosa puede no funcionar. Y con esto y un bizcocho... hasta otra entrega, que no creo que sea maana a las ocho... Este es el link a la solucin de esta entrega, recuerda no hacer trampas e intentarlo primero. (De todas formas, el link no te llevar a ningn sitio, ya que an no la he puesto... je, je...) Nota 25/Jun/98: Ya est operativo el link de la solucin al ejercicio.
Y continuando con la sana costumbre de recibir tus preciados, y la mayora de las veces aduladores, comentarios, cosa que agradezco y que dicho sea de paso, es la nica razn por la que sigo con el curso bsico... pues eso, aqui te dejo el link para que me escribas lo que te ha parecido esta entrega, pero, recuerda: no para hacer consultas, gracias. Nos vemos. Guillermo
250
esto me ayudar a saber si tengo que poner cosas ms sencillas o dedicarme a ensear otras cosillas, no s..., por ejemplo porqu cuando todo est oscuro no se ve nada... je. Para volver a la entrega 19, pulsa en este link. Este es el listado completo de la solucin que YO he encontrado al ejercicio, por supuesto no tiene porqu ser igual a la tuya, si quieres puedes mandarme una copia del resultado que has encontrado... no te garantizo nada, pero lo mismo hasta te comento sobre l... Venga, nimo! que lo difcil an no ha empezado... ;-) Esta es una foto del programa en ejecucin y el listado del mismo:
'-----------------------------------------------------------------'Ejercicio para la entrega 19 '(solucin) ' 'Guillermo 'guille' Som, 1998 '-----------------------------------------------------------------Option Explicit (24/Jun/98)
251
'Para probar uso el fichero de colegas.dat 'el tamao de cada campo era: 30, 2, 50 'Private Type t_Colega ' ' ' ' txtOrigen = "colegas.dat" 'Crear los controles de destino '(empezamos por UNO porque el control CERO ya est creado) For i = 1 To 9 'Cargarlos en memoria Load lblDest(i) Load txtDestTam(i) 'Asignarles la posicin y hacerlos visible With txtDestTam(i) .Visible = True .Top = txtDestTam(i - 1).Top + .Height + 45 lblDest(i).Top = .Top - 15 lblDest(i).Visible = True lblDest(i) = "Campo " & i + 1 & ":" 'Ajustar el TabIndex, '(se supone que ya estaban por orden) lblDest(i).TabIndex = txtDestTam(i - 1).TabIndex + 1 .TabIndex = lblDest(i).TabIndex + 1 End With Next 'Borrar el contenido de los TextBoxes For i = 0 To 9 txtTam(i).Text = "" txtDestTam(i).Text = "" Next End Sub Nombre As String * 30 Edad As Integer email As String * 50
'End Type
252
'Variables para los nombres y nmeros de ficheros Dim nFic As Long, nFic2 As Long Dim sFic As String, sFic2 As String 'Estos arrays controlarn los tamaos de cada campo Dim aOrigen() As Long Dim aDestino() As Long 'Nmero de campos en cada fichero Dim nOrigen As Integer Dim nDestino As Integer 'Tamaos de los registros Dim tOrigen As Integer Dim tDestino As Integer 'Las cadenas que contendrn los datos Dim sOrigen As String Dim sDestino As String 'Nmero de registros del fichero de origen Dim numReg As Integer Dim tamFic As Long 'Para usos generales Dim i As Long, j As Long Dim posReg As Long Dim sTmp As String 'Antes de hacer nada, comprobamos que exista el fichero 'de origen sFic = Trim$(txtOrigen) If Len(Dir$(sFic)) = 0 Then MsgBox "ATENCIN! No existe el fichero de origen." txtOrigen.SetFocus Exit Sub End If 'Asignamos el nombre del fichero de destino sFic2 = Trim$(txtDestino) 'Se asignarn los tamaos de cada registro, se dejar 'de comprobar cuando el contenido del textbox sea cero. 'Si se usara un TextBox con el nmero de campos, la cosa 'sera ms fcil de controlar, pero...
253
' 'Empezamos por el origen For i = 0 To 9 If Val(txtTam(i)) = 0 Then 'ya no hay nada ms que comprobar Exit For Else nOrigen = nOrigen + 1 ReDim Preserve aOrigen(nOrigen) 'asignamos el tamao del campo nOrigen aOrigen(nOrigen) = Val(txtTam(i)) 'ajustamos el tamao total del registro tOrigen = tOrigen + aOrigen(nOrigen) End If Next 'Ahora comprobamos el destino For i = 0 To 9 If Val(txtDestTam(i)) = 0 Then 'ya no hay nada ms que comprobar Exit For Else nDestino = nDestino + 1 ReDim Preserve aDestino(nDestino) 'asignamos el tamao del campo nDestino aDestino(nDestino) = Val(txtDestTam(i)) 'ajustamos el tamao total del registro tDestino = tDestino + aDestino(nDestino) End If Next ' 'Ya tenemos la informacin suficiente, ' 'Por si da error al acceder a los ficheros On Local Error GoTo ErrorConvertir 'Abrimos los ficheros en modo binario nFic = FreeFile Open sFic For Binary As nFic
254
'Averiguar el nmero de registros de este fichero tamFic = LOF(nFic) numReg = tamFic \ tOrigen 'Comprobar que el tamao especificado concuerda con el fichero 'Si el nmero de registros multiplicado por el tamao de cada 'registro es diferente al tamao del fichero... If numReg * tOrigen <> tamFic Then MsgBox "Los tamaos especificados en los campos de origen" & vbCrLf & _ "no concuerdan con el tamao del fichero.", vbCritical, "Convertir ficheros" Close txtTam(0).SetFocus Exit Sub End If 'Abrimos el fichero de destino nFic2 = FreeFile Open sFic2 For Binary As nFic2 ' 'Preparamos la cadena que contendr los datos de origen 'esta no cambiar de tamao sOrigen = Space$(tOrigen) 'Hacemos un bucle para todos los registros de origen For j = 1 To numReg Get nFic, , sOrigen 'La cadena de destino se formar con el tamao de 'los campos de origen ms el tamao de los nuevos campos, 'si el nmero de campos de destino es diferente, 'simplemente se rellenar la cadena con espacios sDestino = "" ' 'Esta variable contendr la posicin dentro del registro 'del campo que se est procesando posReg = 1 For i = 1 To nOrigen 'Tomamos el contenido del campo actual sTmp = Mid$(sOrigen, posReg, aOrigen(i)) 'Asignamos este campo y lo rellenamos de espacios sTmp = Left$(sTmp & Space$(aDestino(i)), aDestino(i))
255
sDestino = sDestino & sTmp 'ajustamos el tamao de la posicin dentro del registro 'de origen posReg = posReg + aOrigen(i) Next 'Ahora hay que rellenar la cadena de destino con espacios 'suficientes hasta completar el nmero de caracteres 'que se han especificado. ' 'El TRUCO est en aadirle a la cadena de destino la 'cantidad de caracteres totales y slo quedarnos 'con esa cantidad, de esta forma nos aseguramos que 'tendremos la cantidad que necesitamos tener... ' sDestino = Left$(sDestino & Space$(tDestino), tDestino) 'Lo guardamos Put nFic2, , sDestino Next 'Se acab de convertir, cerramos los ficheros Close 'Guardamos la informacin de los formatos usados: ' 'Uso un formato standard INI para que se pueda leer de forma 'fcil, incluso usando el ejemplo de la entrega 20 ' nFic = FreeFile Open "Convertir.ini" For Output As nFic 'Datos de origen: Print #nFic, "[Datos de Origen]" Print #nFic, "Fichero=" & sFic Print #nFic, "Nmero de campos=" & nOrigen For i = 1 To nOrigen Print #nFic, "Tamao Campo" & CStr(i) & "=" & aOrigen(i) Next Print #nFic, "" 'Datos de destino: Print #nFic, "[Datos de Destino]" Print #nFic, "Fichero=" & sFic2
256
Print #nFic, "Nmero de campos=" & nDestino For i = 1 To nDestino Print #nFic, "Tamao Campo" & CStr(i) & "=" & aDestino(i) Next Close 'Avisamos de que todo acab bien MsgBox "Se ha convertido el fichero de forma satisfactoria," & vbCrLf & _ "La informacin de los datos convertidos est en: Convertir.ini", _ vbInformation, "Convertir ficheros." SalirConvertir: Close Exit Sub ErrorConvertir: MsgBox "Se ha producido el siguiente error:" & vbCrLf & _ Err.Number & " " & Err.Description, vbCritical, "Convertir ficheros" Resume SalirConvertir End Sub El contenido del fichero "Convertir.ini" de la prueba que he hecho, sera el siguiente: [Datos de Origen] Fichero=colegas.dat Nmero de campos=3 Tamao Campo1=30 Tamao Campo2=2 Tamao Campo3=50 [Datos de Destino] Fichero=colegas2.dat Nmero de campos=4 Tamao Campo1=40 Tamao Campo2=2 Tamao Campo3=50 Tamao Campo4=128
257
Nos vemos. Guillermo Si quieres los listados del programilla, para verlo ms cmodamente, los puedes bajar pulsando en este link.
Ya hemos visto las distintas formas de acceder a los ficheros, ahora vamos a ver una instruccin que puede sernos til cuando decidamos "movernos" dentro del fichero. Me explico: cuando se trat el acceso secuencial, coment que la informacin haba que leerla de forma secuencial, es decir un dato despus de otro, bueno, pues esto es cierto slo a medias. No empieces a pegar saltos de alegra, porque tampoco es para tanto. El tema est en que si lees la informacin de forma "seguida", entonces si que es as, pero, si te entra hipo, puedes acceder a cualquier parte del fichero. Cmo? Pues con la siguiente instruccin que te voy a presentar ahora... A ver, instruccin ven, que te voy a presentar... venga, no te de vergenza, estamos en confianza... (es que dice que no le gusta su nombre), aqu est... oras, ores, damas y caballeros, les presento a: SEEK Esta instruccin (que tambin es una funcin) se usa, en modo instruccin, para posicionarnos en cualquier parte del fichero abierto, tanto para leer como para escribir. Si se usa como funcin, nos devuelve la posicin actual, es decir en la que nos encontramos, del fichero. Veamos cmo usarla: Seek #numFic, posicin y tambin variable = Seek(#numFic) El valor devuelto es de tipo Long. Dependiendo del modo en el que est abierto el fichero habr que "interpretar" el valor de distinta forma: Para los ficheros de tipo secuencial y binario, nos da la posicin en bytes (o caracteres). Para los ficheros abiertos como aleatorios (random), nos da la posicin en nmero de registros. Cuando lo usamos en modo instruccin, lo que hace es posicionar el puntero dentro del fichero, de modo que lo siguiente que se lea o se escriba se har en la posicin indicada.
258
Ni que decir tiene que el valor de la posicin debe ser un valor legal. Es decir, no podemos posicionarnos en la posicin CERO ni en una posicin NEGATIVA, ya que no es "legal". El uso de esta funcin/instruccin es til cuando necesitemos avanzar o retroceder dentro del fichero. Y como el movimiento se demuestra andando, vamos a ver un ejemplo. Vamos a crear una pequea utilidad que leer datos de un fichero, buscando claves especiales. Imaginate que quieres acceder a un fichero al estilo de los ficheros INI, en ese tipo de ficheros existen una serie de secciones que estn "enmarcadas" entre corchetes y a continuacin vienen una serie de datos (claves) con una especie de asignaciones que representan los valores de esas claves. Veamos un ejemplo de un fichero de este tipo: [elGuille] nombre=guille email=mensaje@elguille.info La seccin se llama "elGuille" y los dos campos son "nombre" y "email" Realmente para acceder a este tipo de ficheros no necesitaramos usar Seek, ya que podemos acceder secuencialmente, pero vamos a ver cmo podemos "posicionarnos" en una seccin en concreto, despus de haber ledo el contenido y haber tomado "buena nota" de las posiciones. La utilidad de ejemplo, nos va a mostrar todas las secciones disponibles y despus podremos acceder rpidamente a una posicin en concreto. Veamos el aspecto del formulario y el cdigo correspondiente:
'Esta fecha no est mal, es que ya lo tena "manuscrito" desde entonces... 'Ejemplos del curso bsico, ejemplo de Seek ' (26/May/98)
259
Option Explicit Private Type tSecciones Nombre As String Posicion As Long End Type Private aSecciones() As tSecciones Private nSecciones As Integer Private sFic As String Private nFic As Integer
Private Sub Form_Load() 'deshabilitar el botn de leer contenidos cmdLeerContenido.Enabled = False Text1 = "" List1.Clear 'Creamos el fichero de ejemplo sFic = "basico_20.ini" nFic = FreeFile Open sFic For Output As nFic Print #nFic, "[elProfe]" Print #nFic, "Nombre=Guillermo" Print #nFic, "email=mensaje@elguille.info" Print #nFic, "" Print #nFic, "[Alumnos]" Print #nFic, "Cantidad=2" Print #nFic, "Nombre_01=Pepito" Print #nFic, "email_01=pepito@servidor.com" Print #nFic, "Nombre_02=Juanita" Print #nFic, "email_02=juani@servidora.net" Print #nFic, "" Print #nFic, "[Fecha]" Print #nFic, "Fichero creado el da=26/May/1998" Print #nFic, "Fichero actualizado el da=" & Format$(Now, "dd/mmm/yyyy") Close
260
End Sub
Private Sub cmdLeerContenido_Click() 'Leemos el contenido de la seccin seleccionada en el list Dim sCadena As String Dim nItem As Long nItem = List1.ListIndex If nItem >= 0 Then 'borramos el contenido del Text1 Text1 = "" nFic = FreeFile Open sFic For Input As nFic 'posicionamos el fichero en el sitio que nos interesa Seek nFic, aSecciones(nItem + 1).Posicion 'ahora leemos el contenido del fichero hasta encontrar [ 'o hasta que se acabe el fichero Do While Not EOF(nFic) Line Input #nFic, sCadena If Left$(sCadena, 1) = "[" Then 'nada ms que leer Exit Do Else Text1 = Text1 & sCadena & vbCrLf End If Loop Close nFic End If End Sub
Private Sub cmdLeerSecciones_Click() Dim sCadena As String Dim Posicion As Long 'Borramos el contenido del ListBox
261
List1.Clear 'Leemos las secciones disponibles nFic = FreeFile Open sFic For Input As nFic Do While Not EOF(nFic) Line Input #nFic, sCadena 'guardamos la posicin actual, justo despus de leer, 'de esta forma nos indicar la posicin a partir de la 'cual leeremos el contenido... Posicion = Seek(nFic) 'Si es una seccin If Left$(sCadena, 1) = "[" Then 'incrementamos el nmero de secciones nSecciones = nSecciones + 1 'redimensionamos el array ReDim Preserve aSecciones(nSecciones) 'asignamos los valores al array With aSecciones(nSecciones) .Nombre = sCadena .Posicion = Posicion End With 'aadimos esta seccin a la lista List1.AddItem sCadena End If Loop Close nFic If nSecciones Then 'MsgBox "Se han hallado " & nSecciones & " secciones" 'seleccionamos el primer item del listbox List1.ListIndex = 0 'habilitamos el botn cmdLeerContenido.Enabled = True Else MsgBox "No hay secciones en el fichero: " & sFic End If End Sub
262
Private Sub List1_DblClick() 'Tambin podemos ver el contenido de una seccin 'haciendo doble-click cmdLeerContenido_Click End Sub Te explico un poco cmo funciona todo esto. Ya que de lo que se trata no es slo de ver cdigo sino de explicarlo, verdad? En primer lugar declaramos las variables que se van a usar. Una de estas variables es un tipo definido que contendr el nombre de la seccin y la posicin dentro del fichero. Creamos un array dinmico (es decir redimensionable) de este nuevo tipo de datos, en l guardaremos cada una de las secciones halladas en el fichero procesado. Tambin dimensionamos una variable que contendr el nmero de secciones halladas, aunque slo se usa mientras se lee la informacin del fichero, por tanto no es necesario que est declarada en la parte general de las declaraciones del formulario. Recuerda que las variables declaradas en la parte general de un formulario, estn disponibles en todo el formulario. En el Form_Load, que es el punto de entrada de nuestra utilidad, adems de asignar el nombre del fichero y guardar un contenido de ejemplo, borramos el contenido del Text1 y el List1, adems deshabilitamos el botn de leer el contenido de una seccin, para que no se use hasta que no haya datos. Al pulsar en el botn que lee las secciones, primero borramos lo que hubiese antes en el array de secciones y asignamos cero a la variable que contiene el nmero de secciones. Despus de abrir el fichero, en modo secuencial, vamos leyendo lnea por lnea, en cuanto nos encontramos con una lnea que empieza por corchete [, quiere decir que hemos encontrado una seccin, por tanto, incrementamos la variable que lleva la cuenta de las secciones halladas, redimensionamos el array usando Preserve para no perder la informacin antes almacenada, y asignamos la informacin del nombre y la posicin que hemos obtenido con Seek. Fjate que la lectura de la posicin se hace despus de haber ledo la seccin del fichero, esto es as, porque lo que necesitamos saber es la posicin que viene a continuacin de la seccin, ya que despus de la seccin es cuando vienen las claves. (En realidad, se asigna siempre despus de leer una lnea, pero a nosotros slo nos interesa su valor cuando hemos encontrado una seccin). Seguimos leyendo hasta encontrar el final del fichero. Por qu se ha usado el acceso secuencial? Por la sencilla razn de que este tipo de fichero suele ser de tipo ASCII, es decir que no contiene caracteres "raros" y que normalmente se editan en programas del tipo NotePad. De hecho el Windows tiene asociado al bloc de notas (Notepad) para abrir los ficheros que contengan la extensin INI. Adems de que al no saberse la longitud de los datos que contiene, pero que si sabemos que cada lnea termina con un retorno de carro (o cambio de lnea), es ms cmodo usar el Line Input # para leer toda la lnea; el modo binario no nos sera de utilidad, salvo que leysemos el fichero caracter por caracter, cosa que ralentizara el proceso.
263
Para acceder a una seccin en concreto, cosa que ocurre al pulsar en el botn cmdLeerContenido, simplemente abrimos el fichero, posicionamos el "puntero" en el sitio adecuado y leemos lo que haya en el fichero hasta que encontremos otra seccin, (que empezar por un corchete), o hasta que lleguemos al final del fichero. Tambin he puesto cdigo para que al hacer doble-click en un elemento del List1, se lea el contenido de la seccin correspondiente, para ello lo nico que se hace es llamar al evento Click del botn cmdLeerContenido.
Y hasta aqu ha llegado esta entrega, (que realmente formaba parte de la entrega 19), ahora vamos a ver un par de ejercicios para que te vayas soltando en esto de la programacin con el Visual Basic. El primer ejercicio realmente no usa Seek pero si algo parecido a la utilidad esta de leer el contenido de los ficheros del tipo INI, y consiste en crear un array con el contenido de todas las secciones y todas las claves y valores de cada seccin. De forma que el fichero slo se lea una vez y cuando se quiera mostrar el contenido de una seccin se use el contenido del array. La verdad es que no es nada fcil, pero tampoco pienses que es tan complicado como para no poder resolverlo, al menos deberas intentarlo y no coger el camino fcil de ver la solucin, entre otras cosas, porque lo que se pretende con estos ejercicios es que cojas "soltura" en la programacin y si adems de soltarte te quedas con las "buenas" costumbres, pues mejor. A que buenas costumbres me refiero? A usar Option Explicit en todos los mdulos, para de esta forma declarar las variables antes de usarlas y a "indentar" el cdigo para que te sea ms fcil seguirlo... Despus del sermn vamos a ver la pista que te doy: La pista es que puedes usar estos tipos definidos para crear el array de claves y su contenido, y el array para almacenar cada seccin y las claves de cada una de ellas. ' Private Type tContenidos Clave As String Contenido As String End Type Private Type tSecciones Nombre As String NumClaves As Integer Contenidos() As tContenidos End Type Private aSecciones() As tSecciones Como sabes cada clave tiene este formato: Clave=Contenido. Esto te lo digo para que la clave vaya por un lado y el contenido por otro, aunque sea algo ms complicado que almacenar simplemente la clave y el contenido, a la larga te ayudar a manipular mejor las cadenas de caracteres y tambin le darn mayor utilidad al cdigo que se cree con este ejercicio.
264
Como segundo ejercicio, haz lo mismo, pero en lugar de almacenar en el array cada clave y su contenido, usa Seek para "recordar" la posicin de cada una de las claves de cada seccin para despus poder acceder a esa parte del fichero para leer lo que nos interesa. Este segundo ejercicio es un poco ms complicadillo, ya que necesitar usar de forma correcta Seek, tanto en modo funcin como en modo instruccin. Este es el tipo de datos que tendrs que usar: ' Private Type tSecciones Nombre As String NumClaves As Integer Contenidos() As Long End Type Private aSecciones() As tSecciones Fjate que aqu slo guardamos en Contenidos la posicin de cada clave dentro del fichero. Suerte y no desesperes si no lo consigues, no me gustara perder a todos mis alumnos de golpe... creo que no lo soportara. Las soluciones de los dos ejercicios estn en este link.
Y ya slo queda que hagas tu comentario sobre esta entrega. Y si hay algo que necesites que te aclare, relacionado con lo que se ha visto en esta entrega, no te cortes y pregntame, pero slo relacionado con lo que estamos viendo, vale? Nos vemos. Guillermo
265
'Ejemplos del curso bsico, ejemplo de Seek ' 'Solucin a los ejercicios de la entrega 20 ' Option Explicit Private Type tContenidos Clave As String Contenido As String End Type Private Type tSecciones Nombre As String NumClaves As Integer Contenidos() As tContenidos End Type Private aSecciones() As tSecciones Private nSecciones As Integer
(26/May/98)
Private sFic As String Private Sub cmdLeerContenido_Click() 'Leemos el contenido de la seccin seleccionada en el list Dim sCadena As String Dim nItem As Long Dim i As Integer nItem = List1.ListIndex If nItem >= 0 Then 'borramos el contenido del Text1 Text1 = "" With aSecciones(nItem + 1) For i = 1 To .NumClaves sCadena = .Contenidos(i).Clave & "=" & .Contenidos(i).Contenido Text1 = Text1 & sCadena & vbCrLf Next End With End If
266
End Sub Private Sub cmdLeerSecciones_Click() Dim nFic As Integer Dim sCadena As String Dim Posicion As Long Dim nClaves As Integer Dim i As Integer 'Borramos el contenido del ListBox List1.Clear 'Leemos las secciones disponibles nFic = FreeFile Open sFic For Input As nFic Do While Not EOF(nFic) Line Input #nFic, sCadena 'Si es una seccin If Left$(sCadena, 1) = "[" Then nClaves = 0 'incrementamos el nmero de secciones nSecciones = nSecciones + 1 'redimensionamos el array ReDim Preserve aSecciones(nSecciones) 'asignamos los valores al array aSecciones(nSecciones).Nombre = sCadena 'aadimos esta seccin a la lista List1.AddItem sCadena 'ahora leemos el contenido del fichero hasta encontrar [ Do While Not EOF(nFic) Line Input #nFic, sCadena If Left$(sCadena, 1) = "[" Then 'nada ms que leer 'restablecemos la posicin anterior Seek nFic, Posicion Exit Do Else Posicion = Seek(nFic) 'Posicin del signo igual
267
i = InStr(sCadena, "=") If i Then nClaves = nClaves + 1 ReDim Preserve aSecciones(nSecciones).Contenidos(nClaves) With aSecciones(nSecciones) .NumClaves = nClaves 'La clave estar antes del signo igual .Contenidos(nClaves).Clave = Trim$(Left$ (sCadena, i - 1)) 'el contenido de la clave despus del signo .Contenidos(nClaves).Contenido = Mid$ (sCadena, i + 1) End With End If End If Loop End If Loop Close nFic If nSecciones Then 'seleccionamos el primer item del listbox List1.ListIndex = 0 'habilitamos el botn cmdLeerContenido.Enabled = True Else MsgBox "No hay secciones en el fichero: " & sFic End If End Sub Private Sub Form_Load() Dim nFic As Integer 'deshabilitar el botn de leer contenidos cmdLeerContenido.Enabled = False Text1 = "" List1.Clear
268
'Creamos el fichero de ejemplo sFic = "basico_20.ini" nFic = FreeFile Open sFic For Output As nFic Print #nFic, "[elProfe]" Print #nFic, "Nombre=Guillermo" Print #nFic, "email=mensaje@elguille.info" Print #nFic, "" Print #nFic, "[Alumnos]" Print #nFic, "Cantidad=2" Print #nFic, "Nombre_01=Pepito" Print #nFic, "email_01=pepito@servidor.com" Print #nFic, "Nombre_02=Juanita" Print #nFic, "email_02=juani@servidora.net" Print #nFic, "" Print #nFic, "[Fecha]" Print #nFic, "Fichero creado el da=26/May/1998" Close cmdLeerSecciones_Click End Sub Private Sub List1_DblClick() cmdLeerContenido_Click End Sub
El segundo:
' 'Ejemplos del curso bsico, ejemplo de Seek ' 'Solucin a los ejercicios de la entrega 20 ' Option Explicit Private Type tSecciones Nombre As String (26/May/98)
269
NumClaves As Integer Contenidos() As Long End Type Private aSecciones() As tSecciones Private nSecciones As Integer Private sFic As String
Private Sub cmdLeerContenido_Click() 'Leemos el contenido de la seccin seleccionada en el list Dim nFic As Integer Dim sCadena As String Dim nItem As Long Dim i As Integer nItem = List1.ListIndex If nItem >= 0 Then nFic = FreeFile Open sFic For Input As nFic 'borramos el contenido del Text1 Text1 = "" With aSecciones(nItem + 1) For i = 1 To .NumClaves 'Nos posicionamos en el sitio que nos interesa Seek nFic, aSecciones(nItem + 1).Contenidos(i) Line Input #nFic, sCadena Text1 = Text1 & sCadena & vbCrLf Next End With Close nFic End If End Sub Private Sub cmdLeerSecciones_Click() Dim nFic As Integer Dim sCadena As String Dim Posicion As Long
270
Dim nClaves As Integer Dim i As Integer 'Borramos el contenido del ListBox List1.Clear 'Leemos las secciones disponibles nFic = FreeFile Open sFic For Input As nFic Do While Not EOF(nFic) Line Input #nFic, sCadena 'Si es una seccin If Left$(sCadena, 1) = "[" Then nClaves = 0 'incrementamos el nmero de secciones nSecciones = nSecciones + 1 'redimensionamos el array ReDim Preserve aSecciones(nSecciones) 'asignamos los valores al array aSecciones(nSecciones).Nombre = sCadena 'aadimos esta seccin a la lista List1.AddItem sCadena 'ahora leemos el contenido del fichero hasta encontrar [ Do While Not EOF(nFic) 'En las claves nos interesa saber la posicin 'antes de empezar a leerlas Posicion = Seek(nFic) Line Input #nFic, sCadena If Left$(sCadena, 1) = "[" Then 'nada ms que leer 'restablecemos la posicin anterior Seek nFic, Posicion Exit Do Else i = InStr(sCadena, "=") 'Si es una clave tendr el signo igual If i Then nClaves = nClaves + 1 ReDim Preserve aSecciones(nSecciones).Contenidos(nClaves)
271
With aSecciones(nSecciones) .NumClaves = nClaves .Contenidos(nClaves) = Posicion End With End If End If Loop End If Loop Close nFic If nSecciones Then 'seleccionamos el primer item del listbox List1.ListIndex = 0 'habilitamos el botn cmdLeerContenido.Enabled = True Else MsgBox "No hay secciones en el fichero: " & sFic End If End Sub Private Sub Form_Load() Dim nFic As Integer 'deshabilitar el botn de leer contenidos cmdLeerContenido.Enabled = False Text1 = "" List1.Clear 'Creamos el fichero de ejemplo sFic = "basico_20.ini" nFic = FreeFile Open sFic For Output As nFic Print #nFic, "[elProfe]" Print #nFic, "Nombre=Guillermo" Print #nFic, "email=mensaje@elguille.info" Print #nFic, "" Print #nFic, "[Alumnos]"
272
Print #nFic, "Cantidad=2" Print #nFic, "Nombre_01=Pepito" Print #nFic, "email_01=pepito@servidor.com" Print #nFic, "Nombre_02=Juanita" Print #nFic, "email_02=juani@servidora.net" Print #nFic, "" Print #nFic, "[Fecha]" Print #nFic, "Fichero creado el da=26/May/1998" Close cmdLeerSecciones_Click End Sub Private Sub List1_DblClick() cmdLeerContenido_Click End Sub Espero que con los comentarios y si fuera necesario un repasillo a la entrega veinte, no tendrn demasiada complicacin para entender el listado.
273
dura y a la larga poco efectiva, te lo digo por propia experiencia; otra cosa es adaptar ciertas rutinas, esto es ms fcil, sobre todo si no interacta con el usuario. El decir todo esto es para que desistas en "ventanizar" una aplicacin BASIC-DOS, seguramente, salvo que la tuvieras bastante bien estructurada con procedimientos y funciones, te costar ms adaptarla que hacerla de nuevo. No voy a explicar cmo adaptar un listado MS-DOS al Windows, sera una prdida de tiempo, sobre todo si a ti no te interesa, lo que si vamos a "volver" a ver es cmo controlar una aplicacin de Windows, (sino volver a ver, al menos profundizar en el tema). Porque hay ocasiones en las que nosotros debemos tomar el control, por ejemplo cuando el usuario tiene que rellenar un campo y no debe pasar a otro hasta que lo haya hecho de forma correcta. En una ocasin anterior ya vimos que los programas realizados en Visual Basic, y por extensin todos los programas que tengan que trabajar en Windows, se basan en los eventos. Si entendemos bien para que sirven los eventos, seguramente nos ser ms fcil "controlar" el funcionamiento del programa. As que, en esta y en las prximas entregas, vamos a darle un repaso a los eventos ms usuales. Tambin veremos con detalle, unos cuantos controles, los ms habituales, de forma que su uso y aplicacin sean "casi naturales" y al final acabes usndolos como si los conocieras de toda la vida.
Un poco de definicin. Te recuerdo que un evento es una especie de aviso que manda el control de que algo ha ocurrido o est ocurriendo. Esto de los eventos es la "esencia" de la programacin Windows. No estamos atados a una programacin secuencial (o lineal), como se haca en MS-DOS. Con ese tipo de programacin, nosotros decidamos lo que iba a ocurrir a continuacin, y si no decidamos, al menos podamos prever lo que poda suceder. Pero la programacin en Windows es otra historia; el Windows nos "avisa" de que algo est ocurriendo, bueno, realmente Windows no nos avisa de nada, (a ver si piensas que te va a mandar un mensaje por mail), son los controles mediante sus eventos los que hacen sonar la campana para que sepamos que el usuario est haciendo algo, como mover el ratn, presionar una tecla, etc.
Para muestra... ...un botn y un label y un textbox y un... Empecemos por el Label que es ms simple. Las etiquetas (Label) se usan para mostrar informacin, normalmente este tipo de control no necesita intervencin por parte del usuario. Normalmente se suele usar de estas dos formas: Como etiqueta informativa que acompaa a otro control y que nos indica la informacin que ese control nos da o nos pide, segn sea el caso.
274
Por ejemplo, si queremos que el usuario introduzca un nombre, usaremos un textbox para que escriba en l, pero tambin pondremos una etiqueta indicndole que es lo que se espera que escriba. Como panel o lnea informativa, indicndole algn tipo de informacin al usuario. Es habitual que en la parte inferior de un form se aada una etiqueta que "informe" de lo que se debe hacer o lo que el programa est haciendo. Tambin se usan las etiquetas para informar de las opciones que ha seleccionado o del proceso que la aplicacin ha realizado o est a punto de realizar. Resumiendo, cada vez que tengas que informar al usuario, usa etiquetas para ello. Siempre que esa "informacin" no requiera de su intervencin. Las cajas de texto (TextBox) son los controles, por excelencia, para la introduccin de informacin por parte del usuario. Cada vez que el usuario deba escribir lo que nuestra aplicacin necesite, se usar un textbox. Los botones (CommandButton) son los que indicarn, normalmente, al programa que el usuario ha finalizado de introducir la informacin que se necesita y que debe procesarla o si el usuario cambia de opinin y no quiere hacer lo que se peda. Es, por tanto, habitual que se usen dos botones para conseguir esto, uno para "aceptar" y otro para "cancelar". Tambin es habitual que se use para pasar a otra pantalla de informacin. Los ListBox y ComboBox se suelen usar para mostrarle al usuario una "lista" de posibilidades de las que debe escoger una o varias, (para esto ltimo es ms habitual el listbox). Aunque el ComboBox se puede usar tambin para la introduccin de informacin, no es lo habitual, en la mayora de los casos es preferible usar el TextBox, aunque tambin veremos los casos en los que nos viene mejor usar el Combo. Los CheckBox se usan cuando el usuario necesite indicar si se usa o no una opcin determinada, esta opcin debe ser tan "autosuficiente" y slo se necesitar saber si la quiere usar o no. Por ejemplo, si necesitamos que el usuario indique si una vez procesado los datos queremos que se imprima o no. El uso de los OptionButtons es para las ocasiones en las que necesitemos indicar al usuario que escoja entre unas cuantas y elija slo una. Hay ocasiones en las que en lugar de un checkbox, se usan dos optionbuttons; para el mismo caso de imprimir o no, para que indique el sexo, etc. Por supuesto que hay ms controles, pero al menos estos son los que se usarn ms habitualmente, aunque para que la "relacin" sea ms completa, veremos tambin otros dos controles que se suelen usar para "contener" y agrupar a los dems controles: Los PictureBox y Frames, stos se usan para poner controles dentro, sobre todo el Frame. La primera recomendacin para hacer esto, adems de tenerlos "fsicamente" separados
275
del resto, tambin es ms fcil moverlos a otro sitio cuando estamos diseando la aplicacin o el "interface" de cara al usuario. En otras ocasiones necesitaremos estos contenedores para agrupar los OptionButtons, cuando veamos en profundidad los controles, lo entenders. Como ltimamente no dispongo de mucho tiempo y adems me gusta "chincharos" un poco, dejo aqu esta entrega, as que, permanece pendiente y mientras tanto repsate el manual y la ayuda, de esta forma te ser ms fcil adaptarte.
En esta ocasin no te voy a pedir ningn comentario sobre la entrega, ya que tampoco es mucho lo que ha dado de s, pero no quiero que pasen "meses" entre entrega y entrega, as que... aunque poco, algo es algo. De todas formas, en la prxima entrega veremos ya algunos de los eventos ms habituales. Este tipo de entregas son un poco "aburridas", ya que hay que empezar a manejar un poco los conceptos antes de pasar a los ejemplos, por tanto te pido un poco de consideracin para conmigo y aguantes hasta que venga lo realmente interesante: los programillas de ejemplo y esas cosas. Mientras tanto, prtate bien y no hagas estropicios. Nos vemos. Guillermo
276
en otros tipos de "contenedores", (de basura habr pensado alguno, sobre todo cuando se pone manos a la obra y no sale lo que quiere... no te desesperes, ya tendrs tiempo de pillar buenos cabreos...), pero para simplificar, vamos a pensar que estn puestos en un formulario. Esta aclaracin viene a cuento para lo que te voy a contar ahora. Insisto, esto lo tengo manuscrito desde hace dos meses y la verdad es que o recuerdo en que estaba pensando cuando lo escrib, porque nada de lo que viene a continuacin tiene que ver con el hecho de que un control est en un formulario u otro contenedor, pero al menos podrs "intuir" que los controles no tienen porqu estar siempre "puestos" en un formulario. Cuando se produce un evento en un formulario, producido por el propio Form o por cualquier control contenido en l, se deja de procesar el cdigo que se estaba ejecutando y se pasa a procesar el cdigo contenido en el evento. Es importante que tengas muy presente esto que acabo de decir, ya que es la causa de efectos no deseados. Por supuesto, si el evento que se produce no tiene cdigo asociado, no pasa nada. Aclaremos esto un poco; si te has fijado en la ventana de cdigo, cuando seleccionas un control de la ventana izquierda, incluso el propio formulario, en la ventana de la derecha te muestra los eventos disponibles, es decir los eventos que Visual Basic podr interceptar, normalmente no son todos los que se generan, pero s son los que el propio lenguaje nos permite interceptar y por tanto en cualquiera de ellos podemos aadir nuestro cdigo para hacer algo cuando dicho evento se produzca. Pero esto no quiere decir que tengamos que escribir cdigo en todos ellos, sino slo en los que nos interesen, pero el hecho de no escribir cdigo en un evento, no quiere decir que no se producir. Por ejemplo, si no escribes cdigo en el evento LOAD de un form, no impedir que el form se cargue, se cargar, independientemente de que queramos interceptar ese hecho o no. Sigamos con lo que pasa cuando se est ejecutando una parte del cdigo y se produce un evento. Por ejemplo, si mientras se procesa el cdigo de un evento, vuelve a producirse ese mismo evento, se vuelve a procesar el cdigo desde el principio y una vez terminado este segundo evento, se contina por dnde se interrumpi el anterior. Esta es una de las gracias de Windows y todo el tema de los eventos. No te preocupes si no te enteras que pronto veremos ejemplos. Aunque en muchas ocasiones esto no ocurre mientras nosotros no lo permitamos, hay veces en las que no podemos "predecir" que es lo que ocurrir, salvo que comprendamos perfectamente cmo funcionan los controles y sepamos cmo y cuando se producen algunos eventos; tambin puede intervenir la suerte de darnos cuenta de que... "si hago esto con este control, puede ocurrir este o aquel evento" Y esto que os digo es tan cierto como que nadie me ha regalado an un porttil... je, je. Vamos a ver un pequeo ejemplo para que salgas de dudas. Crea un nuevo proyecto, cambia la propiedad Autoredraw del form para que sea True, de esta forma, si lo redimensionas podrs ver lo que se ha imprimido en el. Escribe el siguiente cdigo en el evento Click del form: Private Sub Form_Click() Dim i As Long
277
Dim j As Long Print For i = 1 To 10 Print i; For j = 1 To 2000 DoEvents Next Next Print End Sub Ejecuta el programa, cuando hagas click en el formulario, vers que se imprimen los nmeros del 1 al 10, van un poco lento, para que puedas hacer lo que te dir ahora, mientras se estn imprimiendo los nmeros, vuelve a hacer click en el form, hazlo ms o menos rpido, si tus reflejos no te funcionan como quisieras, cambia el valor 2000 del bucle j con otro mayor, as se irn imprimiendo los nmeros de forma ms lenta. Habrs notado que cuando an no se ha terminado de imprimir los primeros diez nmeros, (si has pulsado antes de que se terminen de imprimir, claro), se empiezan a imprimir en la siguiente lnea desde 1 y cuando dejes de hacer click, irn terminando los bucles... supongamos que has pulsado tres veces, el resultado podra ser este: 1 1 1 7 9 23 23 23 89 10 45678 456 4 5 6 7 8 9 10 10
El primer bucle se interrumpi en 8, se inici el segundo, que se interrumpi en 6, se inici el tercero y continu hasta finalizar, despus continu el segundo, (el que se qued en 6), y termina, una vez que termin el segundo, se continua con el primero que se qued en 8 y por eso se imprimen el 9 y 10. Como notars, no se han perdido los valores... y el Visual record por dnde se qued... esto es debido a que todas las variables de un procedimiento son locales a este procedimiento, sea un evento o no, (realmente los eventos son SUBs que son llamados por Windows), y se guardan antes de volver a entrar en el procedimiento y una vez que el procedimiento acaba, se desechan... la memoria que se usa para guardar temporalmente los valores de las variables de un procedimiento se llama STACK (o pila del programa), hay que tener en cuenta que esta "pila" no es infinita y puede llegar a llenarse... sobre todo cuando las cosas no se hacen bien. A este tipo de variables locales, tambin se les llama variables automticas, por el hecho de que una vez que no se necesitan son automticamente borradas... cosa que no ocurre cuando las variables se declaran a nivel de mdulo o se declaran "estticas", ms adelante veremos ms sobre esto. Lo que este pequeo ejemplo demuestra es lo que te he explicado antes, que se puede "reentrar" en un evento (y por extensin a cualquier procedimiento); si esto mismo se hiciera por cdigo, no porque el usuario interviene con su ratn, tendramos lo que se llama un procedimiento "recursivo", es decir que se llama a s mismo... tendremos ocasin de ver algn ejemplo.
278
El evento ms dado a este tipo de "recursividad" es el evento Click. Este evento se produce cada vez que pulsamos con el ratn sobre un control, incluso sobre un formulario, como hemos visto en este ejemplo. Este evento (Click), est presente en la mayora de los controles, prcticamente en todos. La verdad es que poder "seguir" esto, imaginndose cmo lo hace el VB, es un poco difcil... vamos a ver otro caso, ms usual y que seguramente ser ms fcil de entender... Supongamos que tenemos un cdigo que procesa una serie de cosas y ese proceso puede llegar a ser largo. Si ese cdigo lo tenemos en el evento Click de un botn, con idea de que se procese cada vez que se pulsa en el botn, (cosa super-habitual), si no hacemos nada especial, mientras se est procesando el cdigo, el form completo se quedar "congelado", una vez que haya terminado el proceso, se continuar "aceptando" nuevos eventos, normalmente se quedarn pendientes de procesar y se ejecutarn a continuacin de terminar el proceso largo... Por eso en ocasiones es conveniente usar, como en el ejemplo anterior, la instruccin DoEvents. Con esta instruccin permitimos que Windows notifique "en seguida" de que ha ocurrido otro evento y nos da la posibilidad de procesarlo en ese momento. Eso o al menos indicar al usuario de que tenga paciencia y espere a que se termine de hacer lo que se estaba haciendo, ya que si no lo hacemos puede pensar que el programa se ha quedado "colgado". Vamos a modificar el cdigo anterior del Form_Click para ver esto que estoy diciendo. Sustityelo por este otro: Private Sub Form_Click() Dim i As Long Dim j As Long For i = 1 To 10 Print i; For j = 1 To 2000000 'dos millones Next Next Print End Sub Ejecuta el programa y haz click, vers que no pasa nada, haz click de nuevo y esta vez si que ocurre, hasta puede que el form se quede en blanco y al rato volver a la normalidad con las dos ristras de nmeros impresos... o ms si no has tenido paciencia suficiente... Una forma de solventar esa espera... es hacer que se muestre un mensaje pidindonos que esperemos... pero eso no evitar que volvamos a hacer click en el form... incluso el mensaje no se mostrar hasta que se haya terminado de hacer lo que se estaba haciendo... para que el mensaje se vea enseguida, se pueden hacer dos cosas: Una: usar un DoEvents justo despus de imprimir el mensaje Dos: usar Refresh para que se "pinte" el control, en este caso el formulario.
279
... Print "Un momento por favor..." Refresh For i = 1 To 10 ... En ambos casos tendramos el mensaje mostrado en la pantalla, vale, pero si vuelves a hacer click... se volver a procesar todo el cdigo, etc... Aunque en esta ocasin, hasta que no finalice, no contina el siguiente. Cmo podemos evitar la re-entrada en un evento cuando an no ha terminado? Usando lo que se llama un FLAG (o bandera), es decir una variable que nos indique que ya se est procesando el cdigo y de esta forma poder evitar que se repita si an no ha terminado. Como ya te he contado antes, las variables locales son automticas y se desechan una vez finalizado el procedimiento, al ser automticas no "recuerdan" el valor que tenan antes, salvo en las ocasiones que el VB las guarda en el Stack, pero no son recordadas en las diferentes ocasiones en las que entran en el procedimiento. Recuerdas que te coment lo de las variables estticas? Pues este tipo de variables son las que podemos usar para prevenir estos casos. Las variables estticas se declaran de igual forma que las variables normales, salvo que en lugar de usar Dim, se usa Static. Una vez declarada una variable esttica, deja de ser automtica y VB no guarda una copia cada vez que se entra en el procedimiento, sino que usa la misma cada vez que se procese el cdigo de ese evento. Por tanto cada vez que se "cuela" el cdigo en un evento (o procedimiento), podemos saber si ya hemos estado antes, sin terminarlo o no... para ello habra que hacer esto: Private Sub Form_Click() Dim i As Long Dim j As Long Static bFlag As Boolean 'Si no estamos en el evento If bFlag = False Then 'conectamos la bandera bFlag = True Print "Un momento por favor..." Refresh For i = 1 To 10 Print i; For j = 1 To 1000000 Next 'Debemos permitir que se procesen los mensajes
280
DoEvents Next Print 'Desconectamos la bandera bFlag = False End If End Sub Aqu hay dos detalles a tener en cuenta, el primero es el uso de la variable esttica, el otro es el DoEvents, sin ste, el uso de la variable esttica no servira para nada, ya que al no permitir a Windows que notifique los eventos, estos se quedan guardados en espera a que terminen los que haba pendientes, y para que no se queden guardados, usamos el DoEvents. Prueba a pulsar varias veces en el formulario mientras se muestran los nmeros, vers que no se vuelven a mostrar. Te explico un poco cmo funciona esto: La primera vez que se entre en el evento, el valor de bFlag valdr False, esto siempre es as con las variables Booleanas, cuando se quiere averiguar el valor de una variable que no se ha usado, sta tiene un valor "vacio", cero en las numricas, cadena vaca en las cadenas y False en el caso de las booleanas. Como vale False, se cumple la condicin, por tanto se asigna el valor True a esa variable y se contina procesando el cdigo. Cuando se llega al DoEvents, Windows procesa los mensajes que tuviese pendiente y si tiene que enviarle uno a este formulario, lo har. Suponiendo que hemos pulsado otra vez el ratn, al procesarse los mensajes pendientes, Windows enva al form un nuevo evento Click. Entonces se entra de nuevo en este procedimiento, pero esta vez, el valor de bFlag no es False, por tanto no se hace nada, ya que la condicin no se cumple y se sale del evento. Cuando se ha salido del evento, se contina por donde estaba antes y se sigue procesando el bucle, etc. Una vez terminado el bucle i, se asigna de nuevo el valor False a bFlag, para as poder permitir que se procese en otra ocasin el cdigo que hay. Todo esto es posible gracias a que la variable bFlag es esttica y el valor almacenado se mantiene entre las distintas llamadas, si no asignramos el valor False al final, no podramos entrar ms en este evento, ya que nunca se cumplira la condicin, as que hay que tener cuidado con las variables estticas, si el uso que le queremos dar es parecido al que hemos visto. Hay ocasiones en las que un evento se "reproduce" muy a pesar nuestro, ms que nada porque hay veces en las que no es tan obvio. Por ejemplo, cuando se selecciona un elemento de un ListBox, se produce un evento Click; y si en ese evento Click, tenemos algn cdigo que seleccione elementos del control... podemos tener al Visual Basic bastante atareado... entrando en un evento y desde ese evento entrando a otro y as durante un rato... mientras tanto, nuestro pobre VB guardando las variables automticas en el Stack (pila), pero llega un momento en el que la pila se llena... y en ese momento nos suelta un mensajillo de esos que nos indican que ya est hasta la coronilla de nosotros y de nuestra mala forma de programar... y se para...
281
Igual que yo me voy a parar aqu, porque ya est bastante bien de tanto Click y tanto Stack y todas esas cosas... Al final, la pelcula que te he contado no era exactamente lo que estaba en el guin, pero ms o menos lo que te he explicado era lo que quera explicarte... Para terminar, te dir que en algunos controles, al pulsar Intro se produce tambin un evento Click, por ejemplo en los CommandButtons. Por supuesto para que se procese ese Intro, el control debe tener el foco. Bueno, lo dicho, dejemos aqu esta entrega que hay ms cosas que hacer. A ver si la prxima no tarda tanto como esta y seguimos viendo... (espera que mire los apuntes, a ver que es lo que hay preparado), ... en las notas del 12 de julio estn los eventos del ratn, en las del da 13, los del teclado... en fin, esto promete seguir pesadillo... pero que le vamos a hacer... todo sea para que te enteres un poco de cmo va todo esto de los eventos... Lo dicho en otras ocasiones: prtate bien y no hagas estropicios y si quieres mandarme un mensajillo sobre el curso, usa el link este que te pongo. Nos vemos. Guillermo
282
En estas imgenes podemos ver las listas de "objetos" disponibles en el formulario, en los que podemos interceptar eventos y algunos de los eventos disponibles en el formulario.
En la lista desplegable de la izquierda estn los controles que estn contenidos en el formulario, as como la seccin "General" usada para las declaraciones de variables y procedimientos.
En la lista de la derecha, se muestran los diferentes eventos disponibles para el control seleccionado en la izquierda, en esta imagen vemos algunos de los eventos "interceptados" por el VB referente a los formularios normales.
Por ejemplo, podemos saber cuando se est moviendo el ratn por el control, (te recuerdo que los formularios tambin tienen estos eventos), si se ha presionado un botn y hasta que botn se ha presionado... Vamos a verlos con un poco ms de detalle:
Evento MouseDown. Este evento se produce cuando pulsamos un botn del ratn. Con los parmetros que tiene este evento podemos saber que botn se ha pulsado, la posicin dentro del control y si se est pulsando la tecla Shift (Maysculas), Control o Alt. Vamos a ver los parmetros y algunos de los valores disponibles.
283
Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) Button Indica el botn que se est pulsando, puede tener los siguientes valores: 1, se est pulsando el botn izquierdo, 2, se est pulsando el derecho, 4, se est pulsando el botn central. Slo se controla un botn a la vez. Indica si se est pulsando algunas de las teclas de "cambio", Shift, Ctrl o Alt, el valor devuelto corresponde con: 1, se est pulsando la tecla Shift (maysculas) 2, se est pulsando Control 4, se est pulsando Alt Se permiten combinaciones de estos valores, es decir que si se pulsan varias de esas teclas, los valores sern: 3, se estn pulsando Shift y Ctrl, 5, se estn pulsando Shift y Alt, 6, se estn pulsando Ctrl y Alt, 7, se estn pulsando las tres teclas. Indica las coordenadas de la posicin del puntero del ratn dentro del control. Es importante saber que estos valores son slo dentro del control en cuestin, no referente al formulario o la pantalla. El evento MouseDown se suele usar, entre otras cosas, para mostrar mens emergentes (PopUpMenus), normalmente controlando que se haya pulsado el botn derecho, (cuando Button vale 2)
Shift
X, Y
Evento MuseUp. Este evento se produce cuando se suelta el botn, los parmetros son los mismos que para MouseDown, pero en este caso lo que se detecta es cuando se "suelta" el botn pulsado.
Evento Click. Este evento se produce cuando se hace "click" en un control... es una combinacin del MouseDown seguido de un evento MouseUp... debes tener en cuenta que si ests gestionando los eventos MouseDown, MouseUp y Click te encontrars con que se producen los tres eventos en ese orden, es decir, primero se presiona, despus se suelta y por ltimo se produce el Click.
Evento DblClick. Este como podrs imaginarte, se produce cuando se hace una doble pulsacin, doble click. Si ests controlando estos cuatro eventos, adems de hacer tus propias comprobaciones, deberas saber que normalmente se producen en este orden:
284
Primero el MouseDown, seguido de un MouseUp, a continuacin un Click seguido de un DblClick y por ltimo un MouseUp. Todo esto lo puedes comprobar con el siguiente cdigo: ' 'Pruebas para la entrega 23 del curso bsico ' Option Explicit Private Sub Form_Click() Debug.Print "Se ha pulsado sobre el formulario (Click)" End Sub Private Sub Form_DblClick() Debug.Print "Doble pulsacin en el formulario (DblClick)" End Sub Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) Debug.Print "Se ha pulsado el botn del ratn (MouseDown)" End Sub Private Sub Form_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single) Debug.Print "Se ha soltado el botn del ratn (MouseUp)" End Sub (04/Oct/98)
Evento MouseMove. Este evento se produce cada vez que movemos el ratn por un control o por el formulario. Los parmetros de este procedimiento son los mismos que en los otros referentes al ratn: Private Sub xxx_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) Con este evento podemos saber cuando estamos dentro del control, sabiendo esto, podemos cambiar su apariencia, etc. El problema es que no sabemos cuando no estamos en el control, aunque si interceptamos la entrada en otras partes del formulario, podremos saber que no estamos en el control en cuestin, ya que si estamos en otro control... quiere decir que no estamos en este... Elemental querido Watson!
285
Si quieres ver un ejemplo para hacer "garabatos" en la pantalla... algunos lo llaman dibujar, en el cdigo del evento MouseDown, selecciona la palabra MouseDown y pulsa F1, en el ejemplo que se muestra est el programa para hacer esos garabaticos...
Y esto es todo por hoy... poco, pero mejor poco que nada... la prxima entrega ser sobre los eventos del teclado... que podra haber puesto en esta, pero... Y despus veremos algunos otros ms... lo que no se es si ser en la prxima o en la siguiente... ya veremos. Y para seguir con la norma... si quieres dejar un mensaje referente al curso bsico, puedes hacerlo, para ello usa el siguiente link para que te resulte ms fcil y as controle que lo que me quieres decir es referente al curso y no para hacer una consulta, ya que algunos aprovechan la ocasin para preguntar... Este es el link para que me mandes tu comentario. Nos vemos. Guillermo
Evento KeyPress. Este evento se produce cada vez que una tecla se pulsa, al menos una tecla normal... ya que las teclas "especiales" no se consideran normales y no se suelen detectar en este evento, salvo que se use junto a una tecla "normal". Veamos que informacin acepta este evento como parmetro: Private Sub Text1_KeyPress(KeyAscii As Integer)
286
El parmetro KeyAscii nos indicar el cdigo de la tecla pulsada, este cdigo nos lo da como valor numrico, no como una cadena, es decir que si pulsamos la tecla A mayscula, valdr 65, ya que ese es el valor ASCII de ese caracter, (realmente es un valor ANSI, pero...). Para saber el valor de las teclas, puedes pulsar F2 en el IDE del Visual Basic y buscar las constantes de KeyCode o en la ayuda busca la palabra KeyCode, de todas formas los valores Ascii se pueden saber o usar con la funcin ASC, por tanto se puede hacer una comparacin de este tipo: If KeyAscii = Asc("A") Then 'se ha pulsado la A mayscula End If En este evento se pueden detectar un montn de teclas, todas las alfanumricas y otros caracteres, adems de la tecla INTRO (return), aunque tendrs problemas con la tecla TAB, ya que esta tecla tiene un significado especial y no es tan fcil de detectar... Normalmente se suele detectar la pulsacin de la tecla INTRO, entre otras cosas porque suele emitir un pitido cada vez que se pulsa, al menos en las cajas de texto... para evitar ese "pitido", se puede hacer esto: If KeyAscii = vbKeyReturn Then KeyAscii = 0 End If Con esta asignacin, lo que hacemos es indicarle al VB que no se ha pulsado nada... o al menos decirle que no tenga en cuenta que se ha pulsado esa tecla. Otra de las cosas que se suele hacer cuando se pulsa INTRO es pasar al siguiente control, de la misma forma que si hubisemos pulsado la tecla TAB, esto se suele hacer ms a menudo de lo que parece, sobre todo para usuarios que estn acostumbrados a usar programas de MS-DOS, para conseguirlo, adems de tener "conectada" la propiedad TabStop de los controles, (si esta propiedad tiene el valor FALSE no se puede usar TAB para cambiar de control), tendremos que decirle al Windows que lo que se ha pulsado no ha sido el INTRO sino el TAB... es decir con algo como esto: If KeyAscii = vbKeyReturn Then KeyAscii = 0 'Eliminamos el pitido 'si queremos pasar al siguiente control 'tal como lo haramos pulsando la tecla TAB: SendKeys "{TAB}" End If Es decir, usamos la instruccin SENDKEYS para que enve una tecla TAB... Para ver que teclas podemos "enviar" con SendKeys, consulta la ayuda... Por supuesto que se pueden hacer muchas otras cosas en este evento, pero eso depender de nuestra aplicacin... y de nuestros gustos... 'Eliminamos el pitido
287
Eventos KeyDown y KeyUp. Para tener un mayor control en las teclas pulsadas, se suelen comprobar en los eventos KeyDown y KeyUp, la principal diferencia con el evento KeyPress es que en este caso no son cdigos ASCII, sino cdigos de teclado... Veamos los parmetros: Private Sub Text1_KeyDown(KeyCode As Integer, Shift As Integer) KeyCode es el cdigo de la tecla, no el cdigo ASCII, aunque en la mayora de los casos coincide, aunque no en todos. Shift es para saber si se ha pulsado al mismo tiempo alguna de las teclas especiales: Shift (maysculas), Ctrl o Alt. El valor de las teclas KeyCode se puede ver en la ayuda o pulsando F2, como te indiqu antes, entre otras teclas, adems de las normales, se pueden detectar las teclas de funcin (F1, F2, etc), las teclas de bloqueo de maysculas, bloque numrico, etc. Los valores del parmetro Shift son los mismos que en el evento del ratn, es decir: 1 para Shift, 2 para Control, 4 para Alt y la combinacin de estos valores, psate por la entrega anterior para ver esos valores. Debes tener en cuenta que el evento KeyDown se produce cuando se presiona la tecla y KeyUp cuando se suelta; segn que casos, puede ser interesante hacer las comprobaciones en uno u otro evento. Por ejemplo, podras hacer algn tipo de efecto cuando se pulsa la tecla y otro diferente cuando se suelta...
Si quieres tener un control sobre las pulsaciones de las teclas, puedes querer comprobarlas de forma genrica antes de que sean enviadas al control en el que se produce, hay una forma de hacerlo, para que sea el formulario el que las reciba antes... Para ello debers asignar un valor TRUE a la propiedad KeyPreview del formulario en cuestin. Ahora cada pulsacin de teclas sern procesadas primero por los eventos Keyxxx del formulario y despus por esos eventos del control que tenga el foco. Aunque si un botn tiene asignada a TRUE la propiedad Default, no se procesarn esas pulsaciones en el formulario. Vamos a ver un ejemplo para detectar la pulsacin de la tecla ESC y si esta se produce, mostrar un cuadro de dilogo que preguntar si queremos terminar la aplicacin... no es algo comn hacer esto, pero en algunas ocasiones puede ser interesante poder terminar una aplicacin al pulsar ESC, dnde utilizar esta forma de "cerrar" un programa ser cuestin tuya. Esto tambin se consigue teniendo un botn con la propiedad Cancel = TRUE, si ese botn se encarga de cerrar el programa, ya no tendrs que hacer nada ms... pero vamos a suponer que no tenemos ese botn y queremos cerrarlo al pulsar ESC. Tambin veremos cmo pasar al siguiente control cuando pulsemos INTRO; aunque esto slo ser posible si tenemos asignada a TRUE la propiedad TabStop de cada uno de los controles que estn en ese formulario.
288
Para este ejemplo, vamos a crear un nuevo proyecto, automticamente se aade Form1. Ahora insertaremos varios controles, que realmente no harn nada, pero as veremos cmo funciona todo esto. Veamos aade 2 CommandButtons, 2 TextBoxes y un par de Labels. Te voy a explicar es cmo suelo "manejar" los controles cuando los inserto en un formulario, que tamaos suelo usar, que posicin, etc. Normalmente suelo usar unos tamaos "predefinidos" para los controles: Los Labels suelo darles una altura de 255 285 si tienen borde, a los TextBoxes 315 y a los CommandButtons 375 405. El ancho depender del contenido que vayan a tener, en los botones suelo dejarles el ancho por defecto: 1245 La separacin de los puntos del grid, los suelo tener definidos a 30x30 puntos. Es decir que si muevo los controles o les cambio el tamao, suelo trabajar con valores de 30 puntos (twips?). Si quieres cambiar el valor 120 que es el que tiene el VB por defecto, haz lo siguiente: Selecciona el men Tools (Herramientas), te mostrar un cuadro de dilogo, selecciona la solapa General, vers que hay una serie de opciones para manipular el "grid" que se muestra durante el diseo de los formularios. Si no has cambiado nada, tendrs seleccionada las dos opciones que hay, en ingls dice lo siguiente: (es que ahora tengo instalado el VB en ingls, as que si la traduccin que te doy no coincide con lo que te muestra... intenta encontrarlas) --Show Grid (Mostrar Grid o rejilla), --Align controls to Grid (Ajustar los controles al grid) Tambin hay dos cajas de texto para indicar la separacin de los puntos mostrados en el grid, estos son los valores que tendrs que cambiar.
Array de controles.
Aunque en las aplicaciones simples no hay que preocuparse por los recursos que usemos, no est de ms acostumbrarse a aprovecharlos, as nos ser ms fcil tomarlo como costumbre. Una forma de ahorrar esos recursos del sistema es usando arrays de controles, al menos siempre que sea posible. Por ejemplo los labels son unos controles que nos permiten usar arrays sin demasiadas complicaciones, ya que normalmente se suelen usar para poner ttulo a las cosas y poco ms. En los casos en que uso los labels para mostrar informacin, puede que no use un array, pero por regla general uso arrays de labels. Para crear un array de cualquier control, se puede hacer de la siguiente forma: (esta es la que yo uso, porque entre otras cosas, me mantiene los tamaos que le asign) Inserta un label, se crear Label1, seleccionalo, asigna el tamao que quieres que tenga, copialo, (pulsando el botn dereho del ratn y seleccionando Copiar), ahora pulsa en cualquier parte del formulario y selecciona pegar. Te preguntar si quieres crear un array de Label1 (o el nombre que le hayas dado), dile que Si y ya lo tienes creado. La diferencia entre dos labels, (o cualquier otro control), diferentes y dos que estn en un array es, entre otras, estas: Controles diferentes Nombre Cada una tendr un nombre diferente: Label1, Label2, etc. Un Array de Controles Todas tienen el mismo nombre, pero se debe usar un ndice para indicar a cual nos referimos.
289
Label1(0), Label1(1), etc. Eventos Cada control tendr su propio juego de eventos. Todos los controles comparten el mismo evento, pero ese evento incluir un nuevo parmetro que ser el ndice dentro del array del control que lo ha producido.
La ventaja de usar arrays es que slo tienes que incluir cdigo en un slo evento, ya que todos los controles en ese array comparten el mismo "nombre". Pero an as, cada uno de los controles genera su propio evento. Para poder distinguirlos hay que usar el ndice del array, as si el evento lo ha producido el que tiene el ndice cero, el parmetro Index valdr cero. Vamos a ver cmo se mostrara esos eventos en la ventana de cdigo: Private Sub Label1_MouseMove(Index As Integer, Button As Integer, Shift As Integer, X As Single, Y As Single) Los parmetros son los mismos que si no estuviesen en un array, con la diferencia de que ahora se incluye uno nuevo: Index. Como te he comentado, ste parmetro es el que nos indicar el control que ha recibido el evento. Para poder hacer cosas diferentes segn el control que sea, se puede usar una serie de If...Then o Select Case...End Select, segn tus gustos. Por ejemplo, en el evento Click de un array de Labels: Private Sub Label1_Click(Index As Integer) 'Para saber en que etiqueta se ha hecho click: Select Case Index Case 0 'Se ha hecho Click en el label de ndice cero Case 1 'Se ha hecho Click en el label de ndice uno End Select End Sub Esto mismo es aplicable a los TextBox que tengan algn tipo de relacin, la ventaja, como puedes ver es que comparten los mismos eventos, en algunos casos tendrs que saber el control que se est procesando, pero en otros no, por ejemplo, si tienes un array de TextBoxes y quieres que se seleccione todo el texto al recibir el foco, esto se consigue aadiendo el siguiente cdigo en el evento GotFocus: Private Sub Text2_GotFocus() 'Seleccionar el texto que haya en el Text2 'Si usamos With, es ms cmodo codificar, ya que no hay
290
'que estar escribiendo el nombre del control. With Text2 'Posicin incial de la seleccin: cero, el primer caracter .SelStart = 0 'Seleccionar todo el texto: 'esto se averigua con la longitud del contenido de la 'propiedad Text .SelLength = Len(.Text) End With End Sub Si tuvisemos un array de Text1, haramos lo mismo, pero simplemente cambiando el nombre del control que ponemos despus del With: Private Sub Text1_GotFocus(Index As Integer) 'Seleccionar el texto que haya en el Text1(Index) 'Si usamos With, es ms cmodo codificar, ya que no hay 'que estar escribiendo el nombre del control. With Text1(Index) 'Posicin incial de la seleccin: cero, el primer caracter .SelStart = 0 'Seleccionar todo el texto: 'esto se averigua con la longitud del contenido de la 'propiedad Text .SelLength = Len(.Text) End With End Sub Fjate que el cdigo usado es el mismo que para Text2. Si tuvisemos 20 cajas de texto, tendramos que repetir este cdigo veinte veces, una vez para cada control, pero al tener un array slo habra que ponerlo una vez. Otra forma de no tener que repetir el cdigo un montn de veces, sera creando un procedimiento que hiciera ese trabajo. Veamos el cdigo para ese procedimiento: Antes de ver el cdigo, hay que saber que el procedimiento necesita saber el control en el que se debe seleccionar, por tanto necesitar recibir como parmetro un textbox... veamos el cdigo y espero que captes la forma de hacerlo: Private Sub SeleccionarTexto(unTextBox As TextBox) 'Seleccionar el texto que haya en el TextBox
291
'Si usamos With, es ms cmodo codificar, ya que no hay 'que estar escribiendo el nombre del control. With unTextBox 'Posicin incial de la seleccin: cero, el primer caracter .SelStart = 0 'Seleccionar todo el texto: 'esto se averigua con la longitud del contenido de la 'propiedad Text .SelLength = Len(.Text) End With End Sub Como puedes comprobar el cdigo es el mismo que en los ejemplos anteriores, lo nico que se cambia es el nombre del control que est despus del WITH. El tipo de datos que se recibe como parmetro es del tipo TextBox, ya que ese ese el tipo de control que vamos a manipular, pero si se quisiera hacer ms genrico y poder usarlo con cualquier control que tenga una propiedad Text, podemos cambiar el tipo de parmetro para que valga para cualquier control: Private Sub SeleccionarTexto(unTextBox As Contol) De esta forma, lo mismo dar usar un RichTextBox, un ComboBox o un control que disponga de las propiedades usadas. Para usar este procedimiento, haramos lo siguiente en el evento GotFocus de los controles en los que queremos seleccionar al recibir el foco: 'Para el Text2: Private Sub Text2_GotFocus() 'Llamamos al procedimiento, usando como parmetro 'el control Text2 SeleccionarTexto Text2 End Sub
'Para el array Text1: Private Sub Text1_GotFocus(Index As Integer) 'Llamamos al procedimiento usando el Text1 correspondiente, 'en ese caso hay que indicar el ndice. SeleccionarTexto Text1(Index) End Sub
292
En el caso del Text1, que es un array, se pasa el TextBox que ha recibido el foco: por tanto hay que indicarlo con el ndice. Bueno, vamos al tema, que me estoy despistando un poco... aunque esto tena que contrtelo algn da, as que tampoco ha venido mal del todo... verdad? Para que un formulario procese la pulsacin de las teclas antes que los controles, hay que asignar a la propiedad KeyPreview el valor True. Haz click en el formulario, selecciona la ventana de propiedades, (pulsa F4 si no est visible), busca la propiedad KeyPreview y selecciona True ya que por defecto el valor es False. Aade el siguiente cdigo al cdigo del formulario: Private Sub Form_KeyPress(KeyAscii As Integer) If KeyAscii = vbKeyEscape Then 'Se ha pulsado ESC 'Descargamos el formulario Unload Me ElseIf KeyAscii = vbKeyReturn Then 'Se ha pulsado Intro 'Borramos la tecla pulsada para que no "pite" KeyAscii = 0 'Enviamos una pulsacin TAB SendKeys "{TAB}" End If End Sub Nota: Lo de la pulsacin del Intro no funcionar con los botones ni con las cajas de texto que tengan un valor True en la propiedad MultiLine, ya que el Intro se usa para cambiar de lnea. El orden de tabulacin, es decir que control recibir el foco cuando se pulse la tecla TAB, estar indicado por el valor de la propiedad TabIndex de cada uno de los controles. Ese valor se cambia de forma automtica cuando se modifica el valor de otro control. Al principio tiene el valor segn los controles aadidos, pero si quieres cambiarlo, puedes modificar ese valor, sabiendo que el primer control que recibir el foco ser el que tenga el valor cero y despus los que tengan el 1, 2, etc. Las etiquetas no reciben el foco, pero tienen esa propiedad, entre otras cosas, es interesante que la tengan, ya que lo que hacen es pasar el foco al control que tenga el valor siguiente dentro del TabIndex. Normalmente se le suele dar a una etiqueta el TabIndex anterior al textbox que tenga "asociado", o al que le est dando el ttulo, en caso de que esa sea su "utilidad". Pero no podemos darle el foco a una etiqueta. Entonces que utilidad tiene que tenga la propiedad TabIndex? Que podemos usar una tecla de acceso rpido y si pulsamos esa "tecla" el foco pasar al control siguiente. Vamos a verlo con un ejemplo. En el formulario habamos aadido dos labels: Label1(0) y Label1(1) Selecciona la primera y escribe lo siguiente en la propiedad Caption: &Nombre,
293
comprobars que la N est subrayada, pues esa es la tecla de acceso rpido, para poder acceder a una tecla de acceso rpido tendrs que pulsar Alt+tecla, en este caso Alt+N. Selecciona la otra etiqueta y asignale esto en el caption: &Edad. Para poder usar este acceso rpido tendrs que pulsar Alt+E. Ahora vamos a poner en orden los valores de TabIndex: Supongamos que el formulario tiene este aspecto:
--Selecciona el botn Aceptar, pulsa F4 para mostrar la ventana de propiedades, busca TabIndex y escribe 0, pulsa Intro. --Selecciona el botn cancelar, en la ventana de propiedades seguir estando seleccionada la misma propiedad, escribe de nuevo cero. --Haz lo mismo con el resto de los controles: LblInfo, Text2, Edad, Text1, Nombre Ahora el orden de los valores de TabIndex ser: Nombre, Text1, Edad, Text2, LblInfo, Cancelar y Aceptar. Pulsa F5 para ejecutar el programa, el foco lo recibir primero el Text1, el cual se seleccionar. Si pulsas la tecla TAB vers que el foco va cambiando a Text2, Cancelar y Aceptar, para volver a Text, etc. Prueba pulsando Intro cuando ests en alguno de los TextBoxes y vers que se cambia el foco al siguiente control. Para terminar con las pruebas, pulsa Alt+N y vers que el control que recibe el foco es el Text1, despus pulsa Alt+E y el que reciba el foco ser el Text2.
Bueno, creo que ya est bien por hoy, no sea que te acostumbre a esto de las entregas largas y no es cuestin. En la siguiente entrega veremos algunos eventos ms y con ello concluiremos esta tanda, para pasar a ver algo sobre las propiedades de los controles y algunas otras cosillas... como adelanto, para tu tranquilidad, te dir que ya tengo "manuscritas" tres entregas ms y algunos "esbozos" para otras cuantas... as que no te lo tomes con demasiada calma que voy a seguir dndote la lata... Hasta la prxima que ser, espero dentro de poco... porque como me retrase... no ser hasta entrado el mes de Noviembre, ya que me voy unos das a Gran Canaria... as que...
294
hazme un poco la pelota si quieres ms entregas antes de que me vaya el prximo da 21... je, je... Nos vemos. Guillermo
Si esto de los nmeros de las entregas fuese como los aos de un matrimonio, con esta cumpliramos las bodas de plata, pero esto no es ningn matrimonio ni nada que se le parezca... o casi, ya que es casi como un compromiso entre t, el lector, y yo, el profe, as que vamos a ver si seguimos llevndonos bien para no tener que divorciarnos ni nada por el estilo. La mejor forma de llevarnos bien es que cada uno haga lo que tiene que hacer, t estudiar y practicar y yo dedicarme a seguir cumpliendo entregas... que por otro lado son menos pesadas que cumplir aos... Hoy vamos a continuar con los eventos, esta ser la ltima de la serie, que ya est bien. En la prxima entrega haremos un pequeo repaso a cmo se deben declarar las variables y algunos otros consejos que espero que sigas a pies juntillas, o al menos los tenga casi siempre presentes... ya vers a que me refiero, pero ahora vamos a ver esos cuantos eventos que en algunos casos es conveniente saber utilizar.
Otros eventos.
Ya habrs comprobado que eventos hay para casi cualquier cosa, muchos de ellos no se suelen usar de forma habitual, y siquiera los habituales. La razn es porque no siempre es necesario interceptar todos los avisos que nos envan los controles. Segn el tipo de control y en ciertas de estancias se usan unos u otros pero an no he visto ninguna aplicacin que necesitara manipular todo, qu digo todo, ni siquiera la mitad de ellos. Creo que sera una locura. Hay que saber que algunos eventos son procesados por los controles o formularios cuando ese control o formulario est activo o recibe el foco, mientras esto no ocurra, el susodicho control estar en standby.
Evento GotFocus. Cuando un control se convierte en activo y recibe el foco, produce el evento GotFocus, es decir que tiene el foco de atencin del usuario; de igual forma, cuando deja de ser el control activo, hace sonar la alarma mediante el evento LostFocus.
295
El control en el que suele usarse estos eventos, de forma ms habitual, es el TextBox. Normalmente en el evento de GotFocus se suele seleccionar todo el texto que contenga, para hacerlo, como ya vimos con "profusin" en la entrega anterior, se suele usar un cdigo como este: Private Sub Text1_GotFocus() With Text1 .SelStart = 0 .SelLength = Len(.Text) End With End Sub Aunque ya deberas saberlo, te vuelvo a repetir lo que hacen esas lneas de cdigo: .SelStart = 0 .SelLength = Len(.Text) Le indica al control que la posicin de inicio del texto seleccionado empiece por la posicin cero, es decir la primera letra. SelLength es la longitud del texto seleccionado y el valor que se le asigna es la longitud total del texto que haya en el control.
Por tanto lo que se hace es seleccionar todo el contenido del TextBox, las propiedades SelStart y SelLength tambin se podran usar para saber cul es el texto que hay seleccionado y poderlo usar en los casos de bsqueda y sustitucin de palabras. Pero hay otra forma ms rpida de saberlo, mediante la propiedad SelText del TextBox, con esta propiedad nos indica que texto es el que est seleccionado en ese momento. Para almacenar en una variable el texto que actualmente est seleccionado, haremos esto: 'Guardar en sBuscar el texto seleccionado en Text1 sBuscar = Text1.SelText Evento LostFocus. En el evento LostFocus se suele comprobar si el contenido es el que se espera que haya. Realmente es un pequeo problema el tener que validar los datos en este evento, ya que cuando se producen, inmediatamente hay otro control que recibe el foco y si resulta que los datos contenidos en ese control no son los que esperbamos se intente volver a darle el foco a este control, con lo cual se producira un evento LostFocus en el control que recibi el foco cuando el primero lo perdi y si en ese segundo control tambin hay una comprobacin de datos y stos son errneos, tendremos tal cacao que el pobre Visual Basic se terminar quejndose. Aunque el que se habr quejado habr sido t, ya que me imagino que no te has enterado de lo que acabo de decir, pero no te voy a dar un ejemplo para que lo entiendas, en vez de eso, te voy a poner un ejercicio y as de camino practicas un poco. Crea un proyecto nuevo, en el Form inserta dos TextBox y CommandButton, el botn se usar para salir del programa, en cada uno de los TextBox se comprobar que el contenido no est vacio y no se permitir salir del TextBox en cuestin si el contenido no
296
es el que esperamos que sea, por ejemplo, podras hacer que el primero slo admita nmeros y el segundo slo letras. Aunque esto se podra hacer de otra forma, prefiero que lo hagas en el LostFocus, para que practiques. Pero como te digo, si lo prefieres, puedes validar cualquier otra cosa, por ejemplo que tenga algo escrito o cualquier otra cosa que se te ocurra... yo no suelo validar casi nada en esos eventos ya que normalmente suelen dar ms quebraderos de cabeza que ventajas. Pero de lo que se trata es que seas t el que decida que es lo mejor o peor para tus programas, esto es extensible a cualquier otra cosa que yo u otra persona pueda decirte: evalalo primero y despus decide si es lo que realmente te hace falta. Para que realmente funcionen esas comprobaciones, asignales una cadena vacia nada ms cargarse el formulario y aade tambin cdigo al botn de salir para que descargue el formulario, as podrs comprobar que hasta que no se cumplan las condiciones especificadas, no dejar de "pitarte" para que escribas lo que debes escribir. Por supuesto, estas comprobaciones se harn en los eventos LostFocus de cada control y si el contenido no cumple las condiciones que esperamos, habr que volver a darle el foco, usando el mtodo SetFocus, adems de un Beep para que sepamos que los datos contenidos no son vlidos. El cdigo a usar sera algo como esto: Private Sub Text1_LostFocus() '... 'Si el contenido del control no es vlido entonces Beep Text1.SetFocus End Sub Prubalo y vers cmo ahora entenders lo que digo, y si no lo entiendes al menos habrs visto cmo el Visual Basic se queja. Bueno, vale, te pongo el cdigo que no permitir que los TextBoxes estn vacios y si est al cambiar de control, se quedar en ese, pero como te he dicho, esto volver loco al VB. '-----------------------------------------------------------------'Pruebas para la entrega veinticinco del curso bsico Option Explicit Private Sub Form_Load() 'Borramos el contenido de los TextBox Text1 = "" Text2 = "" (18/Oct/98) '-----------------------------------------------------------------'Volvemos a darle el foco
297
'Hacemos que el Text1 sea el primero en recibir el foco Text1.TabIndex = 0 End Sub Private Sub cmdSalir_Click() Unload Me End Sub Private Sub Text1_LostFocus() 'No permitir que el TextBox est vacio If Len(Trim$(Text1)) = 0 Then Beep Text1.SetFocus End If End Sub Private Sub Text2_LostFocus() 'No permitir que el TextBox est vacio If Len(Trim$(Text2)) = 0 Then Beep Text2.SetFocus End If End Sub Ejecuta el programa, el foco lo tendr el primer cuadro de texto, sin escribir nada, pulsa la tecla TAB para cambiar al otro control, ver cmo el VB se vuelve turuta... Tendrs que pulsar Control+Break (Cttl+Inter) para pararlo... En la versin 6 del VB se incluye un nuevo evento: Validate que ser el que se use para "validar" el contenido de un TextBox, en lugar de hacerlo en el evento LostFocus. Sobre este evento ya veremos algo ms adelante (en unas cuantas celebraciones ms), aunque dentro de poco lo podrs ver en la seccin sobre el VB6 que tengo en mis pginas, las cuales seguramente se sumarn en su momento a este curso... cuando les toque el turno.
Evento Change. Otro evento relacionado con los controles que permiten introducir informacin es: Change. Este evento se disparar siempre que el contenido del control cambie, por tanto en un TextBox se producir si lo que hay en la propiedad Text cambia. Esto ya lo vimos en las primeras entregas, adems de cmo no usar este evento, para que no diese un error de desbordamiento de la pila.
298
Evento OLEDragDrop. Veamos primero cmo hacer que nuestra aplicacin permita que se suelte un fichero y cuando se haga se muestre en un TextBox, por tanto no pruebes con ficheros que no sean de texto. Para poder hacer esto, hay que asignar las siguientes propiedades del Form y del TextBox que mostrar el texto: OLEDropMode = 1 ' Manual El TextBox debera ser Multiline para que se muestre bien el fichero soltado. Para asignar a un TextBox el primer fichero de los que se sueltan en un formulario o cualquier control que tenga la propiedad OLEDropMode igual a uno, vamos a usar un procedimiento genrico, se supone que ser para ficheros de texto, incluso documentos RTF si el control en cuestin es un RecichTextBox. Private Sub AsignarFichero(unTextBox As Control) 'Asignar al TextBox indicado el contenido del fichero soltado Dim sFic As String Dim nFic As Long Dim sText As String 'Asignar slo el primer fichero sFic = Data.Files(1) 'Abrirlo y leer el contenido nFic = FreeFile Open sFic For Input As nFic
299
sText = Input$(LOF(nFic), nFic) Close nFic 'Asignarlo al control indicado unTextBox = sText End Sub Para usar este procedimiento, lo llamaremos desde el evento OLEDragDrop del formulario y del control en cuestin: Private Sub Form_OLEDragDrop(Data As DataObject, Effect As Long, _ Single) End Sub Private Sub Text3_OLEDragDrop(Data As DataObject, Effect As Long, _ Single) End Sub Ahora ya puedes "soltar" un fichero de texto en el formulario o el TextBox y se mostrar el contenido en ese TextBox. Button As Integer, Shift As Integer, X As Single, Y As Button As Integer, Shift As Integer, X As Single, Y As
AsignarFichero Text3
AsignarFichero Text3
Evento DragDrop y propiedad Drag Ejemplo de cmo redimensionar y mover controles en tiempo de ejecucin. Ahora vamos a ver cmo mover controles por el formulario y soltarlos en la posicin deseada. Tambin vamos a ver cmo, mediante el uso de una llamada al API de Windows, podemos hacer que un control sea "redimensionable", la nica pega para hacer que sea redimensionable, o si lo prefieres, la nica condicin, es que el control sea una "ventana", no te confundas, los controles en Visual Basic son controles, pero el Windows trata a algunos de ellos como si fuesen ventanas, por ejemplo el control TextBox es para Windows una ventana, sin embargo el control Label no lo es. Normalmente se puede saber si un control es "ventanero" si tiene la propiedad hWnd, ya que los que no son realmente una ventana, no tienen esa propiedad. Veamos el cdigo y las declaraciones para hacer que todo esto pueda funcionar. Primero te explico que en cada control hay que hacer lo siguiente en los eventos MouseDown y DragDrop, salvo en el Form, como veremos despus, ya que en el Form slo se detectar la accin de soltar el control:
300
Private Sub NombreDelControl_DragDrop(Source As Control, X As Single, Y As Single) EndDragDrop NombreDelControl, Source, X, Y End Sub Private Sub NombreDelControl_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) IniciarDrag NombreDelControl, Button, X, Y End Sub En los controles que no queramos permitir que se suelte, hay que "cancelar" la operacin de Drag & Drop, por ejemplo, si tenemos un botn y no queremos que se suelte encima, habra que hacer algo como esto: Private Sub cmdSalir_DragDrop(Source As Control, X As Single, Y As Single) 'Cancelar la accin de soltado CancelarDrag Source End Sub El procedimiento de calcular la nueva posicin se encarga de hacer las comprobaciones pertinente para posicionar adecuadamente el control soltado; si el control que se mueve, est contenido a su vez en otro control, no se permite que se mueva fuera de su contenedor. Esta forma de actuar puedes cambiarla si quieres, aunque deberas tener en cuenta que el contenedor debe ser el control de destino o el formulario en caso de que se suelte sobre el form, tambin podras hacer que al soltarse un control en otro, el que recibe el control fuese el contenedor... todo es cuestin de que hagas pruebas y lo adecues a tus gustos. Una cosa que debes tener en cuenta si quieres mover los TextBox, es que no te permitir seleccionar el texto usando el ratn, ya que al pulsarse con el ratn se llama al procedimiento que "avisa" que se est haciendo el Drag&Drop y al dejarlo "invisible", pierde el foco, aunque esto se soluciona en el procedimiento que calcula la nueva posicin, podras querer que no se pueda mover, ese es el caso del Text4 del ejemplo que pongo a continuacin. Ahora veamos el cdigo de este ejemplo, el cdigo lo puedes conseguir pulsando en este link: codigo25.zip (6.62 KB)
301
'Revisado/mejorado: el 18/Oct/98 ' 'Guillermo 'guille' Som, 1996-98 '---------------------------------------------Option Explicit Dim frmName As String Dim DY As Single Dim DX As Single Dim NumColumnas As Integer Dim NumFilas As Integer Dim bIniciando As Boolean 'Declaraciones del API para 32 bits Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _ (ByVal hWnd As Long, ByVal nIndex As Long) As Long Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _ (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long Private Declare Function SetWindowPos Lib "user32" _ (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, _ ByVal X As Long, ByVal Y As Long, ByVal cX As Long, ByVal cY As Long, _ ByVal wFlags As Long) As Long Const GWL_STYLE = (-16) Const WS_THICKFRAME = &H40000 ' Const SWP_DRAWFRAME = &H20 Const SWP_NOMOVE = &H2 Const SWP_NOSIZE = &H1 Const SWP_NOZORDER = &H4 Private Sub CmdSalir_Click() Unload Me End Sub
302
Private Sub cmdSalir_DragDrop(Source As Control, X As Single, Y As Single) 'Cancelar la accin de soltado CancelarDrag Source End Sub Private Sub Form_DragDrop(Source As Control, X As Single, Y As Single) 'Cuando se suelta en el form, no especificar el destino EndDragDrop Source, X, Y End Sub Private Sub Form_Load() bIniciando = True 'Asignamos el nombre del formulario, ya que lo necesitaremos 'para saber si se mueve encima del form o de otro control frmName = Me.Name 'Hacer estos controles redimensionables, 'usando el API CambiarEstilo PicColum(0) CambiarEstilo Text2 CambiarEstilo Picture1 CambiarEstilo Frame1 CambiarEstilo Picture3 CambiarEstilo Picture4 CambiarEstilo Text4 Text3 = "Left= " & Text3.Left & ", Top= " & Text3.Top 'Para crear filas/columnas en un Control NumFilas = 2 Label1(0) = "Cabecera de la colunma" Load Text1(1) With Text1(1) Set .Container = PicColum(0) .Visible = True
303
.Top = Text1(0).Top + Text1(0).Height End With Load Label2(1) With Label2(1) .Visible = True .Top = Label2(0).Top + Label2(0).Height .Caption = "Fila 2" End With NumColumnas = 1 bIniciando = False End Sub Private Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) Text2.Text = "X= " & Str$(X) & ", Y= " & Str$(Y) End Sub Private Sub Frame1_DragDrop(Source As Control, X As Single, Y As Single) EndDragDrop Source, X, Y, Frame1 End Sub Private Sub Frame1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) IniciarDrag Frame1, Button, X, Y End Sub Private Sub Label1_DragDrop(Index As Integer, Source As Control, X As Single, Y As Single) EndDragDrop Source, X, Y, Label1(Index) End Sub Private Sub Label2_DragDrop(Index As Integer, Source As Control, X As Single, Y As Single) EndDragDrop Source, X, Y, Label2(Index) End Sub Private Sub PicColum_DragDrop(Index As Integer, Source As Control, X As Single, Y As Single)
304
EndDragDrop Source, X, Y, PicColum(Index) End Sub Private Sub PicColum_Resize(Index As Integer) Dim k As Integer Dim i As Integer If bIniciando Then Exit Sub 'ajustar el ancho del Label y los texts Label1(Index).Width = PicColum(Index).Width For i = 0 To NumFilas - 1 k = i * NumColumnas + Index Text1(k).Width = PicColum(Index).Width Next PicColum(0).Left = Label2(0).Width For i = 0 To NumColumnas - 1 If i > 0 Then PicColum(i).Left = PicColum(i - 1).Left + PicColum(i 1).Width End If PicColum(i).Top = 0 Next End Sub Private Sub Picture1_DragDrop(Source As Control, X As Single, Y As Single) EndDragDrop Source, X, Y, Picture1 End Sub Private Sub Picture1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) IniciarDrag Picture1, Button, X, Y End Sub Private Sub Picture2_DragDrop(Source As Control, X As Single, Y As Single) EndDragDrop Source, X, Y, Picture2 End Sub
305
Private Sub Picture2_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) IniciarDrag Picture2, Button, X, Y End Sub Private Sub Picture3_DragDrop(Source As Control, X As Single, Y As Single) EndDragDrop Source, X, Y, Picture3 End Sub Private Sub Picture3_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) IniciarDrag Picture3, Button, X, Y End Sub Private Sub Picture4_DragDrop(Source As Control, X As Single, Y As Single) EndDragDrop Source, X, Y, Picture4 End Sub Private Sub Picture4_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) IniciarDrag Picture4, Button, X, Y End Sub Private Sub Text1_DragDrop(Index As Integer, Source As Control, X As Single, Y As Single) EndDragDrop Source, X, Y, Text1(Index) End Sub Private Sub Text2_DragDrop(Source As Control, X As Single, Y As Single) EndDragDrop Source, X, Y, Text2 End Sub Private Sub Text2_DragOver(Source As Control, X As Single, Y As Single, State As Integer) 'Si no se quiere que pase por encima otro control 'CancelarDrag Source End Sub
306
Private Sub Text2_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) IniciarDrag Text2, Button, X, Y End Sub Private Sub Text3_DragDrop(Source As Control, X As Single, Y As Single) EndDragDrop Source, X, Y, Text3 End Sub Private Sub Text3_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) IniciarDrag Text3, Button, X, Y End Sub Private Sub Text4_DragDrop(Source As Control, X As Single, Y As Single) EndDragDrop Source, X, Y, Text4 End Sub Private Sub Text4_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) 'Si se quiere poder seleccionar con el ratn 'quitar la siguiente llamada, aunque entonces no se permitir 'mover el control 'IniciarDrag Text4, Button, X, Y End Sub Private Sub CambiarEstilo(queControl As Control) Dim Style As Long On Local Error Resume Next Style = GetWindowLong(queControl.hwnd, GWL_STYLE) If Err Then Err = 0 MsgBox "El control " & queControl.Name & " no permite que se redimensione", vbInformation Exit Sub End If
307
Style = Style Or WS_THICKFRAME Style = SetWindowLong(queControl.hwnd, GWL_STYLE, Style) Style = SetWindowPos(queControl.hwnd, _ Me.hwnd, 0, 0, 0, 0, SWP_NOZORDER Or _ SWP_NOSIZE Or SWP_NOMOVE Or SWP_DRAWFRAME) End Sub Private Sub CancelarDrag(Source As Control) Source.Visible = True Source.Drag vbCancel End Sub Private Sub EndDragDrop(Source As Control, X As Single, Y As Single, Optional Dest As Object) Dim posX As Long Dim posY As Long If Dest Is Nothing Then 'Si no se especifica el control de origen, 'por ejemplo cuando se suelta en el formulario posX = -30 posY = -30 'Si el nombre del contenedor del Source no es el nombre 'del destino, no moverlo. 'Esto ocurrir cuando se mueve un control contenido en otro 'y el destino no es el control que lo contiene If Source.Container.Name <> frmName Then CancelarDrag Source Exit Sub End If Else 'Si el control destino es el mismo que el contenedor del Source 'Esto ocurrir cuando el control que se mueve se suelta 'en el control en que est contenido If Dest.Name = Source.Container.Name Then posX = -60 posY = -60
308
Else 'Si el nombre del contenedor del Source no es el nombre 'del destino, no moverlo. otro 'Esto ocurrir cuando se mueve un control contenido en 'y el destino no es el control que lo contiene If Source.Container.Name <> frmName Then CancelarDrag Source Exit Sub End If With Dest 'El nombre del formulario se asignar 'a la variable frmName If .Container.Name = frmName Then 'Esto ocurrir cuando se suelte un control 'en otro que est contenido en el formulario, 'no contenido en otro control posX = .Left posY = .Top Else 'Esto ocurrir cuando se suelte en un control 'que est contenido en otro control posX = .Container.Left + .Left + 60 posY = .Container.Top + .Top + 60 End If End With End If End If 'Posicionar el control soltado With Source .Visible = True .Move posX + X - DX, posY + Y - DY .Drag vbEndDrag .ZOrder End With 'Si el Source es un textbox, darle el foco If TypeOf Source Is TextBox Then
309
Source.SetFocus End If 'Si se van a usar RichTextBox, hacer la comparacin correspondiente End Sub Private Sub IniciarDrag(Source As Control, Button As Integer, X As Single, Y As Single) If Button = vbLeftButton Then DX = X DY = Y Source.Drag vbBeginDrag 'Cambiar a no visible, ya que si no, 'el form no detectara que se ha soltado, 'si el puntero del ratn no sale del control. Source.Visible = False Source.Drag End If End Sub Hasta aqu ha llegado esta "entrega de plata", si quieres hacer un comentario, gurdatelo para otra ocasin... je, je. Parte de esta entrega la tena escrita desde el 15 de julio de 1998, en total un folio manuscrito por las dos caras y pasado al Word usando el ViaVoice de IBM que aunque no me entiende, (seguramente porque yo no "dicto" bien), algo es algo y en cuanto le ensee a entenderme seguramente las entregas llegarn con mayor rapidez. Aunque es algo "lentillo" y la verdad es que el cdigo no hay forma de hacrselo entender, pero seguir probando a ver si logro que algn da sepa de que le estoy hablando... al menos me rio un poco de lo que escribe cuando no me entiende... Hasta la prxima que ser... imagnatelo... Nos vemos. Guillermo Nerja, 18 de Octubre de 1998
La entrega de hoy, para quitarnos un poco la resaca de la "celebracin" de la anterior, se basar en consejos y observaciones sobre las declaraciones de variables y otras cosillas que debers tener muy en cuenta para no meter la pata hasta el cuello, ya que en ocasiones "nos equivocamos" por no permitir que el Visual Basic tenga en cuenta algunas cosillas de las que "intentamos" hacer, con buena intencin, pero que en ocasiones no son todo lo correctas que debieran. No soy el nico que te dar o que ha dado estos consejos, ya que todos los programadores que llevamos algn tiempo con el VB solemos hacerlo, entre otras cosas porque nos da algo de "rabia" que los programadores de otros lenguajes, (lase Pascal/Delphi y C/C++), se rian de que el Visual Basic es muy propenso a errores tipogrficos... realmente no lo dicen de esta forma, pero el caso es que se quejan de que este lenguaje no "obligue" a declarar las variables, lo malo que tienen "esos" programadores es que realmente no conocen el Visual Basic, al menos no lo habrn visto a fondo o no lo han tocado desde la versin 2... pero para eso estamos aqu los que si lo hemos "estudiado" y lo seguimos haciendo, para que los menos "expertos" aprendan lo ms rpido posible y sobre todo lo mejor posible... Como te comentaba muchos de los errores tipogrficos se solucionan usando una instruccin, que ya te he comentado en otras ocasiones a lo largo de este curso, interminable, de Visual Basic y no es otra que: Option Explicit. Cuando insertamos esta instruccin al principio de un mdulo, ya sea del cdigo de un formulario, mdulos BAS o de cualquier otro, el VB nos obliga a declarar las variables que usemos. Algunos pueden ver eso de no tener que declarar las variables como una ventaja del VB, pero a la larga tener que declarar las variables antes de usarlas, es algo que se agradece... cosa que comprobars en cuanto leas un poco ms de esta entrega. De todas formas, no tienes porqu estar incluyendo esta instruccin en cada mdulo, el Visual Basic puede hacerlo por t de forma automtica cada vez que aadas un nuevo mdulo o formulario en tu aplicacin. Este automatismo no es un valor por defecto en las versiones anteriores al VB6, (en esta versin ya lo es y se agradece, al menos para que los que empecis lo tengis por defecto), pero puedes hacer que lo sea, para ello tendrs que marcar la opcin que para ello tiene el IDE del VB en el men Herramientas/Opciones en la solapa Editor estar "Require variable declaration" (al menos as se llama en la versin inglesa del VB, que es la que uso ltimamente) Que ventajas tiene esto? Que si te equivocas al escribir el nombre de una variable, el VB te avisar de este eerror y as ser ms fcil solucionar problemas, te puedo asegurar que "muchos problemas". Vamos a comprobarlo, crea un nuevo proyecto, te aadir un nuevo formulario (Form1), muestra la ventana de cdigo y si tiene insertado Option Explicit, brralo. Muestra el form y aade un CommandButton, aade el siguiente cdigo al evento Click de este botn: Private Sub Command1_Click() ' Dim SumaActual As Long Dim i As Long
311
SumaActual = 0 For i = 1 To 5000 SumaActual = SumActual + 1 Next MsgBox "El contenido de SumaActual es " & SumaActual End Sub Pulsa F5, haz click en el Command1 y vers que no hace lo que se espera... Ya que si analizas el cdigo, el valor mostrado debera ser 5000, pero, no es as... Vamos a dejar que el VB descubra el error por nosotros; para ello vamos a ejecutar de nuevo el programa, pero esta vez aade Option Explicit al principio del mdulo, es decir en la parte de las declaraciones generales del formulario, vuelve a ejecutar de nuevo el programa, nada ocurre... pulsa en el botn Command1 y vers que si que haba un error tipogrfico. Seguramente dirs que tampoco es para tanto... total, si se escriben bien los nombres de las variables... pues s, pero cuando tengas proyectos con varios formularios y algunos mdulos de otro tipo... entonces ya me contars si no es mejor contar con "detector de errores tipogrficos". Seguramente te habrs fijado que al ejecutar el programa, (usando F5 o pulsando en el botn de la barra de herramientas), el Visual Basic no te ha avisado del fallo, slo lo ha hecho cuando has hecho click en el botn, esto est bien, pero no ayuda mucho cuando el proyecto es grande y tiene varios formularios y varios/muchos procedimientos, ya que hasta que no se ejecute el cdigo que hay en ellos no nos avisar del "fallo". Pero, por suerte, esto se puede evitar... y adems de dos formas distintas. La primera es ejecutando la aplicacin con Ctrl+F5, es decir pulsando la tecla control y al mismo tiempo F5, de esta forma se analiza todo el cdigo antes de iniciar el programa y si el Visual Basic encuentra algn error, te avisar. La otra forma es indicarle al VB que "siempre" analice el cdigo antes de ejecutarlo, de esta forma es como si siempre pulsramos Ctrl+F5 aunque slo se pulse F5. Para que esto sea as, muestra el cuadro de dilogo de Herramientas y en la solapa General busca la casilla "Compile on demand" (o como sea en la versin en espaol), si est marcada esta opcin, qutale la marca, esto en proyectos grandes hace que tarde un poquito ms al ejecutarse en el IDE, pero es porque se analiza todo el cdigo antes de empezar la ejecucin del programa, yo ya me he acostumbrado a que siempre se compile y lo tengo de esta form, ya que no hay nada que ms me moleste que se detenga el programa al entrar en un procedimiento por culpa de un error tipogrfico, la verdad es que si esta opcin hubiese estado en el Basic del MS-DOS nos hubiera evitado algn que otro quebradero de cabeza, por desgracia lleg tarde, con el Visual Basic para DOS, pero ms vale tarde que nunca... aunque ahora casi ni se utiliza... Para terminar con esto de las ventajas de usar Option Explicit, vamos a ver cmo saber si una variable est en uso o no. Bueno, no es que nos de esa informacin, pero algo parecido, la verdad es que echo de menos una caracterstica del QuickBasic del MS-DOS y era que al pulsar F1 en una variable, nos indicaba en que mdulos se estaba usando dicha variable, pero... aunque no existe esa forma de ver cmo est declarada una variable en el Visual Basic, podemos
312
usar otra forma, que es pulsando Shift+F2; te mostrar la declaracin de esa variable... si no te muestra la declaracin, es que no est declarada. A lo que iba, si tenemos declarada una variable, se respeta el "estado" de maysculas/minsculas de ese nombre de variable, por tanto si en el caso del ejemplo anterior, hubisemos escrito (todo en minsculas) sumaactual, el VB automticamente hubiese convertido dicho nombre a SumaActual o como lo hubisemos escrito al DIMensionarla; esta es una de las formas de saber si ya tenemos la variable declarada... aunque sea en otro procedimiento... el nico "fallo" es que el VB recuerda la ltima forma en que se dimension una variable... es decir, si en un procedimiento tenemos esta declaracin: Dim j As Integer y en otra parte de nuestro proyecto esta otra: Dim J As Integer, el VB convertir todas las variables "j" al estado de la ltima declaracin que hayamos usado... Pero al menos sabremos que la tenemos declarada y de esto vamos a ver ms cosas...
Declarar las variables del tipo adecuado Ya que estamos en esto de las declaraciones de las variables, voy a darte otro consejo, que si no estan evidente como el anterior, en algunas ocasiones puede hacer que tu aplicacin trabaje de forma ms eficiente... aunque no te voy a hacer ninguna demostracin, ya que la demostracin la tuviste en la entrega cuatro. La recomendacin es que declares las variables con el tipo adecuado, es decir, que especifiques el tipo de la variable cada vez que la declares, si no lo haces, el Visual Basic le asignar el tipo por defecto, que normalmente suele ser Variant y aunque ese tipo sirva para todo, no es el recomendable para muchas ocasiones, al menos en lo que a bucles se refiere, por poner un ejemplo. Tambin puedes especificar el tipo por defecto, esto yo lo haca siempre en mis programas de MS-DOS, aunque ahora en Visual Basic para Windows no lo suelo hacer, ya que siempre declaro las variable del tipo que quiero que tengan. Pero para que lo puedas hacer, aunque me imagino que ya sabrs, te recuerdo cmo hacerlo. Aade justo despus del Option Explicit la instruccin DEFxxx a-z, sustituye las xxx por el tipp de tu preferencia, por ejemplo: DefInt A-Z asignar el tipo Integer a todas las variables declaradas sin un tipo determinado; por otro lado: DefLng A-Z har que el tipo predeterminado sea el Long. Se pueden usar varios de estos DEFxxx usando distintos rangos de letras, ya que el Defxxx funciona de esta forma: Deftipo rango letras [, rango2 [, rango3, etc]] dde tipo es Int para Integer, Lng para Long, Sng para Single, etc. (busca esta palabra en la ayuda para ms informacin) Los rangos son letras que le indicarn al VB que las variables que empiecen por esas letras sean del tipo indicado, por ejemplo: DefInt a-c, i, j DefLng L-N, R Declarar las variables, siempre que no se especifique el tipo, que empiecen por a, b, c, i y j como variables Integer y las que empiecen por L, m, n y r como Long.
313
Por ejemplo con estas declaraciones: Dim Ahora, Nombre As String, Longitud, Resultado As Currency Ahora ser Integer, Longitud ser Long y las otras dos, del tipo indicado: Nombre es String y Resultado del tipo Currency. Pero ya te digo que prefiero declarar las variables del tipo adecuado, es decir especificando el tipo que ser: Dim Ahora As Integer, Nombre As String, Longitud As Long, Resultado As Currency Yo as lo tengo ms claro, pero puedes hacerlo como prefieras. Lo que si debes hacer es usar el tipo que realmente necesites, por ejemplo si vas a hacer bucles que no supere el valor 32767, puedes usar una variable de tipo Integer, si quieres guardar en sDatos una cadena, declarala como tipo String, etc. Ya que si siempre als declaras como el valor por defecto y se te olvida aadir el DEFxxx correspondiente, tendrs que las variables ser del tipo Variant, que entre otras cosas es ms lenta y ocupa ms memoria. Adems de que en un futuro te ser ms fcil saber el tipo de datos que esas variables contendrn... cosa que agradecers cuando veas de nuevo el cdigo unos meses o aos despus... Y para que te sea ms fcil recordar que debe almacenar cada variable: Usa nombres descriptivos para las variables Pues eso, es ms fcil saber que la variable "Nombre" guarda un nombre que si slo usas "n " Aunque en esto de dar nombre de las variables, coincido con otros programadores, de usar ciertas variables "no descriptivas" para ciertas cosas, no es por nada, sino porque como todos, he aprendido viendo cdigo de otros programadores y siempre se te pega algo del "estilo" que se va generalizando... por ejemplo, en el uso de las variables i, j, k para los bucles. Otro aspecto es el de usar ciertos "prefijos" para indicar el tipo y la visibilidad de las variables... en esto no todos estamos de acuerdo, pero casi... Seguramente habrs "oido" hablar de la notacin "hungara"... no voy a entrar en detalles de que va esto, pero si decirte ms o menos como se aplica, al menos en algunos casos, a la hora de declarar las variables. Para simplificar, y mucho, te dir que el prefijo es la letra o letras (normalmente tres), que se usan delante del nombre de la variable, por ejemplo, las variables del tipo Integer se declararan as: Dim intContador As Integer, lngContador As Long, strNombre As String , etc... De esta forma, es fcil saber que strNombre es del tipo String, aunque no tengamos "cerca" la declaracin de la variable. Yo suelo abreviar el tema y suelo declararlas de esta otra forma: (como otros programadores, que no vayas a creer que es invencin mia) Dim iContador As Integer, sNombre As String Con el tipo Long no siempre uso la "l" (ele minscula), entre otras cosas porque se confunde con el nmero "1" y casi siempre suelo declarar las variables Long con "lng". Para los otros tipos no suelo hacerlo de ninguna forma en particular, aunque el tipo "boolean" las declaro con la letra "b". En el caso de que las variables sean pblicas no suelo aadirle ningn tipo de identificador, ya que en el caso de los formularios se convierten en propiedades, no entro en ms detalles en esto de las propiedades, ya que ser el tema de la prxima entrega.
314
Pero esto de los prefijos no slo se aplican a las variables, tambin se hace a los controles y formularios. Por ejemplo: Prefijo cmd txt lbl chk cbo lst pic img Control CommandButon TextBoxes Labels CheckBoxes CombosBoxes ListBoxes PictureBox Image Ejemplo cmdSalir txtNombre lblStatus chkImpresora cboCiudad lstNombres picStatus imgBoton
Para los tipos definidos las declaraciones del tipo suelo empezarlos con "t_" y las variables declaradas a partir de un tipo con "t", las enumeraciones (ya las veremos en su momento), las empiezo por "e" y las constantes con "c"... En el caso de las variables a nivel de mdulo, las que son visibles en todo el mdulo actual, ahora veremos ms sobre esto, suelo anteponerle el prefijo "m_". Aunque todo hay que decirlo, no tengo un estilo fijo en esto de nombrar a las variables y controles, aunque en la mayora de los casos, es ste el que suelo aplicar... y cada vez con ms "consistencia", ya que a la larga te ayuda el usar algn tipo de recordatorio del tipo/nivel de visibilidad de los controles y variables. Para ir acabando con las recomendaciones del da, voy a darte la penltima: Usa comentarios en tus listados Y es que los comentarios, entre otras cosas, no ocupan espacio en el programa una vez compilado y a la larga, siempre es a la larga, ya que cuando el cdigo est reciente, nos acordamos de todo... pero cuando se lee despus de pasado algunos meses... puede que te ocurra lo que a mi... que al estar revisando listados antiguos, tuve que entender lo que hacia el cdigo y despus comentarlo para que la prxima vez que tuviese que modificarlo, me resultara ms fcil. Por tanto, si no quieres verte en este aprieto, usa y abusa de los comentarios, aunque sea un poco "pesado", te repito que se agradece, sobre todo si el cdigo cae en manos de otro programador, por ejemplo en un grupo de trabajo y tiene que entender lo que "pretendas" hacer...
315
Y para acabar esta entrega "sermonstica" y casi de repaso, vamos a ver el tema de la visibilidad de las variables o el "alcance" que estas tienen dentro del mdulo e incluso del proyecto completo. De que va esto de la visibilidad? Esto va de que cuando se declaran las variables, estas no estn visibles en toda la aplicacin... AH! que lo que no entiendes es lo de "visible"... Te lo explico con un ejemplo: Supn que tienes un procedimiento que cuenta el nmero de veces que una letra est en una cadena, por ejemplo en "Visual Basic" la letra "a" est dos veces, talvez no tenga mucha utilidad este ejemplo, pero... ' Public Function CuantasVeces(ByVal sCadena As String, _ ByVal sLetra As String) As Long 'Esta funcin devolver el nmero de veces que est 'la letra sLetra en la cadena sCadena Dim i As Long Dim nVeces As Long 'Variable para el bucle 'variable temporal para el nmero de veces
'bucle para recorrer la cadena completa For i = 1 To Len(sCadena) 'Si el caracter comprobado es la letra buscada '(se supone que sLetra es slo un caracter) If Mid$(sCadena, i, 1) = sLetra Then 'incrementar el nmero de veces nVeces = nVeces + 1 End If Next 'Devolver el total de veces CuantasVeces = nVeces End Function En este cdigo hay dos variables declaradas: "i" y "nVeces", pues bien, estas variables slo son visibles dentro de esta funcin, es decir, que no tendrn nada que ver con otras variables que tengan el mismo nombre, y estn declaradas en otros sitios. Talvez este ejemplo "solitario" no est demasiado claro. Pero es un fallo que muchos "cometemos", (o al menos, algunos hemos cometido, aunque ya lo tengamos superado... o casi.) He visto ms de un listado en el que se declara una variable en un procedimiento y
316
despus se "intenta" usar en otro sitio distinto en el cual "no se ve" esa variable... Por supuesto, estos problemas de "visibilidad" se evitan usando Option Explicit... realmente no se evitan... sino que nos "obliga" (el VB) a que lo evitemos... ya que no nos dejar usar el programa si no estn las variables "debidamente" declaradas... o al menos declaradas aunque no sea de la forma adecuada... (ver ms arriba lo que comento sobre usar el tipo adecuado). Vamos a verlo con un ejemplillo... para que te des cuenta de lo que podas haberte evitado si usaras el Option Explicit... y si an no te ha dado tiempo a comprobarlo, porque eres de los que siguen "las reglas", ahora te dars cuenta de que poda haber sido peor tu aprendizaje... Para este ejemplo, tendrs que crear un nuevo proyecto, se aade por defecto un formulario (Form1), aade un mdulo BAS (Module1) y escribe lo siguiente en el mdulo BAS: 'En el mdulo BAS Option Explicit Public sNombre As String Public nVeces As Long Public Sub MostrarNombre() 'Muestra el nombre y el valor de nVeces MsgBox "El nombre es: " & sNombre & vbCrLf & _ "y se ha pulsado: " & nVeces & " veces" End Sub Escribe esto en el formulario (Form1) que tendr un TextBox (Text1) y botn (Command1): 'En el Form1 Option Explicit 'Esta variable es "privada" y visible slo en este formulario Private nVeces As Long Private Sub Command1_Click() 'Declaramos una variable "privada", 'y por tanto slo es "visible" dentro de este procedimiento Dim sNombre As String 'Asignamos a la variable el contenido del Text1
317
sNombre = Text1 'incrementamos las veces que se ha pulsado en el botn nVeces = nVeces + 1 'mostramos la informacin MostrarNombre End Sub Ejecuta el programa y vers que cuando pulses en el Command1, no te muestra el nombre y las veces siempre sern cero... aunque pulses varias veces. Por qu ocurre esto si las variables sNombre y nVeces estn declaradas como pblicas en el mdulo BAS? Esto es as, porque a pesar de haber declarado esas dos variables como pblicas en el mdulo BAS, (es decir visible en todo el proyecto), no se ha asignado nada a ellas... Seguro? Entonces, que pintan las asignaciones: sNombre = Text1 y nVeces = nVeces + 1 que hay en el procedimiento Command1_Click? Te lo explico: El caso de sNombre es ms fcil de comprender, a saber, cuando el Visual Basic entra en el procedimiento Command1_Click, declara una variable de tipo cadena llamada sNombre, esa variable slo es visible dentro de este procedimiento, es decir que cuando se use, el VB usar la memoria que ha reservado al encontrarse esa declaracin, por tanto el contenido del TextBox se guardar en ese espacio de memoria, osea: la variable sNombre del procedimiento Command1_Click. En el caso de nVeces es parecido al de sNombre, pero en este caso la "visibilidad" de esta variable es ms amplia que la de sNombre, ya que al estar declarada a nivel de mdulo, es visible en todo el formulario, pero al ser privada slo ser visible, (o accesible, o disponible, como prefieras), dentro del cdigo que est en el propio formulario y no fuera de l. Por tanto, al llamar al procedimiento MostrarNombre, (que est declarado como pblico en el mdulo BAS y por tanto disponible en todo el proyecto), el VB se encuentra con que queremos usar dos variables... comprueba si est debidamente declaradas y usa los valores que contienen... es decir, ninguno; por tanto muestra los valores por defecto... una cadena vacia y un valor cero. Esto en ingls se llama "scope", mbito o alcance; si lo entiendes mejor es la "cobertura" que tiene esa variable, como la de los telfonos mviles (celulares) o emisoras de radio... si no tiene cobertura en una zona, no se escuchar en esa zona... Vamos a ver las distintas formas de declaracin de una variable, para ver el "alcance" que tienen: -Declaradas en un procedimiento (Sub, Function o Property) Slo son visibles en ese procedimiento -Declaradas Privadas (o con Dim) en un formulario o mdulo (BAS, CLS u otro tipo) Slo son visibles en ese formulario o mdulo, incluidos todos los procedimientos de ese mdulo, salvo la restriccin anterior. -Declaradas Pblicas (o Globales) en un mdulo BAS
318
Son visibles en todo el proyecto, salvo cuando se encuentren con variables declaradas en los casos anteriores. -Declaradas Pblicas en formularios o mdulos de clase Sern visibles en cualquier sitio, siempre que se preceda con el nombre del formulario o clase... (ya veremos esto en otra ocasin) O slo dentro de ese mdulo, como si fuesen privadas al propio mdulo. Por tanto, las variables que tengan el mismo nombre, se usarn segn la ltima declaracin que tenga esa variable y si est al alcance del procedimiento en el que se est usando... que lio, verdad? Esta cercana en la declaracin de la variable se puede explicar tambin as: Primero se usarn las que estn declaradas en el propio procedimiento Despus las que estn en el mismo mdulo (o formulario o clase o... cuando digo mdulo, me refiero al "sitio" en el que escribes el cdigo) Por ltimo, las declaradas como globales o pblicas en un mdulo BAS
Hay que puntualizar que las variables pblicas declaradas en un formulario, mdulo de clase o control de usuario, se convierten en propiedades de esos "objetos", y como sabrs para usar una propiedad de un "objeto", debes especificar el nombre del objeto seguido de un punto y despus el nombre de la propiedad; aunque cuando se usan en el mismo mdulo o formulario, no es necesario usar el nombre del "objeto que las contiene", por tanto, si no se especifica el nombre del mdulo, slo son visibles dentro del mismo mdulo en el que se han declarado. Para que el ejemplo anterior funcionara como debe, tendrs que quitar las declaraciones que hay de las variables sNombre y nVeces en el formulario y slo dejar las del mdulo BAS. Prubalo y vers que ahora si que se muestra lo que se debe mostrar.
Usar una variable indicando el mdulo en el que se han declarado Ya que he tocado el tema de especificar el nombre del "objeto" delante de las variables, tambin se pueden usar de esa forma y as evitaramos el tema este de la visibilidad y alcance de las variables, aunque es algo ms "liante"... Esta forma de usarlo fuera del mdulo en el que se ha declarado, slo es vlido si las variables son "pblicas". Para verlo, un ejemplo: Crea un nuevo proyecto, se crear un formulario (Form1), aade un mdulo BAS (se crear Module1, no le cambies el nombre al mdulo) Escribe esto en el mdulo BAS y el formulario, segn se especifica: 'En el mdulo BAS Option Explicit Private pVariable As Long
319
'Esta variable est declarada en el Form1 y en este mdulo Public unaVariable As Long Public gVariable As Long
'El Form1 escribe: Option Explicit Private unaVariable As Long Public otraVariable As Long Private Sub Form_Load() Show 'para saber si est declarada, 'selecciona la variable y pulsa Shift+F2, 'te mostrar dnde est declarada 'Esta variable es la declarada en este formulario unaVariable = 10 'Estas variables estn declaradas como pblicas en el mdulo BAS otraVariable = 20 gVariable = 30 'Esta otra est declarada como pblica en el mdulo BAS, 'pero como tiene el mismo nombre que la declarada en el formulario, 'para poder usarla, hay que especificar el nombre del mdulo Module1.unaVariable = 40 'Esta dar error ya que es privada en el mdulo BAS '(Variable no definida) 'pVariable = 50 'si se usa de esta forma: 'Module1.pVariable = 50 'se encontrar la declaracin al pulsar Shift+F2, 'pero dar error de que no se encuentra el mtodo
320
'Mostrar el contenido de las variables: Print "unaVariable="; unaVariable Print "otraVariable="; otraVariable Print "gVariable ="; gVariable Print "Module1.unaVariable ="; Module1.unaVariable 'Esta no se puede mostrar porque es Privada y por tanto 'slo visible dentro del mdulo BAS 'Print "Module1.pVariable ="; Module1.pVariable End Sub Creo que los comentarios son aclaradores, as que ejectalo y vers lo que muestra, prueba a quitar Module1 cuando se use y asigne a "unaVariable" y vers que el primer valor se pierde (como debe ser...).
Bueno, aunque he tardado en publicarla... al fin hemos continuado con las entregas del cursillo bsico... La verdad es que el manuscrito era del 30 de Septiembre, empec a escribirlo en el OutLook Express cuando estaba en Canarias el 29 de Octubre y por fin lo he acabado el 6 de Diciembre... pero ms vale tarde que nunca... verdad? Confo en que haya servido de algo tanta espera... as que si quieres hacer algn comentario... hazlo pulsando en este link... Nos vemos. Guillermo
Ha pasado un largo tiempo, verdad? aunque en otras ocasiones las esperas han sido an ms largas, as que no te quejes demasiado... De todas formas, aunque ya lo habrs notado, las ltimas entregas que estoy publicando ya las tena escrita desde haca un montn de tiempo, esta en particular desde el 7 de Octubre del 98, pero la pereza unida a la falta de tiempo... hacen que se retrasen en publicar, pero ya sabes que esto de "mecanografiar" no es lo mio... pero ms vale tarde que nunca. Y ahora vamos a ver de que va todo esto y a ver si vale la pena, esperar tanto...
321
Caption
Text
Y como estas, muchas ms, para saber las propiedades que tiene un control, busca en la ayuda y te dir las que dispone... je,je... a ver si os acostumbro a pulsar F1 y me ahorro seguir con las entregas... No es que yo no quiera decrtelo, pero me parece absurdo documentar algo que ya est documentado y aunque no te lo creas bastante bien... aunque habr algunas propiedades que necesitarn de alguna explicacin y ejemplos... y esas, casi seguro, que las veremos.
322
Ahora vamos con los mtodos. Los mtodos no son otra cosa que procedimientos (SUB o FUNCTION). Un mtodo siempre hace algo, a diferencia de las propiedades que, aunque tambin pueden hacer algo, su papel suele ser parecido a una variable; por ejemplo, si queremos ocultar un formulario, llamamos al mtodo Hide, que queremos mover el formulario, pues usamos Move con los parmetros correspondientes y asunto arreglado. Es decir, son procedimientos normales y corrientes, pero su comportamiento est relacionado con el objeto, control o formulario, al que pertenece. Muchas veces a las funciones se las considera propiedades en lugar de mtodos, a esta "opinin" no debes darle ms importancia de la que tiene y centrarte en lo que realmente te interesa: aprender a usarlas e incluso a crearlas. Para saber ms sobre los mtodos... te digo lo mismo que con las propiedades, pulsa F1 y leete la ayuda.
Algunas propiedades son slo de lectura, es decir que no puedes modificarla en tiempo de ejecucin, aunque normalmente si las que puedes modificar en tiempo de diseo, es decir cuando ests trabajando en el IDE del Visual Basic (bien! es la primera vez que sale el nombre en lo que llevo escrito... no te preocupes, este desvaro debe ser a causa del "catarro" que tengo) Por ejemplo, la propiedad Sorted que le indica a un ListBox si debe mostrar los elementos clasificados o no, es de slo lectura cuando ejecutamos el programa, pero mientras "diseamos" el proyecto, nos permite cambiar ese valor. Como el ttulo de esta entrega deca: Propiedades, mtodos y lo que se me ocurra, se me ocurre comentarte que con el VB5 y superior, puedes crearte tus propios controles, para que? para personalizar su funcionalidad a tu gusto o a tus necesidades... pero si no tienes el VB5, an puedes seguir leyendo, ya que no voy a explicar nada, al menos por ahora, que no puedas hacer con el VB4 (lo siento por los que usen VB3, aunque tampoco vendra mal seguir leyendo). Cuando apareci el VB4, adems de permitir crear aplicaciones de 32 bits, tanto el Windows 95 como el Windows NT son sistemas operativos que "soportan" aplicaciones de 32 bits, aunque tambin funcionan las de 16 bits; sin embargo el Windows 3.xx slo soporta aplicaciones de 16 bits, aunque con un "apao" permite ejecutar algunas de 32 bits... aunque no las de VB... a que vena todo esto? Como intentaba decirte antes de "desvariar", con el VB4 lleg la primera intentona de Microsoft para hacer del Visual Basic un lenguaje orientado a objetos, en este punto te podra contar cmo llamaban la gente de Microsoft a esta versin del VB, pero como lo que se, lo he ledo en otros libros... no est bien que te lo cuente... como soy... verdad? (mejor te hubieras callao Guille) Bueno, al grano. Con el Visual Basic 4 lleg un nuevo tipo de mdulo: el mdulo de clase, el cual tiene como extensin .cls Adems de esta nueva extensin, los proyectos de VB tambin estrenaron "look" y pasaron de ser .MAK a .VBP (Visual Basic Project), no es que sea una ventaja, pero al menos se les dio "personalidad" a los proyectos de VB, ya que la extensin MAK, adems de ser usada por las versiones anteriores de VB, tambin las usaban (y usan) los compiladores de Microsoft, tanto para BASIC como para C/C++ y lo ms frustrante del compilador del BASIC de MS-DOS, es que exista una utilidad llamada MAKE, (para crear los ejecutables), que tambin las usaba... y algunas veces era un folln... Dejemos el cuento, que algunas veces me paso con mis "pelculas"...
323
Para ir calentando motores, ya que no creers que te voy a explicar cmo usar este nuevo tipo de mdulos... bueno, no te lo voy a explicar por ahora, pero si dentro de poquitas entregas... Vamos a ver cmo usarlo de forma "muy" genrica, para que al menos sepas que puedes crear tus propias "propiedades" y mtodos... gracias a:
324
Dim unNombre As New cNombre Con esta declaracin le decimos al VB que cree una nueva (New) variable que nos permita usar esta clase llamada cNombre. Nota del 24/Ago/2003: Aqu he usado esa forma de declarar e "instanciar" (crear en memoria) una clase, pero esta no es una forma recomendable y nunca, nunca, deberas usarla. En lugar de declarar y crear una clase de esa forma, usa este otro cdigo: Dim unNombre As cNombre Set unNombre = New cNombre En la entrega correspondiente veremos con ms profundidad todo esto de las clases y la creacin de los objetos en la memoria, la asignacin de esos objetos mediante Set, etc... pero eso ser en otro momento. Una vez que tenemos creado el "objeto", lo usamos de la misma forma que hacamos con los tipos definidos: unNombre.Nombre ="Guillermo" unNombre.AoNacimiento = 1957 'Para mostrar la edad de este cuarentn, haremos: MsgBox unNombre.Nombre & " tiene " & unNombre.Edad & " aos..." Esto imprimir: (suponiendo que estamos en 1999) Guillermo tiene 42 aos... (aunque debera decir "tendr", pero bueno...) Ahora analiza el cdigo y deduce cmo funciona... ya que lo vamos a dejar aqu... as parecer una teleserie y estars pendiente de la pantalla... je, je... esto es la "revancha" por rerte de mi edad. Nos vemos Guillermo P.S. No te preocupes por la "cortedad" de esta entrega... pronto habrs ms... P.S. 2 Si hubiese querido, me podra haber enrollado mostrndote un montn de propiedades
Cada vez que miro las fechas en que estn escritas las entregas y en la que las publico, me da no se qu... pero es que no puedo hacer ms... Aunque ahora, gracias a Encarnica, esto puede que cunda ms... Quin es Encarnica... Encarni?, (que sino se puede mosquear...) Es la "secretaria" de la empresa... bueno, lo de secretaria es por llamarla de alguna forma, ya que tambin se encarga de otros menesteres administrativos... y ahora, adems, en sus ratos libres, se atreve a pasarme a mquina mis "manuscritos"... trabajo que le dar por un lado, entender mi letra y por otro entender el Visual Basic, lo mismo hasta aprende... je, je. Gracias Encarni!... No os doy un e-mail para que les deis tambin las gracias, porque no tiene, pero si quieres agradecrselo, usa este link. Nota: No es necesario que escribas nada, con el asunto es suficiente, pero si escribes... que sepas que no debes incluir consultas ni nada de eso que acostumbris a hacer cada vez que os encontris con un link para enviar un mensaje... este link es exclusivo para agradecer a Encarni el que me alivie el trabajo de teclear y que las entregas aparezcan con mayor frecuencia...
326
el proyecto. Otra forma sera aadiendo una variable pblica al formulario principal Tambin sera visible en todo el proyecto, aunque para acceder a ella tendramos que indicar el nombre del formulario Vamos a usar el segundo mtodo, ya que al ser el formulario que se usar como punta de entrada, siempre estar disponible. Veamos el cdigo del formulario principal al que llamaremos frmPrincipal, para hacer esto, haz que se muestre el formulario, pulsa F4 y en las propiedades, selecciona name (nombre) y sustituye form1 por frmPrincipal. A partir de ahora, cada vez que quieras referirte al formulario principal, tendrs que hacerlo con ese nombre: frmPrincipal. Muestra la ventana de cdigo de este formulario y aade esto (recuerdas para que sirve Option Explicit?) Option Explicit Public Nombre As String Ahora nuestro formulario tiene una nueva propiedad, a la que podremos acceder desde el resto del proyecto usndolo de esta forma: frmPrincipal.Nombre Este formulario principal hace pocas cosas, no s por qu todos los que tienen un papel principal suelen hacer pocas cosas Bueno, al lio... La verdad es que lo nico que hace es llamar a otros formularios y acabar t dirs lo que quieras, pero esto de mandar, hacer poco y adems ser el principal en fin cosas de la vida Vamos a aadir un nuevo formulario, que se llamar frmNombre. Para hacerlo, selecciona el men Proyecto, Aadir formulario, selecciona uno normal y ya est disponible para que le cambies el nombre y codifiques en l. Recuerda: pulsa F4 y selecciona la propiedad Name, borra Form2 y escribe frmNombre. Como te coment, este formulario jefe tiene tres botones, a los que llamar: cmdNombre, cmdJugar, cmdTerminar; cada uno de ellos hace lo que los nombres, ms o menos, indican. Veamos el cdigo: Private Sub cmdNombre_Click() ' Mostranos el form que pide el nombre ' Esto har que se muestre y no se pueda hacer otra cosa ' hasta que se cierre u oculte frmNombre.Show vbModal End Sub
327
Ya ves que hace poco, simplemente muestra otro formulario que se encargar de pedir el nombre del jugador. Aqu hay unas cosillas a tener en cuenta, incluso deberas tomar nota, as que toma nota y qudate con la copla Con Show mostramos el formulario en cuestin, fjate que se usa NombreFormulario.Show, lo cual llama a un mtodo propio de ese formulario, que lo que hace es mostrarlo Otra cosa a tener en cuanta es que si es formulario no est cargado en memoria, al usar este mtodo se carga de forma automtica, esto lo veremos en ms ocasiones, as que... tranquilzate, toma aire, que todo llega El mtodo Show acepta algunos parmetros, que yo conozca son dos, ambos son opcionales y por el momento slo veremos el primero de ellos. ste primer parmetro del mtodo Show, lo que hace es mostrar el formulario de forma modal o no. Mostrar un formulario modal lo que hace es mostrar ese formulario y no hacer otra casa en la aplicacin hasta que deje de mostrarse; es como si toda la aplicacin, (la nuestra), se congele y slo preste atencin a lo que ocurre en ese formulario, esto es importante sobre todo cuando es necesario esperar a que el usuario introduzca los datos necesarios
Debo reconocer que cuando empec con el Visual Basic, esto de no saber que exista esta forma de mostrar los formularios, me dio algn que otro comecoco Por suerte, aprend que la tecla F1 serva para algo ms que estar al lado de F2... Cuando no necesites esperar a que el usuario introduzca datos o no sea importante que se siga usando las aplicaciones a pesar de que se muestre ese formulario; puedes mostrarlo de forma no modal, ese es el valor por defecto del primer parmetro, es decir que si se usa: NombreFormulario.Show, el formulario no se muestra modal, as que ojito A lo que vamos, usando frmNombre.Show vbModal, se muestra el formulario y se espera a que se cierre u oculte. Veamos lo que hace este formulario, la verdad es que tampoco hace mucho, salvo darle informacin al formulario principal. Ahora veremos como. El formulario frmNombre tiene este aspecto:
328
Un textbox para el nombre del usuario, un botn aceptar y otro cancelar Los nombres de estos controles son: txtNombre, cmdAceptar y cmdCancelar. Podramos haber usado la funcin InputBox para hacer esto, pero no es lo mismo El botn cancelar simplemente descarga el formulario de la memoria con: Unload Me. El botn aceptar tambin descarga el formulario, pero antes de eso, asigna el nombre introducido en la propiedad Nombre del formulario principal, fjate en el cdigo para ver como se hace: frmPrincipal.Nombre = txtNombre Como ya coment antes, las propiedades de un formulario se pueden acceder siempre usando el nombre del formulario en cuestin, si no se usara cmo sabramos qu formulario es? La nica excepcin es cuando esas propiedades se usan dentro del mismo formulario ahora lo veremos. Pero esto no est bien o casi si dejsemos en el textbox del formulario que se usa para pedir el nombre, el valor que hay por defecto, es decir el que se muestra cuando se aade el control al formulario, en este caso Text1, nos daramos cuenta que sera ms lgico que se mostrase el nombre del usuario actual, si hubiese alguno, por tanto se debera mostrar el nombre y as permitir usar ese nombre o escribir uno nuevo. Esto lo podemos hacer de dos formas: Una: cmo la propiedad Nombre del form principal es pblica, podemos asignarla al textbox cuando se cargue el formulario que pide el nombre, es decir en el evento Load de frmNombre: Private Sub Form_Load() txtNombre = frmPrincipal.Nombre End Sub La otra forma, sera usar la posibilidad de poder referirnos a los controles de un formulario desde otro formulario; se har de la misma forma que para acceder a una propiedad Veamos el cdigo, en este caso, la asignacin se hace en el procedimiento que se encarga de mostrar el formulario frmNombre, es decir en el evento cmdNombre_Click Private Sub cmdNombre_Click() frmNombre.txtNombre = Nombre frmNombre.Show vbModal End Sub Nota: Cuando se muestra un form modal, es importante no usar el mtodo Show dentro del cdigo del evento Form_Load del formulario mostrado, en el caso de que se haga, el VB nos dar un error indicndonos que no se puede mostrar un formulario que se est mostrando en forma modal.
329
Espero que lo tengas claro, ya que te voy a complicar un poco ms la vida, aunque antes veamos unas consideraciones: Habrs odo de reutilizacin de cdigo y esas otras cosillas relacionadas con la Programacin Orientada a Objetos, (OOP), aunque no toda las buenas formas de hacer las cosas tienen que estar relacionadas con la OOP aunque mejor no entrar en polmica. Si usamos el formulario tal como lo he codificado, no podrs usarlo de forma genrica, (reutilizarlo), ya que dentro de ese cdigo se hace referencia a otro formulario es decir, siempre que usemos frmNombre, necesitaremos tener otro formulario llamado frmPrincipal que al menos tenga una propiedad llamada Nombre Aunque no tiene porqu ser una propiedad como hemos visto, tambin podra ser un control pero olvdate de esto para que no te les demasiado Bien, cmo lo solucionamos? Si has ledo algo sobre la programacin orientada a objetos, puede que te hayas encontrado con palabras como encapsulacin de Qu significa esto? Sin entrar en demasiados detalles didcticos, encapsular sera tener la autonoma suficiente para no depender del exterior y an as, poder funcionar o lo que es lo mismo, al formulario frmNombre no le importa si el formulario que quiere usarlo tiene o no una propiedad llamada Nombre, tampoco le molestar que ese formulario se llame frmPrincipal o de otra forma. El que tiene que saber cosas de frmNombre, ser el formulario que quiere usarlo Qu tiene que saber? En este ejemplo slo existe una propiedad llamada txtNombre que se encargar de devolver el nombre introducido y tambin debera indicar que se ha cancelado aunque esto te lo dejo, (por ahora), a ti ya veremos la respuesta ms adelante. Veamos el cdigo tal como estara despus de todo esto que te he explicado. Empecemos por el final, veamos el cdigo del formulario encapsulador, (frmNombre): Option Explicit Private Sub cmdAceptar_Click() Hide End Sub
Private Sub cmdCancelar_Click() Hide End Sub Ahora te explico de que va esto Veamos antes el cdigo del form principal (frmPrincipal): Option Explicit Public Nombre As String
330
Private Sub cmdNombrer_Click() frmNombre.txtNombre = Nombre frmNombre.Show vbModal Nombre = frmNombre.txtNombre Unload frmNombre End Sub Dirs que nos hemos complicado demasiado antes era ms corto no todo van a ser ventajas, aunque si por aadir dos lneas de cdigos ganamos un formulario que es independiente tu decides Aunque, personalmente, este ltimo cdigo lo escribira as: Private Sub cmdNombrer_Click() With frmNombre .txtNombre = Nombre .Show vbModal Nombre = .txtNombre End With Unload frmNombre End Sub Ahora tenemos ms lneas de cdigo, pero ganamos en claridad adems si te acostumbras a usar With, (segn dicen), el cdigo es ms rpido, al menos cuando se hacen referencias a objetos con distintos niveles, este no es el caso, pero ya te topars con casos de mltiples referencias todo llegar Antes de seguir encontrando pegas, veamos porqu ha cambiado Unload Me por Hide: Unload Descarga un formulario de la memoria, despus de Unload se debe indicar el nombre del formulario. En este caso se usa Me para indicar que se descarga el formulario en el que est esa instruccin. Simplemente oculta el formulario, pero no lo descarga.
Hide
La diferencia est en que si descargamos el formulario, lo borramos de la memoria, por tanto destruimos todo el contenido y lo que hubiese almacenado en los controles y esto no es lo que nos interesa, ya que necesitamos conservar el contenido del txtNombre para que el cdigo que use frmNombre pueda saber el nombre que se introdujo. Un lo verdad? Pues si te las con cuatro lneas de cdigo, no sabes lo que te espera... je, je.
331
Espero que te vayas quedando con la esencia y que vayas asimilando cosas no pretendas saber programar lo que pretendo es que al final, (si es que esto puede terminar algn da), sepas programar y por extensin saber desenvolverte con el Visual Basic. Ahora te dejo con el ejercicio de mejorar lo presente, por ejemplo que el formulario principal sepa que se ha pulsado en Cancelar no te comas mucho el coco y no te compliques demasiado, intenta buscar soluciones fciles, que las hay aunque tambin puedes complicarte la existencia y hacerlo de otra forma diferente usando otras propiedades, por ejemplo ops! ...creo que me he pasado con la ayuda En la prxima entrega seguiremos que ya es tarde y maana hay que currar Pincha este link para ver una de las soluciones Nos vemos. Guillermo Nerja, 6/Oct/98, 03:10 P.S. UF! Hace ya bastante tiempo, verdad?
332
bsicamente las que nos de el textbox... bueno, realmente tendr algunas ms... ya veremos. Para crear el programa, abre un proyecto nuevo, el el form que se crea por defecto, aade un TextBox, asgnale estos valores a las propiedades indicadas: Propiedad Multiline Scrollbars Left Top Name Valor True 3-Both 0 0 txtEditor
Multiline para que permita ms de una lnea de texto; los dos Scrollbars para que podamos escribir lneas de cualquier longitud y que slo cambien al pulsar Intro; Left y Top, para que se posicione en la esquina superior izquierda del formulario.
Crear una barra de estado (StatusBar) Vamos a aadirle al formulario un Picture para que nos sirva de Statusbar, para ello aade un picture al proyecto, seleccinalo y asgnale la propiedad Align a 2 - Align Bottom para que se pegue a la parte inferior del formulario. Asgnale un valor 315 a la propiedad Height, en la propiedad BorderStyle, asgnale un 0 (sin borde). Selecciona del toolbar una etiqueta haz doble click y se insertar en el formulario. Selecciona la etiqueta y crtala (men edicin / cortar). Selecciona el picture y pega la etiqueta (men edicin / pegar). Asgnale estas valores a las propiedades indicadas: Height = 285, Top = 15, Left = 30, BorderStyle = 1 (con borde). Ahora vamos a darles nombre a los controles, al TextBox llmalo txtEditor, el Picture ser picStatus, el Label se llamar lblStatus.
Posicionar los controles automticamente en el formulario Antes de empezar a crear los mens, vamos a indicarle al Visual Basic que "posicione" correctamente los controles cuando el formulario cambie de tamao. Para ello, abre la pantalla del cdigo, selecciona Form de la lista de la izquierda y Resize en la lista de la derecha. Cada vez que un formulario cambia de tamao se ejecuta el evento Form_Resize, por tanto este es el sitio en el que tendremos que codificar para adaptar los controles al tamao adecuado. Lo que vamos a hacer es ajustar el tamao de la etiqueta al tamao del Picture y el TexBox para que ocupe todo el tamao restante. Pero slo haremos los clculos cuando el form no se minimice, ya que si est minimizado, no se ve nada, as que para que vamos a ajustar el tamao de algo que no se ve; para indicarle al VB que slo ejecute el cdigo cuando no vaya a minimizar la aplicacin usaremos la propiedad WindowState, si esta es diferente de vbMinimized querr decir que no se ha minimizado, as pues, aade este cdigo:
333
' Slo cuando no est minimizado el formulario If WindowState <> vbMinimized Then Y ahora empezaremos a ajustar los tamaos: El label ser igual de ancho que el PicStatus menos 60, para que tenga un poco de "respiro" por los lados: ' lblStatus.Width = picStatus.ScaleWidth - 60 esta es simple, ahora haremos lo mismo lo mismo con el txtEditor: ' txtEditor.Width = ScaleWidth el alto ser el alto del form menos el alto del PicStatus: ' txtEditor.Height = ScaleHeight - picStatus.Height y ya est. Se supone que en tiempo de diseo asignaste 0 a las propiedades Left y Top del txtEditor, aunque si quieres, puedes hacerlo en este mismo evento: ' txtEditor.Move 0, 0 eso es lo mismo que haber asignado 0 a las propiedades Left y Top es ms rpido cambiar el tamao de un control con Move que asignando cada una de las propiedades por separado, ya que se usa un slo mtodo en lugar de 4 asignaciones a propiedades, as pues, podramos haber cambiado el tamao del txtEditor de esta otra forma, aunque seguramente sera menos "instructivo": ' txtEditor.Move 0, 0, ScaleWidth, ScaleHeight - picStatus.Height Vamos a probarlo : Pulsa F5 y cambia el tamao de la ventana, vers como se "adaptan" los controles aunque el label no parece enterarse verdad?
334
Para que el label se ajuste al tamao del picture, hay que ajustar ese tamao en el evento Resize del picStatus: ' Private Sub picStatus_Resize() ' Slo cuando no est minimizado el formulario If WindowState <> vbMinimized Then ' Aqu se ajustar el tamao del label ' cuando cambie el del picStatus lblStatus.Width = picStatus.ScaleWidth - 60 End If End Sub Algo sobre los tamaos de los controles: Diferencia entre Height/Width y ScaleHeight/ScaleWidth Como habrs notado, para ajustar el ancho y alto, se est usando ScaleWidth y ScaleHeight, aunque para calcular el alto del textbox tambin se usa picStatus.Height, en cuanto te explique que significan estas propiedades, seguro que lo entiendes. ScaleWidth y ScaleHeight son propiedades que nos informan del ancho y alto "interno" del formulario o control, es decir lo que miden sin contar el borde. Width y Height, por otro lado, nos dicen que el ancho y alto "externo" del form o control. Cuando se calcula el alto del txtEditor necesitamos saber el alto interno del formulario (ScaleHeight) al que hay que restarle el alto total de picStatus (picStatus.Height). Si hubisemos usado Height en el lugar de ScaleHeight, los clculos no nos hubiesen salido correctos, ya en el valor devuelto por esa propiedad nos indicara el alto total del form. Ahora mismo, tal como est el programa habra poco diferencia, aunque an as no se ajustara perfectamente, "desajuste" que quedara demasiado evidente en cuanto aadamos mens. (mens? No era de eso de lo que iba a tratar esta entrega). Otro detalle es que para referirnos al alto y ancho "interno" del formulario, lo hemos usado sin indicar nada ms, esto siempre es as cuando hagamos referencia a una propiedad de un objeto, (en este caso un formulario), y el cdigo se ejecuta "dentro" de ese formulario. Sin embargo cuando hacemos referencia a las propiedades de otros controles, (incluso de otro formulario), tendremos que anteponer el nombre de ese control delante de la propiedad o mtodo, para que el VB sepa a que control nos estamos refiriendo. Borra la lnea que cambiaba el tamao de la etiqueta en el evento Form_Resize y vuelve a pulsar F5, cambia el tamao del formulario, esta vez si que se adapta bien el lblStatus dentro del picture que la contiene. Ya puedes detener el programa, pulsando en la "x" del form. Haz que se muestre el fomulario ya que es necesario para poder aadir mens a nuestro "mini-editor".
335
336
Las partes ms importantes son: Caption/Descripcin que es el texto que se mostrar, Name/Nombre del men que ser donde escribamos el cdigo a ejecutar cuando se seleccione ese men. Vamos a empezar por aadir un men "Fichero", (o archivo si as lo prefieres), en este men tendremos las opciones de Abrir, Guardar, Guardar como y Salir, despus aadiremos otras, segn convenga. Tambin tendremos otro men principal llamado Edicin con las clsicas opciones de ese tipo de men: Deshacer, Cortar, Copiar, Pegar, etc. Pero empecemos por el de fichero: Escribe en el "Caption", &Ficheros, el signo & le indicar al Visual Basic que muestre subrayada la letra que sigue a ese signo, de esa forma se podr acceder pulsando Alt y la letra subrayada, es decir Alt+F En el nombre del men escribe: mnuFic y pulsa Intro o en el botn Siguiente. Se "limpiarn" las casillas de texto y estar listo para escribir las opciones de este men: Escribe en Caption: &Abrir... y en nombre mnuFicAbrir los tres puntos suspensivos es una norma recomendable, que indica que se mostrar un cuadro de dilogo, acostmbrate a seguirla, de esta forma tus aplicaciones tendrn un aspecto "standard windows" (realmente no es un estndar de windows, sino una norma anterior anterior, pero) Despus de la opcin Abrir vamos a aadir Guardar, por tanto en el caption del men escribimos &Guardar y en el nombre de esa opcin: mnuFicGuardar, en este caso no
337
aadimos los tres puntos seguidos ya que lo habitual en las opciones guardar, es guardar sin preguntar, salvo que an no se le haya dado nombre al fichero. Como habrs observado cada vez que aades una opcin se va mostrando en la lista inferior. Antes de seguir vamos a ver cmo quedan nuestros mens. Pulsa el botn "aceptar" del cuadro de dilogo del "diseador de mens". Sorpresa! Como puedes observar, tenemos tres opciones "principales": Ficheros, Abrir y Guardar, pero esta no era la intencin, ya que Abrir y Guardar slo se deberan mostrar al seleccionar el men Ficheros Qu ha pasado? Muy fcil, al menos cuando se sabe cmo trabaja esto de los mens, que al no indicarle lo contrario todos los mens se muestran en la barra principal! cmo podemos crear los submens (o mens que se muestran al seleccionar un men)? Ahora lo veremos, antes de hacerlo, en tiempo de diseo, es decir, sin pulsar F5, pulsa en el men Fichero. Se mostrar la ventana de cdigo, el combo de en la parte izquierda se mostrar mnuFic y en el de la derecha vers que es el evento click, por tanto estaremos en el procedimiento mnuFic_Click. Todo lo que escribas en este men se ejecutar cuando selecciones esta opcin. No escribas nada, cierra la ventana de cdigo para volver a mostrar el formulario. Vamos a hacer que los mens se muestren como deben: al seleccionar Ficheros que se despliegue el men con las opciones Abrir, Guardar, etc. Entra en el diseo de mens, (esto tendrs que hacerlo siempre que quieras aadir nuevas opciones de mens o modificar las ya existentes.) Si te fijas en la lista inferior, vers que las tres opciones que tenemos estn alineadas a la izquierda. Selecciona Abrir en la lista inferior, comprobars que se "rellenan" las casillas con la descripcin y el nombre del men, eso nos indica que est seleccionada esa opcin y que cualquier cambio que hagamos, se har en esa opcin. S que todo esto es evidente, pero por si no lo habas captado ahora pulsa en la flecha que seala a la derecha, esto har que la opcin seleccionada se desplace a la derecha, esto se muestra por tres puntos delante de Abrir, no los confundas con los tres puntos que nosotros le aadimos al final. Haz lo mismo con "Guardar" Cierra el cuadro de dilogo y veras que ahora slo se muestra el men Ficheros. Bien! Ya tenemos lo que queramos! Selecciona ese men y vers que se muestran las dos opciones que hemos aadido; en esta ocasin no se muestra la ventana de cdigo, pero ya veremos que e evento "sigue operativo". Pulsa en la opcin "Abrir..." y en esta ocasin se mostrar la ventana de cdigo con el procedimiento: "mnuFicAbrir_Click", para comprobar que funciona vamos a aadir un mensaje que se mostrar cuando seleccionemos esta opcin: ' Private Sub mnuFicAbrir_Click()
338
' Abrir MsgBox "Esta es la opcin Abrir..." End Sub Ahora para comprobarlo, pulsa F5 y selecciona el men Ficheros, se mostrarn las dos opciones que tenemos en este men: Abrir y guardar. Selecciona Abrir y vers que se muestra el mensaje, lo cual quiere decir que todo est bien. Cierra la aplicacin y vuelve a mostrar el formulario para aadir ms opciones a los mens; as que haz que se muestre el diseador de mens. Selecciona la ltima de las opciones de la lista y pulsa en el botn Siguiente para que podamos aadir ms opciones. Escribe G&uardar como... en la descripcin y mnuFicGuardarComo en el nombre. Si esta nueva opcin se muestra en la lista totalmente a la izquierda, pulsa en la flecha de identacin a la derecha para que est al mismo nivel que las otras dos, es decir que tenga tres puntos delante del Caption que le hemos dado. Pulsa de nuevo en el botn Siguiente, ahora tendrs que escribir un guin, (signo menos), en la descripcin del men, en el nombre del mismo escribe: mnuFicSep1, no sirve de nada, ya que las lneas "divisorias" no se pueden seleccionar, pero deben tener un nombre. Lo que debes "recordar" es que si se indica un "-" en la descripcin del men, estamos indicndole al VB que lo que queremos es que muestre una lnea de divisin. Pulsa en siguiente y escribe: &Salir y mnuFicSalir (ya no es necesario que te diga dnde debes escribirlo, verdad?) Ya tenemos las opciones del men Ficheros, ahora vamos con el men de edicin. Aade este men a continuacin de Salir, escribe &Edicin en el Caption y mnuEdit en el nombre. Cuando lo hayas escrito ver que est debajo de Salir y con los tres puntos delante, si lo dejamos as, no se mostrar en la barra principal de mens, sino que ser una opcin ms del men Ficheros, y eso no es lo que queremos, por tanto, pulsa en la flecha que seala a la izquierda para que se pegue totalmente a la izquierda, y desaparezcan los puntos suspensivos que hay delante del Caption. Porque como ya vimos antes, las opciones que se muestran en la lista y que estn sin los puntos suspensivos son las que se mostrarn en la barra de mens. Antes de aadir opciones al men de edicin, vamos a modificar las opciones que tenemos, insertaremos una nueva al principio, que servir para crear un nuevo fichero. El caption, como puedes imaginar, ser Nuevo y el nombre del men ser mnuFicNuevo. Vamos a aadirla: asegrate que estemos en modo de diseo de mens. La nueva opcin la vamos a insertar al principio, es decir justo antes de Abrir. Por tanto, selecciona Abrir de la lista inferior, pulsa el botn insertar y podrs escribir la descripcin: &Nuevo y el nombre del men: nmuFicNuevo. Cada vez que quieras insertar una nueva opcin puedes hacerlo de esta forma o bien aadiendo la opcin al final y despus "situarla" en el lugar correspondiente usando las flechas arriba y abajo. Cuando insertas una opcin, usando el botn insertar, la opcin insertada tiene la misma "indentacin" que la que estaba seleccionada antes de pulsar en insertar. Ya sabes que puedes modificar dicha identacin, o desplazamiento, usando las flechas de izquierda y derecha. Si lo que quieres es borrar un elemento de men, simplemente la seleccionas y pulsa en "eliminar", aunque esto slo elimina la opcin del men, no el cdigo que tuviese
339
asociado, lo mismo ocurre cuando eliminamos o cambiamos el nombre de un control: si ya tena cdigo en algunos eventos, este cdigo sigue estando, pero no en el sitio que debiera no voy a seguir con esto, ya que lo veremos en otra ocasin, pero al menos cuando le llegue el turno te "sonara" Como ya vimos en La entrega 26 se suele seguir unas "normas" a la hora de nombrar a los controles y variables, y si no se "suele" seguir, al menos se recomienda. En este caso los mens se preceden con "mnu" seguido del nombre de men principal y por ltimo el nombre de la opcin. Aunque, como todos los consejos, eres libre de seguir estas normas o de crear las tuyas propias. En mi caso, "intento" seguir stas que te estoy indicando, aunque algunas veces me las salto, normalmente con la opcin "Salir" que simplemente la llamo: mnuSalir, aunque es mejor llamarlo mnuFicSalir para que sepamos que est "incluida" en el men fic-heros. Pero en esto de los nombre de los mens, aparte de que los puedes "nombrar" como quieras, existe otra forma de hacerlo. Ya vimos que se pueden crear "arrays" de controles y que la ventaja era, sobre todo si estaban relacionados, que no necesitamos escribir el mismo cdigo para cada uno de los eventos que queramos interceptar; simplemente usando el ndice podramos distinguir un control de otro; pero siempre, y esa es la ventaja, en un mismo procedimiento de evento. Pues esto mismo se puede hacer con los mens: podemos crear un array. La nica diferencia es que se crea de forma un poco ms manual y se hace en la "ventana de diseo de mens".
Array de mens
Para ello, se usa el mismo nombre de men, pero usando un "ndice" diferente para cada opcin. El nico requisito "obligatorio" es que los elementos de un array de mens han de estar correlativos. Habitualmente se incluyen en ese array todos los elementos de un men principal bueno, los que se muestran al seleccionar esa opcin. Y esto es lo que vamos a hacer nosotros: incluir en un array todos las opciones del men edicin, que sern las siguientes: Deshacer, separacin, cortar, copiar, pegar, separacin, seleccionar todo; posteriormente aadiremos ms opciones aunque seguramente ser en otra entrega. El nombre del array ser: mnuEditor, el primer elemento, de ndice cero, ser: Deshacer. Vamos a aadirlo pasito a pasito, para que no tropieces y te "descalabres". Supongo que ya estars en el diseador de mens y que la opcin seleccionada es la ltima: Edicin. Pulsa en el botn Siguiente y escribe en el Caption: Des&hacer, en el nombre del men: mnuEditor, (sin acento o tilde... como prefieras llamarlo), en Index escribe 0; pulsa el botn con la flecha a la derecha, para indicar que esta opcin pertenece al men Edicin. Pulsa en siguiente, si no se indenta, ya sabes cmo debes hacerlo; escribe un "-" en el Caption, mnuEditor en el nombre, pero en ndice escribe 1, ya que al llamarse de la misma forma el men, el Visual Basic esperar encontrarse con un ndice que lo diferencie. Haz lo mismo con el resto e las opciones y recuerda usar ndices correlativos, y por supuesto diferentes...
340
Cuando hayas introducido todas las opciones, cierra el diseador de mens si te da algn tipo de error, puede ser porque no hayas usado el mismo nombre de men para todas las opciones del men edicin, porque no estn los ndices correlativos o porque no estn todos indentados en el mismo nivel, es decir con tres puntos suspensivos a la izquierda (esto es lo que se muestra en la lista y no tienes que escribirlos). Este sera el aspecto:
Recuerda que los elementos de un array de mens deber ser correlativos y estar en el mismo nivel de indentacin ("osease" pertenecer al mismo men). Si todo est bien, al mostrar el formulario en tiempo de diseo, no en ejecucin, y pulsar en el men edicin, vers que se muestra las opciones que hemos escrito. Pulsa en cualquier de ellas y vers que siempre se muestra el mismo evento en la pantalla de cdigo: mnuEditor_Click (Index As Integer) El parmetro index ser el que nos indique cual de las opciones ha sido la que se ha coleccionado. Para poder hacer cosas diferentes segn la opcin seleccionada haremos algo como esto: ' Private Sub mnuEditor_Click(Index As Integer) Select Case Index Case 0 ' Deshacer Case 2
341
' Cortar ' Etc... ' End Select End Sub Pero para que resulte ms fcilmente entendible y modificable, en lugar de nmeros, vamos a usar constantes, de esta forma, si aadimos o eliminamos alguna de las opciones del men, slo tendremos que cambiar el valor de la constante y el resto del cdigo no habr que modificarlo. As pues, en la ventana de cdigo, selecciona la parte general de las declaraciones y aade esto: ' ' Constantes para el men de Edicin. ' Los valores se corresponden con el ndice de mnuEditor Const cEdDeshacer = 0 Const cEdCortar = 2 Const cEdCopiar = 3 Const cEdPegar = 4 Const cEdSeleccionarTodo = 6 Ahora las distintas opciones de "select" en el evento mnuEditor_Click quedarn as: ' Private Sub mnuEditor_Click(Index As Integer) Select Case Index Case cEdDeshacer ' Case cEdCortar ' Case cEdCopiar ' Case cEdPegar ' Case cEdSeleccionarTodo ' End Select End Sub
342
Con lo cual hemos ganado en "legibilidad" en el cdigo y, aunque an no lo "sepas", en facilidad a la hora de modificar el cdigo. El cdigo a usar ser el contenido de la siguiente entrega, ya que esta se acaba aqu. Adems del cdigo a usar, veremos cmo aadirle, a las opciones del men, teclas de acceso rpido, por ejemplo: Control + X para cortar, etc. Pero eso ser en nuestro siguientes episodio. Permanezca atento a la pantalla continuar. Nos vemos Guillermo
343
contenido, es decir TODO el contenido, incluido los "desvaros" y chorradillas, como estas, que escribo... si me entero de que no lo hacen... me voy a enfadar. Ya est bien de tantas chorradas, as que vamos a seguir con los mens que el tiempo apremia.
Antes de ver el cdigo "operativo" de las opciones que aadimos en la entrega anterior, vamos a seguir aprendiendo cosas de los mens, para tener los conceptos ms claros, que al fin y al cabo es lo que interesa.
Aunque en nuestro pequeo editor no lo vamos a usar, veremos cmo se crean opciones de mens que a su vez muestran otros mens. Para ello vamos a crear otra opcin en el men ficheros para poder imprimir, pero que nos mostrar un par de opciones: configurar la impresora adems de la opcin imprimir. El aspecto de este men sera el siguiente:
Para poder conseguir un men dentro de otro men no hay que hacer nada especial simplemente usaremos lo que hasta ahora hemos visto: indentar opciones. Ya viste que cuando queramos mostrar las opciones de uno de los mens principales, simplemente "desplazbamos" las siguientes opciones hacia la derecha pues esto mismo es lo que hay que hacer desplazar las nuevas opciones y el Visual Basic sabr que tiene que mostrarlas en otro men. Por tanto, cada vez que necesitamos mostrar un men al hacer "click" en una de las opciones de cualquier men, desplazaremos esas opciones hacia la derecha. Vamos a verlo de forma prctica: Muestra el formulario, haz que se muestre el "diseador de mens", posicinate en la opcin "Salir", pulsa en el botn insertar y escribe en la descripcin, (o caption), &Imprimir, en el nombre del men escribe mnuFicImp, pulsa en siguiente para que acepte lo que hemos escrito, para insertar una nueva lnea, pulsa en el botn insertar y escribe en el caption: &Selecionar impresora y mnuFicImpSelec en el nombre del
344
men; antes de pulsar en siguiente, dale al botn con la flecha hacia la derecha, en esta ocasin tendremos seis puntos suspensivos delante de esta opcin, (me refiero a la lista de abajo), esto indicar que est dos niveles hacia la derecha, es decir que tenemos un men dentro de otro men. Ahora pulsa en los botones siguiente e insertar y escribe &Imprimir y mnuFicImpImp, ya sabes dnde, para que est en el mismo nivel que Seleccionar Impresora, tendrs que pulsar en la flecha que seala a la derecha... para que est en el mismo nivel de mens. Si por casualidad ves que salen ms puntos suspensivos de la cuenta... pulsa en el botn con la flecha a la izquierda para quitar la indentacin que le hayas dado de ms. Cierra el diseador de mens. Muestra el formulario, si pulsas en el men Ficheros y a continuacin en Imprimir..., vers que tenemos un nuevo men, en este caso con las dos opciones que hemos aadido, (ver la figura anterior). As de fcil se crean los sub-mens o mens que se muestran al seleccionar una opcin de un men.
Nota: Cuando tienes opciones ya creadas y quieres insertar nuevas opciones, hay que usar el botn Insertar, pero la "indentacin" que muestra es la misma que tiene el men en el que nos posicionamos antes de insertar, por tanto tendrs que usar las flechas de indentacin para ajustar los niveles de mens.
Private Sub txtEditor_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) ' Si pulsamos el botn derecho... (vbRightButton = 2) If Button = vbRightButton Then ' Mostrar el men de Edicin PopupMenu mnuEdit
345
' Si queremos mostrar en negrita uno de los mens, ' lo indicaremos en el quinto parmetro 'PopupMenu mnuEdit, , , , mnuEditor(cEdCopiar) End If End Sub Ya vimos que uno de los parmetros del evento MouseDown estaba el de saber que botn se est pulsando, por tanto usaremos ese parmetro, que es Button, para saber si es el botn derecho, (vbRightButton que tiene un valor igual a 2), y si es as, mostramos nuestro propio men. Para ello usamos la instruccin PopupMenu, en el primer parmetro indicamos el nombre del men que contiene las opciones que queremos mostrar, del resto de los parmetros, en principio slo nos podra interesar el ltimo, que indica que men debe mostrarse seleccionado. En nuestro caso el men a mostrar es mnuEdit que es del que cuelgan las opciones de este men. Con PopupMenu se puede usar cualquier men, incluso si no est visible. Esa caracterstica se suele usar cuando queremos mostrar mens en nuestra aplicacin pero queremos que se muestren solamente como mens emergentes, en otra ocasin veremos esto. Una vez que un men emergente se ha mostrado funciona de igual manera que si no fuese emergente se que es "lgico", pero lo aclaro por si las moscas es decir cuando se selecciona una opcin, se ejecuta el cdigo que hayamos escrito en el evento click de esa opcin. Un detalle, puede que al pulsar el botn derecho en el textbox, no se muestre el men emergente, casi con toda seguridad tendrs que pulsarlo por segunda vez, si no recuerdo mal, esto no ocurra con el VB4...
346
no slo estn "mostradas", sino que si ejecutas el programa vers que estn operativas y que no es necesario hacer nada extra... simplemente pulsar esas combinaciones de teclas y se ejecutar el cdigo asociado a esa opcin del men.
Para comprobarlo, vamos a codificar la opcin de seleccionar todo: Muestra el form y selecciona el men edicin, pulsa en cualquiera de las opciones y se mostrar la ventana de cdigo, (tambin podras haberla seleccionado directamente, pero as parece que ibas a hacer otra cosa, je, je ), el cdigo en este evento ser el siguiente: Private Sub mnuEditor_Click(Index As Integer) ' Cuando se selecciona un elemento del men Edicin ' se entra en este evento, el ndice nos indicar ' el elemento seleccionado. ' Existen unas constantes para usarlas en lugar del nmero, ' por si aadimos o quitamos algunos Select Case Index Case cEdDeshacer ' Case cEdCortar ' Case cEdCopiar ' Case cEdPegar '
347
Case cEdSeleccionarTodo With txtEditor .SelStart = 0 .SelLength = Len(.Text) End With Case cEdBuscar ' Case cEdBuscarSig ' Case cEdReemplazar ' End Select End Sub Ejecuta la aplicacin y escribe varias lneas en el texbox, pulsa la tecla control y sin soltarla pulsa la tecla a, (esto como habrs observado en otras ocasiones, se simplifica diciendo: pulsa Ctrl+A), y ya est! todo el texto seleccionado!. Por supuesto que tambin puedes ir al men edicin y clickear en la opcin seleccionar todo. Te explico un poco el cdigo que hace que seleccione todo el texto: Le indicamos que la posicin de inicio del texto seleccionado sea la primera posicin: .SelStart = 0 Le indicamos que el texto seleccionado sea de igual longitud que todo el texto que hay escrito: .SelLength = Len(.Text) Si no hubisemos usado el With txtEditor... End With, tendramos que haber especificado el nombre del objeto, el cdigo sera este otro: ' Case cEdSeleccionarTodo txtEditor.SelStart = 0 txtEditor.SelLength = Len(txtEditor)
Ahora vamos a codificar un par de opciones del men edicin, lo vamos a hacer con cdigo en Visual Basic; de eso se trata no?, aunque podramos apoyarnos en llamadas al API de Windows, es decir: usar funciones propias del Windows para hacerlo, pero por ahora lo vamos a dejar, entre otras cosas para que sepas como funciona y sobre todo como se accede al portapapeles, (ClipBoard), desde el Visual Basic.
Las opciones que estarn relacionadas con el portapapeles son: Cortar, Copiar y Pegar; antes de codificar estas opciones en el evento mnuEditor_Click, veamos cmo manipular el clipboard. Los mtodos que vamos a usar de este objeto son: Clear para borrar el contenido del portapapeles GetText para recuperar el texto que haya SetText para asignar un texto GetFormat, para saber si el tipo de formato est disponible en el portapapeles los tipos de formatos se averiguan con las constantes predefinidas en el VB5: vbCFLink vnculo DDE vbCFText formato texto, el que a nosotros nos interesa vbCFBitmat formato bmp vbCFMetafile formato wmf (metafile) vbCFDib mapa de bits independientes del dispositivo, habitualmente todos los grficos soportan este formato vbCFPalette paleta de colores vbCFRtf formato RTF (rich text) GetData obtener los datos del portapapeles SetData Asignar datos al portapapeles Por ahora vamos a trabajar con los cuatro primeros mtodos: Con GetFormat, sabremos si hay algn texto en el portapapeles, en caso de que as sea, al mostrar el men, habilitaremos la opcin Pegar y si no hay texto la deshabilitaremos. De igual manera habilitaremos o no las opciones de Cortar y Copiar, pero en este caso comprobaremos si hay texto seleccionado Dnde se hace esto? Todas estas comprobaciones se hacen en el evento click del men edicin, (del que "cuelgan" las otras opciones), ya que este evento se dispara cuando pulsamos en l y es entonces cuando de muestran las opciones. Private Sub mnuEdit_Click() ' Por defecto, las opciones estn deshabilitadas mnuEditor(cEdCortar).Enabled = False
349
mnuEditor(cEdCopiar).Enabled = False mnuEditor(cEdPegar).Enabled = False ' Comprobamos si hay texto en el portapapeles If Clipboard.GetFormat(vbCFText) Then ' Hay texto, habilitamos la opcin de pegar mnuEditor(cEdPegar).Enabled = True End If ' Si hay texto seleccionado, habilitamos Cortar y Copiar If txtEditor.SelLength Then mnuEditor(cEdCortar).Enabled = True mnuEditor(cEdCopiar).Enabled = True End If End Sub Cuando muestres el men de edicin, seguramente "notars" cmo los item cambian de estado pero eso es lo menos importante, ya que lo que interesa es que estn habilitados los que tengan que estarlo. Ahora que hemos habilitado y/o deshabilitado las distintas opciones de edicin, vamos a usarlas. Segn vimos, el men de edicin est en un array y por tanto comparten el mismo nombre de men y segn el ndice, har referencia a uno u otro, para acceder a las distintas opciones, usamos constantes... ' '... Case cEdCortar ' Copiamos el texto seleccionado en el portapapeles Clipboard.SetText txtEditor.SelText ' y lo quitamos del textbox txtEditor.SelText = "" Case cEdCopiar ' Simplemenente copiamos el texto seleccionado en el portapapeles Clipboard.SetText txtEditor.SelText Case cEdPegar ' Ponemos en el textbox el texto que haya en el portapapeles txtEditor.SelText = Clipboard.GetText()
350
'... Para el caso de deshacer, podemos usar el API de Windows, realmente para todas estas opciones tambin, e incluso no necesitamos ni siquiera codificar nada, ya que el propio Windows se encarga de hacerlo pero lo pongo aqu para que sepas hacerlo ya que puedes aadir otras opciones, como buscar, reemplazar, etc.
Y para el resto de las opciones que hemos visto sera de esta forma: Private Const WM_CUT = &H300 Private Const WM_COPY = &H301 Private Const WM_PASTE = &H302 Private Const WM_CLEAR = &H303 Private Const WM_UNDO = &H304 Para saber si se puede deshacer:
351
If SendMessage(txtEditor.hWnd, EM_CANUNDO, 0&, ByVal 0&) Then mnuEditor(cEdDeshacer).Enabled = True End If Fjate que el ltimo parmetro tiene "antepuesto" al valor CERO la palabra ByVal, esto es porque en la declaracin de la funcin se ha especificado como As Any. Para deshacer, tambin usamos la misma funcin del API, pero en este caso le indicamos que "deshaga": ' Case cEdDeshacer Call SendMessage(txtEditor.hWnd, WM_UNDO, 0, ByVal 0&)
Usar el API para Cortar, Copiar y Pegar: Para los casos de Cortar, Copiar y Pegar, se usaran los "mensajes" apropiados, es decir: WM_CUT, WM_COPY y WM_PASTE. En todos los casos, se usa el hWnd del txtEditor para indicarle a la funcin de Windows, que debe operar sobre esa "ventana", por tanto todas las operaciones se realizarn en la ventana de la cual indicamos el "manejador" de ventanas (handle), que el VB nos proporciona mediante la propiedad de slo lectura hWnd. Todos los controles que "actan" como ventanas, tienen la propiedad hWnd. Como puedes comprobar no tiene nada extrao ni raro, es decir que esto del API de Windows es como todo: para saber lo que hay que hacer hay que saber cmo hacerlo y esa es la intencin de este cursillo, intentar ensearte a hacer cosas con el Visual Basic... e incluso con cosas que no es Visual Basic propiamente dicho... Vamos a ponerlo todo junto y veamos el cdigo que tenemos hasta ahora en el formulario: ' '----------------------------------------------------------------------------' Editor para el curso bsico (19/Oct/98) ' Entrega 29, publicada el 25/Abr/99 ' Entrega 30, publicada el 02/May/99 ' ' Guillermo 'guille' Som, 1998-99 '----------------------------------------------------------------------------Option Explicit
352
' Constantes para el men de Edicin. ' Los valores se corresponden con el ndice de mnuEditor Const cEdDeshacer = 0 Const cEdCortar = 2 Const cEdCopiar = 3 Const cEdPegar = 4 Const cEdSeleccionarTodo = 6 Const cEdBuscar = 8 Const cEdBuscarSig = 9 Const cEdReemplazar = 10 ' Funcin del API de Windows de 32 bits de mltiple uso Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _ (ByVal hWnd As Long, ByVal wMsg As Long, _ ByVal wParam As Long, lParam As Any) As Long ' Constantes para saber si se puede deshacer y deshacer Private Const EM_CANUNDO = &HC6 Private Const EM_UNDO = &HC7 ' Las constantes para cortar, copiar, pegar, etc Private Const WM_CUT = &H300 Private Const WM_COPY = &H301 Private Const WM_PASTE = &H302 Private Const WM_CLEAR = &H303 Private Const WM_UNDO = &H304 Private Sub Form_Resize() '-----------------------------------------------------------------'NOTA: ' ' ScaleWidth y ScaleHeight devuelven el tamao "interno" del form o control.
' Width y Height devuelven el tamao externo del form o control. '------------------------------------------------------------------
353
' Slo cuando no est minimizado el formulario If WindowState <> vbMinimized Then ' Si esto no funciona, que de seguro no funcionar, ' tendrs que ponerlo en el picStatus_Resize 'lblStatus.Width = picStatus.ScaleWidth - 60 ' Ajustar el tamao del TextBox 'txtEditor.Width = ScaleWidth ' Ajustar el alto, hay que tener en cuenta el alto ' del picture 'txtEditor.Height = ScaleHeight - picStatus.Height ' Posicionar el textBox en la parte superior izquierda 'txtEditor.Move 0, 0 ' Esto es lo mismo que lo anterior txtEditor.Move 0, 0, ScaleWidth, ScaleHeight picStatus.Height End If End Sub Private Sub mnuEdit_Click() ' Por defecto, las opciones estn deshabilitadas mnuEditor(cEdDeshacer).Enabled = False mnuEditor(cEdCortar).Enabled = False mnuEditor(cEdCopiar).Enabled = False mnuEditor(cEdPegar).Enabled = False ' Para saber si se puede deshacer: If SendMessage(txtEditor.hWnd, EM_CANUNDO, 0&, ByVal 0&) Then mnuEditor(cEdDeshacer).Enabled = True End If ' Comprobamos si hay texto en el portapapeles If Clipboard.GetFormat(vbCFText) Then ' Hay texto, habilitamos la opcin de pegar mnuEditor(cEdPegar).Enabled = True End If ' Si hay texto seleccionado, habilitamos Cortar y Copiar
354
If txtEditor.SelLength Then mnuEditor(cEdCortar).Enabled = True mnuEditor(cEdCopiar).Enabled = True End If End Sub Private Sub mnuEditor_Click(Index As Integer) ' Cuando se selecciona un elemento del men Edicin ' se entra en este evento, el ndice nos indicar ' el elemento seleccionado. ' Existen unas constantes para usarlas en lugar del nmero, ' por si aadimos o quitamos algunos Select Case Index Case cEdDeshacer Call SendMessage(txtEditor.hWnd, WM_UNDO, 0, ByVal 0&) Case cEdCortar ' Copiamos el texto seleccionado en el portapapeles Clipboard.SetText txtEditor.SelText ' y lo quitamos del textbox txtEditor.SelText = "" ' Si usamos el API: 'Call SendMessage(txtEditor.hWnd, WM_CUT, 0, ByVal 0&) Case cEdCopiar ' Simplemenente copiamos el texto seleccionado en el portapapeles Clipboard.SetText txtEditor.SelText ' Si usamos el API: 'Call SendMessage(txtEditor.hWnd, WM_COPY, 0, ByVal 0&) Case cEdPegar ' Ponemos en el textbox el texto que haya en el portapapeles txtEditor.SelText = Clipboard.GetText() ' Si usamos el API: 'Call SendMessage(txtEditor.hWnd, WM_PASTE, 0, ByVal 0&)
355
Case cEdSeleccionarTodo txtEditor.SelLength = txtEditor.SelLength = Len(txtEditor) With txtEditor .SelStart = 0 .SelLength = Len(.Text) End With End Select End Sub Private Sub mnuFicSalir_Click() ' Terminar el programa Unload Me End Sub Private Sub picStatus_Resize() ' Slo cuando no est minimizado el formulario If WindowState <> vbMinimized Then ' Aqu se ajustar el tamao del label ' cuando cambie el del picStatus lblStatus.Width = picStatus.ScaleWidth - 60 End If End Sub Private Sub txtEditor_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) ' Si pulsamos el botn derecho... (vbRightButton = 2) If Button = vbRightButton Then ' Mostrar el men de Edicin PopupMenu mnuEdit ' Si queremos mostrar en negrita uno de los mens, ' lo indicaremos en el quinto parmetro 'PopupMenu mnuEdit, , , , mnuEditor(cEdCopiar) End If End Sub Ya slo nos queda codificar las opciones de Abrir y Guardar, que veremos en la siguiente entrega y algunas opciones como buscar, reemplazar, que no se si veremos en la siguiente o en otra... Nos vemos Guillermo
356
Pero vamos a aadir otra: Mezclar Esta opcin insrtala entre abrir y guardar, el nombre del men es mnuFicMezclar y la letra M es la que debe estar subrayada (escribe &Mezclar en el caption) Para que sirve mezclar? Para intercalar otro fichero en el texto actual; ahora vamos a ver qu necesitamos para escribir el cdigo de estas opciones. Cuando se pulsa Abrir, (o en cualquiera de las opciones en las que se necesite el nombre de un fichero), lo que el usuario espera es que se le pregunte el fichero a editar. Se puede hacer de forma simple, usando el InputBox o un formulario de nuestra cosecha que simplemente pregunte un nombre de fichero, pero eso no es lo que estamos acostumbrados a ver lo que necesitamos es poder mostrar un:
357
dilogo con los controles que tenemos registrados en el sistema, selecciona: Microsoft Common Dialog Control (ComDlg32.ocx, control de dilogos comunes) y pulsa aceptar. En la barra de herramientas, (la que est a la izquierda), vers que hay un icono como este: Muestra el formulario y haz doble clic en ese icono, y se insertar en el formulario, desde este momento ya est disponible, el nombre por defecto de este control es: CommonDialog1, por tanto para acceder a las propiedades y mtodos tendremos que usar, como es costumbre en todos los controles, ese nombre seguido de un punto y despus la propiedad o mtodo. Por ejemplo para abrir: With CommonDialog1 .DialogTitle = "Seleccionar un fichero para abrirlo" .ShowOpen NombreFichero = .FileName End With Ahora que "sabemos" cmo se usa este control, vamos a usarlo "correctamente" y no es porque lo otra forma no sea correcta, que lo es, me refiero a que nos sea "til" en nuestro proyecto. Bien, estamos dispuestos a entrar de lleno en lo que es la codificacin del programa; ya hemos visto otros "listados", pero este es algo ms complicado... aunque no tiene porqu serlo... Revisemos lo que tenemos y, sobre todo, que utilidad tendr todo este tinglado, s que despus de dos entregas ya se tendra que saber pero... -La intencin es crear un formulario con una serie de mens, una caja de texto en la que poder escribir y poder manipular el texto escrito, pudiendo guardarlo en el fichero de texto y permitirnos asignar el texto contenido en cualquier otro fichero, adems de realizar ciertas tareas de "edicin" en el texto contenido en esa caja de texto. Pero nos falta un pequeo detalle: cmo saber el nombre del fichero con el que queremos "nombrar" lo que hemos escrito o como saber el nombre del fichero que usamos para asignarlo al textbox, es decir necesitamos una variable para guardar un nombre de fichero. Ahora hay otro pequeo detalle: dnde necesitamos usarla? Me explico: necesitamos una variable local a un solo procedimiento? o necesitamos una variable que pueda ser "vista" en todos los procedimientos? Recuerdas lo dicho en la entrega 26? Creo que nuestra variable para almacenar el nombre del fichero debe tener una "cobertura" a nivel de mdulo, ya que se usar tanto en los procedimientos de abrir como en los de guardar. Por tanto declrala en la seccin general del formulario:
358
Private NombreFichero As String Y ahora te tendra que seguir mostrando cdigo, pero, creo que parte de lo que hay que hacer ya lo hemos hecho... voy a comprobarlo y te lo digo... vuelvo enseguida! Si se ha hecho, en la entrega quince para ms seas, as que solo te dir "lo nuevo", pero el resto tendrs que hacerlo por tu cuenta, aunque te dir que es lo que debes hacer y dnde; de esta forma tendrs ejercicios obligatorios... si no los resuelves, no tienes programa, je, je. Antes de ver el cdigo de este mini-editor, vamos a ver un nuevo sistema de nombrar constantes: Enum Nota: esto slo existe en la versin 5 o superior. La enumeracin se usa para constantes de forma correlativa, aunque eso puede cambiarse. Por ejemplo: Enum eEdicion Deshacer Sep1 Cortar Copiar End Enum Segn esto, Deshacer vale 0, Sep1=1, Cortar=2, etc. Osea el primer nombre de constante valdr cero y las dems irn incrementando el valor de uno en uno. Si no queremos que se comporte de esta forma, es decir que empiece con cero, podemos asignar el valor que queramos: Enum ePrueba Primero = -1 Segundo Tercero End Enum Ahora los valores son: Primero= -1, Segundo= 0, Tercero= 1 Tambin podemos asignar los que queramos:
359
Enum ePrueba2 Uno = 1 Dos Tres Nulo = 0 Cuatro = 4 Cinco End Enum Fjate que al indicarle Uno=1, se empieza por uno y el siguiente valor ser dos, etc., hasta que volvamos a asignar un nuevo valor, en este caso Nulo=0, Cuatro vale 4 y los siguientes, si no se le indica lo contrario, van aumentando de uno en uno. Otra cosa que se suele hacer con esto de las enumeraciones, siempre que los valores asignados sean correlativos, es indicar cual es el valor menor y cual el mayor; as se pueden usar en ciertos bucles y para ciertas comprobaciones en las que necesitemos que un valor determinado est entre los dos valores indicados: Enum eEdicion edPrimero edDeshacer = edPrimero edSep1 edCortar edCopiar edPegar edSep2 edSeleccionarTodo edSep3 edBuscar edBuscarSig edReemplazar edUltimo = edReemplazar End Enum En este caso, se asignan dos constantes: edPrimero y edUltimo para indicar los valores mnimo y mximo del rango de las constantes. Recuerda que cada vez que se asigna un valor a una constante de una enumeracin, si en la siguiente no se le indica explcitamente un valor, se incrementa el valor anterior en uno. Por tanto, edPrimero y edDeshacer valdrn cero, edSep1 vale 1, edCortar vale dos y as sucesivamente. Otra cosa a saber sobre las enumeraciones, es que los valores siempre son numricos del tipo long. '5 '2 '3
360
Cuando creamos una enumeracin, estamos creando un nuevo tipo de variable... o casi, lo importante es que podemos crear variables de la enumeracin: Dim miVarEnum As ePrueba Al asignar un valor a una variable declarada de esta forma, el Visual Basic nos indicar los nombres (o valores posibles) Por ejemplo:
es decir, nos muestra una lista despegable con los valores posibles. Tambin podemos usar esos nombres de forma independiente, sin necesidad de crear una variable especial: Dim x As Long x = Primero Es decir, se asignar a la variable x lo que vale la constante Primero (-1) Un dato importante: no deberas usar el mismo nombre de constante en diferentes enumeraciones, ya que el Visual Basic podra confundirse... aunque te avisar de que hay un "conflicto". Si tenemos estas dos enumeraciones: Enum ePrueba Primero = -1 Segundo Tercero End Enum Enum ePrueba1 Primero = -1 Otro = Primero Siguiente Ultimo = Siguiente End Enum
361
Y asignamos x = Primero, nuestro querido Visual Basic nos dir que se ha detectado un nombre ambiguo, tal como nos muestra este mensaje:
Para solucionarlo podemos hacer dos cosas: ---Usar nombres diferentes en las constantes enumeradas, (lo ms recomendable). ---Indicar el nombre de la enumeracin: x = ePrueba1.Primero Para terminar esto, decirte que puedes usar nombres de constantes con espacios, aunque esto es algo que est bien cuando creamos "controles", para que los nombres de las constantes de las propiedades sean descriptivas: Enum ePrueba3 [Fecha Actual] [Fecha Anterior] End Enum Fjate en el uso de corchetes. Cuando se usan estos valores, el propio Visual Basic se encarga de asignarlo con los corchetes: unaFecha = [Fecha Actual] Y si los asignamos nosotros directamente, hay que poner el nombre de la "constante" dentro de los corchetes, sino el Visual Basic entender que son dos nombres diferentes sin ningn tipo de operacin de por medio... lo cual se convertir en un "bonito" error... El nico "defectillo" que tienen las constantes de las enumeraciones, es que los nombres usados no mantienen el estado de maysculas / minsculas, es decir que si declaramos Primero en un Enum y despus escribimos ese nombre en minsculas se cambiar la declaracin a minsculas: x = primero Pero an as, si tenemos Option Explicit podemos detectar valores no "creados"; aunque para mi gusto, se debera "respetar" el estado de maysculas/minsculas de la declaracin.
362
Un ltimo comentario y ya termino..., al igual que ocurre con los procedimientos en los que no se indica "explcitamente" que es privado, sern pblicos por defecto. As que, si no tienes intencin de hacer pblicas las enumeraciones, declralas como Private. Ahora veamos parte del cdigo nuevo, el resto ser el que se mostr en la entrega anterior. Los valores de las constantes para el men de edicin las vamos a tener en una enumeracin. Por tanto si la versin que ests usando es anterior a la 5, tendrs que declararlas como constantes normales, (tal y como se mostr en la entrega 30). En la parte de las declaraciones del formulario: ' Constantes para el men de Edicin. ' Los valores se corresponden con el ndice de mnuEditor Private Enum eEdicion cedPrimero cedDeshacer = cedPrimero cedSep1 cedCortar cedCopiar cedPegar cedSep2 cedSeleccionarTodo cedSep3 cedBuscar cedBuscarSig cedReemplazar cedUltimo = cedReemplazar End Enum Private NombreFichero As String Private Modificado As Boolean el texto ' Nombre del fichero ' Para indicar si se ha cambiado
Al iniciarse el formulario asignaremos los filtros para la seleccin del tipo de ficheros en el cuadro de dilogo. El filtro se asigna a la propiedad Filter del CommonDialog y el formato a usar ser: descripcin del tipo, el tipo, descripcin, tipo, etc. cada uno de estos "datos" estar separado por el signo | (ALT+124) o Alt Gr y 1. Private Sub Form_Load() ' Asignar el filtro para el dilogo comn
363
CommonDialog1.Filter = "Textos (*.txt)|*.txt|Todos (*.*)|*.*" End Sub Cada vez que escribamos algo en el textbox habr que indicarle a nuestro programa de que se ha cambiado, por tanto asignaremos True a la variable Modificado: Private Sub txtEditor_Change() Modificado = True End Sub Ahora veremos el cdigo de algunas de las opciones del men de ficheros, el resto lo veremos en la siguiente entrega, junto con todo el cdigo... Private Sub mnuFicAbrir_Click() ' Comprobar si el texto se ha modificado ' si es as, guardarlo o no, segn la respuesta del usuario ' ***ejercicio*** With CommonDialog1 .DialogTitle = "Seleccionar un fichero para abrilo" .FileName = NombreFichero .ShowOpen If Len(.FileName) Then NombreFichero = .FileName ' Abrir el fichero y asignarlo al textbox ' ***ejercicio*** Modificado = False End If End With End Sub
Private Sub mnuFicGuardar_Click() ' Si no se ha asignado el nombre al fichero, preguntar por l If Len(NombreFichero) = 0 Then mnuFicGuardarComo_Click
364
Private Sub mnuFicGuardarComo_Click() ' Preguntar el nombre del fichero y guardarlo With CommonDialog1 .DialogTitle = "Guardar el fichero" .FileName = NombreFichero .ShowSave If Len(.FileName) Then NombreFichero = .FileName GuardarFichero End If End With End Sub
Private Sub mnuFicMezclar_Click() ' Preguntar el nombre del fichero a mezclar With CommonDialog1 .DialogTitle = "Fichero a mezclar" .ShowOpen If Len(.FileName) Then ' Leer el fichero y guardarlo en una variable Dim nFic As Long Dim sMerge As String Dim sTmp1 As String, sTmp2 As String nFic = FreeFile Open .FileName For Input As nFic sMerge = input$(LOF(nFic), nFic) Close nFic ' Tomar lo que hay hasta la posicin del cursor With txtEditor
365
sTmp1 = Left$(.Text, .SelStart) sTmp2 = Mid$(.Text, .SelStart + 1) .Text = sTmp1 & sMerge & vbCrLf & sTmp2 End With End If End With End Sub
Private Sub mnuFicNuevo_Click() ' Comprobar si se ha modificado el fichero actual ' ***ejercicio*** txtEditor = "" Modificado = False End Sub
Private Sub GuardarFichero() ' Guardar el contenido del textbox en NombreFichero ' ***ejercicio*** Modificado = False End Sub Como te imaginars donde pone ***ejercicio*** es lo que tienes que hacer, ya sabes que en la entrega quince se hizo algo parecido. Por lo dems, nos queda la parte de: seleccionar impresora, imprimir, buscar y reemplazar, pero eso lo dejaremos para otra ocasin... aunque si no quieres esperar, psate por la seccin "Mis Utilidades" y busca la entrada que dice "Un procedimiento genrico para imprimir" En la prxima entrega tambin veremos algo sobre el control de errores... cosa que ser necesaria "controlar" para los casos en que se pulse "Cancelar" en el cuadro de dilogo de seleccin del nombre del fichero. As que, paciencia... y a esperar... si puedes... y si no puedes... pues eso... Hasta la prxima Nos vemos Guillermo
366
367
Veamos esto que te estoy contando en el cdigo de abrir y de camino vemos la respuesta: ' Private Sub mnuFicAbrir_Click() ' Comprobar si el texto se ha modificado ' si es as, guardarlo o no, segn la respuesta del usuario Dim ret As Long If Modificado Then ret = MsgBox("El fichero se ha modificado, quieres guardarlo?", vbYesNoCancel) ' Si hemos contestado "Si" If ret = vbYes Then ' Guardarlo mnuFicGuardar_Click ' Si pulsamos el botn Cancelar, salimos del procedimiento ElseIf ret = vbCancel Then Exit Sub End If End If ' Usar deteccin de errores para saber si se ha pulsado en cancelar On Error Resume Next With CommonDialog1 ' Esto har que VB devuelva un error al pulsar Cancelar .CancelError = True ' .DialogTitle = "Seleccionar un fichero para abrilo" .FileName = NombreFichero .ShowOpen ' Si no se ha producido ningn error, ' es que NO se ha pulsado en Cancelar If Err.Number = 0 Then If Len(.FileName) Then NombreFichero = .FileName ' Abrir el fichero y asignarlo al textbox Dim nFic As Long
368
Dim sTmp As String nFic = FreeFile Open .FileName For Input As nFic sTmp = Input$(LOF(nFic), nFic) Close nFic ' Asignarlo al textbox txtEditor.Text = sTmp Modificado = False End If End If End With ' Es buena costumbre volver a ponerlo a cero... Err = 0 End Sub Como ves no tiene mayor problema comprobar si el contenido del fichero ha cambiado y preguntar si queremos guardarlo, etc. (me estoy refiriendo al ejercicio que haba al principio del procedimiento de abrir), en el procedimiento de Guardar se comprueba si hay algn nombre asignado y si no es as, se preguntar por ese nombre... de eso se encargan los procedimientos de Guardar y Guardar como... Para hacer la pregunta he usado un MsgBox, pero con tres opciones: Si, No y Cancelar... por si nos arrepentimos y no queremos guardar el contenido del textbox con el nombre por defecto, por ejemplo... En cuanto a la deteccin de errores... primero ponemos las instrucciones esas que vimos hace un rato... en el Cuadro de dilogos se asigna a True la propiedad .CancelError, con esto se le dice al Visual Basic que si se pulsa en Cancelar, produzca un error detectable, (el nmero 32755), por tanto, despus del .ShowOpen comprobamos si NO se ha producido un error, en ese caso quiere decir que se puede continuar, ya que si se produce un error, no hacemos nada y se contina con el resto del cdigo... que por cierto es ninguno, ya que el cdigo restante est dentro del If Then... salvo el volver a poner el nmero del objeto Err a cero, para que no se quede ningn nmero de error "colgado" y pueda interferir en otros procedimientos. Sigue este link y aprenders un poco ms sobre esto de la deteccin de errores.
En cuanto a la respuesta de cmo abrir el fichero y asignarlo al textbox, ya tenias la respuesta en la parte de mezclar... o casi, pero como ves es bastante simple y no hay que hacer nada del otro mundo. Simplemente abrimos el fichero, leemos el TOTAL del contenido del mismo y lo asignamos a una variable y despus lo asignamos al TextBox, aunque podramos haberlo asignado
369
directamente... pero es que tengo costumbre de usar variables intermedias... cosas... en fin... Fjate que despus de abrir el fichero se asigna el valor False a la variable Modificado, esto es para que se sepa que el contenido no se ha modificado despus de asignar el contenido al textbox... aunque creo que esto ya lo expliqu en otra ocasin... no? la verdad es que no me acuerdo, as que mejor son dos que ninguna... Veamos ahora:
Ahora veamos:
En esta ocasin tambin usaremos el Cuadro de Dilogo pero, para saber cuantas copias quiere el usuario imprimir, si la quiere en horizontal o vertical, etc., etc. (aunque esto ltimo se suele hacer al configurar la impresora, no al imprimir). ' Private Sub mnuFicImpImp_Click() ' Imprimir el contenido del TextBox en la impresora (23/Ene/00) On Error Resume Next ' Averiguamos cuantas copias quiere el usuario y dejamos que elija otras cosas, ' el propio cuadro de dilogo nos lo permitir hacer... With CommonDialog1 .CancelError = True .DialogTitle = "Imprimir" ' cdlPDHidePrintToFile archivo ' cdlPDNoPageNums etc. ' CdlPDUseDevModeCopies Dejar al SO que se encargue de imprimir las copias ' soporte, estar ' copias. .Flags = cdlPDHidePrintToFile Or cdlPDNoPageNums Or cdlPDUseDevModeCopies .ShowPrinter ' Si no se ha cancelado If Err = 0 Then imprimir. ' En la propiedad .Copies estar el nmero de copias a En caso de que la impresora no lo deshabilitada la opcin del nmero de No mostrar desde que pgina imprimir, No mostrar el botn de imprimir en un
' Hay casos en los que las impresoras "automticamente" usan ese valor, el nmero de ' por tanto si no queremos hacer un bucle para imprimir
' copias solicitadas, podemos dejar que sea el propio sistema el que ' se encargue de esa cuestin... ' El problema, cuando la impresora no permite imprimir varias copias
371
' Para simplificar, dejaremos que sea el propio O.S. el que se encargue ' ' Imprimimos el contenido del textbox... simple, verdad? Printer.Print "" Printer.Print txtEditor.Text Printer.EndDoc End If End With End Sub Tambin podramos hacerlo ms complicado, de forma que podamos controlar cada una de las lneas a imprimir... no es que tenga ninguna utilidad prctica, pero as sabes como controlar el contenido de cada una de las lneas de un TextBox Multiline, por ejemplo puedes usarlo para hacer que el texto est justificado, etc... aunque no se si esa parte la veremos en el curso bsico... ya veremos.
372
Dim L1 As Long, L2 As Long ' Constantes para usar con SendMessage Const EM_GETLINECOUNT = &HBA Const EM_LINEFROMCHAR = &HC9 Const EM_LINELENGTH = &HC1 ' Nmero de lneas del TextBox k = SendMessage(qControl.hWnd, EM_GETLINECOUNT, 0, 0&) Printer.Print "" For i = 0 To k - 1 ' Primer carcter de la lnea actual L1 = SendMessage(qControl.hWnd, EM_LINEINDEX, i, 0&) + 1 ' Longitud de la lnea actual L2 = SendMessage(qControl.hWnd, EM_LINELENGTH, L1, 0&) ' Imprimimos el trozo de texto que representa a una lnea Printer.Print Mid$(qControl.Text, L1, L2) Next ' Le indicamos que ya no hay ms que imprimir Printer.EndDoc End Sub Esto es todo, al menos por ahora, aunque an queda por hacer la parte de Buscar y Reemplazar, pero eso lo dejaremos para otra ocasin, ya que si no se iba a alargar ms de la cuenta el tema y no es plan, como adelanto te dir que seguramente, lo programaremos usando clases, que ya va siendo hora de que entremos de lleno en ese mundo tan desconocido para unos y casi tan mgico para otros... o casi... an as, espero que no tengas queja... ya que esta entrega ha valido por dos... o ms... ahora, a esperar a la siguiente... paciencia, paciencia... Nos vemos Guillermo
Cuando quieras que el Visual Basic "ignore" los errores que se produzcan en tu aplicacin o en parte de ella, usa: On Error Resume Next Esto har que si se produce un error, se contine ejecutando el cdigo como si nada hubiese ocurrido. Por supuesto que la recomendacin es que compruebes si se ha producido un error, ya que no es bueno dejar que los errores ocurran sin ms. Para ello tendrs que chequear el valor de la propiedad Number del objeto Err, (que al ser la propiedad por defecto no es necesario especificarla), si ese valor es cero quiere decir que no se ha producido un error; veamos un ejemplo: On Error Resume Next ' Error 13 producir un error de tipos (Type Mismatch) Error 13 If Err.Number Then MsgBox "Se ha producido el siguiente error:" & vbCrLf & _ Err.Number & ", " & Err.Description End If Pero si haces esto, procura hacer un poco de limpieza... ya que, si desde este procedimiento llamas a otros procedimientos que a su vez tienen la instruccin On Error Resume Next y no has "limpiado" el valor del nmero del error... cualquier comprobacin que hagas de ese valor dar como resultado que se muestre el mensaje. Veamos un par de ejemplos: Para crear el programa de pueba, crea un nuevo proyecto, aade tresd botones (Command1, Command2 y Command3), y pega este cdigo: Private Sub Command1_Click() ' Ejemplo para detectar errores en Visual Basic Dim i As Integer On Error Resume Next i = MsgBox("Pulsa SI para producir un error en este evento," & vbCrLf & _ "pulsa en NO para llamar al procedimiento Command2_Click" & vbCrLf & _ "pulsa en Cancelar para llamar al procedimiento Command3_Click", vbYesNoCancel)
374
If i = vbYes Then ' Error 13 producir un error de tipos (Type Mismatch) Error 13 ElseIf i = vbNo Then ' El error producido en el procedimiento Command2 est controlado, ' por tanto no se mostrar el mensaje del final Command2_Click Else ' Esto producir un error en Command3, pero se detectar aqu Command3_Click End If If Err Then MsgBox "Se ha producido el siguiente error:" & vbCrLf & _ Command1_Click" End If End Sub Err.Number & ", " & Err.Description, , "En
Private Sub Command2_Click() On Error Resume Next ' Error 76, (Path not found) Error 76 If Err Then ' Este error est comprobado dentro de este procedimiento, por tanto no mostrar nada End If ' Limpiamos el valor del error Err = 0 End Sub
375
' Este procedimiento produce un error nmero 5 Error 5 ' Este mensaje NUNCA se mostrar MsgBox "El valor de Err.Number es: " & Err.Number & vbCrLf & _ "Aqu no se notar que se ha producido un error..." & vbCrLf, , "En Command3_Click" End Sub Veamos que es lo que hace este cdigo y porqu. Cuando pulses en el Command1 te mostrar un mensaje pidiendote que selecciones el tipo de prueba que quieres hacer, para probar cada una de ellas, tendrs que pulsar varias veces en ese botn, una para cada una de las tres posibilidades. Si pulsas en "SI", el error se producir en este mismo evento y el mensaje del final nos indicar que se ha producido el error nmero 13. Cuando pulses en "NO", se llamar al procedimiento Command2_Click en el que se produce un error 76, pero que el propio procedimiento se encarga de gestionar y "limpiar", por tanto, no ocurrir, al menos aparentemente, nada. Por ltimo, al pulsar en "Cancelar", se llama al procedimiento Command3_Click, el cual produce el error 5, pero no detecta los errores; pero como el Visual Basic "sabe" que an hay una rutina "interceptadora" de errores en funcionamiento, la del Command1, deja de ejecutar el cdigo errneo y vuelve a la siguiente instruccin que haya en el procedimiento Command1... Despus de estas tres pruebas, pulsa en el Command2. Nada ocurre, ya que el cdigo detecta los posibles errores. Cuando pulses en el Command3, vers que el Visual Basic se detiene mostrandonos una ventana de error, esto ocurre porque no hay ninguna rutina de deteccin de errores en funcionamiento y cuando no la hay... el Visual Basic nos muestra la suya propia y detiene el programa. Ahora cambia el cdigo del Command3_Click por este otro: ' Private Sub Command3_Click() On Error Resume Next ' Este procedimiento produce un error nmero 5
376
Error 5 ' Ahora si que se mostrar este mensaje MsgBox "El valor de Err.Number es: " & Err.Number & vbCrLf & _ "Aqu no se notar que se ha producido un error..." & vbCrLf, , "En Command3_Click" End Sub Como vers, al no "limpiar" el valor de la propiedad Err.Number, el valor se mantiene; y a pesar de que se haya detectado el error en ese evento, al volver de nuevo al cdigo del Command1, se mostrar el mensaje de que hay error... y adems el mensaje que tenemos en el evento Command2_Click, el cual antes no se mostraba.
Resumiendo: Si detectas los errores con Resume Next, acostumbrate a dejar el valor de Err.Number a cero antes de que acabe y/o antes de salir del procedimiento. Recuerda que para salir de un procedimiento puedes usar Exit Sub, Exit Function o Exit Property. Tambin debes saber que, cuando acaba un procedimiento, la rutina que gestiona los errores tambin acaba, pero, como has podido comprobar, el valor del error permanece asignado.
377
' Private Sub Command4_Click() ' Ejemplo para detectar errores en Visual Basic Dim i As Integer On Error Goto HayError i = MsgBox("Pulsa SI para producir un error en este evento," & vbCrLf & _ "pulsa en NO para llamar al procedimiento Command5_Click", vbYesNo) If i = vbYes Then ' Error 13 producir un error de tipos (Type Mismatch) Error 13 ElseIf i = vbNo Then ' Esto producir un error en Command5, pero se detectar aqu Command5_Click End If ' Es conveniente NO entrar en la rutina de deteccin de errores por "error" Exit Sub ' Etiqueta para cuando se produzca un error HayError: MsgBox "Se ha producido el siguiente error:" & vbCrLf & _ Err.Number & ", " & Err.Description, , "En Command4_Click" End Sub
Private Sub Command5_Click() ' En este procedimiento no hay rutina de deteccin de errores ' Este procedimiento produce un error nmero 5 Error 5 End Sub
Al pulsar en el Command4, se muestra un cuadro de dilogo, si pulsas "SI", se producir un error dentro de ese procedimiento y se "saltar" a la etiqueta indicada en On Error
378
Goto ..., en este caso HayError, y se mostrar el mensaje de que se ha producido un error... Si pulsas en "NO", se llamar al procedimiento Command5_Click, se producir un error y, como en ese procedimiento no hay rutina de deteccin de errores, el Visual Basic pasa a la anterior que hubiese activa, la de Command4_Click, y all es donde se muestra el mensaje de aviso. Pero si pulsas directamente en el Command5, vers que el error es detectado directamente por el Visual Basic, mostrndonos un mensaje y, en caso de que fuese un ejecutable, acabando la aplicacin. Esto es porque en el cdigo del procedimiento Command5_Click no hay ninguna rutina de deteccin de errores y tampoco hay ninguna otra rutina "pendiente" que controle errores, cosa que si es cierta cuando se llama al procedimiento Command5_Click desde el Command4_Click. Recuerda que esto slo ocurre cuando se llaman a procedimientos dentro de otros procedimientos. Fjate del Exit Sub que hay antes de llegar a la etiqueta "HayError", esto es necesario hacerlo para que, en caso de que no se produzca un error, no se entre en la parte en la que se detectan los errores. Dentro de la parte en la que se detectan los errores, podemos usar instrucciones como: Resume Next, con esto tendramos algo parecido al On Error Resume Next, ya que cuando el VB se encuentra con un Resume Next, contina en la siguiente instruccin a la que produjo el error. Resume NmeroLnea, en esta ocasin se continuar con el cdigo que haya a partir de la etiqueta (o nmero de lnea) NmeroLnea... Si Visual Basic se encuentra con una instruccin Resume y no hay un error, mostrar una queja indicndones que se ha encontrado con un Resume sin que haya error...
379
' Controlamos los posibles errores On Error GoTo HayError2 ' no puede haber dos etiquetas con el mismo nombre ' Producimos un error... Mensaje = "Instruccin: Error 10" Error 10 ' Llamamos al procedimiento Command7_Click Mensaje = "Instruccin: Command7_Click" Command7_Click
Exit Sub HayError2: MsgBox "'" & Mensaje & "'" & vbCrLf & vbCrLf & _ "Se ha producido un error: " & vbCrLf & _ Err.Number & " - " & Err.Description ' Continuar por la siguiente instruccin Resume Next End Sub
Private Sub Command7_Click() ' On Error Resume Next ' Este mensaje de error ser ignorado, (por el On Error Resume Next anterior) Error 13 ' Dejamos de detectar errores On Error GoTo 0 ' A ver que pasa con este otro error Error 15
380
' Este cdigo nunca se ejecutar MsgBox "Un mensaje desde Command7_Click" & vbCrLf & "que nunca se mostrar" End Sub Un detalle: Si en un mismo mdulo vamos a usar varias veces On Error Goto NmeroLnea, no podemos usar el mismo nombre para la etiqueta... si no lo tenemos en cuenta, el Visual Basic nos lo recordar. El cdigo de Command6_Click no debera tener ningn problema, as que pasamos al del Command7_Click: Al principio tenemos un On Error Resume Next, por tanto cualquier error que se produzca ser ignorado... La prueba: producimos un Error 13 y no passa nada... Nos encontramos ahora con On Error Goto 0, esto dejar de "detectar" errores en este procedimiento, (este detalle es importante saberlo, ya que slo se dejan de detectar en este procedimiento... no en el resto... cosa que puede producirnos algn que otro quebradero de cabeza al pensar que se dejar de detectar errores y ser el propio Visual Basic el que se encargue de mostrarnos el error; esto se usa sobre todo cuando se est "debugueando" un programa, pero al final nos encontramos que haba una rutina "superior" en la que si que se detectan los errores y... en fin... que puede que nos liemos ms de lo que yo ya te estoy liando... lo dicho... si te aclaras con toda esta parrafada... me lo explicas!) Sigamos: tenemos que despus de dejar de detectar los errores, producimos un error: Error 15, pero como en este procedimiento ya no hay rutina de deteccin de errores, (recuerdas? la cancelamos con el On Error Goto 0), el Visual Basic "busca" alguna rutina activa y si la hay la enva a esa rutina, que en nuestro ejemplo est en el procedimiento Command6_Click. Por tanto el mensaje ese que hay al final del Command7_Click nunca se ejecutar... Ni siquiera si se pulsa directamente en el Command7, ya que al no existir ninguna rutina a un nivel superior, ser el propio Visual Basic el que se encargue de detener el programa al encontrarse con un error no detectado...
Una cosa ms: No hay ninguna forma de crear rutinas genricas para deteccin de errores... es decir no se puede crear un procedimiento genrico para detectar errores... al menos hasta la versin 6 del Visual Basic, en futuras versiones... puede ser que lo hagan... pero... eso ya se ver...
Confo que tengas claro todo esto de la deteccin de errores... si no es as... pues... pero para ti...
381
Aunque no es normal que con el calor que hace por estas latitudes en estas fechas me vuelva "trabajador", (seguramente ser por el "mono" de no haberme puesto delante del ordenador por culpa del "virus" ese que me atac hace unos meses), aqu te traigo una nueva entrega de este cursillo que va a ser ms largo (en fechas) que... no se me ocurre ahora ningn ejemplo, pero bueno, ya me entiendes, sobre todo teniendo en cuenta que lo empec en Abril del 97... cuanto tiempo ha pasado ya! Pero lo importante es que aqu estamos de nuevo con una otra entrega del cursillo bsico de Visual Basic, en este caso, vamos a acabar con lo que quedaba pendiente del editor que nos ha servido de ejemplo en las ltimas entregas. Lo que vamos a hacer en este caso es crear nuestro propio dilogo de buscar y reemplazar y tambin veremos el cdigo que habra que usar para realizar esas operaciones sobre el texto escrito.
Dilogo de Buscar y Reemplazar para el Editor Antes de empezar a ver el cdigo de este cuadro de dilogo, vamos a hacer unos pequeos cambios en el formulario del editor: -- Cambia los nombre del men de edicin de mnuEditor a mnuEdicion, (es que es ms lgico) -- El cdigo de mnuEdicion_Click debe quedar as: (despus veremos porqu) Private Sub mnuEdicion_Click(Index As Integer) '------------------------------------------------------------------------' Usando el cdigo del mdulo MgsDBR es ms cmodo (03/Jul/00) ' ya se encarga de todo... '------------------------------------------------------------------------' Set LineaEstado = lblStatus MgsDBR.menuEdicion Index
382
Si ya tuvisemos el cdigo que ahora veremos, eso sera todo lo que habra que hacer para que funcionasen todas las opciones del men de Edicin... fcil?, no, simple, ya que el cdigo simplemente est escrito en otro sitio... pero escrito est... que conste! y a mi me consta, que lo he escrito yo... je, je.
-- El cdigo de comprobacin que hay en el evento mnuFicSalir_Click lo he pasado al del Form_QueryUnload, para que tambin se pregunte si se pulsa en el botn de cerrar el formulario, (la "x" que hay arriba a la derecha) Por tanto esos dos eventos quedaran as: Private Sub mnuFicSalir_Click() ' Terminar el programa ' ' La comprobacin de si hay que guardar el fichero est en el ' evento Form_QueryUnload, para que tambin sirva si se pulsa en la "x" ' del formulario. ' Unload Me End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer) ' Al terminar el programa, ' comprobar si se ha modificado el fichero... (22/Ene/00) ' ' Pero slo se debera comprobar si (03/Jul/00) ' se pulsa en el botn "x" del formulario ' o si se cierra por medio de cdigo, (con Unload) ' Dim ret As Long ' Slo si se cierra por medio de nuestro cdigo o por cerrar el formulario If UnloadMode = vbFormCode Or UnloadMode = vbFormControlMenu Then
383
If Modificado Then ret = MsgBox("El fichero se ha modificado, quieres guardarlo?", vbYesNoCancel) ' Si hemos contestado "Si" If ret = vbYes Then ' Guardarlo mnuFicGuardar_Click ' Si pulsamos el botn Cancelar, salimos del procedimiento ' y por tanto no terminamos el programa. ElseIf ret = vbCancel Then Exit Sub End If End If End If End Sub Veamos ahora ese cdigo, aunque antes, una imagen del aspecto del formulario (en tiempo de diseo) que nos servir para buscar y reemplazar, adems de para usarlo como un ImPutBox.
El formulario de Buscar y Reemplazar Para que este dilogo funcione, necesitamos, adems del propio formulario, el cdigo de un mdulo BAS, que es realmente el que hace casi todo el trabajo. Veamos primero el cdigo del formulario: '----------------------------------------------------------------------------' Form genrico para dilogo Buscar/Reemplazar (03/Jul/00) ' Se necesita el mdulo MgsDBR.bas ' ' Guillermo 'guille' Som, 1996-2000
384
Private Sub cmdCancel_Click() ActualizarCombo iFFAccion = cFFAc_Cancelar Unload Me End Sub Private Sub cmdFindNext_Click() ActualizarCombo sFFBuscar = txtFind.Text sFFPoner = "" iFFAccion = cFFAc_BuscarSiguiente Unload Me End Sub Private Sub cmdReplace_Click() ActualizarCombo sFFBuscar = txtFind.Text sFFPoner = txtReplace.Text If Len(sFFPoner) = 0 Then iFFAccion = cFFAc_Buscar Else iFFAccion = cFFAc_Reemplazar End If
385
Unload Me End Sub Private Sub cmdReplaceAll_Click() ActualizarCombo sFFBuscar = txtFind.Text sFFPoner = txtReplace.Text If Len(sFFPoner) = 0 Then iFFAccion = cFFAc_Buscar Else iFFAccion = cFFAc_ReemplazarTodo End If Unload Me End Sub Private Sub Combo1_Change(Index As Integer) Static YaEstoy As Boolean If bBuscandoEnCombo Then Exit Sub On Local Error Resume Next If Index = 0 Then txtFind = Combo1(0).Text Else txtReplace = Combo1(1).Text End If Err = 0 End Sub Private Sub Combo1_Click(Index As Integer) If bBuscandoEnCombo Then Exit Sub
386
If Combo1(Index).ListIndex Then Combo1(Index).Text = Combo1(Index).List(Combo1(Index).ListIndex) End If If Index = 0 Then txtFind = Combo1(Index).Text Else txtReplace = Combo1(Index).Text End If End Sub Private Sub Form_Load() ' Si no se ha especificado ningn nombre de fichero de configuracin If sFFIni = "" Then ' Asignar el nombre del fichero INI. ' ' Se podra hacer as: 'sFFIni = App.Path & "\BuscReemp.ini" ' pero si el programa es el directorio raiz, por ejemplo en C:, ' tendramos esto: 'C:\\BuscReemp.ini' y dara error ' ' Asi que nos creamos una funcin que devuelva el path pero sin ' la barra del final. sFFIni = AppPath & "\BuscReemp.ini" End If ' Posicionar en el centro de la ventana principal Move (Screen.Width - Width) \ 2, (Screen.Height - Height) \ 2 Combo1(0).Clear Combo1(1).Clear ' En un sub, para que acepte el tag de los combos. ' Si se dejaba en el Form_Load, no se actualizaban los valores de inicio 'IniciarCombo
387
Timer1.Interval = 100 Timer1.Enabled = True End Sub Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer) ' Si se cierra por el controlbox o ' cualquier forma distinta del propio cdigo, asumir que se ha cancelado. If UnloadMode <> vbFormCode Then iFFAccion = cFFAc_Cancelar End If End Sub Private Sub Form_Unload(Cancel As Integer) Dim n As Integer Dim vTmp As String Dim sTmp As String Dim i As Integer Dim j As Integer Dim sTag As String ' Si no se ha cancelado... If iFFAccion <> cFFAc_Cancelar Then ' Guardar el contenido de los combos en el fichero INI ActualizarCombo For i = 0 To 1 n = Combo1(i).ListCount sTag = Trim$(Combo1(i).Tag) If n > NumeroMaximoDeItems Then n = NumeroMaximoDeItems GuardarIni sFFIni, sTag, "NumEntradas", CStr(n) For j = 0 To n - 1 vTmp = "Entrada" & CStr(j) sTmp = Combo1(i).List(j) GuardarIni sFFIni, sTag, vTmp, sTmp Next Next End If
388
Set gsDBR = Nothing End Sub Private Sub ActualizarCombo() '----------------------------------------------------' Esta rutina actualiza el contenido de los dos combos, ' si la entrada en el Combo.Text no est, la incluye. ' Se podra usar la llamada al API de Windows. '----------------------------------------------------' Actualizar el contenido del Combo Dim sTmp As String Static k As Integer ' bBuscandoEnCombo = True For k = 0 To 1 sTmp = Combo1(k).Text If Len(Trim$(sTmp)) Then ' El valor devuelto no nos interesa Call ActualizarLista(sTmp, Combo1(k)) End If Next bBuscandoEnCombo = False End Sub Private Sub IniciarCombo() Dim j As Integer Dim i As Integer Dim n As Integer Dim vTmp As String Dim sTmp As String Dim sTag As String ' Asignar los valores anteriores del combo For i = 0 To 1 sTag = Trim$(Combo1(i).Tag) n = 0 n = LeerIni(sFFIni, sTag, "NumEntradas", n) If n > NumeroMaximoDeItems Then n = NumeroMaximoDeItems
389
' For j = 0 To n - 1 vTmp = "Entrada" & CStr(j) sTmp = LeerIni(sFFIni, sTag, vTmp, "") If Len(sTmp) Then Combo1(i).AddItem sTmp End If Next Next End Sub Private Sub Timer1_Timer() ' Asignar los valores anteriores del combo Timer1.Enabled = False ' IniciarCombo End Sub Private Function AppPath() As String ' Devolver el path actual sin la barra final de directorio ' ' Si el ltimo caracter es la barra de directorio, If Right$(App.Path, 1) = "\" Then ' devolver todos los caracteres menos el ltimo. AppPath = Left$(App.Path, Len(App.Path) - 1) Else ' sino, devolver el path normal AppPath = App.Path End If End Function ' Ya no necesitaremos ms este evento!!!
Ahora veamos el contenido del mdulo: gsDBR.bas: ' '----------------------------------------------------------------------------' gsDBR.bas Mdulo para el dilogo de Buscar y Reemplazar (03/Jul/00)
390
' ' (c)Guillermo 'guille' Som, 1997-2000 '----------------------------------------------------------------------------Option Explicit ' Control en el que se mostrar lo que el dilogo est haciendo ' Se tendr que usar con SET, por ejemplo: Set LineaEstado = Label1 Global LineaEstado As Control ' ' Variables y constantes globales (o pblicas) para buscar/reemplazar ' ' Constantes para el men de Edicin ' ' Es recomendable tener un men de edicin con estas opciones ' y en este mismo orden. ' Public Enum emnuEdicion mEdDeshacer = 0 mEdCortar = 1 mEdCopiar = 2 mEdPegar = 3 ' Const mEdSep1 = 4 mEdBuscarActual = 5 mEdBuscarSigActual = 6 mEdReemplazarActual = 7 ' Const mEdSep2 = 8 mEdSeleccionarTodo = 9 End Enum ' ' Global sFFBuscar As String textboxes) Global sFFPoner As String ' Global iFFAccion As Integer hemos hecho ' Indicar que es lo que ' para salir del dilogo, ' La cadena a buscar (de los ' La cadena a poner
391
' ver las siguientes constantes: ' ' Constantes para la accin a realizar Global Const cFFAc_Cancelar = True Global Const cFFAc_IDLE = 0 Global Const cFFAc_Buscar = 1 Global Const cFFAc_BuscarSiguiente = 2 Global Const cFFAc_Reemplazar = 3 Global Const cFFAc_ReemplazarTodo = 4 Global Const cFFAc_Aceptar = 5 ' Global sFFIni As String ' ' '--------------------------' Funciones Globales del API Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _ (ByVal hWnd As Long, ByVal wMsg As Long, _ ByVal wParam As Long, lParam As Any) As Long Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" _ (ByVal hWnd As Long, ByVal wMsg As Long, _ ByVal wParam As Long, ByVal lParam As Long) As Long ' Declaracin de las constantes, para usar con SendMessage/PostMessage Global Const WM_CUT = &H300 Global Const WM_COPY = &H301 Global Const WM_PASTE = &H302 ' Global Const EM_CANUNDO = &HC6 Global Const EM_UNDO = &HC7 '-------------------------------------------------' Profile.bas ' Autor: ' Fecha inicio: 24/Feb/97 04:05 (24/Feb/97) Guillermo Som Cerezo, 1997 ' Archivo de configuracin
392
' ' Mdulo genrico para las llamadas al API ' usando xxxPrivateProfileString '-------------------------------------------------' ' Declaraciones privadas para guardar y leer ficheros INIs Private Declare Function GetPrivateProfileString Lib "Kernel32.dll" Alias "GetPrivateProfileStringA" _ (ByVal lpApplicationName As String, ByVal lpKeyName As Any, _ ByVal lpDefault As String, ByVal lpReturnedString As String, _ ByVal nSize As Long, ByVal lpFileName As String) As Long Private Declare Function WritePrivateProfileString Lib "Kernel32.dll" Alias "WritePrivateProfileStringA" _ (ByVal lpApplicationName As String, ByVal lpKeyName As Any, _ ByVal lpString As Any, ByVal lpFileName As String) As Long
'--------------------------------------------------------------------------' Procedimiento equivalente a SaveSetting de VB. ' SaveSetting ' En VB/32bits usa el registro. En VB/16bits usa un archivo de texto.
' GuardarIni al usar las llamadas del API, siempre se escriben en archivos de texto. '--------------------------------------------------------------------------Public Sub GuardarIni(ByVal lpFileName As String, ByVal lpAppName As String, ByVal lpKeyName As String, ByVal lpString As String) ' Guarda los datos de configuracin ' Los parmetros son los mismos que en LeerIni ' Siendo lpString el valor a guardar ' Dim LTmp As Long LTmp = WritePrivateProfileString(lpAppName, lpKeyName, lpString, lpFileName) End Sub '---------------------------------------------------------------------------
393
' Funcin equivalente a GetSetting de VB. ' GetSetting ' En VB/32bits usa el registro. En VB/16bits usa un archivo de texto.
' LeerIni al usar las llamadas del API, siempre se escriben en archivos de texto. '--------------------------------------------------------------------------Public Function LeerIni(ByVal lpFileName As String, ByVal lpAppName As String, ByVal lpKeyName As String, Optional ByVal vDefault) As String 'Los parmetros son: 'lpFileName: 'lpAppName: 'lpKeyName: 'vDefault: ' ' Dim lpString As String Dim LTmp As Long Dim sRetVal As String 'Si no se especifica el valor por defecto, 'asignar incialmente una cadena vaca If IsMissing(vDefault) Then lpString = "" Else lpString = vDefault End If 'Longitud mxima permitida '(antes 255) sRetVal = String$(32367, 0) LTmp = GetPrivateProfileString(lpAppName, lpKeyName, lpString, sRetVal, Len(sRetVal), lpFileName) If LTmp = 0 Then LeerIni = lpString Else LeerIni = Left(sRetVal, LTmp) End If (25/Ene/98) La Aplicacin (fichero INI) La seccin que suele estar entrre corchetes Clave Valor opcional que devolver si no se encuentra la clave.
394
Public Function ActualizarLista(ByVal sTexto As String, cList As Control, Optional vTipoBusqueda, Optional vAddLista) As Long 'Esta funcin comprobar si el texto indicado existe en la lista 'Si no es as, lo aadir 'El valor devuelto, ser la posicin dentro de la lista -1 si hay "fallos" ' 'Para buscar en el List/combo usaremos una llamada al API '(si ya hay una forma de hacerlo, para que re-hacerla?) ' 'Constantes para los combos Const CB_FINDSTRINGEXACT = &H158 Const CB_FINDSTRING = &H14C Const CB_SELECTSTRING = &H14D 'Constantes para las Listas Const LB_FINDSTRINGEXACT = &H1A2 exactamente igual Const LB_FINDSTRING = &H18F de la cadena Const LB_SELECTSTRING = &H18C de la cadena ' Dim lTipoBusqueda As Long Dim bTipoBusqueda As Integer parte, 2=desde el principio Dim bAddLista As Boolean Dim L As Long 'Si se busca palabra completa o parcial, 'por defecto COMPLETA If IsMissing(vTipoBusqueda) Then bTipoBusqueda = False Else bTipoBusqueda = vTipoBusqueda End If 'Si se debe aadir o no, por defecto SI '0= Exacta, 1= cualquier 'Busca la cadena 'Busca en cualquier parte 'Busca desde el principio
395
If IsMissing(vAddLista) Then bAddLista = True Else bAddLista = vAddLista End If 'Si el control es un Combo If TypeOf cList Is ComboBox Then If bTipoBusqueda = 1 Then lTipoBusqueda = CB_FINDSTRING ElseIf bTipoBusqueda = 2 Then lTipoBusqueda = CB_SELECTSTRING Else lTipoBusqueda = CB_FINDSTRINGEXACT End If 'Si el control es un list ElseIf TypeOf cList Is ListBox Then If bTipoBusqueda = 1 Then lTipoBusqueda = LB_FINDSTRING ElseIf bTipoBusqueda = 2 Then lTipoBusqueda = LB_SELECTSTRING Else lTipoBusqueda = LB_FINDSTRINGEXACT End If Else 'no es un control List o Combo, salir ActualizarLista = -1 Exit Function End If If cList.ListCount = 0 Then 'Seguro que no est, as que aadirla, si viene al caso... L = -1 Else L = SendMessage(cList.hWnd, lTipoBusqueda, -1, ByVal sTexto) End If 'Si no est, aadirla
396
If L = -1 Then If bAddLista Then 'Con el 0 se aade al principio de la lista cList.AddItem sTexto, 0 L = ActualizarLista(sTexto, cList, bTipoBusqueda, bAddLista) End If End If ActualizarLista = L End Function Public Function gsReemplazar(sBuscar As String, sPoner As String, Optional vModo, Optional vCaption) As Integer 'Prepara el dilogo de Reemplazar Dim iModo As Integer Dim sCaption As String If IsMissing(vModo) Then iModo = cFFAc_Reemplazar Else iModo = vModo End If If IsMissing(vCaption) Then sCaption = "Reemplazar" Else sCaption = CStr(vCaption) End If iFFAccion = cFFAc_IDLE With gsDBR 'Por ahora no se muestra en reemplazar .Caption = sCaption .cmdFindNext.Default = False .cmdFindNext.Visible = False .cmdReplaceAll.Default = True .Combo1(0).Text = sBuscar .Combo1(1).Text = sPoner 'Mostrar el form y esperar a que se tome una accin ( 6/Sep/97)
397
.Show vbModal 'Do ' ' End With 'Devolver la cadena a reemplazar y buscar sBuscar = sFFBuscar sPoner = sFFPoner 'Si tanto buscar como poner estn en blanco, devolver cancelar If Len(Trim$(sBuscar)) = 0 Then If Len(Trim$(sPoner)) = 0 Then iFFAccion = cFFAc_Cancelar End If End If 'Devolver la accin gsReemplazar = iFFAccion End Function Public Function gsBuscar(sBuscar As String, Optional vModo, Optional vCaption) As Integer 'Prepara el dilogo para buscar Dim iModo As Integer Dim sCaption As String Dim bCompleta As Boolean Dim bAtras As Boolean If IsMissing(vModo) Then iModo = cFFAc_Buscar bCompleta = False bAtras = False End If 'Slo permitir buscar y buscar-siguiente Select Case iModo Case cFFAc_Buscar, cFFAc_BuscarSiguiente 'est bien, no hay nada que hacer Case Else iModo = cFFAc_Buscar End Select .Show DoEvents
398
If IsMissing(vCaption) Then sCaption = "Buscar" Else sCaption = CStr(vCaption) End If iFFAccion = cFFAc_IDLE With gsDBR .Caption = sCaption .cmdReplace.Visible = False .lblReplace.Visible = False .cmdReplaceAll.Visible = False .Combo1(1).Visible = False .Combo1(1).Enabled = False .cmdFindNext.Left = .cmdReplaceAll.Left If iModo = cFFAc_BuscarSiguiente Then .cmdFindNext.Caption = "Siguiente" DoEvents End If .Combo1(0).Text = sBuscar 'Mostrar el form y esperar a que se tome una accin .Show vbModal 'Do ' ' End With 'Devolver la cadena seleccionada/introducida sBuscar = sFFBuscar 'Devolver la accin gsBuscar = iFFAccion End Function .Show DoEvents
Public Sub gsPedirUnValor(ByVal spuvTitulo As String, _ ByVal spuvMensaje As String, _ ByVal spuvPregunta As String, _
399
ByRef spuvValor As String, _ ByVal spuvBoton As String) '------------------------------------------------------------------------' Rutina de propsito general para pedir un valor 23/May/96) ' ' Los parmetros son: ' ' ' ' ' ' spuvBoton spuvTitulo spuvMensaje spuvPregunta spuvValor El ttulo de la ventana El texto a mostrar como explicacin El texto con la pregunta a realizar El texto a mostrar en la caja de texto, tambin se usa para devolver la respuesta El texto a poner en el botn de aceptar (00.22
'------------------------------------------------------------------------With gsDBR .Caption = spuvTitulo .Combo1(0).Visible = False .lblBuscar.Width = .ScaleWidth - 120 .lblBuscar = spuvMensaje .Combo1(0).Visible = False .cmdReplace.Visible = False .cmdFindNext.Default = False .cmdFindNext.Visible = False .lblReplace = spuvPregunta .cmdReplaceAll.Default = True .cmdReplaceAll.Caption = spuvBoton If Len(Trim$(spuvValor)) Then .Combo1(1).Text = spuvValor Else If .Combo1(1).ListCount Then .Combo1(1).ListIndex = 0 End If End If .Show vbModal End With spuvValor = sFFPoner
400
End Sub
Private Sub AccionBuscar(Index As Integer) '------------------------------------------------------------------------' Procedimiento genrico para realizar bsquedas (31/Ago/97) ' ' Valores "externos" necesarios: ' ' mostrar ' ' ' ' Index El parmetro que apuntar a los ndices del men de edicin que deber tener estas ' opciones: ' ' ' ' ' ' ' ' ' ' ' ' ' Estas constantes estn declaradas en la enumeracin emnuEdicion ' '------------------------------------------------------------------------Static sBuscar As String Static lngUltimaPos As Long Dim lngPosActual As Long Deshacer Cortar Copiar Pegar ---(separador) Buscar Buscar Siguiente Reemplazar ---(separador) Seleccionar Todo Ctrl+A Ctrl+B o Ctrl+F F3 Ctrl+H Ctrl+Z Ctrl+X Ctrl+V Ctrl+P LineaEstado un control para mostrar mensajes temporales Hacer un set a una etiqueta en la que se el progreso de la bsqueda: Set LineaEstado = lblStatus
401
Dim sTmp As String Dim tText As TextBox 'Control On Error Resume Next Set tText = Screen.ActiveForm.ActiveControl ' Si no es un cuadro de texto, salir If Not (TypeOf tText Is TextBox) Then Err = 0 Exit Sub End If If LineaEstado Is Nothing Then ' Poner a cero el nmero de error, ya que esto nos dar ' la "pista" de que todo haya ido bien Err = 0 ' intentarlo con lblStatus, si no existe, salir... Set LineaEstado = Screen.ActiveForm.lblStatus ' Si se produce un error, es que no podemos usar "LinaEstado" If Err Then Err = 0 ' salir del procedimiento Exit Sub End If End If ' Guardar el valor mostrado, antes de entrar a esta rutina LineaEstado.Tag = LineaEstado ' para procesar las otras acciones adicionales Select Case Index Case mEdBuscarActual ' Si hay texto seleccionado... With tText If .SelLength > 0 Then sBuscar = Trim$(.SelText) End If End With ' Para "personalizar" la seccin de bsqueda... (15/Abr/97)
402
gsDBR.Combo1(0).Tag = "Buscar_" '& sUsuario If gsBuscar(sBuscar, , "Buscar en el campo actual") > cFFAc_IDLE Then sBuscar = Trim$(sBuscar) If Len(sBuscar) Then sBuscar & "..." LineaEstado = "Buscando en el campo actual " & DoEvents lngUltimaPos = 0& lngPosActual = InStr(tText, sBuscar) If lngPosActual Then lngUltimaPos = lngPosActual + 1 ' posicionarse en esa palabra: With tText .SelStart = lngPosActual - 1 .SelLength = Len(sBuscar) End With Else Beep MsgBox "No se ha hallado el texto buscado", vbOK + vbInformation, "Buscar en el campo actual" End If ' posicionarse en ese control tText.SetFocus End If End If Case mEdBuscarSigActual 'Si no hay nada hallado con anterioridad 'o no se ha procesado la ltima bsqueda en este control If Len(sBuscar) = 0 Or lngUltimaPos = 0& Then AccionBuscar mEdBuscarActual Else LineaEstado = "Buscando " & sBuscar & "..." DoEvents lngPosActual = InStr(lngUltimaPos, tText, sBuscar) If lngPosActual Then lngUltimaPos = lngPosActual + Len(sBuscar) 'posicionarse en esa palabra: With tText
403
.SelStart = lngPosActual - 1 .SelLength = Len(sBuscar) End With Else lngUltimaPos = 1& Beep MsgBox "No se ha hallado el texto buscado.", vbOK + vbInformation, "Buscar en el campo actual" End If ' posicionarse en ese control tText.SetFocus End If Case mEdReemplazarActual ' Si hay texto seleccionado... With tText If .SelLength > 0 Then sBuscar = Trim$(.SelText) End If End With sFFBuscar = sBuscar sFFPoner = "" ' Personalizar las secciones de buscar/reemplazar gsDBR.Combo1(0).Tag = "Buscar_" '& sUsuario gsDBR.Combo1(1).Tag = "Reemplazar_" '& sUsuario iFFAccion = gsReemplazar(sFFBuscar, sFFPoner, , "Reemplazar en el campo actual") If iFFAccion <> cFFAc_Cancelar Then Screen.ActiveForm.MousePointer = vbHourglass DoEvents sBuscar = Trim$(sFFBuscar) If Len(sFFBuscar) <> 0 And Len(sFFPoner) <> 0 Then If iFFAccion = cFFAc_Reemplazar Or iFFAccion = cFFAc_ReemplazarTodo Then LineaEstado = "Reemplazando " & sBuscar & "..." DoEvents lngUltimaPos = 0& lngPosActual = InStr(tText, sBuscar) If lngPosActual Then
404
lngUltimaPos = lngPosActual + Len(sBuscar) sTmp = tText sTmp = Left$(sTmp, lngPosActual - 1) & sFFPoner & Mid$(sTmp, lngPosActual + Len(sFFBuscar)) tText = sTmp ' Si slo es reemplazar uno... If iFFAccion = cFFAc_Reemplazar Then ' posicionarse en la palabra modificada: With tText .SelStart = lngPosActual - 1 .SelLength = Len(sFFPoner) End With ' Dejar el puntero del ratn como estaba Screen.ActiveForm.MousePointer = vbDefault ' Salir Exit Sub End If ' Cambiar todas las coincidencias en el msmo text lngUltimaPos = 1 Do lngPosActual = InStr(lngUltimaPos, sTmp, sFFBuscar) If lngPosActual Then lngUltimaPos = lngPosActual + 1 sTmp = Left$(sTmp, lngPosActual - 1) & sFFPoner & Mid$(sTmp, lngPosActual + Len(sFFBuscar)) tText = sTmp End If Loop While lngPosActual ' ' posicionarse en la ltima palabra modificada With tText .SelStart = lngUltimaPos - 2 .SelLength = Len(sFFPoner) End With
405
DoEvents Else Beep MsgBox "No se ha hallado el texto buscado.", vbOK + vbInformation, "Buscar en el campo actual" End If ' Si se ha reemplazado todo, no debe estar esta palabra... lngUltimaPos = 0& End If End If Screen.ActiveForm.MousePointer = vbDefault DoEvents End If Case mEdSeleccionarTodo With tText .SelStart = 0 .SelLength = Len(.Text) End With End Select LineaEstado = LineaEstado.Tag End Sub
Public Sub menuEdi() ' Habilitar las opciones disponibles Dim Habilitada As Boolean Dim i As Integer ' Dim elForm As Form ' Los separadores no se pueden deshabilitar!!! On Local Error Resume Next Set elForm = Screen.ActiveForm ' Asegurarnos que es un textbox If TypeOf Screen.ActiveForm.ActiveControl Is TextBox Then 'ok, todo bien...
406
Habilitada = True Else 'no poder hacer estas cosas Habilitada = False End If For i = mEdDeshacer To mEdSeleccionarTodo elForm!mnuEdicion(i).Enabled = Habilitada Next ' ' Algunos chequeos para las opciones de edicin: If Habilitada Then ' Si no se puede deshacer, no habilitarlo If SendMessage(Screen.ActiveForm.ActiveControl.hWnd, EM_CANUNDO, 0, ByVal 0&) Then elForm!mnuEdicion(mEdDeshacer).Enabled = True Else elForm!mnuEdicion(mEdDeshacer).Enabled = False End If ' Comprobar si hay algo que pegar... If Clipboard.GetFormat(vbCFText) Then elForm!mnuEdicion(mEdPegar).Enabled = True Else elForm!mnuEdicion(mEdPegar).Enabled = False End If ' Si hay texto seleccionado, habilitamos Cortar y Copiar If Screen.ActiveForm.ActiveControl.SelLength Then elForm!mnuEdicion(mEdCortar).Enabled = True elForm!mnuEdicion(mEdCopiar).Enabled = True Else elForm!mnuEdicion(mEdCortar).Enabled = False elForm!mnuEdicion(mEdCopiar).Enabled = False End If End If Err = 0 End Sub
407
Public Sub menuEdicion(Index As Integer) Dim sTmp As String Select Case Index Case mEdDeshacer '------------------------------------------------------------' IMPORTANTE: ' En ambos casos se podra usar SendMessage, ' pero en el caso de EM_CANUNDO, NO servira PostMessage, ' porque esta funcin slo devuelve un valor de ' si se ha puesto o no en la cola de mensajes de windows. '------------------------------------------------------------'Si se puede deshacer... If SendMessage(Screen.ActiveForm.ActiveControl.hWnd, EM_CANUNDO, 0, ByVal 0&) Then 'Deshacerlo! Call PostMessage(Screen.ActiveForm.ActiveControl.hWnd, EM_UNDO, 0, ByVal 0&) End If Case mEdCopiar Call PostMessage(Screen.ActiveForm.ActiveControl.hWnd, WM_COPY, 0, ByVal 0&) Case mEdCortar Call PostMessage(Screen.ActiveForm.ActiveControl.hWnd, WM_CUT, 0, ByVal 0&) Case mEdPegar Call PostMessage(Screen.ActiveForm.ActiveControl.hWnd, WM_PASTE, 0, ByVal 0&) Case mEdBuscarActual AccionBuscar mEdBuscarActual Case mEdBuscarSigActual AccionBuscar mEdBuscarSigActual Case mEdReemplazarActual AccionBuscar mEdReemplazarActual Case mEdSeleccionarTodo AccionBuscar mEdSeleccionarTodo End Select End Sub
408
Como te dije al principio, para usar el cuadro de dilogo, solamente hay que llamar al procedimiento menuEdicion con el ndice de la accin que queremos realizar, para el caso de Buscar sera un valor de 5 o usar la constante mEdBuscarActual. Pero esto est bien para buscar texto dentro del TextBox que tiene actualmente el foco, si quieres usarla para otras cosas, por ejemplo buscar en una base de datos, tendrs que crearte tu propio cdigo, (si tienes pereza, uedes esperar a que nos toque la parte de las bases de datos o echarle un vistazo a la cuarta entrega del Proyecto Paso a Paso que tengo en mis pginas, no es se usa este dilogo, pero te puede dar una idea). En el procedimiento AccionBuscar tienes la forma en que se puede llamar a este formulario para que muestre el cuadro de dilogo y usar los valores elegidos por el usuario.
Hasta aqu hemos llegado, a ver que preparo para la prxima entrega, ya que estoy un poco "liado" (entindase por liado: confundido, sin claridad mental... jo!), sobre que es lo que pondr, ya que no me decido entre "algo" bsico de tratamiento de bases de datos y empezar con el "escabroso" tema de los mdulos de clases (para crear objetos en Visual Basic). En fin... ya veremos que es lo que te encuentras. Mientras tanto, disfruta con lo que hay y espero que te sea provechoso. Nos vemos Guillermo
409
funcionarn, (o al menos deberan funcionar), perfectamente con las versiones 4.0 y superiores... confiemos en ello. Y ya, sin ms dilacin, empecemos con el acceso a bases de datos desde el Visual Basic, para ello usaremos como base de ejemplo la incluida en todas las versiones del VB: Biblio.mdb, si no la tienes, necesitars un poco de imaginacin, espero que eso no sea un problema. (Guille, aqu tendras que haber dicho algo como... "para una mente tan inteligente como la tuya", de esa forma, le daras coba al personal y se mosqueara menos contigo, por no tener la base de ejemplo, cuando vas a aprender?) Despus del comentario del "otro Guille", empecemos con:
410
arranque, por defecto en C. En el caso del VB4, no recuerdo ahora en que directorio se instalaba... Una vez seleccionada la base de datos, ya disponemos de una conexin, mediante el control Data a dicha base de datos. Pero, (como es habitual, siempre hay un pero), en casi todas las bases de datos suelen existir varias "tablas" que contienen datos. Para seleccionar una de las tablas, vuelve a mostrar la ventana de propiedades del control Data y selecciona la propiedad RecordSource, vers que en la cuadrcula de la derecha hay una lista desplegable, en ella se muestran las tablas disponibles, en nuestro ejemplo usaremos la de Autores, por tanto selecciona dicho elemento de la lista, puede que en lugar de llamarse Autores, (si la base de datos no est traducida), se llame Authors. Ahora si que tenemos configurado nuestro control Data para que muestre los datos almacenados en una tabla de una base de datos. Para continuar con nuestro ejemplo, vamos a aadir otros controles con los cuales poder mostrar la informacin contenida en dicha tabla de autores. Vamos a aadir tres etiquetas (Label) y tres cajas de texto (TextBox) En la barra de herramientas haz doble-click en el icono de las etiquetas: , (si posicionas el puntero del ratn sobre dicho "icono", vers que te muestra un ToolTip con el texto Label), se aadir un nuevo control al formulario llamado Label1, posiciona dicha etiqueta en la parte izquierda del formulario, debajo del control Data1 y vuelve a repetir la operacin dos veces ms, (posiciona cada una de las etiquetas debajo de la anterior), para obtener un total de tres etiquetas: Label1, Label2 y Label3 respectivamente. Para aadir las tres cajas de texto que necesitamos, repite la operacin, pero en este caso el control que debes elegir de la barra de herramientas es: (TextBox). Alinalos a la derecha de cada una de las etiquetas anteriores. Ahora tendremos tambin estos tres controles: Text1, Text2 y Text3, el aspecto del formulario sera el siguiente:
Vamos a cambiar el tamao de las etiquetas y las cajas de texto. Selecciona la primera etiqueta (Label1) y haz que el alto de la misma sea 315, esto puedes hacerlo de dos formas: 1.) Usando el el ratn, arrastra hacia arriba desde la parte inferior del control (al seleccionar se muestran unos cuadros que indican que puedes cambiar el tamao arrastrando en cualquiera de ellos, segn la posicin de dicho "cuadro" servir para el ancho, alto o ambos) 2.) Escribiendo dicho tamao en la ventana de propiedades, en este caso en la propiedad Height.
411
Para que las tres etiquetas tengan el mismo tamao, podemos hacerlo tambin de dos formas, para que se cambien de tamao todas las etiquetas a un mismo tiempo, en lugar de ir cambiado cada una de ellas por separado: 1.) Selecciona las tres etiquetas y escribe el tamao en la propiedad Height de la ventana de propiedades. 2.) Selecciona las etiquetas Label3, Label2 y por ltimo Label1, (manteniendo pulsado la tecla Ctrl y haciendo un simple Click con el ratn), abre el men Formato (Format) y selecciona la opcin Hacer del mismo tamao (Make same size), de dicho men, selecciona Altura (Height). En este procedimiento, es importante que el ltimo control seleccionado sea el que indique el tamao con el que queremos igualar el resto de los controles. Seguramente todo esto ya lo sabrs, pero... (de alguna forma hay que hacer que esta entrega sea ms larga, verdad Guille?) A continuacin vamos a cambiar el tamao de las cajas de texto: Selecciona Text1 y haz que el ancho sea: 2895. Selecciona los otros dos controles e igulalos en el ancho... imagnate cmo... El aspecto final ser este otro:
Una vez diseado el "aspecto" del formulario, (eres libre de adecuarlo a tus gustos particulares), vamos a indicarle al Visual Basic que nos muestre informacin de la tabla de autores en cada una de las cajas de texto: Selecciona las tres cajas de texto, pulsa en la ventana de propiedades y selecciona la propiedad DataSource, con esta propiedad indicamos que Data control queremos usar con cada caja de texto, (o con cualquier otro control que tenga la mencionada propiedad). En la lista desplegable, selecciona el nico elemento que hay: Data1 (o el nombre que le hayamos dado al control data). Ahora vamos a "enganchar" cada una de los textboxes con un registro de la mencionada tabla de autores: Selecciona el control Text1 y en la ventana de propiedades selecciona DataField, de la lista desplegable selecciona Au_ID. En los otros controles, selecciona Author para el Text2 y Year Born para el Text3. Ahora asignemo al caption de las etiquetas los datos que nos mostrar, asigna el Caption de cada una de las tres etiquetas, (ya sabes, pulsa en la etiqueta, muestra la ventana de propiedades y modifica la propiedad Caption), con estos valores: ID: para el Label1, Autor: para el Label2 y Ao nacimiento: para el Label3.
412
Y ya est!
Pulsa F5 para ejecutar el proyecto y vers que se muestra el primer registro, (seguramente en el ao de nacimiento Year Born, no te muestre nada hasta que llegues al registro 73.) Para mostrar el resto de registros, pulsa en los botones de "desplazamiento" del control data, dichos controles sirven para ir al: Primero, anterior, siguiente y ltimo respectivamente. Si escribes o modificas lo que se muestra, tambin se modificar en la base de datos. Fjate que no hemos usado ni una lnea de cdigo, entre otras cosas porque todo lo hemos realizado en tiempo de diseo. Pero ahora vamos a ver cmo hacer que todo esto funcione igual, pero en lugar de hacerlo en tiempo de diseo, lo haremos en tiempo de ejecucin, es decir: al ejecutar el proyecto, aunque la asignacin de la propiedad DataSource de las cajas de texto hay que hacerlo en tiempo de diseo, ya que no se puede hacer en tiempo de ejecucin . Para ello usaremos otro formulario, con los mismos controles que el actual, pero sin "conectarlos" a ninguna tabla de una base de datos ni nada de eso. Antes guarda el proyecto actual, yo lo he llamado Basico34.vbp, el formulario lo he guardado como fBasico34.frm Crea un nuevo proyecto, aade un formulario, en ste aade un Data control, tres etiquetas y tres cajas de texto, selecciona las tres cajas de texto y en la ventana de propiedades selecciona DataSource y asigna el Data1, adems de asignar el tamao, tanto de las cajas de texto como de las etiquetas. Ya deberas saber que cuando se ejecuta una aplicacin de Visual Basic y por defecto se muestra un formulario, al mostrarse dicho formulario, se ejecuta, (entre otros), el evento Form_Load, ser en este evento donde le indiquemos al Visual Basic que base de datos usaremos, que tabla y que campos. Veamos el cdigo, para poder introducir este cdigo, haz doble click en el formulario, por defecto se mostrar el evento Form_Load. ' Private Sub Form_Load() ' Indicarle el path de la base de datos ' ACUERDATE DE PONER EL PATH CORRECTO! Data1.DatabaseName = "C:\Program Files\Microsoft Visual Studio\VB98\BIBLIO.MDB" ' ' Indicarle que tabla queremos usar Data1.RecordSource = "Authors" ' ' Asignar a cada uno de los texboxes el campo de la tabla Text1.DataField = "Au_ID" Text2.DataField = "Author"
413
Pulsa F5 y vers que tambin funciona. Para ir abriendo boca de lo que seguir, vamos a aadir una caja de texto en la cual se podr escribir un nmero y al pulsar INTRO se mostrar el autor que tenga ese nmero como Au_ID, para ello aade una nueva caja de texto, no asignes el DataSource, ya que este texbox no estar ligado a la base de datos. La posicin y el tamao de esa caja de texto que he puesto es la siguiente: Left= 1500, Top= 120, Width = 615 y 315 de altura. El nombre es Text4. Lo que vamos a hacer es que cuando se pulse INTRO en ese control, se convertir el valor introducido a un nmero y se har una bsqueda en el contenido del Data1, aqu te muestro el cdigo: ' Private Sub Text4_KeyPress(KeyAscii As Integer) ' Se buscar slo cuando pulsemos INTRO Dim nReg As Long ' ' Comprobar si la tecla pulsada es Intro: vbKeyReturn o 13 que es lo mismo If KeyAscii = vbKeyReturn Then ' Esta asignacin evita que suene un BEEP KeyAscii = 0 ' Convertir el contenido de TextBox en un nmero nReg = Val(Text4) ' Buscar la primera coincidencia en el recordset del Data1 ' en el campo Au_ID Data1.Recordset.FindFirst "Au_ID = " & nReg End If End Sub
Para realizar la bsqueda he usado FindFirst, esto har que se muestre (o asigne como registro activo) el primer registro que coincida con lo indicado, en este caso el Au_ID que tenga como valor el nmero indicado en la variable nReg o sea el valor introducido en el Text4. En caso de que no se halle el valor buscado, no se alterar el registro actual, es decir "aparentemente" no pasar nada y todo se quedar tal y como estaba... o bien se pondr en el primer registro, creo que es esto ltimo lo que ocurre...
414
Ahora vamos a buscar en el campo Autor. A diferencia del campo Au_ID, que es numrico y slo buscamos una coincidencia "exacta", el campo Autor es del tipo String y en l podramos buscar una coincidencia exacta, al igual que en el caso del ID o bien "algo" que est contenido en dicho campo, tal es el caso de que queramos buscar la primera coincidencia de una autora llamada Jane; como habrs comprobado, la informacin del autor se muestra en el formato Apellido, Nombre, por tanto si queremos buscar el nombre sera ms bien complicado, ya que lo que buscamos est al final... Para ello podemos usar los signos de comodines ? (interrogacin) y * (asterisco). -El primero de ellos sirve para indicarle que no tenga en cuenta el caracter que est en la posicin indicada por la interrogacin, por tanto, si buscamos esto: J?an, mostrar tanto Juan como Jean, es decir, dar por bueno cualquier secuencia que empiece por "J", tenga cualquier caracter y acabe por "an". -En el segundo caso, el del asterisco, si ste est al principio de lo escrito, le estaremos indicando que encuentre todo lo que tenga al final el texto indicado: *Jane, buscar el primer registro que acabe con Jane, pero si especificamos el asterisco al final: Jane*, mostrar todos los registros que empiecen por Jane, por ltimo, si usamos dos asteriscos, uno al principio y otro al final: *Jane*, nos mostrar el primero que "contenga" el nombre indicado, no importando los caracteres que tenga antes o despus. Cuando busquemos datos exactos la comparacin la haremos con el signo igual (=), pero si queremos usar la bsqueda con comodines no nos servir el signo igual, ya que hay que indicarle que queremos usar una comparacin ms verstil, en este caso usaremos LIKE. Hay que saber que al buscar en campos del tipo String (cadenas de caracteres), hay que usar los apstrofes para indicar el principio y final de la cadena buscada, si no lo hiciramos, se producira un error. Despus de lo dicho, el cdigo de bsqueda quedara de la siguiente forma: ' Private Sub Text4_KeyPress(KeyAscii As Integer) ' Se buscar slo cuando pulsemos INTRO Dim nReg As Long ' ' Comprobar si la tecla pulsada es Intro: vbKeyReturn o 13 que es lo mismo If KeyAscii = vbKeyReturn Then ' Esta asignacin evita que suene un BEEP KeyAscii = 0 ' Convertir el contenido de TextBox en un nmero nReg = Val(Text4) ' Buscar la primera coincidencia en el recordset del Data1 ' en el campo Author Data1.Recordset.FindFirst "Author Like '" & Text4.Text & "'" End If End Sub
415
Fjate que no he usado ningn comodn, por tanto tendrs que escribirlos t, por ejemplo, para buscar la primera Jane, tendrs que escribir: *Jane en la caja de texto y pulsar Intro. Haz la prueba, ejecuta el proyecto y escribe *Jane en el Text4 y pulsa Intro, te mostrar el registro 1897, (Lenser, Jane), ahora escribe *Jane* y tras pulsar Intro, te mostrar el registro 840 que contiene Janet, (Hamlin, Janet), escribe *J y vers que se muestra el registro nmero 1242, (Baer, J) para terminar estas pruebas, escribe J* y en esta ocasin se mostrar el registro 1, (Jacobs, Russell) Y ahora un ejercicio: Aade dos controles Options para que podamos buscar tanto por el nmero del ID como por el nombre del Autor, el aspecto del formulario sera este:
El Option1 ser el indique que se busque por el ID y Option2 ser para buscar por el campo Author. La solucin en la prxima entrega.
Bueno, hasta aqu ha llegado esta primera entrega referente al acceso a datos. En futuras entregas veremos cmo realizar bsquedas (o consultas), as como otras cosas ms, entre ellas acceder a una base de datos sin el Data Control y tambin al acceso a bases de datos del tipo ADO (activeX Data Objects). Pero eso ser en otra ocasin, hasta entonces, que lo programes bien! Nos vemos Guillermo P.S. Si quieres bajarte los ejemplos usados (y la solucin al ejercicio), pulsa este link. (basico34_cod.zip 4.35 KB) La pgina con las soluciones de esta entrega.
416
Aqu tienes la respuesta a la entrega 34. ' Private Sub Text4_KeyPress(KeyAscii As Integer) ' Se buscar slo cuando pulsemos INTRO Dim nReg As Long ' ' Comprobar si la tecla pulsada es Intro: vbKeyReturn o 13 que es lo mismo If KeyAscii = vbKeyReturn Then ' Esta asignacin evita que suene un BEEP KeyAscii = 0 ' Convertir el contenido de TextBox en un nmero nReg = Val(Text4) ' Buscar la primera coincidencia en el recordset del Data1 If Option1.Value Then ' en el campo Au_ID Data1.Recordset.FindFirst "Au_ID = " & nReg End If If Option2.Value Then ' en el campo Author Data1.Recordset.FindFirst "Author Like '" & Text4.Text & "'" End If End If End Sub
Se que hay muchos programadores de VB a los que no les gusta usar el Datacontol para acceder a las bases de datos; pero tambin se que usar este control es ms fcil que usar cdigo directo, aunque esto ltimo tambin lo vamos a ver a lo largo de este curso bsico, incluso lo vamos a mezclar con el Datacontrol, ya que se puede acceder a bases de datos de las dos formas de forma conjunta. En esta entrega vamos a ampliar el ejemplo usado en la entrega anterior, para aadirle algunas opciones nuevas, para aadir nuevos registros y para eliminar registros existentes, por tanto, si quieres conservar intacta la base de datos Biblio.mdb, te recomiendo que hagas una copia de la misma. Tambin vamos a usar la opcin de buscar que puse como ejercicio, por tanto, si no te has ledo las soluciones de la entrega 34, es conveniente de que le eches un vistazo. Vamos a empezar por mejorar la bsqueda en la base de datos:
418
Data1.Recordset.FindFirst "Author Like '" & Text4.Text & "'" End If End Sub Ahora hay que modificar el evento KeyPress del control Text4, para que llame al nuevo procedimiento. Borra el cdigo que haba anteriormente en ese evento y sustityelo por este otro: ' Private Sub Text4_KeyPress(KeyAscii As Integer) ' Se buscar slo cuando pulsemos INTRO ' ' Comprobar si la tecla pulsada es Intro: vbKeyReturn o 13 que es lo mismo If KeyAscii = vbKeyReturn Then ' Esta asignacin evita que suene un BEEP KeyAscii = 0 ' Llamamos al procedimiento Buscar: Buscar End If End Sub Prubalo, para que veas que todo funciona como antes.
Ahora, vamos a aadir un botn para que busque el primer registro que coincida con lo escrito, esto es para hacer lo mismo que cuando pulsas Intro en el Text4, pero para el usuario ser ms lgico que el hecho de tener que pulsar Intro. Por tanto, aade un nuevo botn, (no te preocupes por ahora dnde colocarlo en el formulario, ya lo haremos dentro de poco), cmbiale el nombre a cmdBuscar y el Caption a Buscar, (por defecto ser Command1) y escribe o aade este cdigo (tambin puedes hacerlo copiando y pegando): ' Private Sub cmdBuscar_Click() ' Simplemente llamamos al procedimiento Buscar: Buscar End Sub Poca cosa, verdad? Pues es igual de efectivo que pulsando Intro en el Text4, pruebalo para que veas que funciona.
419
Creo que ya es hora de que nos vayamos complicando la vida... Vamos a aadir un botn para seguir buscando a partir del ltimo dato hallado: Aade un nuevo botn, llmalo cmdBuscarSig y en el Caption pones: Buscar siguiente, (seguramente el texto lo escribir en dos lneas, pero no te preocupes). El cdigo a usar en el evento Click de ese nuevo botn sera prcticamente el mismo que en el de Buscar, aunque antes debemos aadir un nuevo procedimiento para que busque el siguiente dato al ltimo que busc: ' Private Sub BuscarSiguiente() ' Procedimiento para buscar el dato indicado (12/Feb/01) Dim nReg As Long ' ' Buscar la siguiente coincidencia, a partir del ltimo hallado ' If Option1.Value Then nReg = Val(Text4) ' Data1.Recordset.FindNext "Au_ID = " & nReg End If If Option2.Value Then ' Data1.Recordset.FindNext "Author Like '" & Text4.Text & "'" End If End Sub Si te fijas, el cdigo es prcticamente el mismo que el del procedimiento Buscar, lo nico que cambia es que aqu se usa FindNext en lugar de FindFirst. Es decir FindFirst busca el primer dato que coincida con lo buscado y FindNext el siguiente al ltimo que se busc. Para probar este nuevo procedimiento, en el evento cmdBuscarSig_Click, escribe: BuscarSiguiente. As es como quedara ese evento: Private Sub cmdBuscarSig_Click() ' Buscar el siguiente registro BuscarSiguiente End Sub ' en el campo Author ' en el campo Au_ID ' Convertir el contenido de TextBox en un nmero
Como hemos visto, el cdigo usado en los dos procedimientos de bsqueda son
420
prcticamente iguales, as que vamos a unificarlos para crear un slo procedimiento de bsqueda, de esta forma, refrescars tu memoria y sabrs algo ms de parmetros en procedimientos, as como parmetros opcionales.
Parmetros opcionales.
Como sabrs, (y si no lo sabes, te lo cuento yo ahora), a partir de la versin 4 de Visual Basic se pueden usar parmetros opcionales en los procedimientos y funciones. Esto quiere decir que podemos usar el procedimiento de varias formas, indicando todos los parmetros o slo los que realmente son necesarios. Por ejemplo, en el caso en que estamos ahora, el procedimiento Buscar podra tener un parmetro opcional para indicarle si es la primera bsqueda o la siguiente. No voy a entrar en demasiadas explicaciones, ya que este tema lo veremos de forma ms amplia en otra entrega, sobre todo porque tiene sus pormenores, o lo que es lo mismo, existen diferencias entre las versiones 4 y posteriores (al menos hasta la 6) de Visual Basic e incluso en las dos ltimas se puede usar de dos formas diferentes... En esta ocasin voy a usar el formato de las versiones 5 y 6, en estas versiones los parmetros opcionales pueden ser de un tipo de datos diferente a Variant y tambin pueden indicrsele un valor por defecto, para que, si no se especifica, tenga el valor indicado; por supuesto, si no le indicamos el valor, tendrn el valor que ese tipo de datos tengan por defecto, por ejemplo el tipo Boolean tendr un valor False si no se indica el valor. Cuando se indican parmetros con tipo, a diferencia de los parmetros del tipo Variant, no se puede usar IsMissing para comprobar si el parmetro se ha especificado o no, pero, eso es otro tema... Este es el cdigo del procedimiento Buscar y a continuacin te indico cmo llamarlo desde el evento Click del botn cmdBuscarSig: ' Private Sub Buscar(Optional ByVal Siguiente As Boolean = False) ' Procedimiento para buscar el dato indicado (12/Feb/01) ' Si Siguiente = True, se busca a partir del registro activo ' Si no se indica, (valdr False), buscar el primer registro Dim nReg As Long ' ' Buscar la primera coincidencia en el recordset del Data1 ' If Option1.Value Then nReg = Val(Text4) ' ' Si se busca el siguiente dato If Siguiente Then Data1.Recordset.FindNext "Au_ID = " & nReg ' en el campo Au_ID ' Convertir el contenido de TextBox en un nmero
421
Else Data1.Recordset.FindFirst "Au_ID = " & nReg End If End If If Option2.Value Then ' ' Si se busca el siguiente dato If Siguiente Then Data1.Recordset.FindNext "Author Like '" & Text4.Text & "'" Else "'" End If End Sub Data1.Recordset.FindFirst "Author Like '" & Text4.Text & End If ' en el campo Author
Private Sub cmdBuscarSig_Click() ' Buscar el siguiente registro Buscar True End Sub
Puedes borrar el procedimiento BuscarSiguiente, ya que no es necesario. Tampoco es necesario modificar el cdigo de los eventos Text4_KeyPress ni el de cmdBuscar_Click, ya que esos dos eventos llaman al procedimiento Buscar con el valor predeterminado y al ser opcional, no es necesario indicarlo... Si pruebas el nuevo cdigo, te dars cuenta de que, la primera vez, pulsando tanto en Buscar como en Buscar siguiente, encuentra lo mismo, esto es debido a que FindNext, busca a partir de la ltima posicin hallada y en el caso de buscar por primera vez, busca desde el principio. En el caso de que cambies el texto buscado y pulses en Buscar siguiente, se mostrar el prximo registro, desde el ltimo hallado, que contenga dicho texto, (estas pruebas hay que hacerlas en el campo Author, ya que no tienen ningn sentido hacerlo en el ID del autor). Para buscar el resto de autores con el nuevo texto, tendrs que pulsar en Buscar para que empiece a buscar desde el principio. Fjate en el cdigo, se da por hecho de que Siguiente tiene un valor, el cual, (al ser del tipo Boolean), puede ser False o True. En caso de que sea True, es decir se ha especificado con ese valor, se buscar con FindNext y en caso de no especificarse o de hacerlo con el valor False, se usar FindFirst. Lo mismo da: Buscar False que Buscar (sin parmetros) Esto ltimo: lo de no especificarse o si se especifica con el valor False, es algo que hay
422
que tener en cuenta si el parmetro fuese de tipo Variant (cosa obligatoria si usas VB4), ya que IsMissing slo nos informa si el parmetro no se ha especificado, pero si se especifica, puede hacerse con un valor False o True, por tanto, tambin habra que tenerlo en cuenta... Ya s que dije que lo iba a dejar para otra entrega... pero, que haces si ests usando VB4 dejar aqu el curso? As que vamos a ver el cdigo de Buscar para usar con VB4 o con las versiones 5 y 6 pero usando el tipo Variant. ' Private Sub Buscar(Optional ByVal vSiguiente As Variant) ' Procedimiento para buscar el dato indicado (12/Feb/01) ' ' Si Siguiente = True, se busca a partir del registro activo ' Si no se indica, (valdr False), buscar el primer registro ' ' Cuando el parmetro es de tipo Variant, ' no se puede indicar un valor por defecto, ' as que vamos a usar una variable interna para indicar el valor del parmetro Dim Siguiente As Boolean ' Dim nReg As Long ' ' Asignar correctamente el valor del parmetro indicado ' Si no se especifica, le asignamos el valor por defecto, en este caso: False If IsMissing(vSiguiente) Then Siguiente = False Else ' Si se indica, asignarlo a la variable interna Siguiente = CBool(vSiguiente) End If ' El resto del cdigo es igual que antes ' Buscar la primera coincidencia en el recordset del Data1 ' If Option1.Value Then nReg = Val(Text4) ' en el campo Au_ID ' Convertir el contenido de TextBox en un nmero
423
' ' Si se busca el siguiente dato If Siguiente Then Data1.Recordset.FindNext "Au_ID = " & nReg Else Data1.Recordset.FindFirst "Au_ID = " & nReg End If End If If Option2.Value Then ' ' Si se busca el siguiente dato If Siguiente Then Data1.Recordset.FindNext "Author Like '" & Text4.Text & "'" Else "'" End If End Sub Fjate que he cambiado el nombre del parmetro y he creado una variable interna que es la que posteriormente se usa para saber si se busca desde el primero o desde el anterior. En el caso de que no se haya especificado el parmetro, la comprobacin de IsMissing se cumple, por tanto aqu le indicaremos el valor que queramos que tenga por defecto, en nuestro ejemplo no es necesario asignar ningn valor, ya que False es el valor que tendr la variable Siguiente si no le asignamos ningn valor. Por tanto en este ejemplo, podramos haber hecho slo lo siguiente, sin necesidad de usar el IsMissing: Siguiente = CBool(vSiguiente) Pero lo he mostrado de la forma recomendable, para que sepas qu hacer en el caso de que el valor por defecto fuese otro del que Visual Basic asigna a las variables por defecto. Data1.Recordset.FindFirst "Author Like '" & Text4.Text & End If ' en el campo Author
Una vez visto el procedimiento de bsqueda, vamos a aadir dos nuevos botones para Aadir y Eliminar registros. Recuerda lo que te dije al principio: haz una copia de la base de datos para que las pruebas no afecten al contenido de la misma. Aade dos nuevos botones y asignales los nombres cmdAdd y cmdBorrar, as como los captions Aadir y Eliminar, el cdigo ser el siguiente: ' Private Sub cmdAdd_Click() ' Aadir un nuevo registro
424
Data1.Recordset.AddNew End Sub Private Sub cmdBorrar_Click() ' Eliminar el registro actual Data1.Recordset.Delete End Sub
Tanto cuando se aade como cuando se borra, se debera mover el registro actual para que los cambios tengan efecto en la base de datos, ya que si se aade un nuevo registro y el mismo no se actualiza, se pierde. Para probarlo, ejecuta el proyecto, pulsa en Aadir y directamente sin hacer nada ms cierra el ejecutable, (pulsando en la x del formulario o ventana). Ejecuta de nuevo el proyecto y si pulsas en el botn de ir al ltimo registro del Datacontrol, vers que no hay ningn registro nuevo. Ahora vuelve a pulsar en Aadir y escribe lo que quieras en las cajas de texto del Nombre y Ao de nacimiento y pulsa en el botn de ir al primer registro y despus al ltimo (ya sabes que me refiero a los botones del Datacontrol), vers que ahora si se muestra dicho registro y si pulsas en el botn de ir al anterior, vers que se salta un nmero... ese es el que haba reservado para nosotros, pero al no actualizar los datos, se perdi en el limbo... Ser por estos detalles que algunos programadores prefieran usar cdigo puro y duro en lugar del Datacontrol? Puede... pero no creo que sea por este motivo... ya que esto mismo se puede mejorar y no es necesario abandonar el Datacontrol para que todo funcione ms o menos como debiera... Primero vamos a hacer que los nuevos datos tengan algo, para ello, le asignaremos la cadena Nuevo al nombre del autor y haremos que se desplace al ltimo registro, para que los datos sean permanentes, (si no modificamos algunos de los campos, el nuevo registro se perdera) En el caso de Eliminar, vamos a movernos al primer registro, para que el registro activo sea uno con informacin. Este sera el cdigo de los eventos Click de los dos botones: ' Private Sub cmdAdd_Click() ' Aadir un nuevo registro Data1.Recordset.AddNew ' Aadimos algn texto, para que no se pierda este registro Text2 = "Nuevo" ' Movemos al ltimo registro para que los cambios se hagan permanentes ' y se muestre el nuevo registro Data1.Recordset.MoveLast End Sub
425
Private Sub cmdBorrar_Click() ' Eliminar el registro actual Data1.Recordset.Delete ' Movemos al primer registro para que los cambios se hagan permanentes ' (tambin podriamos haberlo movido al ltimo registro) Data1.Recordset.MoveFirst End Sub Toma nota de la forma en que se hara para mover al primer o al ltimo registro del Recordset. Tambin habra que tener en cuenta si hemos sobrepasado el principio o el final de los registros, cosa que ocurre cuando no hay ms registros, para ello podemos comprobar tanto EOF (End Of File, final del fichero) como BOF (Beginnig Of File, principio del fichero) y en caso de que sea cierto (True), avisar o simplemente no hacer nada... Esto slo habra que hacerlo al eliminar registros, ya que al aadir se supone que habr al menos el que aadimos... ' Private Sub cmdBorrar_Click() ' ' Comprobar que hay registros, porque si no hay, dar error If (Data1.Recordset.EOF Or Data1.Recordset.BOF) Then ' Avisar de que no hay registros Else ' Eliminar el registro actual Data1.Recordset.Delete ' ' Movemos al primer registro para que los cambios se hagan permanentes ' (tambin podriamos haberlo movido al ltimo registro) Data1.Recordset.MoveFirst End If End Sub
Y esto es todo por hoy. En la prxima entrega veremos cmo acceder a bases de datos usando el ADO datacontrol. Nos vemos Guillermo
426
P.S. Si quieres bajarte los ejemplos usados, pulsa este link. (basico35_cod.zip 3.11 KB)
' Indicar el path correcto de la base de datos ' ACUERDATE DE PONER EL PATH CORRECTO! Const sPathBase As String = "C:\Program Files\Microsoft Visual Studio\VB98\BIBLIO.MDB" ' ' Crear la conexin manualmente ' Con "Provider=Microsoft.Jet.OLEDB.4.0;" se permite abrir bases de datos de Access 2000
427
With Adodc1 .ConnectionString = _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & sPathBase & ";" ' Indicarle de que tabla vamos a leer los datos .RecordSource = "Authors" End With
Nota: He de aclarar que las pruebas sobre acceso a bases de datos con el ADO datacontrol estn realizadas con el Visual Basic 6.0 y el Service Pack 4 (SP4). Si el cdigo anterior te produjera un error, primero comprueba que est escrito correctamente (hay que empezar por lo ms simple), si sigue sin funcionar, prueba a cambiar el 4.0 que est entre comillas por 3.51, si an as tampoco funciona... prueba a instalar el ADO, creo que se instala automticamente con el Internet Explorer 5.5, si a pesar de todo esto tampoco funciona... no se que aconsejarte, aparte de que consigas el VB6 y el SP4... o tambin que pases de esta entrega... Te explico un poco el cdigo anterior. La constante es para que sea fcil modificarla si prefieres usar otra base de datos. El With Adodc1 es para indicarle al VB que vamos a modificar varias propiedades del ADO datacontrol, aqu supongo que no has modificado el nombre de dicho control, si lo has hecho, cambia Adodc1 por el nombre que hayas puesto. El mtodo .ConnectioString, segn dice la ayuda del Visual Basic: Contiene la informacin que se utiliza para establecer una conexin a un origen de datos. Osea, es lo que le dice al ADO datacontrol de que base de datos obtener los datos y tambin de que tipo es, en este caso es una base de datos del tipo Microsoft.Jet, o lo que es lo mismo, del tipo Access. Por tanto Provider= indica de que tipo de base de datos se trata y Data Source= el path de la base de datos en cuestin, (fjate que se usa la constante con el path y nombre de la base de datos). Por ltimo a la propiedad .RecordSource le indicamos que es lo que queremos que maneje el datacontrol, en este caso una tabla de la base de datos; aunque tambin podramos haberle indicado una consulta, pero esto ltimo lo veremos en otra ocasin. Ahora vamos a aadir unos cuantos controles al formulario, para poder trabajar con los datos manejados por el ADO datacontrol. Los controles que usaremos sern 3 etiquetas y 3 cajas de texto, pero ambos sern un array de controles, (en el ejemplo de las entregas 34 y 35 usamos controles independientes, pero creo que es ms cmodo usar array de controles).
428
Del men Edicin, selecciona Copiar, (tambin puedes hacerlo con el botn derecho del ratn). Del men Edicin, selecciona Pegar, se mostrar un mensaje preguntando si quieres crear un array de Label1, contesta que si. Vuelve a pegar (Edicin/Pegar) Y ya tienes tres etiquetas con ndices que van desde cero a dos. Aade un TextBox y haz la misma operacin que con la etiqueta: Seleccinalo; cpialo; pgalo, contesta que SI al mensaje de que quieres crear un array; vuelve a pegar. Al final tendrs tres cajas de Texto llamados Text1 con ndices de cero a dos. Posiciona los controles de forma que las etiquetas estn alineadas con las cajas de texto, no te preocupes por el Caption de las etiquetas, ya que lo modificaremos desde cdigo. Aade unos cuantos controles ms, para que podamos hacer bsqueda, etc. tal y como se hizo en las dos entregas anteriores. Al final deberamos tener los siguientes controles: Control Label1 Text1 Label2 Text2 Option1 Option2 cmdBuscar cmdBuscarSig cmdSalir cmdAdd cmdBorrar Adodc1 ndices array) 0a2 0a2 Ninguno Ninguno Ninguno Ninguno Ninguno Ninguno Ninguno Ninguno Ninguno Ninguno del array (o ninguno si no es un
(no (no (no (no (no (no (no (no (no (no
es es es es es es es es es es
un un un un un un un un un un
array) array) array) array) array) array) array) array) array) array)
429
Aqu te muestro el cdigo usado para que funcione este invento, como comprobars, es prcticamente el mismo que el usado en las entregas anteriores, con unas cuantas aadiduras para que funcione correctamente con el ADO datacontrol. Por tanto creo que no necesita ms explicaciones que las que se incluyen en el propio cdigo. El cdigo del procedimiento Buscar es el que tiene ms cambios, entre otras cosas porque la forma de buscar en ADO es diferente al de DAO, en este ltimo (DAO), se usan FindFirst, FindNext, etc. y en ADO slo existe Find. Tambin he usado un marcador en la rutina de buscar; por si no se hallan datos, que el registro activo sea el mismo que estaba antes de buscar. chale un vistazo a los comentarios que hay en el cdigo. ' '----------------------------------------------------------------------------' Prueba de ADO Datacontrol para la entrega 36 (14/Feb/01) ' ' Guillermo 'guille' Som, 2001 '----------------------------------------------------------------------------Option Explicit Private Sub Adodc1_MoveComplete(ByVal adReason As ADODB.EventReasonEnum, ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, ByVal pRecordset As ADODB.Recordset) ' Mostrar el ID del registro actual ' si se pasa del primero o del ltimo, dar error On Error Resume Next ' Mostrar el ID del registro actual usando el recordset pasado como parmetro Adodc1.Caption = "Registro actual: " & pRecordset.AbsolutePosition ' Si da error, indicarlo (20/Sep/99) If Err Or pRecordset.BOF Or pRecordset.EOF Then Adodc1.Caption = "Ningn registro activo" ' Habra que moverlo a un registro con informacin ' *** Dejarlo comentado *** activos ' para que el procedimiento de bsqueda avise si no hay datos 'Adodc1.Recordset.MoveFirst
430
End If Err = 0 End Sub Private Sub cmdAdd_Click() ' Aadir un nuevo registro Adodc1.Recordset.AddNew ' Aadimos algn texto, para que no se pierda este registro Text1(1) = "Nuevo" ' Actualizamos los datos Adodc1.Recordset.Update ' Hacemos que se "recargue" los datos del recordset Adodc1.Refresh ' Movemos al ltimo registro para que los cambios se hagan permanentes ' y se muestre el nuevo registro Adodc1.Recordset.MoveLast End Sub Private Sub cmdBorrar_Click() ' Borrar el registro actual ' Se comprueba que haya algn registro activo, ' para ello se comprueba que no hayamos pasado del principio o el final del Recordset ' ' Comprobar que hay registros, porque si no hay, dar error If (Adodc1.Recordset.EOF Or Adodc1.Recordset.BOF) Then ' Avisar de que no hay registros Adodc1.Caption = "Ningn registro activo" Else ' Eliminar el registro actual Adodc1.Recordset.Delete ' ' Movemos al primer registro para que los cambios se hagan permanentes ' (tambin podriamos haberlo movido al ltimo registro) Adodc1.Recordset.MoveFirst End If
431
End Sub Private Sub cmdBuscar_Click() ' Buscar el primer registro que coincida con el dato buscado Buscar End Sub Private Sub cmdBuscarSig_Click() ' Buscar el siguiente Buscar True End Sub Private Sub cmdSalir_Click() Unload Me End Sub Private Sub Form_Load() ' Text2 = "" Option2.Value = True ' ' Indicar el path correcto de la base de datos ' ACUERDATE DE PONER EL PATH CORRECTO! Const sPathBase As String = "C:\Program Files\Microsoft Visual Studio\VB98\BIBLIO.MDB" ' ' Crear la conexin manualmente ' Con "Provider=Microsoft.Jet.OLEDB.4.0;" se permite abrir bases de datos de Access 2000 With Me.Adodc1 .ConnectionString = _ "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & sPathBase & ";" ' Indicarle de que tabla vamos a leer los datos .RecordSource = "Authors" End With ' Indicar el DataSource de los Textboxes ' ya que con ADO se puede asignar en tiempo de ejecucin Dim i As Long
432
For i = 0 To 2 Set Text1(i).DataSource = Adodc1 Next ' Asignar los campos Text1(0).DataField = "Au_ID" Text1(1).DataField = "Author" Text1(2).DataField = "Year Born" ' Mostrar en las etiquetas el campo a usar For i = 0 To 2 Label1(i).Caption = Text1(i).DataField & ":" Next End Sub Private Sub Text2_KeyPress(KeyAscii As Integer) ' Se buscar slo cuando pulsemos INTRO ' ' Comprobar si la tecla pulsada es Intro: vbKeyReturn o 13 que es lo mismo If KeyAscii = vbKeyReturn Then On Error Resume Next ' Esta asignacin evita que suene un BEEP KeyAscii = 0 ' Buscar End If End Sub Private Sub Buscar(Optional ByVal Siguiente As Boolean = False) ' Procedimiento para buscar el dato indicado (18/Ene/01) ' Si Siguiente = True, se busca a partir del registro activo Dim nReg As Long Dim vBookmark As Variant ' En ADO debe ser Variant, no vale un String Dim sADOBuscar As String ' ' Iniciamos la deteccin de errores On Error Resume Next '
433
' Buscar la primera coincidencia en el recordset del Data1 If Option1.Value Then ' Convertir el contenido de TextBox en un nmero nReg = Val(Text2) ' en el campo Au_ID sADOBuscar = "Au_ID = " & nReg End If If Option2.Value Then ' en el campo Author sADOBuscar = "Author Like '" & Text2.Text & "'" End If ' Guardar la posicin anterior, por si no se halla lo buscado... vBookmark = Adodc1.Recordset.Bookmark ' If Siguiente = False Then ' Buscar desde el principio Adodc1.Recordset.MoveFirst Adodc1.Recordset.Find sADOBuscar Else ' Busca a partir del registro actual Adodc1.Recordset.Find sADOBuscar, 1 End If ' Devolver un error si no se halla lo buscado ' aunque no siempre es as... If Err.Number Or Adodc1.Recordset.BOF Or Adodc1.Recordset.EOF Then Err.Clear MsgBox "No existe el dato buscado o ya no hay ms datos que mostrar." ' Posicionar el recordset en la posicin guardada Adodc1.Recordset.Bookmark = vBookmark End If End Sub
Y con esto acabamos la introduccin a las bases de datos. En futuras entregas veremos otras cosillas sobre el acceso a datos, aunque antes de seguir con el acceso a base de datos, empezaremos a ver un nuevo tipo de mdulo de cdigo: las clases. As que... preprate para lo que viene. Nos vemos Guillermo
434
P.S. Si quieres bajarte los ejemplos usados, pulsa este link. (basico36_cod.zip 4.21 KB)
Nota: Como ya coment en la entrega 30, la versin, (o versiones), de Visual Basic que hace mejor uso de las clases, sobre todo de las colecciones, es la versin 5, (y mejor an la 6, aunque con la versin 5 ser ms que suficiente), por tanto, si ests usando una versin anterior a la 5, puede que algunos de los ejemplos que muestre no funcione correctamente, sobre todo en el tema de las propiedades por defecto y en el manejo de colecciones propias. De todas formas, confo en que todo lo que explique te quede claro, incluso si usas la versin 4, ya que con versiones anteriores, no podrs usar los mdulos de clases, simplemente porque Visual Basic no sabe de que se trata! Si no tienes el Visual Basic 5 o superior, puedes bajarte del sitio de Microsoft una versin reducida, que aunque no te permite crear ejecutables, si que puedes probar todo lo que aqu veamos, aunque sea en el propio entorno integrado (IDE). Si no lo han cambiado de sitio, dicha versin de VB: la VB5CCE (Visual Basic 5 Control Creation Edition), la puedes encontrar en la siguiente direccin: http://msdn.microsoft.com/vbasic/downloads/tools/cce/default.aspx Nota del 30/Jun/2003: He actualizado el link del VB5CCE. Si vuelven a cambiarlo de sitio, es posible que en la direccin indicada no lo encuentres, as que tendrs que buscar en el sitio de Microsoft por "Control Creation Edition" y suerte! Tambin te puedes pasar por la pgina VB-Resumen de mi sitio y all seguramente el link est ms actualizado.
435
436
cdigo como controles propiamente dicho, (en futuras entregas aprenderemos a crear nuestros propios controles ActiveX u OCX). Slo espero que no te confundas.
Clases/Objetos = Encapsulacin:
La ventaja de usar clases en nuestros proyectos es que podemos encapsular todo el cdigo y tratarlas como si de cajas negras se tratasen, es decir, sabemos que es lo que hacen, cmo usarlas, pero no es imprescindible saber el cdigo que se usa para que todo funcione. Por tanto una de las caractersticas principales que un objeto (o clase) debe tener es que no dependa del exterior, es decir, que sea autosuficiente: todo la informacin que necesite se debe suministrar mediante las propiedades que dicha clase u objeto exponga al exterior, (mediante las propiedades pblicas) y de igual manera, toda la informacin que un objeto deba mostrar al mundo exterior, se haga mediante las propiedades, mtodos y eventos que dicha clase exponga al exterior, (declarados como pblicos). Bien, para entrar en calor, creo que lo mejor es ver un ejemplo. Asi que abre el Visual Basic, crea un nuevo proyecto EXE y sigue estos pasos para aadir un mdulo de clase: Abre el men Proyecto y pulsa en: Aadir Mdulo de Clase, te mostrar un cuadro de dilogo mostrndote varias tipos de mdulos de clase, pulsa en la primera que te muestra: Mdulo de Clase, pulsa en Abrir y se aadir un nuevo fichero, si te fijas en la ventana de propiedades, tendr como nombre: Class1, (al menos as es como se llama en la versin inglesa de Visual Basic, que es la que yo uso). Ese nombre ser el que identificar al objeto cuando queramos acceder a l, (realmente ser el que usaremos para crear variables que accedan a dicho objeto), por tanto es importante darle un nombre con el cual podamos, ms o menos, identificar el uso para el que se ha creado dicho objeto. Por tanto vamos a darle un nombre a la clase, y como en este ejemplo vamos a usar el objeto para almacenar los datos de un "colega" nuestro, le daremos el nombre: cColega. Nota: Tambin puedes nombrarla Colega, la "c" es para indicar que se trata de una clase; no es una norma obligatoria, pero... es ampliamente usada por muchos programadores de Visual Basic, (entre ellos yo); otros usan el prefijo de la "c" slo para el nombre del fichero, as que queda a tu criterio usarla en el nombre de la clase o no. Esta clase tendr varias propiedades, una para cada uno de los datos que manipular, el Nombre, el e-mail y la fecha de nacimiento, adems de un mtodo que nos indicar que edad tiene dicho colega. Veamos cmo aadir propiedades y mtodos a una clase:
437
1. La forma ms simple, es declarando las propiedades como variables pblicas: Cuando declaramos una variable Public en un mdulo de clase, dicha variable se convierte en una propiedad. 2. La otra forma es creando un procedimiento Property. La ventaja de usar los procedimientos Property, es que podemos hacer comprobaciones extras que con las variables sera imposible hacer. Por ejemplo, al asignar un valor a la propiedad e-mail, se podra comprobar que dicho valor contenga el signo arroba (@); en el caso la fecha de nacimiento, se podra validar que fuese una fecha correcta y en cualquiera de estos casos poder producir un error e incluso un evento para avisar de que algo no se ha hecho bien. Al asignar un valor a una variable, dicho valor se asigna sin ms, no se comprueba nada de nada, simplemente se asigna, sin embargo al usar un procedimiento para hacer dicha asignacin podemos hacer las comprobaciones necesarias para asegurarnos que lo que se est asignando es lo correcto. Cuando usamos procedimientos Property para almacenar valores de propiedades, nos vemos obligados a usar una variable que mantenga el valor asignado a dicha propiedad, esa variable se suele ocultar del mundo exterior declarndola Private, de esta forma esa variable slo es visible dentro del cdigo del mdulo de clase. Recuerdas lo que te dije del mbito o visibilidad de las variables? Veamos el cdigo que se podra usar para implementar las propiedades de la clase cColega: Para el nombre del colega, simplemente declaramos una variable pblica: Public Nombre As String Para el e-mail crearemos una variable privada para guardar internamente la direccin de e-mail: (como no podemos usar el signo - en el nombre de una variable, tendremos que usar email, sin signo de separacin) Private m_email As String m_ es para indicar que es una variable declarada a nivel de mdulo y por tanto visible en todo el mdulo en el que se ha declarado. Ahora tenemos que crear el procedimiento Property. ste podemos crearlo de dos formas, usando la opcin Aadir procedimiento... del men de Herramientas o escribiendo el cdigo directamente... vamos a usar el primer mtodo: Selecciona Aadir Procedimiento del men Herramientas, te mostrar un cuadro de dilogo preguntando que tipo de procedimiento quieres crear y con que nombre. Selecciona la opcin Property (Propiedad), en mbito (Scope) selecciona Public y escribe email en la caja de texto. Se crearn dos nuevos procedimientos: Public Property Get email() As Variant End Property Public Property Let email(ByVal vNewValue As Variant)
438
End Property Cada uno de estos dos procedimientos son: Property Get para devolver el valor de la propiedad y Property Let para asignar un nuevo valor a la propiedad. Nota: Por defecto el tipo de datos asignado a un procedimiento Property es del tipo Variant, por tanto habr que modificarlo para que sea del tipo que vamos a usar. En el procedimiento que se usa para devolver el contenido actual de la propiedad, simplemente devolvemos el contenido de la variable privada:
Public Property Get email() As String ' Devolver el contenido de esta propiedad, ' el cual est almacenado en la variable privada email = m_email End Property En el procedimiento que se usa para asignar un nuevo valor a la propiedad, es donde tenemos que hacer la comprobacin de que se asigna un valor que representa una direccin de correo electrnico, (realmente slo se comprueba que tenga el signo @)
Public Property Let email(ByVal NewValue As String) ' Comprobar que el nuevo valor a asignar tenga el signo @ If InStr(NewValue, "@") = 0 Then ' Asignamos los datos del error, pero no lo producimos With Err .Number = 13 .Description = "El valor asignado a email no es una direccin de correo electrnico." .Source = "cColega.email = " & NewValue ' Para producir el error, usariamos '.Raise 13 End With Else ' Asignamos el nuevo valor a la variable privada m_email = NewValue End If End Property
439
En este procedimiento se comprueba que la cadena tenga el signo arroba, si no es as, la comparacin realizada se cumple y se asigna al objeto Err un error indicando lo que se ha hecho mal; si quitas el comentario que hay delante de .Raise, el error se produce en la clase y de esa forma no se detectara en la aplicacin que use la clase. En caso de que la cadena asignada a la propiedad tenga el signo @, el nuevo valor se asignar a la variable privada. Dentro de un poco veremos el cdigo usado para asignar los valores a las propiedades y manejar los errores que se produzcan. Antes vamos a codificar la propiedad FechaNacimiento. Como esta propiedad contendr una fecha, lo lgico sera que el tipo de datos fuese Date, el problema o la ventaja, segn se mire, es que si asignamos un valor errneo a dicha propiedad, ser el propio Visual Basic el que se encargue de avisarnos de que no es correcta. Pero si queremos detectar el valor errneo dentro del procedimiento o bien usar algn tipo de conversin "inteligente" de una fecha que supuestamente es errnea, (por ejemplo: permitir introducir la fecha con distintos signos de separacin y asignarlo en el formato que internamente queramos), entonces lo mejor sera usar otro tipo de datos, por ejemplo del tipo String; como siempre, quedar a tu criterio el usar un tipo de datos u otro. Sea como fuere, necesitaremos una variable privada para mantener el valor asignado, en este caso he declarado una variable de esta forma: Dim m_FechaNacimiento As Date El declarar una variable de tipo Date no implica, ni obliga a, que tengamos que usar ese tipo de datos en los procedimientos de la propiedad, lo nico obligatorio es que tanto el valor devuelto por el procedimiento Property Get sea del mismo tipo de datos que el de la variable usada en el procedimiento Property Let. Es decir, no podemos tener un procedimiento Property devolviendo un tipo de datos y recibiendo otro diferente, independientemente del tipo de datos que tenga la variable privada usada para contener el valor. La forma ms fcil de "codificar" ese procedimiento sera de esta forma: Public Property Let FechaNacimiento(ByVal NewValue As String) ' Si no es una fecha correcta... If IsDate(NewValue) = False Then ' Asignar un error With Err .Number = 13 .Description = "El valor asignado a FechaNacimiento no es una fecha vlida." .Source = "cColega.FechaNacimiento = " & NewValue End With Else m_FechaNacimiento = NewValue End If End Property
440
Esta otra es algo ms complicada, en ella se permite aceptar fechas en diferentes formatos as como tambin en formatos sin separadores, estos son algunos de los "valores" aceptados para la fecha 13/04/2001: 130401, 13042001, 134, 1304, 13/4, 13/4/01, 13/4/2001, 13.4.01, 13.04.01, 13-04-01, 13-4, etc. e incluso valores como 0413, 04.13.01 (en este caso porque no hay duda de que 13 no puede ser un mes...).
Public Property Let FechaNacimiento(ByVal NewValue As String) Dim i As Long Dim s As String ' ' Comprobar si se usan puntos como separador ' si es as, cambiarlos por / Do i = InStr(NewValue, ".") If i Then Mid$(NewValue, i, 1) = "/" End If Loop While i ' ' Comprobar si se usan - como separador ' si es as, cambiarlos por / Do i = InStr(NewValue, "-") If i Then Mid$(NewValue, i, 1) = "/" End If Loop While i ' s = "" Do i = InStr(NewValue, "/") If i Then s = s & Right$("0" & Left$(NewValue, i - 1), 2) & "/" NewValue = Mid$(NewValue, i + 1) End If Loop While i NewValue = s & NewValue ' If InStr(NewValue, "/") Then
441
If Len(NewValue) = 5 Then ' Si es igual a 5 caracteres, es que falta el ao NewValue = NewValue & "/" ElseIf Len(NewValue) < 3 Then ' Si es menor de 3 caracteres es que falta el mes NewValue = NewValue & "/" & CStr(Month(Now)) & "/" End If ElseIf Len(NewValue) < 3 Then NewValue = NewValue & "/" & CStr(Month(Now)) & "/" Else s = "" For i = 1 To 2 s = s & "/" & Mid$(NewValue, (i - 1) * 2 + 1, 2) Next s = s & "/" & Mid$(NewValue, 5) NewValue = s End If NewValue = Trim$(NewValue) ' ' Comprobar si tiene una barra al principio, si es as, quitarla If Left$(NewValue, 1) = "/" Then NewValue = Mid$(NewValue, 2) End If ' Si tiene una barra al final, es que falta el ao If Right$(NewValue, 1) = "/" Then NewValue = NewValue & CStr(Year(Now)) End If ' ' Convertir la fecha, por si no se especifican todos los caracteres ' Nota: Aqu puedes usar el formato que ms te apetezca NewValue = Format$(NewValue, "dd/mm/yyyy") ' ' Si no es una fecha correcta... If IsDate(NewValue) = False Then ' Asignar un error With Err .Number = 13
442
.Description = "El valor asignado a FechaNacimiento no es una fecha vlida." .Source = "cColega.FechaNacimiento = " & NewValue End With Else m_FechaNacimiento = NewValue End If End Property Bien, todo esto que hemos visto hasta ahora es aplicable a cualquier tipo mdulo, no solo a los de clases, (por ejemplo, en ciertas ocasiones es bastante til usar procedimientos Property en los formularios). Ahora vamos a ver cmo podemos usar esta clase en un proyecto y poder comprobar cmo se detectan los posibles errores si la asignacin no es correcta. Para probar, vamos a usar el formulario que tenemos en el proyecto que hemos creado y en el cual debe estar la clase que acabamos de ver. Nota: Deja tu mente en blanco, y presta atencin a lo que sigue... si an asi no consigues entenderlo... apntate a algn curso de meditacin trascendental y despus sigue leyendo...
443
Para clarificar un poco las cosas: (si es que todo esto se puede clarificar...) Cuando aadimos un control a un formulario, Visual Basic crea una variable, (con el nombre que le hemos dado a dicho control), y de forma automtica, (sin que nos enteremos), crea un nuevo objeto que lo asigna a dicha variable; sin embargo con las clases, somos nosotros los que tenemos que decirle que cree un nuevo objeto y lo asigne a dicha variable. Por tanto, despus de declarar una variable para que contenga el objeto, hay que crear un objeto nuevo y asignarlo a dicha variable, todo esto se hace de la siguiente forma: (aunque no siempre tiene porqu ser as... Guille! no compliques ms la cosa, que bastante complicada est ya...), Set oColega = New cColega Esto le indica al VB que cree un nuevo objeto del tipo cColega: New cColega y dicho objeto lo asigne a la variable oColega: Set oColega = Nota: Para asignar un objeto a una variable, se usa SET, si no usamos Set, lo que estaramos asignando a dicha variable sera el contenido de la propiedad por defecto. Por ejemplo, para guardar en una variable el control Text1, haramos esto: Dim unTextBox As TextBox Set unTextBox = Text1 Pero para guardar en una variable el contenido de la propiedad por defecto de Text1, (es decir el contenido de la propiedad Text), haramos esto otro: Dim sUnTexto As String sUnTexto = Text1 Que es lo mismo que hacer: sUnTexto = Text1.Text
Seguramente dirs... tan complicado es de entender? Si est "chupao" Me gustara creer que no te ha parecido tan complicado y que lo has entendido al 100%, pero... No te preocupes! Te aseguro que acabars entendindolo! ya que si no lo haces, te quedars estancado con el Visual Basic 6 y no podrs disfrutar de lo que la nueva versin ofrece... je, je, je... as que... si no lo has entendido... ponte las pilas y aplcate al mximo. Veamos ahora el cdigo del formulario con el cdigo para usar la clase. Este formulario tiene tres etiquetas, tres cajas de texto: Text1(0), Text1(1) y Text1(2) un botn llamado cmdAsignar y un ListBox.
Private Sub cmdAsignar_Click() ' Dimensionamos una variable del tipo del objeto (clase) Dim oColega As cColega ' Creamos un nuevo objeto y lo asignamos a la variable Set oColega = New cColega ' ' Detectamos los errores que se produzcan On Error Resume Next
444
' ' Asignamos a la propiedad Nombre el contenido de Text1(0) oColega.Nombre = Text1(0).Text ' Asignamos a la propiedad email el contenido de Text1(1) oColega.email = Text1(1).Text ' Si se produce un error If Err Then ' Mostrar el mensaje de aviso MsgBox "Se ha producido un error en: " & Err.Source & vbCrLf & Err.Description ' Posicionar el cursor en la caja de textos del email Text1(1).SetFocus ' Limpiamos el error Err = 0 ' Salimos de este procedimiento Exit Sub End If ' ' Asignamos la fecha de nacimiento oColega.FechaNacimiento = Text1(2).Text If Err Then ' Mostrar el mensaje de aviso MsgBox Err.Number & ", se ha producido un error en: " & Err.Source & vbCrLf & Err.Description ' Posicionar el cursor en la caja de textos del email Text1(2).SetFocus ' Limpiamos el error Err = 0 ' Salimos de este procedimiento Exit Sub End If ' ... resto del cdigo List1.AddItem oColega.Nombre & " - " & oColega.FechaNacimiento End Sub
445
Esto lo explico, entre otras cosas, para que sepas que se puede hacer, ya que seguramente lo habrs visto en los ejemplos que acompaan al Visual Basic as como en algunos listados que hayan podido caer en tus manos, incluso (mea culpa) en listados que yo mismo he escrito, aunque prometo que NUNCA ms lo volver a hacer... e incluso te pedira que me dijeras en que listados, (siempre que sean mos), has visto esa forma de declarar las clases, para que pueda corregirlo... Te recomiendo y te rogara, (y si pudiera, te obligara), encarecidamente de que NUNCA crees clases de esta forma. Por qu no es recomendable crear las clases de esta forma, si es ms fcil e incluso ms lgico? Porque crear una clase usando New al mismo tiempo que se declara un objeto aade ms cdigo, (aunque nosotros no lo veamos), adems de que no tenemos el control total sobre cuando se crea el objeto, ya que el objeto se crea cuando intentamos usarlo, no cuando "explcitamente" le indicamos que lo cree... Por qu aade ms cdigo? Porque Visual Basic nunca sabe si el objeto est creado o no, as que, junto a cada acceso al objeto aade una comprobacin para saber si dicho objeto est creado o no, ya que si no est creado, "tiene" que crear uno nuevo para que podamos usarlo.
446
'----------------------------------------------------------------------------' Segundo formulario para "probar" que no todo es lo que parece (14/Abr/01) ' ' Guillermo 'guille' Som, 2001 '----------------------------------------------------------------------------Option Explicit ' Valor interno para la propiedad del Form2 Private m_FechaCreacion As Date Private Sub Form_Load() ' Asignar el valor de la fecha de creacin ' (aunque realmente es la fecha en que se carga el formulario) m_FechaCreacion = Now ' Label1 = "Form creado el: " & m_FechaCreacion End Sub Public Property Get FechaCreacion() As Date Let) ' Propiedad de slo lectura (no existe el procedimiento Property FechaCreacion = m_FechaCreacion End Property En el primer formulario aade dos etiquetas (Label1 y Label2) y dos botones (Command1 y Command2) y aade este cdigo: '----------------------------------------------------------------------------' Formulario para "probar" que no todo es lo que parece (14/Abr/01) ' ' Guillermo 'guille' Som, 2001 '----------------------------------------------------------------------------Option Explicit
447
Private Sub Command1_Click() ' Mostramos el Form2 Form2.Show ' Mostrar la fecha de creacin Label1 = Form2.FechaCreacion End Sub Private Sub Command2_Click() ' Descargar el formulario Unload Form2 ' Si no se asigna Nothing al Form2, ' la fecha mostrada ser la misma que antes... es decir, an existe el objeto 'Set Form2 = Nothing ' ' Una vez descargado, accedemos a la propiedad de la fecha de creacin: Label2 = "El Form2 se cre el: " & Form2.FechaCreacion End Sub Ejecuta el proyecto y pulsa en el Command1 para que se muestre el Form2. Pulsa en el Command2 para descargar el Form2. Fjate que la fecha mostrada es la misma que cuando se cre dicho formulario. Esto prueba que el formulario, a pesar de estar descargado, an existe, ya que conserva el valor de la variable interna y si esa variable existe es que el formulario an est en algn lugar de la memoria. Ahora quita el comentario que hay delante de Set Form2 = Nothing y vuelve a ejecutar el proyecto. Pulsa primero en Command1 y despus en Command2 (igual que antes) Fjate que la fecha mostrada al cerrar el formulario es otra, (realmente es 00:00:00) Esto, aunque parezca que prueba algo... realmente no prueba nada, salvo que la fecha, (guardada en la variable privada), no est asignada. Y no est asignada, porque al asignar Nothing al "objeto" Form2, ste se destruye y con l se destruye la variable. Pero al acceder de nuevo a la propiedad, el Form2 se vuelve a crear ya que, Visual Basic usa una variable, (llamada Form2), como si estuviese declarada con New, ( es decir VB hace algo parecido a esto: Dim Form2 As New Form2), por tanto, cada vez que usamos dicha variable, si el objeto no existe, Visual Basic vuelve a crearlo, pero el valor de la propiedad no se asigna, ya que la asignacin se hace en el evento Load y ese evento slo se produce al cargarse el formulario de forma explcita con Load o al mostrarlo con Show. El evento que siempre se ejecuta al crearse un objeto (o clase), es Initialize, en el caso de los formularios es: Form_Initialize. Por tanto, si en ese evento se asigna el valor de la fecha de creacin a la variable m_FechaCreacion...
448
Private Sub Form_Initialize() m_FechaCreacion = Now End Sub Que valor se mostrar en Label2 despus de asignar Nothing a Form2? 1.- La misma que haba al mostrar el formulario, (la que se muestra en Label1). 2.- El valor 00:00:00 3.- Un valor posterior al mostrado en Label1. Solucin: 3.- Un valor posterior al mostrado en Label1, ya que al acceder a una propiedad de una clase que no existe, es decir, que an no est creada, dicha clase se crea y por tanto se produce el evento Initialize de la clase y es en este evento cuando se asigna el valor de la fecha actual. Si quieres, quita la asignacin que hay en el evento Form_Load del Form2 y vers que an as, el valor es diferente. Te das cuenta del "peligro" de usar New al declarar un objeto? Por qu lo hace Visual Basic con los formularios a pesar de saber que es peligroso? Seguramente para facilitarnos las cosas, pero... (por suerte, esto cambia en la nueva versin de Visual Basic: VB.NET) Para terminar, (seguramente esto har que lo entiendas mejor... en eso confo...), vamos a ver que es lo que ocurre si nos "saltamos" la auto creacin que hace Visual Basic de la variable que contiene la clase Form2, (recuerda que internamente y sin que nos enteremos, declara una variable de esta forma: Dim Form2 As New Form2) Cmo nos saltamos esta auto creacin? Creando nosotros mismos una variable que apunte a la clase Form2, tal y como haramos con cualquier otra clase. Por tanto modifica el cdigo del Form1 para que sea como el que te muestro: (el cdigo del Form2 queda igual) '----------------------------------------------------------------------------' Formulario para "probar" que no todo es lo que parece (14/Abr/01) ' ' Guillermo 'guille' Som, 2001 '----------------------------------------------------------------------------Option Explicit ' ' Variable del tipo Form2 Private elForm2 As Form2 Private Sub Form_Load() ' Creamos el objeto Set elForm2 = New Form2
449
End Sub Private Sub Command1_Click() ' Mostramos el Form2 elForm2.Show ' Mostrar la fecha de creacin Label1 = elForm2.FechaCreacion End Sub Private Sub Command2_Click() ' Descargar el formulario Unload elForm2 ' Destruimos el objeto (lo eliminamos de la memoria) Set elForm2 = Nothing ' ' Si una vez descargado, accedemos a la propiedad de la fecha de creacin: ' ESTO PRODUCIRA UN ERROR, YA QUE LA VARIABLE elForm2 NO EXISTE! Label2 = "El Form2 se cre el: " & elForm2.FechaCreacion End Sub Ejecuta el proyecto y haz la misma prueba que antes, primero pulsa en Command1 y despus en Command2. En esta ocasin se mostrar un error de que la variable no est establecida. Lo comprendes? Cuando asignamos Nothing a una variable que apunta a un objeto, dicha variable se libera de cualquier referencia, es decir, se destruye el objeto al que apuntaba y por tanto ya no est asignada. Aunque todo este rollo te lo he mostrado con formularios en lugar de clases creadas por nosotros, es por dos motivos: El primero es para que lo puedas entender mejor, espero! El segundo es para que te des cuenta que un formulario realmente es una clase. La moraleja de todo esto es para que "desistas" en usar variables de clases dimensionadas con New. Segunda moraleja: si usas formularios con variables creadas por ti mismo, NO USES NUNCA LA VARIABLE CREADA POR VISUAL BASIC, ya que si lo haces, todo lo aprendido se va al garete... y si no, prueba esta lnea, ponla justo despus de la asignacin a Nothing que hacemos a la variable elForm2 en el evento Command2_Click: Label2 = "El Form2 se cre el: " & Form2.FechaCreacion Al usar la variable creada por Visual Basic, no se producir ningn error, pero el objeto sigue estando en el limbo del VB... Je, je, je... que me gusta liar las cosas...
450
Y hasta aqu hemos llegado por hoy... (mucho folln, verdad?) No te preocupes si an no tienes las cosas muy claras, ya que en las prximas entregas seguiremos trabajando con clases. Y ya sabes que la mejor forma de aprender es practicando, as que, practicaremos y practicaremos hasta que aprendas. Nos vemos Guillermo
451
Nota: Para crear el array de tres TextBoxes, aade un TextBox, asgnale 0 (cero) a la propiedad Index. Cpialo y pgalo, cuando te pregunte si quieres crear un array, dile que si. Vuelve a pegar de nuevo para tener tres cajas de textos. Aade la clase cColega que se us en la entrega anterior: Proyecto/Aadir mdulo de clase... y selecciona en la solapa "existente" el mdulo de clase cColega.cls Este es el aspecto del formulario en tiempo de diseo:
Aade el siguiente cdigo y pulsa F5 para probarlo. Despus te explico un poco... Escribe varios datos y pulsa en Aadir. Para mostrar los datos, pulsa en el botn Mostrar o en el botn Mostrar Array.
'----------------------------------------------------------------------------' Entrega 38 (colecciones) (01/Jun/01) ' ' Guillermo 'guille' Som, 2001 '----------------------------------------------------------------------------Option Explicit ' Coleccin para almacenar los datos de los colegas Private mColegas As Collection ' Array para almacenar los datos de los colegas Private maColegas() As cColega Private Sub cmdAsignar_Click()
452
' Dimensionamos una variable del tipo del objeto (clase) Dim oColega As cColega ' Creamos un nuevo objeto y lo asignamos a la variable Set oColega = New cColega ' ' Detectamos los errores que se produzcan On Error Resume Next ' ' Asignamos a la propiedad Nombre el contenido de Text1(0) oColega.Nombre = Text1(0).Text ' Asignamos a la propiedad email el contenido de Text1(1) oColega.email = Text1(1).Text ' Si se produce un error If Err Then ' Mostrar el mensaje de aviso MsgBox "Se ha producido un error en: " & Err.Source & vbCrLf & Err.Description ' Posicionar el cursor en la caja de textos del email Text1(1).SetFocus ' Limpiamos el error Err = 0 ' Salimos de este procedimiento Exit Sub End If ' ' Asignamos la fecha de nacimiento oColega.FechaNacimiento = Text1(2).Text If Err Then ' Mostrar el mensaje de aviso MsgBox Err.Number & ", se ha producido un error en: " & Err.Source & vbCrLf & Err.Description ' Posicionar el cursor en la caja de textos del email Text1(2).SetFocus ' Limpiamos el error Err = 0 ' Salimos de este procedimiento Exit Sub End If '
453
' Aadimos el colega a la coleccin ' (no se comprueba que ya exista ese dato en la coleccin) mColegas.Add oColega ' Mostrar el nmero de colegas que hay en la coleccin lblInfo.Caption = "Hay " & mColegas.Count & " colegas en la coleccin" ' ' Para aadirlo en el array, debemos saber cuantos datos hay ' para poder crear un nuevo elemento en el array y aadirlo Dim i As Long ' Averiguamos cuantos datos hay (el cero no lo tenemos en cuenta) i = UBound(maColegas) ' incrementamos el nmero en uno i = i + 1 ' redimensionamos el array para poder almacenar los datos del nuevo colega ' (usamos Preserve para mantener los datos anteriores) ReDim Preserve maColegas(i) ' Aadimos el nuevo colega al array ' (como asignamos un objeto, hay que usar Set) Set maColegas(i) = oColega End Sub Private Sub cmdMostrar_Click() ' Mostrar el contenido de la coleccin en el List1 ' ' una variable temporal para acceder a cada uno de los colegas Dim oColega As cColega ' List1.Clear ' Recorremos todos los colegas que haya en la coleccin For Each oColega In mColegas List1.AddItem oColega.Nombre & " - " & oColega.FechaNacimiento Next ' ' ' ' ' Tambin se puede hacer de esta otra forma: Dim i As Long '
454
'
For i = 1 To mColegas.Count
' List1.AddItem mColegas(i).Nombre & " - " & mColegas(i).FechaNacimiento ' Next End Sub Private Sub cmdMostrarArray_Click() ' Mostrar el contenido del array en el List1 ' Dim i As Long ' List1.Clear ' Recorremos todos los colegas que haya en el array ' (el ndice cero no lo tenemos en cuenta) For i = 1 To UBound(maColegas) List1.AddItem maColegas(i).Nombre & " - " & maColegas(i).FechaNacimiento Next End Sub Private Sub Form_Load() ' Datos de prueba Text1(0).Text = "Guillermo" Text1(1).Text = "mensaje@elguille.info" Text1(2).Text = "7.6.57" ' ' Creamos el objeto coleccin Set mColegas = New Collection ' lblInfo = "" ' ' Creamos un array vacio ReDim maColegas(0) End Sub Private Sub Text1_GotFocus(Index As Integer) ' Seleccionar el texto al entrar With Text1(Index) .SelStart = 0
455
La explicacin:
Bsicamente el cdigo es parecido en la entrega anterior, lo nico que ahora hacemos es aadir cada dato de los colegas al array y despus, pulsando en Mostrar o en Mostrar Array, se mostrarn los datos en el ListBox. Veamos cada cosa por separado, en primer lugar te explicar lo de la coleccin. Private mColegas As Collection Esta lnea crea una variable del tipo Collection. En el Form_Load se crea el objeto (recuerdas que no basta con declarar las variables?) Set mColegas = New Collection Para aadir nuevos elementos a la coleccin se usa el mtodo Add mColegas.Add oColega Ahora que sabemos cmo aadir datos a la coleccin, vamos a ver cmo mostrar los datos de esa coleccin. Podemos hacerlo de dos formas, una de la forma clsica, recorriendo cada uno de los elementos de la coleccin mediante un bucle FOR con una variable ndice, para saber cuantos elementos hay en la coleccin usamos la propiedad Count: For i = 1 To mColegas.Count Para acceder a cada uno de los elementos de la coleccin, sabiendo el ndice, usaremos la propiedad Item, sta al ser la propiedad por defecto, no es necesario especificarla: mColegas(i).Nombre Sera lo mismo que: mColegas.Item(i).Nombre La otra forma de recorrer todos los elementos de una coleccin es usando FOR EACH, para ello necesitamos un objeto del tipo contenido en la coleccin, para que devuelva el contenido de cada uno de los elementos: For Each oColega In mColegas Para mostrar los datos de cada uno de los elementos, haremos esto: oColega.Nombre Ya que en cada iteracin del bucle, se devuelve uno de los objetos contenidos en la coleccin. Ahora veamos la explicacin para almacenar los datos de los colegas en un array: Empezamos por declarar un array del tipo cColega, ya que lo que queremos almacenar en dicho array son datos del tipo cColega, (en el caso de la coleccin, no era necesario, ya que el tipo Collection puede almacenar cualquier tipo de datos, siempre usa el tipo Variant y un Variant, como ya deberas saber, permite contener cualquier tipo de objeto): Private maColegas() As cColega
456
Iniciamos el array con cero elementos (realmente uno, pero de ndice cero): ReDim maColegas(0) Cuando asignamos un nuevo colega, tenemos que ampliar el array para que pueda almacenar el nuevo dato, para ello tenemos que saber cuantos elementos tiene el array, incrementarlo en uno y redimensionar el array para que pueda contenerlo, pero sin que pierda los datos que ya tuviera: i = UBound(maColegas) ' incrementamos el nmero en uno i=i+1 ' redimensionamos el array para poder almacenar los datos del nuevo colega ' (usamos Preserve para mantener los datos anteriores) ReDim Preserve maColegas(i) Por ltimo asignamos al nuevo elemento el dato en cuestin: Set maColegas(i) = oColega Hay que usar SET porque lo que estamos asignando es un objeto, no un valor. Para recorrer los elementos del array, tenemos que usar el sistema clsico, es decir hacer un bucle FOR con una variable ndice que recorra todos los elementos del array, (el elemento cero no lo tenemos en cuenta): For i = 1 To UBound(maColegas) Para mostrar cada uno de los datos, lo haremos de la siguiente forma: maColegas(i).Nombre Fjate que la forma de mostrar los datos de un array es casi igual que cuando se usaba FOR i = en el ejemplo de la coleccin. Como habrs notado, es ms fcil usar el objeto Collection que manejar un array, entre otras cosas, porque no tenemos que preocuparnos de redimensionar nada ni de saber cuantos elementos tiene ni nada de eso... (siempre y cuando usemos FOR EACH) El objeto Collection no slo sirve para almacenar objetos, tambin se puede usar para almacenar datos "normales" como Integer, Strings, etc. Otra de las ventajas del objeto Collection es cuando queremos eliminar un dato: mColegas.Remove Index Simplemente se le indica que ndice queremos eliminar y ya est. Esto mismo sera un poco ms complicado con un array. Si no te lo crees, prueba a eliminar el elemento nmero x del array, (la solucin te la dar en la prxima entrega). Adems de lo visto, con las colecciones podemos impedir que se aadan elementos que ya existan, para ello se puede indicar una Clave junto con el elemento que se aade, (un ejemplo lo tendremos en la prxima entrega): mColegas.Add Objeto, Clave E incluso en que posicin queremos aadirlo: Antes o detrs de un elemento dado: mColegas.Add Objeto, Clave, AntesDe, DespuesDe Por ejemplo, si queremos aadir un objeto despus del tercer elemento: mColegas.Add oColega, , 3
457
Te recomiendo que hagas tus propias pruebas y as vers cmo funciona. Y esto es todo por ahora. En la prxima entrega veremos cmo crear nuestra propias colecciones a las que podremos aadir opciones de guardar los datos en el disco, poder recuperarlos y cualquier cosa que se nos ocurra... la cuestin ser aadir los mtodos que necesitemos... as que, estate pendiente y paciencia... Nos vemos Guillermo
458
"representaciones", sobre todo si son grficas, pues te lo imaginas o simplemente sigues leyendo, que puede ser que hasta te enteres de lo que estoy hablando...) Para probarlo, muestra el contenido de las dos variables, cambia el contenido de sNombre y vuelve a mostrar, vers que cada una de las variables tiene un valor distinto. Cmo? Que quieres ver el cdigo completo... Valeeee! No insistas!, aqu lo tienes: Crea un nuevo proyecto e inserta este cdigo en el evento Form_Load Show ' Por si se te olvida ponerlo... Dim sNombre As String Dim sCopia As String ' Asignamos el contenido a sNombre y hacemos la copia sNombre = "el Guille" sCopia = sNombre ' Mostramos los contenidos Print "El original: " & sNombre Print "La copia: " & sCopia ' Cambiamos el contenido de sNombre sNombre = "Guillermo" ' Volvemos a mostrar Print Print "El original: " & sNombre Print "La copia: " & sCopia Como podrs comprobar, el original cambia, pero la copia permanece igual. Es decir, el que se cambie el original no afecta a la copia, ya que son dos "cosas" distintas. Pero esto mismo no se puede hacer con los objetos. Al menos no de la forma simple de una asignacin. Para que sepas por dnde van los tiros, practiquemos con un ejemplo. Supongamos, (sigo suponiendo, pero en estos casos, deberas "meterte" de lleno en el tema y convertir estas suposiciones en cdigo), que queremos hacer una copia de un objeto del tipo cColega, (la clase que hemos estado usando en las entregas anteriores y de la cual ms tarde te mostrar el cdigo completo, ya que en las entregas anteriores no lo hice, y slo te mostr parte del cdigo y he recibido algunas quejas indicndome que faltaba cdigo... mea culpa!) Pero para no caer en errores de suposiciones, (algunas veces supongo que entiendes lo que digo y puede que "suponga" mal, as que... dejmonos de suposiciones y vayamos a cosas concretas), te voy a mostrar el cdigo completo para que entiendas lo que quiero explicarte... que con tanto suponer y tanta parrafada, no se si te habrs perdido o seguirs en sintona... Declaramos una variable para que contenga un objeto del tipo cColega y le asignamos algn valor: Dim tColega As cColega ' La variable que contendr un objeto del tipo cColega Set tColega = New cColega ' Ahora est preparada para contener datos
459
tColega.Nombre = "Guille" ' Asignamos los datos tColega.email = "mensaje@elguille.info" Ahora creamos otra variable en la que queremos copiar lo que tiene el objeto tColega: Dim CopiaColega As cColega Set CopiaColega = New cColega ' Creamos un nuevo objeto, aunque, como despus vers, no es necesario Si hacemos esta asignacin: CopiaColega = tColega, recibiremos un error, ya que Visual Basic espera que se estn copiando datos normales, en este caso espera que tanto CopiaColega como tColega tengan propiedades por defecto que se puedan copiar de la misma forma que se copian dos variables normales (como vimos en el caso anterior de sNombre y sCopia) Cuando queremos "copiar" objetos, hay que usar SET, (no comento ms, ya que despus lo aclaro), por tanto, lo que se debera hacer es: Set CopiaColega = tColega Pero vamos a comprobar que no es oro todo lo que reluce. Mostramos el contenido de CopiaColega Text1.Text = "Contenido de CopiaColega: " & vbCrLf & CopiaColega.Nombre & " " & CopiaColega.email Ahora cambiamos el nombre de tColega tColega.Nombre = "Guillermo" Y volvemos a mostrar el contenido de CopiaColega Text1.Text = Text1.Text & vbCrLf & _ "Contenido de CopiaColega despus de cambiar el nombre de tColega: " & vbCrLf & _ CopiaColega.Nombre & " " & CopiaColega.email & vbCrLf
Para probar esto que te digo y puedas ver y no tener que crertelo porque yo te lo diga, en el formulario que tienes creado de la prueba anterior... cmo? que no has probado lo anterior? entonces, tendrs que crear un nuevo proyecto... (si quieres, claro) Aade un CommandButton (Command1) y una caja de texto (Text1) con la propiedad MultiLine = True y ScrollBars = Both y pega el cdigo, (s, lo que est en negrita, incluso la asignacin que da el error, para que compruebes que es verdad y que da un error, as vas aprendiendo de los errores...) Pulsa F5 y haz click en el botn, vers que es lo que ocurre. Si todo va como debera ir, te habr mostrado esto: Contenido de CopiaColega: Guille mensaje@elguille.info Contenido de CopiaColega despus de cambiar el nombre de tColega: Guillermo mensaje@elguille.info
O sea que no tenamos una copia... entonces que tenemos? Pues, dos variables que "apuntan" al mismo objeto. Con las consecuencias que esto tiene: que si se modifica el contenido de una de las variables, el otro objeto tambin "aparenta" que se ha modificado... y digo aparenta, porque en realidad slo existe un objeto del tipo cColega en la memoria, pero hay dos variables que apuntan a ese objeto. En otros lenguajes de programacin sera algo as como un puntero.
460
Como puedes comprobar, me repito, (hace un par de entregas vimos algo de esto), pero es para que te quede bien claro el concepto. Entonces... cmo se puede hacer una copia de un objeto? Pues manualmente, copiando cada una de las propiedades del objeto original en la copia. Para este caso si que es necesario declarar el objeto de destino como NEW, ya que, en el caso anterior, no era necesario crear un nuevo objeto cuando lo que "pretendamos" era "apuntar" al mismo objeto en memoria. Prubalo quitando la lnea con este cdigo: Set CopiaColega = New cColega y vers que funciona igual (de mal) La explicacin es: que al ser una variable que simplemente hace referencia a un objeto ya existente, no es necesario crear un nuevo objeto que despus no se va a usar. Veamos el cdigo de la segunda prueba, haciendo copia de cada una de las propiedades. Aade un nuevo botn e inserta este cdigo, despus prubalo y vers que el cambio del original no afecta a la copia, por la sencilla razn de que son dos objetos totalmente diferentes. Private Sub Command2_Click() ' Creamos una variable y le asignamos datos Dim tColega As cColega ' La variable que contendr un objeto del tipo cColega Set tColega = New cColega ' Ahora est preparada para contener datos tColega.Nombre = "Guille" ' Asignamos los datos tColega.email = "mensaje@elguille.info" ' ' Creamos otra variable en la que queremos copiar ' lo que tiene el objeto tColega: Dim CopiaColega As cColega Set CopiaColega = New cColega ' Ahora copiamos cada una de las propiedades: CopiaColega.Nombre = tColega.Nombre CopiaColega.email = tColega.email ' Mostramos el contenido de CopiaColega Text1.Text = "Contenido de CopiaColega: " & vbCrLf & CopiaColega.Nombre & " " & CopiaColega.email ' Ahora cambiamos el nombre de tColega tColega.Nombre = "Guillermo" ' Y volvemos a mostrar el contenido de CopiaColega Text1.Text = Text1.Text & vbCrLf & "Contenido de CopiaColega despus de cambiar el nombre de tColega: " & vbCrLf & CopiaColega.Nombre & " " & CopiaColega.email & vbCrLf ' En este caso no se ha alterado el valor de CopiaColega... como era de esperar End Sub
461
Pues esto mismo es lo que tendramos que hacer en un mtodo que hiciese una copia del objeto: copiar cada una de las propiedades de dicho objeto. Este tipo de mtodos suele llamarse Clone, (por lo de clonacin o copia idntica), y se podra codificar de esta forma:
Public Function Clone() As cColega ' Esta funcin devolver una copia "nueva" de ste mismo objeto (12/Jul/01) ' Dim nuevoColega As cColega ' Set nuevoColega = New cColega ' ' Asignar cada una de las propiedades de esta clase With nuevoColega .email = Me.email .FechaNacimiento = Me.FechaNacimiento .Nombre = Me.Nombre End With ' Devolver el objeto "cloneado" Set Clone = nuevoColega End Function Te explico un poco todo esto: La funcin devuelve un objeto del mismo tipo que la clase, ya que de lo que se trata es de hacer una copia del objeto en s. Se crea una variable temporal del tipo a devolver, (que es del mismo tipo que el objeto o clase), y se asigna un NUEVO objeto a dicha variable. Se asigna al nuevo objeto los valores del objeto que implementa el mtodo Clone, para ello se usa ME, aunque slo para mayor claridad, de sta forma sabes que se est refiriendo al propio objeto en el que se ha creado la funcin. Por ltimo se devuelve una referencia al nuevo objeto creado y con los datos copiados. El SET es necesario por lo que antes te he comentado de que lo que se quiere "copiar" es el objeto entero. En esta ocasin SET Clone = nuevoColega devuelve una referencia al objeto nuevoColega que no es ni ms ni menos que el NUEVO objeto que acabamos de crear. ' Crear un nuevo objeto ' Un objeto del tipo cColega
462
Para usarlo, simplemente asignaremos a la variable de destino lo que la funcin (o mtodo) Clone devuelva: Set CopiaColega = tColega.Clone que no es ni ms ni menos que una "nueva" copia del objeto tColega. Como puedes comprobar en la funcin Clone lo nico que hacemos es "encapsular" lo que antes hicimos manualmente, es decir "ocultamos" lo que la funcin hace para copiar el objeto, ya que al usuario lo nico que le interesa es saber que se mtodo hace una nueva copia del objeto que lo implementa. Y si aadimos nuevas propiedades a la clase, lo nico que tendramos que hacer es aadir el cdigo correspondiente para que las nuevas propiedades tambin se copien al utilizar el mtodo Clone. Veamos el cdigo necesario para "probar" que todo esto que digo funciona. Aade un nuevo botn al formulario y pega el siguiente cdigo: Private Sub Command3_Click() ' Creamos una variable y le asignamos datos Dim tColega As cColega del tipo cColega Set tColega = New cColega datos tColega.Nombre = "Guille" ' ' Creamos otra variable en la que queremos copiar ' lo que tiene el objeto tColega: Dim CopiaColega As cColega ' ' No es necesario crear el nuevo objeto, ya que el mtodo Clone ' devuelve un nuevo objeto 'Set CopiaColega = New cColega ' ' Hacemos una clonacin Set CopiaColega = tColega.Clone ' ' Mostramos el contenido de CopiaColega Text1.Text = "Contenido de CopiaColega: " & vbCrLf & CopiaColega.Nombre & " " & CopiaColega.email ' Ahora cambiamos el nombre de tColega tColega.Nombre = "Guillermo" ' Y volvemos a mostrar el contenido de CopiaColega Text1.Text = Text1.Text & vbCrLf & "Contenido de CopiaColega despus de cambiar el nombre de tColega: " & vbCrLf & CopiaColega.Nombre & " " & CopiaColega.email & vbCrLf ' ' La variable que contendr un objeto ' Ahora est preparada para contener ' Asignamos los datos
tColega.email = "mensaje@elguille.info"
463
' Usando Clone tampoco se altera el objeto copiado ' ' Cambiemos el contenido del nombre de la copia CopiaColega.Nombre = "Pepe" ' Mostramos el contenido de la propiedad Nombre de los dos objetos Text1.Text = Text1.Text & vbCrLf & "Contenido de tColega.Nombre: " & tColega.Nombre Text1.Text = Text1.Text & vbCrLf & "Contenido de CopiaColega.Nombre: " & CopiaColega.Nombre End Sub Ejecuta el programa y pulsa en el botn que acabas de aadir, vers que te muestra esto: Contenido de CopiaColega: Guille mensaje@elguille.info Contenido de CopiaColega despus de cambiar el nombre de tColega: Guille mensaje@elguille.info Contenido de tColega.Nombre: Guillermo Contenido de CopiaColega.Nombre: Pepe Es decir, que los cambios que se hagan a cualquiera de las dos variables sern independientes y no se "mezclarn", por la sencilla razn de que cada una de las variables "apunta" a un objeto diferente.
Existe otra forma de hacer clonaciones de objetos. Pero para ello hay que tener el Visual Basic 6.0, ya que con las versiones anteriores no funciona. Te dejo el link a la pgina que tiene el cdigo de ejemplo, (y la pgina explicativa), para que sepas cmo hacerlo, por ahora no te doy ms explicaciones que las que pueda haber en los comentarios y en dicha pgina, entre otras cosas porque se tratan temas que an no se han explicado, as que... eso es lo que hay, pero al menos hay algo, que en definitiva es lo importante verdad? Link a la pgina de clonacin de objetos usando Visual Basic 6.0
Y hasta aqu hemos llegado. En la prxima entrega, posiblemente, veremos cmo crear nuestras propias colecciones... espero que en esa ocasin sea as... si es que antes no se me ocurre alguna cosilla nueva... ya veremos! Nos vemos Guillermo P.S.
464
Parece imposible, verdad? Pero... es cierto! (que es posible copiar objetos, no que es imposible...) Aunque tiene sus inconvenientes... si es que se le puede llamar inconveniente a hacer algo que es "habitual" hacerlo con los controles ActiveX.
Cmo se pueden copiar objetos en Visual Basic? Hasta ahora, la nica forma de copiar objetos es creando un mtodo en la propia clase que devuelva un nuevo objeto con el contenido de las propiedades, (normalmente a ese mtodo se le llama Clone), ni tan siquiera usando lenguajes como C++ se podan hacer copias de objetos... Pero ahora, con la versin 6.0 (y posteriores) de Visual Basic, las clases pblicas tienen una propiedad llamada Persistable la cual puede tomar dos valores, por defecto el valor es 0-NotPersistable, pero si se cambia al valor 1-Persistable, se aaden tres nuevos eventos a la clase: InitProperties, ReadProperties y WriteProperties. Si has creado tus propios controles ActiveX, seguramente habrs usado estos eventos, ya que guardando los valores de las propiedades en el objeto PropertyBag puedes mantener valores en las propiedades diferentes a los predeterminados. Para que nos entendamos: Cuando creas un control ActiveX, puedes hacer que los valores asignados a las propiedades sea "recordados" y no se pierdan en el limbo cada vez que abres el formulario en el que est colocado el control. Imaginate el inconveniente, por no usar otra palabra malsonante, si cada vez que cargas un proyecto previamente guardado, tuvieses que asignar el valor del Caption del formulario... Pues lo mismo que el formulario "recuerda" los valores asignados a las propiedades en
465
tiempo de diseo, se puede hacer con los controles creados por nosotros, y todo ello gracias al objeto PropertyBag y a los eventos ReadProperties -leer los valores de las propiedades- y WriteProperties -guardar los valores de las propiedadesCmo sabe Visual Basic que el valor de una propiedad ha cambiado? No lo sabe, pero, aunque lo supiera, como el valor de una propiedad puede cambiar no slo en los procedimientos Let o Set, Visual Basic pone a nuestra disposicin la instruccin PropertyChanged, de esta forma podemos avisarle que una propiedad ha cambiado y as poder guardar el nuevo valor en el objeto PropertyBag. De todas formas, todo esto est bien explicado en la ayuda del Visual Basic, as que si quieres saber ms sobre la forma de "persistir" los valores de las propiedades... ya sabes... Ahora vamos a pasar al tema que nos interesa: Crear nuevas copias de objetos con Visual Basic Los requisitos necesarios para que un objeto creado por un componente ActiveX sea "duplicable" son: Que la clase sea pblica Que la clase tenga asignado el valor 1 a la propiedad Persistable Que las propiedades que nos interese copiar se almacenen en el objeto PropertyBag, (usando el evento WriteProperties) Si tenemos otros objetos incluidos en la clase y nos interesa copiar tambin esos objetos, estos deben ser tambin "Persistables" En el cdigo que sigue, veremos cmo crear nuevas copias de nuestros objetos. El componente de ejemplo tiene dos clases, una con la propiedad Persistable asignada a 1 y la otra asignada a 0. Por tanto, de una se podr hacer copias y de la otra no, al menos usando el mtodo rpido de copiar los objetos semi-automticamente con el objeto PropertyBag. En el cdigo de ejemplo se muestran dos formas diferentes para hacer copias de objetos: En el mtodo Clone de la clase Persistable se usa el objeto PropertyBag, mientras que en la clase NotPersistable se usa lo que hasta ahora hemos tenido que usar para poder hacer copias de un objeto. En el cdigo del formulario de prueba se muestra el cdigo necesario para hacer copias usando un fichero. De esta forma podemos guardar el contenido de las propiedades en un fichero y posteriormente leerlo para que un nuevo objeto tenga los mismos valores... Las posibles utilidades de esta tcnica la dejo a tu imaginacin... Aqu tienes el cdigo, el cual est lo suficientemente comentado, (al menos eso espero), como para que sea fcilmente comprensible. Nota: Los procedimientos GuardarObjeto y LeerObjeto del formulario, muestran la
466
forma de guardar el contenido de un objeto en un fichero y despus poder recuperarlo para crear un nuevo objeto.
(esto mismo no lo acabo de repetir un poco ms arriba?)
Si quieres ms informacin sobre el tema, consulta: Persistencia en datos de componentes, en la ayuda de Visual Basic (MSDN Library) y para saber ms sobre persistencia en los controles ActiveX: Guardar las propiedades de un control, en Generar un control ActiveX.
El cdigo
El cdigo del formulario de prueba y una "captura" del mismo, as como un poco de explicacin de los controles: (este formulario estar en un proyecto que tendr una referencia al componente que tiene las clases mostradas ms abajo) Cuando pulses en el botn "Copiar NotPersistable usando un fichero", te mostrar un error indicando que no se pueden copiar objetos no persistente, sin embargo, al pulsar en "Copiar NotPersistable", se usa el mtodo Clone de la clase, por tanto si que se podr copiar. Por otro lado, al pulsar tanto en "Copiar Persistable" como en "Copiar Persistable usando un fichero", la propiedad "Comentario" no se copiar, ya que el valor de esa propiedad no se guarda en el objeto PropertyBag. Los botones "Refresh..." harn que se muestren los contenidos de las clases en las cajas de texto. El Frame superior mostrar la clase original (la Persitable o NotPersistable, segn el botn pulsado). El Frame inferior mostrar el contenido de la copia realizada a la clase. "Fecha Creacin" indicar la fecha y hora de creacin de la clase... lo aclaro por si piensas que es la creacin de otra cosa... nunca se sabe!
467
'----------------------------------------------------------------------------' tPersistable (22/Ago/99) ' Revisado para el curso bsico (12/Jul/01) ' ' Prueba de copiar objetos usando la propiedad Persitable ' Para ms informacin ver en la MSDN de Visual Basic 6.0: ' ' ' Guillermo 'guille' Som, 1999-2001 '----------------------------------------------------------------------------Option Explicit Persistencia en datos de componentes
468
' Declaramos las variables de los objetos de prueba Private m_Clase1 As cPersistable Private m_Clase2 As cPersistable Private m_Clase3 As cNotPersistable Private m_Clase4 As cNotPersistable Private Sub cmdAsignar_Click(Index As Integer) ' Asignar el contenido de los TextBoxes a las clases ' El botn de indice 0 asigna los valores a las clases bsicas If Index = 0 Then With m_Clase1 .Nombre = Text1(0) .email = Text1(1) .AoNacimiento = Text1(2) .Comentario = Text1(3) End With With m_Clase3 .Nombre = Text1(0) .email = Text1(1) .AoNacimiento = Text1(2) .Comentario = Text1(3) End With Else ' El ndice 1 asigna los valores a las otras clases (las copias) With m_Clase2 .Nombre = Text1(4) .email = Text1(5) .AoNacimiento = Text1(6) .Comentario = Text1(7) End With With m_Clase4 .Nombre = Text1(4) .email = Text1(5) .AoNacimiento = Text1(6) .Comentario = Text1(7) End With
469
End If cmdAsignar(Index).Enabled = False End Sub Private Sub cmdCopiar_Click() ' Copiar el objeto "Persistable" del 1 en el 2 ' Copiarlo usando Clone ' ---La copia se hace usando el objeto PropertyBag Set m_Clase2 = m_Clase1.Clone ' Mostrar el segundo objeto ' Para probar que realmente son objetos diferentes: ' (si no lo fuesen, mostrara el nombre con la palabra <COPIA>) With m_Clase2 .Nombre = "<COPIA> " & .Nombre End With cmdRefresh_Click 0 End Sub Private Sub cmdCopiar2_Click() ' Copiar el objeto "NotPersistable" del 1 en el 2 ' Copiarlo usando Clone ' ---La copia se hace manualmente, es decir propiedad a propiedad Set m_Clase4 = m_Clase3.Clone ' Mostrar el segundo objeto ' Para probar que realmente son objetos diferentes: ' (si no lo fuesen, mostrara el nombre con la palabra <COPIA>) With m_Clase4 .Nombre = "<COPIA> " & .Nombre End With cmdRefresh_Click 1 End Sub Private Sub cmdCopiarF_Click() '//////////////////////////////////////////////////////////////////// //////
470
' El siguiente cdigo es para copiar objetos usando un fichero intermedio '//////////////////////////////////////////////////////////////////// ////// ' Guardar el objeto 1 If GuardarObjeto(m_Clase1) Then ' Si se pudo guardar es que la clase es "persistente", ' por tanto, leerlo y asignarlo al objeto2 If LeerObjeto(m_Clase2) Then ' Mostrar el segundo objeto With m_Clase2 .Nombre = "<COPIA> " & .Nombre End With End If End If cmdRefresh_Click 0 End Sub Private Sub cmdCopiarF2_Click() '//////////////////////////////////////////////////////////////////// ////// ' El siguiente cdigo es para copiar objetos usando un fichero intermedio ' (esto no funcionar, ya que las propiedades no son "persistentes") '//////////////////////////////////////////////////////////////////// ////// ' ' Guardar el objeto 1 If GuardarObjeto(m_Clase3) Then ' Si se pudo guardar es que la clase es "persistente", ' por tanto, leerlo y asignarlo al objeto2 If LeerObjeto(m_Clase4) Then ' Mostrar el segundo objeto With m_Clase4 .Nombre = "<COPIA> " & .Nombre End With cmdRefresh_Click 1
471
End If End If End Sub Private Sub cmdRefresh_Click(Index As Integer) ' Mostrar el contenido de las clases en los TextBoxes ' Tenemos cuidado de los posibles errores que se produzcan On Local Error Resume Next ' El ndice 0 mostrar los contenidos de las clases Persistentes If Index = 0 Then With m_Clase1 Text1(0) = .Nombre Text1(1) = .email Text1(2) = .AoNacimiento Text1(3) = .Comentario Label2(0) = .Copia.Nombre lblFecha(0) = .FechaCreacin End With With m_Clase2 Text1(4) = .Nombre Text1(5) = .email Text1(6) = .AoNacimiento Text1(7) = .Comentario Label2(1) = .Copia.Nombre lblFecha(1) = .FechaCreacin End With Else ' El ndice 1 mostrar los contenidos de las clases No Persistentes With m_Clase3 Text1(0) = .Nombre Text1(1) = .email Text1(2) = .AoNacimiento Text1(3) = .Comentario Label2(0) = .Copia.Nombre lblFecha(0) = .FechaCreacin End With
472
With m_Clase4 Text1(4) = .Nombre Text1(5) = .email Text1(6) = .AoNacimiento Text1(7) = .Comentario ' Esto producir un error si se copia mediante un fichero Label2(1) = .Copia.Nombre ' lblFecha(1) = .FechaCreacin End With End If cmdAsignar(0).Enabled = False cmdAsignar(1).Enabled = False Err = 0 End Sub Private Sub Form_Load() ' Limpiar las cajas de texto Dim i As Long For i = 0 To Text1.Count - 1 Text1(i) = "" Next Label2(0) = "" Label2(1) = "" ' Crear las clases ' Las dos que se pueden copiar: Set m_Clase1 = New cPersistable ' No es necesario crearla con New 'Set m_Clase2 = New cPersistable ' Las dos que no se podrn copiar: Set m_Clase3 = New cNotPersistable Set m_Clase4 = New cNotPersistable
473
' Unos valores de ejemplo: quin es este? Text1(0) = "Guillermo 'guille'" Text1(1) = "mensaje@elguille.info" Text1(2) = 1957 Text1(3) = "El Guille" ' Asignar los valores a la clase cmdAsignar_Click 0 ' Mostrar los valores cmdRefresh_Click 0 ' El objeto de copia debe ser una clase Persistable Set m_Clase1.Copia = New cPersistable ' Prueba usando un objeto no persistente: ' (esto no funcionar, ya que el objeto no es persistente y por tanto ' no puede guardarse en el PropertyBag) 'Set m_Clase1.Copia = New cNotPersistable m_Clase1.Copia.Nombre = "Guillermo" Set m_Clase3.Copia = New cNotPersistable m_Clase3.Copia.Nombre = "Guillermo" End Sub Private Sub Form_Unload(Cancel As Integer) ' Eliminar los objetos previamente declarados Set m_Clase1 = Nothing Set m_Clase2 = Nothing Set m_Clase3 = Nothing Set m_Clase4 = Nothing Set fPersistable = Nothing End Sub Private Sub Text1_Change(Index As Integer) ' Habilitar el botn adecuado si se cambia el contenido de las cajas Dim queClase As Long
474
queClase = 0 If Index > 3 Then queClase = 1 End If cmdAsignar(queClase).Enabled = True End Sub Private Function GuardarObjeto(queClase As IPruebaPersistable, _ Optional ByVal sFic As String = "CopiaObjeto" _ ) As Boolean '-------------------------------------------------------------------------' Guardar el objeto indicado en un fichero de texto (22/Ago/99) ' ' Se usa el parmetro del tipo IPruebaPersistable ya que esa interface ' est implementada en los dos objetos del componente de prueba ' ' Esta funcin devolver: ' ' False si se produjo error True si todo fue bien
'-------------------------------------------------------------------------Dim varTemp As Variant Dim pb As PropertyBag Dim sPath As String On Error GoTo ErrGuardar ' ' Aadir al path de la aplicacin la barra de directorio sPath = App.Path If Right$(sPath, 1) <> "\" Then sPath = sPath & "\" End If ' Instanciacin de un objeto PropertyBag.
475
Set pb = New PropertyBag ' Guarda el objeto en el PropertyBag mediante WriteProperty. pb.WriteProperty sFic, queClase ' Asigna el contenido del PropertyBag a un variable de tipo Variant. varTemp = pb.Contents ' Lo guarda en un archivo de texto. Open sPath & sFic & ".txt" For Binary As #1 Put #1, , varTemp Close #1 GuardarObjeto = True Exit Function ErrGuardar: MsgBox "Error al guardar el objeto en el fichero:" & vbCrLf & _ sFic & vbCrLf & Err.Number & " - " & Err.Description Err = 0 GuardarObjeto = False End Function Private Function LeerObjeto(queClase As IPruebaPersistable, _ "CopiaObjeto" _ Optional ByVal sFic As String = ) As Boolean '-------------------------------------------------------------------------' Leer el objeto del fichero y asignarlo a la clase indicada (22/Ago/99) ' ' Se usa el parmetro del tipo IPruebaPersistable ya que esa interface ' est implementada en los dos objetos del componente de prueba ' ' Esta funcin devolver: ' ' False si se produjo error True si todo fue bien
476
'-------------------------------------------------------------------------Dim varTemp As Variant Dim byteArr() As Byte Dim pb As PropertyBag Dim sPath As String On Error GoTo ErrLeer ' Aadir al path de la aplicacin la barra de directorio sPath = App.Path If Right$(sPath, 1) <> "\" Then sPath = sPath & "\" End If ' Instanciacin de un objeto PropertyBag. Set pb = New PropertyBag ' Lee el contenido de un archivo en una variable de tipo Variant. Open sPath & sFic & ".txt" For Binary As #1 Get #1, , varTemp Close #1 ' Asigna el valor de la variable Variant a una matriz de bytes. byteArr = varTemp ' Asigna el valor a la propiedad Contents del objeto PropertyBag pb.Contents = byteArr ' Instancia el objeto desde el objeto PropertyBag Set queClase = pb.ReadProperty(sFic) LeerObjeto = True Exit Function ErrLeer: MsgBox "Error al leer el objeto del fichero:" & vbCrLf & _ sFic & vbCrLf & Err.Number & " - " & Err.Description Err = 0 LeerObjeto = False End Function
477
'----------------------------------------------------------------------------' IPruebaPersistable (22/Ago/99) ' Revisado para el curso bsico (12/Jul/01) ' ' Interface para usar en el componente PruebaPersistable ' (esta clase no tiene porqu ser Persistable y aunque lo sea no servir de nada, ' es decir: la clase NotPersistable seguir sin poder copiarse) ' ' Guillermo 'guille' Som, 1999-2001 '----------------------------------------------------------------------------Option Explicit ' Aunque estas propiedades estn declaradas como "variables" pblicas, ' al usar Implements se crearn dos procedimientos: Get y Let ' (Get y Set en caso del objeto Copia) Public Copia As IPruebaPersistable Public Nombre As String Public AoNacimiento As Long Public email As String Public Comentario As String ' Propiedad de slo lectura Public Property Get FechaCreacin() As String End Property
'-----------------------------------------------------------------------------
478
' cPersistable (22/Ago/99) ' Revisado para el curso bsico (12/Jul/01) ' ' Componente para hacer copias de objetos usando la propiedad Persitable ' ' Para poder usar esto de la "persistencia" de las propiedades hay que ' asignar a la propiedad Persistable de la clase el valor 1Persistable ' ' Guillermo 'guille' Som, 1999-2001 '----------------------------------------------------------------------------Option Explicit ' Clase genrica para usar tanto con esta clase como con la otra no persistente ' De esta forma se tiene una misma clase para poder acceder a los mtodos y ' propiedades de cualquier clase que la implemente, ' por ejemplo, el mtodo Copia devuelve un objeto de este tipo Implements IPruebaPersistable ' Valor por defecto del ao de nacimiento Private Const cAoNacimiento As Long = 1999 ' Variables privadas para contener los valores de las propiedades Private m_FechaCreacin As String Private m_Copia As IPruebaPersistable Private m_Nombre As String Private m_AoNacimiento As Long Private m_email As String ' Esta propiedad no es persistente, es decir no se guarda en el PropertyBag Private m_Comentario As String
479
Public Property Get AoNacimiento() As Long ' Se devuelve el valor contenido en la variable privada AoNacimiento = m_AoNacimiento End Property Public Property Let AoNacimiento(ByVal NewValue As Long) ' Se asigna el nuevo valor en la variable privada m_AoNacimiento = NewValue ' y se avisa al Visual Basic de que esta propiedad ha cambiado PropertyChanged "AoNacimiento" End Property Public Property Get Comentario() As String Comentario = m_Comentario End Property Public Property Let Comentario(ByVal NewValue As String) ' Como esta propiedad no la hemos hecho "persistente", ' no se llama a PropertyChanged m_Comentario = NewValue End Property Public Property Get Copia() As IPruebaPersistable ' Como lo que se devuelve es un objeto, ' hay que hacerlo usando Set Set Copia = m_Copia End Property Public Property Set Copia(ByVal NewValue As IPruebaPersistable) ' Esta propiedad devuelve un objeto, por tanto se implementa ' como Set en lugar de Let Set m_Copia = NewValue PropertyChanged "Copia" End Property Public Property Get email() As String email = m_email End Property
480
Public Property Let email(ByVal NewValue As String) m_email = NewValue PropertyChanged "email" End Property ' Propiedad de slo lectura ' por eso slo est el procedimiento Get Public Property Get FechaCreacin() As String FechaCreacin = m_FechaCreacin End Property Public Property Get Nombre() As String Nombre = m_Nombre End Property Public Property Let Nombre(ByVal NewValue As String) m_Nombre = NewValue PropertyChanged "Nombre" End Property ' Este procedimiento se ejecuta cada vez que se crea una instancia de la clase Private Sub Class_Initialize() m_AoNacimiento = cAoNacimiento 'Debug.Print "cPersistable_Initialize" ' Esto hara que se quedara sin espacio en la pila, ' ya que al crear una nueva instancia hara que se creara otra dentro de esa ' y as sucesivamente 'Set m_Copia = New cPersistable End Sub Private Sub Class_InitProperties() ' En los controles ActiveX, este procedimiento slo se ejecuta una vez: ' cuando se inserta el control en el contenedor; ' pero en las clases, se ejecuta cada vez que se crea la clase.
481
' Asignamos la fecha y hora actual a la variable privada m_FechaCreacin = Format$(Now, "dd/mmm/yyyy hh:mm:ss") ' indicamos que la propiedad ha cambiado, ' (slo se avisar aqu, ya que esta propiedad es de slo lectura) PropertyChanged "FechaCreacin" End Sub Private Sub Class_ReadProperties(PropBag As PropertyBag) ' Este procedimiento se ejecuta cada vez que se leen los valores ' guardados en el objeto PropertyBag ' Por si se produce algn error On Local Error Resume Next ' Asignamos los valores almacenados a las variables privadas m_Nombre = PropBag.ReadProperty("Nombre") m_AoNacimiento = PropBag.ReadProperty("AoNacimiento", cAoNacimiento) m_email = PropBag.ReadProperty("email") ' Para que la propiedad Comentario sea persistente, quitar el comentario 'm_Comentario = PropBag.ReadProperty("Comentario") m_FechaCreacin = PropBag.ReadProperty("FechaCreacin") ' Set m_Copia = PropBag.ReadProperty("Copia", Nothing) Err = 0 End Sub Private Sub Class_Terminate() 'Set m_Copia = Nothing ' Debug.Print "cPersistable_Terminate" End Sub Private Sub Class_WriteProperties(PropBag As PropertyBag)
482
' Este evento se ejecuta cada vez que se guardan los valores en el objeto ' PropertyBag On Local Error Resume Next PropBag.WriteProperty "Nombre", m_Nombre PropBag.WriteProperty "email", m_email PropBag.WriteProperty "AoNacimiento", m_AoNacimiento, cAoNacimiento ' Si no se guarda la propiedad Comentario, no se podr "clonar" 'PropBag.WriteProperty "Comentario", m_Comentario PropBag.WriteProperty "FechaCreacin", m_FechaCreacin ' Para que el objeto se pueda guardar, debe ser Persistable PropBag.WriteProperty "Copia", m_Copia, Nothing Err = 0 End Sub ' Los procedimientos implementados delegan en las propiedades de la clase ' Se podran asignar las variables privadas, pero entonces habra que ' "avisar" de los cambios llamando a PropertyChanged, adems de que si en ' los procedimientos se hacen algunas comprobaciones, pues... ' por tanto es mejor llamar a los propios mtodos de la clase. ' Private Property Let IPruebaPersistable_AoNacimiento(ByVal RHS As Long) Me.AoNacimiento = RHS End Property Private Property Get IPruebaPersistable_AoNacimiento() As Long IPruebaPersistable_AoNacimiento = Me.AoNacimiento End Property
483
Private Property Let IPruebaPersistable_Comentario(ByVal RHS As String) Me.Comentario = RHS End Property Private Property Get IPruebaPersistable_Comentario() As String IPruebaPersistable_Comentario = Me.Comentario End Property Private Property Set IPruebaPersistable_Copia(ByVal RHS As IPruebaPersistable) Set Me.Copia = RHS End Property Private Property Get IPruebaPersistable_Copia() As IPruebaPersistable Set IPruebaPersistable_Copia = Me.Copia End Property Private Property Let IPruebaPersistable_email(ByVal RHS As String) Me.email = RHS End Property Private Property Get IPruebaPersistable_email() As String IPruebaPersistable_email = Me.email End Property Private Property Get IPruebaPersistable_FechaCreacin() As String IPruebaPersistable_FechaCreacin = m_FechaCreacin End Property Private Property Let IPruebaPersistable_Nombre(ByVal RHS As String) Me.Nombre = RHS End Property Private Property Get IPruebaPersistable_Nombre() As String IPruebaPersistable_Nombre = Me.Nombre End Property Public Function Clone() As cPersistable
484
' Devuelve una copia de esta clase (23/Ago/99) ' Se usa la tcnica descrita en la ayuda de Visual Basic 6.0 ' para copiar objetos usando ficheros de texto, ' aunque en este caso no sea necesario ningn fichero intermedio... ' ' Nota: ' Anteriormente haba usado una NUEVA variable intermedia, ' pero no es necesario, incluso si la variable a la que se asigna con Clone ' ' Dim pb As PropertyBag ' Instanciacin de un objeto PropertyBag. Set pb = New PropertyBag ' Guarda el objeto en el PropertyBag mediante WriteProperty. pb.WriteProperty "CopiaObjeto", Me ' Instancia el objeto desde el objeto PropertyBag Set Clone = pb.ReadProperty("CopiaObjeto") End Function no se ha creado con NEW
'----------------------------------------------------------------------------' cNotPersistable (22/Ago/99) ' Revisado para el curso bsico (12/Jul/01) ' ' Esta clase tiene asignado el valor 0 a la propiedad Persistable, ' por tanto de esta clase no se podr hacer copias ' ' Para poder usar esto de la "persistencia" de las propiedades hay que
485
' asignar a la propiedad Persistable de la clase el valor 1Persistable ' (ver la clase cPersistable) ' ' Guillermo 'guille' Som, 1999-2001 '----------------------------------------------------------------------------Option Explicit Implements IPruebaPersistable Private m_FechaCreacin As String Private m_Copia As IPruebaPersistable Private m_Nombre As String Private m_AoNacimiento As Long Private m_email As String Private m_Comentario As String Private Sub Class_Initialize() 'Set m_Copia = New IPruebaPersistable m_FechaCreacin = Format$(Now, "dd/mmm/yyyy hh:mm:ss") End Sub Private Sub Class_Terminate() 'Set m_Copia = Nothing End Sub ' Los procedimientos implementados delegan en las propiedades de la clase ' Private Property Let IPruebaPersistable_AoNacimiento(ByVal RHS As Long) Me.AoNacimiento = RHS End Property Private Property Get IPruebaPersistable_AoNacimiento() As Long IPruebaPersistable_AoNacimiento = Me.AoNacimiento End Property
486
Private Property Let IPruebaPersistable_Comentario(ByVal RHS As String) Me.Comentario = RHS End Property Private Property Get IPruebaPersistable_Comentario() As String IPruebaPersistable_Comentario = Me.Comentario End Property Private Property Set IPruebaPersistable_Copia(ByVal RHS As IPruebaPersistable) Set Me.Copia = RHS End Property Private Property Get IPruebaPersistable_Copia() As IPruebaPersistable Set IPruebaPersistable_Copia = Me.Copia End Property Private Property Let IPruebaPersistable_email(ByVal RHS As String) Me.email = RHS End Property Private Property Get IPruebaPersistable_email() As String IPruebaPersistable_email = Me.email End Property Private Property Get IPruebaPersistable_FechaCreacin() As String IPruebaPersistable_FechaCreacin = m_FechaCreacin End Property Private Property Let IPruebaPersistable_Nombre(ByVal RHS As String) Me.Nombre = RHS End Property Private Property Get IPruebaPersistable_Nombre() As String IPruebaPersistable_Nombre = Me.Nombre End Property
487
Public Property Get AoNacimiento() As Long AoNacimiento = m_AoNacimiento End Property Public Property Let AoNacimiento(ByVal NewValue As Long) m_AoNacimiento = NewValue End Property Public Property Get Comentario() As String Comentario = m_Comentario End Property Public Property Let Comentario(ByVal NewValue As String) m_Comentario = NewValue End Property Public Property Get Copia() As IPruebaPersistable Set Copia = m_Copia End Property Public Property Set Copia(ByVal NewValue As IPruebaPersistable) Set m_Copia = NewValue End Property Public Property Get email() As String email = m_email End Property Public Property Let email(ByVal NewValue As String) m_email = NewValue End Property ' Propiedad de slo lectura Public Property Get FechaCreacin() As String FechaCreacin = m_FechaCreacin End Property Public Property Get Nombre() As String
488
Nombre = m_Nombre End Property Public Property Let Nombre(ByVal NewValue As String) m_Nombre = NewValue End Property Public Function Clone() As cNotPersistable ' Devuelve una copia de esta clase (23/Ago/99) Dim NewClase As cNotPersistable Dim NewCopia As IPruebaPersistable Set NewClase = New cNotPersistable Set NewCopia = New IPruebaPersistable ' Esto hara que se quedara sin espacio en la pila 'Set NewCopia = Me.Clone ' As funcionara bien With NewCopia .Nombre = Me.Copia.Nombre .AoNacimiento = Me.Copia.AoNacimiento .Comentario = Me.Copia.Comentario .email = Me.Copia.email ' Cmo se copiara el objeto??? ' El objeto debera tener un mtodo Clone para copiarlo. '.Copia = Me.Copia End With ' Asignar cada una de las propiedades a la nueva copia. ' La desventaja es que, si se tienen muchas propiedades... ' pues es ms trabajo y puede que por despiste se olvide algo... With NewClase .Nombre = m_Nombre .AoNacimiento = m_AoNacimiento .email = m_email .Comentario = m_Comentario Set .Copia = NewCopia End With ' Si se devuelve este mismo objeto,
489
' no se crear una nueva instancia de la clase 'Set Clone = Me Set Clone = NewClase End Function
Si quieres bajar el cdigo completo del ejemplo, pulsa este link. (copiar_objetos.zip 10.1 KB) Nerja, 24 de Agosto de 1999 Revisado el 12/Jul/2001 para el curso bsico
490
Vayamos al tema que nos interesa, ya que no es plan de hacer "marketing" gratuito a los distribuidores que "no sponsorizan" este curso bsico... hum! Crea un nuevo proyecto, se crear un proyecto con un formulario. Aade los siguientes controles para que el formulario quede con el aspecto que te muestro a continuacin, (que es parecido al usado en las entregas anteriores de acceso a datos):
Aspecto del formulario en tiempo de diseo Los controles usados son: En la parte superior: cmdMover, un array de 0 a 3 Label1, un array de 0 a 2, Text1, un array de 0 a 2 cmdAdd, cmdActualizar, cmdBorrar El segundo grupo: Label2, Text2, Option1, Option2, cmdBuscar, cmdBuscarSig Elbotn de salir es: cmdSalir En el men Proyecto/Referencias... selecciona Microsoft DAO 3.51 Object Library -aunque tambin puedes seleccionar cualquier otra que empiece por Microsoft DAO... cualquiera de ellas vale... puede que tambin tengas la 3.6 e incluso la 2.5/3.51 Compatibility Library, todo depender de la versin de DAO que tengas instaladaA diferencia de cuando seleccionas un componente, despus de cerrar el cuadro de dilogo no vers nada nuevo en la ventana de herramientas, ya que las referencias no aaden nuevos controles, pero si que aaden nuevas libreras que exponen objetos que podemos usar en nuestra aplicacin... Uno de esos objetos es el objeto Database, el cual se usar para "mantener" una referencia a la base de datos, con la cual podremos abrir recordsets que nos permitirn, entre otras cosas, introducir y modificar los datos de cualquier tabla incluida en la base de datos a la que queramos acceder... Qu es un Recordset? Segn la ayuda de Visual Basic, "Un objeto Recordset representa los registros de una
491
tabla o los registros del resultado de ejecutar una consulta". Una consulta es... eso... una consulta... una especie de bsqueda avanzada, en la que podemos indicar varios criterios de bsqueda... aunque tambin, como vers pronto, es algo ms sencillo que todo eso. No te preocupes que veremos algunos ejemplos de "consultas". Para poder manejar los objetos Database y Recordset, hay que crear unas variables para dichos objetos, por tanto en la seccin de declaraciones del formulario (General/Declaraciones), aade estas lneas: Option Explicit Private db As Database Private rs As Recordset
De esta forma tendremos una variable llamada db que apuntar al objeto Database y otra, llamada rs, que apuntar a un objeto del tipo Recordset. En el evento Form_Load asignaremos los "objetos reales" a esas dos variables. Para poder abrir la base de datos, necesitamos saber el path en el que se encuentra dicha base de datos, para ello vamos a crea una constante, en la cual tendrs que indicar el path correcto, es decir, el sitio exacto en el que se encuentra la base de datos, en nuestro caso ser BIBLIO.MDB, la base de ejemplo que se incluye con Visual Basic. Const sPathBase As String = "C:\Program Files\Microsoft Visual Studio\VB98\BIBLIO.MDB" A continuacin abrimos la base de datos usando la funcin OpenDatabase, la cual devuelve un objeto de la base de datos recin abierta, uno de los parmetros que espera recibir dicha funcin es el path de la base de datos que queremos abrir, en nuestro ejemplo usaremos el contenido de la constante anterior: sPathBase. ' Crear el objeto de base de datos Set db = OpenDatabase(sPathBase) A continuacin creamos el objeto recordset a partir de una "consulta" realizada a la base de datos, en este caso lo que queremos "consultar" son TODOS los campos de la tabla Authors: ' Crear el recordset con la tabla que queremos manipular Set rs = db.OpenRecordset("SELECT * FROM Authors", dbOpenDynaset) La constante dbOpenDynaset le indica al mtodo OpenRecordset que lo que queremos asignar a la variable rs, (de tipo Recordset), es del tipo Dynaset, este tipo de recordset permite mostrar y modificar los datos asignados a dicho recordset; adems de este tipo de recordset, existen otros los cuales slo se pueden usar para "recorrer" los datos o mostrarlos, si as lo prefieres, pero no permiten modificarlos, ese tipo de recordset tambin lo veremos en esta misma entrega. Una vez que tenemos la variable rs, podemos acceder al contenido de la tabla Authors tal y como lo haciamos antes con el control Data. Para que te hagas una idea, rs tendr el mismo funcionamiento que el que tena Data1.Recordset en los ejemplos de las entregas anteriores sobre acceso a datos, en particular las entregas 34 y 35.
492
Como no tenemos un control data, el cual nos permita movernos por los diferentes registros de la tabla, en el formulario hay cuatro botones, que se usarn para esa tarea: movernos tanto al primero, anterior, siguiente o ltimo registro, el cdigo para hacerlo es el que te muestro a continuacin:
Private Sub cmdMover_Click(Index As Integer) On Error Resume Next ' ' Cuando las propiedades BOF y EOF dan como resultado TRUE, ' es que no hay datos, por tanto, salir del procedimiento If rs.BOF = True And rs.EOF = True Then Exit Sub End If ' ' Mover al registro indicado segn el botn pulsado If Index = 0 Then rs.MoveFirst ElseIf Index = 1 Then rs.MovePrevious ElseIf Index = 2 Then rs.MoveNext ElseIf Index = 3 Then rs.MoveLast End If ' ' Si estamos antes del primero. mover al primero If rs.BOF Then rs.MoveFirst ' Si estamos despus del ltimo, mover al ltimo ElseIf rs.EOF Then rs.MoveLast End If ' Si no se ha producido error, mostrar los datos If Err = 0 Then MostrarRegistro End If ' Err = 0 End Sub ' Al ltimo ' Al siguiente ' Al anterior ' Al primero
493
Lo primero que hacemos es comprobar si hay datos en el recordset, para ello comprobamos que ni BOF ni EOF den como resultado un valor verdadero. Cuando BOF es True, significa que el recordset est "antes" del primer registro, por otro lado EOF ser True cuando estemos despus del final de los registros. Cuando estas dos propiedades devuelven un valor verdadero, es que no hay datos en el recordset . A continuacin movemos el "puntero" del recordset al registro indicado, segn el valor de Index sabremos que botn se ha pulsado y por tanto que "accin" tenemos que elegir. En los comentarios puedes ver qu valor corresponde con cada movimiento. Como es posible que el usuario pulse en el botn anterior o siguiente despus de estar al principio o al final respectivamente, tenemos que "controlar" si ya estamos al principio o al final del recordset para posicionarnos en el registro adecuado. Por ltimo, si todo ha ido bien, (no se ha producido un error), llamamos al procedimiento que muestra el contenido del registro activo en las cajas de texto. Esto ltimo lo hace el procedimiento MostrarRegistro, cuyo cdigo es el siguiente:
Private Sub MostrarRegistro() ' Mostrar los datos del registro actual ' A este procedimiento hay que llamarlo cada vez que ' queramos mostrar los datos del registro actual. With rs Text1(0) = .Fields("Au_ID") Text1(1) = .Fields("Author") Text1(2) = .Fields("Year Born") End With End Sub Si le echas un vistazo al cdigo de las pruebas con el control Data, notars que no era necesario llamar expresamente a un procedimiento para que los datos se mostraran, esto era as porque el propio datacontrol se encargaba de actualizar la informacin en los controles que tena "enlazados", pero ahora no tenemos un control que "automatice" esta tarea, as que, tenemos que hacerlo por nuestros medios... por suerte no es tan difcil. Ahora vamos a ver los procedimientos de Aadir un nuevo registro, actualizar uno ya existente y eliminar el registro actual. En el propio cdigo encontrars la explicacin de que es lo que hace cada lnea usada. El cdigo de Aadir un nuevo registro: Private Sub cmdAdd_Click() ' Aadir un nuevo registro With rs .AddNew
494
' Aadimos algn texto, para saber que es un nuevo dato .Fields("Author") = "Nuevo Autor" ' Actualizamos los datos, para que se graben en el recordset .Update End With End Sub El cdigo de Actualizar el contenido de las cajas de texto en el recordset: Private Sub cmdActualizar_Click() ' Guardar el contenido de las cajas de texto With rs ' Antes de actualizar los datos del recordset, ' hay que ponerlo en modo edicin .Edit ' Este campo es autonumrico, as que no asignarlo '.Fields("Au_ID") = Text1(0) + 0 ' Aadimos una cadena vaca al final ' ya que si Text1(1) est vaco, se asignar un valor NULL y dar error .Fields("Author") = Text1(1) & "" sumar 0 ' Idem con el ao de nacimiento, pero como es numrico, se .Fields("Year Born") = Text1(2) + 0 ' Actualizar los datos en el recordset .Update End With End Sub Y por ltimo el cdigo para eliminar un registro... como siempre, borrar es muy fcil... Private Sub cmdBorrar_Click() ' Borrar el registro actual ' Se comprueba que haya algn registro activo, ' para ello se comprueba que no hayamos pasado del principio o el final del Recordset ' ' Comprobar que hay registros, porque si no hay, dar error If Not (rs.EOF Or rs.BOF) Then
495
' Eliminar el registro actual rs.Delete ' ' Movemos al primer registro cmdMover_Click 0 End If End Sub Bien, bsicamente estas tareas son prcticamente iguales a las usadas con el datacontrol, con la salvedad de que hay que mostrar los datos cuando se cambia el registro activo. Ahora veremos el cdigo usado para buscar los datos, el cual es tambin prcticamente lo mismo que cuando se tena el datacontrol. Fjate que la nica diferencia es que en lugar de usar la propiedad Recordset del datacontrol, se usa el recordset "cargado" con los datos y que est contenido en la variable rs. Este es el cdigo de buscar y buscar siguiente, para lo cual usamos los mtodos FindFirst y FindNext respectivamente.
Private Sub cmdBuscar_Click() ' Buscar el primer registro que coincida con el dato buscado Buscar End Sub Private Sub cmdBuscarSig_Click() ' Buscar el siguiente 'Buscar Siguiente:=True Buscar True End Sub Private Sub Buscar(Optional ByVal Siguiente As Boolean = False) ' Procedimiento para buscar el dato indicado (18/Ene/01) ' Si Siguiente = True, se busca a partir del registro activo Dim nReg As Long Dim sBookmark As String Dim sBuscar As String ' ' Iniciamos la deteccin de errores On Error Resume Next '
496
' Buscar la primera coincidencia en el recordset del Data1 If Option1.Value Then ' Convertir el contenido de TextBox en un nmero nReg = Val(Text2) ' en el campo Au_ID sBuscar = "Au_ID = " & nReg End If If Option2.Value Then ' en el campo Author sBuscar = "Author Like '" & Text2.Text & "'" End If ' With rs ' Guardar la posicin anterior, por si no se halla lo buscado... sBookmark = .Bookmark ' If Siguiente = False Then ' Buscar desde el principio .MoveFirst .FindFirst sBuscar Else ' Busca a partir del registro actual .FindNext sBuscar End If ' Devolver un error si no se halla lo buscado ' aunque no siempre es as... If .NoMatch Then Err.Clear MsgBox "No existe el dato buscado o ya no hay ms datos que mostrar." ' Posicionar el recordset en la posicin guardada .Bookmark = sBookmark End If ' Mostrar los datos del registro actual MostrarRegistro End With End Sub
497
Cmo realizar consultas con DAO? Ya hemos visto cmo introducir y modificar registros, tambin hemos visto cmo buscar un dato y seguir mostrando el resto de las coincidencias que se vayan produciendo. Pero hay ocasiones en las que a veces es necesario poder ver TODO el resultado de la bsqueda, incluso a veces nos interesa buscar ms de un dato a la vez. Eso mismo se puede hacer con FindFirst y FindNext, es decir, podemos buscar en ms de un campo del mismo registro el dato o datos que nos interesa a un mismo tiempo. Pero adems de usar recordsets de slo lectura, podemos acelerar esa tarea de "obtener" la informacin que queramos realizando consultas en lugar de ir buscando uno por uno los datos. Independientemente de cmo hagamos la consulta, siempre nos quedar el recurso de poder usar los mtodos FindXXX para buscar a su vez datos dentro del resultado de la consulta, ya que al fin y al cabo el resultado de dicha consulta se almacena en un objeto recordset... que lio! ahora veremos con ejemplos cmo funciona todo esto. Para mostrar el resultado de la "bsqueda" o consulta, vamos a usar un control ListView. Para aadir un control ListView a nuestro proyecto, primero hay que "aadir" el control a la barra de herramientas de Visual Basic, para ello, selecciona la opcin Componentes del men Proyecto y del cuadro de dilogo que te muestra, selecciona Microsoft Windows Common Controls, (pude que a continuacin de este nombre te muestre la versin de Visual Basic, en mi caso, me muestra 6.0, pero tambin la 5.0 (SP2), ya que tengo instalado esas dos versiones de VB. Una vez que has pulsado en Aceptar, vers que se han aadido nuevos controles a la barra de herramientas (ToolBox), selecciona el que representa al Listview y aadelo al formulario, mediante cdigo configuraremos la apariencia. Si quieres puedes crearte un nuevo proyecto para esta "segunda" prueba, ya que, adems de aadir el ListView, tambin vamos a quitar los botones de bsqueda, porque no tiene sentido tenerlos... al menos para el propsito de este ejemplo. El aspecto del formulario sera el siguiente:
498
El cdigo para configurar el ListView en tiempo de ejecucin para mostrar la informacin de la tabla Authors de la base de datos Biblio.mdb lo pondremos en el evento Form_Load y es el siguiente: With ListView1 ' El tipo de Listview que queremos es del tipo "reporte" .View = lvwReport ' Que muestre las lneas de separacin entre datos .GridLines = True ' Que no se puedan modificar los datos del listview .LabelEdit = lvwManual ' Aadimos las cabeceras .ColumnHeaders.Add , , "Au_ID", 900 .ColumnHeaders.Add , , "Autor", 2700 .ColumnHeaders.Add , , "Ao nacimiento", 1500, lvwColumnRight End With Ahora la bsqueda slo se har por el nombre del autor y se mostrarn en la lista todos los autores que coincidan con los datos que queremos buscar. El cdigo del botn Buscar sera el siguiente:
Private Sub cmdBuscar_Click() ' Mostrar los datos en el listview Dim sBuscar As String Dim tRs As Recordset Dim tLi As ListItem ' ' Formar la cadena de la consulta: ' Se busca por el nombre del autor y se muestran clasificados por el nombre sBuscar = "SELECT * FROM Authors WHERE Author LIKE '" & Text2 & "' ORDER BY Author" ' Creamos un recordset del tipo "esttico", el cual no es modificable ' para poder modificarlo, tendra que ser del tipo dbOpenDynamic Set tRs = db.OpenRecordset(sBuscar, dbOpenSnapshot) ' Comprobar que hay datos en el recordset With tRs ' Si no hay datos...
499
If (.BOF And .EOF) Then MsgBox "No se han encontrado los datos buscados" Else ' Mostrar los datos hallados ListView1.ListItems.Clear .MoveFirst Do While Not .EOF Set tLi = ListView1.ListItems.Add(, , .Fields("Au_ID") & "") tLi.SubItems(1) = .Fields("Author") & "" tLi.SubItems(2) = .Fields("Year Born") & "" .MoveNext Loop End If End With End Sub En otra ocasin veremos cmo "disear" un formulario para consultas con distintas opciones de bsqueda, as como para bsqueda en mltiples campos. En la prxima entrega veremos el cdigo que habra que usar para hacer esto mismo pero usando ADO. As que, paciencia y a ser buenos. Nos vemos Guillermo P.S. Este link te permite bajar el cdigo de los dos ejemplos. (basico40_cod.zip 7.98 KB)
500
El formulario de prueba: Crea un nuevo proyecto, se crear un proyecto con un formulario. Aade los siguientes controles para que el formulario quede con el aspecto que te muestro a continuacin, (que es el mismo del ltimo ejemplo de la entrega anterior):
Aspecto del formulario en tiempo de diseo Los controles usados son: En la parte superior: cmdMover, un array de 0 a 3 Label1, un array de 0 a 2, Text1, un array de 0 a 2 cmdAdd, cmdActualizar, cmdBorrar El segundo grupo: Label2, Text2, cmdBuscar
501
Un Listview1 para mostrar los resultados de la bsqueda Una etiqueta (LblData) para mostrar algunos mensajes y el botn de salir es: cmdSalir Las referencias necesarias: En el men Proyecto/Referencias... selecciona Microsoft ActiveX Data Objects 2.6 Library -aunque tambin puedes seleccionar cualquiera de las otras que te muestra, (si es que te muestra alguna ms); a mi me aparecen las versiones 2.0, 2.1, 2.5 y 2.6, la nica que no deberas seleccionar es la 2.0 que ya est obsoletaUna vez aadida la referencia a los objetos ADO, puedes usar los objetos expuestos por esta librera. En este ejemplo usaremos dos de esos objetos, que sern los que en la mayora de los casos usemos: El objeto Connection y el objeto Recordset. El primero es el que permite acceder a la base de datos y el segundo ser el que acceda a los datos propiamente dicho, cuando veas el cdigo seguramente lo entenders y si no lo entiendes... es que deberas leerte TODAS las entregas, empezando por la primera! (tendr que excusarse el Guille de alguna forma si no entiendes lo que explica) Los objetos ADO ms comunes: Normalmente, el objeto Connection suele declararse de forma que sea visible en todo el formulario, salvo en el caso de que aadieses algn mdulo BAS y necesitaras usarlo desde ese mdulo BAS, en cuyo caso, deberas declararlo Pblico o Global, pero como por ahora no es necesario... dejemos las cosas estar... El objeto Recordset de ADO produce eventos, por tanto, si necesitamos acceder a esos eventos, declararemos la variable con WithEvents, de esa forma podemos interceptar los eventos que produzca de la misma manera que lo hacemos con el resto de controles (de esto veremos ms en las entregas de las clases) En el procedimiento Buscar, veremos cmo usar otro recordset, pero de la forma tradicional: sin eventos. Escribe este cdigo en las declaraciones generales del formulario: Option Explicit ' En ADO, se usa el objeto Connection para abrir las bases de datos Private cnn As ADODB.Connection ' Necesitamos los eventos si queremos controlar algunas cosillas Private WithEvents rst As ADODB.Recordset Al cargar el formulario, creamos los objetos y asignamos la informacin correspondiente para abrir la base de datos y crear o llenar el Recordset. Como te dije, la base de datos se abre usando el objeto Connection, del cual usaremos el mtodo Open al cual hay que indicarle el "proveedor" y el nombre de la base de datos: Private Sub Form_Load() ' Text2 = ""
502
' ' Indicar el path correcto de la base de datos ' ACUERDATE DE PONER EL PATH CORRECTO! Const sPathBase As String = "C:\Program Files\Microsoft Visual Studio\VB98\BIBLIO.MDB" ' ' Crear los objetos Set cnn = New ADODB.Connection Set rst = New ADODB.Recordset ' ' Crear la conexin manualmente 97 2000 ' Usar "Provider=Microsoft.Jet.OLEDB.3.51;" para bases de Access ' Usar "Provider=Microsoft.Jet.OLEDB.4.0;" With cnn .ConnectionString = _ "Provider=Microsoft.Jet.OLEDB.3.51;" & _ "Data Source=" & sPathBase & ";" .Open End With ' Indicarle de que tabla vamos a leer los datos rst.Open "SELECT * FROM Authors", cnn, adOpenDynamic, adLockOptimistic ' ' Asignar los nombres de los campos a las etiquetas Label1(0).Caption = "Au_ID:" Label1(1).Caption = "Author:" Label1(2).Caption = "Year Born:" ' With ListView1 ' El tipo de Listview que queremos es del tipo "reporte" .View = lvwReport ' Que muestre las lneas de separacin entre datos .GridLines = True ' Que no se puedan modificar los datos del listview .LabelEdit = lvwManual ' Aadimos las cabeceras .ColumnHeaders.Add , , "Au_ID", 900 .ColumnHeaders.Add , , "Autor", 2700 para bases de Access
503
.ColumnHeaders.Add , , "Ao nacimiento", 1500, lvwColumnRight End With ' ' Si hay datos, posicionarlo en el primer registro: cmdMover_Click 0 End Sub En el ejemplo de la entrega anterior, tenamos un procedimiento al que llambamos cada vez que tenamos que actualizar la informacin del registro que estaba activo. Pero como el Recordset de ADO produce eventos, vamos a usar uno de esos eventos: MoveComplete, el cual se produce cada vez que se cambia el registro activo. Como te coment antes, las variables declaradas con WithEvents siguen la misma "nomenclatura" que los eventos de los controles, por tanto, ese evento estar en: rst_MoveComplete (selecciona el objeto rst de la lista desplegable derecha y el evento MoveComplete de la lista de la izquierda), ste es el cdigo para mostrar los datos cada vez que se cambia el registro activo, aunque realmente no sera necesario si hubisemos "ligado" los controles con el recordset... pero de esa forma no tendramos el control total sobre los datos, as que vamos a seguir con esto de usar los eventos: Private Sub rst_MoveComplete(ByVal adReason As ADODB.EventReasonEnum, _ ByVal pError As ADODB.Error, adStatus As ADODB.EventStatusEnum, _ ByVal pRecordset As ADODB.Recordset) ' Cada vez que el registro actual cambie, ' se producir este evento (igual que con el DataControl) With rst '--------------------------------------------------------------------' Nota aclaratoria del 18/Dic/2003: ' Cuando en un Recordset no hay datos, tanto BOF como EOF devuelven True '--------------------------------------------------------------------If .EOF And .BOF Then lblData.Caption = "No hay ningn registro activo" .MoveFirst Else Text1(0) = .Fields("Au_ID") ' Por si el dato es nulo, aadirle una cadena vacia Text1(1) = .Fields("Author") & "" Text1(2) = .Fields("Year Born") & "" End If
504
End With End Sub En este evento podemos usar tanto el objeto rst como el que est en el parmetro: pRecordset, los dos se refieren al mismo objeto. Hacemos una pequea comprobacin de que no nos encontremos con un recordset vaco o que est fuera de los lmites permitidos, (cosa que ocurre cuando queremos pasar al siguiente registro cuando estamos al final (se produce EOF) o cuando pasamos al registro anterior y estamos al principio, (se produce BOF). Si todo va bien, asignamos a las cajas de textos el contenido de los campos correspondientes. Fjate que en algunos de los campos aado una cadena vaca al contenido del campo, esto es para los casos en que esos campos contengan un valor nulo. Si se lo quitas al Year Born, vers cmo se produce un error, ya que en la base de datos Biblio, que es la que usaremos para este ejemplo, ese campo no suele estar asignado. Ahora te muestro el resto del cdigo para los botones de Mover, Nuevo, Actualizar y Eliminar, que es prcticamente igual al de la versin para DAO, con la salvedad de Actualizar, ya que en ADO no es necesario poner un registro (o columna que es como se debera decir), en modo edicin para poder modificarlo, ya que al usar Update, se actualiza el registro. Private Sub cmdMover_Click(Index As Integer) ' Mover segn el botn pulsado 'On Error Resume Next ' With rst If Index = 0 Then .MoveFirst ElseIf Index = 1 Then .MovePrevious ElseIf Index = 2 Then .MoveNext ElseIf Index = 3 Then .MoveLast End If ' If .BOF Or .EOF Then .MoveFirst lblData.Caption = " No hay datos..." Else lblData.Caption = " Registro actual: " & rst("Au_ID") End If ' ltimo ' Siguiente ' Anterior ' Primero
505
End With ' Err = 0 End Sub Private Sub cmdAdd_Click() ' Aadir un nuevo registro rst.AddNew ' Aadimos algn texto, para que no se pierda este registro Text1(1) = "Nuevo" ' Actualizamos los datos rst.Update ' Movemos al ltimo registro para que los cambios se hagan permanentes ' y se muestre el nuevo registro rst.MoveLast End Sub Private Sub cmdActualizar_Click() ' Guardar el contenido de las cajas de texto With rst ' Este campo es auto numrico, as que no asignarlo '.Fields("Au_ID") = Text1(0) + 0 ' Aadimos una cadena vaca al final ' ya que si Text1(1) est vaco, se asignar un valor NULL y dar error .Fields("Author") = Text1(1) & "" sumar 0 ' Idem con el ao de nacimiento, pero como es numrico, se .Fields("Year Born") = Text1(2) + 0 ' Actualizar los datos en el recordset .Update End With End Sub Private Sub cmdBorrar_Click() ' Borrar el registro actual ' Se comprueba que haya algn registro activo, ' para ello se comprueba que no hayamos pasado del principio o el final del Recordset
506
' ' Comprobar que hay registros, porque si no hay, dar error If (rst.EOF Or rst.BOF) Then ' Avisar de que no hay registros lblData.Caption = "Ningn registro activo" Else ' Eliminar el registro actual rst.Delete ' ' Movemos al primer registro para que los cambios se hagan permanentes ' (tambin podramos haberlo movido al ltimo registro) rst.MoveFirst End If End Sub Creo que no necesita informacin adicional, en los comentarios est todo explicado. Para terminar, vamos a ver el cdigo para Buscar datos: Private Sub cmdBuscar_Click() ' Mostrar los datos en el listview Dim sBuscar As String Dim tRs As Recordset Dim tLi As ListItem ' ' Comprobar si tiene caracteres "no vlidos" para ADO: ' NOTA: Replace es una funcin de VB6 sBuscar = Text2 sBuscar = Replace(sBuscar, "*", "%") sBuscar = Replace(sBuscar, "?", "_") ' Text2 = sBuscar ' Formar la cadena de la consulta: ' Se busca por el nombre del autor y se muestran clasificados por el nombre sBuscar = "SELECT * FROM Authors WHERE Author LIKE '" & sBuscar & "' ORDER BY Author" ' Creamos un recordset del tipo "esttico", el cual no es modificable
507
' para poder modificarlo, tendra que ser del tipo dbOpenDynamic Set tRs = cnn.Execute(sBuscar) ' Comprobar que hay datos en el recordset With tRs ' Si no hay datos... '--------------------------------------------------------------------' Nota aclaratoria del 18/Dic/2003: ' Cuando en un Recordset no hay datos, tanto BOF como EOF devuelven True '--------------------------------------------------------------------If (.BOF And .EOF) Then MsgBox "No se han encontrado los datos buscados" Else ' Mostrar los datos hallados ListView1.ListItems.Clear .MoveFirst Do While Not .EOF Set tLi = ListView1.ListItems.Add(, , .Fields("Au_ID") & "") tLi.SubItems(1) = .Fields("Author") & "" tLi.SubItems(2) = .Fields("Year Born") & "" .MoveNext Loop End If End With End Sub Caracteres comodines: Como podrs comprobar el procedimiento de buscar es muy parecido, por no decir casi idntico al usado en DAO, con la salvedad de que hacemos una comprobacin para cambiar los caracteres comodines que hubiese en el texto indicado para la bsqueda, ya que en ADO no son los mismos que en DAO. Segn la ayuda de ADO, se usar el carcter % (tanto por ciento) en lugar del * (asterisco), cuando queramos indicar cualquier cosa que haya en el sitio en que se encuentra dicho carcter, por ejemplo: %jan% encontrar todos los registros que contenga "jan", est en el sitio que est: Janet, Alejandro, etc. Si pusiramos jan%, slo mostrara los que empezaran con jan, etc. Por otro lado, el carcter _ (subrayado bajo) sustituye a la ? (interrogacin), en este caso este comodn significa "cualquier carcter que est en esa posicin", por ejemplo: %r_us % encontrar cualquier palabra que contenga: una r seguida de cualquier cosa, seguida de us. Tal es el caso de Rouse, Marcus, etc.
508
La funcin Replace usada para cambiar esos caracteres es propia de la versin 6 de Visual Basic, en VB5 no existe, as que tendrs que crearte tu propia funcin Replace. Bueno, vale... aqu la tienes: Private Function Replace(ByVal Expresion As String, _ ByVal Encontrar As String, _ ByVal ReemplazarCon As String) As String '--------------------------------------------------------------' Funcin Replace para usar con VB5 o anteriores '--------------------------------------------------------------Dim i As Long, j As Long ' j = 1 Do ' Buscar la cadena indicada en la expresin i = InStr(j, Expresion, Encontrar) nueva ' Si la hemos hallado, quitamos dicha cadena y ponemos la If i Then Expresion = Left$(Expresion, i - 1) & ReemplazarCon & Mid$(Expresion, i + Len(Encontrar)) j = i + 1 End If Loop While i ' Devolver la cadena Replace = Expresion End Function Esto esto amigos! (y para las amigas nada?, es que cuando se habla de forma genrica, se usa el gnero masculino... como si fuese neutro... s, ya, es que algunos que dicen ser muy masculinos, realmente son neutros... a quin te refieres? no, a nadie en concreto... es por aclarar las cosas... Ah! crea...) Dejemos a los Guilles con su discusin y te espero en la prxima entrega, que an no s de que tratar... ser de clases? ser de acceso a datos? ser sobre algn tema nuevo? ya veremos... Nos vemos Guillermo
509
Buenas, ya estoy de nuevo por aqu... s, se que han pasado un montn de das... pero... En esta entrega vamos a seguir con las clases, en particular con las colecciones personalizadas. Si quieres refrescarte la memoria, podras volver a las entregas anteriores sobre las clases, segn recuerdo son las entregas 37, 38 y 39. En la entrega 38 se trat el tema de las colecciones, pero de forma genrica, en esta entrega veremos cmo crear nuestra propia clase-coleccin para que maneje elementos del tipo que nosotros queramos. Es habitual que los nombres de las colecciones que contendrn elementos de un tipo definido por nosotros, (en este caso de una clase definida por nosotros, no un tipo de datos creado mediante TYPE), tengan un nombre que sea el plural del nombre del objeto que contendr. Por ejemplo, si la coleccin va a contener elementos del tipo cColega, se llamara cColegas. Esto simplemente es una "recomendacin", ya que el nombre que tenga la coleccin es el que t decidas, al Visual Basic le da igual que se llame cColegas o LosColegas o el nombre que se te venga a la cabeza... siempre y cuando sea un nombre "vlido". Para seguir con la clase definida en la entrega 39, vamos a llamarla cColegas y contendr elementos del tipo cColega. Esta coleccin tendr los mtodos habituales en todas las colecciones, recordmoslos: Add, para aadir nuevos elementos. Remove, para eliminar un elemento en cuestin. Count, para saber cuantos elementos contiene. Item, para acceder a dicho elemento. Adems se podr recorrer la coleccin usando For Each, para ello tendremos que recurrir a un serie de asignaciones algo extraas, pero... que al fin y al cabo nos permita darle esa funcionalidad a nuestra clase. Tambin aadiremos otros mtodos, tales como: Clone, para poder hacer copias de la coleccin, pero no una copia por referencia, sino una copia totalmente independiente de la clase/coleccin, tal como hicimos con la clase cColega. Exists, que devolver un valor Verdadero o Falso, segn un elemento est o no en la coleccin. Clear, que eliminar todos los elementos de la coleccin y la dejar preparada para usarla desde cero. Adems, vamos a aadir un mtodo llamado Equals, que permitir comprobar si la un objeto del tipo de la coleccin es igual a otro objeto de ese mismo tipo... esto es til para poder "comparar" dos objetos. Este mismo mtodo se lo aadiremos a la clase cColega.
510
Dicho todo esto y antes de ver el cdigo de la coleccin personalizada, vamos a crear el mtodo Equals de la clase cColega (la cuestin es siempre hacerse de rogar... este Guille!)
Te recuerdo que esto que acabamos de hacer no tiene nada que ver con el mtodo Equals, sino para que nuestra clase-coleccin se pueda usar con bucles For Each. Ahora s! Veamos el cdigo del mtodo Equals, (realmente una funcin que devolver True o False, segn los elementos comparados sea o no iguales).
511
Primero veremos el cdigo y despus te lo explico para que no te quede duda... aunque es muy simple, como podrs comprobar. Public Function Equals(ByVal unColega As cColega) As Boolean ' Comprueba si el objeto pasado en el parmetro es igual a este objeto Dim iguales As Boolean ' ' Si el tipo pasado no es del tipo cColega, no son iguales If (TypeOf unColega Is cColega) Then ' Comprobar si cada una de las propiedades son iguales, ' al estar anidadas, ' slo sern iguales si llega a la ltima comparacin. With unColega If .email = Me.email Then If .FechaNacimiento = Me.FechaNacimiento Then If .Nombre = Me.Nombre Then iguales = True End If End If End If End With Else ' Esta asignacin no es necesaria, ' ya que el valor por defecto de la variable iguales es False iguales = False End If Equals = iguales End Function Veamos qu es lo que hace esta funcin. Como esta funcin comparar el objeto actual con otro del mismo tipo, (en este caso del tipo cColega), el parmetro pasado debe ser del tipo cColega. Fjate que he usado ByVal, pero lo mismo da si se usa ByRef, ya que el Visual Basic no permitir que el parmetro sea de otro tipo... si lo intentas, recibirs un error del tipo TypeMismatch si el parmetro es un objeto, pero no del tipo cColega, o bien un error indicndote que el parmetro debe ser un objeto. An as... es decir, sabiendo que a esa funcin slo llegarn objetos del tipo cColega, he aadido una comparacin que comprueba que realmente el objeto es del tipo cColega, esto slo es para que sepas cmo comprobaras que el objeto sea de ese tipo... ya que es til si nos decidiramos a cambiar el tipo del parmetro por otro ms genrico, ya sea Object o Variant, (preferiblemente Variant, si lo quieres hacer "sper" genrico); de esta
512
forma, el Visual Basic no dara ningn error y ser el cdigo de la funcin el que se encargue de comprobar si son o no iguales. Esta sera la forma de declarar la funcin, ya que el cdigo contenido en ella seguira siendo el mismo que hemos visto hace unas lneas: Public Function Equals(ByVal unColega As Variant) As Boolean Es tu decisin utilizar una u otra forma, todo depende de cmo quieras que el Visual Basic acte e incluso cmo de cuidadoso debe ser el programador que utilice tu clase... si es el programador el que debe tener en cuenta si el objeto pasado a la funcin es del tipo correcto o no, utiliza la primera versin, pero si quieres "curarte en salud" y que no se produzca un error porque ese programador no ha sido lo suficientemente cuidados, utiliza la segunda. (Guille, creo que si le das tu opinin personal... pues...) Vale, cual te recomiendo que uses? Yo me inclinara por la versin "ms estricta", ya que se supone que ese mtodo sirve para comprobar si dos objetos del tipo cColega son iguales, por tanto el que utilice nuestra clase ser el que deba tener en cuenta que as sea... Pero por otro lado... (se nota que el Guille es Gminis y eso de la doble personalidad...), creo que no hay que dar por hecho que el programador ser cuidadoso, por tanto, en los ejemplos, usaremos la segunda versin, la que tiene el parmetro del tipo Variant. De esta forma, podremos hacer algo como esto: (se supone que tenemos una variable a nivel de mdulo llamada mColega, que es del tipo cColega y que se ha instanciado correctamente) ' comparar un objeto del tipo cColega con otro de otro tipo diferente Dim otro As Variant ' otro = "Pepe" ' If mColega.Equals(otro) Then MsgBox "Los dos objetos son iguales" Else MsgBox "Los dos objetos son diferentes" End If Tambin podramos hacer esto, y funcionara correctamente: ' comparar un objeto del tipo cColega con otro de otro tipo diferente Dim otro As Variant ' Set otro = mColega
513
' If mColega.Equals(otro) Then MsgBox "Los dos objetos son iguales" Else MsgBox "Los dos objetos son diferentes" End If En el primer caso nos dira que son diferentes y en el segundo que son iguales. Nota: En el segundo ejemplo, en el que se asigna a la variable otro el objeto del tipo cColega, tambin se podra usar la primera versin de la funcin Equals. Bien, ya que sabemos cmo comprobar si dos objetos son iguales... (Eh! Guille! que dijiste que ibas a explicar cmo funcionaba esto... y te has enrollao tanto con lo de los tipos... que...) ...esto... pues s... veamos cmo funciona todo el cdigo... Ya ha quedado claro para que usamos el TypeOf: para comprobar si el objeto pasado en el parmetro es del tipo cColega, en caso de que as sea, se comprueba cada una de las propiedades del objeto unColega con el contenido en esta clase, (para ello usamos Me). Como puedes comprobar, he anidado cada comparacin dentro de otra, de forma que slo se comprobar la siguiente si la anterior ha dado como resultado un valor verdadero. En caso de que alguna de esas comparaciones "falle", es decir, la misma propiedad de los dos objetos no sean iguales, el contenido de la variable iguales seguir teniendo el valor que Visual Basic le asigna por defecto, en el caso de las variables de tipo Boolean, ser False. Por tanto la funcin devolver un valor falso... cosa que se hace en la ltima lnea de la funcin al devolver el valor contenido en la variable iguales. Slo decirte, que la parte Else de la comparacin que comprueba si unColega es del tipo cColega, no es necesaria, ya que le estamos asignando el valor que ya tiene por defecto. Por tanto, el cdigo completo de la funcin Equals es este: Public Function Equals(ByVal unColega As Variant) As Boolean ' Comprueba si el objeto pasado en el parmetro es igual a este objeto Dim iguales As Boolean ' ' Si el tipo pasado no es del tipo cColega, no son iguales If (TypeOf unColega Is cColega) Then ' Comprobar si cada una de las propiedades son iguales, ' al estar anidadas, ' slo sern iguales si llega a la ltima comparacin. With unColega If .email = Me.email Then If .FechaNacimiento = Me.FechaNacimiento Then If .Nombre = Me.Nombre Then
514
iguales = True End If End If End If End With End If Equals = iguales End Function Ahora s... por fin! podremos ver lo que realmente tenamos que ver...
515
Private mCol As New Collection Consigues lo mismo, pero tambin aades "sobrecarga" o trabajo extra al Visual Basic, ya que cada vez que se utilice la variable mCol, el VB tendr que comprobar si ya existe una instancia en memoria y si no es as, tiene que crearla... Para que lo comprendas mejor, si tenemos el siguiente cdigo: i = mCol.Count el Visual Basic realmente hara lo siguiente: If mCol Is Nothing Then Set mCol = New Collection End If i = mCol.Count Es decir, comprueba si ya est creada la coleccin y si no es as, crea una nueva instancia, con lo cual, cada vez que se vaya a usar el objeto, Visual Basic tiene que comprobar si ya est creado el objeto en memoria! Creo que esto ya te lo he dicho antes, si no es as... apntatelo y que no se te olvide... Una vez aclarado estoy ya que tenemos el cdigo que declara el objeto Collection que usar nuestra clase-coleccin, empecemos por los mtodos que suelen incluirse en todas las colecciones.
516
El parmetro lo declaramos con el tipo Variant para que nos permita usar las dos formas que las colecciones de Visual Basic permite: 1- Utilizar un ndice numrico que ser la posicin del objeto que queremos borrar, el primer elemento ser el 1. 2- Utilizar la clave que hemos usado para el objeto que queremos eliminar. En el caso de que el elemento que queremos eliminar no est en la coleccin, se producir un error, por tanto podramos aadir deteccin de errores, para evitar que eso ocurra. Pero, si no se produce un error, el usuario de nuestra clase no sabr que ese elemento no estaba... y por tanto no "prestara" atencin y podra creer que est trabajando de forma correcta, por tanto vamos a dejar el cdigo as, tal como est. Si quieres crear una versin del mtodo Remove que tenga en cuenta lo que te acabo de comentar, tendrs que declarar Remove como una funcin y si se produce un error devolver un valor Falso y en caso de que se borre correctamente el elemento indicado, devolver un valor Verdadero, de forma que se pueda usar de la siguiente forma: ' oColegas es una variable del tipo cColegas If oColegas.Remove(3) Then Text1 = "Se ha borrado correctamente" Else Text1 = "No se ha borrado el elemento indicado" End If La forma de declarar esta versin de Remove no te lo muestro, para que lo hagas como "ejercicio".
517
Set oColega = mColegas.Add("Guille", "mensaje@elguille.info") El cual tambin permitira aadir elementos a la coleccin sin necesidad de "recibir" el objeto: mColegas.Add "otro Guille", "guille@mvps.org" 4- Una mezcla de los otros: -Si el primer parmetro es un objeto del tipo cColega funcionar como lo indicado en el punto 2 -Si el primer parmetro NO es un objeto del tipo cColega, supondremos que es el nombre. De esta forma podramos aadir objetos a la coleccin de cualquiera de estas formas: Set oColega = New cColega oColega.Nombre = "el Guille" oColega.email = "mensaje@elguille.info" oColega.FechaNacimiento = "07/06/1957" ' mColegas.Add oColega mColegas.Add oColega, "mensaje@elguille.info" mColegas.Add oColega, "mensaje@elguille.info", "07/06/1957" mColegas.Add "el Guille", "mensaje@elguille.info" mColegas.Add "el Guille", "mensaje@elguille.info", "07/06/1957" Set oColega = mColegas.Add("el Guille") Set oColega = mColegas.Add("el Guille", "mensaje@elguille.info") Set oColega = mColegas.Add("el Guille", "mensaje@elguille.info", "07/06/1957") Si despus de estos ejemplos no te sabes mi direccin de correo... en fin. Es decir, podemos crear nuevos objetos de formas muy variadas... Pero lo interesante es saber cmo se hace esto? Y la verdad es que no se si sera conveniente mostrarte el cdigo... (venga Guille, no te hagas de rogar) No es por hacerme de rogar ni aparentar "suspense", es que puede ser que te les... as que, lo mejor es mostrarlos todos, para que vayas comprendiendo mejor las opciones. (y as de camino la entrega es ms larga... no sabes n!) 1- Al estilo del objeto Collection de Visual Basic: Public Sub Add(ByVal Item As cColega, _ Optional ByVal key As String, _
518
Optional ByVal before As Variant, _ Optional ByVal after As Variant) mCol.Add Item, key, before, after End Sub Aqu lo que hacemos es recibir cuatro parmetros, los tres ltimos opcionales. El primero debe ser del tipo cColega, ya que esta coleccin slo admitir objetos de ese tipo. 2- Este es como el anterior, pero sin los dos ltimos parmetros, aunque el segundo ser opcional. 3- En esta forma de hacerlo, los parmetros sern equivalentes a las propiedades del objeto, por tanto se usarn para crear un nuevo objeto del tipo cColega y ese nuevo objeto se aadir a la coleccin y se devolver por la funcin. Public Function Add(ByVal elNombre As String, _ ByVal elEmail As String, _ Optional ByVal laFecha As String) As cColega Dim tColega As cColega Set tColega = New cColega ' With tColega .email = elEmail .FechaNacimiento = laFecha .Nombre = elNombre End With ' usamos el email como clave, para asegurarnos de que sea nico mCol.Add tColega, elEmail ' Devolver el objeto recin creado Set Add = tColega End Function 4- Por ltimo, la versin "multi-uso", que se podr usar como cualquiera de las dos anteriores: Public Function Add(ByVal uno As Variant, _ Optional ByVal elEmail As String, _ Optional ByVal laFecha As String) As cColega Dim tColega As cColega ' ' si el tipo del primer parmetro es un objeto del tipo cColega
519
If TypeOf uno Is cColega Then Set tColega = uno ' si se indican los otros parmetros... If Len(elEmail) > 0 Then tColega.email = elEmail End If If Len(laFecha) > 0 Then tColega.FechaNacimiento = laFecha End If Else ' sino, suponemos que el primer parmetro es el nombre Set tColega = New cColega ' With tColega .email = elEmail .FechaNacimiento = laFecha .Nombre = uno End With End If ' usamos el email como clave, para asegurarnos de que sea nico mCol.Add tColega, tColega.email ' ' Devolver el objeto recin creado Set Add = tColega End Function Para conseguir nuestro objetivo, el primer parmetro ser de tipo Variant, con idea de que permita "cualquier" tipo de dato en ese parmetro. A continuacin comprobamos si dicho parmetro es un objeto del tipo cColega, de ser as, se asignar a la variable que hemos creado dentro del procedimiento y opcionalmente se asignarn el resto de los parmetros, siempre que stos se hayan indicado con algo diferente a una cadena vaca. Si el primer parmetro NO es del tipo cColega, vamos a asumir que es el valor que asignaremos a la propiedad Nombre del objeto que queremos crear. En caso de usarla de esta segunda forma, deberamos especificar tambin el email, ya que es esa propiedad la que usamos como clave del objeto, por la sencilla razn de que no tendremos dos colegas con una misma cuenta de correo... Debido a que es la propia clase cColega la que comprueba si lo que se asigna a la propiedad email es una cuenta de correo, si ese segundo parmetro no lo fuera, se producira un error.
520
mColegas.Item("mensaje@elguille.info").Nombre = "Guille" mColegas.Item(2).Nombre = "Guille" El cdigo ms simple para esta funcin sera el siguiente: Public Function Item(ByVal Index As Variant) As cColega Set Item = mCol(Index) End Function El problema se presentar si el elemento al que queremos acceder no existe en la coleccin. Pero eso podemos solventarlo con algunos de los mtodos que despus aadiremos. Lo que sera interesante es poder usar este mtodo sin tener que indicar la palabra Item, tal como se hace en las colecciones propias de Visual Basic, de forma que esas dos asignaciones pudiramos hacerlas de esta otra forma: mColegas("mensaje@elguille.info").Nombre = "Guille" mColegas(2).Nombre = "Guille" Para conseguirlo, tenemos que indicarle al Visual Basic que el mtodo Item es el mtodo por defecto de la clase. Para ello, tenemos que posicionarnos en la declaracin del procedimiento (aunque no es estrictamente necesario), ahora pulsa en el men Tools>Procedure Attributes... (Herramientas/Atributos de procedimientos), tal como se muestra en esta imagen:
Del cuadro de dilogo que se muestra, selecciona Item de la lista superior, pulsa en el botn Avanzado para que se muestre la otra parte de la ventana y en la lista ID de procedimiento selecciona (Default), tal como se muestra en esta imagen:
521
Para que una propiedad sea la predeterminada Y despus de pulsar Intro para aceptar las selecciones, el mtodo Item ser el predeterminado y por tanto no ser necesario indicarlo para usarlo. Crear un "enumerador" para nuestra coleccin. Para terminar con la coleccin, (no te preocupes que no me he olvidado de que hay ms cosas, me refiero a terminar con los mtodos que se incluyen en las colecciones normales), vamos a crear un mtodo "especial" que permita recorrer los elementos de la coleccin usando For Each. Para conseguir nuestro objetivo, vamos a crear un mtodo que se llame NewEnum y que sea del tipo IUnknown, no voy a entrar en detalles del "por qu", simplemente veremos el cdigo y la forma de "configurar" este procedimiento en el cuadro de dilogo del atributos de procedimientos. Empecemos por el cdigo: Public Function NewEnum() As IUnknown ' Debe ser un miembro oculto y ' el id del procedimiento debe ser -4 ' Set NewEnum = mCol.[_NewEnum] End Function Slo aclararte que los corchetes son necesarios ya que el nombre de la propiedad contiene un carcter no vlido, (empieza por un guin bajo), ste indica que es un miembro oculto... Pero slo con esto no conseguimos nuestro objetivo, tal como se indica en los
522
comentarios, el procedimiento NewEnum debe estar oculto y tener un ID de procedimiento con valor -4, esto lo hacemos mediante el cuadro de dilogo usado para asignar la propiedad predeterminada, tal como podemos ver en la siguiente imagen:
Para crear un enumerador que permita recorrer la coleccin usando For Each Una vez hecho esto, podemos recorrer los elementos de la coleccin de esta forma: ' mostrar el contenido de la coleccin Dim oColega As cColega ' Text1 = "" For Each oColega In mColegas Text1 = Text1 & oColega.Nombre & ", " & _ oColega.email & ", " & _ oColega.FechaNacimiento & vbCrLf Next Aunque tambin podemos hacerlo de la forma clsica: Dim i As Long ' Text1 = ""
523
For i = 1 To mColegas.Count Text1 = Text1 & mColegas(i).Nombre & ", " & _ mColegas(i).email & ", " & _ mColegas(i).FechaNacimiento & vbCrLf Next
Bien, con lo visto hasta ahora, tenemos la misma funcionalidad que la mayora de las colecciones de Visual Basic. Vamos a aadir nuevos mtodos.
524
If Err.Number <> 0 Then Exists = False Else Exists = True End If End Function Nota: A este mtodo tambin podramos llamarlo Contains, (que es como suele llamarse en las colecciones de la nueva versin .NET de Visual Basic).
Tal como hicimos con la clase cColega, vamos a crear un mtodo que permita hacer una copia independiente de la coleccin. Te recuerdo que cuando asignamos un objeto a otro, lo nico que conseguimos es crear una nueva referencia que apunta al mismo objeto que ya est creado en la memoria, es decir slo existe un objeto en la memoria. Pero mediante este mtodo creamos otra copia en la memoria, de forma que los cambios realizados en ella no afectarn al original. El cdigo es bien simple, ya que vamos a aprovecharnos de que la clase cColega ya tiene un mtodo que permite crear copias de ese objeto. Veamos el cdigo: Public Function Clone() As cColegas ' Hacer una copia de esta coleccin ' Dim tColega As cColega Dim tColegas As cColegas ' Set tColegas = New cColegas ' ' Aadir a la nueva coleccin una copia de cada elemento For Each tColega In mCol tColegas.Add tColega.Clone() Next ' Set Clone = tColegas End Function Lo que hacemos es crear una nueva coleccin del mismo tipo de la clase, recorremos cada uno de los elementos contenidos y aadimos una copia, con idea de que se cumpla nuestro objetivo, tener copias independientes.
525
Y para terminar (ya era hora!), nos queda por ver cmo sera el cdigo para el mtodo para saber si dos colecciones son iguales:
526
ser del tipo Variant, no nos mostrara los miembros). A continuacin comprobamos que el nmero de elementos de las dos colecciones sean iguales, en caso de que no lo sean, el valor de la variable iguales no cambiar, con lo cual la funcin devolver un valor False. A partir de este punto asignamos un valor verdadero a esa variable, esto es as porque las siguientes comparaciones buscan valores que diferencien a ambas colecciones... Para ello, hacemos un bucle que recorra todos los elementos, comprobamos si cada uno de los elementos existe en la otra coleccin, si no es as, asignamos un valor falso y salimos del bucle. Si el email es igual en los dos objetos, comprobamos si realmente son iguales, en caso de que no lo sean, asignamos un valor falso y salimos del bucle. Puede que pienses que esto se podra haber simplificado usando slo esta comparacin: If oColega.Equals(oColegas(oColega.email)) = False Then iguales = False Exit For End If Pero esto dara error si en la coleccin pasada por parmetro no contiene el objeto que se comprueba. Por qu? Como hemos visto, si hacemos esto: oColegas(laClave), que es lo mismo que si hacemos esto otro: oColegas.Item(laClave), y dicha clave no est contenida en la coleccin, se producir un error, ya que en el mtodo Item de la coleccin no se hace ningn tipo de deteccin de errores, por tanto si el elemento al que se quiere acceder no existe, se produce un error. (Guille eso lo dijiste antes..., ya lo s, pero as seguro que queda ms claro...) Por esa razn se hace primero la comprobacin de que exista el objeto en la coleccin. Y hasta aqu hemos llegado en esta entrega que tanto se ha hecho esperar... aunque confo que con lo que me he enrollado, no te quejes y espero que tampoco te "empaches" demasiado... je, je. Posiblemente, en la prxima entrega veremos ms cosas referentes a las clases... pero no nos precipitemos que si despus cambio de idea... lo mismo me regaas... Nos vemos Guillermo P.S. Aqu tienes el cdigo completo de las clases y un proyecto de prueba: basico42_cod.zip 6.01 KB P.S.2: Aqu te explico "someramente" lo que es eso del For Each, ya que segn parece slo lo dije "de pasada" en la entrega 38.
527
For Each elemento In laColeccin ' lo que haya que hacer con el elemento Next ...
Ha pasado algo ms de un mes desde la entrega anterior, pero al menos estoy en lo que me propuse: publicar al menos una entrega cada mes, para que no te aburras y as tambin evitar que me eches la bronca por olvidarme de actualizar el Curso Bsico de Visual Basic... s, que algunos me regaan y todo... snif! En esta entrega vamos a continuar con las clases, aunque esta entrega tratar algo que puede que te vuelva un poco ms "turuleta" de lo que ya puedas estar... e incluso puede que genere ms trfico de correo a mi cuenta del que a mi me gustara, ya que puede ser que no te enteres de nada... no, no te estoy llamando ignorante... ni se me ocurrira! pero es que de lo que aqu vamos a tratar es algo que puede que al principio te confunda y acabes por pensar que... no me entero de nada! abandono! y no es eso lo que me gustara, no quiero que abandones... slo piensa que si el Guille se ha enterado... cualquiera se puede enterar! y no lo digo por decir, que a pesar de lo que muchos se creen soy ms torpe que... en fin... no se con que "bicho torpe" compararme, pero seguro que alguno existir... je, je. En serio... si no te enteras, no desesperes... que tarde o temprano acabars por enterarte... te lo aseguro!
528
objeto contiene, as como ocultar el cdigo que maneja dicha informacin. La encapsulacin nos ayuda a olvidarnos de cual es la implementacin realizada en los procedimientos (mtodos y propiedades) de una clase, para que slo nos preocupemos de cmo usarlos. Polimorfismo: Polimorfismo viene del griego y significara algo as como: muchas-formas. Todos los mtodos implementados en las clases deben tener una forma de comportarse, as como ser consistente con la informacin que debe tratar, ya que, como te he comentado antes, la encapsulacin es la caracterstica que permite ocultar cmo estn implementados (o codificados) los mtodos y propiedades de las clases y el polimorfismo sera el contrato firmado para que esos procedimientos se utilicen de forma adecuada. Bueno, no es realmente eso, pero a ver si as lo entiendes, ya que, creo que esto no se entiende hasta que se "demuestra", a ver si con la siguiente "definicin" queda algo ms claro: Se dice que una clase es polimrfica cuando podemos usar sus mtodos y propiedades sin importarnos qu objeto los implementa. Ya que Polimorfismo significa eso: mltiples formas, es decir, si un objeto implementa el mtodo Show, podemos usar ese mtodo siempre de la misma forma sin importarnos el objeto que estemos usando, si esto no es as, no estaremos utilizando de forma correcta el Polimorfismo. El Polimorfismo en Visual Basic se puede usar de dos formas diferentes, segn se compruebe si el procedimiento (o miembro de la clase) pertenece al objeto que lo utiliza, en tiempo de diseo o en tiempo de ejecucin, esto tambin es conocido como compilacin temprana (early binding) o compilacin tarda (late binding) respectivamente. Adems de estas dos caractersticas, aclaremos los siguientes puntos: Mtodos: Los mtodos son las acciones que una clase puede ejecutar, normalmente son procedimientos de tipo Sub o Function. Para que sepamos cuando un procedimiento es un mtodo, debemos pensar si dicho procedimiento realizar una accin sobre los datos que la clase maneja. Esta aclaracin es porque en algunos sitios te dirn que las funciones pueden ser propiedades, pero una funcin siempre es un mtodo, aunque en ocasiones estn implementadas de forma que puedan hacernos pensar lo contrario. Propiedades: Las propiedades de una clase (u objeto) son las caractersticas de esa clase. Las propiedades podran ser tambin las caractersticas de los datos que la clase maneja (o manipula). como deberas saber, las propiedades las podemos declarar de dos formas: creando un procedimiento de tipo Property o bien declarando una variable pblica. No te queda claro? Por ejemplo, si nuestra clase "maneja" a nuestros colegas, (realmente maneja o trata la informacin de nuestros colegas, no a nuestros colegas... aunque algunas veces sera interesante que pudisemos manejar a un colega y "encapsularlo" dentro de una clase, para que nos deje tranquilos... je, je), las propiedades seran los datos o caractersticas que tenemos de nuestros colegas, por ejemplo: el nombre, el correo electrnico, la edad, si est loco... por la msica, por ejemplo, etctera... Mientras que los mtodos podran ser las acciones que queremos realizar sobre esos datos: que los muestre, que los borre o que los cambie, incluso podramos hacer que esa informacin se almacenara en un archivo o en una base de datos, incluso enviarles de forma automtica un mensaje cuando sea su cumpleaos, etc.
529
Ahora empecemos con los quebraderos de cabeza. Cmo podemos usar la caracterstica de la Encapsulacin? Aqu no tenemos que hacer nada especial, el mero hecho de crear un mtodo o una propiedad en una clase ya implica que estamos usando la caracterstica de la encapsulacin. Cmo podemos usar la caracterstica del Polimorfismo? Como te coment antes, hay dos formas de implementar (usar) el polimorfismo, segn se haga en tiempo de diseo o en tiempo de ejecucin. Empecemos viendo cmo hacerlo de la forma "menos recomendada", aunque algunas veces ser la nica, pero te recomiendo que la evites siempre que puedas. Si declaras una variable de tipo Object, (e incluso uno de tipo Variant), podrs asignarle cualquier objeto a dicha variable y acceder a los miembros del objeto, veamos un ejemplo: Si tenemos un objeto del tipo cColega (el cdigo lo vimos en la entrega 37 y se ampli en la entrega 39), podramos tener algo como esto: Private Sub cmdLate_Click() ' declaramos las variables Dim unColega As cColega ' tambin se puede declarar como As Variant Dim o As Object ' ' creamos el objeto de tipo colega Set unColega = New cColega ' y le aadimos algunos datos unColega.Nombre = "Guille" unColega.email = "mensaje@elguille.info" ' ' asignamos el colega (unColega) al objeto (o) Set o = unColega ' mostramos el nombre del colega MsgBox "El colega es: " & o.Nombre ' End Sub Esto funcionara como se espera: mostrara el nombre correcto. Esto es Polimorfismo, aunque se use compilacin en tiempo tardo (late-binding), es decir, el runtime del Visual Basic no sabe nada de la propiedad Nombre del objeto referenciado por la variable o, pero intenta acceder a dicha propiedad y como "sabemos" que la tiene, se muestra el nombre que hemos asignado. En caso de que asignemos a esa variable un objeto que no tenga la propiedad Nombre,
530
el cdigo se compilar correctamente, y no ser hasta que est ejecutndose cuando el VB se de cuenta de que el objeto referenciado por la variable o no tiene una propiedad llamada Nombre, con lo que se producir un error en tiempo de ejecucin. Por ejemplo, si en lugar de acceder a la propiedad Nombre (que si la tiene), queremos mostrar los apellidos mediante este cdigo: MsgBox "El colega es: " & o.Apellidos Como resulta que el objeto referenciado por la variable o (que es del tipo cColega), no tiene una propiedad llamada Apellidos, el runtime de Visual Basic nos mostrar este mensaje cuando llegue a esa lnea de cdigo:
Error en tiempo de ejecucin al acceder a una propiedad que no existe Pero hasta que no llegue a esa lnea no sabremos que haba algo mal. Por supuesto, podemos usar deteccin de errores para que ese mensaje no se muestre, pero eso slo har que no se muestre, no que no se produzca, que es al fin y al cabo lo que debemos evitar. Para poder solventar este tipo de problemas, podemos hacer dos cosas: 1- La que te he dicho antes, utilizar deteccin de errores. 2- Usar un objeto del tipo especfico, as siempre sabremos si dicho objeto tiene la propiedad a la que queremos acceder y si no la tiene, el VB nos avisar en tiempo de diseo, antes de que se compile el programa. Pero, (recuerdas que siempre hay un pero?), qu hacemos si queremos crear un procedimiento que reciba un parmetro de tipo genrico y lo mismo nos sirva para un objeto del tipo cColega como otro de otro tipo distinto?, por ejemplo, si hemos creado una clase llamada cColega2 y que tambin tiene la propiedad Nombre y otras de las incluidas en cColega, adems de algunas nuevas. Pues nada... ya que esto en VB no se puede hacer de forma "limpia"... o si lo prefieres de forma natural o coherente... Lo nico que podramos hacer es usar como parmetro de ese procedimiento una variable de tipo Object, que acepta cualquier tipo de objeto, (incluso formularios y controles), y hacer una serie de comprobaciones, para saber si dicho objeto es de un tipo u otro y usar una variable adecuada a dicho tipo. Veamos el cdigo de la clase cColega2 (versin simplificada) y el de ese procedimiento: La clase cColega2: '-----------------------------------------------------------------------------
531
' cColega2 (21/Dic/02) ' Clase ampliada del tipo Colega ' ' Guillermo 'guille' Som, 2002 '----------------------------------------------------------------------------Option Explicit Public Nombre As String Public Apellidos As String ' Public email As String Public FechaNacimiento As Date
El procedimiento, (que estar, por ejemplo en un formulario): Private Sub mostrar(elObjeto As Object) If TypeOf elObjeto Is cColega Then ' si el objeto es de tipo cColega ' mostrar slo el nombre MsgBox "El colega es: " & elObjeto.Nombre ElseIf TypeOf elObjeto Is cColega2 Then ' si el objeto es de tipo cColega2 ' mostrar el nombre y apellidos MsgBox "El colega es: " & elObjeto.Nombre & " " & elObjeto.Apllidos End If End Sub Lo que aqu hacemos es: recibir un objeto de cualquier tipo como parmetro, despus comprobamos si es del tipo cColega y mostramos el nombre, si es del tipo cColega2, mostramos el nombre y los apellidos. Pero este cdigo, para mi gusto no sera la mejor solucin, todava podemos equivocarnos al teclear cualquiera de las propiedades y no nos daremos cuenta hasta que estemos ejecutando el programa. Si eres una persona observadora, te habrs fijado que en lugar de escribir Apellidos, he escrito Apllidos y esa no es una propiedad de la clase cColega2.
532
Para solucionar ese inconveniente podemos hacer algo como esto: Private Sub mostrar2(elObjeto As Object) If TypeOf elObjeto Is cColega Then Dim colega1 As cColega Set colega1 = elObjeto ' si el objeto es de tipo cColega ' mostrar slo el nombre MsgBox "El colega es: " & colega1.Nombre ElseIf TypeOf elObjeto Is cColega2 Then Dim colega2 As cColega2 Set colega2 = elObjeto ' si el objeto es de tipo cColega2 ' mostrar el nombre y apellidos MsgBox "El colega es: " & colega2.Nombre & " " & colega2.Apellidos End If End Sub Con esto, aunque tengamos que escribir ms cdigo, evitaremos usar propiedades no existentes, ya que ser el propio IDE de Visual Basic el que nos muestre las propiedades y mtodos de cada objeto, tal como podemos comprobar en las siguientes imgenes:
533
Bien, dirs... esto est muy bien, pero... esto es polimorfismo? Pues... si y no... realmente no, aunque podra ser... La verdad es que no. Al menos si entendemos polimorfismo como una de las caractersticas de la programacin orientada a objetos. Pero ni el polimorfismo nos solucionara el problema suscitado por el uso de dos clases (u objetos) diferentes en el mtodo mostrar o mostrar2.
Otra cosa sera que tuvisemos un mtodo Mostrar en cada una de las clases y en el procedimiento mostrar del formulario se usara dicho mtodo para que se muestre lo que se tenga que mostrar, en el caso de cColega slo se mostrara el Nombre y en el caso de cColega2 se mostrara el Nombre y Apellidos, veamos cmo declarar esos mtodos en cada una de las clases y cmo se usara: El mtodo Mostrar de la clase cColega: Public Sub Mostrar() MsgBox "El nombre es: " & Nombre End Sub
El mtodo Mostrar de la clase cColega2: Public Sub Mostrar() MsgBox "El nombre es: " & Nombre & " " & Apellidos End Sub
534
Private Sub mostrar3(elObjeto As Object) If TypeOf elObjeto Is cColega Then Dim colega1 As cColega Set colega1 = elObjeto ' llamar al mtodo Mostrar del objeto colega1.Mostrar ElseIf TypeOf elObjeto Is cColega2 Then Dim colega2 As cColega2 Set colega2 = elObjeto ' llamar al mtodo Mostrar del objeto colega2.Mostrar End If End Sub
La llamada al mtodo mostrar3: Private Sub cmdMostrar3_Click() ' declaramos las variables Dim unColega As cColega Dim o As Object ' ' creamos el objeto de tipo colega Set unColega = New cColega ' y le aadimos algunos datos unColega.Nombre = "Guille" unColega.email = "mensaje@elguille.info" ' ' asignamos el colega (unColega) al objeto (o) Set o = unColega ' lo mostramos mostrar3 o ' ' creamos un objeto del tipo cColega2... Dim unColega2 As cColega2 Set unColega2 = New cColega2 ' y le asignamos algunos datos
535
unColega2.Nombre = "Guille" unColega2.Apellidos = "Som Cerezo" unColega2.email = "mensaje@elguille.info" ' lo mostramos mostrar3 unColega2 End Sub
Aqu tenemos que observar que el cdigo de Mostrar de las dos clases es muy parecido, pero cada uno muestra los datos que el que escribi la clase quiera... a nosotros no debe importarnos, lo nico que nos interesa es que llamando al mtodo Mostrar se mostrarn los datos (esto es encapsulacin). Por otro lado, en el mtodo mostrar3 usamos dichos mtodos para que se muestre el mensaje que corresponda (esto podra ser polimorfismo, pero an no lo es). Para que realmente pudiramos aprovecharnos de las caractersticas del polimorfismo, deberamos poder crear, (en el formulario), un mtodo mostrar que nos permitiera llamar al mtodo Mostrar del objeto pasado como parmetro sin preocuparnos de si el objeto es del tipo cColega o cColega2. Esto lo podemos solucionar mediante las interfaces. Una interfaz es una especie de plantilla que podemos aplicar a nuestras clases. En esa plantilla se definen los mtodos y propiedades, as como la forma en que deben implementarlas las clases que quieran firmar un contrato con esa interfaz. Las clases que usen esa interfaz tendrn una definicin de dichas propiedades y mtodos tal y como lo indica la plantilla. Y para que sirve todo eso? Para que, si una clase implementa dicha interfaz, podamos llamar a los miembros declarados en ella de forma genrica, sin necesidad de usar un objeto intermedio o genrico. Por ejemplo, supongamos que tenemos una interfaz llamada IColega, si dicha interfaz (o interface si lo prefieres), tiene un mtodo Mostrar y tanto la clase cColega como cColega2 la implementan, podramos mostrar los datos del colega usando el siguiente mtodo del formulario: Private Sub mostrar4(elObjeto As Object) If TypeOf elObjeto Is IColega Then Dim elColega As IColega Set elColega = elObjeto elColega.Mostrar Else MsgBox "El objeto no es del tipo IColega" End If End Sub
536
Aqu comprobamos si el objeto es del tipo IColega, si es as, se llama al mtodo Mostrar, despus de hacer una asignacin del objeto pasado como parmetro a una variable del tipo IColega, en caso de que no sea de ese tipo, se mostrar un mensaje de aviso. La declaracin de la interfaz (realmente es una clase) IColega sera algo como esto: '----------------------------------------------------------------------------' IColega (21/Dic/02) ' Interfaz para las clases de tipo Colega ' ' Guillermo 'guille' Som, 2002 '----------------------------------------------------------------------------Option Explicit Public Sub Mostrar() ' no es necesario escribir cdigo ' slo es necesario definirlo End Sub Como podemos comprobar, la declaracin de una interfaz no tiene porqu contener cdigo, slo es necesario indicar cmo se deben declarar los miembros que dicha clase contenga. Si tanto la clase cColega como cColega2 han firmado un contrato para utilizar el mtodo Mostrar tal y como lo indica la interfaz IColega, es decir, si esas dos clases "implementan" dicha interfaz, podremos usar el siguiente procedimiento para llamar al procedimiento mostrar4 del formulario: Private Sub cmdMostrar4_Click() ' declaramos las variables Dim unColega As cColega Dim o As Object ' ' creamos el objeto de tipo colega Set unColega = New cColega ' y le aadimos algunos datos unColega.Nombre = "Guille" unColega.email = "mensaje@elguille.info" ' ' asignamos el colega (unColega) al objeto (o)
537
Set o = unColega ' lo mostramos mostrar4 o ' ' creamos un objeto del tipo cColega2... Dim unColega2 As cColega2 Set unColega2 = New cColega2 ' y le asignamos algunos datos unColega2.Nombre = "Guille" unColega2.Apellidos = "Som Cerezo" unColega2.email = "mensaje@elguille.info" ' lo mostramos mostrar4 unColega2 ' ' incluso podramos llamar al mtodo con otro objeto mostrar4 Command1 End Sub En este caso, el cdigo es casi como el del tercer ejemplo (cmdMostrar3), slo que en lugar de llamar a mostrar3, se llama a mostrar4. Tambin hemos aadido una nueva llamada al mtodo mostrar4, pero pasndole como parmetro cualquier otro objeto, en este caso hemos pasado un parmetro del tipo Command, que por supuesto no implementa IColega, con lo cual se mostrar un aviso indicndonos que no implementa esa interfaz. Seguramente la pregunta que te hars es: Puedo usar ese cdigo directamente y se usar el mtodo Mostrar de esas dos clases? La respuesta es: No. A pesar de que tanto la clase cColega como cColega2 tengan un mtodo llamado Mostrar no significa que hayan firmado un contrato con la interfaz IColega. Por tanto, no podramos usar ese cdigo, al menos tal y como estn declaradas esas dos clases. Para poder usar ese cdigo, las dos clases deberan firmar un contrato con IColega. Entonces, cmo firmo ese contrato para las clases cColega y cColega2? Utilizando la instruccin Implements. Cuando usamos la instruccin Implements, la cual hay que usarla en la parte de las declaraciones de la clase que queremos que la implemente, seguida del nombre de la interfaz a implementar, se crea un nuevo objeto en dicha clase, al cual podemos acceder, desde el IDE de VB, si pulsamos en la lista desplegable de la izquierda, tal como hacemos, por ejemplo, cuando queremos acceder al cdigo de un evento de un formulario, tal como podemos ver en la siguiente captura:
538
Para que se pueda mostrar ese nuevo objeto, tendremos que aadir la siguiente lnea a ambas clases: Implements IColega Despus de hacer esto, tendremos el objeto IColega en esa lista desplegable y al seleccionarla, en la derecha nos mostrar los mtodos que dicha interfaz contiene, en este caso slo tendr uno: Mostrar. Y si lo seleccionamos se mostrar el siguiente cdigo: Private Sub IColega_Mostrar() End Sub Dentro tendremos que escribir el cdigo que se usar cuando se acceda a esta clase mediante un objeto del tipo IColega. Pero como lo que queremos es que se use el cdigo que ya hemos escrito, simplemente llamamos al mtodo Mostrar escrito en nuestra clase, con lo cual, el cdigo completo quedara as: Private Sub IColega_Mostrar() Me.Mostrar End Sub Si la clase IColega tuviera ms de un mtodo, tendramos que escribir cdigo en todos los procedimientos que dicha clase contenga, ya que si implementamos una interfaz, debemos implementar todos los mtodos y propiedades de dicha interfaz. Implementar una interfaz es un compromiso, en el que nos comprometemos a hacer las cosas tal y como dicha interfaz requiere. Te voy a mostrar el cdigo completo de la clase cColega2 para que no te quede duda. '----------------------------------------------------------------------------' cColega2 (21/Dic/02)
539
' Clase ampliada del tipo Colega ' ' Guillermo 'guille' Som, 2002 '----------------------------------------------------------------------------Option Explicit Implements IColega Public Nombre As String Public Apellidos As String ' Public email As String Public FechaNacimiento As Date Public Sub Mostrar() MsgBox "El nombre es: " & Nombre & " " & Apellidos End Sub Private Sub IColega_Mostrar() Me.Mostrar End Sub Fjate que el mtodo IColega_Mostrar est declarado como Private, pero eso no debe preocuparte. Si hacemos lo mismo con la clase cColega, podremos usar el cdigo mostrado anteriormente sin ningn tipo de problema. Para finalizar, aclararte que cuando usamos lo siguiente: Dim elColega As IColega Set elColega = elObjeto elColega.Mostrar A la variable elColega, (que es un objeto del tipo IColega), realmente le estamos asignando slo una parte del cdigo de la clase representada por elObjeto, la parte que implementa dicha interfaz. Es decir, si la variable elObjeto fuese del tipo cColega, no estaramos asignando todo el objeto, slo una parte del mismo. Esto podemos comprobarlo si queremos acceder a la propiedad Nombre: s = elColega.Nombre
540
Esto dara error, ya que el objeto elColega, (que como sabemos es del tipo IColega), no tiene una propiedad llamada Nombre. Slo me queda decirte que en Visual Basic todas las clases se pueden usar con la instruccin Implements. Es decir, no tenemos porqu crear una clase especfica para crear una interfaz. Pero te recomiendo que lo hagas as, para que sea ms evidente y sobre todo porque as es como tendrs que hacerlo cuando "migres" a la versin .NET de Visual Basic. Ahora s, hasta aqu hemos llegado en esta ocasin. En la prxima entrega seguiremos viendo ms cosas relacionadas con las clases y, casi con seguridad, un ejemplo ms prctico sobre la ventaja de usar esto de la implementacin de interfaces. Pero eso ser... casi con seguridad, el ao que viene, mientras tanto... espero que pases unas Felices Fiestas... s, aunque no seas creyente... que no hay que ser creyente para, aunque sea una vez al ao, desear cosas buenas a la gente... je, je. Nos vemos Guillermo
Aqu tienes el fichero zip con el cdigo usado en esta entrega: basico43_cod.zip 5.02 KB
541
ltimas entregas, es precisamente las clases, el polimorfismo y las interfaces... pues eso... As que, sin ms prembulos, empecemos, (o mejor dicho), sigamos con esta entrega. Antes de empezar a mostrarte el cdigo, voy a explicarte un poco el planteamiento que vamos a hacer, para que te resulte ms fcil seguir el cdigo que te mostrar. Tendremos una clase con una serie de propiedades, que en este caso simplemente son para poder hacer las pruebas y el ejemplo, seguramente no ser de mucha utilidad, pero con ella veremos los conceptos, para que puedas aplicar los tuyos propios, ya que, lo que me gustara es que "siempre" te quedes con la base de lo que te explico, no que uses el cdigo directamente, ya que con ello no llegars a aprender, y de lo que aqu se trata es que aprendas a hacer las cosas para que despus lo apliques como ms te convenga. Como te comentaba en la entrega anterior, en la versin de Visual Basic de la que trata este curso, (la 6.0 y anteriores, realmente hasta la 5, ya que las anteriores no aceptan la implementacin y otras de esas moneras de la OOP), se puede usar cualquier clase como una interfaz a implementar. Esa clase puede contener cdigo, aunque cuando se implementa ese cdigo "desaparece" y slo se queda "la firma" o estructura de la clase, es decir los mtodos y propiedades pblicas que contenga. Tambin te recomend que en lugar de usar una clase para realizar la implementacin, crearas una clase en la que slo estn definidos los miembros de la interfaz, por tanto aqu vamos a seguir las recomendaciones que te di en la entrega anterior, para que no digas que es muy fcil recomendar como hacer las cosas y despus hacerla de una forma diferente. Una cosa que debes saber, no se si ya te lo he dicho, es que cuando declaramos una variable pblica en una clase, esa variable se convierte automticamente en una propiedad. Por tanto, si slo declaramos las variables, en lugar de crear propiedades propiamente dichas, cuando implementemos esa clase, se crearn los correspondientes procedimientos Property Let y Property Get. Te digo esto para que no te extraes cuando te ocurra. De todas formas, en los ejemplos que voy a utilizar en esta entrega, definir los miembros de la interfaz de la forma "correcta", es decir usando Property Let/Get en lugar de declarar las variables, realmente slo se declararn las propiedades de lectura (Get), ya que no es necesario declarar las de escritura (Let), entre otras cosas, porque no se van a usar.
La aplicacin que vamos a usar de ejemplo, tendr un formulario principal y otro que ser el que se utilice para configurar ciertos valores, adems tendremos dos clases, una de ellas ser la interfaz y la otra, la clase propiamente dicha, en la que se almacenarn los datos que usaremos. Seguramente, cuando veas el cdigo y el diseo de los formularios, te parecern iguales o muy parecidos, esto es as, para simplificar, ya que no es plan de hacer una aplicacin demasiada "completa" para ver el "corazn" de lo que aqu te quiero explicar. Por supuesto, tendrs que hacer algo por tu propia cuenta si quieres que el ejemplo sea ms "sofisticado", ya que, como te he comentado al principio, lo que me interesa ensearte es cmo hacerlo, no darte el cdigo completo para que lo copies y pegues... es que si no pones de tu parte, no llegars a aprender... ya que considero que con todo lo que hasta ahora he explicado en este curso bsico, deberas tener la "base" suficiente para que no te resulte difcil investigar por tu cuenta y riesgo, de forma que amples los conocimientos que, se supone, hasta ahora has ido acumulando.
542
Veamos el aspecto del formulario principal, as como el de configuracin. Como te he comentado, el formulario principal tendr una serie de controles, los cuales se usarn para almacenar unos datos y esos datos se podrn modificar con el de configuracin, para que el funcionamiento "aparente" de los dos formularios no sea el mismo, el formulario principal leer y almacenar los datos en un fichero de texto. Pero quiero que pongas en funcionamiento tu imaginacin e imagines que el formulario principal hace ms cosas... as no te parecer que los dos formularios hacen lo mismo. De todas formas, la parte de Leer y Guardar te las dejo a ti como ejercicio, as comprobars si has aprendido algo... Este es el aspecto de los dos formularios que usaremos en este ejemplo:
543
En el formulario de configuracin, el botn Aplicar se habilitar o deshabilitar segn se hayan o no modificado los datos, cuando se pulse en dicho botn, se asignarn los nuevos valores a una variable basada en una clase. Cuando se pulse en Aceptar, tambin se asignarn esos valores y se ocultar el formulario para que el formulario principal tome nuevamente el control. Cuando se pulse en Cancelar, se descartarn los cambios y se ocultar el formulario para poder volver al formulario principal. Nota (o truco): Si a la propiedad StatUpPosition del formulario de configuracin le asignas el valor 1 - CenterOwner, har que se muestre centrado con respecto a la posicin del formulario principal.
Asignar los botones predeterminados de un formulario para Cancelar y Aceptar Cuando se hace un formulario al estilo del mostrado para la configuracin, el botn Cancelar se suele asociar con la tecla ESC y el de Aceptar con la tecla Intro, de forma que cuando se pulse cualquiera de esas dos teclas se ejecuten las acciones indicadas en esos botones. Esta funcionalidad se consigue asignando unas propiedades que todos los botones (CommandButton) tienen: Para asignar un botn como predeterminado (se ejecuta el evento Click al pulsar Intro), hay que asignar un valor True a la propiedad Default. Por otro lado, para que un botn acte como receptor de la tecla Escape, hay que asignar un valor True a la propiedad Cancel. Despus veremos el cdigo de este formulario, aunque antes hay que saber cmo hacer que se muestre dicho formulario... y esperar a que se elijan las opciones.
Cmo llamar a un formulario desde otro Aunque esto ya lo hemos visto, (creo), vamos a verlo nuevamente, aunque sea de pasada. Para que un segundo formulario se muestre, podemos hacerlo de varias formas, aunque la forma en que dicho formulario se comportar slo puede ser de dos formas distintas: 1- Que se muestre, pero que el formulario que lo ha llamado (o mostrado) siga ejecutndose, es decir no se espera a que el segundo formulario termine. Esto puede ser til cuando necesitamos que ese segundo formulario acte de forma independiente del primero, pero ese no es el comportamiento esperado cuando se muestra un formulario, como en nuestro caso, que se vaya a usar como configuracin. 2- Que se muestre, pero que hasta que no se cierre u oculte, el formulario que lo ha llamado no contine. Este caso es el que nos interesa, adems de que es el que normalmente utilizan los formularios de configuracin, tambin llamados "cuadros de dilogo".
544
Para el primer caso se usara lo siguiente: NombreFormulario.Show o tambin de esta forma: NombreFormulario.Show vbModeless Es decir, mustralo pero en forma no-modal (o no como cuadro de dilogo). Para el segundo caso, habr que indicarle al mtodo Show que se quiere mostrar en modal, es decir como un cuadro de dilogo, para ello habr que usar: NombreFormulario.Show vbModal Los valores de vbModal y vbModeless realmente son constantes de Visual Basic cuyos valores son uno y cero respectivamente. Como te he comentado, la principal diferencia entre estas dos formas de mostrar un formulario, es que en modo no-modal, se mostrara el formulario, pero se continuara en la siguiente lnea, por ejemplo: Form2.Show MsgBox "Despus de mostrar el formulario" En este caso, se mostrara el formulario indicado (Form2) y justo despus de mostrarse, se ejecutara la instruccin MsgBox y el formulario seguira abierto. Pero si lo mostramos de forma modal: Form2.Show vbModal MsgBox "Despus de mostrar el formulario" La instruccin MsgBox no se ejecutara hasta que no se cerrara u ocultara el Form2. Es decir, esperara a que se cierre el formulario antes de continuar con la siguiente instruccin. En el ejemplo que veremos aqu se usa esta segunda forma.
Una vez hechas estas aclaraciones, veamos el cdigo, empezando por la interfaz IConfig, la cual se implementar tanto en la clase "real" cConfig, como en el formulario de configuracin, al cual llamaremos fConfig.
La clase (interfaz) IConfig: '----------------------------------------------------------------------------' IConfig (06/Ene/03) ' Interfaz para usar con un formulario de configuracin
545
Public Property Get Valor1() As String End Property Public Property Get Valor2() As String End Property Public Property Get Valor3() As String End Property Public Property Get Opcion1() As Boolean End Property
Como puedes comprobar, slo se definen los procedimientos de lectura (Property Get), pero no se utiliza ningn cdigo "operativo", ya que esta clase slo se usa como "plantilla".
La clase cConfig: '----------------------------------------------------------------------------' cConfig (06/Ene/03) ' Clase para usar con un formulario de configuracin ' ' Guillermo 'guille' Som, 2003 '----------------------------------------------------------------------------Option Explicit
546
Implements IConfig Public Valor1 As String Public Valor2 As String Public Valor3 As String Public Opcion1 As Boolean Private Property Get IConfig_Opcion1() As Boolean IConfig_Opcion1 = Opcion1 End Property Private Property Get IConfig_Valor1() As String IConfig_Valor1 = Valor1 End Property Private Property Get IConfig_Valor2() As String IConfig_Valor2 = Valor2 End Property Private Property Get IConfig_Valor3() As String IConfig_Valor3 = Valor3 End Property Public Sub CopiarIConfig(ByVal newValue As IConfig) ' hacer una copia del parmetro en los valores de la clase Me.Opcion1 = newValue.Opcion1 Me.Valor1 = newValue.Valor1 Me.Valor2 = newValue.Valor2 Me.Valor3 = newValue.Valor3 End Sub Public Function Clone() As cConfig Dim tConfig As cConfig Set tConfig = New cConfig ' tConfig.CopiarIConfig Me '
547
Set Clone = tConfig End Function Public Function Equals(ByVal compararCon As cConfig) As Boolean Dim b As Boolean ' If compararCon.Opcion1 = Me.Opcion1 Then If compararCon.Valor1 = Me.Valor1 Then If compararCon.Valor2 = Me.Valor2 Then If compararCon.Valor3 = Me.Valor3 Then b = True End If End If End If End If ' Equals = b End Function Public Sub Guardar(ByVal fichero As String) ' guardar en el fichero indicado los valores de las propiedades de la clase ' ' el cdigo est omitido para que lo hagas como ejercicio ' End Sub Public Sub Leer(ByVal fichero As String) ' leer del fichero indicado los valores de las propiedades de la clase ' ' el cdigo est omitido para que lo hagas como ejercicio ' End Sub
En esta clase, hemos implementado IConfig, para que al acceder a esa "parte" del cdigo, se obtengan los datos de las propiedades. Tambin se han implementado algunos mtodos, los cuales servirn para Leer y Guardar informacin en un fichero de disco, (ese cdigo es tarea tuya), por otro lado, tenemos un mtodo que nos permitir efectuar una copia del objeto: Clone, otro que comprobar si el
548
contenido de esta clase es idntico al de otra del mismo tipo: Equals y por ltimo, un mtodo, (CopiarIConfig), que recibe como parmetro un objeto del tipo IConfig, que se usar para asignar a las propiedades de la clase los valores indicados en el parmetro, este mtodo se usar para "actualizar" el contenido de las propiedades de la clase con otro objeto externo del tipo IConfig o que implemente la interfaz IConfig. Fjate que el mtodo Clone, hace uso de ese mtodo para realizar la copia de los datos del objeto actual en la nueva variable que devuelve.
El cdigo del formulario principal: '----------------------------------------------------------------------------' fEntrega44 (06/Ene/03) ' Pruebas para la entrega 44 del Curso Bsico de VB ' ' Guillermo 'guille' Som, 2003 '----------------------------------------------------------------------------Option Explicit Private mConfig As cConfig Private sFic As String Private Sub Form_Load() ' el nombre del fichero de datos sFic = App.Path & "\Prueba44.txt" ' Set mConfig = New cConfig ' asignar los valores de prueba al objeto mConfig.Valor1 = "El valor1" mConfig.Valor2 = "El valor2" mConfig.Valor3 = "El valor3" mConfig.Opcion1 = True ' mostrarlos en los controles del formulario asignarConfig mConfig ' End Sub Private Sub cmdCerrar_Click() Unload Me
549
End Sub Private Sub cmdGuardar_Click() ' guardar los datos en un fichero mConfig.Guardar sFic End Sub Private Sub cmdLeer_Click() ' leer los datos del fichero indicado mConfig.Leer sFic ' ' '$POR HACER: actualizar los controles con los valores de mConfig ' ' End Sub Private Sub cmdConfig_Click() ' no es necesario crear una variable para acceder al formulario ' pero es una buena prctica, la cual te recomiendo encarecidamente. Dim fc As fConfig ' Set fc = New fConfig ' cargamos el formulario para que se inicialicen los valores que pudiera ' haber en el evento Form_Load Load fc ' ' llenar el combo del formulario de configuracin ' con algunos datos Dim i As Long ' For i = 1 To 10 fc.Combo1.AddItem "Valor de prueba nmero " & CStr(i) Next ' ' Asignamos los valores a la clase mConfig.Valor1 = Text1
550
mConfig.Valor2 = Text2 mConfig.Valor3 = Text3 If Check1.Value = vbChecked Then mConfig.Opcion1 = True Else mConfig.Opcion1 = False End If ' ' usando un procedimiento fc.AsignarIConfig mConfig ' ' mostramos el formulario de forma modal para que "espere" ' hasta que se haya cerrado u ocultado fc.Show vbModal ' ' comprobamos si se ha cancelado la configuracin If fc.Cancelado = False Then ' slo asignar los valores si no se ha cancelado ' ' asignamos los nuevos valores a la copia local de este formulario ' si se pasa como parmetro del tipo IConfig, funcionar usando el formulario asignarConfig fc ' End If ' ' descargamos el formulario de configuracin y Unload fc ' eliminamos el objeto de la memoria Set fc = Nothing End Sub Private Sub asignarConfig(ByVal newValue As IConfig) ' actualizamos el contenido de la clase mConfig.CopiarIConfig newValue ' ' actualizamos el contenido de los controles Text1 = newValue.Valor1
551
Text2 = newValue.Valor2 Text3 = newValue.Valor3 If newValue.Opcion1 Then Check1.Value = vbChecked Else Check1.Value = vbUnchecked End If End Sub
Veamos con detalle el cdigo usado: Declaramos una variable del tipo cConfig para almacenar los datos (mConfig). Declaramos una variable que tendr el nombre del fichero en el que se guardarn los datos (sFic). En el evento Form_Load, asignamos el nombre del fichero, el cual estar en el mismo directorio que la aplicacin, para ello utilizamos App.Path, a continuacin creamos una nueva instancia del objeto mConfig, le asignamos unos valores "de prueba" y llamamos al mtodo asignarConfig para que se reflejen en los controles los valores de la clase. Cuando se pulsa en el botn Cerrar, se descarga el formulario principal, con lo cual tambin se termina la aplicacin: Unload Me. Al pulsar en el botn Guardar, se llama al mtodo Guardar de la clase cConfig y se le pasa como parmetro el nombre del fichero en el que se guardar el contenido de esa clase. Fjate que ser el propio mtodo Guardar de la clase el que se encargue de abrir ese fichero y guardar el contenido de las propiedades. Cuando se pulse en el botn Leer, se llama al mtodo Leer del objeto mConfig, (que es del tipo cConfig), el cual se encargar de leer la informacin del fichero pasado como argumento (o parmetro), ese mtodo (el de la clase) ser el que se encargue de, adems de leer la informacin del fichero, comprobar si ese fichero existe, etc. Una vez ledos los datos, se tendr que reflejar esa informacin en los controles del formulario, esto tambin es algo que debes hacer como ejercicio, slo comentarte que la solucin es muy simple y ya est hecha... o casi. Cuando el usuario pulse en el botn Configurar, tendremos que mostrar el formulario de configuracin, al cual tenemos que indicar los valores originales a mostrar y una vez cerrado, de forma "aceptable", esos datos hay que asignarlos a la clase (en este caso) del formulario principal. Nota: Aqu estamos usando una clase para mantener la informacin en el formulario principal, en otras ocasiones, es posible que esa informacin est asignada a valores de variables "normales", pero el planteamiento que te estoy explicando es para usar clases e interfaces, por tanto, he elegido ese mtodo o forma de comunicacin entre los formularios y los datos a configurar. Vamos a hacer una pequea "parada" o repaso ms detallado sobre el cdigo usado en el evento del botn Configurar, ya que aqu hay un par de cosillas, creo que, "interesantes". La primera de ellas es que hemos declarado una variable para usar el formulario de configuracin. Realmente no es necesario ni obligatorio hacerlo de esta forma, aunque te recomendara que te acostumbraras a hacerlo as. La forma ms sencilla, hubiera sido usar directamente el nombre que le hemos asignado
552
al formulario: fConfig, ya que el Visual Basic declara y crea una variable "global" con dicho nombre. Por tanto, si eliminas o comentas las lneas que hay antes del Load fc y cambias todos los fc por fConfig, el programa seguir funcionando igual. Al crear una variable (fc) del tipo del formulario, lo que estamos haciendo, entre otras cosas, es comprobar que los formularios realmente son y actan como clases. Y se "instancian" o crean de la misma forma que cualquier variable del tipo de una clase, es decir usando Set variable = clase. Seguramente te preguntars por qu esa complicacin extra? Pues, porque s... porque as puedes comprobar que puedes usar un formulario igual que cualquier otra clase y tambin porque as es ms evidente cual es nuestra intencin, si ese cdigo lo ve cualquier otra persona, sabr exactamente cual es tu intencin. Recuerda que quiero ensearte "buenos modales" en esto de la programacin, por tanto, siempre tendrs la libertad de hacerlo de la forma que ms te guste: como el Guille te recomienda o "a las bravas", tu eres quin tiene la ltima palabra. Despus de esta pequea aclaracin sobre modales, sigamos examinando el cdigo: Despus de cargar el formulario en la memoria (Load fc), asignamos una serie de valores al ComboBox, esto lo hago as, para que sepas que puedes acceder a cualquier control de un formulario desde otro y asignar o recuperar cualquiera de los valores que dicho control tenga. El siguiente paso es asignar a las propiedades de la variable mConfig el contenido de los controles del formulario principal. Fjate que en el formulario principal estamos usando una caja de textos para el contenido de la propiedad Valor3 y en el de configuracin, (como comprobars dentro de poco), se est usando un ComboBox. Una vez que la variable mConfig tiene asignado los valores de las propiedades, la pasamos con parmetro del mtodo AsignarIConfig que hemos implementado en el formulario de configuracin, esto har, (como vers dentro de poco), que se asignen los valores tanto a la variable del tipo cConfig que hay en ese formulario, como a los controles. Hecho esto, el formulario de configuracin tendr toda la informacin que necesita, por tanto lo mostramos de forma modal (como cuadro de dilogo), usando fc.Show vbModal, de esta forma, el cdigo del formulario principal "esperar" a que el formulario de configuracin desaparezca de la faz de la pantalla, para continuar. Cuando vuelve, comprobamos si se ha cancelado, en caso de que no se haya cancelado, la propiedad Cancelado tendr un valor False y por tanto tendremos que aceptar los nuevos valores. Esa propiedad (Cancelado) es una propiedad que nosotros (en este caso yo) hemos definido en el formulario de configuracin, tal como podrs comprobar cuando veamos el cdigo que falta. Si no se ha cancelado, asignaremos a la variable mConfig del formulario principal los valores que se han asignado en el de configuracin, para ello utilizamos el mtodo asignarConfig, al cual se le pasa como parmetro el formulario (fc), pero ese mtodo acepta un objeto del tipo IConfig, por tanto Visual Basic filtrar el formulario para que slo se "vea" la parte implementada por la interfaz IConfig. Por ltimo se descarga el formulario (para que no siga en memoria) y se librera la memoria utilizada por la variable fc, aunque esto ltimo no es necesario, ya que ser el propio VB el que se encargue de hacer esa limpieza, pero esto es algo que yo acostumbro a hacer... cosas mas! Para acabar con la explicacin del cdigo del formulario principal, slo nos queda ver el procedimiento asignarConfig. Este procedimiento (o mtodo privado) del formulario, recibe como parmetro un objeto del tipo IConfig, por tanto cualquier objeto de ese tipo o que lo implemente, se puede usar como parmetro. Lo que se hace en ese procedimiento, es hacer una copia en la variable mConfig, (por
553
medio del mtodo CopiarIConfig), de los datos contenidos en el objeto pasado como parmetro, as nos aseguramos de que la variable siempre est actualizada. Tambin se asignan esos valores a los controles para que sea "visual" para el usuario. Si algo de lo que acabamos de ver no te ha quedado muy claro, espera a ver el resto del cdigo.
El cdigo del formulario de configuracin: '----------------------------------------------------------------------------' fConfig (06/Ene/03) ' Pruebas para la entrega 44 del Curso Bsico de VB ' ' Guillermo 'guille' Som, 2003 '----------------------------------------------------------------------------Option Explicit Implements IConfig Private mConfig As cConfig Public Cancelado As Boolean Private Sub Check1_Click() comprobarCambiado End Sub Private Sub cmdAceptar_Click() cmdAplicar_Click Cancelado = False Hide End Sub Private Sub cmdAplicar_Click() ' asignar los valores a la clase mConfig.Valor1 = Text1 mConfig.Valor2 = Text2 mConfig.Valor3 = Combo1.Text
554
If Check1.Value = vbChecked Then mConfig.Opcion1 = True Else mConfig.Opcion1 = False End If ' cmdAplicar.Enabled = False End Sub Private Sub cmdCancelar_Click() Cancelado = True Hide End Sub Private Sub Combo1_Change() comprobarCambiado End Sub Private Sub Form_Load() Set mConfig = New cConfig cmdAplicar.Enabled = False End Sub Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer) Cancelado = True End Sub Private Sub Form_Resize() If WindowState <> vbMinimized Then Line1.X1 = 90 Line1.X2 = ScaleWidth - 90 Line2.X1 = 90 Line2.X2 = ScaleWidth - 90 End If End Sub Private Property Get IConfig_Opcion1() As Boolean IConfig_Opcion1 = (Check1.Value = vbChecked)
555
End Property Private Property Get IConfig_Valor1() As String IConfig_Valor1 = Text1 End Property Private Property Get IConfig_Valor2() As String IConfig_Valor2 = Text2 End Property Private Property Get IConfig_Valor3() As String IConfig_Valor3 = Combo1.Text End Property Private Sub Text1_Change() comprobarCambiado End Sub Private Sub Text2_Change() comprobarCambiado End Sub Private Sub comprobarCambiado() Dim datosCambiados As Boolean ' If mConfig.Valor1 <> Text1 Then datosCambiados = True End If If mConfig.Valor2 <> Text2 Then datosCambiados = True End If If mConfig.Valor3 <> Combo1.Text Then datosCambiados = True End If If mConfig.Opcion1 <> (Check1.Value = vbChecked) Then datosCambiados = True End If '
556
cmdAplicar.Enabled = datosCambiados End Sub Public Sub AsignarIConfig(ByVal newValue As IConfig) ' esta asignacin slo funcionar si newValue es del tipo cConfig 'Set mConfig = newValue mConfig.CopiarIConfig newValue ' Text1 = mConfig.Valor1 Text2 = mConfig.Valor2 Combo1.Text = mConfig.Valor3 If mConfig.Opcion1 Then Check1.Value = vbChecked Else Check1.Value = vbUnchecked End If comprobarCambiado End Sub En el formulario de configuracin tenemos una variable (mConfig) del tipo cConfig, que ser la que reciba los valores a "configurar"... (cuantas configuraciones!), tambin implementa la interfaz IConfig, adems de tener una propiedad llamada Cancelado, (recuerda que las variables de una clase declaradas como pblicas se convierten en propiedades), que nos servir de "seal" para saber si el usuario ha cancelado o no la "configuracin"... Fjate que en este formulario el nombre de la variable con los datos, tambin se llama mConfig, pero esto es slo "pura coincidencia" (o mejor dicho, para no tener que calentarme la cabeza pensando en otro nombre), ya que no tiene porqu llamarse igual que la variable usada en el formulario principal. Adems deberas saber que esa variable no se podr "conectar" ni confundir con la del otro formulario, por la sencilla razn de que ambas estn declaradas como "privadas", (Private), por tanto slo visibles dentro de cada formulario. En el evento Click de los botones Aceptar y Cancelar, se asigna el valor apropiado a la propiedad Cancelado, adems de que se oculta (mediante Hide) el formulario, de esta forma se devuelve el control al formulario principal y se conservan los datos, ya que al no cerrarlo, lo seguimos teniendo en memoria. En el caso del botn Aceptar, llamamos al procedimiento cmdAplicar_Click, para que se asignen los valores a la variable mConfig, esto es para simular el efecto de pulsar en el botn Aplicar, ya que ese botn se encarga de actualizar los datos de la variable con el contenido de los controles. En cuanto al cdigo de los eventos que se disparan cuando se cambia alguno de los controles con los datos, simplemente llaman al procedimiento comprobarCambiado en el cual se hacen las comprobaciones pertinentes para saber si se debe o no habilitar el botn Aplicar. De esta forma, si el usuario escribe o cambia cualquiera de esos valores, dicho botn estar habilitado (el contenido ha cambiado) o deshabilitado, para reflejar que
557
el contenido de esos controles se ha dejado como estaba, realmente se comprueba si estaba como antes de haber pulsado en Aplicar. Creo que el cdigo del procedimiento comprobarCambiado no necesita ninguna explicacin, (al menos detallada), ya que lo nico que se hace es comprobar si el contenido de las propiedades de la clase coinciden con el contenido de los controles. Si acaso, explicarte cmo funciona esta lnea: If mConfig.Opcion1 <> (Check1.Value = vbChecked) Then Aqu lo que se comprueba es si el valor de Opcion1 (que es de tipo Boolean) es distinto del resultado de comprobar si el valor de Check1 es igual a vbChecked. Esa expresin devolver True o False dependiendo de que el valor sea o no vbChecked, es decir que est o no "marcado". Esto ltimo lo tienes un poco ms claro en el cdigo del evento Click del botn Aplicar, ya que all se hace una comprobacin ms "especfica" y detallada. El mtodo pblico AsignarIConfig es el que se encarga de asignar los valores a la variable interna del tipo cConfig y a los controles con los valores que se hayan indicado, este mtodo se llama desde el formulario principal para asignar los valores iniciales de la configuracin. Para terminar, en el evento Load del formulario se crea una nueva instancia de la variable "privada" que contendr los datos de configuracin, adems de deshabilitar el botn Aplicar. El evento QueryUnload se produce justo antes de que el formulario se cierre, por tanto este evento se producir si el usuario pulsa en la "x" para cerrarlo, si es as, asignamos un valor False a la propiedad Cancelado, aunque, realmente no es necesario hacerlo, pero... para que quede evidente que si se cierra, se supone que se ha cancelado. En el caso del evento Resize, se comprueba si no se ha minimizado y si es as, se posicionan las lneas que hacen de separador 3D, (las lneas que estn encima de los tres botones).
Espero que todo haya quedado claro y comprensible, no slo por los comentarios que he hecho al cdigo, sino por los propios comentarios que el cdigo contiene. En caso de que no te hayas enterado... pues lete de nuevo esta entrega y sobre todo la anterior, para ver si as te aclaras un poco... (es que si te digo que me preguntes, no te vas a esforzar en entenderlo y me dar la impresin de que "realmente" no quieres esforzarte, as que... no me preguntes!). Recuerda que tienes que hacer los ejercicios que te he mencionado, la solucin la veremos en la siguiente entrega, la cual an no se de que tratar... de clases? de bases de datos? se acabar ya el curso? Pues, tendrs que esperar, como mnimo al mes que viene o a finales de este, segn me de... je, je. Mientras tanto, busca algo que leer o repsate las 43 entregas anteriores... que no es plan de que se te olviden las cosas por mi tardanza en escribir nuevas entregas. Nos vemos Guillermo
Aqu tienes el fichero zip con el cdigo usado en esta entrega: basico44_cod.zip 5.50 KB
558
Bueno, parece ser que me estoy "portando bien", ya que esta nueva entrega est dentro de los plazos que me impuse hace poco: publicar como mnimo una entrega cada mes. S, ya se que deberan ser ms entregas por mes, pero al menos es mejor tener una entrega al mes que no tener que estar varios meses sin ninguna nueva... as que no te quejes mucho... je, je.
559
Public Sub Leer(ByVal fichero As String) ' leer del fichero indicado los valores de las propiedades de la clase ' ' el cdigo est omitido para que lo hagas como ejercicio ' SOLUCIN: Dim nFic As Long Dim s As String ' ' comprobar si existe el fichero ' si no existe, se sale del procedimiento y no se hace nada ' If Dir$(fichero) = "" Then ' Exit Sub ' End If ' ' NOTA: ' Es posible, que si el nombre del fichero est mal formado ' (no es un nombre vlido), se produzca un error. ' En ese caso se podra usar el siguiente cdigo, ' o usar una funcin ExistFile como se ha mostrado en entregas anteriores. Dim i As Long ' On Error Resume Next i = Len(Dir$(fichero)) ' Si se produce un error en la lnea anterior, es que no existe. ' Adems de que si i vale cero, es que no existe. If Err.Number <> 0 Or i = 0 Then Exit Sub End If ' ' ' asignar un "canal" libre nFic = FreeFile ' abrir el fichero para lectura en el canal indicado Open fichero For Input As nFic ' leer los cuatro valores en el mismo orden en el que se guardaron Line Input #nFic, s Me.Valor1 = s Line Input #nFic, s Me.Valor2 = s Line Input #nFic, s Me.Valor3 = s Line Input #nFic, s ' Nota: ' Esta asignacin tambin puede dar error si se ha manipulado el fichero, ' para evitar ese error, podemos usar On Error Resume Next para evitar ' que el programa se detenga. ' ' Como resulta que ya tenemos un On Error activo, no ser necesario ' indicarlo nuevamente, pero si la comprobacin de que el fichero existe ' se hace desde una funcin, habra que quitar el comentario. 'On Error Resume Next Me.Opcion1 = CBool(s) ' cerrar el canal abierto Close nFic ' End Sub Este tampoco es complicado, aunque algo ms "largo" que el anterior, entre otras cosas porque se hace una comprobacin de que el fichero indicado existe y otra de que el valor asignado a la
560
propiedad Opcion1 es el correcto. En teora no habra que hacer estas comparaciones, o al menos no habra porqu usar On Error, ya que se supone que el nombre del fichero es correcto, independientemente de que dicho fichero exista o no y por otro lado, cuando se lea ese fichero los datos estarn guardados de forma correcta. Pero siempre es preferible prevenir. Ahora veamos el cdigo del formulario principal, tanto del evento producido al guardar como al leer: Private Sub cmdGuardar_Click() ' guardar los datos en un fichero ' ' SOLUCIN: ' (aunque no estaba como ejercicio, si no se hace, no se guarda correctamente) ' ' Asignamos los valores a la clase y guardarlos mConfig.Valor1 = Text1 mConfig.Valor2 = Text2 mConfig.Valor3 = Text3 If Check1.Value = vbChecked Then mConfig.Opcion1 = True Else mConfig.Opcion1 = False End If ' mConfig.Guardar sFic End Sub Private Sub cmdLeer_Click() ' leer los datos del fichero indicado mConfig.Leer sFic ' ' '$POR HACER: actualizar los controles con los valores de mConfig ' SOLUCIN: asignarConfig mConfig ' ' End Sub En el caso del evento producido al pulsar en el botn Guardar, antes de llamar al mtodo correspondiente de la clase, hay que asignar los valores a las propiedades de dicha clase. Para el evento del botn Leer lo tenemos ms fcil ya que todo el trabajo de asignar el contenido de la clase en los controles del formulario se hace por medio del procedimiento asignarConfig. Espero y confo en que lo hayas hecho bien... y si no es as... pues tampoco pasa nada, ya que por eso te doy la solucin... je, je.
561
fichero, bien porque hayamos aadido alguna nueva propiedad o porque haya cambiado el tipo de datos, tan slo tendremos que cambiar el cdigo de esos procedimientos, de forma que en el resto del cdigo no tengamos que hacer ningn cambio. Por esa razn, es importante y sobre todo aconsejable, que en la medida de lo posible, hagamos que sea el cdigo incluido en la propia clase el que se encargue de manejar la informacin o los datos que dicha clase manipular. Ya que, al menos eso es lo que se supone, la propia clase es la que "mejor" sabe cmo manipular los datos que contiene. Si, por ejemplo, tienes que crear una funcin o procedimiento para clasificar esos datos, siempre ser ms conveniente que el cdigo que se encargue de realizar esa clasificacin est contenido en la propia clase que usar funciones o procedimientos externos. Hacindolo de esta forma, sobre todo, ganamos ms legibilidad en nuestro cdigo.
Siguiendo con el tema de la encapsulacin y la forma de manejar los datos o la informacin de las clases, en ocasiones nos podemos encontrar con la necesidad de que sea la propia clase la que nos avise de que ha sucedido algo. Puede ser interesante que, por ejemplo, si la clase usada en el proyecto de la entrega anterior detecta algn tipo de error al leer o guardar los datos, nos lo comunique. O bien, que si son muchos los datos que tiene que guardar o leer, nos vaya mostrando cada uno de los datos que est procesando. Esto lo podemos conseguir mediante los eventos.
562
End Sub Es decir, de la misma forma que hasta ahora hemos estado usando los eventos. Fjate que tambin se sigue la nomenclatura estndar de usar el nombre del objeto un guin bajo y el nombre del evento; aunque este es un detalle del que tenemos que despreocuparnos, ya que es el propio entorno de desarrollo el que se encarga de dar nombre a los eventos de la forma correcta.
Fig.1, el IDE de VB muestra los eventos que la clase puede producir. Como sabrs, cuando no indicamos si el parmetro es por valor o por referencia, se supone que es por referencia, por tanto, en el caso del evento ElementoSeleccionado, el parmetro index ser ByRef y en los otros dos, tal y como se indica en la declaracin mostrada en la figura, son del tipo ByVal. Nota: Como te dije antes, cuando declaramos un evento en una clase, ese evento est definido como pblico, pero el que est definido como pblico no significa que se pueda lanzar desde cualquier sitio, ya que slo se puede usar RaiseEvent nombreEvento() desde la propia clase en la que se ha definido el evento.
563
Me explico, para que lo comprendas mejor: Supongamos que tenemos una clase llamada cConEventos, la cual produce los tres eventos mostrados en la figura 1. Si declaramos una variable de ese tipo en otra parte de nuestro proyecto, lo normal es que lo hagamos de esta forma: Dim mConEvento As cConEventos Despus creamos el objeto en la memoria (lo instanciamos) usando New: Set mConEvento = New cConEventos A partir de este momento podemos usar dicho objeto, ya que se ha creado en la memoria. Esto ya lo tenemos claro, verdad? ya que al fin y al cabo es la forma "habitual" de declarar e instanciar una clase. Pero esto no nos permitira usar los eventos declarados en la clase, a pesar de que tengamos definidos los procedimientos Sub de los eventos, los cuales se definiran de la siguiente forma: Private Sub mConEvento_Aviso(ByVal elAviso As String) ' End Sub Private Sub mConEvento_ElementoSeleccionado(index As Integer) ' End Sub Private Sub mConEvento_Prueba(ByVal mensaje As String) ' End Sub Por qu? Por la sencilla razn de que si bien la clase produce eventos, Visual Basic no sabe que queremos usarlos, es decir, esos tres procedimientos que, al menos en teora, deberan interceptar los eventos no los interceptarn. Para que Visual Basic se entere de que la clase se va a usar para procesar o interceptar los eventos, hay que declararla usando la instruccin WithEvents. Sabiendo esto, cuando queramos usar una clase que produce eventos, tenemos que declararla de la siguiente forma: Dim WithEvents mConEvento As cConEventos A partir de ese momento, nuestro querido (y, algunas veces, tozudo) Visual Basic sabe que nuestra intencin es usar los eventos de esa clase. Adems, una vez declarada una variable de esta forma, podemos comprobar que dicha variable se muestra en la lista desplegable de los objetos que producen eventos, (la que est en la izquierda del panel de cdigo), tal como podemos ver en la siguiente imagen:
564
Fig. 2, Lista con los objetos que producen eventos. Una vez seleccionado el objeto en la lista desplegable de la izquierda, podemos ver los eventos que dicha clase produce, para ello debemos desplegar la lista de la derecha. En la siguiente figura podemos ver los tres eventos que la hipottica clase cConEventos produce:
Fig. 3, Los eventos de la clase seleccionada en la lista. Los eventos se muestran de forma alfabtica, pero el que toma el foco, (el que se crea nada ms que mostrar ese objeto), es el primero que hayamos definido en la clase. En este caso, el evento Prueba. A partir de este momento podemos usar los eventos que queramos, no es necesario tener que codificarlos todos, slo los que nos interese interceptar. Cuando mostramos la lista con los eventos, el IDE de Visual Basic nos muestra en negrita los que ya tienen cdigo. Si declaramos una clase con WithEvents y esa clase no produce eventos, dicha clase no se mostrar en la lista de clases que podemos usar para declarar esa variable, adems de que al ejecutar la aplicacin, el Visual Basic nos mostrar un error de que dicha clase no produce eventos, tal y como podemos comprobar en la siguiente figura:
565
Fig. 4, Error al declarar con WithEvents una variable que no produce eventos.
Para ver en la prctica todo esto que se ha comentado, vamos a crear un proyecto en el que definiremos una clase que produce eventos y los interceptaremos en un formulario. Para ello vamos a crear un nuevo proyecto, al que aadiremos una clase llamada cConEventos que definir dos eventos, los cuales se producirn al llamar a un mtodo de esta misma clase. Uno de esos eventos se producir mientras se aaden nuevos datos a un array y el otro al terminar de aadir dichos datos, devolviendo el nmero total de elementos. Veamos el cdigo de esa clase: Option Explicit Private mDatos() As String ' Event NuevoDato(ByVal elDato As String) Event DatosCreados(ByVal total As Long) Public Sub CrearDatos() Dim i As Long ' ReDim mDatos(10) For i = 0 To 10 mDatos(i) = "El dato nmero " & CStr(i) RaiseEvent NuevoDato(mDatos(i))
566
Next RaiseEvent DatosCreados(11) End Sub Como podemos comprobar, los dos eventos que nuestra clase emitir al "receptor" de utilice dicha clase, sern: NuevoDato el cual tiene un parmetro de tipo String que representar al nuevo dato que se est manipulando y DatosCreados cuyo parmetro de tipo Long nos indicar el nmero total de datos que la clase acaba de crear. Esos dos eventos se lanzan (o disparan) en el mtodo CrearDatos, que ser el nico que podremos usar desde cualquier variable declarada con el tipo de la clase cConEventos. El cdigo usado en este ltimo procedimiento es simple, pero te lo detallo para que no tengas problemas de comprensin... (s, ya se que lo has entendido, pero...) -Definimos una variable que usaremos para el bucle For. -Redimensionamos el array para que tenga 11 elementos, de cero a diez. -Hacemos un bucle para que se repita desde 0 a 10. -En cada ciclo del bucle, asignamos un valor al elemento i (usando la variable contadora del bucle) del array mDatos y -lanzamos el evento NuevoDato en cuyo parmetro indicamos el contenido del elemento que acabamos de asignar. -Continuamos repitiendo el bucle hasta que estn asignados los once elementos. -Por ltimo, lanzamos el evento DatosCreados, en cuyo parmetro indicamos el valor once. Para poder usar esta clase desde el formulario creado en el proyecto, al cual aadiremos un CommandButton al que llamaremos crearDatosCmd y un ListBox llamado List1, tambin definiremos la clase usando WithEvents, crearemos una nueva instancia en el evento Load del formulario y al pulsar en ese botn, llamaremos al mtodo CrearDatos. Debido a que la variable estar definida con WithEvents, tendremos que escribir el cdigo en cada uno de los dos eventos para poder comprobar que todo esto que te estoy contando realmente funciona. En uno de ellos, el que se produce al aadir un nuevo elemento al array, haremos que el parmetro indicado en el evento se aada al ListBox y cuando se produzca el evento DatosCreados, mostraremos un mensaje que nos avise de cuantos elementos se han creado, para mostrar ese mensaje usaremos la instruccin MsgBox. Te atreves a codificar todo esto que te acabo de decir? No me digas que no, que me enfado... Bueno, vale... te doy unas pistas: Nota: Si lo vas a hacer por tu cuenta y no quieres pistas, no leas lo que sigue... aunque dependiendo de la resolucin de tu monitor, es posible que veas el resto del cdigo... as que intentar dejar unas cuantas lneas en blanco para que no puedas ver el cdigo y dems pistas... Pero me gustara que lo intentaras antes de ver la solucin...
Vale, la solucin te la muestro en una pgina aparte... as no tendrs la excusa de que lo has visto sin querer...
567
568
Set mForm2 = New Form2 ' asignamos a la variable mForm una referencia al objeto recin creado Set mForm = mForm2 End Sub Private Sub mForm_Click() ' Este evento se producir al hacer una pulsacin en el Form2 Label1 = "Evento desde Form2: Form_Click" End Sub Private Sub mForm_Load() ' Esto evento se producir al cargarse el Form2 Label1 = "Evento desde Form2: Load" End Sub Private Sub mForm2_Prueba(ByVal mensaje As String) ' Este evento se producir desde el Form2 Label1 = "Evento desde Form2: " & mensaje End Sub Private Sub mostrarForm2Cmd_Click() ' mostrar el nuevo formulario a la derecha del principal mForm2.Move Me.Left + Me.Width + 60, Me.Top ' mostrar el formulario mForm2.Show End Sub Como te he comentado antes, si declaramos con WithEvents una variable del tipo especfico Form2, slo podremos acceder a los eventos que nosotros hayamos definido, por esa razn he declarado otra variable con WithEvents: mForm que permitir acceder a los eventos "genricos" del formulario, en este caso slo interceptamos dos, pero igualmente podramos acceder al resto. En el evento Load de este formulario asignamos a la variable mForm2 una nueva instancia del formulario Form2 y a continuacin asignamos tambin el objeto apuntado por esa variable a la variable mForm de forma que tanto una como la otra variable estarn apuntando al formulario Form2. No te extrae que se pueda realizar esa asignacin, ya que esto es polimorfismo... recuerdas? La clase Form2 es en realidad un formulario (del tipo Form), por tanto estamos asignando a la variable mForm la parte del Form2 que es del tipo Form, es decir todo excepto el evento que nosotros hemos definido. Cuando se produzcan los eventos Load y Click del formulario Form2, se producirn los dos eventos interceptados con la variable mForm, los cuales mostrarn este hecho en la etiqueta Label1. Por otro lado, cuando se produzca el evento Prueba, se interceptar por medio del evento mForm2_Prueba. Por ltimo, cuando se pulse en el botn para mostrar el formulario Form2, ste se posicionar a la derecha del formulario principal y a continuacin se mostrar. Cuando ejecutes el proyecto, podrs comprobar que al mostrarse por primera vez el formulario Form2, en la etiqueta se mostrar el mensaje de que se ha producido el evento Load, ese mensaje slo se mostrar la primera vez que pulsemos en dicho botn, o cada vez que pulses en dicho botn y el segundo formulario no est cargado en la memoria. Esto ltimo puedes comprobarlo cerrando el segundo formulario y volviendo a pulsar en el botn. Si aades este cdigo al Form1, se mostrar un mensaje cuando se cierre el segundo formulario, as podrs comprobar mejor eso que te acabo de comentar. Private Sub mForm_Unload(Cancel As Integer)
569
' Este mensaje se mostrar al cerrar el segundo formulario Label1 = "El segundo formulario se ha descargado." End Sub Si adems pulsas en el segundo formulario se producir el evento Click y se mostrar el mensaje correspondiente, lo mismo ocurrir cuando pulses en el botn, aunque en ese caso el evento que se producir ser el que hemos definido.
Espero que con todo esto que te he comentado tengas ms claro cmo definir y lanzar eventos en las clases, adems de saber cmo poder interceptar esos eventos desde otras partes del proyecto. En la prxima entrega veremos cmo definir una clase que ample el funcionamiento de un control. De esa forma tendrs la posibilidad de ampliar el funcionamiento de los controles y adaptarlos a tus necesidades. Nos vemos Guillermo
Aqu tienes el fichero zip con el cdigo usado en esta entrega: basico45_cod.zip 4.97 KB
570
a crear un procedimiento Buscar, el cual, segn el parmetro recibido, buscar la primera coincidencia o seguir buscando desde el ltimo dato hallado. Antes de aadir un nuevo botn, vamos a modificar el cdigo actual para usar el nuevo procedimiento Buscar. Crea un nuevo procedimiento, en el men Herramientas (Tools), selecciona Aadir procedimiento..., llmalo Buscar y haz que sea privado, ya que no tiene ningn sentido que sea pblico, porque slo se usar desde el formulario. Tambin puedes copiar y pegar el siguiente cdigo: ' Private Sub Buscar() ' Procedimiento para buscar el dato indicado (12/Feb/01) Dim nReg As Long ' ' Buscar la primera coincidencia en el recordset del Data1 ' If Option1.Value Then nReg = Val(Text4) ' Data1.Recordset.FindFirst "Au_ID = " & nReg End If If Option2.Value Then ' Data1.Recordset.FindFirst "Author Like '" & Text4.Text & "'" End If End Sub Ahora hay que modificar el evento KeyPress del control Text4, para que llame al nuevo procedimiento. Borra el cdigo que haba anteriormente en ese evento y sustityelo por este otro: ' Private Sub Text4_KeyPress(KeyAscii As Integer) ' Se buscar slo cuando pulsemos INTRO ' ' Comprobar si la tecla pulsada es Intro: vbKeyReturn o 13 que es lo mismo If KeyAscii = vbKeyReturn Then ' Esta asignacin evita que suene un BEEP ' en el campo Author ' en el campo Au_ID ' Convertir el contenido de TextBox en un nmero
571
KeyAscii = 0 ' Llamamos al procedimiento Buscar: Buscar End If End Sub Prubalo, para que veas que todo funciona como antes.
Ahora, vamos a aadir un botn para que busque el primer registro que coincida con lo escrito, esto es para hacer lo mismo que cuando pulsas Intro en el Text4, pero para el usuario ser ms lgico que el hecho de tener que pulsar Intro. Por tanto, aade un nuevo botn, (no te preocupes por ahora dnde colocarlo en el formulario, ya lo haremos dentro de poco), cmbiale el nombre a cmdBuscar y el Caption a Buscar, (por defecto ser Command1) y escribe o aade este cdigo (tambin puedes hacerlo copiando y pegando): ' Private Sub cmdBuscar_Click() ' Simplemente llamamos al procedimiento Buscar: Buscar End Sub Poca cosa, verdad? Pues es igual de efectivo que pulsando Intro en el Text4, pruebalo para que veas que funciona. Creo que ya es hora de que nos vayamos complicando la vida... Vamos a aadir un botn para seguir buscando a partir del ltimo dato hallado: Aade un nuevo botn, llmalo cmdBuscarSig y en el Caption pones: Buscar siguiente, (seguramente el texto lo escribir en dos lneas, pero no te preocupes). El cdigo a usar en el evento Click de ese nuevo botn sera prcticamente el mismo que en el de Buscar, aunque antes debemos aadir un nuevo procedimiento para que busque el siguiente dato al ltimo que busc: ' Private Sub BuscarSiguiente() ' Procedimiento para buscar el dato indicado (12/Feb/01) Dim nReg As Long ' ' Buscar la siguiente coincidencia, a partir del ltimo hallado ' If Option1.Value Then ' en el campo Au_ID
572
' Convertir el contenido de TextBox en un nmero nReg = Val(Text4) ' Data1.Recordset.FindNext "Au_ID = " & nReg End If If Option2.Value Then ' Data1.Recordset.FindNext "Author Like '" & Text4.Text & "'" End If End Sub Si te fijas, el cdigo es prcticamente el mismo que el del procedimiento Buscar, lo nico que cambia es que aqu se usa FindNext en lugar de FindFirst. Es decir FindFirst busca el primer dato que coincida con lo buscado y FindNext el siguiente al ltimo que se busc. Para probar este nuevo procedimiento, en el evento cmdBuscarSig_Click, escribe: BuscarSiguiente. As es como quedara ese evento: Private Sub cmdBuscarSig_Click() ' Buscar el siguiente registro BuscarSiguiente End Sub ' en el campo Author
Como hemos visto, el cdigo usado en los dos procedimientos de bsqueda son prcticamente iguales, as que vamos a unificarlos para crear un slo procedimiento de bsqueda, de esta forma, refrescars tu memoria y sabrs algo ms de parmetros en procedimientos, as como parmetros opcionales.
Parmetros opcionales.
Como sabrs, (y si no lo sabes, te lo cuento yo ahora), a partir de la versin 4 de Visual Basic se pueden usar parmetros opcionales en los procedimientos y funciones. Esto quiere decir que podemos usar el procedimiento de varias formas, indicando todos los parmetros o slo los que realmente son necesarios. Por ejemplo, en el caso en que estamos ahora, el procedimiento Buscar podra tener un parmetro opcional para indicarle si es la primera bsqueda o la siguiente. No voy a entrar en demasiadas explicaciones, ya que este tema lo veremos de forma ms amplia en otra entrega, sobre todo porque tiene sus pormenores, o lo que es lo mismo, existen diferencias entre las versiones 4 y posteriores (al menos hasta la 6) de Visual Basic e incluso en las dos ltimas se puede usar de dos formas diferentes... En esta ocasin voy a usar el formato de las versiones 5 y 6, en estas versiones los parmetros opcionales pueden ser de un tipo de datos diferente a Variant y tambin pueden indicrsele un valor por defecto, para que, si no se especifica, tenga el valor indicado; por supuesto, si no le indicamos el valor, tendrn el valor que ese tipo de datos
573
tengan por defecto, por ejemplo el tipo Boolean tendr un valor False si no se indica el valor. Cuando se indican parmetros con tipo, a diferencia de los parmetros del tipo Variant, no se puede usar IsMissing para comprobar si el parmetro se ha especificado o no, pero, eso es otro tema... Este es el cdigo del procedimiento Buscar y a continuacin te indico cmo llamarlo desde el evento Click del botn cmdBuscarSig: ' Private Sub Buscar(Optional ByVal Siguiente As Boolean = False) ' Procedimiento para buscar el dato indicado (12/Feb/01) ' Si Siguiente = True, se busca a partir del registro activo ' Si no se indica, (valdr False), buscar el primer registro Dim nReg As Long ' ' Buscar la primera coincidencia en el recordset del Data1 ' If Option1.Value Then nReg = Val(Text4) ' ' Si se busca el siguiente dato If Siguiente Then Data1.Recordset.FindNext "Au_ID = " & nReg Else Data1.Recordset.FindFirst "Au_ID = " & nReg End If End If If Option2.Value Then ' ' Si se busca el siguiente dato If Siguiente Then Data1.Recordset.FindNext "Author Like '" & Text4.Text & "'" Else "'" End If End Sub Data1.Recordset.FindFirst "Author Like '" & Text4.Text & End If ' en el campo Author ' en el campo Au_ID ' Convertir el contenido de TextBox en un nmero
574
Private Sub cmdBuscarSig_Click() ' Buscar el siguiente registro Buscar True End Sub
Puedes borrar el procedimiento BuscarSiguiente, ya que no es necesario. Tampoco es necesario modificar el cdigo de los eventos Text4_KeyPress ni el de cmdBuscar_Click, ya que esos dos eventos llaman al procedimiento Buscar con el valor predeterminado y al ser opcional, no es necesario indicarlo... Si pruebas el nuevo cdigo, te dars cuenta de que, la primera vez, pulsando tanto en Buscar como en Buscar siguiente, encuentra lo mismo, esto es debido a que FindNext, busca a partir de la ltima posicin hallada y en el caso de buscar por primera vez, busca desde el principio. En el caso de que cambies el texto buscado y pulses en Buscar siguiente, se mostrar el prximo registro, desde el ltimo hallado, que contenga dicho texto, (estas pruebas hay que hacerlas en el campo Author, ya que no tienen ningn sentido hacerlo en el ID del autor). Para buscar el resto de autores con el nuevo texto, tendrs que pulsar en Buscar para que empiece a buscar desde el principio. Fjate en el cdigo, se da por hecho de que Siguiente tiene un valor, el cual, (al ser del tipo Boolean), puede ser False o True. En caso de que sea True, es decir se ha especificado con ese valor, se buscar con FindNext y en caso de no especificarse o de hacerlo con el valor False, se usar FindFirst. Lo mismo da: Buscar False que Buscar (sin parmetros) Esto ltimo: lo de no especificarse o si se especifica con el valor False, es algo que hay que tener en cuenta si el parmetro fuese de tipo Variant (cosa obligatoria si usas VB4), ya que IsMissing slo nos informa si el parmetro no se ha especificado, pero si se especifica, puede hacerse con un valor False o True, por tanto, tambin habra que tenerlo en cuenta... Ya s que dije que lo iba a dejar para otra entrega... pero, que haces si ests usando VB4 dejar aqu el curso? As que vamos a ver el cdigo de Buscar para usar con VB4 o con las versiones 5 y 6 pero usando el tipo Variant. ' Private Sub Buscar(Optional ByVal vSiguiente As Variant) ' Procedimiento para buscar el dato indicado (12/Feb/01) ' ' Si Siguiente = True, se busca a partir del registro activo ' Si no se indica, (valdr False), buscar el primer registro '
575
' Cuando el parmetro es de tipo Variant, ' no se puede indicar un valor por defecto, ' as que vamos a usar una variable interna para indicar el valor del parmetro Dim Siguiente As Boolean ' Dim nReg As Long ' ' Asignar correctamente el valor del parmetro indicado ' Si no se especifica, le asignamos el valor por defecto, en este caso: False If IsMissing(vSiguiente) Then Siguiente = False Else ' Si se indica, asignarlo a la variable interna Siguiente = CBool(vSiguiente) End If ' El resto del cdigo es igual que antes ' Buscar la primera coincidencia en el recordset del Data1 ' If Option1.Value Then nReg = Val(Text4) ' ' Si se busca el siguiente dato If Siguiente Then Data1.Recordset.FindNext "Au_ID = " & nReg Else Data1.Recordset.FindFirst "Au_ID = " & nReg End If End If If Option2.Value Then ' ' Si se busca el siguiente dato If Siguiente Then "'" Data1.Recordset.FindNext "Author Like '" & Text4.Text & ' en el campo Author ' en el campo Au_ID ' Convertir el contenido de TextBox en un nmero
576
Else "'" End If End Sub Fjate que he cambiado el nombre del parmetro y he creado una variable interna que es la que posteriormente se usa para saber si se busca desde el primero o desde el anterior. En el caso de que no se haya especificado el parmetro, la comprobacin de IsMissing se cumple, por tanto aqu le indicaremos el valor que queramos que tenga por defecto, en nuestro ejemplo no es necesario asignar ningn valor, ya que False es el valor que tendr la variable Siguiente si no le asignamos ningn valor. Por tanto en este ejemplo, podramos haber hecho slo lo siguiente, sin necesidad de usar el IsMissing: Siguiente = CBool(vSiguiente) Pero lo he mostrado de la forma recomendable, para que sepas qu hacer en el caso de que el valor por defecto fuese otro del que Visual Basic asigna a las variables por defecto. Data1.Recordset.FindFirst "Author Like '" & Text4.Text & End If
Una vez visto el procedimiento de bsqueda, vamos a aadir dos nuevos botones para Aadir y Eliminar registros. Recuerda lo que te dije al principio: haz una copia de la base de datos para que las pruebas no afecten al contenido de la misma. Aade dos nuevos botones y asignales los nombres cmdAdd y cmdBorrar, as como los captions Aadir y Eliminar, el cdigo ser el siguiente: ' Private Sub cmdAdd_Click() ' Aadir un nuevo registro Data1.Recordset.AddNew End Sub Private Sub cmdBorrar_Click() ' Eliminar el registro actual Data1.Recordset.Delete End Sub
Tanto cuando se aade como cuando se borra, se debera mover el registro actual para que los cambios tengan efecto en la base de datos, ya que si se aade un nuevo registro y el mismo no se actualiza, se pierde. Para probarlo, ejecuta el proyecto, pulsa en Aadir y directamente sin hacer nada ms cierra el ejecutable, (pulsando en la x del formulario o ventana). Ejecuta de nuevo el proyecto y si pulsas en el botn de ir al ltimo registro del Datacontrol, vers que no hay ningn registro nuevo.
577
Ahora vuelve a pulsar en Aadir y escribe lo que quieras en las cajas de texto del Nombre y Ao de nacimiento y pulsa en el botn de ir al primer registro y despus al ltimo (ya sabes que me refiero a los botones del Datacontrol), vers que ahora si se muestra dicho registro y si pulsas en el botn de ir al anterior, vers que se salta un nmero... ese es el que haba reservado para nosotros, pero al no actualizar los datos, se perdi en el limbo... Ser por estos detalles que algunos programadores prefieran usar cdigo puro y duro en lugar del Datacontrol? Puede... pero no creo que sea por este motivo... ya que esto mismo se puede mejorar y no es necesario abandonar el Datacontrol para que todo funcione ms o menos como debiera... Primero vamos a hacer que los nuevos datos tengan algo, para ello, le asignaremos la cadena Nuevo al nombre del autor y haremos que se desplace al ltimo registro, para que los datos sean permanentes, (si no modificamos algunos de los campos, el nuevo registro se perdera) En el caso de Eliminar, vamos a movernos al primer registro, para que el registro activo sea uno con informacin. Este sera el cdigo de los eventos Click de los dos botones: ' Private Sub cmdAdd_Click() ' Aadir un nuevo registro Data1.Recordset.AddNew ' Aadimos algn texto, para que no se pierda este registro Text2 = "Nuevo" ' Movemos al ltimo registro para que los cambios se hagan permanentes ' y se muestre el nuevo registro Data1.Recordset.MoveLast End Sub Private Sub cmdBorrar_Click() ' Eliminar el registro actual Data1.Recordset.Delete ' Movemos al primer registro para que los cambios se hagan permanentes ' (tambin podriamos haberlo movido al ltimo registro) Data1.Recordset.MoveFirst End Sub Toma nota de la forma en que se hara para mover al primer o al ltimo registro del Recordset. Tambin habra que tener en cuenta si hemos sobrepasado el principio o el final de los registros, cosa que ocurre cuando no hay ms registros, para ello podemos comprobar tanto EOF (End Of File, final del fichero) como BOF (Beginnig Of File, principio del fichero) y en caso de que sea cierto (True), avisar o simplemente no hacer nada...
578
Esto slo habra que hacerlo al eliminar registros, ya que al aadir se supone que habr al menos el que aadimos... ' Private Sub cmdBorrar_Click() ' ' Comprobar que hay registros, porque si no hay, dar error If (Data1.Recordset.EOF Or Data1.Recordset.BOF) Then ' Avisar de que no hay registros Else ' Eliminar el registro actual Data1.Recordset.Delete ' ' Movemos al primer registro para que los cambios se hagan permanentes ' (tambin podriamos haberlo movido al ltimo registro) Data1.Recordset.MoveFirst End If End Sub
Y esto es todo por hoy. En la prxima entrega veremos cmo acceder a bases de datos usando el ADO datacontrol. Nos vemos Guillermo P.S. Si quieres bajarte los ejemplos usados, pulsa este link. (basico35_cod.zip 3.11 KB)
579
a seguir con la creacin/definicin de clases. Aunque el tema ese de crear nuestros propios controles seguramente lo veremos en alguna entrega posterior... y digo "seguramente" porque no se si ese tema (cuantos temas) debera entrar en un "curso bsico", aunque la verdad es que el concepto de curso bsico creo que ya ha sido casi superado... o no? no s, en fin... A lo que vamos. Como vimos en la entrega anterior, podemos definir nuevos eventos en un formulario, en esa ocasin lo que hicimos fue aadir un formulario al proyecto y definir un evento, el cual podramos interceptar desde otro sitio. Pero en el caso de los controles, no podemos hacerlo de esa forma, por la sencilla razn de que no existe una clase especfica de un control en la que podamos aadir esos nuevos eventos. Por tanto, tendremos que crear una clase en la que se pueda manejar un control y ser en esa clase donde definamos nuevos eventos y tambin nuevas propiedades y mtodos. Esto mismo lo podemos hacer con los formularios adems de con prcticamente cualquier control. Para qu ampliar un control? Si realmente te hicieras esa pregunta, no se si decirte que te dediques a otra cosa... pero bueno, supongamos que te intriga el saber qu puede llevarnos a ampliar el funcionamiento de un control... Una de las razones podra ser que no te gusta el funcionamiento estndar de un control y quieres que haga ms cosas o que las que hace, las haga a tu gusto o a la forma que a ti te gustara. Por ejemplo, imagnate que quieres que una caja de textos te muestre los nmeros negativos en color rojo o que te permita indicarle que slo quieres que acepte nmeros o letras o... pueden ser tantas las cosas que se te podran ocurrir, que necesitaramos como mnimo una entrega para nombrarlas. En esta entrega, (no se si me tambin en la siguiente), vamos a ampliar el funcionamiento de una caja de textos no multilnea, a la que aadiremos algn nuevo evento y propiedades, as como tambin algn que otro mtodo... ya veremos qu le aadimos, pero sea lo que sea, te servir para saber cmo hacerlo y despus puedes ampliarlo o modificarlo a tu gusto. Creando una clase para ampliar un TextBox Vamos a empezar haciendo poca cosa con el TextBox ampliado por lo que seguramente te preguntars para qu hacer esto?, la respuesta es bien simple, para que no te compliques con cosas nuevas sin llegar a comprender lo bsico, que al fin y al cabo es de lo que se trata: aprender lo bsico para que ests preparado para hacer lo que quieras... o casi... Para probar lo que te voy a contar a continuacin, necesitaremos crear un nuevo proyecto. Al form que se crea junto con el proyecto, vamos a aadirle dos cajas de textos y cuatro etiquetas. Uno de esos textbox (Text2) ser el que usaremos para ampliarlo... Aunque en esta primera tentativa no lo vamos a ampliar, simplemente lo manipularemos mediante una clase y aprenderemos cmo usar ese control mediante la clase. Para ello, aade un mdulo de clase y dale el nombre cTextBoxEx. Lo primero que tendremos que hacer es crear una variable a nivel de mdulo que nos permita interceptar los eventos de un control de tipo TextBox, para ello aadiremos esta declaracin: Option Explicit ' creamos una variable para manejar el textbox
580
' la declaramos con WithEvents para que interceptemos los eventos ' y podamos adaptarlos a nuestro gusto Private WithEvents mText As TextBox Con esto lo que hacemos es declarar la variable mText para que sea del tipo TextBox, al estar declarada con WithEvents, podremos interceptar los eventos que produzca el textbox que est asociado a esa variable. Nota: Las variables declaradas con WithEvents, pueden ser privadas o pblicas, pero nunca se pueden crear arrays (o matrices) ni usarlas para instanciar directamente la clase con New. A continuacin vamos a declarar varios eventos pblicos, estos eventos estarn disponible en cualquier sitio en el que se utilice esta clase declarada con WithEvents. Estos eventos, simplemente sern unos cuantos de los que ya disponen todos los TextBox, pero vamos a usarlos para que sepas cmo manipularlos y, si as lo deseas, adaptarlos a tu gusto o necesidad. ' Los eventos pblicos que producir la clase ' Aqu se ha usado la misma definicin que los eventos originales ' pero podramos haberlos declarado como se nos antojara. Public Event Change() Public Event GotFocus() Public Event KeyDown(KeyCode As Integer, Shift As Integer) Public Event KeyPress(KeyAscii As Integer) Public Event KeyUp(KeyCode As Integer, Shift As Integer) Public Event LostFocus() Public Event MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) Public Event MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) Public Event MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single) Una vez que hemos definido los eventos que nuestra clase podr producir, tenemos que encontrar la forma de producirlos, en esta ocasin los produciremos cuando se produzcan en el objeto que la clase manipular, por tanto, en los eventos producidos en el TextBox, produciremos nuestros eventos. ' los eventos producidos por el TextBox original ' desde estos eventos se lanzarn los que la clase implementa ' por tanto, podemos cambiar ese comportamiento a nuestro gusto Private Sub mText_Change()
581
RaiseEvent Change End Sub Private Sub mText_GotFocus() RaiseEvent GotFocus End Sub Private Sub mText_KeyDown(KeyCode As Integer, Shift As Integer) RaiseEvent KeyDown(KeyCode, Shift) End Sub Private Sub mText_KeyPress(KeyAscii As Integer) RaiseEvent KeyPress(KeyAscii) End Sub Private Sub mText_KeyUp(KeyCode As Integer, Shift As Integer) RaiseEvent KeyUp(KeyCode, Shift) End Sub Private Sub mText_LostFocus() RaiseEvent LostFocus End Sub Private Sub mText_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) RaiseEvent MouseDown(Button, Shift, X, Y) End Sub Private Sub mText_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single) RaiseEvent MouseMove(Button, Shift, X, Y) End Sub Private Sub mText_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single) RaiseEvent MouseUp(Button, Shift, X, Y) End Sub Es decir, simplemente usamos RaiseEvent seguido del evento que haya que producir. Esto, como puedes comprobar, es lo que habra que hacer o si lo prefieres, es la nica forma de hacerlo, al menos en este caso, ya que los eventos del objeto mText slo se
582
producen en la clase y no fuera de ella, ya que fuera, se producirn los que la propia clase haya implementado, esto lo comprobaremos en un momento. Ahora vamos a ver cmo indicarle a la clase el objeto del tipo TextBox que debe manipular. Para ello, crearemos un procedimiento (mtodo), el cual recibir un parmetro, que ser el TextBox que la clase debe manipular. A ese mtodo lo llamaremos de la misma forma que la clase... S, as es como se hace en C++/C# y que pasa? que podra haberlo llamado New como lo hace el VB.NET? pues no! adems de porque no me gusta, por la sencilla razn de que no podemos tener un mtodo que se llame igual que una palabra clave del Visual Basic. Veamos el "constructor" extra de la clase, y digo "extra", por la sencilla razn de que todas las clases tienen un constructor, es decir, un procedimiento que se ejecuta cuando se crea una nueva instancia de la clase (usando New), ese procedimiento es: Class_Initialize, pero al crear una nueva instancia de la clase, no se puede indicar ningn parmetro, por tanto tendremos que crear un procedimiento al que "forzosamente" haya que llamar para que la clase sepa que TextBox es el que manipular. Tambin veremos el cdigo del destructor de la clase, (Class_Terminate), es decir, el cdigo que se ejecutar cuando la clase ya no est referenciada por ningn objeto. ' IMPORTANTE: ' Hay que usar este mtodo para inicializar el control a ampliar Public Sub cTextBoxEx(ByVal value As TextBox) If TypeOf value Is TextBox Then Set mText = value Else Err.Raise 13, "cTextBoxEx", "El objeto debe ser del tipo TextBox" End If End Sub
Private Sub Class_Initialize() ' este procedimiento se produce al crear una nueva instancia de la clase. End Sub Private Sub Class_Terminate() ' este se produce cuando la clase ya no se utiliza ms. ' aqu liberaremos los recursos que estemos empleando. Set mText = Nothing End Sub
583
Como puedes ver en el comentario del procedimiento cTextBoxEx, es muy IMPORTANTE que se llame a ese mtodo antes de hacer nada con el objeto creado a partir de la clase; esto es as, por la sencilla razn de que si no lo hacemos, la variable mText no estar apuntando a ningn objeto. Tambin puedes comprobar que hago una comprobacin de que el tipo del parmetro sea un TextBox, para ello he usado la siguiente lnea: If TypeOf value Is TextBox Then Realmente no sera necesario, pero... as, si decides cambiar el parmetro a un tipo ms genrico, como por ejemplo Object, te asegurars de que se est asignando un objeto del tipo TextBox, que es el tipo de datos que la clase manipular. En caso de que el objeto no fuera del tipo adecuado, se producira un error indicando ese hecho, para ello usamos el mtodo Raise del objeto Err. En el procedimiento (realmente es un evento) Class_Initialize no hacemos nada, ya que, al menos por ahora, no necesitamos hacer nada; sin embargo en el evento Class_Terminate, asignamos a la variable mText un valor Nothing, para liberar recursos, aunque si no lo hacemos, ser el propio VB el que se encargue de liberar esos recursos, pero, a mi me gusta hacerlo de forma explcita... cosas mas. Ahora veamos el cdigo a usar en el formulario. Recuerda que tenemos dos objetos del tipo TextBox, uno de ellos, el Text2, ser el que nuestra clase manipular. De las cuatro etiquetas que te indiqu, dos de ellas se usarn para mostrar informacin de que se han producido los eventos. Tambin tendremos un botn para cerrar el formulario (cmdCerrar). Veamos el cdigo del formulario: Option Explicit Private WithEvents txtBoxEx As cTextBoxEx
Private Sub Form_Load() Set txtBoxEx = New cTextBoxEx txtBoxEx.cTextBoxEx Text2 End Sub
' Text1 ser un control TextBox normal Private Sub Text1_Change() lblTxt1 = " evento Text1_Change" End Sub
584
Private Sub Text1_GotFocus() lblTxt1 = " evento Text1_GotFocus" End Sub Private Sub Text1_KeyPress(KeyAscii As Integer) lblTxt1 = " evento Text1_KeyPress, con KeyAscii= " & CStr(KeyAscii) End Sub
' txtBoxEx ser nuestra clase, que manipular al Text2 Private Sub txtBoxEx_Change() lblTxt2 = " evento txtBoxEx_Change" End Sub Private Sub txtBoxEx_GotFocus() lblTxt2 = " evento txtBoxEx_GotFocus" End Sub Private Sub txtBoxEx_KeyPress(KeyAscii As Integer) lblTxt2 = " evento txtBoxEx_KeyPress, con KeyAscii= " & CStr(KeyAscii) End Sub Como puedes comprobar, este cdigo no tiene ningn misterio, ya que es as como habra que hacerlo y para ayudarnos, el propio Visual Basic nos informa de los eventos que produce el objeto indicado por la variable txtBoxEx, por la sencilla razn de que la hemos declarado con WithEvents. En lo nico que debes tener "cuidado" es hacer lo que se hace en el evento Form_Load, ya que en ese evento se crea la nueva instancia de la clase: Set txtBoxEx = New cTextBoxEx y se asigna el textbox que debe manipular: txtBoxEx.cTextBoxEx Text2.
Aadir nuevos miembros a la clase Ahora que tenemos "la base" de cmo habra que manipular un TextBox desde una clase, vamos a aadir nueva funcionalidad a esa clase, ms que nada para que tenga alguna razn el rizar el rizo, ya que si simplemente queremos tener la misma funcionalidad que tiene un TextBox normal, no hace falta crear ninguna clase intermedia... Empezaremos aadiendo un mtodo ToString, el cual se usar de la misma forma que lo hace su hermano Visual Basic .NET (o las clases de .NET Framework para ser ms precisos).
585
Lo que este mtodo har, ser devolver una cadena del contenido del TextBox, pero, para que tenga alguna "gracia", (ya que eso se puede hacer simplemente llamando a la propiedad Text del objeto), vamos a usar un parmetro, el cual indicar el formato que queramos que devuelva. Por ejemplo, si el contenido de la clase es una fecha, podra interesarnos que devuelva esa fecha en el formato dd/mm/yyyy o si es un nmero que podamos darle "formato", para ello usaremos la funcin Format$ del VB para que aplique el formato que le indiquemos. Veamos el cdigo del mtodo ToString de la clase cTextBoxEx: Public Function ToString(Optional ByVal formato As String = "") As String ' Si se produce algn error, ignorarlo On Error Resume Next ' ' devolvemos el contenido del text con el formato indicado, ' si es que se ha indicado algn formato If formato = "" Then ToString = mText.Text Else ToString = Format$(mText.Text, formato) End If End Function En la declaracin indicamos que el parmetro es opcional, por tanto, esta funcin se puede usar de dos formas: 1- sin indicar el parmetro 2- indicando el formato a aplicar al contenido del textbox En caso de que no se indique el parmetro, se asignar a dicha variable el valor por defecto que se indica en la declaracin, es decir: una cadena vaca. Este hecho se tiene en cuenta en la comparacin que hay dentro de la funcin y se har una cosa u otra, segn se haya o no indicado el parmetro. En el caso de que no se haya indicado, o si se haya indicado, pero sea una cadena vaca, se devolver el contenido de la propiedad Text del TextBox usado internamente. Si se indica un formato, este ser el que se use para devolver el contenido de dicha propiedad. Debido a que es posible que se use un formato errneo, utilizamos una captura de errores, de forma que si se produce un error, simplemente se contine con la siguiente lnea. Como sabemos, si declaramos una funcin o un procedimiento Sub de forma Public, ese procedimiento se convierte en un mtodo de la clase y por tanto lo podemos usar desde cualquier sitio. (S, ya se que esto lo sabes, pero es simplemente para recordrtelo y as poder tener tiempo para poner el ejemplo.) Veamos cmo usarlo desde el cdigo del formulario, para ello, aade un botn (CommandButton) al que llamaremos cmdFormato y aade este cdigo al evento Click: Private Sub cmdFormato_Click() lblTxt2 = txtBoxEx.ToString("dd/mm/yyyy")
586
End Sub Bien, ya tenemos la base y el conocimiento de cmo podemos ampliar un TextBox, ahora slo falta echarle un poco de imaginacin y adaptarlo a nuestras necesidades. Para ampliar ms la clase, vamos a aadirle una propiedad que indicar el tipo de datos que manipular la clase, esos datos podrn ser de tres tipos diferentes: -Normal, aceptar cualquier cosa, tal como lo hace el textbox de forma predeterminada -Numrico, slo aceptar nmeros, estos a su vez podrn ser con y sin decimales -Fecha, aceptar slo fechas o al menos lo intentar... Para indicar estos valores, vamos a declarar una enumeracin pblica en la clase, de forma que la propiedad usada para el tipo de datos, utilice los valores de esa enumeracin. Tambin declararemos una variable privada para mantener el valor de esa propiedad, aunque podramos haber declarado la propiedad como una variable pblica, pero esto no nos permitira hacer ciertas comprobaciones, como por ejemplo, comprobar que el valor asignado a esa propiedad es uno de los valores permitidos. Veamos cmo hacer todo esto y despus utilizaremos esa propiedad para que realmente slo acepte ese tipo de datos: ' enumeracin de los valores posibles para el tipo de datos Public Enum eTipo Normal Enteros Decimales Fecha End Enum ' variables (campos) privados para las propiedades de la clase Private m_Tipo As eTipo Este cdigo lo tendrs que aadir en la parte "General" de la clase y lo que sigue, lo puedes aadir al final del cdigo que ya tenemos: ' las propiedades de la clase Public Property Get Tipo() As eTipo Tipo = m_Tipo End Property Public Property Let Tipo(ByVal value As eTipo) ' aqu podramos comprobar que el tipo asignado sea el correcto Select Case value Case eTipo.Decimales, eTipo.Enteros, eTipo.Fecha, eTipo.Normal
587
' no hacemos nada Case Else value = eTipo.Normal End Select m_Tipo = value End Property El Property Let es el cdigo que se utiliza cuando asignamos un valor a la propiedad, por tanto ser aqu donde hagamos la comprobacin de que el valor asignado sea del tipo correcto, es decir, uno de los valores de la enumeracin. Para ello, usamos un bloque Select Case para comprobar esos valores y en el caso de que no sea uno de los permitidos, asignamos el valor Normal, que es el que tendr esa propiedad de forma predeterminada, por la sencilla razn de que al ser el primer valor de la enumeracin, valdr cero. Si quisiramos que dicho valor predeterminado fuese otro, lo podramos asignar en el constructor de la clase, tal como podemos comprobar a continuacin: Private Sub Class_Initialize() ' este procedimiento se produce al crear una nueva instancia de la clase. ' asignamos el valor predeterminado de la propiedad Tipo: m_Tipo = eTipo.Normal End Sub Pero como te he dicho, no es necesario, por la sencilla razn de que eTipo.Normal vale cero. Y aqu vamos a acabar esta entrega... no sin antes proponerte un ejercicio. Ese ejercicio consiste en tener en cuenta si el tipo de datos que manipular la clase es de tipo Decimal o Entero slo permita que se asignen nmeros... pero cuidado! los nmeros tambin permiten que se indique si es negativo y en caso de que sean nmeros decimales, podra ser necesario que admita valores de tipo "cientfico", es decir que el nmero puede contener la letra E o D, segn sea del tipo Single o Double. La solucin en la prxima entrega.
Aqu tienes la solucin al ejercicio propuesto en la entrega 46. Realmente lo nico que haba que aadir era el cdigo necesario a la pulsacin de teclas en la clase, para ello tenas que modificar el evento KeyPress del objeto mText que es el que se encarga de manipular el TextBox asociado con esta clase. Lo que hacemos (al menos lo que yo creo que habra que hacer) es: Comprobar el tipo asignado en la propiedad Tipo, de forma que si es del tipo Enteros se compruebe si se han pulsado las teclas: BackSpace (borrar hacia atrs), los signos ms (+), menos (-) y cualquiera de los dgitos de cero a nueve. En el caso de que el valor de la propiedad Tipo sea Decimales se comprueba adems si se ha pulsado la coma, el punto y cualquiera de las letras E o D (tanto en maysculas como en minsculas), de esta forma, aceptaremos cualquier tipo de valor decimal. Tanto si el valor de la propiedad Tipo es Enteros o Decimales, se utiliza un segundo Select Case para comprobar si es una de las teclas aceptadas o no, en caso negativo, asignamos un valor cero al parmetro recibido (KeyAscii) para que no tenga en cuenta la tecla pulsada, y si es una de las teclas aceptadas, simplemente no hacemos nada, con lo cual permitimos que se acepte la pulsacin de esa tecla. Y siempre se obliga a que se produzca el evento KeyPress de esta clase, para ello utilizamos la ltima lnea: RaiseEvent KeyPress(KeyAscii), ya que este ser el evento que la clase que hayamos declarado en el formulario recibir, por tanto en muy importante no olvidar de aadir esta produccin del evento al final del procedimiento. En ese evento ya estar filtrada la tecla pulsada, de forma que si la tecla no es vlida, no se recibir... bueno, si se recibe, pero al ser un valor cero, no se tiene en cuenta. Por ltimo, si el valor asignado a la propiedad Tipo no es ninguno de los dos que hemos comentado, admitir cualquier tecla y esa tecla ser la que se enve al evento del objeto creado en el formulario desde el que se utilice nuestra clase.
El cdigo completo es el siguiente: Private Sub mText_KeyPress(KeyAscii As Integer) Select Case m_Tipo Case eTipo.Enteros Select Case KeyAscii Case 8, 43, 45, 48 To 57 ' Slo admitir teclas consideradas numricas ' El 8 es la tecla Backspace (borrar hacia atrs) ' Los cdigos 43 y 45 son los signos + y respectivamente Case Else
589
' No es una tecla numrica, no admitirla KeyAscii = 0 Beep End Select Case eTipo.Decimales Select Case KeyAscii Case 8, 43 To 46, 48 To 57, 68, 69, 100, 101 ' Slo admitir teclas consideradas numricas ' El cdigo 44 es la coma y el 46 es el punto ' La E y D son para nmeros con notacin cientfica ' (68 y 100 es la E y e, 69 y 101 es D y d) ' El 8 es la tecla Backspace (borrar hacia atrs) ' Los cdigos 43 y 45 son los signos + y respectivamente Case Else ' No es una tecla numrica, no admitirla KeyAscii = 0 Beep End Select End Select RaiseEvent KeyPress(KeyAscii) End Sub
Espero que esto sea lo que ms o menos hayas hecho, aunque no es necesario que sea exactamente como aqu te lo he mostrado, ya que existen otras formas de hacer lo mismo, lo importante es que se hayan hecho estas comprobaciones, me refiero a aceptar las teclas mencionadas, para que se acepten los datos que hemos definido, es decir: nmeros, tanto decimales como enteros.
Otra cosa es aceptar valores de fecha, en ese caso habra que aceptar, adems de las cifras, los separadores de las fechas y tambin podramos hacer que al pulsar Intro en el textbox se comprobara si la fecha es correcta y esas cosas... pero eso lo dejo a tu gusto... si revisas entregas anteriores, vers que tenamos cdigo para comprobar fechas... si no recuerdo mal. De todas formas, es posible que en otra ocasin te de mi solucin para que tengas un punto de referencia de cmo hacerlo, pero eso no ser ahora... ya veremos cuando! Nos vemos Guillermo
590
Seguramente pensars que hay muchas cosas que explicar de Visual Basic, y tienes razn, pero debido a que el ttulo de este "tutorial" es Curso Bsico de Programacin en Visual Basic, no pretenders que te lo explique todo... s, ya s que eso es lo que "el Guille" debera hacer... pero... Tampoco te creas que es que quiero "liquidar" ya este curso de Visual Basic... que por ahora no lo voy a dar por terminado, pero... (ya van dos peros), en fin... que tampoco se puede explicar todo... y aunque parezca que no, pero en este curso ya son muchos los conceptos que se han explicado, no todos, lo s, pero si los suficientes como para que te arriesgues "un poco" e investigues por tu cuenta y riesgo... No, que no, que no voy a "pasar" del curso bsico, simplemente te estoy "medio" advirtiendo que no abarcar todos los temas, as que... intenta buscar ms documentacin u otros cursos por Internet y no lo dejes todo de manos del Guille, que no lo vas a tener todo... aunque tambin te recomiendo que eches un vistazo a las muchas cosas que hay publicadas en mi sitio sobre VB que alguna te servir... espero! Bueno, despus de este prrafo "liante" vamos a ver que es lo que podemos hacer... o mejor dicho, que es lo que me gustara explicarte en las entregas que seguirn en un futuro prximo en esto que empieza ahora y que poda ser la segunda parte del Curso Bsico de Programacin con Visual Basic.
Introduccin
Lo que me gustara explicarte son cosas como la creacin de controles ActiveX (OCX) y libreras (DLL) ActiveX, que al fin y al cabo son conceptos bastante similares, cuando las explique vers que es as. Tambin tengo pensado, siguiendo en esta mima lnea de "automatizacin" de Visual Basic, a explicarte cmo modularizar o "trocear" tus aplicaciones en componentes ActiveX, de forma que puedas crear componentes (controles o libreras ActiveX) que sean totalmente funcionales y que puedas usar de forma independiente en distintas aplicaciones. Para no confundirte mucho, vamos a empezar a repasar algunos "conceptos" de los que hablaremos en esta y prximas entregas, para que cuando te encuentres con esos "trminos" no te hagas un lo... (traduzco: no te confundas).
Componentes.
591
Empecemos por explicar qu significa eso de componentes. Segn la documentacin de la MSDN Library que se incluye con Visual Studio 6.0 (VB 6.0), un componente es: Cualquier software compatible con Automatizacin, por lo que puede usarse mediante programacin en una aplicacin personalizada. Incluye controles ActiveX, servidores de Automatizacin basados en Visual Basic y servidores de Automatizacin basados en Visual C. Antes de "explicarte" o aclararte que significa esto, veamos lo que dice la documentacin sobre Automatizacin: Una tecnologa que permite que las aplicaciones proporcionen objetos de una forma coherente a otras aplicaciones, herramientas de programacin y lenguajes de macros. Ahora que tenemos definidos dos de los conceptos que aparecen en la documentacin de Visual Basic, te explicar con lenguaje lo ms llano posible qu significa esto de los componentes. Desde la versin 4.0 de Visual Basic, este lenguaje permite crear aplicaciones compatibles con lo que antes se llamaba automatizacin OLE, (OLE: Object Linking and Embedding, vinculacin e incrustacin de objetos), ese nombre ahora es ms conocido como automatizacin COM (o simplemente como COM). Las siglas COM significan Component Object Model que traducido sera algo as como modelo de objetos componentes. Siguiendo mi bsqueda de definiciones en las diferentes "documentaciones" de Microsoft, me he encontrado tambin con esta otra sobre COM: COM es el "modelo de objetos" fundamental sobre el que se generan OLE (Object Linking and Embedding, Vinculacin e incrustacin de objetos) y los controles ActiveX. COM permite que un objeto exponga su funcionalidad a otros componentes y aloje aplicaciones. Define cmo el objeto se expone a s mismo y cmo funciona dicha exposicin en procesos y en redes. Adems, COM define el ciclo de vida del objeto. No es que no quiera explicarte lo que significan todas estas "siglas", es que prefiero que leas lo que la documentacin "algunas" veces te dice y despus te lo aclaro, para que lo digieras mejor... si es posible. Lo que debe quedarte claro, es que un componente es un "trozo" de cdigo que podemos usar en cualquier aplicacin. La peculiaridad de los componentes es que son totalmente operativos por s mismos, te aclaro esto para que no pienses que al decir que es un trozo de cdigo, una rutina (funcin o procedimiento) podra ser un componente, ya que eso no es totalmente cierto. Los componentes COM son programas que permiten ser utilizados desde otro programas mediante automatizacin. Por regla general, los componentes suelen ser libreras (o si lo prefieres bibliotecas) ActiveX (DLL), controles ActiveX (OCX) e incluso ejecutables ActiveX (EXE). Nota: Puede que en esta y siguientes entregas, simplemente utilice la palabra componente, pero cuando lo haga me referir a componentes de automatizacin
592
(o componentes COM), ya que en el mundo de .NET tambin se usa el trmino componente y su uso no se refiere precisamente a un "objeto" de automatizacin. No hace falta que te diga que no es lo mismo un EXE ActiveX que un EXE normal, por la sencilla razn de que si un EXE normal fuese lo mismo... desde hace tiempo que estaramos creando componentes COM. Igualmente no es lo mismo una DLL normal que una DLL ActiveX, ya que una DLL "normal" simplemente tiene funciones que podemos usar en nuestras aplicaciones, pero estas se usan directamente, sin la intervencin de COM, tal es el caso de las DLLs del API de Windows las cuales podemos usar en cualquier programa. Por otro lado las DLL ActiveX son libreras que se pueden usar slo con lenguajes que puedan trabajar con Automatizacin OLE (o COM). Visual Basic est totalmente "adherido" a la automatizacin, es decir, entiende cmo crear objetos contenidos en componentes COM (de automatizacin) y, lo ms importante, tambin puede crear componentes COM para que puedan ser usados desde otros lenguajes "adheridos" a la automatizacin. Por tanto, Visual Basic podr crear libreras (DLL) de automatizacin (componentes COM), pero no podr crear libreras "normales". Aclaro este punto, porque a pesar de que la extensin sea la misma para una librera normal y una de automatizacin, nuestro querido VB no podr crear ficheros con la extensin DLL que se puedan usar de la misma forma que las libreras del API de Windows o las creadas por compiladores como C/C++ e incluso Delphi. Las libreras creadas por Visual Basic siempre son libreras de automatizacin. Despus de estas aclaraciones, vamos a seguir con las explicaciones de porqu pueden ser tiles estos componentes COM, aunque cuando hable de componentes (o componentes COM) me estar refiriendo normalmente a "cdigo" que podemos usar desde cualquier aplicacin de automatizacin, y ese "cdigo" normalmente estar compilado en la forma de una librera (DLL). Realmente no es "cdigo", al menos como se entiende por cdigo cuando se dice "cdigo fuente", sino a cdigo compilado o cdigo binario, es decir, un cdigo fuente que se ha compilado.
Qu ventajas tiene la creacin y utilizacin de componentes? Si has llegado a esta entrega nmero 47, (sin saltarte ninguna), ya habrs estado usando algunos componentes de automatizacin, por ejemplo ADO (ActiveX Data Objects) es un componente de automatizacin, el cual nos permite crear objetos para poder acceder a las bases de datos. Tambin habrs usado "componentes" en la forma de libreras del API de Windows. Y por supuesto que habrs usado "componentes" en la forma de controles OCX. Por tanto la utilidad de usar "componentes" es obvia, si ya hay alguno que nos sirva para nuestra aplicacin, lo usamos y no tenemos que programar ni una lnea para tener la funcionalidad que ese componente nos ofrece. Por otro lado, la creacin de un componente nos permite escribir un cdigo que "posiblemente" nos servir para usarlo en ms de una aplicacin, adems de que ese mismo componente podemos distribuirlo para que otros programadores puedan usarlo. El "posiblemente" lo he entrecomillado, porque si creamos un componente no quiere decir
593
que "forzosamente" tengamos que usarlo en ms de una aplicacin, ya que es muy posible que simplemente lo usemos una vez y nada ms, pero eso no es un impedimento para que lo "encapsulemos" en un componente (librera DLL o control OCX). Cual sera la ventaja de crear un componente aunque slo nos sirva para una aplicacin? La ventaja es que si en un futuro queremos hacer cambios en el cdigo de ese componente, slo tendremos que distribuir esa librera y no el resto de la aplicacin, incluso (aunque ese tema no lo trataremos en este curso) puede ser que ese componente se ejecute desde un servidor, con lo cual simplemente actualizndolo en el servidor, el resto de las aplicaciones "cliente" que lo utilicen estarn actualizados "a la ltima". La ventaja tambin es evidente, ya que si no tuvisemos el cdigo "troceado", tendramos que compilar toda la aplicacin, de esta forma slo tendramos que compilar el componente y distribuirlo... cuando digo distribuirlo, en la mayora de los casos, simplemente ser "copiar y pegar" esa librera en el directorio que estaba y nada ms. S, ya se lo que estars pensando, y si an no lo ests pensando, este pensamiento te "llegar" cuando sepas algo de la creacin de componentes ActiveX, y como no es plan de esperar el tiempo necesario para que sepas algo de componentes, te explico cual sera ese pensamiento al que me refiero: Pregunta/pensamiento: Si tengo que compilar el componente y distribuirlo, qu problema hay con compilar toda la aplicacin y distribuirla? Respuesta de tu "subconsciente Guille": Pues ninguno. Precisamente por eso te lo "recalco", porque como no hay ninguna diferencia, (salvo que el componente sea uno que resida en un servidor y entonces si que habra diferencia), puedes pensar que para qu complicarme la vida haciendo componentes, cuando es mejor escribir todo el cdigo junto, que con total seguridad me dar menos quebraderos de cabeza? Y si tienes este ltimo pensamiento "pasars" de crear y usar componentes, y si es eso lo que va a ocurrir... que puetas, (ltimamente el Guille se est volviendo algo ms recatado y ahora en lugar de decir coo, dice puetas, que es casi lo mismo pero suena menos grosero), hago yo aqu tratando de explicarte todo esto? Pues eso, aunque no los uses, o no pretendas usarlos, emppate de qu va todo esto de los componentes y "aprende" a usarlos aunque pienses que no te ser realmente til. A la larga lo agradecers, de verdad. Es como lo de usar Option Explicit, cuando te acostumbras a declarar todas las variables, ya no puedes "vivir" sin declararlas, lo mismo que cuando te acostumbras a indentar el cdigo, si no est indentado, parece que ni lo entiendes... (Este Guille no tiene remedio! tena que aprovechar la coyuntura para darte un par de consejillos de los suyos.)
Otra de las cosas que vamos a ir viendo con todo esto de los componentes, sern cosas relacionadas con la programacin orientada a objetos, aunque si bien es cierto que el Visual Basic no es un lenguaje orientado a objetos, podemos hacer cosas que "casi" parezcan... por supuesto estoy hablando del Visual Basic 6.0 (y anteriores), ya que el Visual Basic .NET si que es un lenguaje orientado a objetos. Todo esto es debido a que la creacin de componentes ActiveX nos permitir entrar un "poco" en esa dinmica, por aquello de que la creacin de componentes nos permitir "granular" nuestra aplicacin en trozos (que al fin y al cabo sern clases que se convertirn en objetos). Ya se que esto no ser Programacin Orientada a Objetos, (as en maysculas o en negrita), pero al menos te dar una idea de cmo podra ser... y sobre todo, como es ahora mismo si te atreves a entrar en el mundo de .NET, que al fin y
594
al cabo debera ser el lenguaje que deberas usar, as que... no se que haces perdiendo el tiempo en este curso si ya tenas que estar en el de Visual Basic .NET. Dicho todo esto, quiero que prepares cuerpo y mente para lo que seguir y, si ves que no ests totalmente preparado, (o preparada), es que va siendo hora de que empieces de nuevo por la entrega uno de este curso, te leas la ayuda del Visual Basic o te dediques a otra cosa, ya que... mejor o peor, (segn quien opine), en las 46 entregas anteriores te he explicado un montn de cosas, las cuales, si las has "aderezado" con alguna otra lectura, seguro que te permitir empezar a adentrarte en el mundillo de la programacin con Visual Basic, que al fin y al cabo es lo que este "curso" ha pretendido y pretende.
Nota: Te recomiendo que si el tema de las clases no lo tienes claro, te repases las entregas 37, 38, 39, 39.2, 42, 43, 44, 45 y 46, ya que para poder crear componentes de automatizacin es fundamental el conocimiento de cmo definir una clase, adems de cmo crear nuevas instancias (objetos en memoria) de una clase.
De todas formas, vamos a repasar rpidamente los conceptos ms importantes. Repaso rpido sobre las clases. Para escribir el cdigo de una clase, hay que usar un mdulo especial, el cual le indicar al Visual Basic que el cdigo insertado en l es una clase. Este tipo de mdulos tendrn la extensin .cls.
Para poder usar una clase, tenemos que crear una referencia a esa clase, esto se hace declarando una variable cuyo tipo ser el nombre que le hemos dado a la
595
Cuando declaramos una variable con el tipo de datos de una clase, realmente no tenemos nada, simplemente una variable "capaz" de manejar un objeto del tipo de la clase.
Para poder usar esa clase, tenemos que crear una nueva instancia en la memoria, esto lo conseguimos mediante un cdigo similar a este: Set miClase = New cClase
Si bien podemos declarar la clase y crear el objeto en una sola instruccin: Set miClase As New cClase Esto no es recomendable y NO DEBERAS HACERLO NUNCA, ya que el rendimiento de la aplicacin se vera afectado, adems de que podra provocar problemas "colaterales" que en ocasiones sera difcil de descubrir. Por qu se vera afectado el rendimiento? Porque cuando declaramos e instanciamos el objeto en la misma lnea, el Visual Basic aadir cdigo extra cada vez que se use esa variable para comprobar si el objeto ya existe, y en caso de que no exista lo crear "automticamente". Qu problemas aadidos podramos tener? Debido a esta auto-creacin de los objetos declarados con As New, puede ser que por "accidente" creemos un objeto sin que ese fuese nuestro propsito. Por ejemplo, si le asignamos un valor Nothing a la variable, (para que el objeto deje de existir), y volvemos a usarlo, (incluso para saber si el contenido es Nothing, lo cual indicara que ya no existe), el Visual Basic lo volver a crear (instanciar), con lo cual nunca sabramos si lo hemos eliminado, ya que al comprobarlo, lo estamos volviendo a crear... Y lo que es peor, con el uso de ese tipo de objetos nos acostumbraramos a "mal usarlos", ya que, al saber que
596
Cualquier "variable" declarada como pblica dentro de una clase, se convierte automticamente en una propiedad de esa clase. Este tipo de "elementos" de las clases, en otros lenguajes se llaman campos, pero el Visual Basic (realmente el COM) le da un tratamiento de propiedad, cosa que podemos comprobar si usamos Implements (ver la entrega 44).
Para asignar un objeto a una variable, siempre debemos usar Set variable = Objeto, ya que el SET es lo que le dice al VB que nuestra intencin es asignar un objeto y no la propiedad que la clase tiene por defecto. El concepto de cmo crear una propiedad (o mtodo) que ser el que se usar por defecto, se explic en la entrega 42. Por ejemplo, si la propiedad por defecto de la clase a la que apunta la variable miClase es Nombre, al hacer esto: miClase = s Lo que estamos haciendo es asignando a la propiedad Nombre del objeto miClase el contenido de la variable s. Pero si la variable s realmente es otro objeto y lo que queremos que se asigne a miClase es el objeto referenciado por s, habra que hacer esto otro: Set miClase = s
Cuando asignamos, mediante Set, un objeto a otro, no estamos haciendo una copia del objeto, lo que le estamos diciendo al compilador es que ahora la variable indicada despus de Set tambin apunte al mismo objeto que existe en la memoria, por tanto slo existir un objeto en memoria, pero existirn varias variables que hagan referencia a ese objeto. Esto es importante, ya que cualquier cambio que hagamos en el objeto
597
referenciado mediante cualquiera de las variables que apuntan a dicho objeto, se reflejar en todas las variables que apunten a ese objeto, ya que slo habr un objeto creado en la memoria. En la entrega 39 y tambin en la 42 se explic esto al hablar del mtodo Clone.
Todas las clases tienen dos procedimientos "especiales" que se usan al estilo de los eventos (al menos por la forma en que se las llama en Visual Basic), que nos pueden servir para saber cuando se crea una clase y cuando se destruye. Si necesitamos hacer algo "justo" cuando se crea una clase, podemos escribir cdigo en el "evento" Class_Initialize, (esto es lo que en OOP se llama el constructor). Si necesitamos hacer algo cuando se destruya la clase (realmente el objeto creado en la memoria), se llama al "evento" Class_Terminate, (esto sera el destructor de la clase... o casi). Estos procedimientos los podemos usar para, por ejemplo, en el caso del "constructor", asignar valores que deben tener las propiedades por defecto, crear objetos que la clase usar, etc. Y en el caso del "destructor" para eliminar las referencias a los objetos que nuestra clase necesite. En las entregas 38 y 42 se usaban estos procedimientos para crear/destruir los objetos del tipo Collection que la clase necesitaba.
Los elementos o miembros que una clase puede contener son: Constantes, en forma de enumeraciones o declaradas directamente con Const. Propiedades, declaradas como variables pblicas o mejor an, (recomendado), declarndolas como procedimientos Property. Mtodos, declarados como procedimientos Sub o Function. Eventos, esta ser la forma de notificar al programa que est usando nuestra clase de que ha ocurrido algo digno de tener en cuenta.
598
Los elementos de una clase pueden estar declarados como Private, Friend o Public. Private, estos elementos slo sern visibles dentro de la propia clase. Friend, estos elementos sern visibles dentro de la clase y por instancias creadas desde cualquier parte de la mima aplicacin (realmente desde el mismo proyecto en el que se encuentre nuestra clase). Public, ser visible en cualquier parte.
Para terminar, recordarte que los formularios tambin son clases, especiales, pero clases al fin y al cabo, y podemos usarlas de la misma forma como usaramos cualquiera de las que nosotros codifiquemos.
Creo que con este repaso, ms o menos, recordars las cosas ms importantes sobre las clases. De todas formas, algunos de estos conceptos los iremos "repasando" a lo largo de esta y siguientes entregas, incluso puede que aparezcan algunos nuevos y si no nuevos, puede que los explique un poco ms a fondo... ya veremos.
Para terminar esta entrega, vamos a crear un componente (una librera ActiveX) y veremos cmo podemos "probarla" y usarla desde otros proyectos.
599
Empecemos abriendo del Visual Basic, en este ejemplo voy a usar el VB6, si no tienes el VB6, podrs usar el VB5, pero no te servir el VB5CCE, (ver el apndice A), ya que el VB5CCE no permite crear libreras DLL ActiveX, lo siento. En otra ocasin veremos cmo crear un control ActiveX y en esa ocasin si podrs usar el VB5CCE. Cuando nos pregunte el tipo de proyecto a crear, seleccionaremos ActiveX DLL, (ver la figura 1)
Se crear un proyecto llamado Project1 (al menos en la versin inglesa de VB6 que es la que tengo instalada) Ese proyecto tendr una clase, llamada Class1. Lo primero que debemos hacer es cambiar el nombre de la clase y del proyecto. A la clase la vamos a llamar cEntrega47, al proyecto lo llamaremos Entrega47AX. Es importante que el nombre de la clase y el del proyecto se llamen de forma distinta. Para poder cambiar el nombre de la clase, selecciona la clase en el Explorador de proyectos y en la ventana de propiedades, escribe el nombre en la propiedad Name (ver figura 2). Si tienes el VB5, habr menos propiedades mostradas. Haz lo mismo con el proyecto, selecciona el proyecto en el Explorador de proyectos y cambia la propiedad Name, que ser la nica que se muestre.
600
Figura 2, propiedades de la clase Adems de la propiedad Name (nombre) del proyecto, tambin podemos configurar otras cosas, pero eso lo veremos en otra ocasin. Ahora vamos a aadirle los "miembros" que tendr la clase. Nota: Aqu voy a usar "las recomendaciones", aunque en este caso podra usar el camino corto por aquello de que es un ejemplo trivial, no quiero que ya desde este momento te vayas acostumbrando a las "malas" formas de programar. Para crear propiedades, puedes usar el "asistente" del VB, o bien escribir el cdigo directamente. Si has ledo el Apndice A, en el que te explicaba cmo configurar el entorno del VB, sabrs que me gusta tener en la barra de herramientas un botn (o icono) para crear nuevos procedimientos. Pero si no tienes esa opcin en la barra de herramientas, puedes acceder a ella mediante la opcin del men Tools>Add Procedure... (Herramientas>Aadir procedimiento...). Se mostrar un cuadro de dilogo como el mostrado en la figura 3.
601
Y se crear la propiedad con el nombre que hemos indicado, en este caso, la propiedad Nombre. Fjate que cuando creas una propiedad de esta forma, siempre ser del tipo Variant, (ver figura 4), por tanto tendrs que cambiar el tipo para que sea del adecuado, en este caso ser del tipo String.
Figura 4, el cdigo creado por Add Procedure... El cdigo ser el siguiente: '----------------------------------------------------------------------------' Clase cEntrega47 (22/Ago/03) ' Prueba para el Curso Bsico de Visual Basic ' ' Guillermo 'guille' Som, 2003 '----------------------------------------------------------------------------Option Explicit ' las variables privadas que se usarn con las propiedades Private m_Nombre As String ' La propiedad Nombre Public Property Get Nombre() As String
602
Nombre = m_Nombre End Property Public Property Let Nombre(ByVal newValue As String) m_Nombre = newValue End Property ' La propiedad Version ser de slo lectura Public Property Get Version() As String Version = "1.00.0001" End Property ' El mtodo HoraActual devuelve la fecha y hora actual Public Function HoraActual() As Date HoraActual = Now End Function Ahora vamos a aadir un nuevo proyecto, en este caso ser del tipo Exe normal. En el men File (Archivo) selecciona Add project... (Aadir proyecto...) se mostrar el cuadro de dilogo mostrado en la figura 5 (que es parecido al de la figura 1), selecciona Standard EXE (recuerda que mi VB est en ingls).
Figura 5, Aadir un nuevo proyecto Al pulsar en "Open" (Abrir) se crear un nuevo proyecto, el cual se aadir al Explorador de proyectos (ver figura 6).
603
Cambia el nombre del proyecto para que tenga el nombre tEntrega47, el nombre del formulario lo vamos a dejar como est, es decir, se llamar Form1.
Figura 6, El explorador de proyectos Si te fijas, este grupo de proyectos se llamar Group1. Este nombre puedes cambiarlo cuando lo vayas a grabar o tambin desde el men File>Save Group Project As... (Archivo>Guardar Grupo de Proyectos Como...) Ya tenemos nuestros dos proyectos. Pero el que los dos proyectos estn "juntos" no quiere decir que el uno sepa del otro. Realmente actuarn como si no se conocieran, es decir, no podremos usar la clase cEntrega47 desde el proyecto de prueba, ya que el proyecto de prueba (tEntrega47) no conoce la existencia de la librera Entrega47AX. Para que nuestro proyecto de pruebas se entere de la existencia de esa librera, tendremos que aadir una referencia a la misma, (igual que haramos para aadir una referencia a los objetos de ADO). Por tanto, tendrs que seleccionar ese proyecto en el Explorador de proyectos y en el men Project (Proyecto), seleccionar la opcin References... (Referencias...) y de las que te muestre, selecciona la que nos interesa, ver figura 7:
604
Figura 7, Referencias del proyecto EXE de prueba. A partir de este momento ya podemos crear objetos del tipo cEntrega47, porque el proyecto del ejecutable "sabe" de la existencia del proyecto de la librera. Cosa que podemos comprobar si declaramos, en la ventana de cdigo del formulario, una variable que haga referencia a la clase cEntrega47 (ver figura 8).
Figura 8, Intellisense nos muestra las clases que contiene la librera Entrega47AX Con ese cdigo mostrado en la figura 8, tenemos una variable que sabe lo que hacer con un objeto del tipo cEntrega47, es decir, que puede tener una referencia a un objeto del tipo cEntrega47. Para que "realmente" tenga una referencia a un objeto real de la memoria, tendremos que instanciarlo usando New, (como de costumbre), cosa que haremos en el evento Form_Load del formulario, ahora veremos el cdigo, ya que antes vamos a aadir unos controles al formulario para poder probar la clase definida en el componente.
605
Aade unas etiquetas para que nos muestre la versin y la fecha y hora actual, aunque para que muestre la fecha y hora, tendremos que pulsar en el botn. El aspecto del formulario en tiempo de diseo ser el mostrado en la figura 9:
Figura 9, El formulario en tiempo de diseo Ahora slo queda mostrar el cdigo del formulario y probar que todo funciona bien. Antes de probar que todo funciona bien, veamos el cdigo. '----------------------------------------------------------------------------' Formulario de prueba para la clase cEntrega47 (22/Ago/03) ' Prueba para el Curso Bsico de Visual Basic ' ' Guillermo 'guille' Som, 2003 '----------------------------------------------------------------------------Option Explicit Private prueba47 As Entrega47AX.cEntrega47 Private Sub cmdFechaHora_Click() Label4.Caption = prueba47.HoraActual End Sub Private Sub Form_Load() Set prueba47 = New cEntrega47 Label2.Caption = prueba47.Version End Sub
606
Fjate que la variable declarada a nivel de mdulo (en el formulario), se ha declarado como del tipo Entrega47AX.cEntrega47, pero tambin se poda haber declarado como As cEntrega47, esto no es una caracterstica de nuestro proyecto, sino de todos los componentes ActiveX, ya que no es necesario indicar el "componente" en el que est la clase, salvo que sepamos que puede haber "conflictos de nombres", es decir, dos clases que se llamen de igual forma, pero que estn en distintos componentes, (esto suele ocurrir cuando tenemos referencias a DAO y ADO, en ambos existe un objeto llamado Recordset). En el Form_Load se instancia (se crea el nuevo objeto) usando el nombre de la clase, sin especificar el del componente, esto no es buena prctica, en este caso te lo muestro para que sepas que los dos cEntrega47 son la misma clase, pero para seguir con las buenas prcticas de programacin y, sobre todo, para ser consistentes, deberamos usar la forma larga o corta, pero no mezclarlas. Para probar que todo funciona, tendramos que pulsar F5 o bien en el botn "play" de la barra de herramientas. Pero como resulta que el primer proyecto que hemos agregado al grupo de proyectos es la librera, se mostrar un cuadro de dilogo preguntndote que quieres ejecutar... es decir, que no se puede ejecutar una librera as por las buenas... por tanto deberamos indicarle al Visual Basic que el proyecto que queremos probar es el ejecutable. Para hacer esto, hay que decirle que el proyecto de inicio es tEntrega47, (se mostrar en negrita en el explorador de proyectos), para poder indicar cual ser el proyecto de inicio, tenemos que hacerlo usando el men contextual (pulsa con el botn derecho o secundario del ratn) en el proyecto tEntrega47 y del men que se muestra, selecciona Set As Start Up (ver la figura 10)
Figura 10, indicar cual ser el proyecto de inicio Una vez que le hemos indicado al VB que el proyecto de inicio es tEntrega47, podemos pulsar F5 y se mostrar el formulario (como en cualquier proyecto "normal"), vers que se muestra la versin de la clase (la que hemos dicho que muestre), y si pulsamos en el botn, se mostrar la fecha y hora actual. Y esto es todo por hoy. Espero que, aunque de forma orientativa, ya sepas cmo crear (o al menos probar en el
607
IDE de Visual Basic), un componente ActiveX y lo que es mejor, cmo poder usarlo desde otro proyecto. Slo me queda aclararte que como an no lo hemos compilado, slo podremos crear referencias si ese otro proyecto, que queremos que use el componente, est en el mismo grupo de proyectos que el del componente. En otra ocasin veremos cmo compilarlo y cmo configurar la utilizacin de ese componente, cosa que mucha gente no explica y que puede tener sus ms y menos en cuanto a rendimiento... o casi... Pero eso ser en otra ocasin, que ahora me tengo que ir con la parienta, que est algo pachuchilla con un resfriadillo de estos de verano, y cuando las parientas estn as, necesitan ms cariete de lo normal... Nos vemos Guillermo
608