Visual Basic 2012 (VB - NET) - Los Fundamentos Del Lenguaje PDF

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 297

ShareVideos

Visual Basic 2012 (VB.NET)Los


fundamentos del lenguaje
Este libro sobre VB.NET está dirigido a los desarrolladores, incluso principiantes, que
quieran dominar Visual Basic.NET. Después de describir el entorno de desarrollo (Visual
Studio 2012), el lector descubrirá los fundamentos de la programación orientada a
objetos con VB.NET y avanzará poco a poco hacia el desarrollo de aplicaciones Windows
Form. Las novedades del lenguaje respecto a la programación asíncrona le permitirán
mejorar el rendimiento y proactividad de las aplicaciones. Los numerosos ejemplos y
consejos de uso de las herramientas de depuración le serán de gran ayuda durante la
realización de una aplicación.

Se dedica un capítulo al acceso a las bases de datos con la ayuda de ADO.NET y SQL, lo
que le permitirá avanzar hacia el desarrollo de aplicaciones cliente-servidor. Se presentan y
detallan las potentes funcionalidades de LINQ para facilitar el acceso y manipulación de
los datos. También se presenta el uso del lenguaje XM, que facilita el intercambio de
información con otras aplicaciones.

Los usuarios de versiones anteriores descubrirán las novedades y mejoras de esta versión
2012 para desarrollar, incluso de manera más rápida y sencilla, aplicaciones para el
Framework .NET 4.5.

Se presenta la distribución de una aplicación con Windows Installer y la tecnología Click


Once.

Los capítulos del libro:


Prólogo – Presentación de la plataforma .NET – Presentación de Visual Studio –
Organización de una aplicación – Fundamentos del lenguaje – Programación orientada a
objetos – Gestión de errores y depuración del código – Las aplicaciones de Windows –
Acceso a las bases de datos – Presentación de LINQ – Utilización de XML –
Implementación de aplicaciones y componente
Thierry GROUSSARD
Después de más de 10 años como analista y desarrollador, Thierry GROUSSARD se
orientó a la formación, particularmente en el campo del desarrollo. Sus profundos
conocimientos de las necesidades de la empresa y sus cualidades pedagógicas hacen que
sus libros estén especialmente adaptados al aprendizaje y a la puesta en práctica del
desarrollo VB.NET con Visual Studio 2012.

Introducción

ShareVideos
Desde la primera versión aparecida en 2002, el lenguaje Visual Basic ha seguido una
evolución constante hasta esta versión 2012. Sigue siendo el lenguaje de referencia de
Microsoft a pesar de la competencia de su joven compañero C#. Esta popularidad se debe
seguramente a la excepcional longevidad de este lenguaje, cuyos orígenes se remontan al
lenguaje GW-BASIC disponible con MS-DOS. En efecto, son muchos los informáticos que
dieron sus primeros pasos en el mundo del desarrollo mediante este lenguaje.

El objetivo de este libro consiste en presentar las bases de este lenguaje para permitirle
explotar lo mejor posible las funcionalidades de la versión 4.5 del Framework .NET. Una
vez aprendidas estas bases, tendrá usted la sartén por el mango para tratar el diseño de las
aplicaciones gráficas.

En sus futuras aplicaciones necesitará seguramente manipular información presente en una


base de datos. Los dos capítulos dedicados a este tema le aportarán una ayuda muy valiosa
para llevar a cabo esta tarea. El primero le familiarizará con la utilización de ADO.NET,
que es la tecnología clásica de Microsoft para la gestión del acceso a una base de datos. El
segundo presenta el lenguaje LINQ, cuyo principal objetivo consiste en uniformizar los
accesos a los datos de una aplicación, y ello, sea cual sea el origen de estos datos (base de
datos, archivos XML, objetos...).

Aunque el despliegue es, por supuesto, la última etapa de la elaboración de una aplicación,
no debe descuidarse. En el último capítulo de este libro se tratan las dos tecnologías
disponibles de despliegue que le permiten simplificar la instalación de sus aplicaciones en
los puestos de los usuarios.

Este libro, sin embargo, no tiene como vocación sustituir la documentación del
Framework .NET, que debe seguir siendo su referencia para obtener datos como la lista de
métodos o propiedades presentes en una clase.

Introducción
La plataforma .NET facilita un conjunto de tecnologías y herramientas que simplifican el
desarrollo de aplicaciones y propone una solución para casi cualquier tipo de aplicaciones:

 Aplicaciones Windows clásicas.


 Aplicaciones Web.
 Servicios Windows.
 Servicios Web.

Todos estos tipos de aplicaciones se pueden realizar gracias a un elemento esencial: el


Framework .NET. Este Framework se encarga, mediante numerosas capas de software
superpuestas, de la integridad de la vida de una aplicación, desde el desarrollo hasta la
ejecución. El Framework debe estar instalado en el sistema operativo con el cual va a
interactuar. El primer sistema que permite acogerlo es, por supuesto, Windows, pero hay
otras versiones disponibles que permiten la adaptación de la plataforma .NET a sistemas
tales como Linux o Unix.

ShareVideos
El Framework contiene dos elementos principales: el Common Language Runtime y la
librería de clases del .NET Framework.

El Common Language Runtime es la base del .NET Framework. Se puede considerar el


runtime como un motor de ejecución que gestiona la ejecución de código y que también
asegura la gestión de la memoria. El código del que se encarga el Common Language
Runtime se llama código gestionado.

La biblioteca de clases es un conjunto de clases que se puede utilizar para el desarrollo de


cualquier aplicación. Lo trataremos a lo largo de este libro.

1. Principio de funcionamiento del Common Language Runtime

En las aplicaciones Windows tradicionales, el sistema se encarga directamente de la


ejecución del código. En efecto, este se genera a través del compilador asociado al lenguaje
de programación utilizado para el diseño de la aplicación. El resultado de esta compilación
corresponde a un archivo binario que contiene el código especificado para el
microprocesador y el sistema operativo con los cuales debe funcionar la aplicación. No es
posible ninguna compatibilidad con otro tipo de microprocesador o sistema operativo. Para
convencerse de ello, basta con intentar la ejecución de una aplicación prevista para
Windows en un sistema Linux. Si se intenta la ejecución en una estación de trabajo SUN,
que utiliza un tipo de microprocesador radicalmente diferente, el resultado es idéntico. La
solución para paliar estos problemas consiste en generar para la aplicación no un código
específico, sino uno genérico, independiente de cualquier plataforma de software o de
hardware. Se confía este código, en el momento de la ejecución, a una máquina virtual que
se asegura de su ejecución. Este código se llama Microsoft Intermediate Language (MSIL).
Durante la ejecución de la aplicación, la máquina virtual se encarga de este código y
asegura su traducción en instrucciones específicas para el microprocesador de la máquina.
Esta traducción no se efectúa en bloque desde el principio de la aplicación, sino únicamente
en función de las necesidades. En efecto, ¿para qué perder tiempo traduciendo del código
MSIL, si no se va a utilizar después? Es por esta razón por lo que el compilador utilizado
para esta traducción se llama compilador Just In Time (JIT).

Las ventajas de esta solución son obvias, ya que para ejecutar una misma aplicación en
varias plataformas de hardware o de software basta con obtener la máquina virtual capaz de
efectuar la traducción. Esta máquina virtual está disponible para todos los sistemas
Microsoft. El proyecto Mono propone una versión de la máquina virtual para las
plataformas siguientes:

 Linux,
 Mac OS X,
 Sun Solaris,
 BSD - OpenBSD, FreeBSD, NetBSD.

ShareVideos
Están disponibles para descargar en el sitio http://www.mono-project.com.

El esquema siguiente retoma el conjunto de estas operaciones:

2. Los servicios del Common Language Runtime

La máquina virtual no se conforma con efectuar la traducción del código. El código MSIL
también se llama código gestionado, lo que supone que un cierto número de operaciones
adicionales se realizarán sobre el código en el momento de la ejecución. La figura siguiente
recoge el conjunto de las funcionalidades disponibles en el Common Language Runtime.

Class Loader

Gestiona la carga en memoria de las instancias de clases.

IL to Native Compilers

Convierte el código intermedio (MSIL) en código nativo.

Code Manager

Gestiona la ejecución del código.

Garbage Collector

Asegura la gestión de la memoria vigilando las instancias de clases que ya no son


accesibles.

Security Engine

Permite la verificación de la identidad de la persona que ejecuta el código y acepta o no


esta ejecución, en función de los permisos otorgados.

Debug Engine

Permite la depuración de la aplicación al encargarse, por ejemplo, de la ejecución paso a


paso del código.

Type Checker

Vigila el uso de variables no inicializadas y las conversiones entre variables de tipo


diferente.

ShareVideos
Exception Manager

Facilita la gestión estructurada de las excepciones en relación con Windows Structured


Exception Handling (SEH). Esta técnica permite una gestión individual de cada excepción
más que una gestión global.

Thread Support

Propone un conjunto de clases que permite la realización de aplicaciones multihilo.

COM Marshaler

Permite traducir llamadas hacia componentes COM, asegurando por ejemplo la conversión
de los tipos de datos.

Base Class Library Support

Facilita el acceso a los servicios disponibles en el sistema operativo anfitrión.

3. La Base Class Library

El Framework .NET pone a disposición de los desarrolladores un conjunto de herramientas


que les permite obtener una solución rápida para la mayoría de los problemas encontrados
durante la realización de una aplicación.

Estas herramientas están disponibles en forma de clases. Al contrario de las bibliotecas de


código de los lenguajes de la generación anterior, que solo eran una lista sin fin de
procedimientos o funciones, la biblioteca de clases se organiza con la forma de una
estructura jerarquizada. El elemento esencial de esta jerarquía es el espacio de nombres
(Namespace). Permite la agrupación lógica de clases que tienen puntos en común. Por
ejemplo, encontramos en el espacio de nombres System.Data todas las clases utilizables
para acceder a una base de datos.

Naturalmente esta biblioteca de clases es independiente de cualquier lenguaje de


programación. Permite, pues, la mezcla de diferentes lenguajes a lo largo del desarrollo de
una aplicación. También está perfectamente integrada a Visual Studio, lo que nos procura
una comodidad de uso apreciable con herramientas tales como IntelliSense. Como esta
biblioteca está orientada a objeto, es fácilmente extensible a través de relaciones de
herencia.

La biblioteca contiene una cantidad increíble de espacios de nombres y clases; tantos, que
es muy posible que no tenga que utilizar nunca algunos de ellos a lo largo de sus
desarrollos con Visual Basic.

Los espacios de nombres más utilizados son los siguientes:

ShareVideos
System

Es el espacio de nombres raíz para los tipos de datos en el Framework .NET. Contiene en
particular la definición de la clase Object, que es el ancestro de todos los tipos de datos del
Framework .NET.

System.Windows

Contiene el conjunto de los elementos que permiten la creación de interfaces para usuarios
de Windows.

System.Web

Contiene todos los recursos necesarios para la creación de aplicaciones Web, con las clases
de la tecnología ASP.NET, por ejemplo, o las clases utilizables para la creación de
servicios Web XML.

System.Data

Contiene un conjunto de clases especializadas en el acceso a las bases de datos con el


soporte de ADO.NET.

System.Xml

El lenguaje XML está ahora por todas partes y este espacio de nombres contiene las clases
que permiten la manipulación de documentos XML.

4. Las versiones y evoluciones de la plataforma .NET

La primera versión (1.0) de la plataforma .NET salió en enero de 2002 con Visual Studio
2002. Esta versión se sustituyó rápidamente por la versión 1.1, que corrigió algunos
problemas de juventud de la versión anterior y añadió tecnologías que solo estaban
disponibles antes como instalaciones independientes. Las principales aportaciones de esta
versión fueron:

 Los controles móviles ASP.NET (antiguamente Microsoft Mobile Internet Toolkit),


que extienden el Framework .NET con compatibilidad con los periféricos móviles
(sin cable) tales como los teléfonos móviles y las agendas digitales personales.
 El proveedor de datos .NET Framework para ODBC y el proveedor de datos para
Oracle, que previamente solo estaban disponibles a través de descarga, fueron
suministrados con el .NET Framework.
 La compatibilidad de la nueva actualización del protocolo de Internet, comúnmente
llamado IP versión 6 o más sencillamente IPv6. Este protocolo está diseñado para
aumentar de manera sensible el espacio de direccionamiento utilizado para
identificar los puntos de entrada de comunicación en Internet.

ShareVideos
Está disponible con la versión 2003 de Visual Studio en abril 2003.

Hubo que esperar a noviembre de 2005 para ver llegar la versión 2.0 asociada a la salida de
Visual Studio 2005. Esta versión aporta muchas mejoras:

 La compatibilidad con la nueva generación de ordenadores de 64 bits que permiten


la creación de aplicaciones más competentes.
 Una evolución mayor en el acceso a las bases de datos con ADO.NET 2.0, que
mejora el uso de XML.
 El desarrollo de aplicaciones Web es también cada vez más fácil con la nueva
versión de ASP.NET que propone una multitud de nuevos controles.
 La utilización de la clase Consola se ha optimizado con la inserción de nuevas
propiedades y métodos (gestión de los colores, borrado, posición del cursor...).
 El .NET Framework 2.0 vuelve a introducir la funcionalidad Modificar &
Continuar, que permite al usuario que depura una aplicación en Visual Studio
modificar el código fuente en modo de apagado. Una vez aplicadas las
modificaciones del código fuente, el usuario puede retomar la ejecución del código
y observar el efecto.
 La aparición de la noción de genérico, que permite declarar y definir clases,
estructuras, interfaces, métodos y delegados con parámetros de tipo no especificado
o genérico en lugar de tipos específicos. Los tipos reales se especifican
posteriormente en tiempo de ejecución.

La versión 3.0 llega en noviembre 2006 y aporta nuevas tecnologías a la vez que sigue
siendo principalmente una versión 2.0. Estas tecnologías están disponibles en descargas que
vienen a integrarse al Framework 2.0. A continuación le mostramos un vistazo de estas
novedades:

 Windows Presentation Foundation (WPF) representa el nuevo sistema de interfaces


gráficas. Se basa en un motor de aspecto vectorial y permite una separación más
clara entre la definición de la interfaz gráfica de una aplicación y su código. Para
esto utiliza el lenguaje XAML (eXtensible Application Markup Language). Las
tareas se pueden repartir más fácilmente entre diseñadores y desarrolladores.
 Windows Communication Foundation (WCF) constituye la nueva base de
desarrollo de aplicaciones distribuidas. Facilita la comunicación entre aplicaciones
añadiendo una capa de abstracción que uniformiza las técnicas de comunicación
entre estas aplicaciones (Servicios Web, .NET Remoting, Microsoft Transaction
Server y Microsoft Message Queuing).
 Windows Workflow Foundation (WF) está compuesto por una plantilla de
programación de un motor de ejecución y herramientas para integrar flujos de
trabajo en una aplicación. Se puede definir un flujo de trabajo como un conjunto de
acciones o etapas que se ejecutan en un orden predefinido. Estas acciones se pueden
encadenar en función de condiciones, interacciones con procesos informáticos o en
función de interacciones humanas.
 Windows Cardspace facilita una nueva técnica a los usuarios para identificarse en
una aplicación. Tiene la misma vocación que Microsoft Passport, pero no es
específica de las aplicaciones Microsoft (Hotmail, MSDN…).

ShareVideos
La versión 3.5, de noviembre de 2007, aporta principalmente mejoras y evoluciones a las
tecnologías aparecidas con la versión 3.0. La única verdadera novedad de esta versión
corresponde a la aparición del lenguaje LINQ (Language Integrated Query). El objetivo de
este lenguaje consiste en uniformizar el método utilizado para extraer información de una
fuente de datos. Este nuevo lenguaje permite consultar colecciones de objetos, bases de
datos SQL Server, DataSet ADO.NET y documentos XML con la misma sintaxis.

La versión 4.0, publicada en mayo de 2010, mejora aun más las capacidades y
funcionalidades disponibles. Entre sus novedades podemos señalar:

 La mejora del recolector de basura, que se ejecuta ahora en segundo plano. Durante
una operación de recolección, los hilos de la aplicación ya no quedan suspendidos
para facilitar la limpieza de la memoria, lo que aumenta naturalmente el
rendimiento de la aplicación.

 La inclusión de un núcleo de ejecución para poder trabajar con lenguajes dinámicos


tales como IronPython y IronRuby. Con este tipo de lenguajes, los tipos de variables
ya no serán determinados en el momento del diseño de la aplicación, sino durante la
ejecución de esta.

 La inclusión de conceptos de covarianza y de contravarianza facilita la utilización


de elementos genéricos (clases o métodos).

 La disponibilidad de nuevos tipos de datos como BigInteger y Complex permiten


respectivamente el uso de valores numéricos enteros sin límite superior o inferior y
la manipulación de números complejos.

 La mejora de las clases de manipulación del sistema de archivos y la creación de


archivos mapeados en memoria.

 Poder tener en cuenta más fácilmente los procesadores multinúcleo que permiten el
reparto de los procesos con la programación paralela.

La versión 4.5, que salió al mercado en junio del año 2012, es un complemento de la
versión 4.0. La principal novedad es la simplificación de la programación en modo
asíncrono. Esta técnica evita el bloqueo de un thread mientras se ejecuta un método.

Escritura, compilación y ejecución de una


aplicación
En este capítulo, vamos a detallar el ciclo de vida de una aplicación desde la redacción del
código hasta la ejecución de la aplicación, estudiando en detalle los mecanismos puestos en
marcha.

ShareVideos
1. Escritura del código

La inmensa mayoría de las aplicaciones se desarrollan gracias a un entorno integrado que


agrupa las principales herramientas necesarias, a saber:

 Un editor de texto.
 Un compilador.
 Un depurador.

Este enfoque es, de lejos, el más cómodo. Sin embargo se necesita una pequeña fase de
aprendizaje para familiarizarse con la herramienta. Para nuestra primera aplicación, vamos
a utilizar un proceso un poco diferente ya, que vamos a emplear herramientas individuales:
el bloc de notas de Windows para la escritura del código y el compilador en línea de
comandos para Visual Basic.

Nuestra primera aplicación será muy sencilla, ya que simplemente mostrará el mensaje
«Buenos días» en una ventana de comandos.

Este es el código para nuestra primera aplicación, que enseguida explicaremos. Se debe
introducir con la ayuda del bloc de notas de Windows o cualquier otro editor de texto
siempre que este no añada ningún código de compaginación en el interior del documento,
como hacen por ejemplo los programas de tratamiento de texto.

Ejemplo

Imports System
public Module test
dim mensaje as string="Buenos días"
public sub main ()
console.writeline(mensaje)
end sub
end module

Hay que guardar este código dentro de un archivo con la extensión .vb. Dicha extensión no
es obligatoria, pero usándola se respetan las convenciones de Visual Studio.

Detallamos ahora algunas líneas de nuestra primera aplicación:

Imports System

Esta línea permite acceder a los elementos presentes en el espacio de nombres System. Sin
ella, habría que utilizar los nombres plenamente cualificados para todos los elementos
contenidos en el espacio de nombres. En nuestro caso, deberíamos utilizar entonces:
System.Console.writeline("Buenos días")

public Module test ... end module

ShareVideos
En Visual Basic, toda parte de código debe estar contenida dentro de un módulo o clase.

dim mensaje as string="Buenos días"

Esta línea declara una variable. Se deben declarar todas las variables antes de poder
utilizarlas. La declaración permite especificar el tipo de información que la variable va a
contener: aquí, una cadena de caracteres y eventualmente un valor inicial, "Buenos días" en
nuestro caso.

public sub Main() ... end sub

Todas las instrucciones que no sean declaraciones deben estar ubicadas en un


procedimiento o una función. La mayor parte del código se coloca por lo tanto entre las
instrucciones Sub y End Sub o Function y End Function. Entre todos los procedimientos y
funciones se designa a uno de ellos como el punto de entrada en la aplicación. A través la
ejecución de este procedimiento arranca la aplicación. Este procedimiento debe ser público
y se tiene que llamar Main.

Console.writeline("Buenos días")

La clase Console definida dentro del espacio de nombres System proporciona un conjunto
de métodos, que permiten mostrar información en la consola o leer información de ella. El
procedimiento writeline permite mostrar una cadena de caracteres en la consola.

Hay que destacar también que Visual Basic no hace distinción entre minúsculas y
mayúsculas dentro de las instrucciones. Si se utiliza el editor de Visual Studio para escribir
el código, este insertará automáticamente las modificaciones para hacer uniforme la
«ortografía» del código.

2. Compilación del código

El Framework .NET incluye un compilador en línea de comandos para Visual Basic. Para
compilar el código fuente de nuestro ejemplo, debemos abrir una ventana de comando DOS
con objeto de poder lanzar el compilador. Para ello se ha creado un método abreviado en el
menú Inicio durante la instalación. Este método abreviado lanza la ejecución de un archivo
.bat que coloca algunas variables de entorno necesarias para el correcto funcionamiento de
las herramientas Visual Studio en línea de comandos.

Ya con la ventana de comando abierta, conviene colocarse en el directorio en el que se


encuentra el archivo fuente.

La compilación se lanza con el comando vbc Buenos días.vb.

ShareVideos
Tras un breve instante, el compilador nos devuelve el mando. Podemos comprobar la
presencia del archivo ejecutable y su correcto funcionamiento.

Nuestra primera aplicación es realmente muy sencilla. Para aplicaciones más complejas,
será útil especificar algunas opciones para el funcionamiento del compilador. El conjunto
de las opciones disponibles se puede obtener lanzando el comando vbc / ?.

Las principales opciones son:

/out:archivo.exe

Esta opción permite especificar el nombre del archivo resultado de la compilación. Por
defecto, se utiliza el nombre del archivo fuente actual que está siendo compilado.

/target :exe

Esta opción pide al compilador la generación de un archivo ejecutable para una aplicación
en modo consola.

/target :winexe

Esta opción pide al compilador la generación de un archivo ejecutable de aplicación


Windows.

/target :library

Esta opción pide al compilador la generación de un archivo librería dll.

/referencia :lista de archivos

Esta opción indica al compilador la lista de los archivos referenciados en el código y


necesarios para la compilación. Los nombres de los archivos se deben separar con una
coma.

3. Análisis de un archivo compilado

Ahora que se ha creado nuestro archivo ejecutable, intentemos ver lo que contiene.

Primera solución: abrirlo con el bloc de notas de Windows

El resultado no es muy elocuente.

ShareVideos
Hemos dicho que el compilador genera código MSIL. Por lo tanto, este es el código que
visualizamos en el bloc de notas. Para visualizar el contenido de un archivo MSIL, el
Framework .NET propone una herramienta más adecuada.

Segunda solución: utilizar un desensamblador

Esta herramienta se lanza a partir de la línea de comandos con la instrucción ildasm.

Permite visualizar un archivo generado por el compilador de forma más clara que con el
bloc de notas. Conviene indicar el archivo que se desea examinar a través del menú
Archivo - Abrir. El desensamblador visualiza entonces su contenido.

La información presente en el archivo se puede separar en dos categorías: el manifiesto y el


código MSIL. El manifiesto contiene los metadatos que permiten describir el contenido del
archivo y los recursos que necesita. Hablamos en este caso de un archivo autodescriptivo.
Esta técnica es muy interesante, ya que, en cuanto el Common Language Runtime lee el
archivo, dispone de toda la información necesaria para su ejecución.

Ya no es necesario utilizar una entrada en el registro de la máquina. Se puede visualizar el


manifiesto haciendo doble clic en su nombre.

En el manifiesto encontraremos datos que indican que, para que la aplicación pueda
funcionar, necesita los ensamblados externos mscorlib, Microsoft.VisualBasic y System.

La segunda parte corresponde realmente al código MSIL. Se utiliza un conjunto de iconos


para facilitar la visualización de los datos.

Símbolo Significado
Más información
Espacio de nombres
Clase
Interfaz
Clase de valores
Enumeración
Método
Método estático
Campo
Campo estático

ShareVideos
Evento Como para el manifiesto, un doble clic en un elemento
permite obtener más detalles. De esta manera
Propiedad
podemos, por ejemplo, visualizar la traducción de
Elemento de manifiesto o nuestro procedimiento main.
de información de clase

En un ejemplo de código tan sencillo, es fácil encontrar la correspondencia entre el código


Visual Basic y su traducción en código MSIL. Para las personas entusiasmadas con el
código MSIL, existe un ensamblador MSIL: ilasm. Esta herramienta espera un archivo de
texto que contiene código MSIL y lo transforma en formato binario.

Ya que somos capaces de visualizar el código MSIL, podemos verificar si es realmente


independiente del lenguaje fuente utilizado para desarrollar la aplicación. Este es el código
C# que realiza la misma tarea que nuestro código Visual Basic.

using System;
class Program
{
static String mensaje = "Buenos días";
static void Main(string[] args)
{
Console.WriteLine(mensaje);
}
}

Tras la compilación y desensamblaje por ildasm, veamos lo que nos presenta para el
método Main.

No hay diferencias en relación con la versión Visual Basic del método Main.

También es posible dar los pasos inversos y transformar un archivo de texto que contiene
código MSIL en el archivo binario correspondiente. Esta transformación se hace gracias al
ensamblador ilasm. La única dificultad consiste en crear el archivo de texto que contiene el
código MSIL, ya que, aunque la sintaxis es comprensible, no resulta muy intuitiva. Una
solución puede consistir en pedir a la herramienta ildasm (el desensamblador) que genere
este archivo de texto. Para ello, después de haber abierto el archivo ejecutable o la librería
dll con ildasm, usted debe utilizar la opción Dump del menú Archivo. Se le invita entonces
a elegir el nombre del archivo que desea generar (extension .il).

Este archivo se puede modificar a continuación con un simple editor de texto. Sustituya por
ejemplo el contenido de la variable mensaje con la cadena "Hello".

.method private hidebysig specialname rtspecialname static


void .cctor() cil managed
{
// Code size 11 (0xb)

ShareVideos
.maxstack 8
IL_0000: ldstr "Hello"
IL_0005: stsfld string Program::message
IL_000a: ret
} // end of method Program::.cctor

Guarde el archivo. Ahora solo queda volver a generar el archivo ejecutable gracias al
ensamblador ilasm. Para ello, introduzca la línea de comandos siguiente:

ilasm Buenos días.il /output=Hello.exe

La opción /output=Hello permite indicar el nombre del archivo generado. Si no se


especifica esta opción, se utilizará el nombre del archivo fuente. Ahora puede lanzar el
nuevo ejecutable y verificar el mensaje visualizado. Todas estas manipulaciones se pueden
hacer en cualquier archivo ejecutable o librería dll. La única dificultad reside en el volumen
de información facilitado por la descompilación. Sin embargo, esto conlleva un problema:
cualquier persona que disponga de los archivos ejecutables o bibliotecas dll de una
aplicación puede modificar la aplicación. Por supuesto las modificaciones pueden resultar
peligrosas, sobre todo si la modificación afecta un valor que representa una información
importante de la aplicación (contraseña, clave de licencia...). Un remedio contra este tipo de
manipulación consiste en hacer lo más incomprensible posible el código generado por el
descompilador. Para ello, hay que actuar a nivel del archivo ejecutable o de la biblioteca dll
modificando los datos que contiene sin, por supuesto, perturbar el funcionamiento. Hay
herramientas llamadas ofuscadores son capaces de realizar esta operación. Visual Studio se
facilita con una herramienta de la empresa PreEmptive Solutions llamada DotFuscator
Community Edition. Esta versión permite realizar las operaciones básicas para «embrollar»
un archivo. El principal tratamiento efectuado en el archivo consiste en renombrar
los identificadores contenidos en él (nombre de las variables, nombre de los procedimientos
y funciones...) con valores muy poco explícitos, en general de caracter único. He aquí un
extracto de la descompilación del archivo Buenos días.exe tras su tratamiento con
Dofuscator Community Edition.

.class private auto ansi beforefieldinit a


extends [mscorlib]System.Object
{
.field private static string a
.method private hidebysig static void a (string[] A_0) cil managed
{
.entrypoint
// Code size 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: ldsfld string a::a
IL_0006: call void
[mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method a::a

.method public hidebysig specialname rtspecialname


instance void .ctor() cil managed

ShareVideos
{
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method a:: .ctor

.method private hidebysig specialname rtspecialname static


void .cctor() cil managed
{
// Code size 11 (0xb)
.maxstack 8
IL_0000: ldstr "Buenos días"
IL_0005: stsfld string a::a
IL_000a: ret
} // end of method a:: .cctor

} // end of class a

En este archivo es imposible encontrar los nombres utilizados en el código. El


procedimiento main se llama ahora ’a’, la variable mensaje también se llama ’a’.
¡Imagínese el resultado de este tratamiento en un archivo que contiene varias decenas de
variables y procedimientos!

La versión Professional Edition permite además la encriptación de las cadenas de


caracteres, la modificación y el añadido de código inútil para complicar las estructuras de
control (bucles, condiciones…).

A continuación presentamos un ejemplo de transformación de la documentación de


DotFuscator.

El código original:

public int CompareTo(Object o)


{
int n = occurrences - ((WordOccurrence)o).occurrences;
if (n == 0)
{
n = String.Compare(word, ((WordOccurrence)o).word);
}
return(n);
}

El código generado:

public virtual int _a(Object A_0) {


int local0;
int local1;
local0 = this.a - (c) A_0.a;
if (local0 != 0) goto i0;
goto i1;
while (true) {

ShareVideos
return local1;
i0: local1 = local0;
}
i1: local0 = System.String.Compare(this.b, (c) A_0.b);
goto i0;
}

¡El análisis de miles de líneas de código de este tipo puede provocar dolor de cabeza! Por lo
tanto, es preferible conservar el código original para las modificaciones posteriores. Hay
más información disponible en el sitio web: http://www.preemptive.com/

4. Ejecución del código

Cuando un usuario ejecuta una aplicación gestionada, el cargador de código del sistema de
explotación carga el Common Language Runtime, que luego lanza la ejecución del código
gestionado. Como el procesador de la máquina en la que se ejecuta la aplicación no puede
encargarse directamente del código MSIL, el Common Language Runtime debe convertirlo
en código nativo.

Esta conversión no se aplica a la totalidad del código cargado en la aplicación. Convierte el


código según las necesidades. Los pasos adoptados son los siguientes:

 Al cargar una clase, el Common Language Runtime sustituye cada método de la


clase con un trozo de código que requiere al compilador JIT que lo compile en
lenguaje nativo.
 Luego, cuando se utiliza el método en el código, la porción de código generado en
la carga entra en acción y compila el método en código nativo.
 El trozo de código que requiere la compilación del método luego es sustituido por el
código nativo generado.
 Las futuras llamadas de este método se harán directamente en el código nativo
generado.

Evolución de Visual Basic 1 a Visual Basic


.NET 2012
Desde la versión 1.0 de 1991 hasta la versión 6.0 de 1998, Visual Basic ha sufrido muchas
evoluciones. En el origen se diseñó Visual Basic como un lenguaje sencillo que permitía
desarrollar rápidamente una aplicación para Windows (como lo permitía GWBASIC para
MS-DOS). Esta sencillez de uso se basa en gran medida en el entorno de desarrollo que
oculta las tareas fastidiosas de la creación de una aplicación para Windows.

De la versión 1.0 a la versión 3.0, no asistimos a ninguna revolución en VB, sino a las
evoluciones clásicas de un lenguaje de programación.

ShareVideos
Con la aparición de la versión 4.0 en 1996, VB pasó a medirse con los mejores, gracias a
una multitud de evoluciones:

 Posibilidad de crear aplicaciones de 32 bits (todavía estaba asegurada la


compatibilidad con aplicaciones de 16 bits).
 Creación de DLL a partir de VB.
 Utilización de DLL (escritas en VB o cualquier otro lenguaje).
 Aparición de funcionalidades objeto en VB (uso de clases).

A pesar de, o más bien a causa de, todas estas evoluciones, la versión 4.0 de VB no era muy
estable.

Rápidamente, en 1997, Microsoft sacó la versión 5.0 de Visual Basic, que no aportó,
grandes evoluciones salvo la desaparición de las aplicaciones de 16 bits.

Las evoluciones de la versión 6.0, que salió un año más tarde, se basan principalmente en el
método de acceso a las bases de datos, con la sustitución de DAO (Data Access Object) de
las versiones anteriores por ADO (Active Data Object), que se convierte de hecho en el
método común a los lenguajes Microsoft para el acceso a los datos.

Sin embargo, esta versión deberá esperar el service pack 4 para permitir el funcionamiento
correcto de ciertos controles de acceso a los datos (el Data Environment).

Aunque esté generada en código nativo por la compilación, una aplicación VB siempre
requiere el módulo runtime para poder ejecutarse en una máquina (vbrun.dll), ya que, a
diferencia de los lenguajes como C++, VB no utiliza la interfaz de programación WIN 32
para llamar las funciones del sistema operativo.

La versión siguiente, que se estrena en 2002, aporta cambios radicales en Visual Basic. Esta
versión se integra en la suite Visual Studio .NET, que se basa en una nueva infraestructura
para la creación y la ejecución de aplicaciones bajo Windows: el Framework .NET. Los
principios de funcionamiento de esta infraestructura se describen en el capítulo
Presentación de la plataforma .NET.

Las versiones 2003 y 2005 siguen la evolución del Framework.NET (versión 1.1 luego 2.0)
aportando cada vez más funcionalidades y herramientas que facilitan y aceleran el
desarrollo de aplicaciones.

La versión 2008 también aporta su conjunto de novedades, entre las cuales las más notables
son las siguientes:

 Posibilidad de generar una aplicación para una versión específica del Framework
(2.0, 3.0, 3.5).
 Compatibilidad o mejora de la compatibilidad de nuevas tecnologías para el
desarrollo Web (AJAX, JavaScript, CSS...).

ShareVideos
 Integración de LINQ en los lenguajes Visual basic y Visual C#, que permite
uniformizar el acceso a los datos de manera independiente de la fuente (objetos,
bases de datos, archivo XML).
 Inclusión de una herramienta de mapeo objeto/relacional (O/R Designer).
 Creación de aplicaciones WPF optimizadas para Windows Vista.
 Posibilidad de crear informes con Report Designer (en sustitución de Crystal
Report).

La versión 2010, estrenada en mayo de 2010, prosigue la evolución de este lenguaje. El


objetivo sigue siendo proponer al usuario muchas funcionalidades que le permitan
aumentar su productividad al escribir menos líneas de código. Entre estas funcionalidades
podemos señalar:

 Posibilidad de escribir una instrucción sobre varias líneas sin tener que utilizar el
carácter de continuación (_) exigido en las versiones anteriores.

 Implantación automática de las propiedades de las clases.

 Inicialización de colecciones durante la creación.

 Generación automática de clases, métodos, estructuras, propiedades a partir de su


utilización.

 Compatibilidad con la varianza durante la utilización de los elementos genéricos


(clases, interfaces, delegados...).

La versión 2012 de Visual Basic integra las novedades del Framework 4.5, como por
ejemplo la simplificación de la programación asíncrona, añadiendo dos nuevas palabras
claves: async y await. El entorno de desarrollo también ofrece nuevas funcionalidades.
Entre ellas, la jerarquía de llamadas será una preciada ayuda para mejorar el seguimiento
del flujo de ejecución de una aplicación y anticipar las consecuencias de una modificación
del código.

Instalación y primer arranque


1. Configuración necesaria

Para permitir un correcto funcionamiento, Visual Studio necesita una configuración


mínima. Microsoft aconseja los siguientes valores:

Componente Mínimo recomendado Prestaciones óptimas


Procesador Pentium 1,6 GHz o equivalente Pentium 2,2 GHz o más
RAM 1024 MB 2048 MB o más
Espacio de disco 3 GB en el disco de sistema y de

ShareVideos
2,8 a 3,8 GB en otro disco 2.
Vídeo 1024 x 768 1280 x 1024 o más Proce
Lector CD-Rom o dimie
Indispensable Indispensable
DVD nto de
Cualquier versión instal
Windows 7
Sistema operativo
posterior (Windows) ación
Windows Server
Windows Server 2012 Los
elementos necesarios son:

 Los CD-Rom o DVD de Visual Studio.NET.


 Espacio disponible en su disco duro (de 3,8 a 5 GB en función de las herramientas
instaladas).
 Y sobre todo paciencia, ya que la instalación es larga...

Después de insertar el primer DVD y algunos segundos de carga, se visualiza la pantalla


siguiente:

Esta pantalla le propone seleccionar el directorio de instalación del producto y le indica el


espacio en disco necesario para esta instalación. Para proseguir con la instalación, también
debe aceptar el contrato de licencia. La siguiente etapa le permite seleccionar las
funcionalidades adicionales que desea instalar e iniciar la instalación del producto.

La siguiente pantalla le informa del progreso de la instalación:

Es necesario ser paciente porque el proceso de instalación puede ser bastante largo, en
función de las opciones que se hayan seleccionado.

Al finalizar el proceso, la siguiente pantalla le informa del éxito de la instalación y le ofrece


la posibilidad de ejecutar directamente el producto.

3. Primer lanzamiento

Un acceso directo creado automáticamente por el programa de instalación le permite lanzar


Visual Studio.

ShareVideos
Durante el primer lanzamiento, Visual Studio le propone personalizar el entorno de trabajo.
En función de su preferencia por un idioma determinado, Visual Studio configura el
entorno con las herramientas adaptadas. Se puede modificar esta configuración más
adelante con el menú Herramientas - Importar y exportar configuraciones.

Ahora debemos examinar las herramientas a nuestra disposición.

Descubrimiento del entorno


1. Página de inicio

Esta página se visualiza cada vez que inicia Visual Studio. Le permite acceder rápidamente
a los últimos proyectos en los que ha trabajado, crear un nuevo proyecto o abrir un proyecto
existente. La pestaña Últimas noticias permite activar un flujo RSS que facilita
información de las actualizaciones disponibles.

Al crear un nuevo proyecto o abrir un proyecto existente, se lanza el entorno Visual Studio.

2. Entorno Visual Studio

El entorno se compone de tres tipos de elementos:

 Una zona de barra de menús y de barras de herramientas.


 Una zona central de trabajo.
 Una multitud de ventanas que constituyen las diferentes herramientas a nuestra
disposición.

El conjunto presenta un aspecto cargado y, después de añadir una o dos barras de


herramientas y la aparición de algunas ventanas adicionales, la zona de trabajo se hace muy
angosta, sobre todo en una pantalla de tamaño reducido.

Afortunadamente, hay varias soluciones disponibles para gestionar nuestro espacio de


trabajo:

 El anclaje de las ventanas.


 La ocultación automática de ventanas.

ShareVideos
 La utilización de pestañas.

El anclaje de ventanas no sirve para ganar espacio en la pantalla, pero nos permite colgar en
un borde de la pantalla o de una ventana tal o cual ventana. También es posible convertir
cada ventana en flotante, al hacer doble clic en su barra de título o al utilizar el menú
contextual. Luego se puede desplazar o anclar esta ventana en otro borde. Para guiarnos en
el anclaje de una ventana, Visual Studio muestra, durante su desplazamiento, unas guías
que permiten elegir el borde donde efectuar el anclaje.

Los iconos situados en la periferia de la pantalla permiten el anclaje en el borde correspondiente


de la pantalla. Los iconos que aparecen en el centro de la ventana cuando el ratón pasa por
encima de ella controlan el anclaje en sus bordes o como pestaña adicional para la ventana.

Más interesantes para ganar espacio en la pantalla, las ventanas ocultables solo son visibles si el
cursor del ratón se encuentra encima de su superficie. Si no, solo una zona de pestañas, ubicada
en el borde del entorno de desarrollo, permite que se muestre su contenido. Para conservar
siempre visible una ventana, basta con bloquearla utilizando la chincheta presente en su barra de
título .

Finalmente, la utilización de pestañas permite compartir una misma zona de pantalla entre
diferentes ventanas y, en este sentido, los diseñadores de Visual Studio las han utilizado sin
moderación.

Las herramientas disponibles


Miremos más en detalle las diferentes barras de herramientas y ventanas que están a nuestra
disposición.

1. Las barras de herramientas

En Visual Studio hay como mínimo treinta barras de herramientas disponibles. La


visualización de cada una de ellas se puede controlar con el menú contextual, haciendo
doble clic en la barra principal de menús.

Por supuesto, es inútil visualizar el conjunto de las barras de herramienta de manera


simultánea; solo deben mostrarse las más útiles.

Estándar

ShareVideos
Editor de texto

Editor de cuadros de diálogo

Ubicación

Depurar

Las otras barras disponibles se muestran sobre la marcha y en función de sus necesidades
con el fin de evitar sobrecargar su pantalla.

Las ventanas disponibles son también bastante numerosas y vamos a descubrir las más
corrientes.

2. El cuadro de herramientas

A partir del cuadro de herramientas, vamos a elegir los elementos utilizados para el diseño
de la interfaz de la aplicación.

El cuadro de herramientas se organiza por secciones, que permiten encontrar los controles
fácilmente.

Cada uno podrá personalizar su cuadro de herramientas al añadirle, por ejemplo, controles
no disponibles por defecto. Puede ser conveniente, antes de añadir controles al cuadro de
herramientas, crear una nueva sección para albergarlos. Para ello, visualice el menú
contextual del cuadro de herramientas (haciendo clic con el botón secundario del ratón en el
cuadro de herramientas), elija la opción Agregar Ficha, luego dé un nombre a la nueva
sección así creada. Después de haber seleccionado esta nueva sección, puede añadirle
controles. Visualice de nuevo el menú contextual del cuadro de herramientas, y escoja la
opción Elegir elementos.

Se presenta entonces la lista de los controles (COM o .NET), disponibles en la máquina,


que le permite seleccionar los controles que se deben insertar en esta sección del cuadro de
herramientas. La configuración del cuadro de herramientas no está relacionada con el
proyecto activo, sino con el propio entorno (el cuadro de herramientas será idéntico sea
cual sea el proyecto abierto).

ShareVideos
3. El explorador de servidores

El explorador de servidores está disponible con el menú Visualización - Explorador de


servidores o mediante el método abreviado de teclado [Ctrl][Alt] S. Se visualiza en una
nueva pestaña de la ventana asociada al cuadro de herramientas.

La mayoría de las aplicaciones requieren otras máquinas presentes en la red para poder
funcionar. Por lo tanto, es necesario tener, durante la fase de desarrollo de una aplicación,
la posibilidad de acceder a los recursos disponibles en otras máquinas.

El elemento de la ventana Explorador de servidores que se utiliza de manera más frecuente


será la sección Conexiones de datos.

En particular permite la gestión de los objetos disponibles en el servidor SQL (tablas,


vistas, procedimientos almacenados).

El explorador de servidores también permite gestionar los servicios que funcionan en las
máquinas, tanto a través de la interfaz gráfica como del código. Ofrece la posibilidad de
visualizar la actividad de las máquinas analizando los contadores de rendimiento o
recuperando la información en los diferentes registros de eventos. Un sencillo arrastrar-
soltar entre el explorador de servidores y una ventana de diseño genera automáticamente el
código que permite manipular este elemento en la aplicación. Por ejemplo, el
desplazamiento de un contador de rendimiento sobre una ventana genera el código
siguiente:

Friend WithEvents perfCptMemoria As


System.Diagnostics.PerformanceCounter
Me.perfCptMemoria = New System.Diagnostics.PerformanceCounter
Me.perfCptMemoria.CategoryName = "Memoria"
Me.perfCptMemoria.CounterName = "Kilo-bites disponibles"
Me.perfCptMemoria.MachineName = "portatilTG"

4. El explorador de soluciones

El explorador de soluciones permite la visualización de los elementos que constituyen una


solución y la modificación de sus propiedades.

La utilización del explorador de soluciones se presenta en detalle en el capítulo dedicado a


la organización de una aplicación.

ShareVideos
5. La visualización de clases

La visualización de clases es accesible con el menú Ver - Otras ventanas - Vista de clases
o con la combinación de teclas [Ctrl][Shift] C. Comparte su zona de pantalla con el
explorador de soluciones.

La visualización de clases permite tener una visión lógica de una solución presentando las
diferentes clases utilizadas en esta solución.

6. La ventana de propiedades

La ventana de propiedades se puede visualizar con tres métodos:

 Utilizando el menú Ver - Ventana Propiedades.


 Con la tecla de función [F4].
 Con la opción Propiedades del menú contextual disponible al hacer clic con el
botón secundario del ratón en uno de los elementos que constituye un proyecto
(elemento gráfico de la interfaz de usuario, archivo o carpeta del proyecto). La
ventana de propiedades adapta automáticamente su contenido en función del
elemento seleccionado y permite modificar estas características.

Los elementos cuyas características desea modificar se pueden seleccionar directamente en


la lista desplegable o en la interfaz de la aplicación.

Hay dos presentaciones disponibles para la lista de propiedades:

El modo Alfabético, que se activa al hacer clic en el icono .

El modo Por categoría, que se activa al hacer clic en el icono .

7. La lista de tareas

Esta ventana le permitirá sustituir decenas de Post-it pegados en el borde de su pantalla. En


efecto, usted podrá gestionar lo que queda por hacer en su proyecto teniendo en cuenta una
lista de modificaciones pendientes de aportar a su código.

La información presente en la lista puede tener dos orígenes:

 Los comentarios insertados en su código.


 La información introducida directamente en la ventana.

ShareVideos
Puede ubicar en su código comentarios que aparecerán luego en la lista de tareas. Esta
técnica le permite, por ejemplo, indicar una modificación que deba efectuar más tarde en su
código.

Basta con que el comentario empiece por ’todo’ para luego retomarlo automáticamente en
la lista de tareas.

También puede introducir directamente los datos en la lista de tareas. Para ello deberá
elegir tareas de usuario utilizando la zona de lista disponible en la barra de título de la lista
de tareas.

La inclusión de una tarea se ejecuta con el botón , disponible en la lista de tareas.

Puede especificar una descripción y una prioridad para la nueva tarea haciendo clic en la
columna de izquierda, en la lista de tareas. Hay tres niveles de prioridad disponibles:

 Alta.
 Normal.
 Baja.

Para cada tarea, una casilla de verificación permite indicar si se ha realizado. Su


descripción aparece entonces tachada en la lista de tareas. Para las tareas de usuario, no hay
enlace automático con una porción cualquiera de código.

8. La lista de errores

El código que introduce es analizado continuamente por Visual Studio y los posibles
errores de sintaxis que encuentra se muestran en la ventana Lista de errores.

Para ir directamente a la línea donde apareció un error de sintaxis, basta con hacer doble
clic en el elemento correspondiente de la lista (en el ejemplo anterior, haga doble clic en {
El carácter no es válido para alcanzar la línea 7). No es necesario pedir la compilación
completa del código para rastrear todos los errores de sintaxis. En cuanto el error queda
corregido, desaparece automáticamente de la lista de errores.

Los botones de errores, advertencias y mensajes activan un filtro sobre los mensajes
visualizados en la lista de errores.

9. La ventana de edición del código

Vamos a dedicar más tiempo a esta ventana, ya que propone muchas funcionalidades que
permiten automatizar las acciones más corrientes.

ShareVideos
a. Los Snippets o fragmentos de código

Los Snippets son fragmentos de código que se pueden incorporar muy fácilmente a un
archivo fuente. Permiten escribir rápidamente porciones de código relacionado con
situaciones habituales. Visual Studio propone, de forma predeterminada, una multitud de
Snippets. Hay dos soluciones disponibles para insertar un Snippet:

 Usar la opción Insertar fragmento de código del menú contextual del editor de
código.
 Usar las combinaciones de teclas [Ctrl] K, luego [Ctrl] X.

Para estos dos métodos, Visual Studio le propone elegir en una lista el Snippet que le
interesa. Se pueden personalizar estas porciones de código del Snippet, que están resaltadas
en azul claro. La modificación de una de estas porciones de código repercute en todas las
apariciones en el Snippet.

En el ejemplo que viene a continuación, se utilizó un Snippet para añadir una nueva
propiedad a una clase.

Se efectuará la modificación de los valores newPropertyValue, String y NewProperty en


cascada en el conjunto del código del Snippet.

Usted también puede diseñar sus propios Snippets. Para ello, debe crear un archivo XML
que contendrá el código del Snippet, y darle la extensión .snippet.

Para ayudarle en la creación de un Snippet, Microsoft tiene previsto un Snippet. Usted


puede incorporarlo en su archivo XML con el menú contextual Insertar fragmento de
código - Snippet.

Obtendrá el documento siguiente:

<?xml version="1.0" encoding="utf-8" ?>


<CodeSnippet Format="1.0.0"
xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<Header>
<Title>title</Title>
<Author>author</Author>
<Shortcut>shortcut</Shortcut>
<Description>description</Description>
<SnippetTypes>
<SnippetType>SurroundsWith</SnippetType>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>

ShareVideos
<IDname/ID>
<Defaultvalue/Default>
</Literal>
</Declarations>
<Code Language="XML">
<![CDATA[<test>
<name>$name$</name>
$selected$ $end$</test>]]>
</Code>
</Snippet>
</CodeSnippet>

A continuación podrá personalizar su Snippet. En primer lugar, deberá modificar la sección


Header sustituyendo los valores de las diferentes etiquetas.

<Header>
<Title>Recorre una matriz de datos</Title>
<Author>Thierry</Author>
<Shortcut>matriz</Shortcut>
<Description>Funcionalidad que permite recorrer una
matriz de datos</Description>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>

La sección Declarations permite crear parámetros utilizados en el Snippet. Para cada


parámetro, deberá crear una sección <Literal> y facilitar un nombre para el parámetro y un
valor por defecto.

<Declarations>
<Literal>
nombreMatriz</ID>
<Default>laMatriz</Default>
</Literal>
<Literal>
tipoMatriz</ID>
<Default>tipoDelaMatriz</Default>
</Literal>
<Literal>
tamañoMatriz</ID>
<Default>tamañoDelaMatriz</Default>
</Literal>
</Declarations>

Luego debe indicar para qué lenguaje está previsto su Snippet.

<Code Language="VB">

Y finalmente definir en la etiqueta CDATA el código Snippet. En este código, puede


utilizar los parámetros del Snippet enmarcándolos entre dos caracteres $.

<![CDATA[

ShareVideos
dim $nombreMatriz$($tamañoMatriz$) as $tipoMatriz$
dim index as integer
for index=0 to $tamañoMatriz$ -1
’ añada el código que gestiona la matriz
next ]]>

Luego puede guardar el archivo y su Snippet está listo. Conviene ahora integrarlo en Visual
Studio. Para ello, deberá activar el gestor de Snippet a través del menú Herramientas -
Administrador de fragmentos de código.

El botón Importar permite añadir su Snippet a los ya disponibles en Visual Studio.

Después de haber seleccionado el archivo que contiene el Snippet, deberá elegir la


ubicación en la que se guardará.

Su Snippet está ahora disponible en el editor de código.

Solo le queda personalizar el código generado.

b. Seguimiento de las modificaciones

Puede visualizar los fragmentos de código que ya han sido modificados desde el
lanzamiento de Visual Studio. Se identifican las modificaciones con un borde de color en el
margen del editor de código.

 Un borde amarillo indica que se ha modificado el código, pero que aún no ha sido
guardado.
 Un borde verde indica que se ha modificado y guardado el código.

También puede renombrar fácilmente un elemento y propagar de forma automática la


modificación al resto del código. El uso típico consiste en cambiar el nombre de una
variable o clase. No debe renombrar la variable directamente en el código, sino utilizar el
cuadro de diálogo visualizado usando la opción Cambiar nombre del menú contextual del
editor de código sobre el nombre actual de la variable.

ShareVideos
La modificación realizada mediante este cuadro de diálogo repercute sobre el conjunto del
código donde se utiliza la variable.

10. Las herramientas de edición de código

Los editores de texto de Visual Studio disponen de muchas funcionalidades que permiten
facilitar las operaciones efectuadas con frecuencia durante la escritura del código de una
aplicación.

Selección de texto

Como complemento de las funciones clásicas de selección de texto y de copiar-pegar, el


editor de Visual Studio permite la selección de zonas rectangulares de texto al mantener
pulsada la tecla [Alt] durante la selección. Cuando se introduce texto en la selección, el
texto se duplica en cada línea de la selección.

Se dispone por ejemplo del método siguiente, que visualiza en la consola los datos
personales de un individuo:

Private Sub visualizaciónResultados(ByVal c As Cliente)


Console.Write("Apellido:" + c.Apellido);
Console.Write("Nombre:" + c.Nombre);
Console.Write("Calle:" + c.Calle);
Console.Write("Código Postal:" + c.CódigoPostal);
Console.Write("Ciudad:" + c.Ciudad);
Console.Write("Teléfono:" + c.Telefono);
Console.Write("Correo electrónico:" + c.Correo)
End Sub

Para modificar este método y escribir estos datos en un archivo en vez de visualizarlos en la
consola, solo es necesario crear el archivo y modificar todas las instrucciones .Write para
que se apliquen en el archivo creado. Para ello, añada simplemente la línea siguiente para la
creación del archivo:

Dim archivo As StreamWriter = New StreamWriter("resultados")

Modifique después cada instrucción .Write para escribir hacia el archivo, y no hacia la
consola. Seleccione para ello una zona rectangular que contenga todas las palabras console
e introduzca la palabra archivo.

Se sustituye entonces la palabra Console en todas las líneas de la selección.

ShareVideos
También es posible insertar texto simultáneamente en varias líneas creando una zona de
selección rectangular de cero caracteres de ancho en todas las líneas donde se debe efectuar
la inserción.

A continuación se inserta el texto introducido en todas las líneas de la selección.

Resaltar las referencias

Al hacer clic en un símbolo en el código fuente, todas las instancias de este símbolo quedan
marcadas en el documento.

Funcionalidad Generar con el uso

Durante el desarrollo de una aplicación, a veces ocurre que se intenta utilizar un elemento
antes de su declaración, posponiendo esta para más tarde. Sin embargo, esta solución tiene
el inconveniente de no poder realizar ningún test hasta que todos los elementos utilizados
hayan sido definidos. También es deprimente para el desarrollador ver decenas de líneas de
código subrayadas en rojo.

El editor de Visual Studio es capaz de generar el código necesario para los elementos que
faltan. Cuando el ratón pasa sobre el elemento referido, aparece un botón debajo del
elemento.

Al hacer clic en este botón, aparece un menú contextual que propone las opciones que
permiten generar el código que puede resolver los problemas detectados.

Se adaptan las opciones disponibles en este menú contextual según la ubicación del
elemento en el que este está activado. En el ejemplo anterior, el término nombre puede
corresponder a un nombre de método, propiedad o variable. Según los casos, se puede
visualizar un cuadro de diálogo para personalizar la generación del código. Solo hace falta
completar el cuadro de diálogo siguiente para que el esqueleto de código se genere.

ShareVideos
Zoom

Esta funcionalidad permite efectuar un zoom hacia delante o atrás sobre una ventana de
texto. Se puede acceder a ella accionando la rueda del ratón mientras se mantiene la tecla
[Ctrl] pulsada.

Las soluciones
1. Presentación

Para ayudarle en la creación de aplicaciones, Visual Studio le propone varios elementos que
sirven para agrupar los componentes de una aplicación. El contenedor de más alto nivel es
la solución en la que podrá ubicar uno o varios proyectos. Estos proyectos contendrán, a su
vez, todos los elementos para que el compilador sea capaz de generar el archivo ejecutable
o dll del proyecto. El explorador de soluciones nos va a permitir manipular todos estos
elementos.

2. Creación de una solución

La creación de una solución es automática cuando se empieza un nuevo proyecto en Visual


Studio. Durante la creación del nuevo proyecto, se le pedirá información al respecto.

A través del cuadro de diálogo, podrá facilitar los datos siguientes:

 La versión del Framework necesario para utilizar la aplicación.


 El lenguaje utilizado para desarrollar el proyecto.
 El tipo de proyecto que se va a crear.
 El nombre del proyecto.
 El directorio principal donde estarán almacenados los archivos.
 El nombre de la solución.
 La creación de un directorio para la solución.

Después de validar este cuadro de diálogo, el explorador de soluciones le presenta la nueva


solución en la que va a poder trabajar. Todos los archivos que la componen ya están
creados y guardados en su disco, en la ubicación que usted ha especificado.

Una solución contendrá, al menos, los archivos siguientes:

 Un archivo con la extensión .sln, que es el archivo de configuración de la solución.


Este archivo contiene, entre otros, la lista de todos los proyectos que componen la
solución. Se completa a medida que se añaden nuevos proyectos a la solución.
 Un archivo con la extensión .suo, donde se guardan las opciones asociadas a la
solución. Este archivo permite encontrar estas opciones.

ShareVideos
 Un archivo para el proyecto, con la extensión .vbproj. Este archivo contiene todos
los datos de configuración del proyecto, junto con la lista de los archivos que lo
constituyen, la lista de las referencias utilizadas en él, las opciones a utilizar para la
compilación del proyecto, etc.
 Numerosos archivos con la extensión .vb, que contendrán el código fuente de todas
las clases, hojas y módulos que constituyen el proyecto.
 Un archivo .resx asociado a cada hoja de su aplicación. Este archivo en formato
XML contiene, entre otras, la lista de los recursos utilizados en esta hoja.
 Al final, una solución contiene otros muchos archivos en función de los elementos
utilizados en su proyecto (acceso a una base de datos, archivos HTML...).

3. Modificación de una solución

Las soluciones, como contenedores que son, permiten gestionar sus elementos. Es posible
añadir, suprimir, cambiar el nombre de elementos en la solución.

a. Añadir un proyecto

Hay varias posibilidades disponibles para añadir un proyecto:

Si desea crear un nuevo proyecto, elija la opción Nuevo Proyecto del menú Archivo -
Agregar. Un cuadro de diálogo le propone entonces configurar las características del nuevo
proyecto y, en particular, definir un directorio por defecto para guardar el proyecto. Si este
directorio no corresponde a la ubicación donde usted desea guardar el proyecto, puede
seleccionar una nueva. Esta operación se deberá realizar para cada proyecto que desee
añadir. Puede ser interesante modificar la ruta propuesta por defecto para guardar los
proyectos. Para hacerlo, abra el menú Herramientas - Opciones, en el cuadro de diálogo
elija la opción Proyectos y soluciones y modifique la sección Ubicación de los proyectos.

Si usted desea añadir un proyecto ya existente, elija la opción Abrir proyecto del menú
Archivo - Agregar. Un cuadro de diálogo de selección de archivos le permite entonces
elegir el archivo .vbproj del proyecto que quiere añadir a la solución.

Tenga en cuenta que el proyecto se queda en su ubicación de origen en el disco.

b. Suprimir un proyecto

Para suprimir un proyecto, utilice el menú contextual del explorador de soluciones


efectuando un clic sobre el nombre del proyecto que desea suprimir de la solución.

Se ha eliminado el proyecto de la solución, pero queda grabado en el disco. Para suprimirlo


de manera definitiva, utilice el explorador de Windows para suprimir los archivos de este
proyecto. Si no borra los archivos, se puede añadir de nuevo el proyecto a una solución en
otro momento.

ShareVideos
c. Cambiar el nombre de un proyecto

Para cambiar el nombre de un proyecto, utilice el menú contextual del explorador de


soluciones haciendo clic con el botón secundario del ratón en el nombre del proyecto que
desea cambiar.

El nombre del proyecto se convierte entonces en modificable en el explorador de


soluciones. Esta modificación se refiere únicamente al nombre del archivo .vbproj asociado
al proyecto. No modifica en ningún caso el nombre del directorio en el que se encuentran
los archivos del proyecto.

d. Descargar un proyecto

Cuando desee excluir de manera temporal un proyecto del proceso de generación o impedir
la edición de sus componentes, puede descargar el proyecto de la solución usando la opción
Descargar el proyecto.

No se retira un proyecto descargado de la solución, sino que simplemente queda marcado


como ’No disponible’.

Por supuesto, se puede rehabilitar el proyecto en la solución utilizando la opción Volver a


cargar el proyecto del menú contextual.

4. Organización de una solución

Si está trabajando con una solución que contiene varios proyectos, puede añadir un nuevo
nivel de jerarquía creando carpetas de solución. Estas permiten la agrupación lógica de
proyectos dentro de una solución.

Para ello, cree primero las carpetas en la solución, y después organice los proyectos en
estas carpetas.

Las carpetas de solución no crean carpetas físicas en el disco, solo son contenedores lógicos
en el interior de la solución.

a. Creación de una carpeta de solución

Se puede crear una carpeta de solución usando dos métodos.

En ambos, seleccione la solución en el explorador de soluciones.

ShareVideos
A continuación utilice el menú Proyecto - Agregar nueva carpeta de solución o el menú
contextual disponible haciendo clic con el botón secundario del ratón en el nombre de la
solución.

Sea cual sea el método utilizado, debe facilitar un nombre para la carpeta creada.

b. Crear un proyecto en una carpeta

La creación de un proyecto en una carpeta de solución es idéntica a la creación de un


proyecto directamente en la solución.

Previamente, basta con que seleccione la carpeta en la que desea crear el proyecto.

c. Desplazar un proyecto a una carpeta

A menudo es necesario organizar una solución con carpeta cuando ya existen proyectos en
la solución.

En este caso hay que crear las carpetas y después arrastrar-soltar los proyectos a las
carpetas correspondientes.

5. La carpeta Elementos de solución

Las soluciones contienen principalmente proyectos, aunque es posible tener en una solución
archivos gestionados de manera independiente de un proyecto particular, pero asociados a
la solución. Es el caso, por ejemplo, de un archivo icono que quiera utilizar en varios
proyectos de la solución. Estos archivos se llaman elementos de solución y se encuentran
en una carpeta específica de esta.

Para añadir un nuevo elemento de solución, utilice el menú contextual en el nombre de la


solución seleccionando la opción Agregar - Nuevo elemento o la opción Añadir -
Elemento existente.

Entonces el nuevo elemento se añade en la carpeta Elementos de solución. Tenga en


cuenta que, por defecto, este archivo no existe en una solución, sino que se crea
automáticamente durante la inclusión del primer elemento de solución. Luego se pueden
modificar los elementos de solución con un editor específico para el tipo de archivo creado.

6. La carpeta Archivos varios

A veces puede querer visualizar el contenido de un archivo mientras está trabajando en una
solución, como por ejemplo el acta de una reunión. Este archivo no debe pertenecer a la
solución de manera permanente. Puede abrirlo con un editor externo y pasar de Visual
Studio a este editor externo, pero resulta más práctico visualizar el archivo directamente en
el entorno Visual Studio.

ShareVideos
Utilice la opción Abrir archivo del menú Archivo.

El cuadro de diálogo le permite elegir el archivo que desea abrir. En función del tipo de
archivo, se le asociará automáticamente un editor por defecto, para permitir su
modificación. A veces puede resultar útil elegir el editor asociado a un archivo. Para ello, el
botón Abrir del cuadro de diálogo dispone de un menú que propone la opción Abrir con,
que permite la elección del editor asociado al archivo.

El cuadro de diálogo siguiente le propone la lista de editores disponibles.

Seleccione el editor asociado al archivo con el que desea trabajar y valide.

El archivo está ahora disponible en la carpeta Archivos varios de la solución. De la misma


manera que la carpeta Elementos de solución, la carpeta Archivos varios no existe por
defecto en la solución, sino que se crea automáticamente al abrir un archivo.

Solo será visible en el explorador de soluciones si se activa la opción correspondiente en el


entorno Visual Studio. Para ello, abra el menú Herramientas - Opciones. Luego, en el
cuadro de diálogo, elija la opción Entorno - Documentos y active la opción Mostrar
archivos varios en el Explorador de soluciones. Como la carpeta Elementos de solución,
esta es una carpeta «lógica» y no corresponde a ninguna ubicación en el disco.

7. Configuración de una solución

Las soluciones disponen de propiedades que permiten configurar sus comportamientos


durante la generación o ejecución de la aplicación. Estas diferentes propiedades están
agrupadas en un cuadro de diálogo accesible con la opción Propiedades del menú
contextual de una solución. Hay cinco categorías de propiedades disponibles:

 Proyecto de inicio.
 Dependencias del proyecto.
 Parámetros de análisis del código.
 Depurar archivos de código fuente.
 Propiedades de configuración.

Miremos en detalle cada una de ellas.

a. Configuración del proyecto de inicio

Esta página de propiedades de la solución determina, entre los proyectos disponibles, cuál o
cuáles son lanzados al arrancar la solución.

ShareVideos
Hay tres opciones disponibles:

Selección actual

Esta opción indica que el proyecto seleccionado en el explorador de soluciones se ejecutará


cuando se inicie la solución.

Proyecto de inicio único

Un combo propone la lista de los proyectos disponibles en la solución y entre los que se
debe elegir el que será ejecutado al iniciar la solución. El nombre de este proyecto se señala
en negrita en el explorador de soluciones. Esta selección también se puede hacer con el
menú contextual del explorador de soluciones eligiendo la opción Establecer como
proyecto de lanzamiento.

Proyectos de inicio múltiples

Una tabla presenta la lista de todos los proyectos disponibles en la solución. Para cada uno
de ellos, se debe indicar la acción que se va a ejecutar en el inicio de la aplicación. Las
opciones posibles son:

 Ninguna.
 Iniciar.
 Iniciar sin depurar.

Si elige iniciar varios proyectos a la vez en el arranque de la solución, también debe indicar el
orden en el que se iniciarán estos proyectos. Este orden corresponde en realidad al orden de los
proyectos en la tabla. Los botones y permiten modificarlo.

b. Dependencias del proyecto

La generación de algunos proyectos requiere la generación previa de otros proyectos. Es el


caso, por ejemplo, si se pide la generación de un proyecto que utiliza una referencia hacia
otro: entonces, este es una dependencia del proyecto inicial.

La siguiente página de propiedades permite configurar estas dependencias.

En la lista de los proyectos, seleccione el proyecto para el cual desea configurar las
dependencias. Los otros proyectos de la solución aparecen en una lista con una casilla de
verificación para cada uno. A la hora de generar el proyecto, todos los proyectos de los que
depende serán automáticamente regenerados si han sido modificados desde la última
generación o si nunca han sido generados.

ShareVideos
Algunas dependencias no se pueden modificar; entonces, la casilla de verificación aparece
en gris.

En general, es el caso cuando un proyecto posee una referencia a otro proyecto o cuando,
añadiendo una dependencia, se puede crear un bucle. Por ejemplo, el proyecto1 depende del
proyecto2, e inversamente.

También se pueden configurar las dependencias de proyecto mediante el menú contextual


del explorador de soluciones, con la opción Dependencias del proyecto.

c. Parámetros de análisis del código

Esta página de propiedades permite configurar las reglas que se utilizan cuando se analiza
el código de los diferentes elementos de la solución.

Para cada proyecto de la solución, puede indicar la configuración que utilizarán las
herramientas de análisis.

La opción Todas las reglas de Microsoft es la más estricta y detecta la menor anomalía,
principalmente:

 Los parámetros de función que esta no usa.


 Las variables locales que no se usan.
 Los nombres de parámetros poco explícitos.
 Los identificadores que no respetan las convenciones de nomenclatura respecto al
uso de mayúsculas y minúsculas.

d. Depurar archivos de código fuente

Durante la depuración de una aplicación, el entorno Visual Studio necesita acceder al


archivo fuente del código que está depurando. Esta página de propiedades permite
especificar los directorios que serán analizados durante la búsqueda del código fuente.

La lista Directorios que contienen código fuente muestra el nombre de los directorios que
serán revisados en búsqueda de código fuente durante la depuración de una aplicación. Se
puede gestionar esta lista gracias a la barra de herramientas, cuyos botones permiten:

Comprobar la existencia del directorio.


Añadir un nuevo directorio.

ShareVideos
Suprimir el directorio seleccionado de la lista.
Desplazar el directorio hacia abajo en la lista.
Desplazar el directorio hacia arriba en la lista.

La lista No buscar los archivos de código fuente siguientes excluye algunos archivos de
la búsqueda.

e. Configuración

Las opciones de configuración permiten definir cómo se generan varias versiones de una
solución y de los proyectos que la componen. Por defecto, hay dos configuraciones
disponibles para una solución en Visual Studio: la configuración Debug y la configuración
Release.

Para cada uno de los proyectos presentes en la solución, las dos configuraciones también
estarán disponibles. A nivel del proyecto, las configuraciones permiten definir opciones de
compilación. Se utiliza la configuración Debug durante el desarrollo y las pruebas del
proyecto. Se utiliza la configuración Release para la generación final del proyecto.

En realidad, tenemos un sistema de tres niveles: para cada configuración de solución, se


indica qué configuración utilizar para cada proyecto, y para cada configuración de
proyecto, se especifican las opciones de compilación. Se pueden modificar las opciones de
compilación a nivel de las propiedades del proyecto.

Los proyectos
Los proyectos son los contenedores de segundo nivel en una aplicación. Se utilizan para
organizar lógicamente, gestionar, generar y depurar los componentes de una aplicación. La
generación de un proyecto suele producir un archivo ejecutable o una biblioteca dll. Un
proyecto puede ser muy sencillo y no contener más de dos elementos, un archivo fuente
(.vb) y el archivo de proyecto (.vbproj). En general, los proyectos contienen numerosos
archivos fuente, scripts de bases de datos, referencias hacia servicios Web, recursos
gráficos, etc.

Visual Studio propone por defecto un conjunto de plantillas de proyectos. Estas plantillas
representan un punto de partida para la mayoría de las necesidades en el desarrollo de una
aplicación. Para casos más específicos, puede crear sus propias plantillas de proyecto.

1. Creación de un proyecto

Para crear un proyecto, active el menú Archivo - Nuevo proyecto. Un cuadro de diálogo
le propone entonces elegir las características del nuevo proyecto.

ShareVideos
Elija primero la versión del Framework para la que desea desarrollar el proyecto. La
versión elegida influye en los tipos de proyectos que usted puede crear.

Elija luego el lenguaje con el que desea desarrollar el proyecto. Las opciones disponibles
dependen de los lenguajes instalados en Visual Studio. Por supuesto, en nuestro caso,
elegiremos Visual Basic.

Luego elija el tipo de proyecto que desea desarrollar. El cuadro de diálogo propone
entonces las diferentes plantillas de proyectos disponibles según el tipo de proyecto
elegido.

Después de haber elegido, indique un nombre para el proyecto, una ubicación para sus
archivos y un nombre para la solución. El asistente utiliza la plantilla seleccionada para
crear los elementos del proyecto.

Después de unos instantes, el proyecto está disponible en el explorador de soluciones.

Ahora personalice la plantilla.

a. Las plantillas de proyectos

Hay muchas plantillas de proyectos disponibles en Visual Studio. Estas plantillas facilitan
los elementos básicos necesarios para desarrollar cada tipo de proyecto. Siempre contienen
al menos el archivo del proyecto, más un ejemplar del elemento más utilizado para el tipo
de proyecto correspondiente. Por ejemplo, para un proyecto de biblioteca de clases, se crea
un archivo fuente que contiene un boceto de clases. Las plantillas proporcionan también
referencias e importaciones por defecto para las bibliotecas y los espacios de nombres más
útiles en función del tipo de proyecto.

Aplicación Windows Forms

Esta plantilla de proyecto es seguramente la más utilizada. Permite el desarrollo de una


aplicación Windows estándar. La plantilla añade los elementos siguientes al proyecto:

 Un archivo AssemblyInfo.vb utilizado para la descripción de la aplicación, con,


entre otros, los datos relativos a la versión.
 Un formulario de base con su archivo fuente form1.vb.

Las siguientes referencias se añaden e importan automáticamente:

 System
 System.Core
 System.Data
 System.Data.DataSetExtensions
 System.Deployment
 System.Drawing

ShareVideos
 System.Windows.Forms
 System.Xml
 System.Xml.Linq

Biblioteca de clases

Esta plantilla de proyecto se puede utilizar para crear clases y componentes que luego
podrán ser compartidos con otros proyectos. Los siguientes elementos se añaden
automáticamente al proyecto:

 Un archivo AssemblyInfo.vb utilizado para la descripción del proyecto con, entre


otra, la información relativa a la versión.
 Una clase de base con su archivo fuente class1.vb.

Las siguientes referencias se añaden e importan automáticamente:

 System
 System.Core
 System.Data
 System.Data.DataSetExtensions
 System.Xml
 System.Xml.Linq

Biblioteca de controles Windows

Como la plantilla anterior, este tipo de proyecto permite crear una biblioteca de clases
utilizable en otros proyectos. Esta biblioteca es más específica, ya que está dedicada a la
creación de controles, utilizables luego en una aplicación Windows. Estos controles
amplían el cuadro de herramientas disponible en las aplicaciones Windows. Los siguientes
elementos se añaden automáticamente al proyecto:

 Un archivo AssemblyInfo.vb utilizado para la descripción del proyecto, con, entre


otras, la información relativa a la versión.
 Una clase UserControl1 que hereda de la clase
System.Windows.Forms.UserControl y que proporciona las funcionalidades de base
para un control Windows, con su archivo fuente UserControl1.vb.

Las siguientes referencias se añaden e importan automáticamente:

 System
 System.Core
 System.Data
 System.Data.DataSetExtensions
 System.Drawing
 System.Windows.Forms
 System.Xml

ShareVideos
 System.Xml.Linq

Aplicación de consola

Este tipo de aplicación está destinado a ser ejecutado a partir de la línea de comando de una
ventana de línea de comandos. Por supuesto, está diseñada sin interfaz gráfica, las
entradas/salidas se hacen desde la línea de comando y hacia la consola.

Este tipo de aplicación es muy práctico para realizar pruebas con Visual Basic, ya que
permite concentrarse en un punto especial, sin tener que preocuparse por el aspecto de la
presentación de la aplicación.

Muchos ejemplos de este libro se basan en una aplicación de consola. Sin embargo, hay que
admitir que, a pesar de su sencillez de creación, este tipo de aplicación ha quedado
obsoleto.

Los siguientes elementos se añaden por defecto al proyecto:

 Un archivo AssemblyInfo.vb utilizado para la descripción, entre otras, de


información relativa a la versión.
 Una clase de base con su archivo fuente class1.vb.

Las siguientes referencias se añaden e importan automáticamente:

 System
 System.Core
 System.Data
 System.Data.DataSetExtensions
 System.Deployment
 System.Xml
 System.Xml.Linq

Servicio de Windows

Se diseña este tipo de proyecto para la creación de aplicaciones que se ejecutan como tarea
en segundo plano en el sistema. El lanzamiento de este tipo se puede efectuar
automáticamente al iniciar el sistema y no necesita que una sesión de usuario esté abierta
para poder ejecutarse.

Este tipo de aplicación está desprovisto de interfaz de usuario. Si se debe comunicar


información al usuario, esta deberá transitar por los sucesos del sistema, que pueden ser
consultados por el visor de sucesos. Los siguientes elementos se añaden al proyecto:

 Un archivo AssemblyInfo.vb utilizado para la descripción con, entre otras, la


información relativa a la versión.

ShareVideos
 Una clase de base con el esqueleto de los procedimientos OnStart y OnStop
llamados automáticamente al iniciar y al parar el servicio.

Las siguientes referencias se añaden e importan automáticamente:

 System
 System.Core
 System.Data
 System.Data.DataSetExtensions
 System.Deployment
 System.ServiceProcess
 System.Xml
 System.Xml.Linq

Aplicación WPF

Esta plantilla de proyecto permite beneficiarse del nuevo sistema de visualización gráfica
de Windows, utilizado en Windows Vista y en las versiones posteriores.

Los siguientes elementos se añaden automáticamente al proyecto:

 Un archivo AssemblyInfo.vb utilizado para la descripción de la aplicación con,


entre otra, la información relativa a la versión.
 Un archivo Application.Xaml y su archivo de código asociado,
Application.Xaml.vb, destinado a la gestión de los eventos desencadenados al nivel
de la aplicación.
 Una ventana de base Window1.Xaml y su archivo de código asociado,
Window1.Xaml.vb.

Las siguientes referencias se añaden e importan automáticamente:

 PresentationCore
 PresentationFramework
 System
 System.Core
 System.Data
 System.Data.DataSetExtensions
 System.Xaml
 System.Xml
 System.Xml.Linq
 WindowsBase

Biblioteca de controles de usuario WPF

ShareVideos
Al igual que la librería de controles Windows, este tipo de proyecto permite extender el
cuadro de herramientas ya disponible en las aplicaciones WPF. Se añaden los siguientes
elementos al proyecto:

 Un archivo AssemblyInfo.vb utilizado para la descripción de la aplicación con,


entre otra, la información relativa a la versión.
 Un archivo UserControl1.xaml para la definición del aspecto gráfico del control.
 Un archivo UserControl1.xaml.vb para el código asociado a este control.

Las referencias siguientes se añaden e importan automáticamente:

 PresentationCore
 PresentationFramework
 System
 System.Core
 System.Data
 System.Data.DataSetExtensions
 System.Xaml
 System.Xml
 System.Xml.Linq
 WindowsBase

Biblioteca de controles personalizados de WPF

Este tipo de proyecto también tiene por objetivo extender el cuadro de herramientas
disponible para las aplicaciones WPF. A diferencia del tipo de proyecto anterior, los
controles no han sido creados completamente, ya que están basados en controles existentes
cuyas características extienden.

Las referencias e importaciones son idénticas al tipo de proyecto anterior.

Proyecto vacío

Debe utilizar esta plantilla cuando desee crear su propio tipo de proyecto. Solo el archivo
proyecto está creado. A cambio, no se añade ningún otro elemento automáticamente y no se
crea ni importa ninguna referencia.

b. Creación de una plantilla de proyecto

Puede crear su propia plantilla de proyecto según sus costumbres de desarrollo y hacerlo de
tal manera que aparezca entre las plantillas predefinidas.

Deberá diseñar los elementos siguientes:

 Un archivo de definición que contenga los metadatos de la plantilla. Visual Studio


utiliza este archivo para la visualización del proyecto en el entorno de desarrollo y

ShareVideos
para la asignación de propiedades por defecto al proyecto. Estos datos están
contenidos en un archivo XML con la extensión .vstemplate.
 Un archivo para el proyecto (.vbproj).
 Los archivos fuente y recursos incluidos por defecto durante la creación de un
proyecto a partir de esta plantilla.

Estos archivos deben ser comprimidos en un archivo zip. El archivo zip debe contener los
archivos individualmente, y no la carpeta en la que están ubicados.

El archivo .vstemplate debe tener el formato siguiente:

<VSTemplate Version="3.0.0"
xmlns="http://schemas.microsoft.com/developer/vstemplate/2005"
Type="Project">
<TemplateData>
<Name>ApliPerso</Name>
<Description>creación de un proyecto con una configuración
personalizada
</Description>
<ProjectType>VisualBasic</ProjectType>
<DefaultName>ApliPerso</DefaultName>
</TemplateData>
<TemplateContent>
<Project File="ApliPerso.vbproj">
<ProjectItem>AssemblyInfo.vb</ProjectItem>
<ProjectItem>Hoja1.vb</ProjectItem>
<ProjectItem>Hoja1.Designer.vb</ProjectItem>
<ProjectItem>Hoja1.resx</ProjectItem>
</Project>
</TemplateContent>
</VSTemplate>

En este archivo encontramos:

En la sección Name

El nombre visualizado por el cuadro de diálogo de creación de un nuevo proyecto.

En la sección Description

Una descripción detallada del proyecto.

En la sección ProjectType

El nombre de la carpeta en la que se clasificará este proyecto en el cuadro de diálogo de


creación de proyecto.

En la sección DefaultName

ShareVideos
El nombre utilizado por defecto para todos los proyectos creados desde esta plantilla. Se
completa este nombre con un sufijo numérico en la creación del proyecto.

En la sección Project File

El nombre del archivo de proyecto asociado a la plantilla. Este archivo debe estar presente
en el archivo zip de la plantilla.

En las secciones ProjectItem

Los elementos que forman parte del proyecto. Estos elementos también deben estar
disponibles en el archivo zip.

c. Modificación de una plantilla existente

La modificación de una plantilla consiste en utilizar un archivo zip existente que contiene
los elementos necesarios para el proyecto y añadir elementos adicionales. Si se añaden
archivos a la plantilla, se deben ubicar en el archivo zip y también referenciarlos en el
archivo .vstemplate. Las plantillas predefinidas de Visual Studio se colocan en el directorio
C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\
ProjectTemplates\VisualBasic. Para que se tengan en cuenta las modificaciones, se debe
actualizar la caché utilizada por Visual Studio. Para ello:

Abra una ventana de comando Visual Studio.

Introduzca el comando devenv /setup. Sea paciente, ya que este comando tarda bastante en
ejecutarse. Después de la ejecución del comando, sus modificaciones están disponibles en
la plantilla de proyecto.

d. Utilización de un proyecto existente como plantilla

Puede que sea la solución más sencilla para construir una plantilla de proyecto.

En un primer momento, cree la plantilla como un proyecto ordinario.

Una vez finalizado su proyecto, expórtelo como plantilla. El menú Archivo - Exportar
plantilla lanza un asistente para guiarle durante la creación de la plantilla.

Este primer cuadro de diálogo le propone elegir el proyecto que quiere exportar, junto con
la sección del cuadro de diálogo de creación de proyecto en la que se colocará la futura
plantilla.

ShareVideos
Este segundo cuadro de diálogo le invita a elegir un icono para su plantilla de proyecto, un
nombre para la plantilla y una descripción. Dos opciones adicionales le permiten tener en
cuenta inmediatamente la nueva plantilla en Visual Studio y presentar el resultado de la
generación al mostrarle el contenido del archivo zip creado. Después de validar este último
cuadro de diálogo, la nueva plantilla de proyecto está disponible en Visual Studio.

Este método es muy sencillo para construir una nueva plantilla de proyecto y evita darle
vueltas a la sintaxis del archivo .vstemplate.

En el marco de un desarrollo en equipo, puede resultar interesante compartir las plantillas


personalizadas entre todos los miembros del equipo.

Copie otra vez los archivos zip en una red compartida.

Configure el entorno Visual Studio para permitirle acceder a las plantillas. Esta
modificación se efectúa gracias al cuadro de diálogo disponible desde la opción del menú
Herramientas - Opciones.

2. Modificación de un proyecto

Las plantillas de proyectos son muy útiles para crear rápidamente las bases de una
aplicación, pero a menudo necesitarán la inclusión de nuevos elementos al proyecto. Estas
inclusiones se hacen por medio del menú contextual del explorador de proyectos.

Active la opción Agregar - Nuevo elemento para elegir el tipo de elemento que desea
añadir al proyecto. El cuadro de diálogo propone un número impresionante de elementos
que se pueden añadir a un proyecto.

Indique un nombre para el archivo que contiene el nuevo elemento.

En función de los tipos de proyecto, hay opciones adicionales disponibles en el menú


contextual que permiten añadir con rapidez un nuevo elemento. Se visualizan simplemente
en el cuadro de diálogo anterior con el tipo de elemento correspondiente ya
preseleccionado.

También es posible retomar un elemento existente en otro proyecto y añadirlo a un


proyecto. Utilice en este caso la opción Agregar - Elemento existente del menú contextual

ShareVideos
del explorador de proyectos. Un cuadro de diálogo le propone la selección del archivo que
desea incluir en el proyecto.

El botón Agregar de este cuadro de diálogo dispone de un menú que permite añadir el
archivo normalmente (se lleva a cabo en ese momento una copia local del archivo) o crear
un vínculo en el archivo (se utiliza el archivo original). Hay que ser prudente con esta
posibilidad, ya que el archivo «no pertenece» realmente a la aplicación, y se puede
compartir entre varias aplicaciones. Si se suprime el archivo del disco, no se podrá compilar
las aplicaciones que lo utilizan.

La gestión de los archivos en el explorador de soluciones es idéntica a la gestión de los


archivos en el explorador de Windows. Los archivos se pueden copiar y pegar o desplazar
arrastrándolos de una carpeta a otra. El uso de las teclas [Ctrl], [Mayús] y [Ctrl][Mayús]
durante el arrastre modifica la acción realizada. Si se hace clic y arrastrar dentro de un
mismo proyecto, se realiza un desplazamiento de archivo. Si se realiza entre dos proyectos,
entonces se efectúa una copia del archivo. Este comportamiento se puede modificar
utilizando la tecla [Mayús] mientras se arrastra. Para efectuar una copia de archivo dentro
de un proyecto, se usa la tecla [Ctrl] mientras se arrastra. Se puede crear un enlace
utilizando la combinación de teclas [Ctrl][Mayús] mientras se arrastra.

Para quitar un elemento de un proyecto, hay dos opciones accesibles a través del menú
contextual del explorador de soluciones:

 La opción Suprimir suprime el archivo del proyecto y también del disco.


 La opción Excluir del proyecto quita el archivo del proyecto, pero no lo suprime
del disco. Esta opción es útil si otros proyectos utilizan este archivo mediante un
vínculo.

3. Propiedades de los proyectos

Los proyectos son elementos fundamentales en la creación de una aplicación con Visual
Basic. Poseen muchas propiedades que permiten modificar sus comportamientos en el
momento del diseño o de la ejecución de la aplicación. El conjunto de las propiedades está
accesible a través de un cuadro de diálogo que presenta, mediante pestañas, las diferentes
secciones de configuración de un proyecto.

Active este cuadro de diálogo con la opción Propiedades del menú contextual del
explorador de soluciones o con el botón de la barra de herramientas del explorador de
proyecto.

ShareVideos
a. Propiedades de aplicación

Las propiedades presentes en esta pestaña van a permitir configurar el comportamiento de


la aplicación.

Nombre del ensamblado

Esta propiedad determina el nombre utilizado para el archivo resultante de la compilación


de la aplicación. Por defecto, este archivo lleva el mismo nombre que el proyecto, pero se
pueden modificar los dos de manera independiente el uno del otro. La extensión asociada al
archivo depende del tipo del proyecto.

Versión de .NET Framework de destino

Esta propiedad indica la versión del Framework necesaria para ejecutar la aplicación. De
manera predeterminada, este valor es igual al seleccionado durante la creación del proyecto.

Tipo de aplicación

Esta propiedad determina el tipo de aplicación generada por la compilación del proyecto.
La plantilla elegida en el momento de la creación del proyecto suele determinar esta
propiedad, que se modifica pocas veces, ya que depende mucho del código de su proyecto
(si diseñó su aplicación como una aplicación Windows y desea considerarla como una
aplicación para consola, ¡se arriesga a tener mucho código inútil!).

Formulario de inicio

Esta propiedad determina el punto de entrada en la aplicación durante su ejecución.


Normalmente, corresponde a la ventana principal de la aplicación o a un procedimiento Sub
Main. Esta propiedad solo está disponible para los proyectos que se pueden ejecutar de
manera autónoma. Es inútil para los proyectos de tipo biblioteca.

Espacio de nombres de la raíz

Todos los elementos del proyecto, accesibles desde otro proyecto, pertenecen al espacio de
nombres definido por esta propiedad. Esta viene a añadirse a los posibles espacios de
nombres definidos a nivel del propio código. Por defecto, esta propiedad corresponde al
nombre del proyecto, pero se puede modificar de manera independiente a este. Incluso
puede estar vacía, lo que le permite generar espacios de nombres directamente en el código.

Icono

ShareVideos
Esta propiedad configura el icono asociado al archivo compilado del proyecto cuando se
visualiza en el explorador de Windows o cuando la aplicación aparece en la barra de tareas
de Windows.

Información de ensamblado

Esta opción permite facilitar datos sobre el código generado por la compilación del
proyecto. Un cuadro de diálogo permite rellenar diferentes secciones relativas a la
descripción del proyecto.

El usuario de su código podrá consultar estos datos visualizando las propiedades del
archivo compilado en el explorador de Windows.

Ver configuración de Windows

Esta opción le permite determinar el nivel de ejecución requerido por la aplicación. Dicha
información es utilizada por el mecanismo User Account Control (UAC) de Windows.
Indica bajo qué identidad correrá el código de la aplicación. Hay tres valores posibles:

 asInvoker: la aplicación se ejecuta con la identidad actual del usuario y no requiere


aumento de privilegios.
 highestAvailable: la aplicación se ejecuta con el mayor nivel de privilegios del
usuario.
 requireAdministrator: la aplicación se debe ejecutar con el privilegio administrador,
y UAC puede pedirle su consentimiento para aumentar sus privilegios.

Habilitar marco de trabajo de la aplicación

Esta opción determina si quiere activar una interacción más desarrollada entre la aplicación
y el sistema operativo. Si esta opción está activa, el elemento de inicio de la aplicación debe
ser una hoja. El uso de esta opción hace que las siguientes propiedades pasen a estar
disponibles.

Habilitar estilos visuales de XP

Si esta opción se activa y la aplicación se ejecuta en un sistema operativo Windows XP, la


interfaz de usuario de la aplicación se adaptará al formato Windows activo.

Convertir aplicación de instancia única

Por defecto, puede lanzar todas las instancias de una misma aplicación que quiera en una
única máquina. Sin embargo, a veces puede ser útil autorizar el funcionamiento de una sola
instancia de la aplicación en un determinado momento (problema de licencia de usuario,
conservación de los recursos de la máquina...). La activación de esta opción garantiza que

ShareVideos
no haya más de una instancia de la aplicación en ejecución en la máquina. Si se lanza una
nueva instancia cuando ya hay otra en el sistema operativo, el foco se pasa directamente a
la instancia existente. A nivel de la aplicación, el evento StartupNextInstance también se
desencadenará.

Guardar My.Settings al cerrar

Esta opción indica si las propiedades personalizadas de la aplicación se guardan cuando se


cierra la aplicación. Esto permite, por ejemplo, grabar las preferencias del usuario.

Modo de autenticación

Por defecto, las aplicaciones Visual Basic utilizan la autenticación de Windows para
identificar el usuario de la aplicación. Si quiere gestionar usted mismo esta identificación,
debe utilizar la opción Definido para la aplicación.

Modo de apagado

Esta opción determina el comportamiento de la aplicación cuando se cierra. Por defecto, la


ejecución de la aplicación se para cuando se cierra la ventana de inicio de la aplicación,
aunque haya otras ventanas activas (siempre que no sea una ventana modal). La opción Al
cerrar el último formulario produce el cierre de la aplicación en el momento del cierre de
la última ventana activa de la aplicación o cuando se llaman las instrucciones
My.application.exit o end explícitamente en el código.

Pantalla de presentación

Las pantallas de inicio se utilizan a menudo para proporcionar información al usuario


durante el inicio de la aplicación. Visual Studio propone una plantilla de pantalla de inicio
personalizable. Esta pantalla aparece mientras se carga la ventana principal de la aplicación.

Ver eventos de aplicaciones

Esta opción permite acceder a los gestores de eventos del objeto de la aplicación. Los
gestores de eventos permiten reaccionar frente a diferentes situaciones:

Startup

La aplicación arranca.

StartUpNextInstance

Se acaba de lanzar una nueva instancia de la aplicación.

Shutdown

ShareVideos
La aplicación se cierra.

UnHandledException

Se acaba de producir una excepción no controlada.

NetworkAvailabilityChanged

Se acaba de modificar el acceso a la red (el cable ha sido enchufado o desenchufado).

b. Referencias e importaciones de un proyecto

Para poder utilizar elementos externos disponibles en un ensamblado, tiene que añadir una
referencia a este ensamblado.

La siguiente página de propiedades recoge toda la información relativa a las referencias de


un proyecto.

En esta página de propiedades, la lista Referencias presenta todos los ensamblados


actualmente referenciados en el proyecto.

Esta lista se puede actualizar con los botones Agregar, Quitar y Actualizar, que permiten
respectivamente añadir una referencia local o una referencia Web, eliminar una referencia o
actualizar una referencia Web.

El botón Rutas de acceso de referencia permite indicar los directorios adicionales que
contienen ensamblados disponibles. Estos directorios se analizan al abrir el cuadro de
diálogo para añadir referencias, y los eventuales ensamblados que contienen se añaden a la
lista de ensamblados disponibles. Cuando se añade una referencia a un ensamblado, en la
primera ejecución de la aplicación se utiliza el archivo original. Puede utilizar la creación
automática de una copia local de este archivo en el directorio de la aplicación. Para ello:

Seleccione la referencia en cuestión y modifique la propiedad Copy Local en la ventana de


propiedades de Visual Studio.

El botón Referencias sin utilizar muestra la lista de todas las referencias no utilizadas en
el código y le propone eliminarlas del proyecto.

Los elementos disponibles en los ensamblados referenciados son, sin duda, parte de un
namespace. Para poderlos utilizar fácilmente, es posible importar automáticamente algunos
espacios de nombres. La lista Espacios de nombres importados recoge los espacios de
nombre importados automáticamente en todos los códigos del proyecto. Puede completar
esta lista eligiendo en el cuadro de texto el nombre del espacio de nombres importado y
validando con el botón Agregar importación del usuario.

ShareVideos
c. Propiedades de depuración

Las propiedades presentes en esta página determinan el comportamiento del proyecto


durante su depuración.

Acción de inicio

Esta propiedad determina el comportamiento del proyecto durante el inicio de la


depuración. Hay tres opciones posibles:

 Proyecto de inicio indica que el propio proyecto debe ser ejecutado. Solo se puede
utilizar para los proyectos de aplicación Windows o para los proyectos de aplicación
de consola.
 Programa externo de inicio permite provocar la ejecución de una aplicación
externa que se va a encargar de realizar llamadas al código de nuestro proyecto. Se
utiliza esta opción para la depuración de las librerías de clases.
 Iniciar explorador con la dirección URL es idéntica a la opción anterior, excepto
que la aplicación iniciada es una aplicación Web.

Opciones de inicio

Argumentos de la línea de comandos precisa los argumentos pasados a la aplicación


durante su ejecución por Visual Studio. El código puede utilizar estos argumentos para
determinar la acción que hay que acometer: por ejemplo, lanzar la aplicación en modo de
mantenimiento.

Directorio de trabajo permite especificar el directorio activo durante la ejecución de la


aplicación.

Usar máquina remota autoriza la depuración de una aplicación que se ejecuta en otra
máquina. En este caso, se debe indicar el nombre de la máquina remota en la cual se va a
ejecutar el código.

Habilitar depuradores

Estas opciones determinan los diferentes tipos de depuradores activos, como complemento
del depurador de código gestionado de Visual Studio.

d. Propiedades de compilación

Las propiedades de esta página se refieren al funcionamiento del compilador y a su


eventual optimización.

ShareVideos
Ruta de acceso de los resultados de la compilación

Esta propiedad indica el directorio en el que se copia el archivo resultante de la


compilación del proyecto. Por defecto, se trata del subdirectorio bin del directorio en el que
se encuentra el proyecto.

Option Explicit

Esta opción permite exigir o no que se declare cualquier variable antes de su utilización. Se
aplica a todos los archivos fuente de un proyecto. Es posible, sin embargo, modificar esta
opción para un archivo en concreto, añadiendo la directiva Option Explicit On u Option
Explicit Off al principio de un archivo.

Option Strict

Esta opción permite controlar las conversiones realizadas. Si se intenta realizar una
conversión restrictiva, el compilador genera un error. Como la anterior opción, esta se
aplica a todos los archivos fuente de un proyecto y se puede modificar para un archivo en
concreto con el comando Option Strict On u Option Strict Off.

Option Compare

Esta opción determina cómo se realiza la comparación de cadenas de caracteres en una


aplicación. Con el valor Binary, la aplicación hace una distinción entre los caracteres en
minúscula y en mayúscula en una comparación. El valor Text permite evitar esta
distinción.

Option infer

Esta opción indica si la inferencia de tipos de variables locales está activa. Con cada
opción, el mismo compilador determina el tipo de variable de acuerdo con el tipo que se le
asigna.

CPU de destino

Esta opción especifica el procesador para el que se debe generar el código. Hay tres
opciones disponibles:

 X86: para los procesadores de 32 bits compatibles Intel.


 X64: para los procesadores de 64 bits compatibles Intel.
 Any CPU: para todos los procesadores.

Preferencia de 32 bits

ShareVideos
Esta opción indica que la aplicación siempre se ejecutará como una aplicación de 32 bits,
incluso en un sistema de 64 bits. Solo está disponible si la unidad central de destino está
definida como AnyCPU.

Configuración de advertencias

El compilador es capaz de detectar los potenciales problemas de su código y de generar


advertencias asociadas. Puede configurar la acción del compilador para diferentes
categorías de problemas. Las tres acciones posibles son:

 Ninguna acción: el compilador ignora el problema.


 Advertencia: el compilador genera una advertencia en la lista de tareas.
 Error: el compilador genera un error de compilación.

Puede también deshabilitar todas las advertencias utilizando la opción Deshabilitar todas
las advertencias o, al revés, tratarlas todas como errores con la opción Considerar todas
las advertencias como errores.

Generar archivo de documentación XML

Con esta opción el compilador busca en el código los comentarios especiales colocados
gracias a los caracteres ’’’ y los utiliza para generar el archivo de documentación. Este
archivo se crea dentro del directorio en el que se genera el archivo compilado.

Eventos de compilación

Este cuadro de diálogo permite configurar un comando que se puede lanzar


automáticamente antes o después de la generación del proyecto.

Cada uno de los comandos se puede introducir en la zona de texto correspondiente. Los
botones Edición anterior a la compilación y Edición posterior a la compilación abren
una ventana de edición que facilita la introducción del comando.

También este cuadro de diálogo propone una lista de macros que permite la recuperación y
el uso por su comando de ciertos parámetros del proyecto. El ejemplo presentado en la
figura anterior efectúa una copia completa del directorio de la aplicación en el directorio
C:\copia de seguridad, antes de cada generación.

La ejecución del comando después de la generación puede ser condicional y ocurrir solo en
caso de generación exitosa o si la generación actualizó la salida del proyecto.

ShareVideos
Si un comando debe ejecutar un archivo .bat después de la generación, la llamada de este
debe venir precedida de la palabra clave call.

Opciones de compilación avanzadas

Esta opción abre un cuadro de diálogo que permite la configuración avanzada del
compilador.

Este cuadro de diálogo permite la configuración de las siguientes opciones:

 Quitar comprobaciones de desbordamiento con enteros.


 Autorizar las optimizaciones del código durante la compilación.
 Especificar la dirección en la que se cargará la biblioteca dll.
 Indicar si se añade información de depuración al resultado de la compilación.
 Definir las constantes de compilación.
 Pedir la generación de datos para permitir la serialización XML.

e. Recursos de un proyecto

Se utilizan los recursos para externalizar ciertos elementos de una aplicación. Permiten
realizar rápidamente modificaciones sencillas en una aplicación sin tener que buscar entre
miles de líneas de código. La utilización más clásica consiste en separar del código las
constantes cadena de caracteres. También puede crear recursos de iconos, imágenes,
archivos de texto o audio. Este cuadro de diálogo gestiona todos los recursos.

Para cada recurso, indique un nombre y un valor. Por supuesto, el nombre será utilizado en
el código para poder recuperar el valor.

En función del tipo de recurso, tiene a su disposición un editor adaptado para modificarlo.
Los recursos pueden ser relacionados o vinculados, en función de su tipo. Un recurso
relacionado está almacenado en su propio archivo y el archivo Resources.resx contiene
simplemente un vínculo hacia el archivo original. Un recurso vinculado está almacenado
directamente en el archivo Resources.resx de la aplicación. En todos los casos, se
compilarán los recursos en el ejecutable de la aplicación.

Veamos ahora cómo acceder a los recursos a partir del código de la aplicación. Todos los
recursos son accesibles a través de la propiedad Resources del objeto My. El ejemplo
siguiente utiliza:

 Un recurso cadena de caracteres (MensajeBienvenidaEs).


 Un recurso icon (IconApli).
 Un recurso imagen bitmap (ImagenFondo).

ShareVideos
 Un archivo de sonido (Musica).

Private Sub Form1_Load(ByVal sender As Object, ByVal e As


System.EventArgs)
Handles Me.Load
Me.Icon = My.Resources.IconAppli
Me.BackgroundImage = My.Resources.ImagenFondo
My.Computer.Audio.Play(My.Resources.Musica,
AudioPlayMode.BackgroundLoop)
MsgBox(My.Resources.MensajeBienvenidaEs)
End Sub

f. Parámetros d

Las variables, constantes y enumeraciones


1. Las variables

Las variables le permitirán almacenar, durante la ejecución de su aplicación, diferentes


valores útiles para el funcionamiento de su aplicación. Se debe declarar una variable
obligatoriamente antes de su uso en el código. Durante la declaración de una variable, se
fijan sus características.

a. Nombre de las variables

Veamos las reglas que se deben respetar para nombrar las variables:

 El nombre de una variable empieza obligatoriamente con una letra.


 Puede estar formado por letras, cifras o el carácter subrayado (_).
 Puede contener un máximo de 1023 caracteres (en la práctica, es preferible limitarse
a un tamaño razonable).
 No se distingue entre mayúsculas y minúsculas (la variable EdadDelCapitan es
equivalente a la edaddelcapitan), aunque otros lenguajes sí hacen esta distinción, y
deberá tener cuidado también si genera un ensamblado utilizado por otro lenguaje.
 Las palabras claves del lenguaje no deben utilizarse (es posible hacerlo, poniendo el
nombre de la variable entre los caracteres [ y ]. Por ejemplo, una variable llamada
next deberá utilizarse en el código de la siguiente manera [next]=56).

b. Tipo de las variables

Al especificar un tipo para una variable, indicamos qué datos vamos a poder almacenar en
esta variable.

Hay dos categorías de tipos de variables disponibles:

 Los tipos por valor: la variable contiene realmente la información.

ShareVideos
 Los tipos por referencia: la variable contiene la dirección de memoria donde se
almacena la información.

Los diferentes tipos de variables disponibles están definidos a nivel del propio
Framework. Puede utilizar igualmente los alias definidos a nivel de VB, posiblemente más
explícitos. El tipo System.Int32 definido a nivel del Framework puede ser sustituido por el
tipo integer en Visual Basic.

Se pueden clasificar los diferentes tipos en seis categorías.

Los tipos numéricos enteros

Tipos enteros con signo En el


mom
8 ento
Sbyte -128 127
bits de
16 elegir
Short -32.768 32.767
bits un
32 tipo
Integer -2.147.483.648 2.147.483.647
bits para
sus
- 64 varia
Long 9.223.372.036.854.775.807
9.223.372.036.854.775.808 bits bles
Tipos enteros sin signo enter
Byte 0 255 8 bits as,
debe
UShort 0 65.535 16 bits tener
UInteger 0 4.294.967.295 32 bits en
ULong 0 18.446.744.073.709.551.615 64 bits cuent
a los
valores mínimo y máximo que piensa almacenar en la variable con el fin de optimizar la
memoria utilizada por la variable. Es inútil utilizar un tipo Long para una variable cuyo
valor no será superior a 50; un tipo Byte es en este caso suficiente.

El ahorro de memoria parece irrisorio para una variable única, pero se vuelve interesante en
el caso de usar matrices de gran dimensión.

Si lo que se desea es optimizar la velocidad de ejecución de su código, es preferible utilizar


el tipo Integer.

Los tipos decimales

4
Single -3.40282347E+38 3.40282347E+38
bytes

ShareVideos
8 Se
Double -1.7976931348623157E+308 1.7976931348623157E+308
bytes deben
tener en
-79.228.162.514.264.337.593. 79.228.162.514.264.337.593. 16
Decimal cuenta
543.950.335 543.950.335 bytes
las
mismas consideraciones de optimización que para las variables enteras. En este caso, la
velocidad de ejecución máxima se obtiene con el tipo Double. El tipo Decimal es más
recomendable para cálculos financieros, en los cuales se prohíben los errores de redondeo,
en detrimento de la velocidad de ejecución del código.

Los tipos de carácter

El tipo Char (carácter) se utiliza para almacenar un único carácter. Una variable de tipo
Char utiliza dos bytes para almacenar el código Unicode del carácter. En el juego de
caracteres Unicode, los primeros 128 caracteres son idénticos al juego de caracteres ASCII,
los caracteres siguientes hasta 255 corresponden a los caracteres especiales del alfabeto
latino (por ejemplo, los caracteres acentuados) el resto se utiliza para símbolos o para los
caracteres de otros alfabetos.

Para poder almacenar cadenas de caracteres, conviene utilizar el tipo String, que representa
una serie de cero a 2.147.483.648 caracteres. Las cadenas de caracteres son invariables ya
que, durante la asignación de un valor a una cadena de caracteres, se reserva algo de
espacio en memoria para el almacenamiento. Si esta variable recibe luego un nuevo valor,
el sistema le asigna una nueva ubicación en memoria. Afortunadamente, este mecanismo es
transparente para nosotros y la variable siempre hará referencia de forma automática al
valor que le está asignado. Con este mecanismo, las cadenas de caracteres pueden tener un
tamaño variable. El espacio ocupado en memoria se ajusta automáticamente a la longitud
de la cadena de caracteres.

Para asignar una cadena de caracteres a una variable, el contenido de la cadena se debe
introducir entre " ", como en el ejemplo siguiente:

Ejemplo

NombreDelCapitan = "Garfio"

Hay muchas funciones que permiten manipular las cadenas de caracteres y serán detalladas
más adelante en este capítulo. Existe un tipo String en forma de clase que también permite
la manipulación de cadenas de caracteres. En este caso, la manipulación se hará mediante
los métodos disponibles en la clase String. Estos también se detallarán más adelante en este
capítulo.

El tipo Booleano

El tipo Booleano permite utilizar una variable que puede tener dos estados: verdadero/falso,
sí/no, on/off.

ShareVideos
Su asignación se realiza directamente con los valores True o False, como en el ejemplo
siguiente:

Disponible=True
Modificable=False

También es posible asignar un valor numérico a una variable de tipo Booleano. En este
caso, un valor igual a cero se considera como un booleano False y cualquier otro valor
positivo o negativo será considerado como un booleano True.

El tipo Date

El tipo Date permite almacenar, en una variable, datos de fecha y hora.

Su asignación se realiza poniendo los valores entre los signos #, como en el ejemplo
siguiente:

Hoy=#02/28/2011 14:58:23#

El formato utilizado para asignar un valor a una variable de tipo Date será siempre
#mes/día/año horas:minutos:segundos# independientemente del formato de fecha
configurado en su sistema operativo. Las horas se pueden especificar en formato de 12
horas o 24 horas. De todas formas, el entorno de desarrollo valida la recogida de datos y
transformará siempre la fecha al formato 12 horas. El ejemplo precedente será modificado
por el entorno de desarrollo de la siguiente manera:

Hoy=#02/28/2005 2:58:23 PM#

Si asigna a una variable de tipo Date un valor con solo la hora, Visual Basic considerará
que se trata del 1 de enero del año 1 a la hora que habrá indicado.

Ejemplo

Día=#12:35:30#

Corresponde al 1 de enero del año 1 a las 12 horas 35 minutos 30 segundos.

Inversamente, ¡si asigna a una variable de tipo Date un valor con solo la fecha, Visual Basic
considerará que se trata del día que ha indicado, a medianoche!

El tipo Object

Este puede ser el tipo más universal de Visual Basic. En una variable de tipo Object, se
puede almacenar cualquier cosa. En realidad, este tipo de variable no almacena nada. La
variable no contendrá el propio valor, sino la dirección, en la memoria de la máquina,
donde se podrá encontrar el valor de la variable. Tranquilícese, todo este mecanismo es
transparente, y nunca tendrá que manipular las direcciones de memoria directamente.

ShareVideos
Una variable de tipo Object podrá hacer referencia, por tanto, a cualquier otro de tipo de
valor, incluidos tipos numéricos simples. Sin embargo, el código será menos rápido en
razón del uso de una referencia.

Los tipos Nullables

A veces ocurre que en algunas circunstancias una variable no tiene un valor bien definido.
Es, por ejemplo, el caso durante la recuperación de información procedente de una base de
datos si, para algunos campos, no se ha asignado ningún valor en la base. ¿Cómo
representar esta situación utilizando las variables de Visual Basic? Una solución consiste en
utilizar un valor que no tiene ningún significado para la aplicación. Por ejemplo, para una
variable numérica que representa un código postal en la aplicación, se puede considerar
asignar a esta variable un valor negativo en caso de que el código no esté indicado. El resto
del código debe, por supuesto, tener en cuenta esta convención. Para cierto tipo de datos,
esta solución no se puede considerar. Pongamos el caso de una variable del tipo Booleano
en la cual solo hay 2 valores permitidos, «true» o «false». ¿Cómo representar que el valor
de la variable no ha sido asignado?

Para encarar este problema, Visual Basic propone los tipos Nullables, que permiten que las
variables de tipo valor no contengan ninguna información. Para activar esta funcionalidad
en una variable solo hace falta utilizar el carácter ’?’ a continuación del nombre de la
variable o al final de la declaración del tipo, como muestra el ejemplo siguiente.

Dim CodigoPostal ? as integer

Dim CodigoPostal as integer?

En cambio, hay que ser prudente durante la utilización de una variable de este tipo y
verificar antes de usarla si contiene realmente un valor. Para ello, puede consultar la
propiedad HasValue de la misma variable con objeto de determinarlo. Si es el caso, este
valor está disponible gracias a la propiedad Value de la variable. Esta propiedad es de solo
lectura, ya que la asignación de un valor se hace directamente en la variable.

CodigoPostal = 17000
If CodigoPostal.HasValue Then
Console.WriteLine(CodigoPostal)
Else
Console.WriteLine(”Código postal vacío”)
End If

Es indispensable probar la propiedad HasValue antes de usar la propiedad value, ya que, si


la variable no contiene ningún valor, se activa una excepción. Es el caso del ejemplo
siguiente, ya que una variable nullable, al contrario que una variable normal, no contiene
ningún valor por defecto.

ShareVideos
Una variable que contiene un valor puede pasar al estado ’no asignada’ si se le asigna el
valor nothing.

El uso de variables del tipo booleano nullable con los operadores lógicos ’and’ y ’or’ es
complicado. A continuación, se muestra la tabla de verdad de estos dos operadores con
variables nullables.

B1 B2 B1 and B2 B1 or B2 Por
último,
nothing nothing nothing nothing cabe
nothing true nothing true aclarar
nothing false false nothing el uso
de un
true nothing nothing true boolean
true true true true o
true false false true nullable
en una
false nothing false nothing
estructu
false true false true ra
false false false false condici
onal.
Observe el código siguiente:

Dim condicion As Booleano?


...
If condicion Then
Console.WriteLine("resultado positivo")
Else
Console.WriteLine("resultado negativo")
End If

La instrucción ’if’ considera que el resultado es positivo únicamente si la variable contiene


el valor ’true’. Para los otros dos casos, valor ’false’ o valor ’no asignado’, el resultado es
negativo y el código del bloque ’else’ se ejecuta.

c. Conversiones de tipos

Las conversiones de tipos consisten en transformar una variable de un tipo a otro tipo. Las
conversiones se pueden efectuar hacia un tipo superior o inferior.

Si se utiliza una conversión hacia un tipo inferior, hay riesgo de pérdida de información.
Por ejemplo, la conversión de un tipo Double hacia un tipo Long causará la pérdida de la
parte decimal del valor.

Ejemplo

ShareVideos
Dim x As Double
Dim y As Long
x = 21.123456789012344
y = x
Console.WriteLine("valor de x: " & x)
Console.WriteLine("valor de y: " & y)

Se muestra:

valor de x: 21,1234567890123
valor de y: 21

Una opción del compilador permite verificar la existencia de este tipo de conversión.

Dependiendo de la configuración de esta opción, si se intenta realizar cierta conversión se


genera un aviso o un error de compilación.

Pueden también ser implícitas o explícitas según el código utilizado. Las conversiones
implícitas se realizan simplemente asignando una variable de un tipo a una variable de otro
tipo. Las conversiones explícitas necesitan el uso de palabras claves concretas, que suele
tener el formato Cxxx o xxx, y que corresponde al tipo en el que se convertirá el valor.

La siguiente tabla resume estos diferentes operadores.

Operador Tipo de destino


CBool Booleano
CByte Byte
CChar Char
CDate Date
CDbl Double
CDec Decimal
CInt Integer
CLng Long
CObj Object
CSByte SByte
CShort Short
CSng Single
CStr String
CUint UInteger
CULng ULong

ShareVideos
CUShort UShort Otro operador (CType) permite la
conversión hacia un tipo estándar de
lenguaje, pero sobre todo hacia un tipo personalizado, como por ejemplo una clase.

Este operador es más general y necesita dos parámetros:

 La variable que se quiere convertir.


 El tipo hacia el que se quiere realizar la conversión.

Si se intenta una conversión restrictiva, la utilización de estos operadores no causa errores


de compilación, ya que el compilador considera que la realiza a conciencia.

Las conversiones desde cadenas de caracteres y hacia cadenas de caracteres son más
específicas.

Conversión hacia una cadena de caracteres

La función format permite elegir el formato del resultado de la conversión de cualquier


valor a una cadena de caracteres. Esta función recibe como argumentos el valor que hay
que convertir y el formato en el que se desea el resultado. Este segundo parámetro tiene
forma de cadena de caracteres y muestra el resultado en el aspecto deseado.

Algunos formatos estándares están predefinidos, pero también es posible personalizar el


resultado de la función format. A continuación se presentan los parámetros de esta función.

Formateo de valores numéricos

Currency

Formato monetario tal como está definido en las opciones regionales y de idioma del panel
de control del sistema.

Ejemplo: format(12.35,"Currency")

Resultado: 12,35 €

Fixed

Utiliza al menos un carácter para la parte entera y al menos dos caracteres para la parte
decimal de un número. El separador decimal aparece tal como está definido en las opciones
regionales y de idioma del panel de control del sistema.

Ejemplo: format(.2,"Fixed")

Resultado: 0,20

ShareVideos
Percent

Multiplica el valor indicado por cien y después añade el símbolo %.

Ejemplo: format(.2,"Percent")

Resultado: 20,00%

Standard

Formato numérico tal como está definido en las opciones regionales y de idioma del panel
de control del sistema.

Ejemplo: format(245813.5862,"Standard")

Resultado: 245.813,59

Scientific

Notación científica con dos cifras significativas.

Ejemplo: format(245813.58,"Scientific")

Resultado: 2,46E+05

Notación científica con seis cifras significativas.

Ejemplo: format(245813.5862,"E")

Resultado: 2,458136E+005

Formato hexadecimal. Utilizable únicamente para los valores enteros.

Ejemplo: format(245813,"X")

Resultado: 3C035

Yes/No

True/False

On/Off

ShareVideos
Devuelve No, False, Off si el valor es igual a cero; si no, devuelve Yes, True, On para el
resto de los valores.

Cadena de formateo personalizada para valores numéricos

Reserva una ubicación para un carácter numérico. Los ceros no significativos se visualizan.

Ejemplo: format(245813.12,"00000000000.0000")

Resultado: 00000245813,1200

Reserva una ubicación para un carácter numérico. Los ceros no significativos no se


visualizan.

Ejemplo: format(245813.12,"###########.####")

Resultado: 245813,12

Reserva una ubicación para el separador decimal. El carácter realmente utilizado en el


resultado depende de la configuración de las opciones regionales y de idioma del panel de
control del sistema.

Reserva una ubicación para el separador de millares. El carácter realmente utilizado en el


resultado depende de la configuración de las opciones regionales y de idioma del panel de
control del sistema.

Permite utilizar un carácter con un significado especial como carácter ordinario en una
cadena de formateo. En el siguiente ejemplo, el carácter \ hace que el carácter # pierda su
significado especial.

Ejemplo: format(325,"comando N\#0000")

Resultado: comando N#0325

Formatos de fecha y hora

ShareVideos
Formato de Fecha corto y formato de Hora tal y como está definido en las opciones
regionales y de idioma del panel de control del sistema.

Ejemplo: format (now,"G")

Resultado 17.10.05 11:10:42

Formato de Fecha largo tal y como está definido en las opciones regionales y de idioma del
panel de control del sistema.

Ejemplo: format (now,"D")

Resultado lunes 17 de octubre de 2005

Formato de Fecha corto tal y como está definido en las opciones regionales y de idioma del
panel de control del sistema.

Ejemplo: format (now,"d")

Resultado 17/10/05

Formato de Hora tal y como está definido en las opciones regionales y de idioma del panel
de control del sistema.

Ejemplo: format (now,"T")

Resultado 11:45:30

Formato ’ordenable’.

Ejemplo: format (now,"s")

Resultado: 2005-10-17T11:47:30

Cadena de formateo personalizado para valores de fecha y hora

d Día del mes sin cero no significativo

dd Día del mes con cero no significativo

ShareVideos
ddd Nombre del día de la semana abreviado

dddd Nombre del día de la semana completo

M Número del mes sin cero no significativo

MM Número del mes con cero no significativo

MMM Nombre del mes abreviado

MMMM Nombre del mes completo

h Hora sin cero no significativo (formato 12H)

hh Hora con cero no significativo (formato 12H)

H Hora sin cero no significativo (formato 24H)

HH Hora con cero no significativo (formato 24H)

m Minuto sin cero no significativo

mm Minuto con cero no significativo

s Segundo sin cero no significativo

ss Segundo con cero no significativo

y Año en una cifra. Si es el único carácter de la cadena de formateo, en este caso se debe
utilizar %y

aayy Año en dos cifras

yyyy Año en cuatro cifras

zzz Diferencia horaria con relación al tiempo universal (GMT)

Conversión de una cadena de caracteres

La función Val permite la conversión de una cadena de caracteres a valor numérico. Lee la
cadena como parámetro hasta que encuentra un carácter que no sea una cifra, un espacio,
una tabulación o un punto. Transforma después esta porción de cadena en valor numérico,
teniendo en cuenta los eventuales parámetros de formateo definidos a nivel del sistema,
como por ejemplo el separador de millares. Los caracteres «&H» o «&O» colocados al
principio de la cadena indican que el valor se expresa en hexadecimal o en octal.

ShareVideos
Ejemplo: val ("&H7FFF")

devuelve

32767.0

d. Declaración de las variables

Por defecto, el compilador Visual Basic considera que toda variable que aparece en una
aplicación debe haber sido declarada. Puede modificar esta opción del compilador
añadiendo la línea siguiente al código:

Option Explicit Off

Esta opción debe ser la primera línea del archivo fuente, y será aplicada a todo el código del
archivo.

Con esta opción, ya no existe la obligación de declarar una variable antes de utilizarla.
Aunque parezca cómoda, esta solución puede producir errores en el código difíciles de
encontrar. Observe el código siguiente:

Function CalcularIVA(ByVal Precio As Single) As Single


Dim ValorTemporal As Single
ValorTemporal = Precio * 1.18
Return ValorTempor
End Function

Ningún problema al compilar, y sin embargo la función nos devuelve siempre un valor
igual a cero. Si nos fijamos mejor en el código, nos damos cuenta de que hay un error en la
línea Return ValorTempor: faltan dos letras en el nombre de la variable ValorTemporal. A
la hora de compilar, Visual Basic considera que se trata de una nueva variable y por lo tanto
la inicializa a cero. Perdido entre cientos de líneas de código, este tipo de errores puede ser
muy difícil de encontrar. Dejando la opción de declaración de variables como obligatoria,
habría detectado el error al compilar la aplicación.

Veamos cómo declarar las variables en Visual Basic. La instrucción básica para la
declaración de una variable es la instrucción Dim.

La sintaxis general de declaración de una variable es la siguiente:

Dim NombreVariable1[,NombreVariable2,NombreVariableN] [As Tipo de


la Variable]
[= Valor inicial]

Los parámetros entre corchetes son opcionales en la declaración. Si se omite el tipo de


variable, se le asignará por defecto el tipo Object que ofrece Visual Basic.

ShareVideos
Si se omite el valor inicial, la variable se inicializará a cero si corresponde a un tipo
numérico, a una cadena de caracteres vacía si es de tipo String, al valor Nothing si es de
tipo Object y a false si es un Booleano.

Si se especifican varios nombres, todas las variables correspondientes serán del tipo
indicado.

e. Inferencia de tipos

Hemos visto en las secciones precedentes que es recomendable declarar las variables antes
de su uso. Sin embargo, en algunos casos, se puede considerar dejar que el compilador
realice una parte del trabajo. Gracias a la inferencia de tipos, el compilador puede
determinar el tipo que se debe utilizar para una variable local declarada sin la cláusula as.
Para ello, se basa en el tipo de la expresión utilizada para inicializar la variable. En el
ejemplo siguiente la variable es considerada como una cadena de caracteres.

Dim nombre = ”Ángel”

Para asegurarse de que esta variable es considerada realmente como una cadena de
caracteres, basta con pedir a IntelliSense qué nos propone para utilizar esta variable.

En efecto, aquí tenemos a nuestra disposición los métodos y propiedades del tipo String.

Para que la inferencia de tipo funcione correctamente, es obligatorio respetar algunas


reglas:

 La inferencia de tipos solo funciona para variables locales, es decir, aquellas


declaradas dentro de un procedimiento o función.
 La inicialización debe hacerse en la misma línea de código que la declaración. En el
ejemplo siguiente la variable se considera de tipo Object.

 La variable no puede ser declarada estática. Si este es el caso, se considerará de tipo


Object.

f. Ámbito de las variables

El ámbito de una variable es la porción de código a partir de la cual se puede manipular


dicha variable. Depende de dónde está situada la declaración y de la palabra clave usada en
la declaración.

ShareVideos
Ámbito a nivel de bloque

Solamente el código del bloque podrá trabajar con la variable (por ejemplo, en un bucle for
next). Sin embargo, si el mismo bloque de código se ejecuta varias veces durante la
ejecución de un procedimiento o función, en caso de un bucle Do Loop por ejemplo, la
variable solo será creada la primera vez que se ejecuta el código, conservando su valor de
una pasada a otra. Para modificar este funcionamiento, basta con inicializar la variable en el
momento de su declaración. Solo puede utilizarse la palabra clave Dim para declarar una
variable dentro de un bloque de código.

Ejemplo

Dim i As Integer
For i = 0 To 5
Dim j As Integer
Dim k As Integer = 0
j = j + 1
k = k + 1
Console.WriteLine("valor de j:{0}", j)
Console.WriteLine("valor de k:{0}", k)
Next
Muestra:
valor de j:1
valor de k:1
valor de j:2
valor de k:1
valor de j:3
valor de k:1
valor de j:4
valor de k:1
valor de j:5
valor de k:1
valor de j:6
valor de k:1

Ámbito a nivel de procedimiento

Solamente el código del procedimiento o de la función donde se declara la variable podrá


modificar su contenido. Este tipo de variable también se llama «variable local».
Únicamente la palabra clave Dim puede utilizarse para declarar una variable dentro de un
procedimiento o función.

Ámbito a nivel de módulo

Este ámbito corresponde a una variable declarada en el exterior de un procedimiento o


función, es decir, en un módulo, en una clase o en una estructura. La declaración puede
aparecer en cualquier sitio del código (siempre que no sea en un procedimiento o función).
Sin embargo, es aconsejable agrupar las declaraciones para facilitar el mantenimiento del
código.

ShareVideos
Ámbito a nivel de espacio de nombres

La variable se podrá utilizar en el código del espacio de nombres incluso si el código está
situado en distintos módulos. Si no declara explícitamente ningún espacio de nombres en su
código, siempre existirá el espacio de nombres por defecto, y se podrá hablar en ese caso de
ámbito de proyecto.

g. Nivel de acceso de las variables

El nivel de acceso de una variable se combina con el alcance de la variable y determina qué
porción de código puede leer y escribir en la variable. Un conjunto de palabras claves
permite controlar el nivel de acceso. Se utilizan en el lugar de la palabra clave Dim a la
hora de la declaración de la variable.

Public

Los elementos declarados con la palabra clave Public serán accesibles desde cualquier parte
del código del proyecto en el que son declarados, y desde cualquier otro proyecto que
referencie el proyecto en el que se han declarado. La palabra clave Public no puede
utilizarse para declarar una variable dentro de un procedimiento o función.

Protected

Esta palabra clave solo se puede utilizar en el interior de una clase. Permite restringir el
acceso a la variable, al código de la clase y al código de todas las clases que heredan de
ella.

Friend

Los elementos declarados con esta palabra clave serán accesibles desde el ensamblado en el
que han sido declaradas. No puede utilizarse esta palabra clave en el interior de un
procedimiento o función.

Protected Friend

Este nivel de acceso es la unión de los niveles de acceso Protected y Friend. Hace visible la
variable al conjunto del ensamblado en el cual está declarada y a todas las clases que
heredan de aquella en la que está declarada.

Private

Esta palabra clave restringe el acceso de la variable al m&oacu

Los operadores

ShareVideos
Los operadores son palabras claves del lenguaje que permiten la ejecución de operaciones
sobre el contenido de ciertos elementos, en general variables, constantes, valores literales o
resultados de funciones. La combinación de uno o varios operadores y elementos en los
cuales los operadores van a apoyarse se llama una expresión. Estas expresiones se valoran
en el momento de su ejecución, en función de los operadores y valores que tienen
asociados.

Los operadores se pueden repartir en seis categorías.

1. Los operadores de asignación

El único operador disponible en esta categoría es el operador =. Permite asignar un valor a


una variable. El mismo operador se utiliza sea cual sea el tipo de la variable (numérica,
cadena de caracteres...).

2. Los operadores aritméticos

Los operadores aritméticos permiten efectuar cálculos en el contenido de las variables:

Operador Operación realizada Ejemplo Resultado


3. Los
+ Suma 6+4 10
opera
- Resta 12-6 6 dores
* Multiplicación 3*4 12 binari
/ División 25/3 8.3333333333 os
\ División entera 25/3 8
Mod Módulo (resto de la división entera) 25 mod 3 1 Estos
operado
ˆ Potencia 5ˆ3 125
res solo
efectúan operaciones sobre enteros (Byte, Short, Integer, Long). Trabajan a nivel de bit
sobre las variables que manipulan.

Operador Operación realizada Ejemplo Resultado


And Y Binario 45 and 255 45
Or O Binario 99 or 46 111
Xor O exclusivo 99 xor 46 77
Not Negación Not 23 -24

ShareVideos
4. Los operadores de comparación

Los operadores de comparación se utilizan en las estructuras de control de una aplicación


(if then, do loop...). Devuelven un valor de tipo booleano en función del resultado de la
comparación efectuada. A continuación este valor será utilizado por la estructura de
control.

Operador Operación realizada Ejemplo Resultado Alguna


informa
= Igualdad 2=5 False ción
<> Desigualdad 2 <> 5 True adicion
< Inferior 2<5 True al
concern
> Superior 2>5 False iente al
<= Inferior o igual 2 <= 5 True operado
>= Superior o igual 2 >= 5 False r like:
Cadenas de caracteres "pepe" like
Like True  C
iguales "p*"
omo el
Comparación de dos True si O1 y O2 hacen operado
Is O1 Is O2
variables objeto referencia al mismo objeto r =, like
Comparación de dos O1 IsNot True si O1 no hace referencia permite
IsNot probar
variables objeto O2 al mismo objeto que O2
la
Comparación del tipo de True si la variable O1
TypeOf ... TypeOf O1 igualda
la variable con el tipo referencia un objeto creado a
Is ... Is Client d de
dado partir del tipo Cliente
dos
cadenas de caracteres, pero permite además la utilización de caracteres comodín.
 El carácter * permite reemplazar cualquier combinación de caracteres. La expresión
apellido like "t*" nos permite comprobar si la variable apellido contiene un valor
que empiece por t y que tenga un número cualquiera de caracteres.
 El carácter ? permite reemplazar un carácter de la expresión que hay que
comprobar. Para comprobar si la variable apellido contiene un valor que empiece
por t y tenga 4 caracteres utilizaremos la siguiente expresión: Apellido like "t???"

Hay que tener cuidado con las comparaciones de cadenas de caracteres, ya que los
resultados obtenidos pueden variar en función de las opciones elegidas en el compilador.
Las opciones del compilador pueden modificarse en el cuadro de diálogo de propiedades
del proyecto.

La propiedad Option Compare permite especificar el método de comparación utilizado por


el compilador.

ShareVideos
 Con la opción Text, el compilador no hará distinción entre minúsculas y
mayúsculas en una comparación.
 La opción Binary exigirá una estricta igualdad para obtener como resultado true.

5. Los operadores de concatenación

Se pueden utilizar dos operadores para la concatenación de cadenas de caracteres: + y &.


Sin embargo, la finalidad del primer operador, +, es la suma de valores numéricos. Su
funcionamiento es bastante complejo al usarlo con cadenas de caracteres. Se aplican las
siguientes reglas:

 Si los dos operadores son numéricos, se suman.


 Si los dos operadores son de tipo String, se concatenan.
 Si un operador es numérico y el otro es de tipo String, entonces entra en juego otro
parámetro. Si el compilador está configurado con la opción Strict On, se genera un
error de compilación. Si el compilador está configurado con la opción Strict Off, la
cadena de caracteres se transforma implícitamente en Double y se suma al otro
valor. Si la cadena de caracteres no puede convertirse en Double, se genera una
excepción.

Para evitar todas estas cuestiones, utilice el operador &. Con este operador, si uno de los
dos operandos no es de tipo String, se transforma automáticamente a este tipo para poder
ser concatenado.

El inconveniente del operador & es que no es muy rápido. Si tiene que realizar muchas
concatenaciones en una cadena, es preferible utilizar la clase StringBuilder.

Ejemplo

Dim duracion, i As Integer


Dim liebre As String
Dim tortuga As String
Dim principio, fin As Date
principio = Now
For i = 0 To 100000
tortuga = tortuga & " " & i
Next
fin = Now
duracion = DateDiff(DateInterval.Second, principio, fin)
Console.WriteLine("duración para la tortuga:
{0} segundos", duracion)
principio = Now
Dim sb As New StringBuilder
For i = 0 To 100000
sb.Append(" ")
sb.Append(i)
Next
liebre = sb.ToString
fin = Now
duracion = DateDiff(DateInterval.Second, principio, fin)

ShareVideos
Console.WriteLine("duración para la liebre:
{0} segundos", duracion)
If liebre.Equals(tortuga) Then
Console.WriteLine("las dos cadenas son idénticas")
End If

Resultado de la carrera:

duración para la tortuga: 107 segundos


duración para la liebre: 0 segundos
las dos cadenas son idénticas.

¡Este resultado no necesita comentario!

6. Los operadores lógicos

Los operadores lógicos permiten combinar las expresiones en estructuras condicionales o


de bucle.

Operador Operación Ejemplo Resultado Convie


ne tener
If (test1) And
and y Lógico verdadero si test1 y test2 son verdaderos cuidado
(test2) con los
If (test1) Or operado
Or O lógico verdadero si test1 o test2 es verdadero
(test2) res
verdadero si test1 o test2 es verdadero, AndAls
O If (test1) Xor oy
xor pero falso si los dos son verdaderos
exclusivo (test2) OrElse,
simultáneamente
ya que
Not Negación If Not test Invierte el resultado del test la
If (test1) Ídem «y lógico» pero test2 solo será expresi
AndAlso y Lógico
AndAlso(test2) valorado si test1 es verdadero ón que
If (test1) OrElse Ídem «o lógico» pero test2 solo será testea
OrElse O lógico en
(test2) valorado si test1 es falso
segund
o lugar (test2 en este caso) no será siempre ejecutada. Si esta segunda expresión modifica
una variable, esta será modificada solo en los siguientes casos:

 El primer test es verdadero en el caso de AndAlso.


 El primer test es falso en el caso de OrElse.

7. Orden de evaluación de los operadores

Cuando varios operadores se combinan en una expresión, son valorados en un orden muy
preciso. Las operaciones aritméticas se ejecutan primero, luego las operaciones de
comparación y finalmente los operadores lógicos.

ShareVideos
Los operadores aritméticos también tienen entre ellos un orden de evaluación en una
expresión. El orden de evaluación es el siguiente:

 Potencia (ˆ)
 Negación (-)
 Multiplicación y división (*, /)
 División entera (\)
 Módulo (Mod)
 Suma y resta (+, -), concatenación de cadenas (+)
 Concatenación de cadenas (&)

Si necesita un orden de evaluación diferente para su expresión, coloque las porciones que
se deben evaluar primero entre paréntesis, como en la siguiente expresión:

X= (z * 4) ˆ (y * (a + 2))

Usted puede utilizar tantos niveles de paréntesis como desee en una expresión. Es
importante, sin embargo, que la expresión contenga tantos paréntesis de cierre como
paréntesis de inicio; de lo contrario el compilador generará un error.

Las estructuras de control


Las estructuras de control permiten modificar el orden de ejecución de las instrucciones en
su código. Hay dos tipos de estructuras disponibles:

 Las estructuras de decisión: encarrilan la ejecución del código en función de los


valores que podrá tener una expresión de test.
 Las estructuras de bucle: harán ejecutar una porción de su código un cierto número
de veces hasta que se cumpla una condición o mientras se cumpla una condición.

1. Estructuras de decisión

Hay dos soluciones posibles:

a. Estructura If

Se pueden utilizar cuatro tipos de sintaxis para la instrucción If.

If condición then instrucción

Si la condición es cierta, la instrucción se ejecuta; en este caso, «condición» debe ser una
expresión que devuelve un booleano true o false. Con esta sintaxis, solo se ejecuta la
instrucción situada tras el then, siempre que la condición sea verdadera.

ShareVideos
Para poder ejecutar varias instrucciones en función de una condición, se debe utilizar la
siguiente sintaxis:

If condición then
Instrucción 1
...
Instrucción n
End if

En este caso, si la condición es verdadera, se ejecutarán todas las instrucciones situadas


entre then y End If.

También puede especificar una o varias instrucciones detrás del Else, que serán ejecutadas
si la condición es falsa.

If condición then
Instrucción 1
...
Instrucción n
Else
Instrucción 1
...
Instrucción n
End if

También puede anidar una estructura dentro de otra:

If condición1 then
Instrucción 1
...
Instrucción n
ElseIf Condición 2 then
Instrucción 1
...
Instrucción n
ElseIf Condición 3 then
Instrucción 1
...
Instrucción n
Else
Instrucción 1
...
Instrucción n
End if

En este caso, comprobamos la primera condición. Si es verdadera, se ejecuta el bloque de


código correspondiente; si no, comprobamos la siguiente, etc. Si no se cumple ninguna
condición, se ejecuta el código que se encuentra tras else. La instrucción else no es
obligatoria en esta estructura. En ese caso, si ninguna de las condiciones se cumple, no se
ejecuta ninguna instrucción.

ShareVideos
b. Estructura Select case

La estructura select case permite un funcionamiento equivalente, pero de una forma más
clara y sencilla. La sintaxis es la siguiente:

Select case variable


Case valor1
Bloque de código 1
Case valor2
Bloque de código 2
Case valor3
Bloque de código 3
Case else
Bloque de código 4
End select

El valor de la variable se evalúa al principio de la estructura (en el Select case). Después, se


compara el valor obtenido con el valor especificado en el primer case (valor1).

Si los dos valores son iguales, se ejecuta el bloque de código 1 y la ejecución del código
sigue tras el End select.

Si no, el valor obtenido se compara con el valor del case siguiente; si hay correspondencia,
el bloque de código se ejecuta y así sucesivamente hasta el último case.

Si ningún valor de los case concuerda con el obtenido inicialmente, se ejecuta el bloque de
código del case else.

El valor que debe comprobarse puede estar contenido en una variable, pero también puede
ser el resultado de un cálculo. En ese caso, el cálculo se efectúa solo una vez, al principio
del select case. El tipo del valor comprobado puede ser numérico o cadena de caracteres.
Por supuesto, el tipo de la variable comprobada debe corresponder al tipo de los valores en
los diferentes case.

Los valores que es preciso comprobar en los distintos case pueden agruparse como en el
siguiente ejemplo:

Select case respuesta


Case "si","SI"
console.writeline("respuesta positiva")
Case "no","NO"
Console.writeline("respuesta negativa")
Case else
Console.writeline("respuesta indefinida")
End select

O también definirse en forma de intervalo o comparación:

Select case hora


Case 8 to 13

ShareVideos
Console.writeline("Mañana")
Case 14 to 17
Console.writeline("Mediodía")
Case 18 to 21
Console.writeline("Tarde")
Case is >=22
Console.writeline("Noche")
Case else
Console.writeline("hora inválida")
End select

También existen tres instrucciones equivalentes al if then else o al select case, pero en una
sola instrucción:

 La instrucción iif permite reemplazar un if then else con la sintaxis siguiente:

iif (condición, valor devuelto si verdadero, valor devuelto si falso)

Ejemplo

resultado = iif (respuesta="si", "YES", "NO")

 La instrucción switch permite indicar un conjunto de expresiones y valores


asociados y devuelve el valor asociado a la primera expresión verdadera.

Ejemplo

resultado=switch(respuesta="si","YES",respuesta="no","NO")

 La instrucción choose permite elegir un valor de una lista de parámetros en función


del valor de un índice. El ejemplo siguiente nos permite elegir el IVA en función de
un código (de 1 a 4).

Ejemplo

tasaIva = Choose(codigo, 0, 4, 10, 21)

2. Las estructuras de bucle

Hay cuatro estructuras a nuestra disposición:

While ... End While


Do ... Loop
For ... Next
For Each ... Next

Todas tienen como objetivo ejecutar un bloque de código un cierto número de veces en
función de una condición.

ShareVideos
a. Estructura While ... End While
While condición
Bloque de código
End While

Esta sintaxis permite ejecutar el bloque de código si la condición es verdadera. Se evalúa la


condición incluso antes del primer paso por el bucle. Por lo tanto, el bloque de código
podrá no ejecutarse nunca si la condición es falsa desde el principio. En caso de que sea
verdadera en la primera pasada, el bloque de código se ejecuta; la condición se evalúa, si es
de nuevo verdadera, se ejecuta el bloque de código; si no, la siguiente instrucción ejecutada
será la que venga después de End While. Se puede forzar una salida «prematura» del bucle
con la instrucción Exit While. La ejecución continúa con la instrucción siguiente al End
While.

b. Estructura Do ... Loop

La estructura Do Loop tiene cuatro variantes:

Do While condición
Bloque de código
Loop

El funcionamiento de esta sintaxis es exactamente igual al de la estructura While End


While.

Do
Bloque de código
Loop While condición

Esta sintaxis nos permite garantizar que el bloque de código se ejecutará al menos una vez
ya que la condición será comprobada al final del bloque de código.

Las instrucciones anteriores realizan un bucle si una condición es verdadera. Las dos
sintaxis siguientes efectúan un bucle mientras la condición sigue siendo verdadera.

Do until condición
Bloque de código
Loop

En este caso, el bucle se ejecuta hasta que la condición deje de ser verdadera. Si es
verdadera desde el principio, el bloque de código no se ejecuta. Para garantizar al menos
una ejecución del bloque de código, conviene utilizar la siguiente sintaxis, que comprueba
la condición al final del bloque de código.

Do
Bloque de código
Loop until condición

ShareVideos
El bloque de código se ejecuta al menos una vez, después comprueba la condición, y vuelve
a empezar mientras la condición se cumpla. Como en el caso del bucle while, la instrucción
Exit Do provocará una salida anticipada e incondicional del bucle.

c. Estructura For ... Next

Cuando conoce el número de iteraciones que se deben realizar en el bucle, es preferible


utilizar la estructura for ... next. Para poder utilizarla, es necesario declarar en el código una
variable de contador para el bucle.

La sintaxis general es la siguiente:

For Contador=valor inicial To valor Final


Bloque de código
Next

Al principio del bucle, la variable Contador se inicializa con el valor inicial, y se ejecuta el
código. La instrucción next incrementa la variable Contador y realiza la comparación del
valor obtenido con el valor Final del bucle. Si la variable contador es inferior o igual al
valor Final, el bloque de código se ejecuta de nuevo; si no, la ejecución sigue con la
instrucción siguiente a next.

Por defecto, el incremento es de uno; se puede especificar un valor para el incremento


añadiendo la palabra clave step y el valor del incremento. Este valor puede ser negativo,
pero en ese caso se debe indicar un valor inicial superior al valor Final.

Como en las otras estructuras, se puede salir inmediatamente del bucle for next utilizando
la instrucción exit for.

d. Estructura For each ... next

Otra sintaxis del bucle for next que permite ejecutar un bloque de código para cada
elemento de una matriz o colección. La sintaxis general de esta instrucción es la siguiente:

For each elemento in matriz


Bloque de código
Next

No hay contador en esta estructura, ya que ella misma efectúa las iteraciones para todos los
elementos de la matriz o colección.

La variable element sirve para extraer los elementos de la matriz o de la colección para que
el bloque pueda manipularlo. El tipo de la variable element debe ser compatible con el tipo
de elementos almacenados en la matriz o la colección. No debe preocuparse por el número

ShareVideos
de elementos, ya que la instrucción for each es capaz de gestionar el desplazamiento en la
matriz o en la colección. A continuación se muestra un ejemplo para aclarar la situación.

Con un bucle clásico:

Dim matriz() As String = {"rojo", "verde", "azul", "blanco"}


Dim cpt As Integer
For cpt = 0 To UBound(matriz)
Console.WriteLine(matriz(cont))
Next

Con el bucle for each:

Dim matriz() As String = {"rojo", "verde", "azul", "blanco"}


Dim s As String
For Each s In matriz
Console.WriteLine(s)
Next

Como en el caso del bucle for next, puede provocar una salida del bucle antes de haber
recorrido toda la matriz utilizando la instrucción exit for.

e. Otras estructuras

Hay otras dos estructuras disponibles, destinadas más bien a simplificar el desarrollo:

Estructura Using End Using

Esta estructura engloba un bloque de código que utiliza un recurso externo, como por
ejemplo una conexión a un servidor de base de datos. Esta estructura se encarga
automáticamente de liberar el recurso al final del bloque de código. El recurso se puede
crear en la estructura o bien existir previamente y pasarse bajo el control de la estructura.
Al final de la estructura, el recurso se libera llamando al método Dispose.

Ejemplo

Dim ctn As New .SqlConnection


ctn = New SqlConnection("Data Source=TG;Initial Catalog=Northwind;
Integrated Security=True")
Using ctn
...
’ Uso de la conexión
...
End Using
’ La conexión se libera

Estructura With End With

ShareVideos
Esta estructura permite ejecutar una serie de operaciones sobre un objeto sin tener que usar
cada vez su nombre. Facilita mucho la claridad del código y mejora también el rendimiento.

Basta especificar, con la ayuda de la palabra clave With, el nombre de la variable que
queremos usar, y hasta la palabra clave End With el nombre estará sobreentendido. Los
diferentes elementos del objeto están disponibles sin usar el nombre de la variable como
prefijo.

Ejemplo

Dim ctn As New SqlConnection


ctn = New SqlConnection("Data Source=TG;Initial Catalog=Northwind;
Integrated Security=True")
With ctn
.Open()
’....
’....
.Close()
End With

Los procedimientos y funciones


En una aplicación Visual Basic, todas las instrucciones deben ir obligatoriamente dentro de
un procedimiento o de una función. Estos procedimientos o funciones nos permiten crear
bloques de código que luego podrán ser llamados en otras partes de la aplicación. La
llamada al procedimiento o función se hace simplemente utilizando el identificador del
procedimiento o función.

Para que los procedimientos sean reutilizables más fácilmente, tiene la posibilidad de usar
parámetros. Los valores de estos parámetros se especificarán en el momento de la llamada
al procedimiento.

Durante el desarrollo, no dude en crear distintos procedimientos y funciones. La división de


su aplicación en muchos procedimientos y funciones facilitará la depuración (es más fácil
comprobar 10 bloques de código de 15 líneas cada uno que un «tocho» de 150 líneas).
Además, ciertos procedimientos pueden utilizarse varias veces en la aplicación.

 Los procedimientos Sub, que simplemente ejecutan un bloque de código al ser


llamados sin devolver un resultado.
 Los procedimientos de evento, que son los que son llamados automáticamente
cuando se produce un evento durante el funcionamiento de la aplicación (clic del
ratón, pulsación de una tecla...).
 Las funciones que ejecutan un bloque de código y devuelven el resultado de su
cálculo al código que las llamó.
 Los procedimientos property, que permiten manipular las propiedades de los objetos
creados en la aplicación.

ShareVideos
 Los procedimientos operador, utilizados para modificar el funcionamiento de un
operador cuando se aplica a una clase o una estructura.

Veamos ahora cómo declarar procedimientos y funciones.

1. Procedimiento Sub

El código de un procedimiento debe estar situado entre las palabras claves Sub y End Sub.
Se debe nombrar el procedimiento. Se utilizará este nombre durante la llamada. La sintaxis
general de declaración es la siguiente:

Sub MuestraResultado()
Instrucción 1
...
console.System.Writeline( "¡¡¡Funciona!!!") ")
...
Instrucción n
End Sub

Los paréntesis después del nombre se utilizan para especificar las características de los
parámetros que se pasarán durante la llamada. Los paréntesis son obligatorios en la
declaración incluso si no se requiere ningún parámetro para el procedimiento.

Hay muchas palabras claves que se pueden utilizar en la declaración de un procedimiento


para modificar las posibilidades de reutilización del procedimiento. La mayoría de ellas
están vinculadas a la programación orientada a objetos y se estudiarán en otro capítulo.
Para modificar su visibilidad, puede utilizar las palabras claves que ya hemos usado para la
declaración de variables (Private, Public, Friend). Sin especificación, se considerará un
procedimiento público.

Para pedir la ejecución de su procedimiento en el código, basta con especificar su nombre.


Otra manera sería usando la palabra clave Call. La sintaxis es la siguiente:

Call MuestraResultado()

Si su procedimiento no espera parámetros, la utilización de los paréntesis es opcional (el


entorno de desarrollo los añadirá de todas formas).

2. Procedimiento de evento

Dos elementos distinguen un procedimiento clásico de un procedimiento de evento:

 La llamada al procedimiento se realiza automáticamente cuando el evento generado


por el procedimiento se produce en la aplicación.
 El nombre del procedimiento es la concatenación del nombre del elemento para el
que se gestiona el evento, el carácter subrayado (_) y el nombre del evento

ShareVideos
gestionado. Por ejemplo, el siguiente procedimiento se ejecutará cuando se
produzca el evento click en el botón BpOK.

Sub BpOk_Click(ByVal sender As Object, ByVal e As System.EventArgs)


Handles
Button1.Click

La palabra clave Handles indica el objeto y el evento para los cuales se ejecutará el
procedimiento.

3. Función

Una función se declara según el mismo principio que un procedimiento: utilizando las
palabras claves Function y End Function. En cambio, se debe proporcionar un dato
adicional. Como la función debe devolver un valor al código que la llama, debe indicar el
tipo de este valor. La sintaxis de declaración es, por tanto, la siguiente:

Function calculo() As Integer


Instrucción 1
...
...
Instrucción n
End Function

En el código de su función, deberá especificar qué valor será devuelto por su función. Para
ello, hay dos opciones:

 La utilización de la palabra clave Return indicando el valor que quiere devolver. En


este caso, Return provoca inmediatamente la salida de la función, aunque no sea la
última instrucción.
 También puede utilizar el nombre de la función como variable y asignarle el valor
de retorno de la función. En este caso, la ejecución de la función continúa hasta la
última instrucción.

A continuación se puede utilizar una función en el código en lugar de un valor del mismo
tipo que el devuelto por la función. Puede igualmente utilizarse como un procedimiento
Sub; en ese caso simplemente ignoramos el valor devuelto.

Console.WriteLine(calculo())
calculo()

4. Procedimientos Property

Los procedimientos Property permiten agregar una propiedad a una clase, un módulo o una
estructura. Estos procedimientos se denominan, a veces, «accesores». Se utilizan cuando se
modifica (Set) o se utiliza (Get) la propiedad. Su uso parece similar al uso de una variable;

ShareVideos
se puede asignar un valor a una propiedad o leer el valor de una propiedad. Sin embargo,
existen diferencias abundantes e importantes entre las variables y las propiedades:

 Las variables necesitan una sola línea de código para la declaración, mientras que
las propiedades requieren un bloque de código para la declaración.
 El acceso a una variable se efectúa directamente, mientras que el acceso a una
propiedad implica la ejecución de un fragmento de código.
 El contenido de una variable siempre se recupera tal cual, mientras que el contenido
de una propiedad se puede modificar con el código durante el acceso a la propiedad.

La sintaxis general de creación de una propiedad es la siguiente:

Property nombrePropiedad () as tipoDeLaPropiedad


Get
...
Return ...
End Get

Set
...
End Set (...)
End Property

En esta declaración:

 nombrePropiedad corresponde al nombre por el que se puede manipular la


propiedad en el código.
 TipoDeLaPropiedad corresponde al tipo de datos asociado a la propiedad. Puede
utilizar cualquier tipo de datos para una propiedad (los tipos básicos del lenguaje o
un tipo personalizado, como por ejemplo una clase).
 El bloque situado entre Get y End Get contiene el código ejecutado cuando se lee la
propiedad.
 El bloque situado entre Set y End Set contiene el código ejecutado cuando se
asigna la propiedad. El parámetro se usa, como en el caso de un procedimiento
clásico, para pasar información al bloque de código.

Como para cualquier otro elemento declarado en Visual Basic, puede indicar un nivel de
acceso para la propiedad. Se aplica tanto al bloque Get como al Set. También puede añadir
un modificador del nivel de acceso para cada uno de los bloques Get y Set. En este caso,
deben ser más restrictivos que el indicado a nivel de la propiedad.

Las propiedades también pueden ser de solo lectura o de solo escritura. Para ello, debe
añadir ReadOnly o WriteOnly delante del nombre de la propiedad, eliminando, claro está,
el bloque de Set en el caso de una propiedad de solo lectura, o el bloque Get en el caso de
una propiedad de solo escritura.

ShareVideos
5. Los procedimientos operador

Este tipo de procedimiento permite redefinir un operador estándar del lenguaje para
utilizarlo en tipos personalizados (clase o estructura). Tomemos un ejemplo con la
estructura cliente ya utilizada.

Public Structure Cliente


Public Codigo As Integer
Public Apellidos As String
Public Nombre As String
End Structure

Probamos el siguiente código:

Visiblemente, el compilador no es cooperativo con la idea de sumar dos clientes.

Para que este código funcione, debemos indicarle el procedimiento que es preciso seguir
para realizar esta operación. Por lo tanto, debemos redefinir el operador + para utilizarlo
con dos clientes.

Public Structure Cliente


Public Codigo As Integer
Public Apellidos As String
Public Nombre As String
Public Shared Operator +(ByVal c1 As Cliente, ByVal c2 As
Cliente) As Cliente
Dim c As Cliente
c.Codigo = c1.Codigo + c2.Codigo
c.Apellidos = c1.Apellidos & c2.Apellidos
c.Nombre = c1.Nombre & c2.Nombre
Return c
End Operator
End Structure

Después de esta modificación, el compilador se muestra más cooperativo y la ejecución del


procedimiento anterior, test, muestra el siguiente resultado:

325
cliente1cliente2
nombre1nombre2

6. Los argumentos de los procedimientos y funciones

Para que el código sea reutilizable más fácilmente, los valores manipulados por los
procedimientos y funciones pueden pasarse como parámetros en el momento de la llamada
del procedimiento o función. Durante la declaración del procedimiento, deberá especificar
la lista de los parámetros que se esperará. Esta lista se sitúa entre los paréntesis de la

ShareVideos
declaración del procedimiento. Debe indicar, para cada parámetro, su nombre y su tipo. Si
se esperan varios parámetros, conviene separarlos con una coma.

En el código del procedimiento, los parámetros se consideran como variables declaradas


localmente.

Cuando se llama al procedimiento, se deberá indicar un valor para cada uno de los
parámetros esperados. Tomemos un ejemplo de declaración y de utilización:

Function CalculoIVA(PBruto as double, iva as double) as double


CalculoPNeto = PBruto * (1 + (iva / 100))
End function

PBruto = 100
PNeto = CalculoPNeto (PBruto,8)
Console .Writeline (PNeto)

Para pasar una variable como parámetro a un procedimiento (el PBruto del ejemplo
anterior), existen dos posibilidades:

 El paso por valor: en este caso, la información transmitida al procedimiento


simplemente será el contenido de la variable pasada como parámetro. El paso por
valor es la opción por defecto en Visual Basic.
 El paso por referencia: en este caso, la información transmitida al procedimiento ya
no es el contenido de la variable, sino la ubicación donde está almacenada la
variable, en la memoria de la máquina. El código del procedimiento va a buscar el
valor que necesita en esta ubicación. El código del procedimiento puede también
modificar el contenido de la variable y, en este caso, las modificaciones serán
visibles en el código que ha llamado al procedimiento. Para utilizar este modo de
paso, tiene que emplear la palabra clave ByRef delante del parámetro en la
declaración del procedimiento.

Aunque no sea necesario, la palabra clave ByVal también está disponible para
indicar un paso por valor.

Puede indicar en la lista de parámetros de un procedimiento que algunos de ellos


serán opcionales poniendo la palabra clave optional delante del nombre del
parámetro. Los parámetros opcionales deben respetar ciertas reglas:

o Para cada parámetro opcional, debe establecerse en la declaración del


procedimiento un valor por defecto.
o Function CalculoPNeto(PBruto as double, Optional IVA as
double = 21)
o as double

ShareVideos
o Cuando se declara un parámetro opcional, todos los declarados a
continuación deben ser también opcionales. La declaración siguiente no es
válida, ya que el tercer parámetro debe ser opcional:
o Function CalculoPNeto(PBruto as double, Optional IVA as
double =
o 21 divisa as string) as double

La sintaxis es la siguiente:

Function CalculoPNeto(PBruto as double, Optional IVA as double =


21, optional divisa as string = "Ç") as double

Cuando se llama al procedimiento, hay dos posibilidades para indicar el valor utilizado por
cada parámetro:

 El pase por posición: los valores de los parámetros deben aparecer en el mismo
orden que han sido declarados en el procedimiento. Si quiere omitir un valor para
un parámetro, debe reservar su sitio en la llamada del procedimiento.

Resultado=calculoPNeto(250,,"$")

 El paso por nombre: en este caso, se debe indicar en la llamada el nombre de cada
parámetro y su valor. El orden de los parámetros no es importante, pero está
obligado a indicar un valor para los parámetros no opcionales.

Resultado=calculoPNeto(divisa := "$",PBruto := 250)

Otra posibilidad permite crear un procedimiento que podrá recibir un número cualquiera de
parámetros. Indique la palabra clave ParamArray para declarar una matriz de parámetros.

En el siguiente ejemplo, vamos a crear una función que calcula la media de todos los
parámetros que recibe.

Function media(ByVal ParamArray notas() As Double) As Double


Dim nota As Double
Dim suma As Double
For Each nota In notas
suma = suma + nota
Next
media = suma / notas.Length
End Function

Luego se puede llamar la función con un número cualquiera de parámetros.

Resultado=media(1,6,23,45)

Resultado=media(12,78)

ShareVideos
7. Funciones asíncronas

Las funciones asíncronas permiten mejorar la reactividad de una aplicación. Es frecuente


tener que realizar operaciones relativamente largas en una aplicación. Con un modelo de
desarrollo clásico, el conjunto de modificaciones se bloquea hasta que terminan las
operaciones, Esta situación afecta al usuario, ya que este no sabe realmente lo que hace la
aplicación. Si desea parar la aplicación durante este periodo de bloqueo, la única opción
que tiene es utilizar el administrador de tareas de Windows. Para evitar esta situación, ahora
es posible definir funciones asíncronas. La palabra clave async añadida a la firma de una
función provoca su ejecución de manera asíncrona. Para que este mecanismo sea realmente
eficaz, además es necesario indicar, dentro de este tipo de funciones, al menos una
ubicación donde la ejecución de la función se podrá suspender a la espera de que termine
una operación. La palabra clave await delante de una expresión indica estos puntos de
interrupción. Cuando la operación termina, se evalúa la expresión y se retoma la ejecución
de la función. Para que este mecanismo funcione, es necesario que la expresión genere un
tipo Task<...>.

Veamos algunos ejemplos prácticos. Partimos de una función que comprueba si un número
es primo o no:

Public Function esPrimo (nb As Integer) As Boolean


If nb < 2 Then
Return False
End If
If nb = 2 Then
Return True
End If
If nb Mod 2 = 0 Then

Return False
End If
Dim i As Integer
i = 3
Do While (i * i <= nb)
If nb Mod i = 0 Then
Return False
Else
i = i + 1
End If
Loop
Return True
End Function

Esta función no tiene nada de especial, excepto que puede llegar a necesitar mucho tiempo
de ejecución si se le pasa como argumento un entero con un valor muy elevado. Este
fenómeno se amplificará, con toda seguridad, si se llama varias veces. Es lo que vamos a
resaltar con la siguiente aplicación, que permite buscar los números primos entre 0 y un
valor concreto.

ShareVideos
Vamos a añadir una nueva función que cuente cuántos números primos hay entre 0 y el
valor que recibe como argumento.

Public Function contarPrimos(maxi As Integer) As Integer


Dim i As Integer
Dim nb As Integer
nb = 0
For i = 0 To maxi
If esPrimo(i) Then
nb = nb + 1
End If
next
Return nb
End Function

Solo falta añadir un método Main para terminar nuestra aplicación.

Public Sub Main()


Console.Write("Indique el valor máximo para el cálculo: ")
Dim maxi As Integer
maxi = Console.ReadLine()
Dim resultado As Integer
resultado = contarPrimos(maxi)
Console.WriteLine(resultado)
Console.ReadKey()
End Sub

Ahora comprobemos que esto funciona correctamente.

No hay ningún problema y obtenemos rápidamente el resultado. Sin embargo, si lo


intentamos con un límite mucho más elevado (100.000.000 por ejemplo), habrá que ser
muy paciente porque los cálculos serán muy largos. Si no tiene paciencia para esperar el
resultado, debe salir de la aplicación cerrando la consola directamente o a través del
administrador de tareas de Windows. Esta solución no es muy limpia. Podemos mejorarla
proponiendo al usuario una manera para finalizar la ejecución de la aplicación.

Para esto, podemos añadir en nuestro método Main un simple bucle Do While en el que
realizamos nuestro tratamiento y solicitamos al usuario que introduzca un carácter ’s’ para
terminar la aplicación.

Public Sub Main()


Console.Write("Indique el valor máximo para el cálculo: ")
Dim maxi As Integer
maxi = Console.ReadLine()
Dim resultado As Integer
Dim respuesta As Char
Do
resultado = contarPrimos (maxi)
Console.WriteLine(resultado)
reponse = Console.ReadKey().KeyChar

ShareVideos
Loop While (respuesta <> "s")
End Sub

Durante la prueba no hay ninguna mejora. De hecho, las instrucciones presentes en el bucle
do while se ejecutan de manera secuencial y en este caso la instrucción respuesta =
Console.ReadKey().KeyChar solo se ejecutará después de haber mostrado el resultado, es
decir, la final del cálculo. La función contarPrimos sigue estando bloqueada, no hay nada
que se pueda ejecutar en la aplicación hasta que termine. La solución consiste en
transformar la función contarPrimos en una función asíncrona. Para ello, basta con añadir la
palabra clave async en su firma.

public static async int contarPrimos(int maxi)

Añadir esta palabra clave implica otra modificación en la firma.

Por tanto, es necesario modificar la función para que devuelva un tipo Task(Of Integer).

Public Async Function contarPrimos(maxi As Integer) As Task(Of Integer)

Ahora nuestra función se puede convertir en una función asíncrona, pero todavía no lo es.
Esto es lo que nos indica Visual Studio.

La operación que hay que realizar en la función asíncrona se debe pasar como argumento,
como una expresión lambda, al método Run de la clase Task. Es el resultado de la
ejecución de esta expresión lambda la que se utiliza como valor de retorno de la función
asíncrona.

Public Async Function contarPrimos (maxi As Integer) As Task(Of


Integer)
Dim resultado As Integer
resultado = Await Task.Run(Of Integer)(Function()

Dim i As Integer
Dim nb As Integer
nb = 0
For i = 0 To maxi
If esPrimo (i)
Then
nb = nb + 1
End If
Next
Return nb
End Function)
Return resultado
End Function

ShareVideos
La penúltima etapa consiste en usar esta función de manera asíncrona. Para esto vamos a
crear una función nueva que va a llamar a la función contarPrimos. Para que la función
contarPrimos se ejecute de manera asíncrona, tenemos que utilizar la palabra clave await
durante su llamada.

Async Function operacion(maxi As Integer) As Task(Of Integer)


Dim resultado As Integer
resultado = Await contarPrimos (maxi)
Console.WriteLine(resultado)
Console.WriteLine("*****************")
End Function

Para terminar, modificamos la función Main para interrumpir el cálculo.

Public Sub Main()


Dim maxi As Integer
Console.Write("Indique el valor máximo para el cálculo: ")
maxi = Console.ReadLine()
operacion(maxi)
Console.WriteLine("pulse un tecla para detener la aplicación
antes de terminar el cálculo ")
Console.ReadKey()
End Sub

Visual Studio nos indica una anomalía durante la llamada de la función operación.

Este warning no tiene consecuencias para el funcionamiento de nuestra aplicación y además


es el objetivo de todas estas operaciones.

Ensamblados, espacios de nombres y


atributos
1. Los ensamblados

Visual Basic está concebido en torno al Framework .NET, lo que le permite beneficiarse de
numerosas ventajas, sobre todo en cuando a seguridad, en el momento de la ejecución y de
la gestión de memoria. Esta relación también permite asegurar la compatibilidad entre el
código escrito en los distintos lenguajes disponibles. Así es posible utilizar en Visual Basic
elementos creados en otros lenguajes (y al revés), de manera totalmente transparente, sin
tener que preocuparse del lenguaje con el que se ha desarrollado el elemento.

El elemento básico de esta reutilización en el Framework .NET es el ensamblado. Se puede


considerar como la agrupación de tipos, recursos y funcionalidades diseñados para
funcionar juntos.

ShareVideos
Los ensamblados se almacenan en archivos .exe o .dll según el tipo. Se generan
simplemente al compilar el proyecto correspondiente.

Son autodescriptivos, ya que contienen los datos necesarios para su utilización en otro
proyecto. Estos datos están contenidos en el manifiesto del ensamblado. El manifiesto
contiene entre otras cosas:

 La identidad del ensamblado (su nombre y su versión).


 Una lista de archivos utilizados por el ensamblado (por ejemplo, los otros
ensamblados utilizados por este, los recursos bitmap, etc.).

Para poder utilizar un ensamblado en un proyecto, añada simplemente una referencia hacia
el ensamblado. Para ello, utilice el menú contextual de la carpeta de referencia del
proyecto.

El siguiente cuadro de diálogo permite entonces elegir las referencias que deseamos añadir
al proyecto.

Las diferentes opciones permiten elegir, según la categoría, el tipo de referencia que
queremos agregar al proyecto:

Ensamblados

El conjunto de los componentes del Framework .NET y de sus extensiones disponibles.

Solución

Los otros proyectos de la solución actual.

COM

Los componentes COM y ActiveX registrados en el sistema.

Examinar

Búsqueda de un archivo (dll, ocx...) que contiene los recursos.

Reciente

Muestra las referencias añadidas recientemente.

ShareVideos
Es posible añadir varias referencias de manera simultánea utilizando la tecla [Ctrl] durante
la selección en este cuadro de diálogo.

Después de haber realizado estas dos operaciones, los recursos presentes en el ensamblado
son accesibles directamente en el código del proyecto.

2. Los espacios de nombres

Los espacios de nombres organizan de manera lógica los objetos disponibles en un


ensamblado. Se utilizan para resolver las ambigüedades cuando, en un proyecto, se añaden
referencias en unos ensamblados que contienen elementos con nombres idénticos.

Por ejemplo, la clase ListBox existe en los ensamblados System.Web y


System.Windows.Forms. Si se añaden referencias en un proyecto hacia estos ensamblados,
el compilador se arriesga a no poder determinar cuál de estas clases desea realmente
utilizar.

La utilización del nombre plenamente cualificado, incluyendo el espacio de nombres en el


cual se define la clase, permite resolver este tipo de problema.

Puede utilizar, por ejemplo, el siguiente código:

Ejemplo

Dim listaWindows As System.Windows.Forms.ListBox


Dim listaWeb As System.Web.UI.WebControls.ListBox.

Sin embargo, la utilización del nombre plenamente cualificado puede hacer pesada la
escritura del código. Se puede utilizar la palabra clave imports para aligerar el código.
Indica al compilador que ciertos espacios de nombres se sobreentienden.

Por ejemplo, la instrucción Imports System.Data.SqlClient autoriza la utilización de la


siguiente declaración: Dim ctn As SqlConnection, que sin la importación del espacio de
nombres habría provocado un error de compilación:

Las instrucciones Imports deben ser las primeras líneas de código en un archivo fuente
Visual Basic.

Sin embargo, esté atento para no volver a caer en el problema anterior.

La instrucción Imports propone una solución elegante creando un alias durante la


importación del espacio de nombres.

ShareVideos
Imports ctrlWin = System.Windows.Forms
Imports ctrlWeb = System.Web.UI.WebControls
Module TestConta
Dim listaWindows As ctrlWin.ListBox
Dim listaWeb As ctrlWeb.ListBox

Esta solución autoriza la utilización de nombres de una longitud razonable evitando los
conflictos.

Cabe observar también que, según el tipo de proyecto en el que está trabajando, se realizan
referencias e importaciones por defecto.

Puede repasar estas referencias y modificarlas si fuera necesario visualizando las


propiedades del proyecto mediante el menú contextual disponible en el explorador de
soluciones.

Las importaciones por defecto serán activadas en cada módulo de su proyecto.

Los espacios de nombres se declararán en el código con la ayuda de las palabras claves
Namespace y End Namespace.

Todos los elementos declarados entre estas dos palabras claves serán accesibles utilizando
el nombre del espacio de nombres como prefijo.

Namespace Facturacion
Public Class Tarificacion
Public Shared Function calculoPNeto(PBruto As Decimal, tasaIva As
Decimal)
As Decimal
Return PBruto * (1 + tasaIva / 100)
End Function
End Class
End Namespace

En el ejemplo anterior, la función calculoPNeto definida en la clase Tarificacion es


accesible al darle un prefijo con el nombre del espacio de nombres. El nombre del espacio
de nombres raíz especificado deberá agregarse a nivel de las propiedades del proyecto,
separándolo de los otros nombres de espacio de nombres por un punto.

En nuestro ejemplo, la función calculoPneto es accesible con el siguiente código:

Sub Main()
precioNeto = Conta.Facturacion.Tarificacion.calculoPNeto(100, 8)
End Sub

ShareVideos
Utilice la misma técnica en el caso de un espacio de nombres anidados, como en el
siguiente ejemplo:

Namespace Gestion
Namespace Pago
Public Class Sueldo
....
End Class
End Namespace
Namespace Facturacion
Public Class Factura
....
End Class
End Namespace

End Namespace

La clase Sueldo será accesible con el nombre Conta.Gestion.Pago.Sueldo.

3. Los atributos

Los atributos son marcas que puede colocar en su código con el fin de añadir datos
adicionales a los elementos de su aplicación.

Se guardan en los metadatos del ensamblado durante la compilación del proyecto. El


runtime utiliza los metadatos para gestionar la depuración, el seguimiento de las versiones,
la compilación y otros datos relativos a la utilización de su código. Los atributos pueden
aplicarse a un ensamblado, un módulo o una porción de código más pequeña, como un
procedimiento o una función. A veces podrán aceptar argumentos para modificar su
significado.

Los atributos se sitúan en el código entre los símbolos < y >, como las etiquetas HTML. Si
se utilizan varios atributos, deben ir separados con comas. Los posibles parámetros de un
atributo estarán ubicados entre paréntesis.

El alcance de un atributo también puede extenderse con las palabras claves Assembly: o
Module: ubicadas antes del atributo. La sintaxis de utilización de un atributo es, por lo
tanto:

<[Assembly o Module:] Atributo1([parámetro1,parámetro2]),Atributo2

a. Los atributos más comunes en Visual Basic

Entre los atributos disponibles, algunos se usan con mucha frecuencia en el desarrollo con
Visual Basic. Vamos a estudiar su utilización e ilustrarla con un ejemplo.

VBFixedStringAttribute

ShareVideos
Este atributo fuerza la creación de una variable de tipo cadena de caracteres de tamaño fijo.
Se utiliza en la declaración de una estructura, usada para guardar en un archivo líneas de
tamaño constante.

Ejemplo

Structure Cliente
<VBFixedString(15)> public nombre as String
<VBFixedString(15)> public apellidos as String
End Structure

ComClassAttribute

Este atributo pide al compilador la generación de código adicional para hacer compatible
una clase con el modelo COM. De esta manera, la clase será compatible con antiguos
lenguajes de programación incompatibles con la plataforma .NET.

Ejemplo

<ComClass()> Public Class Impuestos

Public Sub New()


MyBase.New()
End Sub

Function CalculoImpuestos(ByVal sueldo As Integer)


Return sueldo * 0.3
End Function
End Class

Para compilar este código, es necesario activar la opción a nivel de propiedades del
proyecto Registrar para Com Interop.

Después de la compilación, la clase puede utilizarse en un lenguaje compatible COM,


referenciando la biblioteca DLL generada.

La figura siguiente muestra el uso desde VB 6.0.

SerializableAttribute, NonSerializedAttribute

Estos dos atributos controlan la serialización de una clase y de sus miembros. La


serialización permite el registro de una instancia de clase en un archivo asegurando así la
persistencia de los datos. El archivo generado puede estar en formato binario o XML; en
este caso, facilita el intercambio de datos entre aplicaciones. Para que una clase sea
utilizable por el mecanismo de serialización, esta debe marcarse con el atributo
SerializableAttribute. Durante la operación de serialización, el contenido de cada uno de los

ShareVideos
miembros de la instancia de la clase se guarda en el archivo. Si algunos de ellos no deben
guardarse en el archivo, se deben marcar con el atributo NonSerializedAttribute.

El ejemplo siguiente define la clase Persona con dos miembros (Apellidos y Nombre) que
serán serializados y un miembro (Edad) que no será serializado. Se crea una instancia de la
clase y se guarda en un archivo de formato XML.

Ejemplo

Imports System
Imports System.IO
Imports System.Runtime.Serialization
Imports System.Runtime.Serialization.Formatters.Soap

<Serializable()> Public Class Persona


Public Nombre As String
Public Apellidos As String
<NonSerialized()> Public Edad As Integer
Public Sub New()
End Sub
End Class
Module test
Public Sub Main()
Dim UnaPersona As New Persona()
UnaPersona.Nombre = "Pablo"
UnaPersona.Apellidos = "García"
UnaPersona.Edad = 25
Dim stream As Stream = File.Open("datos.xml", FileMode.Create)
Dim formatear As New SoapFormatter()
formatear.Serialize(stream, UnaPersona)
stream.Close()
End Sub
End Module

La ejecución de este código genera el siguiente archivo XML:

<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:
xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-
ENC="http://schemas.xmlsoap.
org/soap/encoding/" xmlns:SOAP-
ENV="http://schemas.xmlsoap.org/soap/envelope
" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-
ENV:
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:Persona id="ref-1"
xmlns:a1="http://schemas.microsoft.com/clr/nsassem/
serializable/serializable%2C%20Version%3D1.0.0.0%2C%20Culture%
3Dneutral%2C%20
PublicKeyToken%3Dnull">
<Nombre id="ref-3">Pablo</Nombre>
<Apellidos id="ref-4">García</Apellidos>
</a1:Persona>

ShareVideos
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Encontramos en el archivo nuestra instancia de la clase Persona con sus dos miembros,
Nombre y Apellidos, y, tal y como habíamos indicado en la definición de la clase, el
miembro Edad no se ha guardado.

DllImportAttribute

Se utiliza este atributo para indicar que una función se importa desde una biblioteca de
código no gestionado. Permite en particular la utilización de funciones definidas en una
biblioteca del sistema. En el siguiente ejemplo, la función MoveFile se puede utilizar como
una función clásica.

Ejemplo

<DllImport("KERNEL32.DLL")>Public Function MoveFile(ByVal src As


String,
ByVal dst As String) As Boolean
End Function

Obsolete

Se puede utilizar este atributo para indicar que un elemento, clase, método o propiedad ya
no se utiliza. Si a pesar de todo se utiliza este elemento en una aplicación, el compilador
genera un aviso o un error en función de la configuración del atributo. Una cadena de
caracteres puede ser pasada como parámetro a este atributo para representar el mensaje
visualizado por el compilador. Un segundo parámetro de tipo booleano permite especificar
si la utilización del elemento marcado con este atributo genera un aviso o un error de
compilación.

<Obsolete("Este método ya no se utiliza", False)>


Public Sub Guardar()
Dim unaPersona As Persona
unaPersona = New Persona()
unaPersona.apellido = "García"
unaPersona.elnombre = "Pablo"
unaPersona.edad = 25
Dim flux As Stream
flux = File.Open("c:\datos.xml", FileMode.Create)
Dim formador As SoapFormatter
formador = New SoapFormatter()
formador.Serialize(flux, unaPersona)
flux.Close()
End Sub

La utilización de este método en una aplicación provoca el siguiente aviso durante la


compilación.

ShareVideos
Si este atributo viene definido con un segundo parámetro igual a True, el compilador activa
un error cuando se utiliza el elemento.

<Obsolete("Esta propiedad no debe ser utilizada más", True)>


Property Apellido As String
Get
Return _Apellido
End Get
Set(ByVal value As String)
_Apellido = value
End Set
End Property

Introducción
Con Visual Basic .NET, la noción de objeto es omnipresente y requiere un mínimo de
aprendizaje. Vamos a ver primero el principio de la programación orientada a objetos y el
vocabulario asociado, luego veremos cómo ponerlo en aplicación con Visual Basic.

En un lenguaje de procedimiento clásico, el funcionamiento de una aplicación está


organizado por una sucesión de llamadas a los diferentes procedimientos y funciones
disponibles en el código. No hay ningún vínculo entre los datos y los procedimientos que
los manipulan. Al contrario, en este lenguaje orientado a objetos, vamos intentar agrupar al
máximo los datos y el código para manipularlos. Las clases son la representación simbólica
de los objetos. Describen los campos, propiedades, métodos y eventos de la misma manera
que el plano de un arquitecto describe las diferentes partes de un edificio.

Prosigamos nuestra analogía entre una clase y un plano de un edificio. Sabemos que es
posible construir varios edificios a partir del mismo plano. De la misma manera, varios
objetos pueden ser construidos a partir de la misma clase. Se puede utilizar una clase para
crear tantas instancias como sea necesario.

En el plano de un edificio, algunas zonas pueden tener un acceso limitado a ciertas


personas. De la misma manera, en una clase, ciertos elementos pueden tener un acceso
restringido. Es el principio de la encapsulación.

Los términos clase y objeto a menudo se confunden, pero se trata en realidad de elementos
muy distintos. Una clase representa la estructura de un elemento, mientras que el objeto es
un ejemplar creado a partir de la plantilla de esta estructura. La modificación de un
elemento en un objeto no cambia en absoluto los otros objetos creados a partir de la misma
plantilla (clase). En nuestro ejemplo de plano de un edificio, el hecho de añadir una nueva
habitación a un edificio existente no cambia para nada los otros edificios construidos según
el mismo plano. Por el contrario, la modificación del plano (de la clase) conlleva
modificaciones en todos los nuevos edificios (todos los nuevos objetos).

ShareVideos
Las clases están constituidas por campos, propiedades, métodos y eventos. Los campos y
las propiedades representan los datos contenidos en los objetos. Los campos se consideran
como variables, y es posible leer su contenido o directamente asignarles un valor. Por
ejemplo, si tiene una clase que representa un cliente, puede guardar su nombre en un
campo.

Las propiedades se manipulan de la misma manera que los campos, pero se activan desde
los procedimientos de propiedad Get y Set. Esto otorga más control sobre cómo leer o
asignar valores y permite la validación de los datos antes de su utilización.

Los métodos representan las acciones que un objeto puede asignar. Se aplican creando
procedimientos o funciones en una clase.

Los eventos son datos que un objeto recibe o transmite desde o hacia otro objeto o
aplicación. Los eventos permiten a los objetos ejecutar acciones cuando una situación
particular se verifica. Como Windows es un sistema operativo de eventos, los eventos
pueden proceder de otros objetos, del sistema o de las acciones del usuario sobre el ratón y
el teclado.

Esto es solo una faceta de la programación orientada a objetos. También son fundamentales
otros tres elementos:

 La encapsulación.
 La herencia.
 El polimorfismo.

La encapsulación es la capacidad de crear y controlar el acceso a un grupo de elementos.


Las clases facilitan el medio más fiable para asegurar la encapsulación. Si tomamos el
ejemplo de una cuenta bancaria, en una programación clásica necesitaremos muchas
variables y procedimientos o funciones para manipular los datos. La situación sería aun más
compleja si tuviéramos que gestionar de manera simultánea varias cuentas bancarias.
Habría que trabajar entonces con matrices y hacer malabarismos con los índices. La
encapsulación permite agrupar los datos y el código manipulándolos en una clase. Si debe
trabajar con varias cuentas bancarias de manera simultánea, entonces tendrá varias
instancias de la misma clase, limitando así el riesgo de errores. La encapsulación asegura
también un control sobre la utilización de datos y procedimientos o funciones. Puede
utilizar los modificadores de acceso, tales como Private o Protected, para restringir el
acceso a ciertos métodos, propiedades o campos. Una regla fundamental de la
encapsulación estipula que los datos de una clase solo deben ser manipulados por el código
de la clase (procedimientos de propiedades o métodos). A veces esta técnica se llama
disimulación de datos. Asegura la seguridad de funcionamiento de su código al enmascarar
los detalles internos de la clase, evitando así que se utilicen de manera inadecuada. Autoriza
también la modificación de una parte del código sin alterar el funcionamiento del resto de
la aplicación.

La herencia permite la creación de una nueva clase, basada en una clase existente. La clase
que sirve de plantilla para la creación de otra clase se llama clase base. La clase así creada

ShareVideos
hereda de los campos, propiedades, métodos y eventos de la clase base. La nueva clase
puede personalizarse al agregarle campos, propiedades, métodos y eventos. Las clases
creadas a partir de una clase base se llaman clases derivadas. Así se puede definir una clase
base y reutilizarla varias veces para crear clases derivadas.

El polimorfismo es otra noción importante de la programación orientada a objetos. Gracias


a ello, es posible utilizar varias clases de manera intercambiable incluso si estas clases
implementan sus propiedades y métodos de manera diferente. Estas propiedades y métodos
se pueden usar con el mismo nombre, sin importar la clase a partir de la cual se ha
construido el objeto.

Existen otros tres conceptos igualmente asociados al polimorfismo. La sobrecarga, la


sustitución y la ocultación de miembros permiten la definición de miembros de una clase
con el mismo nombre. Sin embargo, existen algunas diferencias entre estas tres técnicas.

Se utiliza la sobrecarga para diseñar propiedades o métodos que llevan el mismo nombre
pero que tienen un número de parámetros diferentes o tipos de parámetros diferentes.

La sustitución permite la redefinición de métodos o propiedades heredadas de una clase


base. Los miembros sustituidos pueden aceptar el mismo número y tipo de parámetros que
el método o propiedad de la clase base.

La ocultación sirve para sustituir localmente, en una clase, un miembro de una clase.
Cualquier tipo de miembro puede ocultar otro miembro. Por ejemplo, una propiedad puede
ocultar un método heredado. La ocultación se hace únicamente a través del nombre. Los
miembros ocultados no son heredables.

Los tipos genéricos


Los tipos genéricos son elementos de un programa que se adaptan automáticamente para
realizar la misma función en diferentes tipos de datos. Cuando crea un elemento genérico,
no necesita diseñar una versión diferente para cada tipo de datos con el que desea realizar
una función.

Para realizar una analogía con un objeto corriente, vamos a tomar el ejemplo de un
destornillador. En función del tipo de tornillo que se vaya a utilizar, puede coger un
destornillador específico para este tipo de tornillo (plano, de cruz, de estrella, etc.). Una
técnica a menudo utilizada por un manitas experto consiste en adquirir un destornillador
universal, con múltiples cabezas. En función del tipo de tornillo, elige la cabeza adecuada.
El resultado final es el mismo que si dispone de varios destornilladores: puede atornillar y
desatornillar.

Cuando se utiliza un tipo genérico, se configura con un tipo de datos. Esto permite al
código adaptarse automáticamente y realizar la misma acción independientemente del tipo

ShareVideos
de datos. Una alternativa podría ser la utilización del tipo universal Object. La utilización
de los tipos genéricos presenta varias ventajas respeto a esta solución:

 Impone la verificación de los tipos de datos en el momento de la compilación y


evita las verificaciones que deben efectuarse manualmente con la utilización del
tipo Object.
 Evita las operaciones de conversión del tipo Object hacia un tipo más específico y
que, además, consuman recursos.
 Evita la utilización del enlace tardío, ineludible con el tipo Object.
 El entorno de desarrollo facilita la escritura del código gracias a IntelliSense.
 Favorece la escritura de algoritmos independientes de los tipos de datos.

Los tipos genéricos pueden, sin embargo, imponer ciertas restricciones relativas al tipo de
datos utilizado. Por ejemplo, pueden imponer que el tipo utilizado implemente una o varias
interfaces, que sea un tipo de referencia o que posea un constructor por defecto.

Es importante entender correctamente algunos términos utilizados con los genéricos:

El tipo genérico

Es la definición de una clase, estructura, interfaz o procedimiento para el que se puede


especificar al menos un tipo de datos en el momento de su declaración.

El tipo parámetro

Es la ubicación reservada para el tipo de datos en la declaración del tipo genérico.

El tipo argumento

Es el tipo de datos que sustituye el tipo de parámetro durante la construcción de un tipo a


partir de un tipo genérico.

Las limitaciones

Son las condiciones que usted impone y que limitan el tipo argumento que puede facilitar.

El tipo construido

Es la clase, interfaz, estructura o procedimiento declarado a partir de un tipo genérico para


el que ha especificado unos tipos de argumento.

1. Las clases genéricas

Una clase que espera un tipo de parámetro se llama clase genérica. Puede generar una clase
construida facilitando a la clase genérica un tipo argumento para cada uno de estos tipos
parámetro.

ShareVideos
a. Definición de una clase genérica

Puede definir una clase genérica, que facilita las mismas funcionalidades sobre diferentes
tipos de datos. Para ello, debe facilitar uno o varios tipos de parámetro en la definición de la
clase. Tomemos el ejemplo de una clase, capaz de gestionar una lista de elementos con las
siguientes funcionalidades:

 Agregar un elemento.
 Suprimir un elemento.
 Desplazarse hasta el primer elemento.
 Desplazarse hasta el último elemento.
 Desplazarse hasta el elemento siguiente.
 Desplazarse hasta el elemento anterior.
 Obtener el número de elementos.

Primero debemos definir la clase como una clase ordinaria.

Public Class ListaGenerica

End Class

La transformación de esta clase en clase genérica se efectúa añadiendo un tipo de parámetro


inmediatamente después del nombre de la clase.

Public Class ListaGenerica(Of tipo)

End Class

Si son necesarios varios tipos de parámetro, se deben separar con comas sin repetir la
palabra clave Of.

Si su código debe realizar operaciones que no sean asignaciones, debe añadir limitaciones
al tipo de parámetro. Para hacerlo, añada la palabra clave As seguida por la limitación.

Si es necesario aplicar varias limitaciones sobre el parámetro, deben especificarse entre


llaves.

Las limitaciones siguientes están disponibles:

structure

Esta restricción impone que el tipo parámetro sea un tipo por valor, y no un tipo referencia.
Además este tipo no debe ser Nullable.

Public Class ListaGenerica(Of tipo As Structure)

End Class

ShareVideos
class

Esta limitación impone que el tipo parámetro sea un tipo referencia: clase, interfaz, matriz o
delegado.

Public Class ListaGenerica(Of tipo As Class)

End Class

New

Esta limitación impone la presencia de un constructor public y sin parámetro en el tipo


parámetro. También se utiliza con otras limitaciones, como por ejemplo la limitación Class.
Las limitaciones deben ir en la lista separadas con comas.

Public Class ListaGenerica(Of tipo As {Class, New})

End Class

nombre de una clase

Esta limitación exige que el tipo parámetro sea la clase indicada o una de sus subclases.

Public Class ListaGenerica(Of tipo As Persona)

End Class

interfaz1, interfaz2...

Esta limitación exige que el tipo parámetro implemente la interfaz o las interfaces
indicadas.

Public Class ListaGenerica(Of tipo As {IComparable


Ienumerable})

End Class

Si no se especifican limitaciones, las únicas operaciones autorizadas serán las soportadas


por el tipo Object.

En el código de la clase, cada miembro que deba ser del tipo de parámetro debe definirse
con la sintaxis As tipo, en nuestro caso. Veamos ahora el código completo de la clase.

Public Class ListaGenerica(Of tipo)


’ matriz para almacenar los elementos de la lista
Private lista() As tipo
’ puntero de posición en la lista

ShareVideos
Private position = 0
’ puntero para agregar un nuevo elemento
Private elementoSiguiente = 0
’número de elementos de la lista
Private numElementos = 0
’ dimensión de la lista
Private tamaño As Integer
’ indica si la lista está llena
Private completa As Boolean = False
’ constructor con un parámetro que permita dimensionar la lista
Public Sub New(ByVal tamaño As Integer)
lista = New tipo(tamaño - 1) {}
MyClass.tamaño = tamaño
End Sub
Public Sub agregar(ByVal elemento As tipo)
’ verificamos si la lista está completa antes
’ de agregar un elemento
If Not completa Then
lista(elementoSiguiente) = elemento
numElementos = numElementos + 1
completa = (numElementos = tamaño)
’ si la lista no está completa, colocamos el puntero
’ para agregar el elemento siguiente
If Not completa Then
elementoSiguiente = elementoSiguiente + 1
End If
Else
Beep()
End If
End Sub
Public Sub suprime(ByVal índice As Integer)
Dim i As Integer
’ verificamos si el índice no es superior al número de elementos
’ si el índice no es inferior a 0
If índice >= numElementos OrElse índice < 0 Then
Beep()
Exit Sub
End If
’ desplazamos los elementos una posición hacia arriba
For i = indice To numElementos - 2
lista(i) = lista(i + 1)
Next
’ colocamos el puntero para agregar un nuevo elemento
elementoSiguiente = elementoSiguiente - 1
’ actualizamos el número de elementos
numElementos = numElementos - 1
End Sub
Public ReadOnly Property tamañoLista() As Integer
Get
Return numElementos
End Get
End Property

Public Function primero() As tipo


If numElementos = 0 Then
Err.Raise(1000, , "lista vacía")
End If

ShareVideos
’ desplazamos el puntero hacia el primer elemento
posicion = 0
Return lista(0)
End Function
Public Function último() As tipo
If numElementos = 0 Then
Err.Raise(1000, , "lista vacía")
End If
’ desplazamos el puntero hacia el último elemento
posicion = numElementos - 1
Return lista(position)
End Function
Public Function siguiente() As tipo
If numElementos = 0 Then
Err.Raise(1000, , "lista vacía")
End If
’ verificamos que no estamos al final de la lista
If posicion = numElementos - 1 Then
Beep()
Err.Raise(1000, , "ningún elemento siguiente")
Exit Function
End If
’ desplazamos el puntero hasta el elemento siguiente
posicion = posicion + 1
Return lista(posicion)
End Function
Public Function anterior() As tipo
If numElementos = 0 Then
Err.Raise(1000, , "lista vacía")
End If
’ verificamos que no estamos en el primer elemento
If posicion = 0 Then
Beep()
Err.Raise(1000, , "ningún elemento anterior")
Exit Function
End If
’ nos desplazamos hasta el elemento anterior
posicion = posicion - 1
Return lista(posicion)
End Function
End Class

b. Utilización de una clase genérica

Para poder utilizar una clase genérica, ante todo debe generar una clase construida que
proporcione un tipo argumento para cada uno de sus tipos parámetro. Entonces puede
instanciar la clase construida por uno de los constructores disponibles. Vamos a utilizar la
clase diseñada anteriormente para trabajar con una lista de enteros.

Dim lista As New ListaGenerica(Of Integer)(5)

Esta declaración permite instanciar una lista de cinco enteros. Los métodos de la clase
entonces están disponibles.

ShareVideos
lista.Añadir(10)
lista.Añadir(11)

Por supuesto, el compilador comprueba que utilizamos nuestra clase correctamente, en


particular verificando los tipos de datos que le confiamos.

A continuación tenemos el código de una pequeña aplicación que permite probar el


funcionamiento correcto de nuestra clase genérica:

Module testGenerica
Dim lista As New ListaGenerica(Of Integer)(5)
Public Sub main()
lista.Añadir(10)
lista.Añadir(11)
lista.Añadir(12)
lista.Añadir(13)
lista.Añadir(14)
lista.Añadir(15)
menu()
End Sub
Public Sub menu()
Dim elige As Char
On Error GoTo gestionerror
Console.SetCursorPosition(1, 24)
Console.WriteLine("p (primero) < (anterior) >(siguiente) d
(ultimo) f
(fin)")
While elige <> "f"
elige = Console.ReadLine()
Console.Clear()
Console.SetCursorPosition(1, 1)
Select Case elige
Case "p"
Console.WriteLine("el primero {0}", lista.primero())
Case "<"
Console.WriteLine("el anterior {0}",
lista.anterior())
Case ">"
Console.WriteLine("el siguiente {0}",
lista.siguiente())
Case "d"
Console.WriteLine("el último {0}", lista.ultimo())
End Select
Console.SetCursorPosition(1, 24)
Console.WriteLine("p (primero) < (anterior) >(siguiente) d
(último) f (fin)")
End While
Exit Sub
gestionerror:
Console.ForegroundColor = ConsoleColor.Red
Console.WriteLine(Err.Description)
Console.ResetColor()
Resume Next

ShareVideos
End Sub
End Module

También podemos verificar que nuestra clase funciona sin problema si le pedimos trabajar
con cadenas de caracteres.

Public Sub main()


lista.Añadir("primero")
lista.Añadir("segundo")
lista.Añadir("tercero")
lista.Añadir("cuarto")
lista.Añadir("quinto")
menu()
End Sub

2. Interfaces genéricas

De manera totalmente similar a lo que acabamos de ver respecto a las clases genéricas,
también es posible diseñar interfaces genéricas. Utilizan las mismas técnicas de diseño que
las clases genéricas.

a. Definición de una interfaz genérica

La definición de una interfaz genérica es similar en todo punto a la declaración de una


interfaz normal, excepto por el hecho de que se debe especificar al menos un tipo parámetro
después del nombre de la interfaz. La interfaz Comparable definida anteriormente puede
tomar por tanto la forma siguiente:

Public Interfaz ComparableGenerica(Of tipo)


Function compare(ByVal o1 As tipo) As Integer
End Interfaz

El tipo parámetro se puede utilizar en la firma de los métodos exigidos por la interfaz.

b. Utilización de una interfaz genérica

Como ocurre con una interfaz normal, una interfaz genérica debe ser implementada por una
clase. Durante la declaración de la clase, el tipo o los tipos parámetros deben ser sustituidos
por uno o varios tipos argumento.

La utilización de nuestra interfaz genérica puede tomar la forma siguiente:

Public Class Persona


Implements ComparableGenerica(Of Persona)

End Class

El compilador exige ahora que el método o los métodos descritos en la interfaz estén
realmente disponibles en la clase.

ShareVideos
También hay que observar que el compilador haya tenido en cuenta el tipo argumento
utilizado para la declaración de la clase, ya que nos reclama la presencia de una función
llamada compare y espera como parámetro un objeto de tipo Persona (el tipo argumento
especificado en el momento de la declaración de la clase).

De hecho, se puede simplificar mucho el código de la función compare respecto al de la


versión no genérica de la clase, ya que ya no necesita efectuar una operación de conversión
explícita antes de utilizar el parámetro recibido por la función.

Public Function compare(ByVal o1 As Persona) As Integer


Implements ComparableGenerica(Of Persona).compare
Return o1._apellido.CompareTo(_apellido)

End Function

3. Procedimientos y funciones genéricos

Los procedimientos o funciones genéricos son métodos definidos con al menos un tipo
parámetro. Esto permite al código que los llama especificar el tipo de datos que necesita en
cada llamada del procedimiento o función. Sin embargo, se puede utilizar este método sin
indicar información para el tipo argumento. En este caso, el compilador intenta determinar
el tipo en función de los argumentos pasados al método. Esta solución se debe utilizar con
precaución, ya que, si el compilador no puede determinar el tipo de los argumentos, genera
un error de compilación.

a. Creación de un procedimiento o función genérico

La declaración de un procedimiento o función genérico debe contener al menos un tipo


parámetro. Este tipo parámetro se define utilizando la palabra clave Of seguida por un
identificador. A continuación se utiliza este identificador en el resto del código cada vez
que necesite utilizar el tipo parámetro.

Vamos a crear una función genérica capaz de buscar un elemento particular en una matriz
de cualquier tipo. Esta función va a utilizar un tipo parámetro que indica la naturaleza de
los elementos presentes en la matriz. Para poder buscar un elemento en la matriz,
deberemos compararlo con los presentes en todas sus casillas. Para garantizar que esta
comparación será posible, añadimos una limitación en el tipo parámetro: debe implementar
la interfaz Icomparable con el fin de asegurar que el método CompareTo utilizado en la
función esté disponible para cada elemento de la matriz.

La declaración de la función toma la forma siguiente:

Public function busquedaGenerica(Of tipo As Icomparable) (byval


matriz As tipo(), elementoBuscado As tipo) As Integer

ShareVideos
Después de haber comprobado que la matriz contiene al menos un elemento, debemos
comparar el elemento buscado con aquel presente en cada casilla de la matriz Si hay
igualdad, la función devuelve el índice donde se ha encontrado el elemento; si no, la
función devuelve -1. Para efectuar la comparación, utilizaremos la función CompareTo de
cada elemento de la matriz.

Public Function busquedaGenerica(Of tipo As Icomparable)(ByVal matriz


As tipo(), ByVal elementoBuscado As tipo) As Integer
’comprobar si la matriz tiene más de una dimensión
If matriz.Rank() > 1 Then
Return -1
End If
’ comprobamos si la matriz está vacía
If matriz.Length = 0 Then
Return -1
End If
For i As Integer = 0 To matriz.GetUpperBound(0)
If matriz(i).CompareTo(elementoBuscado) = 0 Then
Return i
End If
Next Return -1
End Function

b. Utilización de un procedimiento o función genérico

La utilización de un procedimiento o función genérico es idéntica a la de un procedimiento


o función clásico, salvo que es necesario de especificar un tipo argumento para los tipos
parámetro.

El siguiente código permite probar el correcto funcionamiento de nuestra función.

Public Sub main()


Dim t() As Integer = {12, 45, 85, 47, 62, 95, 81}
Dim resultado As Integer
resultado = busquedaGenerica(Of Integer)(t, 47)
If resultado = -1 Then
Console.WriteLine("valor no encontrado")
Else
Console.WriteLine("valor encontrado en la posición {0}",
resultado)
End If
Console.ReadLine()
Dim s() As String = {"uno", "dos", "tres", "cuatro", "cinco"}
resultado = busquedaGenerica(Of String)(s, "seis")
If resultado = -1 Then
Console.WriteLine("valor no encontrado")
Else
Console.WriteLine("valor encontrado en la posición {0}",
resultado)
End If
Console.ReadLine()
End Sub

ShareVideos
Delegados genéricos
Como cualquier otro elemento, un delegado puede definir unos tipos parámetro en su
declaración. Cuando se utiliza el delegado, hay que facilitar unos tipos argumento para cada
uno de sus tipos parámetro. El siguiente extracto de código declara un delegado genérico.

public Delegate Function comparacion(Of tipo)(ByVal


p1 As tipo, ByVal p2 As tipo) As Integer

A continuación se puede utilizar este delegado en la declaración de un método que facilite


un tipo argumento para cada uno de sus tipos parámetro.

Public Sub tri(ByVal matriz() As Cliente, ByVal comparador As


comparacion(Of Cliente))
Dim i As Integer
Dim j As Integer
Dim c As Cliente
For i = 0 To UBound(matriz) - 1
For j = i + 1 To Ubound(matriz)
If comparador.Invoke(matriz(j), matriz(i)) < 0 Then
c = matriz(j)
matriz(j) = matriz(i)
matriz(i) = c
End If
Next
Next
End Sub

Para poder llamar esta función de ordenación, hay que facilitarle como primer parámetro
una matriz de clientes y una función que respete la firma del delegado como segundo
parámetro.

Function comparaCodigo(ByVal c1 As Cliente, ByVal c2 As Cliente) As


Integer
Select Case c1.codigo
Case Is < c2.codigo
Return -1
Case c2.codigo
Return 0
Case Else
Return 1
End Select
End Function
Dim del As comparador(Of Cliente)
del = New comparador(Of Cliente)(AddressOf comparaCodigo)
ordenacion(matriz, del)

El compilador verifica que la firma de la función sea compatible con la definición del
delegado.

ShareVideos
Varianza
En programación orientada a objetos, la varianza designa el hecho de utilizar un tipo de
objetos que no se corresponde exactamente con el esperado. Sin embargo, hay una pequeña
restricción, ya que el tipo utilizado y el tipo esperado deben formar parte de la misma
jerarquía de clase. Así, el tipo utilizado puede ser un supertipo del tipo esperado o un
subtipo del tipo esperado. Si el tipo utilizado es un supertipo del tipo esperado (tipo menos
derivado), hablamos de contravarianza. Si el tipo utilizado es un subtipo del tipo esperado
(tipo menos derivado), hablamos de covarianza. Tomemos el ejemplo de una clase Persona
y una de sus subclases, la clase Cliente. La covarianza consiste en utilizar la clase Cliente
donde se espera la clase Persona. La contravarianza es el trámite inverso, ya que consiste en
utilizar la clase Persona donde se espera la clase Cliente. Las interfaces genéricas y los
delegados genéricos se encargan de estos dos mecanismos a continuación. Vamos a detallar
estas dos nociones.

1. Varianza en las interfaces genéricas

Para ilustrar todo esto, utilizaremos las dos clases definidas a continuación:

Public Class Persona

Protected elApellido As String


Protected elNombre As String
Protected laFecha_naci As DateTime
Protected laContraseña As String

Public Sub New()


elApellido = ""
elNombre = ""
laContraseña = ""
End Sub

Public Sub New(ByVal apellido As String, ByVal nombre As String,


ByVal pwd As String)
elApellido = apellido
elNombre = nombre
laContraseña = pwd
End Sub

Public Sub New(ByVal apellido As String, ByVal nombre As String,


ByVal fNaci As DateTime)
elApellido = apellido
elNombre = nombre
laFecha_naci = fNaci
End Sub
Public Property apellido As String
Get
Return elApellido
End Get
Set(ByVal value As String)

ShareVideos
elApellido = value.ToUpper()
End Set
End Property

Public Property nombre As String


Get
Return elnombre
End Get
Set(ByVal value As String)
elnombre = value.ToLower
End Set
End Property
Public Property fecha_naci As DateTime
Get
Return laFecha_naci
End Get
Set(ByVal value As DateTime)

End Set
End Property
Public ReadOnly Property age As Integer
Get
Return DateTime.Now.Year - laFecha_naci.Year
End Get
End Property
Public WriteOnly Property contraseña As String
Set(ByVal value As String)
laContraseña = value
End Set
End Property
End Class
Public Class Cliente
Inherits Persona
Private elNumero As Integer
Public Property numero As Integer
Get
Return elNumero
End Get
Set(ByVal value As Integer)
elNumero = value
End Set
End Property
End Class

Contravarianza en las interfaces genéricas

Las dos clases definidas anteriormente se completan ahora con la siguiente interfaz
genérica.

Interface ComparadorGenerico(Of tipo)


Function compare(ByVal o1 As tipo, ByVal o2 As
tipo) As Integer
End Interface

ShareVideos
Las clases que implementan esta interfaz deberán contener al menos el método compare.
Ahora vamos a crear dos clases capaces de comparar Personas o Clientes al implementar la
interfaz ComparadorGenerico con la clase Persona o Cliente como tipo argumento. La
comparación de las personas se hará según el apellido y la comparación de los clientes
según el número.

Public Class ComparadorPersona


Implements ComparadorGenerico(Of Persona)

Public Function compare(ByVal p1 As Persona, ByVal p2 As


Persona) As Integer Implements ComparadorGenerico(Of
ConsolaAplicacion1.Persona).compare
Return p1.apellido.CompareTo(p2.apellido)
End Function
End Class
Public Function compare(ByVal c1 As Cliente, ByVal c2 As Cliente) As
Integer Implements ComparadorGenerico(Of
ConsolaAplicacion1.Cliente).compare
If c1.numero > c2.numero Then
Return -1
ElseIf c1.numero < c2.numero Then
Return 1
Else
Return 0
End If
End Function

Nuestra última etapa consiste en crear un método utilizando nuestra interfaz genérica como
parámetro. Para ello, añadimos la siguiente función, que verifica la igualdad de dos clientes
en función del comparador que se le pasa como primer argumento.

Public Sub verifIgualdad(ByVal c As ComparadorGenerico(Of


Cliente), ByVal c1 As Cliente, ByVal c2 As Cliente)

If c.compare(c1, c2) = 0 Then


Console.WriteLine("los dos son idénticos")
Else
Console.WriteLine("los dos son diferentes")
End If

End Sub

Solo nos queda probar esto al crear dos instancias de la clase Cliente y al intentar
compararlas con el número de cliente como criterio. Para ello, utilizaremos una instancia de
la clase ComparadorCliente.

Dim c1, c2 As Cliente


c1 = New Cliente()
c1.numero = 10
c1.apellido = "garcía"
c2 = New Cliente()
c2.numero = 10
c2.apellido = "garcía"

ShareVideos
verifIgualdad(New ComparadorCliente(), c1, c2)

Nuestro código funciona correctamente, ya que obtenemos el siguiente mensaje en la


consola.

los dos clientes son idénticos

Si ahora queremos comparar nuestros dos clientes según su apellido, en lugar de hacerlo
según su número, podemos utilizar la clase ComparadorPersona, ya que el método
compare, definido en esta clase, espera como parámetros dos instancias de la clase Persona;
por tanto, si le facilitamos dos instancias de la clase Cliente, funcionará de la misma
manera: nuestras instancias de la clase Cliente disponen de un apellido debido a la relación
de herencia con la clase Persona. Sin embargo, el compilador no tiene la misma opinión
que nosotros y detecta un error.

En realidad, la función verifIgualdad espera como primer parámetro un delegado capaz de


trabajar con Clientes y, en cambio, le facilitamos únicamente un delegado capaz de trabajar
con Personas. Utilizamos la clase Persona donde se espera la clase Cliente, por tanto
estamos en presencia de una contravarianza. Para que el compilador la acepte, es
indispensable añadir la palabra clave in en la declaración de la interfaz genérica.

Interface ComparadorGenerico(Of tipo)


Function compare(ByVal o1 As tipo, ByVal o2
As tipo) As Integer
End Interface

Sin embargo, solo puede declarar un tipo como contravariante en una interfaz o un
delegado genérico, si se utiliza como tipo de argumento de método. En ningún caso se
puede utilizar como tipo de retorno de un método.

Si modificamos nuestra interfaz agregando un método que utilice el tipo contravariante


como tipo de retorno, obtenemos un error de compilación.

Covarianza en las interfaces genéricas

Para ilustrar la covarianza en las interfaces genéricas, vamos a crear una nueva interfaz que
defina el método creacionInstancia. Este método deberá devolver, en las clases que
implementarán esta interfaz, una instancia del tipo argumento utilizado durante la
implementación de la interfaz.

Public Interfaz IFabrica1(Of tipo)

Function CrearInstancia() As tipo

ShareVideos
End Interface

A continuación esta interfaz se implementa con la siguiente clase.

Public Class Fabrica(Of tipo As New)


Implements IFabrica(Of tipo)
Public Function CrearInstancia() As tipo Implements
IFabrica(Of tipo).CrearInstancia
Return New tipo()
End Function
End Class

En esta clase, hay que observar que hemos añadido una restricción sobre el tipo parámetro
para estar seguros de que la clase utilizada como tipo argumento dispone correctamente de
un constructor por defecto.

Ahora podemos crear una instancia de esta clase y utilizarla para producir instancias de la
clase Persona.

Dim fPersona As IFabrica(Of Persona)


fPersona = New Fabrica(Of Persona)()
Dim p As Persona
p = fPersona.CrearInstancia()

Este código se compila sin error y nos permite obtener correctamente instancias de la clase
Persona. Si modificamos este código para crear un objeto Fabrica de Cliente, obtenemos un
error de compilación.

Efectivamente, intentamos asignar a una variable de tipo IFabrica<Persona> una instancia


de IFabrica<Cliente>. En realidad, estamos utilizando la covarianza al especificar un tipo
más derivado que el esperado. Para que el compilador acepte esta situación, hay que utilizar
la palabra clave out en la declaración de la interfaz genérica.

Public Interfaz IFabrica1(Of Out tipo)

Function CrearInstancia() As tipo

End Interface

Sin embargo, esta técnica comporta una limitación, ya que el tipo declarado covariante solo
se puede utilizar como tipo de retorno de una función. Si se utiliza como tipo para un
parámetro de método, el compilador activa un error.

ShareVideos
2. Varianza en los delegados genéricos

Como las interfaces genéricas, los delegados genéricos se encargan de la contravarianza y


covarianza. Así es posible utilizar para un delegado genérico un tipo más derivado o un tipo
menos derivado que el esperado. Las limitaciones son las mismas que para las interfaces
genéricas, ya que un tipo menos derivado solo se puede utilizar como parámetro de un
delegado genérico y un tipo más derivado que el esperado solo se puede utilizar como tipo
de retorno de una función genérica.

Contravarianza en los delegados genéricos

Para ilustrar la contravarianza en los delegados genéricos, vamos a utilizar el ejemplo usado
para los delegados genéricos:

public Delegate Function comparacion(Of tipo)(ByVal p1


As tipo, ByVal p2 As tipo) As Integer
Public Sub ordena(ByVal matriz() As Object, ByVal comparador As
comparacion (Of Client))
Dim i As Integer
Dim j As Integer
Dim o As Object
For i = 0 To UBound(matriz) - 1
For j = i + 1 To UBound(matriz)
If comparador.Invoke(matriz(j), matriz(i)) < 0 Then
o = matriz(j)
matriz(j) = matriz(i)
matriz(i) = o
End If
Next
Next
End Sub

Para efectuar nuestro test, añadimos una función que respete la firma del delegado y
permita realizar la comparación de dos Personas según su apellido.

Public Function compareApellido(ByVal p1 As Persona, ByVal p2 As


Persona) As Integer

Return p1.apellido.CompareTo(p2.apellido)

End Function

Ahora solo nos queda utilizar todo ello para ordenar una matriz de Clientes:

Dim matriz(4) As Cliente


matriz(0) = New Cliente("pepe2", "nombre2 ", "secreto", 2)
matriz(1) = New Cliente("pepe1", "nombre1 ", "secreto", 1)
matriz(2) = New Cliente("pepe5", "nombre5 ", "secreto", 5)
matriz(3) = New Cliente("pepe3", "nombre3 ", "secreto", 3)
matriz(4) = New Cliente("pepe4", "nombre4 ", "secreto", 4)
Dim fct As comparacion(Of Persona)
fct = New comparacion(Of Persona)(AddressOf compareApellido)

ShareVideos
ordenacion(matriz, fct)

Como no hemos tomado precauciones particulares, cuando llamamos la función de


ordenación pasándole como parámetro una instancia de delegado que trabaja con objetos
Persona, mientras que la función esperaba una instancia de delegado que trabaja con
Clientes, el compilador genera un error.

Para que el compilador autorice la contravarianza, hay que utilizar la palabra in en la


declaración del delegado.

Public Delegate Function comparacion(Of tipo)(ByVal p1


As tipo, ByVal p2 As tipo) As Integer

Como para las interfaces genéricas, un tipo puede declararse contravariante únicamente si
se utiliza como tipo de argumentos de método. En ningún caso se puede utilizar como tipo
de retorno de un método.

Covarianza en los delegados genéricos

Para ilustrar el funcionamiento de la covarianza en los delegados genéricos, vamos a crear


una función capaz de devolver una matriz rellenada con instancias de una clase particular.
La creación de las instancias de clase necesarias para rellenar la matriz será confiada a un
delegado.

El delegado genérico correspondiente puede tener la siguiente forma:

Public Delegate Function construccion(Of tipo As New)()


As tipo

La restricción en el tipo nos impone tener un constructor por defecto en la clase


correspondiente.

Ahora podemos escribir dos funciones que respeten la firma del delegado.

Public Function fabricacionCliente() As Cliente

Return New Cliente()

End Function

Public Function fabricacionPersona() As Persona

Return New Persona()

End Function

ShareVideos
Ahora solo nos queda escribir la función que permite la creación de una matriz. Esta
función espera como primer parámetro el tamaño de la matriz, y como segundo parámetro,
el delegado encargado de crear las instancias de clase que sirven para rellenar la matriz.
Esta función devuelve la matriz rellenada.

Public Function LlenarMatriz(ByVal tamaño As Integer, ByVal cc


As construccion(Of Persona)) As Persona()
Dim i As Integer
Dim matriz(tamaño) As Persona

For i = 0 To tamaño - 1
matriz(i) = cc.Invoke()
Next
Return matriz
End Function

Ahora podemos utilizar esto con las pocas líneas de código siguientes.

Dim cp Asconstruccion(OfPersona);
cp = AddressOf fabricacionPersona;
Dim matriz() As Persona

matriz = LlenarMatriz(5, cp)

El compilador no tiene nada que decir en contra de este código.

Ahora podemos intentar rellenar la matriz no con instancias de la clase Persona, sino con
instancias de la clase Cliente. Gracias a la relación de herencia entre estas dos clases, una
casilla de la matriz se puede utilizar para referenciar una instancia de la clase Persona, pero
también una instancia de cualquiera de estas subclases, y por lo tanto de la clase Cliente.

Podemos escribir en toda confianza el código siguiente:

Dim cp As construccion(Of Cliente)


cp = AddressOf fabricacionPersona
Dim tablo As Persona()
matriz = LlenarMatriz(5, cp)

Desafortunadamente, el compilador descubre el truco, ya que facilitamos a nuestra función


una instancia de delegado que utiliza un tipo más derivado que el esperado.

Para que el compilador acepte esta manipulación, hay que autorizarla añadiendo la palabra
clave out en la declaración del delegado.

Public Delegate Function construccion(Of tipo As


New)() As tipo

ShareVideos
Como para la covarianza con las interfaces genéricas, se aplica una restricción, ya que el
tipo covariante solo se puede utilizar como tipo de retorno, y no como tipo para un
parámetro de método.

Las colecciones
Las aplicaciones necesitan a menudo manipular grandes cantidades de información. En
Visual Basic están disponibles muchas estructuras para facilitar la gestión de estos datos.
Están agrupadas bajo el término colección. Como en la vida corriente, hay diferentes tipos
de colección. Puede haber personas que coleccionan todo tipo de cosas, pero no siguen
ninguna organización especial al guardarlas; otras personas están especializadas en la
colección de ciertos tipos de objetos, y hay maníacos que toman todas las precauciones para
poder encontrar enseguida un objeto...

En Framework .NET existen clases que se corresponden con cada una de estas situaciones.

1. Las colecciones predefinidas

Las diferentes clases que permiten la gestión de las colecciones se reparten entre dos
espacios de nombres:

 System.Collections
 System.Collections.Generic

El primero contiene las clases normales, mientras que el segundo contiene las clases
genéricas equivalentes que permiten la creación de colecciones muy características. Estas
colecciones muy características están especializadas en la gestión de un tipo determinado
de datos. Aunque numerosas clases ofrecen funcionalidades diferentes, tienen muchos
puntos en común, ya que implementan las mismas interfaces. Por ejemplo, todas estas
clases pueden facilitar un objeto enumerator que permite recorrer el conjunto de la
colección. Es el objeto que utiliza la instrucción For Each de Visual Basic.

a. Array

La clase Array no es parte del espacio de nombres System.Collections, pero de todos


modos puede ser considerada como colección, porque implementa la interfaz Ilist. Las
matrices creadas a partir de la clase Array tienen dimensiones fijas, aunque es posible
utilizar la instrucción redim para redimensionar una matriz. De hecho, la instrucción Redim
no amplía la matriz, sino que crea una nueva más grande o más pequeña y copia los valores
existentes, si se usa la palabra clave Preserve. Esta clase también contiene muchos métodos
compartidos, que permiten la ejecución de muchas funcionalidades en las matrices. Existen
dos propiedades muy útiles para el uso de la clase Array:

ShareVideos
 Length, que representa el número total de elementos en la matriz.
 Rank, que contiene el número de dimensiones de la matriz.

Esta clase se usa poco para la creación de una matriz porque se prefiere utilizar la sintaxis
Visual Basic.

b. ArrayList y List

La clase ArrayList o su versión genérica List son evoluciones de la clase Array. Aportan
muchas mejoras respeto a esta última.

 El tamaño de un ArrayList es dinámico y se ajusta automáticamente en función de


las necesidades. El tratamiento, que para un Array se debía realizar con la
instrucción Redim, se realiza automáticamente.
 Propone métodos que permiten agregar, insertar y suprimir varios elementos de
manera simultánea en una sola operación.

Por el contrario, en algunos puntos, la clase ArrayList es menos eficaz que una simple
matriz:

 Los ArrayList solo tienen una dimensión.


 Una matriz de datos de un tipo específico es más eficaz que un ArrayList cuyos
elementos se gestionan como Object. La utilización de la versión genérica (List)
permite obtener rendimientos equivalentes.

Como cualquier clase, un ArrayList debe ser instanciado antes de poder utilizarlo. Dos
constructores están disponibles. El primero es un constructor por defecto y crea un
ArrayList con una capacidad inicial de cero. Se dimensiona automáticamente a la hora de
añadir elementos. No se aconseja esta solución, ya que la ampliación del ArrayList
consume muchos recursos. Si tiene una estimación del número de elementos que es preciso
almacenar, es preferible utilizar el segundo constructor, que espera como parámetro la
capacidad inicial del ArrayList. Esto evita el dimensionamiento automático en el momento
de agregar elementos.

Luego se deben agregar los elementos llamando al método Add de la clase ArrayList o List
para cada uno de ellos.

Se pueden combinar estas dos etapas facilitando los valores que se deben almacenar en ella
en el momento de la instanciación del ArrayList o de la List.

Dim lst As New List(Of Integer) From {1, 2, 3, 4, 5}

Cuando el compilador analiza esta línea de código, en realidad efectúa las operaciones
siguientes :

Dim lst As New List(Of Integer)


lst.Add(1)

ShareVideos
lst.Add(2)
lst.Add(3)
lst.Add(4)
lst.Add(5)

También se puede utilizar esta técnica para inicializar listas de objetos.

Dim lstCliente As New List(Of Cliente) From


{
New Cliente("garcía", "pablo", "password", 1),
New Cliente("pérez", "pedro", "pwd", 2)
}

Incluso se puede mejorar evitando tener que crear las instancias de los objetos que se deben
almacenar en la lista. Para ello, es suficiente volver a definir el método Add de la lista con
la forma de un método de extensión para que el compilador pueda llamarlo al agregar
elementos y crear la instancia de cada uno de ellos.

<Extension()>
Sub add(ByVal lst As List(Of Client),
ByVal apellido As String,
ByVal nombre As String,
ByVal contraseña As String,
ByVal code As String)
lst.add(New Cliente With
{
.apellido = apellido,
.nombre = nombre,
.Contraseña = contraseña,
.codigo = codigo
})
End Sub

Ahora se puede utilizar la sintaxis siguiente para instanciar e inicializar una lista de
clientes.

Dim lstCliente As New List(Of Cliente) From


{
{"garcía", "pablo", "password", 1},
{"pérez", "pedro", "pwd", 2}
}

Hay que resaltar que la dimensión indicada no es definitiva y el ArrayList podrá contener
más elementos que los previstos inicialmente.

La propiedad Capacity permite conocer el número de elementos que el ArrayList puede


contener. La propiedad Count indica el número actual de elementos en el ArrayList. Los
métodos Add y AddRange añaden elementos al final de la lista. Los métodos Insert e
InsertRange permiten elegir la ubicación donde se agregarán los elementos. La propiedad
Item, que es la propiedad por defecto de clase, se utiliza para asignar un elemento en una

ShareVideos
posición dada. La supresión de elementos se hace por el método RemoveAt o
RemoveRange; el primero espera como parámetro el índice del elemento que se debe
suprimir, el segundo exige además el número de elementos que se deben suprimir. El
método Clear es más radical y suprime todos los elementos.

El código siguiente ilustra el funcionamiento de esta clase:

Public Sub main()


Dim lista As ArrayList
Dim c As Cliente
lista = New ArrayList()
Console.WriteLine("capacidad inicial de la lista {0}",
lista.Capacity)
Console.WriteLine("número de elementos de la lista {0}",
lista.Count)
Console.WriteLine("añadir un cliente")
c = New Cliente("cliente1", "nombre1", "secreto", 1001)
lista.Add(c)
Console.WriteLine("capacidad de la lista {0}", lista.Capacity)
Console.WriteLine("número de elementos de la lista {0}",
lista.Count)
Console.WriteLine("añadir cuatro clientes")
c = New Cliente("cliente2", "nombre2", "secreto", 1002)
lista.Add(c)
c = New Cliente("cliente3", "nombre3", "secreto", 1003)
lista.Add(c)
c = New Cliente("cliente4", "nombre4", "secreto", 1004)
lista.Add(c)
c = New Cliente("cliente5", "nombre5", "secreto", 1005)
lista.Add(c)
Console.WriteLine("capacidad de la lista {0}", lista.Capacity)
Console.WriteLine("número de elementos de la lista {0}",
lista.Count)
Console.WriteLine("mostrar la lista de clientes")
For Each c In lista
c.mostrar()
Console.WriteLine()
Next
Console.WriteLine("borrar clientes 1002, 1003, 1004")
lista.RemoveRange(1, 3)
Console.WriteLine("capacidad de la lista {0}", lista.Capacity)
Console.WriteLine("número de elementos de la lista {0}",
lista.Count)
Console.WriteLine("mostrar la lista de clientes")
For Each c In lista
c.mostrar()
Console.WriteLine()
Next
Console.WriteLine("mostrar el segundo cliente de la lista")
lista(1).mostrar()
Console.WriteLine()
Console.WriteLine("borrar todos los clientes")
lista.Clear()
Console.WriteLine("capacidad de la lista {0}", lista.Capacity)

ShareVideos
Console.WriteLine("número de elementos de la lista {0}",
lista.Count)
Console.ReadLine()
End Sub

Muestra el resultado siguiente:

capacidad inicial de la lista 0


número de elementos de la lista 0
añadir un cliente
capacidad de la lista 4
número de elementos de la lista 1
añadir cuatro clientes
capacidad de la lista 8
número de elementos de la lista 5
mostrar la lista de clientes
Sr cliente1 apellido1 nacido el 01/01/0001 00:00:00 código de Cliente:
1001
Sr cliente2 apellido2 nacido el 01/01/0001 00:00:00 código de Cliente:
1002
Sr cliente3 apellido3 nacido el 01/01/0001 00:00:00 código de Cliente:
1003
Sr cliente4 apellido4 nacido el 01/01/0001 00:00:00 código de Cliente:
1004
borrar los clientes 1002, 1003, 1004
capacidad de la lista 8
número de elementos de la lista 2
mostrar la lista de clientes
Sr cliente1 apellido1 nacido el 01/01/0001 00:00:00 código de Cliente:
1001
Sr cliente5 apellido5 nacido el 01/01/0001 00:00:00 código de Cliente:
1005
mostrar el segundo cliente de la lista
Sr cliente5 apellido5 nacido el 01/01/0001 00:00:00 código de Cliente:
1005
Borrar todos los clientes
capacidad de la lista 8
número de elementos de la lista 0

La capacidad de la lista no disminuye con la supresión de un elemento, incluso cuando la


lista está vacía.

c. Hashtable y Dictionary

Un Hashtable o su versión genérica Dictionary registra los datos en forma de parejas clave
valor. El Hashtable está constituido internamente por compartimentos que contienen los
elementos de la colección. Para cada elemento de la colección, se genera un código
mediante una función hash basada en la clave de cada elemento. Luego se utiliza el código
para identificar el compartimento en el que se almacena el elemento. Durante la búsqueda
de un elemento en la colección, se efectúa la operación inversa. El código hash se genera a
partir de la clave del elemento buscado. Esta clave sirve después para identificar el

ShareVideos
compartimento en el que se encuentra el elemento buscado. Para que una Hashtable pueda
almacenar un objeto, este debe ser capaz de facilitar su propio código hash.

d. Queue

Se utiliza este tipo de colección cuando se necesita un espacio de almacenamiento


temporal. Cuando se recupera un elemento a partir de la colección, se suprime al mismo
tiempo de esta.

Las colecciones de tipo Queue se adaptan, si necesitara acceder a los datos en el mismo
orden en que han sido almacenados en la colección. Este tipo de gestión se llama a veces
First In - First Out (FIFO). Las tres principales operaciones disponibles son:

 Enqueue para agregar un elemento al final de la cola.


 Dequeue para obtener el elemento más antiguo de la cola y suprimirlo.
 Peek para obtener el elemento más antiguo sin suprimirlo de la cola.

El ejemplo siguiente ilustra la utilización de estos tres métodos.

Public Sub main()


Dim q As Queue
q = New Queue
Dim c As Cliente
c = New Cliente("cliente1", "nombre1", "secreto", 1001)
Console.WriteLine("llegada del primer cliente:{0}", c.apellido)
q.Enqueue(c)
c = New Cliente("cliente2", "nombre2", "secreto", 1002)
Console.WriteLine("llegada del segundo cliente:{0}", c.apellido)
q.Enqueue(c)
c = New Cliente("cliente3", "nombre3", "secreto", 1003)
Console.WriteLine("llegada del tercer cliente:{0}", c.apellido)
q.Enqueue(c)
Console.WriteLine("salida del primer cliente:{0}",
q.Dequeue.apellido)
Console.WriteLine("quedan {0} clientes", q.Count)
Console.WriteLine("salida del segundo cliente:{0}",
q.Dequeue.apellido)
Console.WriteLine("quedan {0} clientes", q.Count)
Console.WriteLine("el tercer cliente se inserta:{0}",
q.Peek.apellido)
Console.WriteLine("quedan {0} clientes", q.Count)
Console.WriteLine("salida del tercer cliente:{0}",
q.Dequeue.apellido)
Console.WriteLine("quedan {0} clientes", q.Count)
Console.ReadLine()
End Sub

e. Stack

Las colecciones de este tipo utilizan el mismo principio que las Queue: cuando se recupera
un elemento de la colección, se suprime de ella. La única distinción respeto a la clase

ShareVideos
Queue es el orden en el que se recuperan los elementos. Este tipo de colección utiliza la
técnica Last In - First Out (LIFO). El ejemplo clásico de este tipo de gestión es la pila de
platos de su cocina. Después de fregar los platos, se guardan en una estantería. Al día
siguiente, al poner la mesa, el primer plato disponible es el último, que guardó del día
anterior.

Las tres principales operaciones disponibles son:

 Push para agregar un elemento encima de la pila.


 Pop para obtener el elemento encima de la pila y suprimirlo.
 Peek para obtener el elemento encima de la pila sin suprimirlo de la pila.

2. Elegir un tipo de colección

A continuación le damos unos consejos para elegir el tipo de colección que más se adapte a
sus necesidades.

 Necesita acceder a los elementos de la colección con un índice: utilice un ArrayList.


 El acceso a los elementos se debe efectuar en el orden en que han sido agregados a
la colección o en el orden inverso: utilice un Queue o un Stack.
 Necesita ordenar todos los elementos en un orden diferente de aquel en el que son
añadidos a la colección: utilice un ArrayList o un Hashtable.
 Los elementos que se han de almacenar en la lista son parejas clave-elemento:
utilice un Hashtable.

Los objetos intrínsecos


Existen numerosos objetos disponibles automáticamente a partir de Visual Basic, sin que se
tenga que crear una instancia. Estos objetos son accesibles con la palabra clave My.
Permiten la manipulación y el acceso a datos que se utilizan frecuentemente durante el
funcionamiento de una aplicación. Proporcionan, por ejemplo, un medio de acceso a las
propiedades de la máquina en la que se ejecuta la aplicación, a través de My.Computer, o a
las propiedades de la propia aplicación a través de My.Application. En función del tipo de
proyecto en el que trabaja, es posible que algunos objetos no estén disponibles. La siguiente
tabla presenta los objetos existentes y el contexto en el que están disponibles.

Aplicación Biblioteca de Servicio


Biblioteca Aplicación
de controles de de
de clases de consola
Windows Windows Windows
My.Application Sí Sí Sí Sí Sí
My.Computer Sí Sí Sí Sí Sí
My.Forms Sí No No Sí No
My.Resources Sí Sí Sí Sí Sí

ShareVideos
My.Settings Sí Sí Sí Sí Sí Según
el tipo
My.User Sí Sí Sí Sí Sí
de
My.WebServices Sí Sí Sí Sí Sí proyect
o en el
que se utilizan, algunas propiedades de estos objetos pueden no estar disponibles. Por
ejemplo, la propiedad MainForm del objeto Application solo se puede utilizar en los
proyectos de aplicaciones de Windows.

 El objeto Application proporciona propiedades, métodos y eventos relativos a la


aplicación en curso. Permite, por ejemplo, recuperar información sobre la
configuración lingüística de la aplicación, los parámetros de la línea de comandos
utilizados para lanzar la aplicación o la información relativa a la versión de la
aplicación.
 El objeto Computer ofrece acceso a los diferentes recursos en la máquina. Permite,
por ejemplo, acceder directamente al sistema de audio de la máquina, al teclado, al
ratón o a la red.
 El objeto Forms pone a su disposición una instancia de cada una de las ventanas
disponibles en el proyecto. El acceso se realiza a través del nombre de clase
correspondiente a la ventana, que se convierte así en una propiedad del objeto
Forms.
 El objeto Resources permite acceder a los recursos de audio, a los iconos y a las
imágenes y cadenas de caracteres definidas a nivel del proyecto.
 El objeto Settings permite acceder a los parámetros de la aplicación definidos por
las propiedades del proyecto.
 El objeto User representa el usuario de la aplicación. Si no se utiliza ninguna
autenticación específica en la aplicación, el objeto User corresponde al usuario con
el que se ha abierto la sesión de Windows.
 El objeto Webservices facilita una instancia de cada servicio Web referenciado en la
aplicación. Como para la propiedad Forms, el acceso se efectúa a través del nombre
del servicio Web.

Los diferentes tipos de errores


Para un desarrollador, los errores son una de las principales fuentes de estrés. En realidad,
podemos clasificar estos errores en tres categorías. Vamos a ver cada una de ellas, así como
las soluciones disponibles para resolverlos.

1. Los errores de sintaxis

Este tipo de errores se produce en el momento de la compilación, cuando una palabra clave
del lenguaje está mal escrita. Eran muy frecuentes con las primeras herramientas de
desarrollo, en las que el editor de código y el compilador eran dos entidades separadas.
Ahora son cada vez más raros en los entornos similares a Visual Studio. La mayoría de
estos entornos ofrecen un análisis sintáctico mientras se escribe el código. Desde este punto

ShareVideos
de vista, Visual Studio proporciona muchas funcionalidades que nos permiten eliminar este
tipo de errores.

Así, por ejemplo, controla que cada instrucción If se acabe correctamente con un End If.

Si detecta un error de sintaxis, Visual Basic propone las posibles soluciones para corregirlo.
Se pueden ver haciendo clic en el icono asociado al error.

Por otra parte, los «errores de ortografía» en los nombres de propiedades o métodos se
eliminan fácilmente gracias a las funcionalidades IntelliSense. IntelliSense se encarga de:

 Mostrar automáticamente la lista de los miembros disponibles.

 Mostrar la lista de parámetros que se pueden facilitar para la llamada de un


procedimiento o función:

Si un método está sobrecargado, IntelliSense muestra su número y le permite


recorrerlos utilizando las flechas arriba y abajo del teclado.

 Mostrar información puntual acerca de los miembros de una clase:

 Completar automáticamente las palabras: empiece por escribir el principio de


palabra, luego utilice la combinación de teclas [Ctrl][Espacio] para mostrar todo lo
que puede utilizar como palabra en ese emplazamiento, que empieza con los
caracteres ya elegidos. Si solo hay una posibilidad, la palabra se añade
automáticamente; si no, selecciónela de la lista y valide con la tecla [Tab].
 Mostrar la lista de los posibles valores para una propiedad de tipo enumeración.

Con todas estas funcionalidades, es prácticamente imposible generar errores de sintaxis en


el código.

ShareVideos
2. Los errores de ejecución

Estos errores aparecen después de la compilación, cuando decide lanzar la ejecución de su


aplicación. La sintaxis del código es correcta, pero el entorno de su aplicación no permite la
ejecución de una instrucción que esta utilice. Esto ocurre, por ejemplo, cuando se intenta
abrir un archivo que no existe en el disco de su máquina. Seguramente obtenga un cuadro
de diálogo de este tipo, no muy agradable para el usuario:

Afortunalmente, Visual Basic permite la recuperación de este tipo de error y evita así la
visualización de este inquietante cuadro de diálogo. Existen dos técnicas para la gestión de
este tipo de errores:

 La gestión en línea.
 Las excepciones.

Las veremos en detalle más adelante en este capítulo.

Los errores de lógica

Los peores enemigos de los desarrolladores. Todo se compila sin problema, todo se ejecuta
sin problema y, sin embargo, «¡no funciona!!»

En este caso, conviene revisar la lógica de funcionamiento de la aplicación. Las


herramientas de depuración nos permiten seguir el desarrollo de la aplicación, ubicar
puntos de parada, visualizar el contenido de las variables, etc.

Tratamiento de los errores


Existen dos técnicas para el tratamiento de los errores de Visual Basic:

 La gestión en línea.
 El tratamiento de la excepción.

1. La gestión en línea

La instrucción On Error es el elemento básico de la gestión de los errores en línea. Cuando


esta instrucción se ejecuta en un procedimiento o función, activa la gestión de los errores
para ese procedimiento o función. Sin embargo, conviene indicar a nuestro gestor cómo
debe reaccionar cuando una instrucción desencadena un error. Hay dos soluciones:

On error resume next

La ejecución del código seguirá por la línea posterior a la que ha provocado el error.

ShareVideos
On error goto etiqueta

La ejecución del código pasará a la línea señalada con etiqueta.

La sintaxis es, por tanto, la siguiente:

Private Sub Nombre_de_procedimiento()


On Error Goto gestionErrores
...
"Instrucciones peligrosas"
...
Exit Sub
gestionErrores:
...
código que gestiona los errores
...
End Sub

La etiqueta hacia la que el gestor de errores dirigirá la ejecución se debe encontrar en el


mismo procedimiento que la instrucción on error goto. La instrucción Exit sub
es obligatoria para que el código de gestión de errores no se ejecute después de las
instruciones normales, sino solo en el caso de errores.

El código del gestor de errores debe determinar el comportamiento que hay que seguir en el
caso de error.

Tres soluciones:

Resume

Se vuelve a intentar la ejecución de la línea que ha producido el error.

resume next

Se sigue con la ejecución en la línea que sigue a la que ha provocado el error.

exit sub o exit function

Se para la ejecución de este procedimiento o función.

Normalmente, el gestor de errores muestra un cuadro de diálogo que le pregunta al usuario


qué quiere hacer.

En función de su respuesta, se utilizará una solución u otra.

Private Sub AbrirArchivo()


On Error GoTo gestionErrores
My.Computer.FileSystem.OpenTextFileReader("c:\prueba")
Exit Sub

ShareVideos
gestionErrores:
Dim respuesta As Integer
respuesta = MsgBox("no se puede leer el archivo",
MsgBoxStyle.AbortRetryIgnore)
Select Case respuesta
Case MsgBoxResult.Retry
Resume
Case MsgBoxResult.Ignore
Resume Next
Case MsgBoxResult.Abort
Exit Sub
End Select
End Sub

Nos queda todavía un problema por resolver: nuestro gestor de errores reaccionará
independientemente del error. Para poder determinar qué error se acaba de producir en la
aplicación, tenemos a nuestra disposición el objeto err, que nos proporciona información
sobre el último error que ha aparecido. Este objeto contiene, entre otras, dos propiedades,
number y description, que nos permiten obtener el código del error y su descripción.
Podemos modificar entonces nuestro código para que reaccione de manera diferente en
función del error.

Private Sub AbrirArchivoBis()


On Error GoTo gestionErrores
My.Computer.FileSystem.OpenTextFileReader("a:\prueba")
Exit Sub
gestionErrores:
Dim respuesta As Integer
Console.WriteLine(Err.Number)
Stop
If Err.Number = 53 Then
respuesta = MsgBox("no se ha encontrado el archivo",
MsgBoxStyle.
AbortRetryIgnore)
Select Case respuesta
Case MsgBoxResult.Retry
Resume
Case MsgBoxResult.Ignore
Resume Next
Case MsgBoxResult.Abort
Exit Sub
End Select
End If
If Err.Number = 57 Then
respuesta = MsgBox("insertar un disco en el lector",
MsgBoxStyle.OKCancel)
Select Case respuesta
Case MsgBoxResult.OK
Resume
Case MsgBoxResult.Cancel
Exit Sub
End Select
End If
End Sub

ShareVideos
Se puede desactivar un gestor de errores utilizando la instrucción on error goto 0. Si se
produce un error después de esta instrucción, no se recupera y la aplicación se detiene.

De hecho, la aplicación no se para inmediatamente, sino que Visual Basic busca en las
funciones de llamada si hay un gestor de errores activo. Si encuentra uno, le encarga la
gestión del error.

Private Sub procedimiento1()


On Error GoTo gestionErrores
procedimiento2()
Exit Sub
gestionErrores:
MsgBox("error de ejecución")
End Sub
Private Sub procedimiento2()
Dim x, y As Integer
x = 0
y = (1 / x)
End Sub

En este ejemplo, procedimiento2 ejecutará una instrucción que activa un error (1 / x). Como
no existe un gestor de errores en este procedimiento, Visual Basic busca entre las llamadas
si existe un gestor activo. El primero que encuentra está en el procedimiento1; será este el
que se encargará de la gestión del error.

2. Las excepciones
a. Recuperación de excepciones

La gestión de las excepciones da la posibilidad de proteger un bloque de código contra los


errores de ejecución que podrían producirse. El código peligroso se debe colocar en un
bloque Try End Try. Si se activa una excepción en este bloque de código, Visual Basic mira
las siguientes instrucciones catch. Si existe una capaz de tratar la excepción, el código
correspondiente se ejecuta; de otra forma, la misma excepción se activaría para ser
recuperada en un bloque Try End Try, de nivel más alto. Una instrucción finally permite
marcar un grupo de instrucciones, ejecutadas antes de la salida del bloque try, tanto si se ha
producido un error como si no.

Por tanto, la sintaxis general es la siguiente:

Try
...
Instrucciones peligrosas
...
catch excepcion1
...
código ejecutado si se produce una excepción de tipo Excepcion1
...
catch excepcion2
...
código ejecutado si se produce una excepción de tipo Excepcion2

ShareVideos
...
Finally
...
código ejecutado siempre antes de salir del bloque Try
...
End Try

Esta estructura tiene un funcionamiento muy parecido al Select case que ya hemos
estudiado. Cada tipo de error se asocia a un tipo de exceptión y, cuando se produce este
error, se crea una instancia de la clase Exception correspondiente. Podremos determinar,
para cada instrucción Catch, qué tipo de excepción debe tratar.

La clase de base es la clase Exception desde la que se crea una serie de subclases
especializadas, cada una para un tipo de error particular. A continuación, presentamos la
lista de las clases que derivan directamente de la clase Exception.

 Microsoft.Build.BuildEngine.InternalLoggerException
 Microsoft.Build.BuildEngine.InvalidProjectFileException
 Microsoft.Build.Framework.LoggerException
 Microsoft.JScript.CmdLineException
 Microsoft.JScript.ParserException
 Microsoft.VisualBasic.ApplicationServices
 Microsoft.VisualBasic.ApplicationServices.NoStartupFormException
 Microsoft.VisualBasic.CompilerServices.IncompleteInitialization
 Microsoft.VisualBasic.CompilerServices.InternalErrorException
 Microsoft.VisualBasic.FileIO.MalformedLineException
 Microsoft.WindowsMobile.DirectX.DirectXException
 System.ApplicationException
 System.ComponentModel.Design.ExceptionCollection
 System.Configuration.Provider.ProviderException
 System.Configuration.SettingsPropertyCannotBeSetForAnonymousUserException
 System.Configuration.SettingsPropertyIsReadOnlyException
 System.Configuration.SettingsPropertyNotFoundException
 System.Configuration.SettingsPropertyWrongTypeException
 System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectExistsException
 System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundExcepti
on
 System.DirectoryServices.ActiveDirectory.ActiveDirectoryOperationException
 System.DirectoryServices.ActiveDirectory.ActiveDirectoryServerDownException
 System.DirectoryServices.Protocols.DirectoryException
 System.IO.IsolatedStorage.IsolatedStorageException
 System.Net.Mail.SmtpException
 System.Runtime.Remoting.MetadataServices.SUDSGeneratorException
 System.Runtime.Remoting.MetadataServices.SUDSParserException
 System.SystemException
 System.Web.Security.MembershipCreateUserException
 System.Web.Security.MembershipPasswordException
 System.Web.UI.ViewStateException

ShareVideos
 System.Windows.Forms.AxHost.InvalidActiveXStateException

Esta lista solo presenta el primer nivel de la jerarquía. Cada una de estas clases también
tiene muchos descendientes.

Estas excepciones se utilizan para indicar, en cada instrucción Catch, el tipo de excepción
que debe generar.

Private Sub AbrirArchivo()


Try
My.Computer.FileSystem.OpenTextFileReader("a:\prueba")
Catch ex As System.IO.IOException
MsgBox("error al abrir el archivo", MsgBoxStyle.OKOnly)
Finally
MsgBox("fin del procedimiento de apertura de un archivo")
End Try
End Sub

Si entre todas las Catch ninguna se corresponde con la excepción generada, la excepción se
propaga al código de los procedimientos o funciones de llamadas, a la búsqueda de una
instrucción Catch capaz de tomar en consideración esta excepción. Si no se encuentra
ningún bloque, se activa un error de ejecución.

Los bloques Catch también pueden ser condicionales, añadiendo la palabra When seguida
por una expresión que se pueda evaluar como un booleano.

Catch ex As Exception When condición < 0


...
Catch ex As Exception When condición > 10
...
End Try

El bloque Catch se ejecuta si se activa una excepción de este tipo y si se cumple la


condición.

Si el parámetro indicado en la instrucción Catch es una clase «de excepción general», esta
instrucción Catch será capaz de capturar todas las excepciones creadas a partir de esta clase
o de estas subclases. El siguiente código nos permite capturar todas las excepciones.

Private Sub AbrirArchivo()


Try
My.Computer.FileSystem.OpenTextFileReader("a:\prueba")
Catch ex As Exception
MsgBox("error al abrir el archivo", MsgBoxStyle.OKOnly)
Finally
MsgBox("fin del procedimiento de apertura de un archivo")
End Try
End Sub

ShareVideos
Las diferentes clases disponen de las siguientes propiedades que nos permiten tener más
datos sobre el origen de la excepción.

Message

Cadena de caracteres asociada a la excepción.

Source

Nombre de la aplicación que activó la excepción.

StackTrace

Lista de todos los métodos por los que ha pasado la aplicación antes de la activación del
error.

TargetSite

Nombre del método que activó la excepción.

InnerException

Obtiene la excepción original si se activan dos excepciones en cascada.

b. Creación y activación de excepciones

Las excepciones son, ante todo, clases. Por lo tanto, es posible crear nuestras propias
excepciones heredando una de las numerosas clases de excepción ya disponibles. Para
respetar las convenciones del Framework .NET, se aconseja conservar el término Exception
en el nombre de la clase. Podemos, por ejemplo, escribir el siguiente código:

Public Class NoFuncionaException


Inherits Exception
Public Sub New()

End Sub
Public Sub New(ByVal mensaje As String)
MyBase.New(mensaje)
End Sub
Public Sub New(ByVal mensaje As String, ByVal inner As Exception)
MyBase.New(mensaje, inner)
End Sub
End Class

Luego se puede utilizar esta clase para activar una excepción personalizada. El siguiente
código activa una excepción personalizada en un bloque catch.

Catch ex As Exception
Throw New NoFuncionaException("error en la aplicación", ex)

ShareVideos
End Try

Las herramientas de depuración


En la sección dedicada a la gestión de los errores, hemos visto que los errores de lógica son
los más dificiles de eliminar de una aplicación. Afortunalmente, Visual Studio .NET nos
propone numerosas herramientas de depuración eficaces y sencillas. Permiten controlar el
desarrollo de la ejecución de la aplicación (ubicando puntos de interrupción y haciendo
ejecutar las instrucciones una a una), visualizar y modificar el contenido de las variables,
visualizar el contenido de la memoria en una ubicación particular, verificar la lista de todas
las funciones utilizadas, etc. Estas herramientas son accesibles desde la barra de
herramientas Depurar.

El menú DEPURAR también facilita el acceso a numerosas herramientas:

Según la configuración del entorno de Visual Studio, es posible que algunas herramientas
no estén disponibles. Puede volver a configurar Visual Studio para integrar estas
herramientas a través del menú Herramientas - Importar y exportar configuraciones.
Los diferentes cuadros de diálogo le proponen guardar su entorno actual antes de
modificarlo, y luego elegir un entorno tipo para importar.

Entre las configuraciones disponibles, la Configuración general de desarrollo contiene la


mayoría de las funcionalidades.

Después de esta importación, se añaden muchas herramientas adicionales al menú


DEPURAR:

Para las siguientes explicaciones de este capítulo, vamos a considerar que esta es la
configuración utilizada en Visual Studio.

1. Control de la ejecución
a. Inicio de la solución

Un proyecto en Visual Studio puede tener tres estados distintos:

 En desarrollo.

ShareVideos
 En ejecución.
 En modo de parada (se interrumpió la ejecución).

El lanzamiento de la ejecución se puede efectuar por la barra de herramientas o por la


combinación de teclas [F5] o [Ctrl][F5]. Si se utiliza esta última solución, la aplicación se
lanza en modo normal y no hay disponible ninguna herramienta de depuración.

Si la solución contiene varios proyectos, se debe configurar uno de ellos como proyecto de
lanzamiento para la solución. Este proyecto también debe tener un objeto de lanzamiento
configurado, y con su ejecución se iniciará la applicación.

b. Parar la solución

Se puede parar la aplicación cerrando todas las ventanas; para una aplicación de Windows,
en cuanto se cierra la última ventana, o a través de las teclas [Ctrl] C para una aplicación de
consola. La barra de herramientas o la combinación de teclas [Ctrl][Alt] [Pausa] también
permiten parar la aplicación.

La combinación de acceso rápido [Mayús][F5] permite relanzar la ejecución de la solución.

c. Interrumpir la solución

La interrupción de la ejecución se efectúa con la combinación de teclas [Ctrl][Pausa] o a


través de la barra de herramientas:

La interrupción se produce sobre la instrucción que sigue a la que esté en curso de


ejecución en el momento de la parada. La ventana de código se vuelve de nuevo visible,
con una marca delante de la línea donde se interrumpió la ejecución.

Este método no es muy práctico, ya que hace falta tener mucha suerte para interrumpir la
ejecución en un lugar preciso. Más adelante veremos que los puntos de interrupción son
una solución mucho mejor para detener la ejecución del código.

d. Proseguir la ejecución

Una vez en modo parado, tenemos distintas formas de continuar con la ejecución de la
aplicación.

ShareVideos
La primera permite retomar la ejecución normal de la aplicación utilizando la misma
técnica que se usa para el inicio del programa (barra de herramientas o combinación de
teclas [F5]). Sin embargo, la técnica más corriente durante una depuración consiste en la
ejecución paso a paso.

Hay tres soluciones disponibles:

 Paso a paso por instrucciones ([F11]).


 Paso a paso por procedimientos ([F10]).
 Paso a paso para salir ([Shift][F11]).

El Paso a paso por instrucciones y el Paso a paso por procedimientos difieren simplemente
en su manera de gestionar las llamadas de procedimientos y funciones. Si estamos en modo
de parada en una línea de código que contiene una llamada a un procedimiento o una
función, el modo Paso a paso por instrucciones va a permitir entrar en el código de la
función y luego lanzar la ejecución de su código línea por línea. El modo Paso a paso por
procedimientos ejecutará el procedimiento o la función de una sola vez sin que podamos
ver lo que ocurre en el interior del procedimiento o función.

El Paso a paso para salir permite la ejecución del código hasta el final de un procedimiento
o función, sin descomponer línea por línea, y después vuelve al modo de parada en la línea
que sigue a la llamada de la función.

Existe una última solución que nos permite ejecutar fácilmente un bloque de código y luego
detenerse en una línea específica. Para ello, un menú contextual en la ventana de código
nos ofrece la posibilidad de volver a lanzar la ejecución hasta la ubicación del cursor, sin
parar en todas las instrucciones entre la línea actual y la posición del cursor (muy útil para
ejecutar rápidamente todas las iteraciones de un bucle).

Si por el contrario desea ignorar la ejecución de un bloque de código o prefiere en cambio


ejecutar de nuevo un bloque de código, es posible desplazar el punto de ejecución para
designar la próxima instrucción ejecutada. Basta con desplazar la flecha amarilla que se
muestra en el margen frente a la siguiente instrucción que se va a ejecutar.

Como nos indica Microsoft, se debe utilizar este comando con precaución. Hay que
recordar los siguientes puntos: las instrucciones colocadas entre el antiguo y el nuevo punto

ShareVideos
de ejecución no se ejecutarán; desplazar el punto de ejecución hacia atrás no anula las
instrucciones ya tratadas; el punto de ejecución solo puede desplazarse dentro de una
función o procedimiento.

2. Puntos de interrupción y TracePoint

Solo tenemos una solución para pasar a modo de parada y consiste en utilizar las teclas
[Ctrl][Alt][Pausa]. Esta solución presenta un gran inconveniente: la ejecución se para en
cualquier punto. Los puntos de interrupción nos facilitan una solución más elegante gracias
a la cual podemos elegir la ubicación donde tendrá lugar la interrupción de la ejecución.

Los puntos de interrupción pueden ser condicionales. Se tienen en cuenta diferentes tipos
de condiciones en su activación (condición, número de paso...).

Los TracePoint son prácticamente idénticos a los puntos de interrupción, excepto que para
un TracePoint se puede especificar la acción ejecutada cuando se alcanza el punto. Puede
ser el paso en modo de parada de la aplicación o la visualización de un mensaje. En el
entorno Visual Studio, los puntos de interrupción o los TracePoint se muestran mediante
una serie de iconos. Los iconos vacíos representan un elemento desactivado.

representa un punto de interrupción normal, activado o desactivado.

representa un punto de interrupción avanzado (condición, número de paso o filtro).

representa un TracePoint normal, activado o desactivado.

representa un TracePoint avanzado (condición, número de paso o filtro).

representa un punto de interrupción o un TracePoint en error.

representa un aviso en un punto de interrupción o un TracePoint.

a. Ubicar un punto de interrupción

Para ubicar un punto de interrupción, existen muchas posibilidades:

 Efectuar un clic en el margen de la ventana de código.


 Colocar el cursor en la línea correspondiente y utilizar la combinación de teclas
[Ctrl] B.
 Utilizar la opción Punto de interrupción - Insertar un punto de interrupción del
menú contextual de la ventana de código.

Todas estas técnicas insertan el punto de interrupción y materializan su ubicación con un


punto rojo en el margen y encuadran en rojo la línea correspondiente.

ShareVideos
Para todas estas soluciones, el código debe ser visible en el editor. La opción Interrumpir
la función del menú Depurar - Nuevo punto de interrupción permite ubicar un punto de
interrupción en un procedimiento o función solo con teclear su nombre.

Cuidado: el cuadro de diálogo le propone precisar en qué línea de la función desea ubicar
un punto de interrupción, pero esta funcionalidad no está disponible para los puntos de
interrupción en funciones.

Los puntos de interrupción que se ubican así son incondicionales. En cuanto la ejecución
llega a esta línea, la aplicación pasa a modo detenido. Se puede perfeccionar el
funcionamiento de los puntos de interrupción al añadirles condiciones, un número de paso o
al transformarlos en TracePoint. Para ello, conviene modificar las propiedades del punto de
interrupción a través del menú contextual disponible haciendo un clic derecho en la línea
correspondiente.

Agregar una condición

Se puede someter a condición el paso a modo de parada. El siguiente cuadro de diálogo


permite precisar las condiciones de ejecución del punto de interrupción.

En este cuadro de diálogo, debemos introducir una expresión que será evaluada a cada paso
por el punto de interrupción. Entonces la ejecución se parará:

 Si el resultado de la evaluación de la condición es verdadero.


 Si el resultado de la evaluación de la condición ha sido modificado desde el último
paso por este punto de interrupción. Hay que observar en este caso que son
necesarios al menos dos pasos para provocar la parada de la aplicación (el primero
sirve simplemente para grabar el resultado de la expresión).

Modificación del número de pasos

Los puntos de interrupción también pueden contar el número de veces que se les alcanza y
activarse para un número particular de pasos.

ShareVideos
Este cuadro de diálogo nos permite definir el número de pasos en el punto de interrupción
para que este pare efectivamente la aplicación. Están disponibles cuatro opciones para la
condición de interrupción en el número de pasos.

Cuidado: si se indica una condición para el punto de interrupción, el número de pasos


corresponde al número de veces que pasa la ejecución de la aplicación por esta línea con la
condición comprobada. Con la configuración de nuestro ejemplo, nos pararemos en el bucle
al paso número 100.000 (la condición será verdadera para
i=0,100,200,300,400,500,600,700,800,900).

Filtrado

Los filtros permiten añadir criterios adicionales para la ejecución de un punto de


interrupción. Estos criterios son relativos al nombre de la máquina donde se ejecuta la
aplicación y al proceso o thread.

La condición debe expresarse con las palabras clave MachineName, ProcessId,


ProcessName, ThreadId, ThreadName y los operadores & (y), || (o), ! (not).

Transformación en TracePoint

Un punto de interrupción se puede transformar en tracePoint precisando una acción


particular que se debe ejecutar cuando se alcance.

Este cuadro de diálogo espera la formulación del mensaje visualizado en la ventana de


salida cuando se alcanza el punto de interrupción. También autoriza la ejecución de una
macro. Para que el punto de interrupción se transforme realmente en TracePoint, la opción
Continuar la ejecución debe estar activada.

b. Activar, desactivar, suprimir un punto de interrupción

De manera momentánea se pueden desactivar los puntos de interrupción gracias al menú


contextual.

A continuación, el punto de interrupción se puede activar de nuevo al utilizar otra vez el


menú contextual. Este mismo menú permite también eliminar un punto de
interrupción, pero es más rápido hacer doble clic en el propio punto de interrupción. El
menú Depurar propone también la opción Eliminar todos los puntos de interrupción, y

ShareVideos
evita así tener que recorrer muchas líneas de código para eliminar el conjunto de los puntos
de interrupción.

Para facilitarnos la tarea durante la depuración de una aplicación, una ventana nos muestra
un resumen de todos los puntos de interrupción ubicados en su proyecto. Esta ventana es
accesible por medio del menú Depurar - Ventanas - Punto de interrupción, y propone un
menú contextual que permite realizar las principales acciones sobre un punto de
interrupción.

Los puntos de interrupción se conservan cuando se cierra el proyecto.

3. Examen del contenido de variables

El interés por depurar consiste en poder seguir el funcionamiento de la aplicación durante


su ejecución. Cuando la aplicación está en modo de parada, es primordial poder visualizar
los valores contenidos en las diferentes variables de la aplicación. Esta visualización nos
permite comprobar el resultado de las operaciones ya efectuadas o anticiparnos a las que se
realizarán en el resto del código.

a. DataTips

Los DataTips ofrecen un medio rápido para visualizar el contenido de una variable. Solo
hay que desplazar el cursor del ratón sobre el nombre y, en un momento, se visualiza una
ventana que presenta el contenido de la variable. Si la variable es un tipo complejo, como
una instancia de clase por ejemplo, el DataTips propone un pequeño signo + que permite
bajar en la estructura de la variable. Los datos mostrados también se pueden modificar
directamente en el DataTips. El DataTips desaparece automáticamente cuando aleja el
ratón.

Para visualizar el resultado de un cálculo de una expresión, conviene previamente


seleccionar la expresión y luego ubicar el cursor del ratón sobre la selección. El depurador
evalúa la expresión y muestra el resultado. El DataTips solo puede visualizar las variables
accesibles en el ámbito actual (variables declaradas en la función donde estamos detenidos
o variables globales).

Un pequeño truco: si desea visualizar el código ocultado por el DataTips sin hacerlo
desaparecer, puede utilizar la tecla [Ctrl], que lo vuelve transparente.

ShareVideos
b. Ventana Automático

La ventana Automático muestra las variables utilizadas en la instrucción actual, en las tres
instrucciones anteriores y en las tres instrucciones siguientes. Esta ventana es accesible a
través del menú Depurar - Ventanas - Automático.

Esta ventana también permite modificar el contenido de una variable haciendo doble clic en
el valor, introduciendo el valor nuevo y aceptando el cambio mediante la tecla [Intro]. La
aplicación seguirá ejecutándose con ese valor nuevo en la variable.

c. Ventana Variables locales

La ventana Variables locales es accesible desde el mismo menú Depurar -Ventanas -


Variables locales y posee un funcionamiento idéntico a la ventana Automático, pero
muestra todas las variables actuales, es decir, las variables globales y las variables
declaradas en el procedimiento o función en curso de ejecución.

En todas estas ventanas, no es posible controlar la lista de las variables que se muestran, ya
que el depurador determina la lista en función del contexto en el que se encuentra la
aplicación. A veces es más práctico configurar manualmente la lista de las variables y
expresiones que queremos controlar durante el funcionamiento de la aplicación.

d. Las ventanas Inspección

La ventana Inspección permite mostrar las variables que parecen interesantes para la
depuración de la aplicación. Esta ventana, o mejor dicho, estas ventanas, ya que existen
cuatro ventanas Watch, se muestran desde el menú Depurar - Ventanas - Inspección y
luego Inspección 1 a Inspección 4. A continuación debemos configurar la ventana
añadiendo las variables y expresiones que deseamos visualizar. Al hacer doble clic en la
columna Nombre, podemos escribir lo que deseamos visualizar en la ventana. También
podemos efectuar un arrastrar-soltar desde la ventana de código. Si escribimos el nombre
de variable compleja (por ejemplo, una instancia de clase), se visualiza, en forma de árbol,
el conjunto de sus propiedades en la ventana.

Solo se muestra el contenido de las variables si la aplicación está en modo de parada en una
línea de código a partir de la cual se puede acceder a la variable. Por ejemplo, el contenido
de las variables locales de un procedimiento o función solo se muestra si el código se para
en este procedimiento o función.

ShareVideos
En el caso contrario, la ventana Inspección nos indica simplemente que esta variable no
está declarada en la parte de código donde nos encontramos, mostrándola en caracteres
grises.

En este ejemplo, se declara la variable e en un procedimiento distinto al que se está


ejecutando en este momento.

Como para las otras ventanas, se puede modificar el contenido de la variable haciendo
doble clic en ella para pasar al modo de edición y validar la modificación con la tecla
[Intro].

e. La ventana Inspección rápida

La ventana Inspección rápida tiene el mismo principio de funcionamiento y es accesible a


través del menú Depurar - Inspección rápida. En este caso, la variable o la expresión en
la que se encuentra el cursor se visualiza en la ventana Inspección rápida. Al ser una
ventana modal, es obligatorio cerrarla antes de continuar la depuración de la aplicación.

El botón Agregar inspección permite agregar rápidamente la expresión en la ventana


Inspección para poder estudiarla en el resto de la depuración.

4. Las otras ventanas de depuración

Existen otras ventanas de depuración, pero algunas de ellas no son realmente útiles para el
desarrollo de aplicaciones Visual Basic. Se reservan más bien para la prueba de
aplicaciones desarrolladas con otros lenguajes, como C++, por ejemplo.

El siguiento ejemplo trata de la ventana de memoria que permite la visualización del


contenido de una zona de la memoria cuya dirección conocemos.

Si quiere, puede ver el código máquina correspondiente a las instrucciones VB.NET.

Otras técnicas de depuración


La compilación condicional

ShareVideos
Puede utilizar la compilación condicional para especificar fragmentos que código que serán
o no compilados en función del valor de una constante definida previamente. Por ejemplo,
puede probar varias soluciones para resolver un problema utilizando varios algoritmos y
verificar cuál es el más eficaz.

El fragmento de código sometido a la condición debe estar colocado entre las instrucciones
#if condicion Then y #endif. En función del valor de la condición, el fragmento de código
será compilado o no. Por supuesto, es necesario que la variable o las variable(s) utilizada(s)
en la condición sea(n) inicializada(s) antes de su aparición en una instrucción #if.

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e


As System. EventArgs) Handles Button1.Click
Dim i As Integer
#Const version = 2
#If version = 1 Then
For i = 0 To 1000000
calcula(i)
Next
#End If
#If version = 2 Then
Do While i < 100000
calcula(i)
i = i + 1
Loop
#End If

End Sub

Las constantes se pueden declarar con la declaración #const, como en el ejemplo siguiente,
o en las propiedades del proyecto.

Sin embargo, hay que tener cuidado, ya que las constantes declaradas con estos dos
métodos solo se pueden utilizar para la compilacion condicional y no son accesibles desde
el código.

Utilización de la clase Debug

La clase Debug dispone de muchos métodos estáticos, y por lo tanto utilizables sin tener
que crear instancias de la clase, que nos permiten insertar instrucciones en el código para
seguir el desarrollo de la aplicación.

Con el método assert, se afirma que, cuando se ejecute esta línea de código, la condición
especificada en el método será verdadera.

ShareVideos
Debug.Assert(y <> 5)

Si la condición es falsa, Visual Basic muestra un cuadro de diálogo que resume la situación.

Este cuadro de diálogo propone tres soluciones para continuar:

Anular Para la ejecución de la aplicación.


La aplicación pasa a modo de parada en la línea que contiene la
Reintentar
instrucción Assert, permitiendo así intentar analizar la situación.
Omitir La ejecución de la aplicación continúa con normalidad.

El método Fail es más radical, ya que muestra el mismo cuadro de diálogo, pero sin
ninguna condición. Se puede utilizar, por ejemplo, en un bloque Try End Try.

Try
z = 1 / x
Catch ex As Exception
Debug.Fail("división por cero")
End Try

Los métodos Write y WriteLine permiten seguir la evolución de una expresión sin que la
aplicación pase al modo de parada. La instrucción mostrará en la ventana de salida el
resultado de la evaluación de la expresión.

Los métodos Indent y Unindent formatean la visualización en la ventana Resultados.

Debug.WriteLine("principio del bucle")


Debug.Indent()
For x = 1 To 5
Debug.WriteLine("pasada n" & x)
...
...
Next
Debug.Unindent()
Debug.WriteLine("final del bucle")

En la ventana de salida se obtiene el resultado siguiente:

Los diferentes tipos de aplicación


Las aplicaciones de Windows se basan en una o más ventanas que constituyen la interfaz
entre el usuario y la aplicación. Para desarrollar este tipo de aplicación, tenemos a nuestra
disposición en el Framework .NET un conjunto de clases que permiten diseñar la interfaz

ShareVideos
de la aplicación. Estos elementos se recogen frecuentemente bajo el término Tecnología
Windows Forms. Una aplicación basada en Windows Forms utiliza uno o más formularios
para construir la interfaz de usuario de la aplicación. En estos formularios (o ventanas),
colocaremos controles, para definir exactamente el aspecto de la interfaz de la aplicación.
Los formularios se crean a partir de clases del Framework .NET que se podrán especializar
añadiendo funcionalidades. El formulario así creado es en sí una clase y será posible
utilizarlo en otra aplicación añadiéndole funcionalidades adicionales mediante una relación
de herencia. Se pueden crear los Windows Forms directamente con el código, pero el
entorno de desarrollo Visual Studio proporciona una serie de herramientas gráficas para
facilitarnos la tarea. Utilizaremos principalmente esta técnica.

1. Modalidades de presentación de las ventanas

En una aplicación de Windows, existen tres estilos de presentación para sus ventanas.

a. Interfaz monodocumento (SDI)

Solo hay una ventana disponible en la aplicación. Para poder abrir un nuevo documento, es
necesario cerrar el documento activo de la aplicación. El bloc de notas de Windows es una
aplicación SDI.

b. Interfaz multidocumento (MDI)

La aplicación está constituida por una ventana principal (la ventana madre), en la que
aparecerán varias ventanas (ventanas hijas) que contienen los documentos en los que se va
a trabajar. Por regla general, este tipo de aplicación dispone de un menú que permite la
organización de las diferentes ventanas hijas. Este tipo de presentación se utiliza en la
mayoría de las aplicaciones ofimáticas.

c. Interfaz de estilo explorador

Este estilo de interfaz se desarrolla cada vez más con respecto a las otras dos. En este caso,
se divide la ventana en dos zonas. La zona de la izquierda presenta en forma de árbol los
elementos que la aplicación puede manipular. La zona de la derecha presenta el elemento
seleccionado en el árbol y permite su modificación. Muchas herramientas administrativas
utilizan esta presentación.

Las ventanas en VB.NET

ShareVideos
Cuando comience una nueva aplicación de Windows Forms, el entorno de desarrollo añade
automáticamente un formulario al proyecto. Este formulario sirve de punto de partida para la
aplicación. Puede lanzar inmediatamente la ejecución de la solución y todo funciona. Obviamente,
la aplicación no permite hacer mucho, pero tiene todas las funcionalidades de una aplicación de
Windows y todo ello sin escribir una sola línea de código. En realidad, existe algo de código que
corresponde a esta aplicación, pero ha sido generado automáticamente por Visual Studio. Puesto
que este código nunca se debe modificar manualmente, los archivos que lo contienen están
ocultos en el explorador de soluciones. Para mostrarlos, puede utilizar el botón de la barra de
herramientas del explorador de soluciones. Podrá constatar que ya existen muchos archivos en el
proyecto. Todos los archivos reservados de Visual Studio tienen la extensión .designer.vb. Por
supuesto, se puede visualizar el contenido de estos archivos.

Este, por ejemplo, es el contenido del archivo Application.designer.vb.

’-------------------------------------------------
’ <auto-generated>
’ This code was generated by a tool.
’ Runtime Version:2.0.50727.1433

’ Changes to this file may cause incorrect behavior and will be
’ lost if the code is regenerated.
’ </auto-generated>
’--------------------------------------------------

Option Strict On
Option Explicit On

Namespace My

’NOTE: This file is auto-generated; do not modify it directly.


’ To make changes, or if you encounter build errors in this file,
’ go to the Project Designer (go to Project Properties or
’ double-click the My Project node in Solution Explorer),
’ and make changes on the Application tab.

Partial Friend Class MyApplication

<Global.System.Diagnostics.DebuggerStepThroughAttribute()> _
Public Sub New()
MyBase.New(Global.Microsoft.VisualBasic.ApplicationServices.Authenticatio
n
Mode.Windows)
Me.IsSingleInstance = false
Me.EnableVisualStyles = true
Me.SaveMySettingsOnExit = true
Me.ShutDownStyle =
Global.Microsoft.VisualBasic.ApplicationServices.ShutdownMode.AfterMainFo
rm
Closes
End Sub

ShareVideos
<Global.System.Diagnostics.DebuggerStepThroughAttribute()> _
Protected Overrides Sub OnCreateMainForm()
Me.MainForm = Global.WindowsApplication1.Form1
End Sub
End Class
End Namespace

Los comentarios colocados en este archivo son muy claros: ¡nunca modifique este archivo
manualmente! Se actualiza de forma automática después de cada modificación de las
propiedades del proyecto. El segundo archivo, actualizado automáticamente, se asocia a la
ventana de la aplicación. En nuestra primera aplicación, se denomina form1.designer.vb.
Contendrá la descripción de todas las acciones, traducidas al código VB, que se van a
realizar para personalizar las características de la ventana.

Veamos el contenido de este archivo:

<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
Inherits System.Windows.Forms.Form

’Form overrides dispose to clean up the component list.


<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub

’Required by the Windows Form Designer


Private components As System.ComponentModel.IContainer

’NOTE: The following procedure is required by the Windows Form


Designer
’It can be modified using the Windows Form Designer.
’Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
components = New System.ComponentModel.Container()
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.Text = "Form1"
End Sub

Contiene la definición de la clase correspondiente a la ventana que hereda de la clase


System.Windows.Forms.Form. Esta definición conlleva una pequeña particularidad con
respecto a la definición de una clase, tal y como hemos visto en el capítulo dedicado a la
programación orientada a objetos. Delante del nombre de la clase se especifica la palabra
clave Partial. Esta palabra clave le indica al compilador que el archivo solo contiene una
parte de la definición de la clase, mientras que la otra parte está disponible en el archivo
Form1.vb. Esta técnica permite repartir los papeles de cada uno:

ShareVideos
 Visual Studio se encarga de generar, dentro del archivo form1.designer.vb, el
código correspondiente a la personalización del aspecto de la ventana.
 El usuario es responsable del código, contenido en el archivo form1.vb, encargado
de la personalización del funcionamiento de la ventana.

Esta solución limita los riesgos de modificación involuntaria de la parte de código


reservada a Visual Studio. El elemento más importante está constituido por el método
InitializeComponent. Este método es llamado automáticamente durante la creación de una
instancia de la ventana, durante la llamada del constructor. Para estar seguro de ello, puede
añadir un constructor por defecto en el archivo Form1.vb y constatar que Visual Studio lo
convierte automáticamente añadiéndole una llamada al método.

Public Class Form1


Public Sub New()
’ This call is required by the Windows Form Designer.
InitializeComponent()
’ Add any initialization after the InitializeComponent() call.
End Sub

Por el contrario, si añade un constructor sobrecargado, usted es el responsable de esa


llamada. Lo más sencillo en ese caso es hacer la llamada al constructor, por defecto, en la
primera línea de su constructor sobrecargado.

Public Sub New(ByVal i As Integer)


MyClass.New()
End Sub

Piense también en respetar en el constructor por defecto el emplazamiento que tiene


reservado para sus inicializaciones personales. Si se colocan antes de la llamada al método
InitializeComponent, este podría modificarlas. Antes de la invocación del método
InitializeComponent, los elementos gráficos de la ventana no están disponibles, ya que el
papel principal de este método consiste en crearlos e inicializar algunas de sus propiedades.

Para que sea posible eliminar todos los objetos instanciados por la clase, se crea un método
Dispose. Este método comienza eliminando los objetos creados; luego llama al método
Dispose de la clase madre.

’Form overrides dispose to clean up the component list.


<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
MyBase.Dispose(disposing)
End Sub

Hemos repasado el código generado automáticamente. Veamos ahora cómo modificar el


aspecto y el comportamiento de nuestra ventana mediante estas propiedades.

ShareVideos
1. Dimensión y posición de las ventanas

La posición de la ventana en la pantalla (o en su contenedor) se modifica con la propiedad


Location. Esta propiedad es una estructura compuesta por dos miembros. Indica las
coordenadas del ángulo superior izquierdo de la ventana con respeto al ángulo superior
izquierdo de la pantalla. Los miembros de esta estructura se pueden modificar en la ventana
de propiedades de Visual Basic. De hecho, cuando usted modifica propiedades, se añade
código para tener en cuenta sus modificaciones. Por ejemplo, si quisiéramos que nuestra
ventana apareciera en las coordenadas 100, 100, deberíamos modificar la propiedad
Location.

Si miramos el código, encontraremos la modificación de nuestra propiedad.

Me.Location = New System.Drawing.Point(100, 100)

Las dimensiones de la ventana se pueden modificar con la propiedad size, que contiene dos
miembros Width y Height que indican la anchura y la altura de la ventana.

Vamos a resumir todo con un pequeño esquema:

Las unidades son píxeles para todas las propiedades relativas a las dimensiones y
posiciones de objetos. Las propiedades Left, Top, Height y Width están disponibles en el
código, pero no en la ventana de propiedades. La correspondencia con las propiedades
Location y Size de estas propiedades se indica entre paréntesis en el esquema.

Estas propiedades se actualizan durante la ejecución de la aplicación si la ventana se


desplaza o se redimensiona. Son accesibles a través del código de la aplicación.

El ancho y el largo de la ventana pueden variar dentro de los límites fijados por las
propiedades MinimumSize y MaximumSize. Por defecto, estas dos propiedades se
inicializan a 0. En este caso, el 0 indica que no hay un límite fijo para el tamaño de la
ventana.

Otras dos propiedades indican el comportamiento de la ventana durante el arranque de la


aplicación.

La propiedad StartPosition permite imponer una posición a la ventana, al iniciarse la


aplicación. Los posibles valores se resumen en la siguiente tabla:

Valor de la propiedad Efecto en la ventana


Manual Las propiedades Location y Size se utilizan para

ShareVideos
mostrar la ventana. La
propied
CenterParent La ventana está centrada en la ventana madre.
ad
CenterScreen La ventana está centrada en la pantalla. Windo
El sistema coloca automáticamente las ventanas a partir wState
de la esquina superior izquierda de la pantalla. Las indica
WindowsDefaultLocation ventanas bajan hacia la derecha de la pantalla cada vez el
que se muestra una nueva ventana. Las dimensiones de estado
cada ventana se especifican en la propiedad Size. de la
ventana
Mismo principio que en el caso anterior, pero el
. Los
WindowsDefaultBounds sistema determina las dimensiones cuando se muestra
tres
la ventana.
valores
posibles son:

Valor de la propiedad Estado de la ventana Por supuesto, se


puede modificar estas
Normal Dimensiones definidas por la propiedad Size. propiedades con el
Minimized Ventana minimizada en la barra de tareas. código de la
Maximized Ventana en toda la pantalla. aplicación. Sin
embargo, es más
eficaz utilizar los métodos SetLocation y SetSize que permiten redimensionar y colocar la
ventana directamente. La utilización de estos métodos o la manipulación directa de las
propiedades activan los eventos Resize y Move en la ventana correspondiente.

El siguiente código nos permite seguir la posición y las dimensiones de la ventana:

Private Sub Form1_Resize(ByVal sender As Object, ByVal e As System.Event


Args)Handles MyBase.Resize

System.Console.WriteLine(" Mi anchura: " & Me.Size.Width)


System.Console.WriteLine(" Mi altura: " & Me.Size.Height)
End Sub
Private Sub Form1_Move(ByVal sender As Object, ByVal e As
System.EventArgs)
Handles MyBase.Move
System.Console.WriteLine(" Estoy en la posición X:" & Me.Location.X )
System.Console.Writeline(" Estoy en la posición Y:" & Me.Location.Y)
End Sub

Obtenemos la siguiente información:

Estoy en la posición X:263


Estoy en la posición Y:311
Mi anchura: 364
Mi altura: 122
Una pequeña curiosidad para acabar con las dimensiones y la posición de las ventanas. Si
reducimos nuestra ventana a icono, haciendo clic en el botón Minimizar o modificando la
propiedad WindowState, obtenemos los valores siguientes:

ShareVideos
Estoy en la posición X:-32000
Estoy en la posición Y:-32000
Mi anchura: 160
Mi altura: 24

¡Las posiciones X e Y de la ventana son valores negativos! En realidad, puede usar valores
que estén dentro del límite de los valores aceptables para un entero. Solo se verá la parte de
su ventana que está entre el cero y la anchura y altura de su pantalla. Puede utilizar el
método GetBounds del objeto Screen para obtener las dimensiones de la pantalla.

Private Sub Form1_Move(ByVal sender As Object, ByVal e As


System.EventArgs)
Handles MyBase.Move

System.Console.WriteLine(" Estoy en la posición X:" &


Me.Location.X)
System.Console.WriteLine(" Estoy en la posición Y:" &
Me.Location.Y)
System.Console.Write(" en una pantalla de:" &
Screen.GetBounds(Me).Size.Width)
System.Console.WriteLine(" por " &
Screen.GetBounds(Me).Size.Height)
End Sub

Este código nos permite conocer las dimensiones de la pantalla que muestra la aplicación.

Estoy en la posición X: 115


Estoy en la posición Y: 203
en una pantalla de: 1024 por 768

Para que el usuario pueda desplazar o modificar las dimensiones de la ventana, debe
disponer de las herramientas necesarias:

 Una barra de título para poder coger la ventana y desplazarla.


 Un borde para poderla redimensionar.
 Botones para poder hacerla más grande, más pequeña y mostrarla con las
dimensiones normales.

Para poder restaurar la ventana, esta debe disponer de un borde «sizable» asignado a su
propiedad FormBorderStyle.

Para poder ser desplazada, una ventana debe tener una barra de título. Esta barra de título se
puede ocultar con la propiedad ControlBox colocada en Falso. En ese caso, no se muestra
ni siquiera el título de la ventana especificado por la propiedad Text. Si la barra de título es
visible, sus diferentes botones se pueden controlar con las siguientes propiedades:

MinimizeBox Mostrar o no el botón de minimizar de la ventana.


MaximizeBox Mostrar o no el botón de maximizar de la ventana.
HelpButton Mostrar el botón de ayuda. Visible solo si los dos botones

ShareVideos
anteriores no son visibles.

2. Colores y fuente de letra utilizados en las ventanas

La propiedad BackColor indica el color de fondo utilizado en la ventana. Este color


también se usa para todos los controles que se colocarán, a partir de ahora, en la ventana.
La propiedad ForeColor indica el color de los elementos que se colocarán directamente en
la ventana o el color de la leyenda de los controles colocados en ella. Existen cuatro
posibilidades para asignar un valor a estas propiedades de color:

 Por la ventana de propiedades, eligiendo un color en la pestaña Personalizado.


 Por la ventana de propiedades, eligiendo un color web. Estos colores corresponden a
los colores disponibles en el lenguaje HTML.
 Por la ventana de propiedades, eligiendo un color del sistema. En este caso, su
aplicación se adaptará automáticamente al entorno del puesto de trabajo en el que
está instalada. Si el usuario ha configurado su puesto para que los botones sean de
color rosa fosforito, encontrará el mismo aspecto en su aplicación.
 Creando su propio color con un poco de rojo, un poco de verde y un poco de azul.
Para mezclar todo y obtener el color final, utilice el método FromARGB, que toma
como parámetro la cantidad de rojo, de verde y de azul y proporciona el color
resultante. Las cantidades de cada color son valores que están entre el 0 y el 255.
Este último valor se corresponde a un color puro.

La propiedad Opacity permite regular la transparencia de su ventana. El valor debe estar


entre el cero (ventana transparente) y el uno (ventana opaca). Cuidado: solo algunos
sistemas tienen en cuenta esta propiedad (Windows 2000, Windows XP, Windows 2003,
Vista). También puede crear un fondo especial indicando una imagen de fondo para su
ventana con la propiedad BackgroundImage. Si la imagen no es suficientemente grande
para cubrir la ventana, se reproduce en mosaico.

Del mismo modo, puede especificar que un color se considere transparente en su ventana.
Para hacerlo, debe asignar a la propiedad TransparencyKey el valor de ese color.

Para explicar la utilización poco clara de esta propiedad, hemos indicado en la ventana
siguiente que el color blanco era transparente (se ve una parte de la ventana de propiedades
a través de la zona de texto).

La propiedad Font permite especificar las características de la fuente de los caracteres, y se


utiliza para mostrar el texto directamente en la ventana. Esta fuente se utiliza también para
todos los controles que se colocarán en la ventana. Puede modificar las propiedades
directamente en la ventana de propiedades, abriendo la propiedad Font y haciendo clic en el
signo más + delante de la propiedad.

ShareVideos
También puede modificar las características de la fuente con el cuadro de diálogo estándar para
elegir la fuente. Este se muestra utilizando el botón , desde la propiedad Font en la ventana de
propiedades.

3. Las ventanas MDI

Las aplicaciones MDI están constituidas por dos tipos de ventanas:

 Las ventanas madres.


 Las ventanas hijas.

En VB.NET, se utiliza la misma clase base para los dos tipos de ventana. En el primer caso,
se indica simplemente que la ventana es una ventana madre MDI colocando en True su
propiedad IsMdiContainer Para añadir después una ventana hija, conviene por supuesto
crear primero la ventana y después asociarla a una ventana madre a través de su propiedad
MdiParent.

Este es un código que crea tres ventanas y las convierte en ventanas hijas MDI:

Dim ventana1 As Form


Dim ventana2 As Form
Dim ventana3 As Form

ventana1 = New Form()


ventana1.Text = "ventana 1"
ventana1.MdiParent = Me
ventana1.Show()
ventana2 = New Form()
ventana2.Text = "ventana 2"
ventana2.MdiParent = Me
ventana2.Show()
ventana3 = New Form()
ventana3.Text = "ventana 3"
ventana3.MdiParent = Me
ventana3.Show()

Para obtener ventanas hijas bien colocadas en su ventana madre, puede utilizar el método
LayoutMdi pasándole como parámetro una de las constantes predefinidas de la
enumeración MdiLayout:

Me.LayoutMdi(MdiLayout.TileHorizontal)

Me.LayoutMdi(MdiLayout.TileVertical)

Me.LayoutMdi(MdiLayout.Cascade)

ShareVideos
Se suele llamar a estos diferentes métodos mediante un menú de la aplicación que
proporciona la lista de las ventanas abiertas en la aplicación. Veremos cómo hacerlo en la
sección dedicada a los menús.

Para ilustrar otras posibles acciones con las ventanas MDI, vamos a realizar una aplicación
del estilo explorador. Veamos a continuación el aspecto general de la aplicación.

En la parte izquierda, vemos en el árbol los documentos disponibles de la aplicación. La


zona derecha se adapta, en función de la selección hecha sobre el árbol, para mostrar la
imagen o el texto de una receta. Debemos crear tres tipos diferentes de ventanas:

 La ventana principal, que va a contener el control TreeView, y el conjunto de


ventanas encargadas de visualizar los documentos.
 Una ventana para mostrar las imágenes.
 Una ventana para mostrar el texto.

Creemos la ventana principal:

Modifique la propiedad IsMdiContainer a True para activar la funcionalidad de ventana


madre MDI.

Agregue un control TreeView.

Modifique la propiedad Dock del control TreeView en Left para que se ajuste al borde
izquierdo de la ventana.

Agregue los elementos al control TreeView con la ayuda del editor de nodos.

En nuestra aplicación, la propiedad Name de los nodos raíz se utiliza para determinar el
tipo de documento (tx para archivo texto, gr para archivo gráfico). En el resto de los nodos
del árbol, guarda el nombre del archivo respectivo.

Las ventanas hijas también son fáciles de configurar.

Para la ventana gráfica:

Establezca la propiedad BorderStyle en none.

ShareVideos
Agregue un control PictureBox.

Establezca la propiedad Dock de este control en Fill para que ocupe toda la superficie
disponible de la ventana.

Establezca la propiedad SizeMode de dicho control en StretchImage para que la imagen se


adapte al tamaño del control (dentro de la ventana).

Para la ventana de texto:

Establezca la propiedad BorderStyle en none.

Agregue un control RichtextBox.

Establezca la propiedad Dock de este control en Fill para que ocupe toda la superficie
disponible de la ventana.

Nos resta ahora escribir las líneas de código para mostrar la ventana correcta al efectuar una
selección sobre el control TreeView. A continuación presentamos dichas líneas de código:

Private Sub TreeView1_AfterSelect(ByVal sender As System.Object, ByVal e


As System.Windows.Forms.TreeViewEventArgs) Handles TreeView1.AfterSelect
If Not IsNothing(e.Node.Parent) Then
Dim f As Form
For Each f In Me.MdiChildren
f.Close()
Next
Select case e.Node.Parent.Name
case "gr"
Dim fGr As Grafico
fGr = New Grafico
fGr.MdiParent = Me
fGr.Show()
fGr.Dock = DockStyle.Fill
fGr.PictureBox1.Image = Image.FromFile("../../" & e.Node.Name)
case "tx"
Dim fTx As Texto
fTx = New Texto
fTx.MdiParent = Me
fTx.Show()
fTx.Dock = DockStyle.Fill
fTx.RichTextBox1.LoadFile("../../" & e.Node.Name)
End Select
End If
End Sub

La totalidad del código del manejador de eventos TreeView1_AfterSelect se invoca


automáticamente cuando el usuario selecciona un elemento del control TreeView. Nuestro
primer trabajo consiste en verificar si el nodo seleccionado es un nodo hijo. Si este es el
caso, cerramos todas las ventanas hijas ya presentes (por medio de este mecanismo el
usuario solo podrá ver una ventana a la vez). Entonces verificamos el tipo de documento

ShareVideos
solicitado consultando la propiedad Name del nodo Padre del elemento seleccionado (gr o
tx). En función del resultado, creamos una instancia de la ventana acorde a la situación.
Establecemos el enlace de parentesco con la ventana principal (propiedad MdiParent). La
ventana mostrada ocupará toda la superficie libre de la ventana madre Mdi (propiedad
Dock=DockStyle.Fill). Por último, mostramos el documento con el control RichTextBox o
PictureBox, según corresponda.

Los eventos de teclado y de ratón


La gestión del teclado y del ratón se realiza exclusivamente utilizando los eventos que estos
dos periféricos son capaces de activar. La mayoría de los controles pueden gestionar estos
eventos. Necesitamos simplemente conocer en qué momento se activan y qué datos
proporcionan.

1. Los eventos de teclado

Las posibilidades de acción del usuario en el teclado son limitadas: puede simplemente
pulsar una tecla del teclado.

En Visual Basic.NET, esta acción se descompone en tres eventos distintos:

KeyDown

Este evento se produce cuando se pulsa la tecla.

KeyUp

Este evento se produce cuando se suelta la tecla.

KeyPress

Este evento se produce cuando se pulsa la tecla, pero solo si esta corresponde a un carácter
ASCII.

En los eventos KeyDown y KeyUp, un parámetro del tipo KeyEventArgs proporciona


información adicional sobre el evento.

Tenemos las siguientes propiedades:

 [Alt] indica el estado de la tecla [Alt] del teclado en el momento en que se produce
el evento.
 [Ctrl] y [Mayús] proporcionan datos iguales para las teclas [Ctrl] y [Mayús].
 KeyCode indica el número de la tecla en el teclado.

ShareVideos
El evento KeyDown se utiliza principalmente para trabajar con las teclas de función.
Podemos, por ejemplo, convertir en mayúsculas el texto de un control TextBox si el usuario
pulsa la combinación de teclas [Mayús][Ctrl][Alt][F8].

Private Sub TextBox1_KeyDown(ByVal sender As Object, ByVal e As


System.Windows.
Forms.KeyEventArgs) Handles TextBox1.KeyDown
If e.Alt And e.Control And e.Shift And (e.KeyCode = Keys.F8)
Then
TextBox1.Text = UCase(TextBox1.Text)
End If
End Sub

Otra solución sería utilizar la propiedad Modifiers, que recoge el estado de las teclas
[Mayús], [Ctrl] y [Alt].

Private Sub TextBox1_KeyDown(ByVal sender As Object, ByVal e


As System.Windows. Forms.KeyEventArgs) Handles TextBox1.KeyDown
If e.Modifiers = Keys.Shift + Keys.Control + Keys.Alt And
e.KeyCode = Keys.F8 Then
TextBox1.Text = UCase(TextBox1.Text)
End If
End Sub

Finalmente, la última posibilidad consiste en utilizar la propiedad KeyData, que recoge el


estado de las teclas [Mayús], [Ctrl], [Alt] y una tecla de función.

Private Sub TextBox1_KeyDown(ByVal sender As Object, ByVal e As


System.Windows.Forms.KeyEventArgs) Handles TextBox1.KeyDown
If e.KeyData = Keys.Shift + Keys.Control + Keys.Alt + Keys.F8
Then
TextBox1.Text = UCase(TextBox1.Text)
End If
End Sub

El evento KeyPress nos indica simplemente, a través del parámetro e de tipo


KeyPressEventArgs, el carácter que se acaba de coger. Por ejemplo, podemos utilizar este
evento para verificar que todos los caracteres introducidos sean numéricos. Si no es así, se
emite un beep.

Private Sub TextBox1_KeyPress(ByVal sender As Object, ByVal e As


System.Windows.Forms.KeyPressEventArgs) Handles TextBox1.KeyPress
If Not IsNumeric(e.KeyChar) Then
Beep()
End If
End Sub

Este procedimiento informa simplemente al usuario de que ha elegido un carácter no


válido, pero el carácter aparece de todos modos en la zona del texto. Sería mejor evitar que
el carácter entrara en la zona del texto. La propiedad Handled del parámetro permite,
asignándole el valor true, indicar que el evento de teclado se acaba de gestionar y que el
control no debe tomarlo en consideración.

ShareVideos
Private Sub TextBox1_KeyPress(ByVal sender As Object, ByVal e As
System.Windows.Forms.KeyPressEventArgs) Handles TextBox1.KeyPress
If Not IsNumeric(e.KeyChar) Then
Beep()
e.Handled = True
End If
End Sub

Si muchos controles TextBox deben tener el mismo funcionamiento, puede copiar este
código en los eventos KeyPress de cada control. Una solución más elegante nos permite
centralizar el tratamiento en el evento KeyPress de la ventana. Para hacerlo, conviene
indicar a la propiedad KeyPreview de la ventana que recibirá los eventos de teclado antes
que el control de la ventana focalizada. Solo hace falta probar el control activo de la
ventana y verificar que «reciba» el tratamiento.

Private Sub TextBox1_KeyPress(ByVal sender As Object, ByVal e As


System.
Windows.Forms.KeyPressEventArgs) Handles TextBox1.KeyPress
If Me.ActiveControl Is TextBox1 Then
If Not IsNumeric(e.KeyChar) Then
Beep()
e.Handled = True
End If
End If
End Sub

2. Los eventos de ratón

Estos eventos están relacionados con la utilización de los botones o de la rueda del ratón. El
evento Click es el evento de ratón más utilizado. Consiste en pulsar y luego soltar el botón
principal del ratón. Es necesario hablar de botón principal del ratón, ya que, en función de
la configuración del puesto de trabajo, este botón principal puede ser también el botón
derecho (configuración para zurdos). El evento DoubleClick no funciona con todos los
controles, así que, por ejemplo, los controles Button no son capaces de gestionar el doble
clic y de hecho generan dos eventos Click seguidos.

Existen también eventos de ratón más elementales:

 MouseDown cuando se pulsa un botón del ratón.


 MouseUp cuando se suelta un botón del ratón.
 MouseWheel cuando se utiliza la rueda del ratón.

Para estos tres eventos, existe un parámetro de tipo MouseEventArgs. A través de las
propiedades disponibles en esta clase, se obtienen los siguientes datos:

 El botón origen del evento con la propiedad Button.


 El número de veces que el botón es pulsado o se deja de pulsar con la propiedad
Clicks.
 El número de movimientos de la rueda con la propiedad Delta. Esta propiedad es un
entero positivo o negativo, que sigue el sentido de rotación de la rueda. Cada

ShareVideos
desplazamiento de una posición de la rueda aumenta o disminuye esta propiedad
con un valor de 120.
 Las propiedades X e Y indican el emplazamiento en el control en el que el evento
de ratón se acaba de producir.

Los desplazamientos del ratón generan cuatro eventos:

 MouseEnter cuando se pone el ratón encima de un control.


 MouseMove cuando el ratón se desplaza sobre el control.
 MouseLeave cuando el ratón deja la superficie del control.
 MouseHover cuando el ratón se queda encima de un control durante un segundo.

Como para los eventos relativos a los botones del ratón, se nos proporciona un parámetro
del tipo MouseEventArgs en estos eventos.

Un pequeño ejemplo para probarlo con una aplicación de diseño muy sencillo.

A cada desplazamiento del ratón, se dibuja un punto en la ventana con el color indicado por
las tres zonas de texto (rojo, verde, azul). Para modificar el color, es suficiente desplazar el
ratón encima de la zona de texto correspondiente (se focaliza automáticamente gracias al
evento MouseHover) y accionar la rueda del ratón para aumentar o disminuir el porcentaje.

Public Class Telepantalla


Dim rojo, verde, azul As Integer
Private Sub telepantalla_MouseMove(ByVal sender As Object, ByVal e
As
System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
Dim g As Graphics
Dim lapiz As Pen
If e.Button = MouseButtons.Left Then
g = Graphics.FromHwnd(Me.Handle)
lapiz = New Pen(Color.FromArgb(rojo * 2.55, verde * 2.55, azul
* 2.55))
g.DrawEllipse(lapiz, e.X, e.Y, 1, 1)
End If
End Sub
Private Sub txtRojo_MouseWheel(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles txtRojo.MouseWheel
rojo += e.Delta / 120
If rojo > 100 Then
rojo = 100
txtRojo.Text = "100%"
ElseIf rojo < 0 Then
rojo = 0
txtRojo.Text = "0%"
Else
txtRojo.Text = rojo & "%"
End If
End Sub

ShareVideos
Private Sub txtVerde_MouseWheel(ByVal sender As Object, ByVal e As
System.Windows.Forms.MouseEventArgs) Handles txtVerde.MouseWheel
verde += e.Delta / 120
If verde > 100 Then
verde = 100
txtVerde.Text = "100%"
ElseIf rojo < 0 Then
verde = 0
txtVerde.Text = "0%"
Else
txtVerde.Text = verde & "%"
End If
End Sub

Private Sub txtAzul_MouseWheel(ByVal sender As System.Object, ByVal


e
As System.Windows.Forms.MouseEventArgs) Handles txtAzul.MouseWheel
azul += e.Delta / 120
If azul > 100 Then
azul = 100
TxtAzul.Text = "100%"
ElseIf azul < 0 Then
azul = 0
TxtAzul.Text = "0%"
Else
TxtAzul.Text = azul & "%"
End If
End Sub
Private Sub txtRojo_MouseHover(ByVal sender As Object, ByVal e As
System.EventArgs) Handles txtRojo.MouseHover
txtRojo.Focus()
End Sub

Private Sub txtVerde_MouseHover(ByVal sender As Object, ByVal e As


System.EventArgs) Handles txtVerde.MouseHover
txtVerde.Focus()
End Sub

Private Sub txtAzul_MouseHover(ByVal sender As System.Object, ByVal


e
As System.EventArgs) Handles txtAzul.MouseHover
TxtAzul.Focus()
End Sub
End Class

3. El Drag and Drop

El Drag and Drop es una funcionalidad muy útil en las aplicaciones de Windows. Permite
el desplazamiento de datos en una aplicación o entre aplicaciones, «pegando» un elemento
al cursor del ratón y dejándolo después encima de su destino.

Se puede pegar prácticamente cualquier elemento al cursor del ratón para luego desplazarlo
o copiarlo (texto, imagen, archivo...). Para guiar al usuario sobre esas posibilidades, el
cursor del ratón se modifica en función de los controles que se sobrevuelan con el ratón.

ShareVideos
Una operación de Drag and Drop se desarrolla en tres fases:

 Pegar un elemento al ratón.


 Desplazar el ratón hacia un destino.
 Soltar encima del destino.

Durante esta última operación, tenemos dos posibilidades:

 El elemento pegado al ratón se desplaza. En este caso, desaparece del control de


partida en el momento en que se le deja encima del control de destino.
 Se copia el elemento pegado al ratón. En este caso, lo que se deja encima del control
de destino es una copia.

Vamos a ver en detalle el código necesario para realizar con éxito una operación de Drag
and Drop. Tomemos un ejemplo sencillo, realizando una copia entre dos zonas de texto
(TxtOrigen, TxtDestino).

a. Inicio del Drag and Drop

Una operación de Drag and Drop se suele iniciar cuando el ratón se mueve encima de un
control y lo selecciona sin dejar de pulsar el botón del ratón. En este caso, el usuario acaba
de pegar el control al ratón.

En el código, esto se traduce simplemente con la llamada del método DoDragDrop del
control de partida. La llamada de este método necesita dos parámetros:

 El elemento que pegamos al cursor del ratón (cadena de caracteres, una imagen de
bitmap o una imagen metafile).
 Las operaciones autorizadas para el objeto seleccionado (copia, desplazamiento...).

Private Sub txtorigen_MouseMove(ByVal sender As Object, ByVal e As


System.
Windows.Forms.MouseEventArgs) Handles txtorigen.MouseMove
If e.Button = MouseButtons.Left Then
txtorigen.DoDragDrop(txtorigen.Text, DragDropEffects.Move +
DragDropEffects.Copy)
End If
End Sub

b. Configuración de los controles para la recepción

Conviene antes que nada configurar los controles de destino para que acepten la recepción
de un elemento modificando la propiedad AllowDrop a true.

Después, debemos gestionar el evento DragEnter que se produce cuando el ratón entra en la
superficie del control con un elemento «pegado». En la gestión de este evento, se debe
determinar qué es lo que está pegado al cursor del ratón para saber si el control lo puede

ShareVideos
aceptar. Finalmente, hay que determinar si el usuario desea desplazar o hacer una copia del
elemento pegado al cursor del ratón.

Para hacerlo en el código, tenemos a nuestra disposición el parámetro e en el evento


DragEnter, que es una instancia de la clase DragEventArg. Esta clase nos proporciona
muchas propiedades para ayudarnos en nuestras decisiones:

AllowedEffect

Permite saber cuáles son las operaciones autorizadas por el control de origen del Drag and
Drop.

Data

Contiene los datos pegados al cursor del ratón. A través de esta propiedad, se puede obtener
el tipo de dato llamando al método GetDataPresent u obtener los datos llamando al método
GetData.

Effect

Indica la acción autorizada por el control de destino. Esta propiedad se utiliza para
controlar el aspecto del cursor del ratón.

KeyState

Indica el estado de las teclas [Mayús], [Ctrl], [Alt] que nos permiten saber si el usuario
desea realizar un desplazamiento o una copia.

X, Y

Indica las coordenadas del ratón en el control.

Private Sub txtdestino_DragEnter(ByVal sender As Object, ByVal e As


System.
Windows.Forms.DragEventArgs) Handles txtdestino.DragEnter
’¿qué lleva pegado?
If e.Data.GetDataPresent(DataFormats.Text) Then
’¿está la tecla Ctrl pulsada?
If (e.KeyState And 8) = 8 Then
’ cursor copia
e.Effect = DragDropEffects.Copy
Else
’ cursor mueve
e.Effect = DragDropEffects.Move
End If
End If
End Sub

ShareVideos
c. Recuperación del elemento pegado

Cuando el usuario deja de pulsar el botón del ratón, se produce el evento DragDrop en el
control de destino. En este evento, hay que recuperar el elemento pegado y colocarlo en el
control de destino. Si se trata de un desplazamiento, también se debe eliminar la
información del control fuente. Problema: ¿quién es el control fuente? No tenemos
información sobre su identidad. La solución consiste en almacenar en una variable una
referencia hacia el control de origen de la operación de Drag and Drop al principio de la
operación.

Dim ctrlOrigen As TextBox

Private Sub txtorigen_MouseMove(ByVal sender As Object, ByVal e As


System.
Windows.Forms.MouseEventArgs) Handles txtorigen.MouseMove
If e.Button = MouseButtons.Left Then
ctrlOrigen = txtorigen
txtorigen.DoDragDrop(txtorigen.Text, DragDropEffects.Move +
DragDropEffects.Copy)
End If
End Sub
Private Sub txtdestino_DragDrop(ByVal sender As Object, ByVal e As
System.
Windows.Forms.DragEventArgs) Handles txtdestino.DragDrop
’ recuperamos la información almacenada en el control de destino
txtdestino.Text = e.Data.GetData(DataFormats.Text)
’ comprobamos si es una copia o un desplazamiento
If (e.KeyState And 8) <> 8 Then
’ borramos del control origen
ctrlOrigen.Clear()
End If
End Sub

Los cuadros de diálogo


Los cuadros de diálogo son ventanas que tienen una función especial dentro de la
aplicación. Se suelen utilizar para pedir al usuario que introduzca datos. Para asegurarse de
que esa información está bien recuperada antes de continuar con la ejecución de la
aplicación, los cuadros de diálogo se muestran a menudo en modalidad modal, es decir, que
el resto de la aplicación está bloqueada mientras el cuadro de diálogo queda visualizado. Es
frecuente que en una aplicación se necesite la misma información: un nombre de archivo
para abrir, una fuente de letra, etc. Para evitarnos tener que crear cada vez un nuevo cuadro
de diálogo, tenemos a nuestra disposición una serie de cuadros de diálogo predefinidos.

1. El cuadro de recogida

El cuadro de recogida permite pedirle al usuario que introduzca una cadena de caracteres.
Se puede acceder a esta posibilidad por medio de la función InputBox. Esta función espera
tres parámetros:

ShareVideos
 El texto mostrado en el cuadro (en general, la pregunta hecha al usuario).
 El título del cuadro de diálogo.
 Un valor predeterminado mostrado en la zona de introducción de texto.

La función devuelve una cadena de caracteres que corresponde al texto introducido por el
usuario, si este valida lo tecleado con el botón Aceptar. Por el contrario, devuelve una
cadena de caracteres vacía.

Dim resultado As String


resultado = InputBox("Introduzca sus apellidos", "identificación",
"apellidos")

2. El cuadro de mensaje

Los cuadros de mensaje permiten mostrar una información al usuario y le dan la posibilidad
de contestar a través de botones de comando del cuadro de mensaje.

El cuadro de mensaje está disponible a través del método Show, en la clase MessageBox.
Este método acepta muchos parámetros para configurar el cuadro de diálogo. El primer
parámetro corresponde al mensaje mostrado. El parámetro siguiente especifica el título del
cuadro de mensaje. Los siguientes parámetros deben ser elegidos entre las constantes
predefinidas para indicar respectivamente:

 Los botones disponibles en el cuadro de mensaje.


 El icono mostrado en el cuadro de mensaje.
 El botón seleccionado por defecto al mostrar el cuadro de mensaje.

Las constantes disponibles son:

 Para la elección de los botones:

Constante Significado  P
ara la
MessageBoxButtons.OK Botón OK solo elecció
MessageBoxButtons.OKCancel Botones OK y Cancelar n de los
Botones Abandonar, Reintentar y iconos:
MessageBoxButtons.AbortRetryIgnore
Cancelar
 P
MessageBoxButtons.YesNoCancel Botones Sí, No y Cancelar ara el
MessageBoxButtons.YesNo Botones Sí y No botón
MessageBoxButtons.RetryCancel Botones Reintentar y Cancelar por
defecto:
Constante Significado
MessageBoxIcon.IconInformation
MessageBoxIcon.IconExclamation
MessageBoxIcon.IconError
MessageBoxIcon.IconQuestion
ShareVideos
Constante Significado
MessageBoxDefaultButton.DefaultButton1 Primer
botón Para obtener el cuadro de mensaje
siguiente:
Segundo
MessageBoxDefaultButton.DefaultButton2
botón
Tercer
MessageBoxDefaultButton.DefaultButton3
botón utilizaremos el código siguiente:

Dim respuesta As Integer


MessageBox.Show("¿Quiere guardar al cerrar la aplicación?", _
"Fin del programa", MessageBoxButtons.YesNoCancel, _
MessageBoxIcon.Question,MessageBoxDefaultButton.Button1)

Como le hacemos una pregunta al usuario, tenemos que recuperar su respuesta para decidir
qué hacer en la aplicación. Para ello, el método Show reenvía un valor que indica el botón
utilizado para cerrar el cuadro de mensaje. Aquí también se definen una serie de constantes
para identificar cada posible caso.

Valor devuelto Botón utilizado Podemos luego evaluar la respuesta:


DialogResult.Ok Botón OK Select Case respuesta
DialogResult.Cancel Botón Cancelar Case
DialogResult.Yes
DialogResult.Abort Botón Abandonar ...
Case
DialogResult.Retry Botón Reintentar DialogResult.No
DialogResult.Ignore Botón Ignorar ...
Case
DialogResult.Yes Botón Sí DialogResult.Cancel
DialogResult.No Botón No ...
End Select

3. Los cuadros de diálogo de Windows

Muchos cuadros de diálogo ya están definidos a nivel del propio sistema operativo
Windows. Para poderlos utilizar en nuestras aplicaciones, tenemos a nuestra disposición
una serie de clases. Vamos a ver cómo configurarlas y utilizarlas en una aplicación.

a. Diálogo de apertura de archivo

Este cuadro de diálogo nos permite seleccionar uno o más nombres de archivo, con la
posibilidad de desplazarse en la estructura en árbol de la máquina. Se utiliza la clase
OpenFileDialog. Debemos entonces crear una instancia en nuestra aplicación.

Dim dlgAbrir As OpenFileDialog


dlgAbrir = New OpenFileDialog()

También conviene configurar nuestro cuadro de diálogo. La propiedad InitialDirectory


indica el directorio en el que se encuentra el cuadro de diálogo en el momento de su

ShareVideos
apertura. Es posible mostrar solo algunos archivos en los directorios que se verán. Para ello,
hay que configurar a través de la propiedad Filter las correspondencias entre la descripción
del contenido y la extensión asociada. La propiedad Filter almacena la información en
forma de cadena de caracteres. La descripción y la extensión se separan en la cadena con el
carácter | ([AltGr] 1). Si existen muchas extensiones disponibles para una misma
descripción, deben separarse con un punto y coma en la cadena. También puede indicar si
se debe agregar una extensión a los nombres de los archivos escritos manualmente, si estos
no la llevan.

La propiedad DefaultExt contiene la extensión que se debe agregar y AddExtension indica


si esta extensión se agrega automáticamente. Si se permite al usuario escoger manualmente
el recorrido y el nombre del archivo que quiere abrir, puede confiarle al cuadro de diálogo
la tarea de verificar que el nombre y el recorrido de acceso sean correctos. Las propiedades
CheckFileExist y CheckPathExist gestionan estas comprobaciones. También puede
autorizar la selección múltiple a través de la propiedad Multiselect.

Finalmente, para mostrar el cuadro de diálogo, se usa el método ShowDialog:

dlgAbrir.InitialDirectory = "c:\dos"
dlgAbrir.Title = "seleccione el archivo que quiere abrir"
dlgAbrir.Filter ="Todos|*.*|Imagenes|*.bmp;*.gif;*.jpg|texto|*.txt"
dlgAbrir.DefaultExt = "txt"
dlgAbrir.AddExtension = True
dlgAbrir.CheckFileExists = False
dlgAbrir.Multiselect = True
dlgAbrir.ShowDialog()

Los nombres del archivo o de los archivos seleccionados están disponibles en la propiedad
FileName para una selección única o en la propiedad FileNames para las selecciones
múltiples. Esta propiedad FileNames es una matriz de cadenas de caracteres que contiene
en cada casilla el nombre completo de uno de los archivos seleccionados.

Dim nombrearchivo As String


For Each nombreArchivo In dlgAbrir.FileNames
System.Console.WriteLine(nombreArchivo)
Next

b. Diálogo de grabación de archivo

El cuadro de diálogo de grabación de archivo es parecido al anterior, aunque la propiedad


Multiselect desaparece y las propiedades CreatePrompt y OverwritePrompt permiten
mostrar un mensaje de aviso, si el nombre del archivo elegido no existe o, al revés, si ya
existe, y se va a sobreescribir.

c. Diálogo de selección de directorio

Este cuadro de diálogo se utiliza para la selección o la creación de un directorio. Se crea a


partir de la clase FolderBrowserDialog, y contiene algunas pocas propiedades. La más

ShareVideos
utilizada es ciertamente la propiedad SelectedPath, que permite la recuperación de una ruta
de acceso al directorio seleccionado. El directorio raíz del cuadro de diálogo está indicado
por la propiedad RootFolder. Esta propiedad recibe uno de los valores de la enumeración
Environment.SpecialFolder, que representa los principales directorios característicos del
sistema, como por ejemplo el directorio Mis Documentos. Si se utiliza esta propiedad, la
selección deberá hacerse en un subdirectorio debajo del directorio raíz. Modificando la
propiedad ShowNewFolderButton, podemos agregar un botón que permita la creación de
un nuevo directorio. El método ShowDialog permite la visualización del cuadro de diálogo.

dlgSelecDirectorio.RootFolder = Environment.SpecialFolder.MyDocuments
dlgSelecDirectorio.ShowDialog()
MsgBox(dlgSelecDirectorio.SelectedPath, , ”Directorio Seleccionado”)

Nótese también que la ruta de acceso retornada por este cuadro de diálogo es una ruta
absoluta, como muestra el siguiente ejemplo:

d. Diálogo de elección de un color

El cuadro de diálogo de elección de color creado a partir de la clase ColorDialog puede


tener dos configuraciones.

Una versión «sencilla» en la que solo están disponibles los colores de base:

Una versión completa en la que el usuario podrá crear colores personalizados:

La propiedad Color permite inicializar el cuadro de diálogo antes de su visualización y


después recuperar el color elegido por el usuario. Puede prohibir la utilización de colores
personalizados o, todo lo contrario, mostrar el cuadro de diálogo completo desde su
apertura. Para prohibir la visualización de los colores personalizados, se modifica la
propiedad AllowFullOpen. Para forzar la visualización completa, se usa la propiedad
FullOpen.

La visualización del cuadro de diálogo se realiza también con el método ShowDialog. Para
conservar una calidad de visualización correcta, también puede autorizar solo la utilización
de los colores puros (los colores obtenidos sobreponiendo diferentes píxeles se eliminarán
de las posibles opciones). Esta opción se debe usar si se tiene una tarjeta gráfica
configurada en 256 colores.

Este ejemplo modifica el color de fondo de la página.

Dim dlgColor As ColorDialog

ShareVideos
dlgColor = New ColorDialog()
dlgColor.FullOpen = True
dlgColor.SolidColorOnly = True
dlgColor.Color = Me.BackColor
dlgColor.ShowDialog()
Me.BackColor = dlgColor.Color

e. Diálogo de elección de fuente

La clase base utilizada para la selección de una fuente es la clase FontDialog. La propiedad
Font permite definir la fuente de caracteres utilizada para inicializar el cuadro de diálogo o,
después de cerrarlo, para recuperar la fuente seleccionada. También puede mostrar un
cuadro de diálogo simplificado sin la elección de color o de los efectos. Para hacerlo, las
propiedades ShowColor y ShowEffects controlan la visualización de estos parámetros en el
cuadro de diálogo. Para garantizar que los parámetros seleccionados correspondan a una
fuente existente en la máquina, puede utilizar la propiedad FontMustExist. Esta propiedad
obligará a que el cuadro de diálogo verifique la existencia de una fuente correspondiente en
el sistema operativo antes de cerrarse. Algunas fuentes contienen muchos juegos de
caracteres. Puede autorizar a los usuarios a elegir uno de esos juegos de caracteres
modificando la propiedad AllowScriptChange. La dimensión de la fuente seleccionada
puede limitarse con las propiedades MaxSize y MinSize.

Para inspeccionar el efecto de la fuente seleccionada, existe una vista previa de algunos
caracteres. Si esta vista previa no es suficiente, tiene la posibilidad de mostrar un botón
Aplicar en su cuadro de diálogo a través de la propiedad ShowApply. Este botón activa un
evento Apply en el cuadro de diálogo. En la gestión de este evento, puede utilizar la
propiedad Font del cuadro de diálogo para visualizar el efecto de la fuente actualmente
seleccionada en su texto. La variable que hace referencia al cuadro de diálogo debe
declararse con la palabra clave WithEvents; por lo tanto, fuera de un procedimiento.

Un pequeño ejemplo para visualizar la utilización de estas propiedades:

Public Class PruebaFuente


Inherits System.Windows.Forms.Form
Dim WithEvents dlgFont As FontDialog
Private Sub cmdFuente_click(ByVal sender As System.Object, _
ByVal e As System.EventArgs)Handles
cmdFuente.Click
dlgFont = New FontDialog()
dlgFont.ShowApply = True
dlgFont.ShowColor = True
dlgFont.ShowEffects = True
dlgFont.MaxSize = 20
dlgFont.MinSize = 12
dlgFont.FontMustExist = True
dlgFont.AllowScriptChange = True

ShareVideos
dlgFont.ShowDialog()
txtPrueba.Font = dlgFont.Font
End Sub
Private Sub dlgFont_Apply(ByVal sender As Object, ByVal e
As System.EventArgs)
Handles dlgFont.Apply
txtPrueba.Font = dlgFont.Font
End Sub
End Class

f. Diálogo de configurar página

A través de este cuadro de diálogo, podrá configurar los siguientes parámetros de


maquetación de su documento (márgenes, orientación...).

Este cuadro de diálogo se crea a partir de la clase PageSetupDialog. Para trabajar, esta clase
necesita dos clases auxiliares: la clase PageSettings sirve para almacenar la configuración
de la maquetación; la clase PrinterSettings almacena la configuración de la impresora
seleccionada. Hay que crear una instancia de estas dos clases y asociarlas a las propiedades
PageSettings y PrinterSettings del cuadro de diálogo. Debe importar el espacio de nombres
System.Drawing.Printing para poder utilizar estas dos clases.

La utilización de diferentes campos del cuadro de diálogo puede prohibirse a través de la


modificación de las siguientes propiedades:

 AllowMargins para la modificación de los márgenes.


 AllowOrientation para la modificación de la orientación.
 AllowPaper para la elección del papel.
 AllowPrinter para la elección de la impresora.

Las elecciones del usuario se recuperarán después, usando las propiedades PageSettings y
PrinterSettings del cuadro de diálogo.

Veamos un ejemplo de utilización:

Imports System.Drawing.Printing
Public Class Form2
Inherits System.Windows.Forms.Form

Private Sub TestPgSetup_Click(ByVal sender As System.Object, ByVal e


As System.EventArgs) Handles testPgSetup.Click
Dim configPg As PageSettings
Dim configPrt As PrinterSettings
configPg = New PageSettings()
configPrt = New PrinterSettings()
dlgPgSetup.PageSettings() = configPg
dlgPgSetup.AllowPrinter = True
dlgPgSetup.PrinterSettings = configPrt
dlgPgSetup.ShowDialog()

ShareVideos
MessageBox.Show("ha elegido la impresora " _
& dlgPgSetup.PrinterSettings.PrinterName _
& " en papel " _
& dlgPgSetup.PageSettings.PaperSize.PaperName _
& " en formato " _
&(IIf(dlgPgSetup.PageSettings.Landscape, "horizontal", "vertical")))
End Sub
End Class

g. Diálogo de configuración de impresión

Con este cuadro de diálogo, puede configurar los parámetros de impresión de su


documento. Se creará a partir de la clase PrintDialog.

Como para el cuadro de diálogo de maquetación, el cuadro de diálogo de configuración de


la impresión necesita una instancia de la clase PrinterSettings para almacenar los datos de
configuración de la impresora.

La utilización de diferentes campos puede prohibirse a través de la modificación de las


siguientes propiedades:

 AllowSelection autoriza la utilización del botón Selección. En general, este botón es


accesible solo si hay algo seleccionado en el documento que quiere imprimir.
 AllowSomePages autoriza la selección de una página de inicio y fin para la
impresión del documento. Este botón debe estar disponible si el documento
contiene varias páginas.
 AllowPrintToFile indica si la opción Imprimir a un archivo está disponible. Esta
funcionalidad permite, por ejemplo, la generación de un archivo en formato
PostScript para exportarlo a otra aplicación.

El resultado de las diferentes opciones seleccionadas está disponible después de cerrar el


cuadro de diálogo, a través de la propiedad PrinterSettings.

He aquí un nuevo ejemplo para este cuadro de diálogo.

Dim configPrt As PrinterSettings


Dim dlgprinter As PrintDialog
configPrt = New PrinterSettings()
dlgprinter = New PrintDialog()
dlgprinter.PrinterSettings = configPrt
dlgprinter.AllowSomePages = True
dlgprinter.AllowSelection = True
dlgprinter.ShowDialog()
Select Case dlgprinter.PrinterSettings.PrintRange
Case PrintRange.AllPages
MessageBox.Show("ha pedido la impresión de todo el documento")
Case PrintRange.SomePages
MessageBox.Show("ha pedido la impresión de la página " _

ShareVideos
& dlgprinter.PrinterSettings.FromPage & " a la página "
_
& dlgprinter.PrinterSettings.ToPage)
Case PrintRange.Selection
MessageBox.Show("ha pedido la impresión de la selección")
End Select

4. Cuadro de diálogo personalizado

Después de este breve resumen sobre los cuadros de diálogo predefinidos, vamos a ver
cómo crear nuestros propios cuadros de diálogo. La base para la creación de un cuadro de
diálogo es una ventana clásica en la que se modifican las siguientes propiedades:

 El estilo del borde, para obtener una ventana que no tenga un tamaño variable.
 La propiedad ShowInTaskBar, que está colocada en False para que la ventana no
aparezca en la barra de tareas.
 Es también necesario disponer de un botón de validación y un botón de anulación
para el cierre del cuadro de diálogo.

Puede crear un cuadro de diálogo cambiando manualmente estas propiedades en una


ventana normal, o seleccionar la opción Cuadro de diálogo en el momento de agregar un
elemento al proyecto.

La visualización del cuadro de diálogo se hará con la llamada al método ShowDialog en


vez del método Show, porque el método ShowDialog muestra la ventana en modalidad
modal (nuestro cuadro de diálogo será la única parte utilizable de la aplicación una vez
abierta).

Al cerrar el cuadro de diálogo, es necesario determinar qué botón ha provocado el cierre.


La solución nos la proporciona el método ShowDialog. Este método nos devuelve uno de
los valores de la enumeración System.Windows.Forms.Dialog-Result. El valor retornado
no aparece por azar. En el momento de diseñar el cuadro de diálogo, estamos obligados a
suministrar el valor que se debe retornar para cada uno de los botones que provocan el
cierre del cuadro de diálogo. Esto es factible mediante la propiedad DialogResult del
cuadro de diálogo en el evento Click de cada uno de los botones o modificando la
propiedad DialogResult de los botones correspondientes al cierre del cuadro de diálogo.
Nótese que en este caso no se generará el evento Click del botón para provocar el cierre del
cuadro de diálogo. Si se utilizan estas dos soluciones simultáneamente, la propiedad
DialogResult del cuadro de diálogo será prioritaria para determinar el valor devuelto por el
método ShowDialog.

Ahora que sabemos cómo configurar y mostrar un cuadro de diálogo, nos queda lo más
difícil: crear la interfaz visual del cuadro de diálogo.

ShareVideos
Utilización de los controles
Los controles nos permitirán crear la interfaz entre la aplicación y su usuario. Es a través de
ellos que el usuario podrá actuar sobre el funcionamiento de la aplicación escogiendo el
texto, eligiendo las opciones, lanzando la ejecución de una parte específica de nuestra
aplicación, etc.

Los controles estarán disponibles en VB.NET a través de una serie de clases que deberemos
instanciar durante la ejecución de la aplicación.

Estas diferentes clases dependen de una jerarquía que comienza por la clase base Control.
Esta clase garantiza las funciones elementales de los controles (posiciones, dimensiones...)
y una clase derivada agrega funcionalidades adicionales, y así hasta la clase final de la
jerarquía.

1. Agregar controles

Los controles pueden agregarse a una ventana de dos maneras diferentes. La más sencilla y
rápida es la utilización del cuadro de herramientas. Existen tres posibilidades para agregar
controles:

 Hacer doble clic en el control en el cuadro de herramientas. Este método permite


colocar un ejemplar con unas dimensiones por defecto en el centro de la ventana.
 Arrastrar entre el cuadro de herramientas y la ventana. Desde que sobrevuela la
ventana, el cursor del ratón le indica con un pequeño signo más (+) que va a agregar
algo a su ventana. La posición en la que suelte el botón de su ratón corresponderá a
la posición de la esquina superior izquierda de su control. Las dimensiones serán los
valores por defecto.
 Seleccionar el control en el cuadro de herramientas y luego hacer clic, en la ventana,
en el lugar en el que desea colocar la esquina superior izquierda de su control.
Después, sin dejar de pulsar el botón del ratón, dibujar un rectángulo hasta alcanzar
las dimensiones deseadas para su control.

Si quiere colocar muchos ejemplares del mismo control en su ventana, es posible bloquear
la selección en el cuadro de herramientas utilizando la tecla [Ctrl] cuando selecciona el
control en el cuadro de herramientas. Podrá colocar entonces diferentes ejemplares del
mismo control sin tener que volver a seleccionarlos en el cuadro de
herramientas manteniendo presionada la tecla [Ctrl].

Algunos controles no tienen interfaz visible en el momento de diseñar la ventana. Para


evitar sobrecargar la superficie de la ventana, se colocan en una zona situada debajo de la
ventana de concepción gráfica. Es por ejemplo el caso de los controles ImageList y Timer,
que veremos un poco más adelante en este capítulo. Es posible agregar controles al cuadro
de herramientas. Estos controles pueden ser controles .NET o controles ActiveX. La
utilización de controles ActiveX conllevará algunos inconvenientes para su aplicación. El

ShareVideos
código de su aplicación será menos eficaz (se precisarán algunas operaciones adicionales
para acceder al control ActiveX).

Su aplicación necesitará modificaciones en el registro de las máquinas para registrar los


controles ActiveX.

Los controles agregados son nombrados automáticamente por Visual Studio, aunque estos
nombres no sean muy explícitos.

El siguiente código no le parecerá muy nítido.

Button1.Enabled = False
TextBox1.Clear()
CheckBox1.Checked = True
RadioButton1.Checked = False
RadioButton2.Checked = True

Es primordial, para que el código sea legible, renombrar los controles preferentemente
durante su creación o más tarde, pero antes de ser utilizados en el código. Es suficiente con
cambiar la propiedad NAME de cada uno en la ventana de propiedades. No hay una regla
absoluta que sea preciso respetar a la hora de dar nombre a los controles. Una solución
frecuentemente utilizada consiste en asociar un prefijo representativo del tipo del control a
un nombre explicitado para la aplicación. Los prefijos no están normalizados, pero la regla
siguiente existe desde las primeras versiones de Visual Basic.

Prefijo Control Utilizando estas convenciones y un poco de sentido


común, el código es notablemente más claro:
cbo ComboBox
lst ListBox cmdValidacion.Enabled = False
txtNombre.Clear()
chk CheckBox chkCursiva.Checked = True
opt RadioButton optAzul.Checked = False
optVerde.Checked = True
cmd Button
txt TextBox 2. Posición y dimensión de los controles
lbl Label
Después de colocar los controles en la ventana, es
posible moverlos o volver a dimensionarlos. Cuando desplaza el ratón por encima de un
control, el cursor cambia de aspecto para indicar la posibilidad de desplazar el control.

Es suficiente hacer clic en el control y después desplazar el ratón. El control seguirá


entonces a su cursor, para representar la futura posición del control. Las líneas de guía se
visualizan durante el desplazamiento del control para facilitar su alineación con los otros
controles ya ubicados en la ventana. Las líneas azules representan las alineaciones posibles
en los bordes de los otros controles, las líneas rosas representan las alineaciones posibles en

ShareVideos
las etiquetas de los controles. El control será efectivamente desplazado en el momento en
que se suelta el botón del ratón.

También es posible usar las flechas del teclado, que además aportan mayor precisión al
desplazamiento.

Igualmente se puede modificar la posición de un control con su propiedad Location en la


ventana de propiedades. Esta propiedad se modifica cuando desplaza un control con el
ratón o con el teclado.

Finalmente, la última posibilidad es modificar con el código las propiedades Left y Top del
control. El siguiente fragmento de código permite desplazar el botón a una posición
aleatoria cada vez que haga clic encima de él.

Private Sub cmdtest_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles
cmdtest.Click
cmdtest.Left = Rnd() * (Me.ClientSize.Width -
cmdtest.Size.Width)
cmdtest.Top = Rnd() * (Me.ClientSize.Height -
cmdtest.Size.Height)
End Sub

Una funcionalidad más avanzada permite colocar los controles en función de la posición de
otros controles. Para poderlas utilizar, es necesario seleccionar varios controles en su
ventana. Para hacerlo, hay dos soluciones:

 Dibujar un rectángulo de selección con el ratón alrededor de los controles.


 Hacer clic en los controles uno tras otro, manteniendo la tecla [Ctrl] pulsada. El
primer control seleccionado aparece con unos puntos de selección blancos.

Las opciones del menú Formato de VB.NET ahora están disponibles y le proporcionan
muchas opciones para colocar los controles. El control que aparece en la selección con
puntos de selección blancos se considera como referencia para la alineación.

Existen muchas otras opciones para organizar la colocación de los controles en su ventana.

El cambio de dimensiones de los controles es también muy sencillo, ya que es suficiente


seleccionar el control o los controles cuya dimensión desea cambiar y colocar el cursor en
uno de los puntos de selección, para que aparezca una flecha que le indique en qué

ShareVideos
dirección puede cambiar la dimensión del control. Luego hay que hacer clic en el cuadrado
correspondiente y desplazar el ratón hasta que el control haya adquirido las dimensiones
deseadas.

También puede utilizar las flechas del teclado en asociación con la tecla [Mayús] para
dimensionar los controles.

El cambio de dimensiones con el código utiliza el método SetBounds, que permite a la vez
fijar la posición y las dimensiones del control. El siguiente código disminuye las
dimensiones del botón cada vez que se hace clic encima.

Private Sub cmdDisminuir_Click(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles cmdDisminuir.Click
cmdDisminuir.SetBounds(cmdDisminuir.Left, cmdDisminuir.Top,
cmdDisminuir.Size. Width - 5, cmdDisminuir.Size.Height - 5)
End Sub

Después de muchos esfuerzos para colocar y dimensionar los controles, sería una pena que
un error de manipulación lo estropeara todo. Para evitar que eso pase, es posible proteger
los controles en la ventana, a través del menú Formato - Bloquear controles. Este
comando bloquea el desplazamiento y el cambio de dimensiones de todos los controles
presentes en la ventana, así como el cambio de dimensiones de la propia ventana. Los
controles pueden desprotegerse después con la misma opción de menú. También puede
desproteger individualmente los controles con la propiedad Locked.

Si crea una aplicación en la que el usuario pueda cambiar el tamaño de la ventana durante
la ejecución, los controles deben seguir las modificaciones de dimensión de la ventana.
Para autorizar el cambio automático del tamaño de un control, puede utilizar la propiedad
Anchor del control. A través de ella, indica que la distancia entre los bordes del control y
las posiciones de anclaje se guardará durante el cambio del tamaño de la ventana. Cuando
se crean, los controles están anclados a los bordes superior e izquierdo de la ventana. La
modificación de esta propiedad se realiza con un pequeño asistente, disponible en la
ventana de propiedades.

Para modificar la propiedad Anchor, hay que seleccionar el rectángulo correspondiente al


lado con que quiere realizar un anclaje, o eliminar un anclaje existente.

Por ejemplo, para la siguiente ventana, los controles están anclados a la izquierda y a la
derecha.

ShareVideos
Si cambiamos el tamaño de la ventana, los controles siguen la maximización horizontal de
la ventana.

También puede indicar que un control debe adaptar una o más de sus dimensiones a las de
su contenedor. Para hacerlo, utilice la propiedad Dock del control, indicando en qué borde
de su contenedor debe adaptar el control una de sus dimensiones.

Por ejemplo, podemos colocar un control PictureBox pidiendo que se fije al borde inferior
de la ventana.

Nuestro PictureBox se adapta automáticamente a la anchura de la ventana y se queda


pegado a su borde inferior.

3. Paso del foco entre controles

Cuando crea su aplicación, debe pensar en las personas a las que no les gusta usar el ratón,
pero que deben poder utilizar la aplicación. Conviene, por lo tanto, crear la aplicación de
manera que pueda funcionar utilizando solo el teclado (¡sin teclado ni ratón será mucho
más difícil!).

En una aplicación de Windows, se dice que un control tiene foco cuando está listo para que
el usuario lo utilice. El foco se puede mover de un control a otro utilizando la tecla [Tab].
Dos propiedades de los controles manejan el paso del foco con la tecla [Tab].

 La propiedad TabStop indica si un control podrá focalizarse utilizando la tecla


[Tab].
 La propiedad TabIndex indica el orden en que el foco pasa de un control a otro.

Por defecto, las propiedades TabIndex se numeran según el orden en el que los controles se
han creado.

Para modificar este orden, puede modificar directamente la propiedad TabIndex de cada
control o usar el menú Ver - Orden de tabulación. Los controles se muestran entonces con
el valor de su propiedad TabIndex en la esquina superior izquierda.

Después debe hacer clic en los controles, en el orden en que quiere que se pase el foco.

El siguiente orden parece mucho más lógico para este cuadro de diálogo.

ShareVideos
Después puede volver al modo normal utilizando otra vez el menú Ver - Orden de
tabulación o utilizando la tecla [Esc].

4. Tecla de acceso rápido

Algunos usuarios con prisa quieren poder situarse directamente en un control sin tener que
pasar por todos los que le preceden en el orden de las tabulaciones. Para ello, puede agregar
una tecla de acceso rápido que se activará a través de la tecla [Alt] más un carácter. Para
especificar el carácter que se debe utilizar para activar el control, hay que agregar en la
propiedad Text del control un carácter «&» delante del carácter utilizado para la tecla de
acceso rápido asociada al control. Esto hace que se active el atajo y se subraye el carácter
en el texto que aparece en el control.

Si quiere insertar un carácter & en la leyenda de su control, hay que repetirlo dos veces en
su propiedad Text.

Para algunos controles (botones, check box, botones de opción...) la utilización del atajo de
teclado equivale a un clic del ratón y lanza la acción correspondiente. Para otros, el atajo de
teclado coloca simplemente el foco en el control correspondiente.

Para los controles que no tienen leyenda, habrá que utilizar un control LABEL, que les
servirá de leyenda y que activará también el atajo de teclado. Veremos todo esto más
adelante en este capítulo.

Ahora que sabemos utilizar los controles en una aplicación, vamos a analizar
detalladamente los más usados.

Los controles de VB.NET


Cada control utilizable en VB.NET está representado por una clase, de la que vamos a
poder crear instancias para diseñar la interfaz de la aplicación. La mayoría de los controles
derivan de la clase Control, de la que heredan un buen número de propiedades, métodos y
eventos.

Vamos a estudiar los elementos más útiles de la clase Control.

ShareVideos
1. La clase Control
a. Dimensiones y posición

Las propiedades Left, Top, Width, Height permiten colocar los controles. Estas propiedades
pueden ser modificadas individualmente y aceptan valores del tipo Integer.

Es posible, pues, utilizar en nuestro código la siguiente sintaxis:

TextBoxNombre.Left = 100
TextBoxNombre.Top = 50
TextBoxNombre.Width = 150
TextBoxNombre.Height = 50

Otras dos propiedades permiten trabajar con la posición y las dimensiones de un control: la
propiedad Location acepta un objeto del tipo Point, gracias al que podemos especificar la
posición de nuestro control; del mismo modo, la propiedad Size, que acepta un objeto del
tipo Size, gestiona las dimensiones del control. Las líneas anteriores se pueden reemplazar
con:

TextBoxNombre.Location = New Point(100, 50)


TextBoxNombre.Size = New Size(150, 50)

En las que construiremos una instancia de Point y de Size, antes de asociarlas a las
propiedades correspondientes.

Una tercera posibilidad nos permite manipular a la vez la posición y las dimensiones de los
controles: la propiedad Bounds espera una instancia de la clase Rectangulo para definir las
características del control. Nuestro código se resume entonces en una sola línea:

TextBoxNombre.Bounds = New Rectangulo(100, 50, 150, 50)

El método SetBounds permite modificar las posiciones y dimensiones de los controles sin
tener que crear una nueva instancia de la clase Rectangulo, pero modificando la que ya está
asociada al control.

TextBoxNombre.SetBounds(100, 50, 150, 50)

La modificación de estas propiedades conlleva la activación de los eventos Resize y Move


en el control. Por supuesto, estos eventos se activan cuando el valor de las propiedades se
modifica en el código, pero también cuando, por ejemplo, la modificación de las
dimensiones de la ventana conlleva la necesidad de volver a colocar o a dimensionar el
control.

El comportamiento de los controles cuando la ventana cambia de tamaño está especificado


en las propiedades Anchor y Dock. Hemos visto cómo modificar estas propiedades a través
de la ventana de propiedades. Para modificarlas con el código, es suficiente asignarles uno
de los valores definidos en las enumeraciones AnchorStyles y DockStyle.

ShareVideos
Hasta ahora, las posiciones con las que hemos trabajado eran posiciones expresadas con
relación a la esquina superior izquierda del contenedor del control. En algunos casos, puede
ser útil obtener las coordenadas de un punto del control no con relación a la esquina
superior izquierda del control, sino con relación a la esquina superior izquierda de la
pantalla. El método PointToScreen permite esta conversión.

Espera, como parámetro, una instancia de la clase Point con las coordenadas expresadas
con relación al control y reenvía una nueva instancia de la clase Point con las coordenadas
expresadas con relación a la pantalla.

El siguiente código convierte en coordenadas de pantalla la posición superior izquierda de


un control TextBox:

System.Console.Write("Control/ventana:")
System.Console.WriteLine(Button2.Location)
Dim p As Point
p = Button2.PointToScreen(Button2.Location)
System.Console.Write("Control/pantalla:")
System.Console.WriteLine(p)

Resultado:

Control/ventana:{X=107,Y=72}
Control/pantalla:{X=306,Y=255}

La operación contraria se puede realizar con el método PointToClient, que toma como
parámetro un punto en coordenadas de pantalla y reenvía un punto expresado en
coordenadas relativas al control. Si se realiza la operación contraria, es decir, partiendo de
las coordenadas de pantalla, se obtiene el mismo valor:

System.Console.WriteLine("control / ventana relativo a la pantalla " &


Button2.PointToClient(p).ToString)

Resultado:

control / ventana relativo a la pantalla{X=107,Y=72}

b. Aspecto de los controles

El color de fondo del control se puede modificar con la propiedad BackColor, mientras que
el color del texto del control se modifica con la propiedad ForeColor.

Se pueden asignar a estas propiedades valores definidos en el espacio de nombre


System.Drawing.Color para obtener colores predefinidos en Visual Basic.

ShareVideos
TextBoxNombre.BackColor = System.Drawing.Color.Yellow

También se pueden utilizar las constantes definidas en el espacio de nombres


System.Drawing.SystemColors para utilizar uno de los colores definidos a nivel del sistema
operativo. En este caso, su aplicación se adaptará en función de la configuración de la
máquina en la que funcione.

TextBoxNombre.BackColor=System.Drawing.SystemColors.InactiveCaptionText

La tercera solución consiste en realizar una mezcla de colores, utilizando la función


FromArgb y especificando como parámetro la cantidad de cada color base (Rojo, Verde,
Azul).

TextBoxNombre.BackColor = System.Drawing.Color.FromArgb (127, 0, 127)

La fuente se puede modificar con la propiedad Font del control. Se puede crear una nueva
instancia de la clase Font y asignarla al control. Existen trece constructores diferentes para
la clase Font, o sea trece maneras distintas de crear una fuente de letra. Nosotros vamos a
usar la más sencilla, indicando simplemente el tipo de fuente y las dimensiones.

TextBoxNombre.Font = New Font(System.Drawing.FontFamily.Generic


Monospace, 16)

Después de haber realizado las modificaciones de estas propiedades, es posible volver a una
configuración normal llamando a los métodos ResetBackColor, ResetForeColor, ResetFont.
Las correspondientes propiedades se reinician con los valores definidos por el contenedor
del control.

La propiedad BackgroundImage permite especificar una imagen que se utilizará de fondo


para el control. Si la imagen no es suficientemente grande para cubrir el control, se
reproduce en mosaico:

BtnValidar.BackgroundImage = New Bitmap("cut.bmp")

¡A veces el resultado es desconcertante!

Para volver a algo más clásico:

BtnValidar.BackgroundImage = Nothing

La propiedad Cursor permite elegir el aspecto del cursor cuando el ratón se encuentra en la
superficie del control. Muchos cursores están predefinidos en Windows.

ShareVideos
Estos cursores están en la colección Cursors y pueden utilizarse directamente
asignándolos a la propiedad Cursor del control.

BtnValidar.Cursor = Cursors.WaitCursor

Si no le interesa ninguno de ellos, puede utilizar un cursor personalizado creando una


instancia de la clase Cursor y asignándolo a la propiedad Cursor del control.

BtnValidar.Cursor = New Cursor("h_nodrop.cur")

La detección de la entrada y de la salida del ratón en el control y la modificación del cursor


se gestionan automáticamente.

Como para la fuente de caracteres, es posible restaurar el cursor por defecto llamando al
método ResetCursor.

La modificación de la mayoría de las propiedades de los controles desencadena un evento.


Estos eventos se identifican con el nombre de la propiedad seguido del sufijo Changed.
Pueden utilizarse para guardar las preferencias de un usuario y personalizar la aplicación.

Private Sub Form1_BackColorChanged(ByVal sender As Object, ByVal e As


System.EventArgs) Handles Me.BackColorChanged
My.Settings.ColorFondo = Me.BackColor
My.Settings.Save()
End Sub

c. Comportamiento de los controles

Los controles colocados en una ventana se pueden ocultar modificando la propiedad Visible
o desactivar modificando la propiedad Enabled. En este caso, el control está siempre
visible, pero aparece con un aspecto gris para indicarle al usuario que este control está
inactivo por el momento.

BtnValidar.Enabled = False

Obviamente, los controles en este estado no pueden recibir el foco en la aplicación. Puede
verificarlo examinando la propiedad CanFocus, que reenvía un Booleano. Puede verificar
también si un control tiene el foco en un momento dado controlando la propiedad Focused
o la propiedad ContainsFocus. Esta última se tiene que utilizar con los controles
contenedores (es decir, los controles que pueden contener otros controles). En este caso,
esta propiedad se coloca en True si uno de los controles situados dentro del contenedor está
focalizado.

El foco puede colocarse en un control sin la intervención del usuario, llamando el método
Focus del control.

ShareVideos
BtnValidar.Focus()

Para controlar el paso del foco de un control a otro, existen cuatro eventos:

 Enter indica que la focalización llega a uno de los controles de un contenedor.


 GotFocus indica que un control en particular ha recibido el foco.
 LostFocus indica que un control ha perdido el foco.
 Leave indica que el foco ya no está en uno de los controles de un contenedor.

Por ejemplo, para destacar que un control está focalizado, se puede utilizar el siguiente
código, que modifica el color del texto cuando el control recibe o pierde el foco:

Private Sub TxtNombre_GotFocus(ByVal sender As Object, ByVal e As


System.
EventArgs) Handles TextBoxNombre.GotFocus
TextBoxNombre.ForeColor = System.Drawing.Color.Red
End Sub

Private Sub TxtNombre_LostFocus(ByVal sender As Object, ByVal e


As System.EventArgs) Handles TextBoxNombre.LostFocus
TextBoxNombre.ResetForeColor()
End Sub

En algunos casos, es deseable verificar los datos introducidos por el usuario en un


formulario antes de continuar en la aplicación. Esta verificación puede realizarse al cerrar el
formulario o a medida que se rellenan los diferentes controles del formulario.

Cada control se puede configurar para permitir la verificación de estos datos modificando la
propiedad CausesValidation a True. Justo antes de que el control pierda el foco, el evento
Validating se activa para permitir la verificación de los datos introducidos por el usuario. Si
lo tecleado no es correcto (en función de los criterios que hemos establecido), podemos
bloquear el paso del foco hacia otro control modificando la propiedad Cancel del objeto
CancelEventArg que se pasa como parámetro. En este caso, el foco se queda en el control
correspondiente a los datos introducidos que no son correctos. Si los datos son correctos, el
evento Validated se activa en el control y el foco se desplaza al control siguiente.

Por ejemplo, para introducir un número de teléfono, podemos verificar que se han
introducido solo valores numéricos. En caso de error, generamos un beep, modificamos el
color del texto y bloqueamos el paso del foco a otro control.

Private Sub TxtTel_Validating(ByVal sender As Object, ByVal e As System.


ComponentModel.CancelEventArgs) Handles txtTel.Validating
If Not IsNumeric(txtTel.Text) And txtTel.Text <> "" Then
Beep()
txtTel.ForeColor = System.Drawing.Color.Red
e.Cancel = True
End If
End Sub

ShareVideos
Private Sub TxtTel_Validated(ByVal sender As Object, ByVal e As
System.
EventArgs) Handles txtTel.Validated
txtTel.ResetForeColor()
End Sub

Dos propiedades son útiles cuando trabajamos con controles contenedor. La propiedad
HasChildren nos permite saber si hay controles colocados en nuestro contenedor. Si es el
caso, la colección Controls contiene la lista de todos estos controles. Por ejemplo, podemos
modificar el color del texto de todos los controles de un contenedor cuando el foco está
colocado en uno de ellos.

Private Sub GroupBox1_Enter(ByVal sender As Object, ByVal e


As System. EventArgs) Handles GrBoxIdent.Enter
If GrBoxIdent.HasChildren Then
Dim x As Object
For Each x In GrBoxIdent.Controls
x.forecolor = System.Drawing.Color.YellowGreen
Next
End If
End Sub

Private Sub GroupBox1_Leave(ByVal sender As Object, ByVal e


As System.EventArgs) Handles GrBoxIdent.Leave
If GrBoxIdent.HasChildren Then
Dim x As Object
For Each x In GrBoxIdent.Controls
x.resetforecolor()
Next
End If
End Sub

La operación opuesta también es posible, es decir, que a partir de un control podemos


recuperar las propiedades de su contenedor. La propiedad Parent proporciona una
referencia hacia el contenedor del control. Podemos, por ejemplo, hacer que el color de
fondo de cada control cambie al mismo tiempo que el de su contenedor.

Private Sub GrBoxIdent_BackColorChanged(ByVal sender As Object,


ByVal e As System.EventArgs) Handles GrBoxIdent.BackColorChanged
If GrBoxIdent.HasChildren Then
Dim x As Object
For Each x In GrBoxIdent.Controls
x.backcolor = x.parent.backcolor
Next
End If
End Sub

Ahora que hemos explorado las propiedades comunes de los distintos controles disponibles,
vamos a estudiarlos uno por uno analizando sus particularidades.

ShareVideos
2. Los controles de visualización de información
a. El control Label

El control Label se usa para mostrar, en un formulario, un texto que el usuario no podrá
modificar. Sirve esencialmente para proporcionar una leyenda a los controles que no la
poseen (zonas de texto por ejemplo, lista desplegable...). En este caso, permitirá también
proporcionar un atajo de teclado para el control.

El texto mostrado por el control se indica con la propiedad Text. Esta propiedad puede, por
supuesto, modificarse con el código de la aplicación. Por lo tanto, hay que tener cuidado,
porque por defecto el control conserva las dimensiones que le han asignado en su creación.
Si la nueva cadena de caracteres asignada a la propiedad Text es más larga que la
especificada en el momento de la creación, solo se podrá ver el inicio. Para evitar este
problema, hay que pedirle al control Label que adapte su anchura en función del texto que
se va a mostrar, modificando la propiedad AutoSize en True.

Por defecto, el control Label no tiene borde. Puede añadirlo modificando la propiedad
BorderStyle, y utilizando uno de los tres valores disponibles.

También tiene la posibilidad de indicar la posición del texto en el control a través de la


propiedad TextAlign. En el código, utilizará una de las constantes predefinidas.

Mediante la ventana de propiedades, es suficiente con hacer clic en la posición deseada


para el texto dentro de su control.

Hay que resaltar que la propiedad TextAlign modificará la posición del texto solo si la
propiedad AutoSize está en False.

Los controles Label también pueden mostrar imágenes. Puede indicar la imagen que desea
mostrar utilizando la propiedad Image. Otra solución consiste en usar un control ImageList,
que servirá, de alguna manera, para almacenar las imágenes de la aplicación. En este caso,
indicamos con la propiedad ImageList en qué control vamos a buscar la imagen y con la
propiedad ImageIndex en qué lugar se encuentra en el control ImageList. Si utiliza un
control ImageList, la propiedad Image de su control se ignorará. Como para el texto, puede
modificar la posición de la imagen en el control gracias a la propiedad ImageAlign, con las
mismas constantes que para la propiedad TextAlign.

ShareVideos
Hemos indicado que el control Label se podía utilizar como atajo de teclado para otro
control. Para hacerlo, hay que tomar tres precauciones.

 Como con los otros controles, agregar un & en la propiedad Text para el carácter
utilizado como tecla de acceso rápido.
 Indicar al control Label su papel de gestor de atajo de teclado modificando la
propiedad UseMnemonic en True.
 Verificar que el control que debe recibir la focalización está inmediatamente
después del control Label en el orden de las tabulaciones (propiedad TabIndex).

b. El control LinkLabel

El control LinkLabel hereda todas las características del control Label y solo agrega
funcionalidades de enlace de estilo Web. Las propiedades adicionales en relación con el
control Label gestionan los diferentes parámetros del enlace.

La propiedad LinkArea indica qué porción del texto activará el enlace. Esta propiedad se
puede modificar a través de la ventana de propiedades, con un pequeño asistente en el que
tiene que seleccionar la porción de texto que constituye el vínculo.

Los colores utilizados para el enlace se pueden modificar con tres propiedades:

LinkColor Color del enlace en el estado normal.


VisitedLinkColor Color del enlace después de una primera utilización.
ActiveLinkColor Color del enlace en el momento en que se hace clic.

La apariencia del enlace se puede modificar con la propiedad LinkBehavior.

Los cuatro valores posibles permiten respectivamente:

 Utilizar la misma configuración para los enlaces que para su navegador.


 Tener los enlaces siempre subrayados.
 Tener los enlaces subrayados cuando el ratón pasa encima de ellos.
 No tener nunca los enlaces subrayados.

Cuando el usuario hace clic en el enlace, el evento LinkClicked se activa en la


aplicación. Es el usuario quien debe escribir el código para ejecutar una acción en su
aplicación.

Tiene también que modificar la propiedad LinkVisited colocándola en True, para indicar
que este enlace ya ha sido utilizado en la aplicación.

ShareVideos
La acción puede ser la apertura de una página de un sitio Web en el navegador por defecto,
como en el siguiente ejemplo:

Private Sub LinkMicrosoft_LinkClicked(ByVal sender As System.Object,


ByVal e As System.Windows.Forms.LinkLabelLinkClickedEventArgs)
Handles LinkLabel1.LinkClicked
System.Diagnostics.Process.Start(" http://www.microsoft.es ")
LinkLabel1.LinkVisited = True
End Sub

O también la visualización de una nueva ventana en nuestra aplicación, como en el


siguiente ejemplo:

Private Sub LinkMicrosoft_LinkClicked(ByVal sender As System.Object,


ByVal e As System.Windows.Forms.LinkLabelLinkClickedEventArgs) Handles
LinkMicrosoft. LinkClicked
Dim ventana As dlgPerso
ventana = New dlgPerso()
ventana.ShowDialog()
LinkMicrosoft.LinkVisited = True
End Sub

c. El control StatusStrip

El control StatusStrip se usa normalmente para presentar información al usuario acerca del
funcionamiento de la aplicación. Puede mostrar la información en distintas zonas. Los datos
se pueden mostrar en forma de texto, de barra de progreso, de menú o de botón de comando
asociado a un menú. Un editor específico, accesible desde la propiedad Items del control,
permite su configuración.

Cada elemento agregado al control StatusStrip debe configurarse individualmente a


continuación. Las propiedades de los elementos que pueden utilizarse para la construcción
de un StatusStrip son muy parecidas a las de los controles normales. Por ejemplo, el
elemento ToolStripStatusLabel es prácticamente idéntico al control LinkLabel.

d. El control ToolTip

Este control permite mostrar una etiqueta de ayuda asociada a un control. Este control no
tiene interfaz visible: se colocará en la zona situada por debajo de la ventana de diseño.
Realiza mucho trabajo sin ningún esfuerzo de programación. Controla siempre, por
ejemplo, dónde se encuentra el ratón. Si está encima de un control, verifica si existe una
sugerencia asociada al control y si es necesario muestra la sugerencia correspondiente a la
propiedad AutoPopDelay.

Para poder funcionar, el control ToolTip debe asociar una cadena de caracteres a cada uno
de los controles de la interfaz. Para hacerlo, cuando un control ToolTip está disponible en

ShareVideos
una ventana, se agrega una propiedad ToolTip a cada control, permitiendo así especificar el
texto de la sugerencia asociada al control.

Las cadenas de caracteres asociadas a cada control pueden indicarse con el código,
llamando el método SetToolTip e indicando como parámetros el nombre del control y la
cadena de caracteres a él asociada.

ToolTip1.SetToolTip(RadioButton3, " Color azul para el texto ")

Esta técnica permite conservar leyendas relativamente breves para los controles, pero que a
la vez proporcionan la información suficiente sobre la utilización de la aplicación.

e. El control ErrorProvider
Este control permite indicar fácilmente al usuario los problemas relativos a los datos elegidos en
un formulario. En general, interviene en la fase de validación de los datos del formulario,
mostrando frente a cada control un pequeño icono para llamar la atención de este. Se puede
proporcionar información adicional con un mensaje asociado al control ErrorProvider.

Un mismo control ErrorProvider se puede utilizar para todos los controles de un formulario.

La activación del control ErrorProvider se puede realizar en el momento de cerrar el


formulario, cuando el usuario hace clic en el botón Aceptar, pero también es posible
controlar la elección en el momento en que se realiza, gestionando por ejemplo los eventos
Validating. Este evento se activa en un control en el momento en que este pierde el foco.
Podemos, en este caso, verificar inmediatamente el valor escogido en el control y
reaccionar mostrando nuestro control ErrorProvider. Para hacerlo, llamamos el método
SetError especificando el nombre del control que nos causa el problema y la cadena de
caracteres mostrada en la sugerencia asociada al control. Si no hay errores, hay que
reiniciar la cadena, para que desaparezca el icono del control ErrorProvider.

Private Sub TextBox1_Validating(ByVal sender As Object, ByVal e As


System.
ComponentModel.CancelEventArgs) Handles TextBox1.Validating
If Not IsNumeric(TextBox1.Text) Then
ErrorProvider1.SetError(TextBox1, " Valor numérico obligatorio
")
Else
ErrorProvider1.SetError(TextBox1, "")
End If
End Sub

f. El control NotifyIcon

Este control se usa principalmente para mostrar información sobre el funcionamiento de un


proceso en ejecución como tarea de fondo en la aplicación. Se muestra en la zona de estado

ShareVideos
del sistema operativo. La propiedad Icon del control determina el icono mostrado. La
propiedad Text representa la leyenda mostrada cuando el ratón pasa por encima del control.

Gestionando el evento DoubleClick del control, puede mostrar un cuadro de diálogo que
permita la configuración del proceso asociado al control.

Private Sub IconService_DoubleClick(ByVal sender As Object, ByVal e As


System.EventArgs) Handles IconService.DoubleClick
Dim dlg As ConfigService
lg = New ConfigService
dlg.ShowDialog()
End Sub

También es posible asociar un menú contextual, utilizando la propiedad ContextMenuStrip.


Este menú puede, por ejemplo, controlar el funcionamiento del proceso al que está asociado
el control.

g. El control HelpProvider

El control HelpProvider garantiza la relación entre el archivo de ayuda y la aplicación. El


archivo de ayuda debe ser generado por la herramienta Html Help Workshop, disponible en
el sitio de Microsoft. Para nuestros ejemplos, utilizaremos un archivo de ayuda existente en
el sistema operativo: el archivo C:\WINDOWS\Help\charmap.chm, que corresponde a la
Tabla de caracteres. Este archivo se debe asociar al control a través de la propiedad
HelpNamespace. La presencia de un control HelpProvider en una ventana agrega
automáticamente tres propiedades a cada control presente en dicha ventana:

HelpKeyword

Indica la palabra clave asociada al control en el archivo de ayuda.

HelpNavigator

Indica la acción ejecutada durante la visualización de la ayuda.

HelpString

Contiene la cadena de caracteres mostrada durante la utilización del botón de un cuadro de


diálogo. Para que este botón esté disponible en el cuadro de diálogo, hay que modificar la
propiedad HelpButton de la ventana a True y ocultar los botones de maximización y de
minimización de la ventana, modificando las propiedades MaximizeBox y MinimizeBox a false.

ShareVideos
El siguiente ejemplo asocia al botón de comando CmdOk el menú de ayuda Visión de
conjunto de la tabla de caracteres del archivo charmap.chm y configura el sistema de
ayuda, para que este menú se muestre automáticamente cuando se utiliza la tecla [F1].

h. El control ProgressBar

Este control se utiliza para informar al usuario acerca del progreso de una acción lanzada en
la aplicación. Muestra esta información en forma de una zona rectangular, que se llenará
más o menos en función del estado de progreso de la acción ejecutada. Se controla el
aspecto de la ProgressBar con su propiedad Style. Existen tres valores:

Continuous

El progreso se muestra con una barra azul llena.

Blocks

El progreso se muestra con una serie de pequeños rectángulos.

Marquee

Esta presentación es idéntica a la anterior, con un movimiento además dentro de la


ProgressBar.

La posición de la barra de progresión está controlada por la propiedad Value. Esta


propiedad puede variar entre los dos extremos indicados por las propiedades Minimum y
Maximum.

Existen tres técnicas disponibles para que se mueva la barra de progreso:

 Modificar directamente la propiedad Value del control. Hay que notar, que en ese
caso, si el valor de esta propiedad supera los extremos, se activa una excepción.
 Utilizar el método PerformStep, que aumenta a cada llamada la propiedad Value
con el valor contenido en la propiedad Step. El control verifica, en este caso, el
valor contenido en la propiedad Value y se asegura de que no supere los extremos.
 Utilizar el método Increment indicando como parámetro el valor utilizado como
incremento para la propiedad Value. El valor de la propiedad Value se verifica
también durante la ejecución de este método.

Si la ProgressBar tiene el estilo Marquee, la propiedad Value no tiene ningún efecto en las
dimensiones de la barra de progreso y no conviene utilizar los métodos PerformStep e
Increment porque se genera una excepción.

ShareVideos
El ejemplo siguiente presenta un original reloj en el que la hora se muestra a través de tres
ProgressBar:

Public Class Reloj


Public Sub New()
’ This call is required by the Windows Form Designer.
InitializeComponent()
’ Add any initialization after the InitializeComponent() call.
pgbHora.Minimum = 0
pgbHora.Maximum = 23
pgbHora.Style = ProgressBarStyle.Continuous
pgbMinuto.Minimum = 0
pgbMinuto.Maximum = 59
pgbMinuto.Style = ProgressBarStyle.Continuous
pgbSegundo.Minimum = 0
pgbSegundo.Maximum = 59
pgbSegundo.Style = ProgressBarStyle.Continuous
Timer1.Interval = 500
Timer1.Enabled = True
End Sub

Private Sub Timer1_Tick(ByVal sender As Object, ByVal e


As System.EventArgs) Handles Timer1.Tick
pgbHora.Value = Now.Hour
pgbMinuto.Value = Now.Minute
pgbSegundo.Value = Now.Second
End Sub
End Class

3. Los controles de edición de texto


a. El control TextBox

El control TextBox se utiliza para permitir al usuario introducir datos. Se puede configurar
el control para insertar texto en una o varias líneas. El tamaño máximo del texto varía de
2.000 a 32.000 caracteres, según la configuración del control (línea simple o líneas
múltiples). El control también es capaz de gestionar la selección de texto y las operaciones
con el portapapeles. Para trabajar con este control, hay disponibles muchas propiedades y
métodos. El texto mostrado en el control puede ser modificado o recuperado mediante la
propiedad Text. Es posible modificar el formato de visualización del texto mediante
distintas propiedades. La propiedad Autosize permite pedirle al control TextBox que
cambie su tamaño en función de las dimensiones de la fuente de caracteres. Esta propiedad
se sitúa casi siempre en True. La propiedad CharacterCasing autoriza a control para que
modifique todos los caracteres elegidos, tanto las minúsculas como las mayúsculas.

La propiedad Lines permite recuperar el texto introducido, línea a línea. Esta propiedad es
una matriz de cadenas de caracteres que contiene tantas casillas como líneas. Esta
propiedad solo tiene interés si el control está configurado para aceptar la introducción de

ShareVideos
datos en varias líneas con la propiedad Multiline en posición True. En este caso, también
hay que prever la posibilidad de poder desplazar el texto añadiendo barras de
desplazamiento con la propiedad ScrollBars. Las distintas posibilidades permitirán tener
una barra de deslizamiento horizontal, vertical o ambas. Cuidado, sin embargo, ya que la
barra de desplazamiento vertical solo será visible si la propiedad WordWrap está en False.
En caso contrario, el control gestiona por sí mismo el salto de línea cuando la longitud de la
línea supera la anchura del control. En este caso, los retornos de carro añadidos
automáticamente no se insertan en el texto.

En este ejemplo, la propiedad Lines contendrá dos elementos, porque el primer retorno de
carro se agrega simplemente con el control para su visualización.

Lines(0)-> Hoy hace buen tiempo en Madrid

Lines(1)-> ¡Que signa así!

La longitud máxima del texto del control se fija con la propiedad MaxLength. Hay que
tener en cuenta que, en el caso de un control de líneas múltiples, los caracteres de retorno
de carro y de salto de línea también cuentan. Se suele utilizan esta propiedad cuando se
hace uso del control TextBox para introducir una contraseña. En este caso, la propiedad
PasswordChar indica el carácter utilizado durante la visualización para ocultar la inserción
del usuario. Se suele utilizar el carácter * o #. Esta propiedad solo influye en la
visualización y los caracteres elegidos por el usuario se recuperan siempre en la propiedad
Text.

La gestión de la selección del texto la realiza el control de forma automática. La propiedad


SelectedText permite recuperar la cadena de caracteres actualmente seleccionada en el
control. Las propiedades SelectionStart y SelectionLength indican respectivamente el
carácter del inicio de la selección (el primer carácter de índice 0) y el número de caracteres
de la selección.

También se utilizan estas propiedades para insertar texto en el control: la propiedad


SelectionStart indica en este caso el punto de inserción y la propiedad SelectedText, el
texto que hay que insertar. Para añadir texto después del ya existente en el control, resulta
más práctico emplear el método AppendText pasándole como parámetro la cadena de
caracteres que desea agregar.

La sustitución de una porción de texto en el control TextBox se ejecuta en dos etapas. Ante
todo, hay que seleccionar el texto que se desea sustituir utilizando las propiedades
SelectionStart y SelectionLength; luego hay que indicar el texto sustitutivo con la
propiedad SelectedText. El texto sustituido y el de sustitución no tienen por qué tener el
mismo tamaño.

TextBox1.SelectionStart = 28
TextBox1.SelectionLength = 11

ShareVideos
TextBox1.SelectedText = "Mediterráneo"

También la selección de texto puede efectuarse con el método Select, indicando el carácter
de inicio de la selección y el número de caracteres de la selección.

TextBox1.Select(28,11)
TextBox1.SelectedText = "Mediterráneo"

La selección de la totalidad del texto se puede efectuar con el método SelectAll. Por
ejemplo, se puede forzar la selección de todo el texto cuando el control recibe el foco.

Private Sub TextBox1_GotFocus(ByVal sender As Object, ByVal e As System.


EventArgs) Handles TextBox1.GotFocus
TextBox1.SelectAll()
End Sub

Clásicamente, en cuanto un control pierde el foco, la selección de texto que estaba en el


interior del texto ya no es visible. La propiedad HideSelection en False permite conservar
la selección visible, incluso si el foco ya no tiene el foco.

Para la gestión del portapapeles, el control TextBox dispone de un menú contextual que
permite realizar las operaciones habituales. Sin embargo, existe la posibilidad de llamar los
métodos Copy, Cut y Paste para gestionar las operaciones de copiar-pegar de otra manera,
por ejemplo un menú de la aplicación. Las operaciones cortar y pegar no serán posibles si
el control TextBox está configurado en solo lectura con la propiedad ReadOnly en True; la
modificación del texto por el usuario es imposible en este caso.

Como todo el mundo se puede equivocar, el control TextBox nos propone el método Undo,
que permite cancelar la última modificación de texto efectuada en el control. Este método
ya se puede utilizar con la opción cancelar del menú contextual del control TextBox o por
el atajo de teclado [Ctrl] Z. También se la puede llamar gracias a otro menú de su
aplicación. Solo existe un nivel de «Undo», ¡no podrá volver al texto que escribió hace dos
horas!

En este control también está disponible el evento TextChanged, que se produce cuando la
propiedad Text del control se ha modificado (con el código de la aplicación o porque lo
hace el usuario). No se debe modificar la propiedad Text de su control en el código de este
evento, so pena de generar un desbordamiento de pila en la ejecución de la aplicación,
como en el ejemplo siguiente.

Private Sub TextBox1_TextChanged(ByVal sender As System.Object, ByVal e


As
System.EventArgs) Handles TextBox1.TextChanged
TextBox1.Text = Rnd()

ShareVideos
End Sub

b. El control MaskedTextBox

Este control es una mejora del control TextBox, porque permite verificar automáticamente
que la información elegida se corresponda con lo que la aplicación espera. La propiedad
Mask determina el formato de los datos que se pueden introducir en el control. El editor,
accesible desde la ventana de propiedades, permite elegir una máscara existente o
configurar su propia máscara.

En la propiedad Mask algunos caracteres tienen un significado especial:

0 representa una cifra obligatoria (0 a 9).

9 representa una cifra o un espacio opcional.

L representa una letra obligatoria (de a a z, o de A a Z).

? representa una letra opcional.

C representa un carácter cualquiera.

. representa el separador decimal.

, representa el separador de miles.

: representa el separador horario.

/ representa el separador de fecha.

$ representa el símbolo monetario.

< los siguientes caracteres serán transformados en minúsculas.

> los siguientes caracteres serán transformados en mayúsculas.

| cancela el efecto de dos caracteres > y <.

\ carácter de escape que hace perder su significado especial al siguiente carácter.

Todos los otros caracteres se visualizan tal y como aparecen en el control.

La siguiente máscara puede utilizarse, por ejemplo, para la introducción de una dirección
IP :

ShareVideos
000\.000\.000\.000

c. El control RichTextBox

El control RichTextBox permite la visualización, la introducción y la modificación de texto


con formato. Posee las mismas funcionalidades que el control TextBox, pero además es
capaz de generar distintas fuentes de caracteres, distintos colores, imágenes, etc. Ofrece
todas las funcionalidades de base de una aplicación de tratamiento de texto. A continuación
vamos a detallar las funciones principales:

Carga y registro de archivo

Los métodos LoadFile y SaveFile permiten la carga y el registro desde o hacia un archivo.
El único parámetro obligatorio para estas dos funciones representa la ruta de acceso
completa hacia el archivo que desea cargar o guardar. El formato de archivo utilizado por
defecto para estas dos funciones es el formato rtf (Rich Text Format). Si fuera necesario
utilizar otros formatos de archivo, deberemos especificarlo con un segundo parámetro que
es una constante de la enumeración RichTextBoxStreamType. En el caso de una lectura de
archivo, es importante que los datos contenidos en el archivo concuerden con la constante
utilizada.

Por ejemplo, la lectura de un archivo de t

Herencia de formularios
Quizá necesite alguna vez que un proyecto invoque a un formulario similar a otro que ya ha
creado para otro proyecto. También puede crear un formulario de base que contenga
parámetros como un fondo estático o una presentación particular de los controles que
piensa utilizar varias veces en un proyecto. Cada nueva versión del formulario contendrá
modificaciones respecto del modelo original. La herencia de formularios nos permite crear
un formulario base y heredar de él para personalizar las nuevas versiones así creadas.

Para poder crear un formulario heredado, previamente necesitamos diseñar el formulario


base. Para que la herencia de formularios esté disponible, el proyecto que contiene el
formulario base debe haber sido obligatoriamente compilado. Para agregar un formulario
heredado, utilizamos el cuadro de diálogo clásico de agregar nuevos elementos al proyecto,
seleccionando la opción Formulario heredado.

A continuación, asigne un nombre a su formulario y haga clic en el botón Agregar. Se


mostrará el cuadro de diálogo Selección de herencia y, si el proyecto actual ya contiene
formularios, se visualizarán. Para heredar de un formulario disponible en otro ensamblado,
haga clic en el botón Examinar, seleccione el archivo (.exe o .dll) que contenga el
formulario base de su elección y confirme con el botón Aceptar. De esta forma, el nuevo
formulario será agregado a su proyecto. En este formulario, los controles procedentes de la
herencia están marcados con el símbolo .

ShareVideos
La propiedad Modifiers de cada control del formulario base determina las acciones posibles
de estos en el formulario heredado. Se aplican las reglas estándar de herencia, que se
resumen a continuación:

 Public: se puede cambiar el tamaño de los controles y también desplazarlos. Los


controles están accesibles internamente por la clase que los declara y externamente
por otras clases.
 Protected: se puede cambiar el tamaño de los controles y también desplazarlos. Los
controles están accesibles internamente por la clase que los declara y por todas las
clases heredadas de la clase padre, pero las clases externas no pueden acceder a
ellos.
 Protected Friend: se puede cambiar el tamaño de los controles y también
desplazarlos. Los controles están accesibles internamente por la clase que los
declara, por todas las clases que heredan de la clase padre y por otros miembros del
ensamblado que los contiene.
 Friend: todos los aspectos del control se consideran accesibles de solo lectura. No se
pueden desplazar, ni cambiar de tamaño, ni modificar sus propiedades. El control
está accesible únicamente por otros miembros del ensamblado que lo contiene.
 Private: todos los aspectos del control se consideran accesibles de solo lectura. No
se pueden desplazar, ni cambiar de tamaño, ni modificar sus propiedades. El control
está solamente accesible por la clase que lo declara.

Por supuesto, se pueden agregar otros controles al formulario heredado para personalizar su
aspecto. Si se modifica el formulario base después de su utilización en una relación de
herencia, las modificaciones se propagarán a los formularios heredados durante la
compilación del formulario base.

Herencia de formularios
Quizá necesite alguna vez que un proyecto invoque a un formulario similar a otro que ya ha
creado para otro proyecto. También puede crear un formulario de base que contenga
parámetros como un fondo estático o una presentación particular de los controles que
piensa utilizar varias veces en un proyecto. Cada nueva versión del formulario contendrá
modificaciones respecto del modelo original. La herencia de formularios nos permite crear
un formulario base y heredar de él para personalizar las nuevas versiones así creadas.

Para poder crear un formulario heredado, previamente necesitamos diseñar el formulario


base. Para que la herencia de formularios esté disponible, el proyecto que contiene el
formulario base debe haber sido obligatoriamente compilado. Para agregar un formulario
heredado, utilizamos el cuadro de diálogo clásico de agregar nuevos elementos al proyecto,
seleccionando la opción Formulario heredado.

A continuación, asigne un nombre a su formulario y haga clic en el botón Agregar. Se


mostrará el cuadro de diálogo Selección de herencia y, si el proyecto actual ya contiene
formularios, se visualizarán. Para heredar de un formulario disponible en otro ensamblado,

ShareVideos
haga clic en el botón Examinar, seleccione el archivo (.exe o .dll) que contenga el
formulario base de su elección y confirme con el botón Aceptar. De esta forma, el nuevo
formulario será agregado a su proyecto. En este formulario, los controles procedentes de la
herencia están marcados con el símbolo .

La propiedad Modifiers de cada control del formulario base determina las acciones posibles
de estos en el formulario heredado. Se aplican las reglas estándar de herencia, que se
resumen a continuación:

 Public: se puede cambiar el tamaño de los controles y también desplazarlos. Los


controles están accesibles internamente por la clase que los declara y externamente
por otras clases.
 Protected: se puede cambiar el tamaño de los controles y también desplazarlos. Los
controles están accesibles internamente por la clase que los declara y por todas las
clases heredadas de la clase padre, pero las clases externas no pueden acceder a
ellos.
 Protected Friend: se puede cambiar el tamaño de los controles y también
desplazarlos. Los controles están accesibles internamente por la clase que los
declara, por todas las clases que heredan de la clase padre y por otros miembros del
ensamblado que los contiene.
 Friend: todos los aspectos del control se consideran accesibles de solo lectura. No se
pueden desplazar, ni cambiar de tamaño, ni modificar sus propiedades. El control
está accesible únicamente por otros miembros del ensamblado que lo contiene.
 Private: todos los aspectos del control se consideran accesibles de solo lectura. No
se pueden desplazar, ni cambiar de tamaño, ni modificar sus propiedades. El control
está solamente accesible por la clase que lo declara.

Por supuesto, se pueden agregar otros controles al formulario heredado para personalizar su
aspecto. Si se modifica el formulario base después de su utilización en una relación de
herencia, las modificaciones se propagarán a los formularios heredados durante la
compilación del formulario base.

Principio de funcionamiento de una base


de datos
Las bases de datos se han convertido en elementos ineludibles en la mayoría de las
aplicaciones. Sustituyen la utilización de archivos gestionados por el propio desarrollador.
Esta aportación permite una mayor productividad durante el desarrollo y también mejora de
manera significativa las prestaciones de las aplicaciones. También permiten a los usuarios
compartir datos entre ellos. Para poder utilizar una base de datos, debe conocer un mínimo
de vocabulario relacionado con esta tecnología.

ShareVideos
1. Terminología

En el contexto de las bases de datos, a menudo se utilizan los siguientes términos:

Base de datos relacional

Una base de datos relacional es un tipo de base de datos que utiliza matrices para almacenar
información. Utiliza valores procedentes de dos matrices para asociar los datos de una tabla
con los de la otra. En general, en una base de datos relacional, los datos se almacenar una
sola vez.

Tabla

Una tabla es un componente de una base de datos que almacena los datos en registros (filas)
y campos (columnas). Los datos se agrupan, en general, en tablas en función de su
categoría. Por ejemplo, tenemos la tabla de Clientes, de Productos o de Pedidos.

Registro

El registro es el conjunto de datos relativos a un elemento de una tabla. A nivel lógico, los
registros son equivalentes a las filas de una tabla. Por ejemplo, un registro de la tabla
Clientes contiene las características de un cliente particular.

Campo

Un registro se compone de varios campos. Cada campo de un registro contiene un único


dato relativo al registro. Por ejemplo, un registro Cliente puede contener los campos
CodigoCliente, Apellido, Nombre...

Clave primaria

Se utiliza una clave primaria para identificar de manera única cada fila de una tabla. La
clave primaria es un campo o una combinación de campos cuyo valor es único en la tabla.
Por ejemplo, el campo CodigoCliente es la clave primaria de la tabla Cliente. No puede
haber dos clientes con el mismo código.

Clave externa

Una clave externa representa uno o varios campos de una tabla que hacen referencia a los
campos de la clave primaria de otra tabla. Las claves externas indican la manera en que se
relacionan las matrices.

Relación

Una relación es una asociación establecida entre campos comunes en dos matrices. Una
relación puede ser de uno a uno, de uno a varios o de varios a varios. Gracias a las

ShareVideos
relaciones, los resultados de las consultas pueden contener datos procedentes de varias
matrices. Una relación de uno a varios entre la tabla Cliente y la tabla Pedido permite que
una consulta devuelva todos los pedidos correspondientes a un cliente.

2. El lenguaje SQL

Antes de poder escribir una aplicación en Visual Basic utilizando datos, debe conocer el
lenguaje SQL (Structured Query Language). Este lenguaje permite dialogar con la base de
datos. Existen diferentes versiones del lenguaje SQL según la base de datos utilizada. Sin
embargo, SQL también dispone de una sintaxis elemental, normalizada, independiente de
cualquier base de datos.

a. Búsqueda de datos

El lenguaje SQL permite especificar los registros que se han de extraer, así como el orden
en el que desea extraerlos. Puede crear una instrucción SQL que extrae información de
varias matrices simultáneamente, o crear una instrucción que extrae únicamente un registro
específico.

Se utiliza la instrucción SELECT para devolver campos específicos de una o varias


matrices de la base de datos.

La siguiente instrucción devuelve la lista de los apellidos y nombres de todos los registros
de la tabla Cliente:

SELECT Apellido,Nombre FROM Cliente

Puede utilizar el símbolo * en lugar de la lista de los campos cuyo valor desea recuperar:

SELECT * FROM Cliente

Puede limitar el número de registros seleccionados utilizando uno o varios campos para
filtrar el resultado de la consulta. Hay diferentes cláusulas disponibles para ejecutar este
filtro.

Cláusula WHERE

Esta cláusula permite especificar la lista de las condiciones que deberán cumplir los
registros para formar parte de los resultados devueltos. El siguiente ejemplo permite
encontrar todos los clientes que viven en Barcelona:

SELECT * FROM Cliente WHERE Ciudad=’Barcelona’

La sintaxis de esta cláusula requiere la utilización de comillas simples para la delimitación


de las cadenas de caracteres.

ShareVideos
Clause WHERE ... IN

Puede utilizar la cláusula WHERE ... IN para devolver todos los registros que cumplen con
una lista de criterios. Por ejemplo, puede buscar todos los clientes que viven en Francia o
en España:

SELECT * FROM Cliente WHERE Pais IN (’Francia’,’España’)

Clause WHERE ... BETWEEN

También puede devolver una selección de registros que se sitúan entre dos criterios
especificados. La siguiente consulta permite recuperar la lista de los pedidos pasados en el
mes de noviembre de 2005:

SELECT * FROM Pedidos WHERE FechaPedido BETWEEN ’01/11/05’ AND ’30/11/05’

Clause WHERE ... LIKE

Puede utilizar la cláusula WHERE ... LIKE para devolver todos los registros en los que
existe una condición particular para un campo dado. Por ejemplo, la siguiente sintaxis
selecciona todos los clientes cuyo apellido empieza con una d:

SELECT * FROM Cliente WHERE Apellido LIKE ’d%’

En esta instrucción, se utiliza el símbolo % para reemplazar una secuencia de caracteres


cualquiera.

Clause ORDER BY ...

Puede utilizar la cláusula ORDER BY para devolver los registros en un orden particular. La
opción ASC indica un orden creciente, la opción DESC indica un orden decreciente. Se
pueden especificar varios campos como criterio de ordenación. Se analizan de izquierda a
derecha. En caso de igualdad en el valor de un campo, se utiliza el siguiente campo:

SELECT * FROM Cliente ORDER BY Apellido DESC,Nombre ASC

Esta instrucción devuelve los clientes organizados por orden decreciente según el apellido
y, en caso de igualdad, por orden creciente según el nombre.

b. Inserción de datos

La creación de registros en una tabla se efectúa con el comando INSERT INTO. Hay que
indicar la tabla en la que desea insertar una fila, la lista de los campos para los cuales usted
especifica un valor y, finalmente, la lista de los valores correspondientes. Por lo tanto, la
sintaxis completa es la siguiente:

ShareVideos
INSERT INTO cliente (codigoCliente,nombre,apellido) VALUES (1000,
’Pedro’, ’García’)

Al insertar este nuevo cliente, solo el apellido y el nombre aparecen en la tabla. Los otros
campos tomarán el valor NULL. Si la lista de campos no está indicada, la instrucción
INSERT exige que se especifique un valor para cada campo de la tabla. Por tanto, está
obligado a utilizar la palabra clave NULL para indicar que, para un campo determinado, no
existen datos. Si la tabla Cliente está compuesta por cinco campos (codigoCliente, apellido,
nombre, dirección, país), la instrucción anterior se puede escribir con la siguiente sintaxis:

INSERT INTO cliente VALUES (1000,’García’,’Pedro’,NULL,NULL)

En este caso, las dos palabras clave NULL son obligatorias para los campos dirección y
país.

c. Actualización de datos

La modificación de campos de registros existentes se efectúa con la instrucción UPDATE.


Esta instrucción puede actualizar varios campos de varios registros de una tabla a partir de
las expresiones que se le facilitan. Para ello, debe proporcionar el nombre de la tabla que se
debe actualizar, así como el valor que hay que asignar a los diferentes campos. La lista se
indica con la palabra clave SET, seguida por la asignación del nuevo valor de los diferentes
campos. Si desea que las modificaciones se lleven a cabo en un conjunto limitado de
registros, debe especificar la cláusula WHERE, con el fin de limitar el alcance de la
actualización. Si no se indica ninguna cláusula WHERE, la modificación se hará en el
conjunto de los registros de la tabla.

Por ejemplo, para modificar la dirección de un cliente en particular, puede utilizar la


siguiente instrucción:

UPDATE Cliente SET dirección= ’Miguel Servet 26 Zaragoza’ WHERE


codigoCliente=1000

Si la modificación se realiza en el conjunto de los registros de la tabla, la cláusula WHERE


es inútil. Por ejemplo, si desea aumentar el precio unitario de todos sus artículos, puede
utilizar la siguiente instrucción:

UPDATE catalogo SET precioUnitario=precioUnitario*1.1

d. Eliminación de datos

La instrucción DELETE FROM permite eliminar uno o varios registros de una tabla. Como
mínimo, se debe facilitar el nombre de la tabla en la que se va a efectuar la eliminación. Si
no se indica nada más, todas las filas de la tabla serán eliminadas. En general, una cláusula
WHERE se añade para limitar la extensión de la eliminación. El siguiente comando borra
todos los registros de la tabla Cliente:

ShareVideos
DELETE FROM Cliente

El siguiente comando es menos radical y solo elimina un registro determinado:

DELETE FROM Cliente WHERE codigoCliente=1000

Por supuesto, el lenguaje SQL es mucho más completo que lo que acabamos de ver y no se
puede limitar a estas cinco instrucciones. Sin embargo, estas son suficientes para la
manipulación de los datos a partir de Visual Basic. Si desea profundizar en el aprendizaje
del lenguaje SQL, consulte uno de los libros disponibles en esta colección que trate este
tema más detalladamente.

Presentación de ADO.NET
ADO.NET es un conjunto de clases, interfaces, estructuras y enumeraciones que permiten
la manipulación de los datos. Los diferentes componentes de ADO.NET permiten separar el
acceso a los datos de la manipulación de los datos. ADO.NET facilita también la utilización
del lenguaje XML, al permitir la conversión al formato XML de datos relacionales o la
importación de datos en formato XML en un modelo relacional. Hay dos modos de
funcionamiento disponibles en ADO.NET:

 El modo conectado.
 El modo no conectado.

1. Modo conectado

En un entorno conectado, la aplicación o el usuario están permanentemente conectados a la


fuente de datos. Desde los principios de la informática, este ha sido el único modo
disponible. Este modo presenta algunas ventajas en su funcionamiento:

 Es fácil de gestionar: la conexión se abre al principio de la aplicación y luego se


cierra al finalizarla.
 El acceso concurrente es más fácil de controlar: como todos los usuarios están
conectados de forma permanente, es más fácil controlar quién trabaja en los datos.
 Los datos están actualizados: siempre gracias a la conexión permanente a los datos,
resulta fácil avisar a todas las aplicaciones que los utilizan de las modificaciones
que se acaban de realizar.

Pero existen ciertos inconvenientes:

 La conexión de red debe ser permanente: si se utiliza la aplicación en un ordenador


portátil, puede que el acceso a la red no esté siempre disponible.
 Nos arriesgamos a desaprovechar recursos del servidor: cuando se establece una
conexión entre una aplicación cliente y un servidor, se asignan recursos del servidor
para la gestión de la conexión. Estos recursos están monopolizados por la conexión,
incluso aunque no haya ningún dato que esté transitando por la conexión.

ShareVideos
Sin embargo, en ciertas situaciones, la utilización de un modo conectado es ineludible. Es
el caso, por ejemplo, de las aplicaciones que efectúan tratamientos en tiempo real.

2. Modo no conectado

Un modo no conectado significa que una aplicación o un usuario no está conectado


constantemente a una fuente de datos. Las aplicaciones de Internet utilizan a menudo este
modo de funcionamiento. Se abre la conexión, se extraen los datos y después se cierra la
conexión. El usuario trabaja con los datos desde su navegador, y la conexión se vuelve a
abrir para la actualización de la fuente de datos o para la obtención de otros. Los usuarios
que trabajan en ordenadores portátiles son los principales usuarios de entornos
desconectados. Un médico puede, por ejemplo, cargar por la mañana los historiales
médicos de los pacientes que va a visitar durante el día, y luego, por la tarde, fusionar las
modificaciones en la base de datos. Las ventajas de un entorno desconectado son las
siguientes:

 Se utilizan las conexiones durante el tiempo más corto posible. De esta manera, un
pequeño número de conexiones disponibles en un servidor son suficientes para
muchos usuarios.
 Un entorno desconectado mejora la escalabilidad y las prestaciones de una
aplicación al optimizar la disponibilidad de las conexiones.

Sin embargo, el entorno desconectado comporta algunos inconvenientes:

 Los datos disponibles en la aplicación no siempre están actualizados. Por ejemplo,


en el caso de nuestro médico, si su ayudante añade los resultados del análisis
después de que él haya recogido las historias médicas de sus pacientes, no podrá
disponer inmediatamente de la información.
 Pueden surgir conflictos cuando se actualiza la información en la base de datos. Este
tipo de problemas se deben solucionar en el momento de diseñar la aplicación. Hay
diferentes planteamientos posibles para la gestión de estos conflictos:
o Autorizar el predominio de las actualizaciones más recientes, suprimiendo
los datos ya presentes en la base de datos.
o Autorizar el predominio de las actualizaciones más antiguas, rechazando las
nuevas actualizaciones.
o Prever código que permita al usuario elegir lo que desea hacer en caso de
conflicto en el momento de actualizar.

3. Arquitectura de ADO.NET

La meta de ADO.NET consiste en facilitar un conjunto de clases que permite el acceso a


las bases de datos. Hay dos tipos de componentes disponibles:

 Los proveedores de datos específicos para un tipo de base de datos. Aseguran la


comunicación con un tipo específico de base de datos y permiten la manipulación
de los datos directamente en la base de datos en modo conectado. Sin embargo, las

ShareVideos
posibilidades son limitadas, ya que solo está disponible un acceso en modo de solo
lectura.
 Las clases de manipulación de datos, independientes del tipo de base de datos,
utilizables incluso sin base de datos, permiten la manipulación local de los datos en
la aplicación.

4. Los proveedores de datos

Los proveedores de datos sirven de pasarela entre una aplicación y una base de datos. Se
utilizan para recuperar la información a partir de la base de datos, y transferir los cambios
efectuados en los datos por la aplicación hacia la base de datos. Hay cuatro proveedores de
datos disponibles en el Framework .NET:

 El proveedor para SQL Server.


 El proveedor para OLE DB.
 El proveedor para ODBC.
 El proveedor para Oracle.

Todos ellos proponen la implementación de cuatro clases de base, necesarias para el


diálogo con la base de datos:

 La clase Connection permite establecer una conexión con el servidor de base de


datos.
 La clase Command permite pedir la ejecución de una instrucción o de un conjunto
de instrucciones SQL a un servidor.
 La clase DataReader proporciona un acceso en solo lectura, y un desplazamiento,
solo hacia delante, de los datos (mismo principio que un archivo secuencial).
 La clase DataAdapter se utiliza para asegurar la transferencia de los datos hacia un
sistema de caché local en la aplicación (DataSet) y actualizar la base de datos, en
función de las modificaciones efectuadas localmente en el DataSet.

Algunas clases están disponibles para, por ejemplo, la gestión de las transacciones o el paso
de parámetros a una instrucción SQL.

a. SQL Server

El proveedor de datos para SQL Server utiliza un protocolo nativo para dialogar con el
servidor de base de datos. Además, consume pocos recursos porque accede al servidor sin
utilizar capa lógicas adicionales como OLE DB u ODBC. Se puede utilizar con SQL Server
a partir de la versión 7. Todas las clases de este proveedor de datos están disponibles en el
espacio de nombres System.Data.SqlClient. En este espacio de nombres, el nombre de
cada clase contiene el prefijo Sql. Así, la clase que permite conectarse a un servidor SQL
Server se llama SqlConnection.

ShareVideos
b. OLE DB

El proveedor OLE DB utiliza la capa lógica OLE DB para comunicarse con el servidor de
base de datos. Puede utilizar este proveedor para dialogar con una base de datos para la que
no existen proveedores específicos, pero para la que está disponible el controlador OLE
DB. Con esta solución, el proveedor no contacta con el servidor directamente, sino que
pasa por el controlador OLE DB para comunicarse. Para que esta comunicación sea
posible, el controlador debe implementar algunas interfaces. Todas las clases están
disponibles en el espacio de nombres System.Data.OleDb. Los nombres de clase de este
espacio de nombres están precedidos por OleDb. Para poder funcionar correctamente, este
proveedor exige la instalación de MDAC 2.6 en la máquina (Microsoft Data Access
Components).

c. ODBC

El proveedor ODBC utiliza un controlador ODBC nativo para comunicarse con el servidor
de base de datos. El principio es idéntico al utilizado para el proveedor OLE DB. Todas las
clases están disponibles en el espacio de nombres System.Data.Odbc. Los nombres de
clases vienen precedidos por Odbc. Para poder funcionar correctamente, este proveedor
exige la instalación de MDAC 2.6 en la máquina (Microsoft Data Access Components).

d. Oracle

El proveedor Oracle permite la conexión a una fuente de datos Oracle, a través de las
herramientas cliente de Oracle. Estas herramientas deben instalarse en el sistema operativo
para poderse conectar a una base de datos Oracle. Es necesaria la versión 8.1.7 o una
superior. Las clases están en el espacio de nombres System.Data.OracleClient y utilizan
Oracle como prefijo de nombre. Para utilizar el proveedor para Oracle, deberá también
agregar una referencia hacia la biblioteca System.Data.OracleClient.dll.

5. Buscar los proveedores disponibles

Para asegurar el buen funcionamiento de una aplicación que utiliza un acceso a los datos,
los proveedores de datos deben estar disponibles en el puesto cliente. La clase
DbProviderFactories propone el método compartido GetFactoryClasses, que permite
enumerar los proveedores de datos disponibles en el cliente. El ejemplo siguiente de código
muestra el nombre, la descripción y el espacio de nombres raíz de cada proveedor instalado
en el equipo de trabajo.

Imports System.Data
Imports System.Data.Common
Module ListaProviders
Sub Main()
Dim resultado As DataTable
’recuperación de la lista de proveedores en un dataTable
resultado = DbProviderFactories.GetFactoryClasses()
Dim columna As DataColumn
Dim fila As DataRow

ShareVideos
’recorre las columnas del DataTable y muestra el nombre
For Each columna In resultado.Columns
Console.Write(columna.ColumnName & vbTab)
Next
Console.WriteLine()
’ recorre el DataTable y muestra cada fila
For Each fila In resultado.Rows
’ recorre cada fila y muestra cada campo
For Each columna In resultado.Columns
Console.Write(fila(columna.ColumnName) & vbTab)
Next
Console.WriteLine()
Next
Console.ReadLine()
Stop
End Sub
End Module

6. Compatibilidad del código

En función del proveedor utilizado, debe importar el espacio de nombres correspondiente


para tener fácil acceso a las clases del proveedor. Sin embargo, como las clases de cada uno
de los proveedores no llevan el mismo nombre, su código sera específico de un tipo de
proveedor. Es posible escribir código prácticamente independiente del tipo de proveedor;
para ello, en vez de utilizar las clases específicas de cada uno de los proveedores, puede
utilizar como tipo de datos las interfaces que implementan. La utilización de una clase
específica solo es indispensable para crear la conexión. Una vez creada la conexión, puede
trabajar únicamente con interfaces. El siguiente ejemplo de código muestra la lista del
contenido de una tabla de una base SQL Server que utiliza únicamente interfaces.

Module accesoBdPorInterfaces
Dim ctn As IDbConnection
Public Sub main()
’ es la única fila de código específica para un proveedor
ctn = New System.Data.SqlClient.SqlConnection("Data
Source=TG;Initial
Catalog=Northwind;Integrated Security=True")
Dim cmd As IDbCommand
cmd = ctn.CreateCommand
ctn.Open()
cmd.CommandText = "select * from products"
Dim lector As IDataReader
lector = cmd.ExecuteReader
Console.WriteLine("Lectura de datos en una base de datos SQL
Server")
Do While lector.Read
Console.WriteLine("número: {0} nombre producto: {1}",
lector.GetInt32(0), lector.GetString(1))
Loop
End Sub
End Module

La ejecución de este código muestra el siguiente resultado:

ShareVideos
Lectura de datos en una base de datos SQL Server
número: 56 nombre producto: Gnocchi di nonna Alice
número: 57 nombre producto: Ravioli Angelo
número: 58 nombre producto: Caracoles de Lleida
número: 59 nombre producto: Raclette Courdavault
número: 60 nombre producto: Queso Roncal
número: 61 nombre producto: Sirope de fresa
número: 62 nombre producto: Tarta de azúcar

Si esta aplicación debe migrar después hacia otro tipo de base de datos, solo hay que
cambiar la línea relativa a la conexión. Si ahora los datos están disponibles en una base
Access, la creación de la conexión toma la siguiente forma:

ctn = New System.Data.OleDb.OleDbConnection ("Provider=Microsoft.Jet.


OLEDB.4.0;
Data Source=C:\Documents and Settings\usuario\Mis documentos\
libro vb.net 2010\
acceso a bases de datos\ejemplos\NWIND.MDB")

La ejecución del código modificado de esta forma genera efectivamente el mismo


resultado:

Lectura de datos en una base de datos Access


número: 56 nombre producto: Gnocchi di nonna Alice
número: 57 nombre producto: Ravioli Angelo
número: 58 nombre producto: Caracoles de Lleida
número: 59 nombre producto: Raclette Courdavault
número: 60 nombre producto: Queso Roncal
número: 61 nombre producto: Sirope de fresa
número: 62 nombre producto: Tarta de azúcar

Pero conviene ser prudente y no utilizar instrucciones SQL específicas para un tipo de base
de datos particular. Para facilitar la revisión del código, es preferible agrupar todas las
instrucciones SQL en forma de constantes de tipo cadena de caracteres al principio de cada
módulo. Con esta técnica, no tendrá que buscar instrucciones SQL en medio de centenares
de líneas de código Visual Basic. También conviene ser prudente durante la utilización de
parámetros en una instrucción SQL. El proveedor para SQL Server utiliza parámetros con
nombre; por lo tanto, el orden de creación de los parámetros no tiene importancia.

El proveedor para OLE DB utiliza la posición de los parámetros en la instrucción SQL para
la sustitución a la hora de la ejecución. El orden de la creación de los parámetros es pues,
en este caso, capital para el funcionamiento correcto de la instrucción.

Utilización del modo conectado


En este capítulo, vamos a tratar de las operaciones que pueden ser ejecutadas en una base
de datos al utilizar el modo conectado. Ciertas nociones estudiadas en este capítulo serán
útiles para el funcionamiento en modo desconectado. Para probar las distintas
funcionalidades estudiadas en este capítulo, utilizaremos un servidor SQL Server 2010. La

ShareVideos
base de datos utilizada será la base Northwind que se crea por defecto cuando se instala el
servidor. Una parte de la estructura de la base está disponible en el esquema siguiente:

1. Conexión a una base de datos

Para poder trabajar con un servidor de base de datos, una aplicación debe establecer una
conexión de red con el servidor. La clase SqlConnection es capaz de gestionar una
conexión hacia un servidor SQL Server versión 7.0 o posterior. Como para cualquier
objeto, en primer lugar debemos declarar una variable.

Dim ctn As System.Data.SqlClient.SqlConnection

Luego, debemos crear la instancia de la clase e inicializarla invocando un constructor. La


inicialización va a consistir esencialmente en indicar los parámetros utilizados para
establecer la conexión con el servidor. Estos parámetros se definen en forma de una cadena
de caracteres. Pueden ser indicados durante la llamada al constructor o modificados por la
propiedad ConnectionString.

a. Cadena de conexión

El formato estándar de una cadena de conexión está constituido por una serie de pares
palabra clave/valor, separadas por puntos y comas. El signo = se usa para la asignación de
un valor a una palabra clave. El análisis de la cadena se efectúa durante la asignación de la
cadena a la propiedad ConnectionString. Los valores asociados a las palabras clave se
extraen y se asignan a las distintas propiedades de la conexión. Si se encuentra un error de
sintaxis, se genera una excepción inmediatamente y no se modifica ninguna propiedad. Por
el contrario, solo se podrá controlar algunas propiedades durante la apertura de la conexión.
En este momento se activará una excepción si la cadena de conexión contiene un error. solo
se puede modificar la cadena de conexión si la conexión está cerrada. Estas son las palabras
claves existentes para una cadena de conexión:

Connect Timeout

Duración en segundos durante la cual la aplicación esperará una respuesta del servidor a su
petición de conexión. Pasado este plazo, se activa una excepción.

Data Source

Nombre o dirección de red del servidor hacia el que se establece la conexión. El número del
puerto se puede especificar después del nombre o de la dirección de red. Si no está
indicado, el número de puerto es igual a 1433.

Initial Catalog

ShareVideos
Nombre de la base de datos en la que se debe efectuar la conexión.

Integrated Security

Si este valor se coloca en false, se debe facilitar un nombre de usuario y una contraseña en
la cadena de conexión. En caso contrario, se utiliza la cuenta Windows del usuario para la
autenticación.

Persist Security Info

Si se posiciona este valor en true, el nombre del usuario y su contraseña son accesibles por
la conexión. Por razones de seguridad, se debe colocar este valor en false. De hecho, es así
si no indica nada en la cadena de conexión.

Pwd

Contraseña asociada a la cuenta SQL Server utilizada para la conexión. Si no existe


contraseña asociada a una cuenta, se puede omitir esta información en la cadena de
conexión.

User ID

Nombre de la cuenta SQL Server utilizada para la conexión.

Connection LifeTime

Indica la duración de una conexión en un pool de conexiones. Un valor igual a cero indica
una duración ilimitada.

Connection Reset

Indica si la conexión es reinicializada cuando vuelve a entrar en el pool.

Max Pool Size

Número máximo de conexiones en el pool.

Min Pool Size

Número mínimo de conexiones en el pool.

Pooling

Indica si la conexión se puede extraer de un pool de conexiones.

Una cadena de conexión adopta la siguiente forma mínima:

ShareVideos
ctn.ConnectionString = "Data Source=Minerve;Initial Catalog=Northwind;
Integrated
Security=true"

b. Pool de conexiones

Los pools de conexiones permiten mejorar las prestaciones de una aplicación al evitar la
creación de conexiones adicionales. Cuando una conexión está abierta, se crea un pool de
conexiones que se basa en un algoritmo basado, a su vez, en la cadena de conexión. Así
cada pool está asociado a una cadena de conexión particular. Si se abre una nueva conexión
y no hay pool que corresponda exactamente a su cadena de conexión, se crea un nuevo
pool. Los pools de conexiones así creados existirán hasta el final de la aplicación. Cuando
se crea un pool, se pueden crear otras conexiones automáticamente para satisfacer el valor
Min Pool Size indicado en la cadena de conexión. Se podrán añadir otras conexiones al
pool hasta alcanzar el valor Max Pool Size de la cadena de conexión. Cuando se requiere
una conexión, se puede obtener a partir de un pool de conexiones (si existe uno que
corresponde exactamente a las características de la conexión requerida). Por supuesto, es
necesario que el pool contenga una disponible y activa.

Si se alcanza el número máximo de conexiones en el pool, la petición se pone en la cola


hasta que una conexión esté de nuevo disponible. Una conexión se vuelve a poner a
disposición del pool durante su cierre o durante la llamada del método Dispose en la
conexión. Por esta razón, se recomienda cerrar explícitamente las conexiones cuando ya no
se utilizan en la aplicación. Se retiran las conexiones del pool cuando este detecta que la
conexión no ha sido utilizada durante un cierto tiempo, indicado por el valor
ConnectionLifeTime de la cadena de conexión. También se retiran las conexiones del pool
si este detecta que la conexión con el servidor ha sido interrumpida.

c. Eventos de conexión

La clase SQLConnection propone dos eventos que le permiten ser avisado cuando el estado
de la conexión cambia o cuando el servidor envía un mensaje de información. El evento
StateChanged se activa cuando cambia el estado de la conexión. El gestor de este evento
recibe un parámetro de tipo StateChangeEventArg, que permite obtener con la propiedad
CurrentState el estado actual de la conexión, y con la propiedad OriginalState, el estado de
la conexión antes de la desactivación del evento. Para probar el valor de estas dos
propiedades, puede utilizar la enumeración ConnectionState.

El evento InfoMessage se activa cuando el servidor le informa de una situación anormal,


pero que no justifica la activación de una excepción (gravedad del mensaje inferior a 10).
El gestor de eventos asociado recibe un parámetro de tipo InfoMessageEventArgs. Por la
propiedad Errors de este parámetro, tiene acceso a objetos SqlErrors que corresponden a las
informaciones enviadas por el servidor. El siguiente código muestra en la consola los
mensajes de información que provienen del servidor.

ShareVideos
Private Sub ctn_InfoMessage(ByVal sender As Object, ByVal e As
System.Data.
SqlClient.SqlInfoMessageEventArgs) Handles ctn.InfoMessage
Dim info As SqlClient.SqlError
For Each info In e.Errors
Console.WriteLine(info.Message)
Next
End Sub

2. Ejecución de un comando

Después de haber establecido una conexión hacia un servidor de base de datos, puede
transmitirle instrucciones SQL. Se utiliza la clase SqlCommand para pedir al servidor la
ejecución de un comando SQL. Esta clase contiene varios métodos que permiten la
ejecución de diferentes tipos de consultas SQL. La clase SqlCommand puede instanciarse
de manera clásica, utilizando uno de sus constructores, o se puede obtener una instancia por
el método CreateCommand de la conexión.

a. Creación de un comando

La primera posibilidad para crear una instancia de SqlCommand consiste en utilizar uno de
los constructores de la clase. El uso del constructor por defecto le obliga a utilizar
diferentes propiedades para facilitar la información relativa a la instrucción SQL que se va
a ejecutar.

La propiedad CommandText contiene el texto de la instrucción SQL que se va a ejecutar.


La propiedad Connection debe hacer referencia a una conexión válida hacia el servidor de
base de datos. El siguiente código resume estas diferentes operaciones:

Dim cmd As SqlCommand


cmd = New SqlCommand
cmd.Connection = ctn
cmd.CommandText = " SELECT * FROM Products"

La segunda solución consiste en utilizar un constructor sobrecargado que acepte como


parámetros la instrucción SQL en forma de cadena de caracteres y la conexión utilizada por
esta SqlCommand. Así se puede resumir el código anterior con la siguiente línea:

Dim cmd As new SqlCommand( " SELECT * FROM Products",ctn)

La tercera solución consiste en utilizar el método CreateCommand de la conexión. En este


caso, se debe especificar únicamente la instrucción SQL.

Dim cmd as SqlCommand


cmd = ctn.CreateCommand
cmd.CommandText = " SELECT * FROM Products"

ShareVideos
b. Lectura de datos

A menudo, la instrucción SQL de un SqlCommand selecciona un conjunto de registros en


la base de datos, o a veces un valor único que es el resultado de un cálculo efectuado sobre
valores contenidos en la base. Una instrucción SQL, que devuelve un conjunto de registros,
debe ser ejecutada con el método ExecuteReader. Este método devuelve un objeto
DataReader que permitirá después la lectura de la información procedente de la base de
datos. Si la instrucción solo devuelve un valor único, el método ExecuteScalar se encarga
de la ejecución y devuelve por sí mismo el valor de la base de datos.

El siguiente código permite saber el número de pedidos pasados por un cliente:

cmd.CommandText = " select count(orderid) from orders where


customerid=’FRANK’"
Console.WriteLine("el cliente FRANK ha pasado {0} pedidos(s)",
cmd.ExecuteScalar())

El caso de las instrucciones que devuelven varios registros es un poco más complejo.
Después de haber ejecutado la instrucción con el método ExecuteReader y de haber
recuperado el objeto DataReader, puede utilizar este último para recorrer los resultados
devueltos. El método Read de la clase DataReader permite el desplazamiento por el
conjunto de los registros devueltos. Este método devuelve un booleano que indica si queda
otro registro. El desplazamiento solo es posible desde el primero hasta el último registro.
Este tipo de desplazamiento se llama Forward Only. La información contenida en el
registro actual es accesible con uno de los métodos Get... de la clase DataReader. Estos
métodos permiten extraer los datos del registro y convertirlos en un tipo de datos .NET.
Existe una versión para cada tipo de datos del Framework .NET. Por supuesto, hace falta
que la información presente en el registro se pueda convertir en el tipo correspondiente. Si
la conversión es imposible, se activa una excepción. Los métodos Get... esperan, como
parámetro, el número del campo en el que deben recuperar la información. También puede
utilizar la propiedad por defecto Item del DataReader, indicando el nombre del campo. En
este caso, no hay conversión y el valor devuelto es de tipo Object.

El siguiente código muestra la lista de todas las categorías de productos disponibles:

Imports System.Data.SqlClient
Module TestExecuteReader
Dim cmd As SqlCommand
Dim ctn As SqlConnection
Dim lector As SqlDataReader

Public Sub main()


ctn = New SqlConnection()
ctn.ConnectionString = "Data Source=localhost;
Initial Catalog=Northwind;Integrated Security=true"
ctn.Open()
cmd = New SqlCommand
cmd.Connection = ctn
cmd.CommandText = " select * from categories"
lector = cmd.ExecuteReader

ShareVideos
Do While lector.Read
Console.WriteLine("número de la categoría:{0}" & vbTab &
"Descripción:{1}", lector.GetInt32(0), lector("CategoryName"))
Loop
lector.Close()
ctn.Close()
End Sub
End Module

La utilización de una conexión por parte de un DataReader se efectúa de manera exclusiva.


Para que la conexión esté de nuevo disponible para otro comando, debe cerrar DataReader
después de su utilización.

c. Modificaciones de los datos

La modificación de los datos en una base de datos se efectúa principalmente con las
instrucciones SQL INSERT, UPDATE, DELETE. Estas instrucciones no devuelven
registros procedentes de la base de datos. Para utilizar estas instrucciones, debe crear un
SqlCommand, y luego pedir la ejecución de este comando través del método
ExecuteNonQuery. Este método devuelve el número de registros afectados por la ejecución
de la instrucción SQL contenida en el SqlCommand. Si la propiedad CommandText
contiene varias instrucciones SQL, el valor devuelto por el método ExecuteNonQuery
corresponde al número total de filas afectadas por todas las instrucciones SQL del
SqlCommand.

El siguiente código añade una nueva empresa de entrega en la tabla Shippers:

Imports System.Data.SqlClient
Module TestExecuteNonQuery
Dim cmd As SqlCommand
Dim ctn As SqlConnection
Public Sub main()
ctn = New SqlClient.SqlConnection()
ctn.ConnectionString = "Data Source=localhost;
Initial Catalog=Northwind;Integrated Security=true"
ctn.Open()
cmd = New SqlClient.SqlCommand
cmd.Connection = ctn
cmd.CommandText = "Insert into shippers (companyname,phone)
values
(’DHL’,’91 123 56 87 43’)"
Console.WriteLine("{0} fila(s) añadidas(s) en la tabla",
cmd.ExecuteNonQuery)
ctn.Close()
End Sub
End Module

d. Utilización de parámetros

La manipulación de instrucciones SQL se puede facilitar con la creación de parámetros;


permiten construir instrucciones SQL genéricas que se pueden volver a utilizar fácilmente.

ShareVideos
El principio de funcionamiento es parecido a los procedimientos y funciones de Visual
Basic. Una alternativa a la utilización de parámetros podría ser la construcción dinámica de
instrucciones SQL por concatenación de cadenas de caracteres.

A continuación, un ejemplo que utiliza esta técnica y que permite la búsqueda de un cliente
por su código (luego veremos cómo mejorar este código al utilizar parámetros):

Imports System.Data.SqlClient
Module TestBuscarConcat
Dim cmd As SqlCommand
Dim ctn As SqlConnection
Dim lector As SqlDataReader
Dim codCliente As String
Public Sub main()
ctn = New SqlConnection()
ctn.ConnectionString = "Data Source=localhost;
Initial Catalog=Northwind;Integrated Security=true"
ctn.Open()
cmd = New SqlCommand
cmd.Connection = ctn
Console.Write("introducir el código del cliente buscado:")
codCliente = Console.ReadLine()
cmd.CommandText = " SELECT * from Customers WHERE CustomerID =
’"
& codCliente & "’"
lector = cmd.ExecuteReader
Do While lector.Read
Console.WriteLine("nombre del cliente:{0}",
lector("ContactName"))
Loop
lector.Close()
ctn.Close()
Console.ReadLine()
End Sub
End Module

La parte importante de este código se encuentra en el momento de asignar un valor a la


propiedad CommandText. Una instrucción SQL correcta se debe construir por la
concatenación de cadenas de caracteres. En nuestro caso, esto es relativamente sencillo
porque solo hay un valor variable en la instrucción SQL. Si fuera necesario variar muchos
datos, sería preciso realizar una gran cantidad de concatenaciones. Los errores clásicos que
se cometen en estas concatenaciones son:

 Olvidarse de un espacio.
 Olvidarse de los caracteres " para enmarcar un valor del tipo cadena de caracteres.
 Un número de carácter ’ impar.

Todos estos errores tienen el mismo efecto: la creación de una instrucción SQL inválida
que será rechazada durante la ejecución por el servidor.

La utilización de los parámetros simplifica considerablemente la escritura de este tipo de


consulta. Se utilizan los parámetros para marcar una ubicación en un requerimiento donde

ShareVideos
estará colocado, en el momento de la ejecución, un valor literal cadena de caracteres o
numérico. Los parámetros pueden ser nominales o anónimos. Un parámetro anónimo se
introduce en una consulta con el carácter ?. Los parámetros nominales se especifican con el
carácter @ seguido por el nombre del parámetro.

La consulta de nuestro ejemplo anterior puede adoptar la siguiente forma:

cmd.CommandText = " SELECT * from Customers WHERE CustomerID = ?"

cmd.CommandText = " SELECT * from Customers WHERE CustomerID = @Codigo”

La ejecución del SqlCommand fracasa ahora si no se le proporcionan los valores de los


parámetros.

El SqlCommand debe tener una lista de valores utilizados para reemplazar los parámetros
en el momento de la ejecución. Esta lista se almacena en la colección Parameters del
SqlCommand. Antes de la ejecución del SqlCommand, es necesario crear los objetos
SqlParameter y añadirlos a la colección. Para cada SqlParameter, hay que proporcionar:

 El nombre del parámetro.


 El valor del parámetro.
 La dirección de utilización del parámetro.

Las dos primeras informaciones se indican durante la construcción del objeto:

Dim paramCodigoCliente As SqlParameter


paramCodigoCliente = New SqlParameter("@Codigo", codigoCliente)

La dirección de utilización indica si la información contenida en el parámetro es pasada al


código SQL para su ejecución (Input), o si le corresponde a la ejecución del código SQL
modificar el valor del parámetro (Output) o los dos (InputOutput). La propiedad Direction
de la clase SqlParameter indica el modo de utilización del parámetro.

El parámetro está ahora preparado para añadirse a la colección Parameters. Conviene


comprobar en este momento si la consulta utiliza parámetros anónimos. Se deben añadir los
parámetros a la colección obligatoriamente en el orden de su aparición en la consulta. Si se
utilizan los parámetros nominales, no es necesario respetar esta regla, aunque sí prudente,
por si un día el código SQL es modificado y deja de utilizar los parámetros nominales.
Podría ser el caso si debe cambiar de tipo proveedor de datos y el nuevo no acepta los
parámetros nominales en una instrucción SQL. Ahora el SqlCommand está listo para la
ejecución. Hay que observar que, con esta solución, no tenemos que preocuparnos del tipo

ShareVideos
de valor esperado por la instrucción SQL para saber si debemos enmarcarlo con caracteres
’. Si se utilizan parámetros en la salida de la instrucción SQL, solo estarán disponibles
después del cierre del DataReader. El siguiente ejemplo muestra, además del nombre del
cliente, el número de pedidos ya pasados:

Imports System.Data.SqlClient
Module TestBuscarConcat
Dim cmd As SqlCommand
Dim ctn As SqlConnection
Dim lector As SqlDataReader
Dim codCliente As String
Dim paramCodCliente As SqlParameter
Dim paramNbComandos As SqlParameter

Public Sub main()


ctn = New SqlConnection()
ctn.ConnectionString = "Data Source=localhost;
Initial Catalog=Northwind;Integrated Security=true"
ctn.Open()
cmd = New SqlCommand
cmd.Connection = ctn
Console.Write("introducir el código del cliente buscado:")
codCliente = Console.ReadLine()
cmd.CommandText = " SELECT * from Customers WHERE CustomerID =
@Codigo;select @nbPed=count(orderid) from orders where
customerid=@codigo"
paramCodCliente = New SqlParameter("@Codigo", codCliente)
paramCodCliente.Direction = ParameterDirection.Input
cmd.Parameters.Add(paramCodCliente)
paramNbPedidos = New SqlParameter("@nbPed", Nothing)
paramNbPedidos.Direction = ParameterDirection.Output
cmd.Parameters.Add(paramNbPedidos)
lector = cmd.ExecuteReader
Do While lector.Read
Console.WriteLine("nombre del cliente:{0}",
lector("ContactName"))
Loop
lector.Close()
Console.WriteLine("este cliente ha pasado {0} pedido(s)",
cmd.Parameters ("@nbPed").Value)
ctn.Close()
Console.ReadLine()
End Sub
End Module

e. Ejecución de un procedimiento almacenado

Los procedimientos almacenados son elementos de una base de datos que corresponden a
un conjunto de instrucciones SQL que pueden ser ejecutadas con una simple llamada a su
nombre. Son verdaderos programas SQL que pueden recibir parámetros y devolver valores.
Además, los procedimientos almacenados son registrados en el caché del servidor, con la
forma compilada, durante su primera ejecución, lo que aumenta sus prestaciones para las
siguientes ejecuciones. Otra ventaja de los procedimientos almacenados es centralizar en el
servidor de base de datos todos los códigos SQL de una aplicación. Si se deben aportar

ShareVideos
modificaciones en las instrucciones SQL, solo tendrá que hacerlo en el servidor sin que sea
preciso retomar el código de la aplicación, es decir, sin tener que volver a generar y
desplegar la aplicación.

La llamada desde Visual Basic a un procedimiento almacenado es prácticamente igual a la


ejecución de una instrucción SQL. La propiedad CommandText contiene el nombre del
procedimiento almacenado. También puede modificar la propiedad CommandType con el
valor CommandType.StoredProcedure para indicar que la propiedad CommandText
contiene el nombre de un procedimiento almacenado. Como para una instrucción SQL, un
procedimiento almacenado puede utilizar parámetros en entrada o en salida. Hay un tercer
tipo de parámetro disponible para los procedimientos almacenados en el tipo ReturnValue.
Este tipo de parámetro sirve para recuperar el valor devuelto por la instrucción Return del
procedimiento almacenado (mismo principio que una función Visual Basic). Para probar
estas nuevas nociones, vamos a utilizar el siguiente procedimiento almacenado que
devuelve el importe total de todos los pedidos pasados por un cliente.

CREATE PROCEDURE TotalCliente @code nchar(5) AS


declare @total money
select @total=sum(UnitPrice*Quantity*(1-Discount)) from Orders,
[Order Details]
where customerid=@code and Orders.orderid=[order details].orderid
return @total
GO

A nivel del código Visual Basic, debemos indicar que se trata de la ejecución de un
procedimiento almacenado y agregar un parámetro para recuperar el valor de retorno de
dicho procedimiento. Este parámetro debe llamarse RETURN_VALUE.

Imports System.Data.SqlClient
Module TestProcedimientoAlmacenado
Dim cmd As SqlCommand
Dim ctn As SqlConnection
Dim paramCodCliente As SqlParameter
Dim paramCantidad As SqlParameter
Dim codCliente As String

Public Sub main()


Console.Write("introducir el código del cliente buscado:")
codCliente = Console.ReadLine()
ctn = New SqlConnection()
ctn.ConnectionString = "Data Source=localhost;Initial
Catalog=Northwind; Integrated Security=true"
ctn.Open()
cmd = New SqlCommand
cmd.Connection = ctn
cmd.CommandText = "TotalCliente"
cmd.CommandType = CommandType.StoredProcedure
paramCodCliente = New SqlParameter("@Cod", codCliente)
paramCodCliente.Direction = ParameterDirection.Input
cmd.Parameters.Add(paramCodCliente)
paramCantidad = New SqlParameter("RETURN_VALUE",
SqlDbType.Decimal)

ShareVideos
paramCantidad.Direction = ParameterDirection.ReturnValue
cmd.Parameters.Add(paramCantidad)
cmd.ExecuteNonQuery()
Console.WriteLine("Este cliente ha pasado {0} euros de pedido",
paramCantidad.Value)
Console.ReadLine()
ctn.Close()
End Sub
End Module

Utilización del modo no conectado


En un modo no conectado, el enlace con el servidor de base de datos no es permanente.
Hay que conservar localmente los datos con los que se desea trabajar. La idea es volver a
crear, con la ayuda de diferentes clases, una organización similar a la de una base de datos.
Las principales clases vienen representadas en el siguiente esquema:

DataSet

Es el contenedor de mayor nivel, juega el mismo papel que la base de datos.

DataTable

Como su nombre indica, es el equivalente de una tabla de base de datos.

DataRow

Esta clase juega el papel de un registro (fila).

DataColumn

Esta clase reemplaza un campo (columna) de una tabla.

UniqueConstraint

Es el equivalente de la clave primaria de una tabla.

ForeignKeyConstraint

Es el equivalente de la clave externa.

DataRelation

Representa un enlace padre/hijo entre dos DataTable.

El esquema siguiente representa esta organización.

ShareVideos
Ahora vamos a ver cómo crear y manipular todas estas clases.

1. Rellenar un DataSet a partir de una base de datos

Para poder trabajar localmente con los datos, debemos traerlos desde la base de datos a un
DataSet. Cada proveedor de datos facilita una clase DataAdapter, que asegura el diálogo
entre la base de datos y un DataSet. Todos los intercambios se hacen por medio de esta
clase, tanto desde la base de datos hacia el DataSet como del DataSet hacia la base de datos
para la actualización de los datos. El DataAdapter utilizará una conexión para contactar con
el servidor y uno o varios comandos para el tratamiento de los datos.

a. Utilización de un DataAdapter

Lo primero que hay que hacer es crear una instancia de la clase SQLDataAdapter. Luego
debemos configurar el DataAdapter con el fin de indicarle qué datos deseamos traer de la
base de datos. La propiedad SelectCommand debe referenciar un objeto Command, que
contiene la instrucción SQL encargada de seleccionar los datos. El objeto Command
utilizado también puede llamar a un procedimiento almacenado. La única restricción es que
la instrucción SQL ejecutada por el objeto Command sea una instrucción SELECT. La
clase DataAdapter también contiene las propiedades InsertCommand, DeleteCommand y
UpdateCommand, que hacen referencia a los objetos Command, utilizados durante la
actualización de la base de datos. Si no deseamos efectuar la actualización de la base, estas
propiedades son optativas. Las veremos más en detalle en el capítulo dedicado a la
actualización de la base de datos.

El método Fill de la clase DataAdapter se utiliza luego para rellenar el DataSet con el
resultado de la ejecución del comando SelectCommand. Este método espera como
parámetros el DataSet que debe rellenar y un objeto DataTable o una cadena de caracteres
utilizada para nombrar el DataTable en el DataSet. El DataAdapter utiliza, internamente, un
objeto DataReader para obtener el nombre y el tipo de los campos para crear el DataTable
en el Dataset y después rellenarlo con los datos. El DataTable y los DataColumn se crean
solo si no existen. En caso contrario, el método Fill utiliza la estructura existente. Si se crea
un DataTable, se añade a la colección Tables del DataSet. El tipo de datos de los
DataColumn se define en función de los mapeos previstos por el proveedor de datos, entre
los tipos de la base de datos y los tipos .NET. El siguiente ejemplo rellena un DataSet con
el código, apellido, dirección y ciudad de los clientes.

Imports System.Data.SqlClient
Module TestDataSet1
Dim cmd As SqlCommand
Dim ctn As SqlConnection
Dim ds As DataSet
Dim da As SqlDataAdapter

Public Sub main()


ctn = New SqlConnection()

ShareVideos
ctn.ConnectionString = "Data Source=localhost;Initial
Catalog=Northwind; Integrated Security=true"
cmd = New SqlCommand
cmd.Connection = ctn
cmd.CommandText = " SELECT CustomerId,ContactName,Address,
city from Customers"
ds = New DataSet
da = New SqlDataAdapter()
da.SelectCommand = cmd
da.Fill(ds, "Customers")
End Sub
End Module

En este código la conexión no se ha abierto y cerrado de manera explícita. En efecto, el


método Fill abre la conexión si no está abierta y, en este caso, la vuelve a cerrar también al
final de su ejecución. Sin embargo, si necesita utilizar varias veces el método Fill, es más
eficaz que gestione usted mismo la apertura y el cierre de conexión. En todos los casos, el
método Fill deja la conexión en el estado en que la ha encontrado.

Por supuesto, un DataSet puede contener varios DataTable creados a partir de DataAdapter
diferentes. Los datos pueden venir de bases de datos diferentes o de tipos de servidores
diferentes.

Cuando el DataAdapter construye el DataTable, se utlizan los nombres de los campos de la


base de datos para nombrar los DataColumn. Es posible personalizar estos nombres
creando objetos DataTableMapping y agregándolos a la colección TableMappings del
DataAdapter. Estos objetos DataTableMapping contienen los objetos DataColumnMapping
utilizados por el método Fill, como traductores entre los nombres de los campos en la base
y los nombres de los Datacolumn en el DataSet. En este caso, en el momento de la llamada
del método Fill, debemos indicarle el nombre del DataTableMapping que debe utilizar.

Si, para uno o más campos, no existe mapeo disponible, el nombre del campo en la base de
datos se usa como nombre para el DataColumn correspondiente. Por ejemplo, podemos
utilizar esta técnica para traducir los campos de la base Northwind.

El siguiente código efectúa esta traducción y muestra el nombre de los DataColumn de la


DataTable creada:

Imports System.Data.SqlClient
Imports System.Data.Common
Module TestTableMapping
Dim cmd As SqlCommand
Dim ctn As SqlConnection
Dim ds As DataSet
Dim da As SqlDataAdapter
Dim mapeo As DataTableMapping
Dim dc As DataColumn

Public Sub main()


ctn = New SqlConnection()
ctn.ConnectionString = "Data Source=localhost;Initial

ShareVideos
Catalog=Northwind; Integrated Security=true"
cmd = New SqlCommand
cmd.Connection = ctn
cmd.CommandText = " SELECT CustomerId,ContactName,Address,
city from Customers"
ds = New DataSet
da = New SqlDataAdapter()
da.SelectCommand = cmd
mapeo = New DataTableMapping("Customers", "Clientes")
mapeo.ColumnMappings.Add("CustomerId", "CodCliente")
mapeo.ColumnMappings.Add("ContactName", "Nombre")
mapeo.ColumnMappings.Add("Address", "Direccion")
mapeo.ColumnMappings.Add("city", "Ciudad")
da.TableMappings.Add(mapeo)
da.Fill(ds, "Customers")
For Each dc In ds.Tables("Clientes").Columns
Console.Write(dc.ColumnName & vbTab)
Next
Console.ReadLine()
End Sub
End Module

Obtenemos el resultado:

CodigoCliente Apellido Dirección Ciudad

b. Agregar restricciones existentes a un DataSet

El método Fill transfiere hacia el DataSet los datos que proceden de la base. A menudo se
utilizan las restricciones de la clave principal en la base de datos y, por defecto, el método
Fill no las transfiere al DataSet. Para poder recuperar estas restricciones en el DataSet, hay
dos soluciones posibles:

 Modificar la propiedad MissingSchemaAction del DataAdapter con el valor


MissingSchemaAction.AddWithKey.

da.MissingSchemaAction = MissingSchemaAction.AddWithKey

 Proceder en dos etapas llamando primero al método FillSchema del DataAdapter


para crear la estructura completa del DataTable, y luego llamar al método Fill para
rellenar el DataTable con los datos.

da.FillSchema(ds, SchemaType.Mapped, "Customers")


da.Fill(ds, "Customers")

El segundo parámetro del método FillSchema indica si se debe tener en cuenta el mapeo o
si se utiliza la información procedente de la base.

Es importante añadir las restricciones de clave principal, ya que el método Fill se va a


comportar de manera diferente según si existen o no.

ShareVideos
Si existen a nivel del DataSet, cuando el método Fill importa un registro desde la base de
datos, verifica si ya no existe una fila con el valor de clave primaria en el DataTable. Si es
el caso, solo actualiza los campos de la fila existente. Si, por el contrario, no hay una fila
con un valor de clave primaria idéntico, entonces se crea la fila en el DataTable.

Si no hay restricción de clave primaria en el DataTable, el método Fill añade todos los
registros procedentes de la base. En este caso, puede que haya duplicados en el DataTable.
Eso es particularmente importante cuando se debe llamar al método Fill varias veces para,
por ejemplo, obtener los datos modificados por otra persona en la base de datos.

2. Configurar un DataSet sin base de datos

No es necesario disponer de una base de datos para poder utilizar los DataSet. Pueden
servir como alternativa a la utilización de matrices para la gestión interna de los datos de
una aplicación. En este caso, todas las operaciones efectuadas automáticamente con el
DataAdapter deberán realizarse manualmente con el código. Esto incluye, en particular, la
creación de los DataTable con sus DataColumn. La primera operación que se debe realizar
consiste en crear una instancia de la clase DataTable. El constructor espera como parámetro
el nombre de la DataTable. Luego se utiliza este nombre para identificar el DataTable en la
colección Tables del DataSet. Tras su creación, el DataTable no contiene ninguna
estructura. Por tanto, debemos crear uno o varios DataColumn y agregarlos a la colección
Columns del DataTable.

Se pueden crear los DataColumn utilizando uno de los constructores de la clase o,


automáticamente, cuando se agregan a la colección Columns. La primera solución aporta
más flexibilidad, ya que permite la configuración de numerosas propiedades del
DataColumn en el momento de su creación. Es necesario indicar, como mínimo, un nombre
y un tipo de datos para la DataColumn.

col = New DataColumn(("PBruto", Type.GetType("int"))


table.Columns.Add(col)
table.Columns.Add("IVA", Type.GetType("decimal"))

Un DataColumn también se puede construir como una expresión basada en otros


DataColumn. En este caso, debe indicar, durante la creación del DataColumn, la expresión
que sirve para calcular su valor. El tipo de datos generados por la expresión debe ser
compatible con el tipo de datos del DataColumn. También debe tener cuidado con el diseño
de la expresión, respetar las mayúsculas y las minúsculas y asegurarse de no crear una
referencia circular entre los DataColumn.

table.Columns.Add("PNeto", Type.GetType("System.Decimal"),
"PBruto * (1 + (IVA /100))")

Para asegurar la unicidad de los valores de un DataColumn, es posible utilizar un tipo de


DataColumn autoincrementado. La propiedad AutoIncrement de este DataColumn se debe
situar en true. También puede modificar el incremento con la propiedad
AutoIncrementStep y el valor de salida con la propiedad AutoIncrementSeed. El valor

ShareVideos
contenido en este DataColumn se calcula automáticamente cuando se agrega una fila a un
DataTable en función de estas propiedades y filas ya existentes en el DataTable.

Este tipo de DataColumn se suele utilizar como clave primaria de un DataTable. Tiene la
posibilidad de definir la clave primaria de un DataTable al facilitar a la propiedad
PrimaryKey una tabla que contenga los diferentes DataColumn que deben componer la
clave primaria. Algunas propiedades de los DataColumn se modificarán automáticamente.
La propiedad Unique se situará en true y la propiedad AllowDBNull en false. Si la clave
primaria está constituida por varios DataColumn, solo se modificará la propiedad
AllowDBNull en estos DataColumn.

col = New DataColumn("Numero", Type.GetType("System.Int32"))


col.AutoIncrement = True
col.AutoIncrementSeed = 1000
col.AutoIncrementStep = 1
table.Columns.Add(col)
table.PrimaryKey = New DataColumn() {col}

3. Manipular los datos en un DataSet

Sea cual sea el método utilizado para rellenar un DataSet, el objetivo de cualquier
aplicación consiste en manipular los datos presentes en el DataSet. La clase DataTable
contiene muchas propiedades y métodos que facilitan la manipulación de los datos.

a. Lectura de los datos

La lectura de los datos es la operación más frecuente realizada en un DataSet. Primero hay
que obtener una referencia al DataTable que contiene los datos, luego podemos recorrer la
colección Rows del DataTable. Esta colección es una instancia de la clase
DataRowCollection. Por defecto, dispone de la propiedad Item, que permite el acceso a una
fila particular por un índice. La propiedad count permite conocer el número de filas
disponibles. Los que suelen usar ADO se perderán un poco al principio, porque en un
DataTable no existe la noción de puntero de registro, de registro actual, de métodos de
desplazamiento por los resultados. Si desea gestionar todas estas nociones, debe preverlo
explícitamente en su código. El método GetEnumerator pone a su disposición una instancia
de clase que implementa la interfaz IEnumerator. Gracias a esta instancia de clase, tenemos
acceso a los métodos MoveNext y Reset, así como a la propiedad Current. Estos tres
elementos permiten recorrer fácilmente todas las filas DataTable. Cada fila corresponde a
una instancia de la clase DataRow. Esta clase posee también una propiedad Item, por
defecto, que proporciona un acceso a los diferentes campos del DataRow. Se puede obtener
cada campo gracias a su nombre o su índice.

El siguiente código ilustra estas nociones mostrando la lista de los clientes:

Imports System.Data.SqlClient
Module TestLecturaDataTable

Dim cmd As SqlCommand

ShareVideos
Dim ctn As SqlConnection
Dim ds As DataSet
Dim da As SqlDataAdapter
Dim en As IEnumerator

Public Sub main()


ctn = New SqlConnection()
ctn.ConnectionString = "Data Source=localhost;Initial
Catalog=Northwind; Integrated Security=true"
cmd = New SqlCommand
cmd.Connection = ctn
cmd.CommandText = " SELECT ContactTitle,ContactName from
Customers"
ds = New DataSet
da = New SqlDataAdapter()
da.SelectCommand = cmd
da.Fill(ds, "Customers")
’ recuperamos el enumerador de las filas del DataTable
en = ds.Tables("Customers").Rows.GetEnumerator
’ nos volvemos a colocar al principio de la tabla (por
seguridad)
en.Reset()
’ iteramos mientras el metodo MoveNext nos indique que quedan
filas
Do While en.MoveNext
’ accedemos a los campos por el nombre
Console.Write(en.Current("ContactName") & vbTab)
’ o por el número
Console.WriteLine(en.Current(0))
Loop
Console.ReadLine()
End Sub
End Module

b. Creación de restricciones en un DataTable

Podemos utilizar limitaciones para poner en marcha restricciones en los datos presentes en
un DataTable. Las restricciones constituyen reglas que se aplican a un DataColumn o a sus
DataColumn relacionadas. Determinan las acciones efectuadas cuando se modifica el valor
contenido en una fila. Solo se tienen en cuenta para un DataSet si su propiedad
EnforceConstraints está en true.

Es posible utilizar dos tipos de restricciones:

UniqueConstraint

Este tipo de restricción garantiza que el valor o los valores presentes en un DataColumn o
un grupo de DataColumn sean únicos. La activación de una restricción única se efectúa
creando una instancia de la clase UniqueConstraint con la lista de los DataColumn
implicados en la restricción. Luego, esta UniqueConstraint se debe añadir a la colección
Constraints del DataTable.

table.Constraints.Add(New UniqueConstraint(New DataColumn() {col}))

ShareVideos
Si la restricción solo se refiere a un DataColumn, también es posible modificar
simplemente la propiedad Unique de este DataColumn a true, para crear una restricción de
tipo Unique. Hay que observar también que la creación de una clave primaria genera
automáticamente una restricción de tipo Unique. Lo contrario no es verdad. La violación de
la restricción al modificar una fila genera una excepción.

ForeignKeyConstraint

Las ForeignKeyConstraint controlan cómo van a comportarse los DataTable relacionados


durante la modificación o la eliminación de un valor en el DataTable principal. Se puede
considerar una acción diferente para una eliminación y una modificación. La clase
ForeignKeyConstraint dispone de las propiedades DeleteRule y UpdateRule, que indican el
comportamiento durante la eliminación o la modificación. Se pueden dar los valores
siguientes:

Cascade

La eliminación o modificación se propaga a la fila o las filas relacionadas.

SetNull

El valor se modifica a DBNull en las filas relacionadas.

SetDefault

El valor por defecto se inserta en las filas relacionadas.

None

No se efectúa ninguna acción en las filas relacionadas.

Es posible agregar una ForeignKeyConstraint creando una instancia de clase e indicándole


el DataColumn o los DataColumn del DataTable padre y el DataColumn o los DataColumn
de la tabla hija. Si muchas DataColumn forman parte de la restricción, se facilitan en forma
de tabla.

El siguiente código añade una restricción entre el DataTable Facturas y el DataTable


FilasFactura, para que la eliminación de una factura conlleve la eliminación de todas sus
filas.

fkFact_LineasFact = New ForeignKeyConstraint("FK_FACT_LINEASFACT",


ds.Tables("Facturas").Columns("Numero"),
ds.Tables("LineasFactura").Columns("NumFact"))
fkFact_LineasFact.AcceptRejectRule = AcceptRejectRule.Cascade
fkFact_LineasFact.DeleteRule = Rule.Cascade
ds.EnforceConstraints = True

ShareVideos
c. Agregar relaciones entre los DataTables

En un DataSet que contiene varios DataTable, puede añadir relaciones entre los DataTable.
Estas relaciones permiten la navegación entre las filas de los diferentes DataTable. Se debe
crear una instancia de la clase DataRelation y agregar a la colección Relations del DataSet.
La creación se puede hacer directamente con el método Add de la colección DataRelations.
La información que se debe facilitar es:

 El nombre de la relación que permite encontrar la DataRelation en la colección.


 El DataColumn o los DataColumn padres en forma de una tabla de DataColumn si
hay muchos.
 El DataColumn o los DataColumn hijos en forma de una tabla si hay muchos.

El código siguiente añade una relación entre la tabla Customers y la tabla Orders:

ds.Relations.Add("Cliente_Pedidos", ds.Tables("Customers").
Columns ("CustomerId"),
ds.Tables("Orders").Columns("CustomerId"))

Hay que observar que las DataRelation funcionan en paralelo con las ForeignKeyConstaint
y las UniqueConstraint. Por defecto, la creación de la relación colocará una
UniqueConstraint en la tabla padre y una ForeignKeyConstraint en la tabla hijo. Si no desea
que estas restricciones se añadan automáticamente cuando no existen, usted debe agregar
un boleano false como cuarto parámetro al añadir la DataRelation.

d. Recorrer las relaciones

El objetivo principal de las relaciones consiste en permitir la navegación de un DataTable


hacia otro en el interior de un DataSet. Así podemos obtener todos los objetos DataRow de
un DataTable relacionados con un DataRow de otro DataTable. Por ejemplo, después de
cargar las matrices Customers y Orders en el DataSet y establecer una relación entre estas
dos tablas, podemos, desde una fila del DataTable Customers, obtener del DataTable
Orders todos los pedidos de este cliente. El método GetChildRows devuelve, en forma de
tabla de DataRow, todas las filas que contienen los pedidos de este cliente.

Este método toma como parámetro el nombre de la DataRelation utilizada para seguir el
enlace. El siguiente ejemplo de código aplica todo ello, mostrando para cada cliente el
número y la fecha de sus pedidos:

Imports System.Data.SqlClient
Module TestRelaciones
Dim cmdCustomers, cmdOrders As SqlCommand
Dim ctn As SqlConnection
Dim ds As DataSet
Dim daCustomers, daOrders As SqlDataAdapter
Dim filaCliente, filaPedidos As DataRow

Public Sub main()


ds = New DataSet

ShareVideos
ctn = New SqlConnection()
ctn.ConnectionString = "Data Source=localhost;Initial
Catalog=Northwind; Integrated Security=true"
cmdCustomers = New SqlCommand
cmdCustomers.Connection = ctn
cmdCustomers.CommandText = " SELECT * from Customers"
daCustomers = New SqlDataAdapter()
daCustomers.SelectCommand = cmdCustomers
daCustomers.Fill(ds, "Customers")
cmdOrders = New SqlCommand
cmdOrders.Connection = ctn
cmdOrders.CommandText = " SELECT * from Orders"
daOrders = New SqlDataAdapter()
daOrders.SelectCommand = cmdOrders
daOrders.Fill(ds, "Orders")
ds.Relations.Add("Cliente_Pedidos", ds.Tables("Customers").
Columns ("CustomerId"), ds.Tables("Orders").Columns("CustomerId"))
For Each filaCliente In ds.Tables("Customers").Rows
Console.WriteLine(filaCliente("ContactName"))
For Each filaPedidos In
filaCliente.GetChildRows("Cliente_Pedidos")
Console.WriteLine(vbTab & "pedido N {0} del {1}",
filaPedidos ("OrderId"), filaPedidos("OrderDate"))
Next
Next
End Sub
End Module

La navegación de una fila hijo hacia una fila padre también es posible, utilizando el método
GetParentRow, que también espera como parámetro el nombre de la relación utilizada
como enlace.

El fragmento de código siguiente muestra el nombre del cliente que ha pasado cada pedido:

For Each lineaPedidos In ds.Tables("Orders").Rows


Console.WriteLine("el pedido {0} lo ha hecho {1}",
lineaPedidos("OrderId"), lineaPedidos.GetParentRow("Cliente_Pedidos")
("ContactName"))
Next

e. Estado y versiones de un DataRow

La clase DataRow es capaz de seguir las diferentes modificaciones aportadas a los datos
que contiene. La propiedad RowState permite controlar las modificaciones aportadas a la
fila.

Para esta propiedad existen cinco posibles valores definidos en una enumeración:

Unchanged

La fila no ha cambiado desde el llenado del DataSet con el método Fill o la validación de
las modificaciones con el método AcceptChanges.

ShareVideos
Added

Se ha añadido la fila, pero las modificaciones aún no han sido validadas por el método
AcceptChanges.

Modified

Uno o varios campos de la fila han sido modificados.

Deleted

Se ha borrado la fila, pero las modificaciones aún no han sido validadas por el método
AcceptChanges.

Detached

Se ha creado la fila, pero aún no forma parte de la colección Rows de un DataTable.

Las diferentes versiones de una fila también están disponibles. Cuando acceda a los valores
contenidos en una fila, puede especificar la versión que le interesa.

Para ello, la enumeración DataRowVersion propone cuatro valores:

Current

Versión actual de la fila. Esta versión no existe para una fila cuyo estado es Deleted.

Default

Versión por defecto de la fila. Para una fila cuyo estado es Added, Modified, Unchanged,
es equivalente a la versión Current. Para una fila cuyo estado es Deleted, es equivalente a la
versión Original. Para una fila cuyo estado es Detached, es igual a la versión Proposed.

Original

Versión original de la fila. Para una fila cuyo estado es Added, no existe.

Proposed

Versión transitoria disponible durante una operación de modificación de la fila o para una
fila que no forma parte de la colección Rows de un DataTable.

Se debe especificar la versión deseada durante el acceso a un campo particular de un


DataRow. Para ello, hay que utilizar una de las constantes anteriores, a continuación del
nombre o del índice del campo, durante la utilización de la propiedad Item, por defecto, del
DataRow.

ShareVideos
Durante la actualización de la base de datos se utilizarán estas distintas versiones para
gestionar los accesos concurrentes por ejemplo.

f. Agregar datos

Se puede añadir una fila a un DataTable agregando simplemente un DataRow a la colección


Rows de un DataTable. Previamente hace falta crear una instancia de la clase DataRow. Es
en este punto donde encontramos un problema.

No hay constructor disponible para la clase DataRow. No es un error de Visual Basic, sino
que simplemente no existe un constructor para esta clase. En efecto, cuando necesitamos
una nueva instancia de un DataRow, no queremos un DataRow cualquiera, sino un
DataRow específico para el esquema de nuestro DataTable. Por esta razón se le confía al
DataTable la tarea de crear la instancia que necesitamos por medio del método NewRow.

Dim nuevaFila As DataRow


nuevaFila = ds.Tables("Customers").NewRow()

El estado de esta fila es, de momento, Detached. Luego podemos añadir datos a esta nueva
fila.

nuevaFila("ContactName") = "Alfonso"

Después, nos queda por añadir la fila de la colección Rows del DataTable.

ds.Tables("Customers").Rows.Add(nuevaFila)

El estado de esta nueva fila es ahora Added.

g. Modificación de datos

La modificación de los datos contenidos en una fila se realiza simplemente al asignar a los
campos correspondientes los valores deseados. Estos valores están almacenados en la
versión Current de la fila. El estado de la fila es entonces Modified. Esta solución presenta
un pequeño inconveniente. Si hay que modificar varios campos de una fila, los estados
transitorios pueden violar varias restricciones colocadas en la DataTable. Es el caso, por
ejemplo, si existe en la DataTable una restricción de clave primaria colocada en dos
DataColumn. Esto tiene como efecto activar una excepción. Para paliar este problema,
podemos pedir temporalmente la suspensión de la verificación de las restricciones para esta
fila. El método BeginEdit pasa la fila a modo edición y suspende entonces la verificación
de las restricciones para esta fila. Los valores asignados a los campos no están almacenados
en la versión Current de la fila, sino en la versión Proposed. Cuando todas las

ShareVideos
modificaciones se han efectuado en la fila, las puede validar o cancelar llamando al método
EndEdit o al método CancelEdit.

También puede verificar los valores, gestionando el evento ColumnChanged del DataTable.
En el gestor de eventos, recibirá un argumento del tipo DataColumnChangeEventArg que
permite saber qué DataColumn se ha modificado (args.Column.ColumnName) y el valor
propuesto para este DataColumn (args.ProposedValue) permitiendo anular las
modificaciones (args.row.CancelEdit). En caso de validación con el método EndEdit, la
versión Proposed de la fila se vuelve a copiar en la versión Current y su estado se convierte
en Modified. Si por el contrario cancela las modificaciones con el método CancelEdit, la
versión Current no se modifica y el estado de la fila no cambia. En todos los casos, después
de la llamada de uno de estos dos métodos, se reactiva la verificación de las restricciones.

El siguiente ejemplo permite la modificación del código postal de un cliente verificando


que sea numérico:

Imports System.Data.SqlClient
Module TestModificacionFila
Dim cmd As SqlCommand
Dim ctn As SqlConnection
Dim codCliente As String
Dim codigoPostal As String
Dim paramCodCliente As SqlParameter
Dim ds As DataSet
Dim da As SqlDataAdapter
Dim WithEvents tabla As DataTable
Public Sub main()
ctn = New SqlConnection()
ctn.ConnectionString = "Data Source=localhost;Initial Catalog=
Northwind;Integrated Security=true"
ctn.Open()
cmd = New SqlCommand
cmd.Connection = ctn
Console.Write("escoger el código del cliente:")
codCliente = Console.ReadLine()
cmd.CommandText = " SELECT * from Customers WHERE CustomerID =
@Codigo"
paramcodCliente = New SqlParameter("@Codigo", codCliente)
paramCodigoCliente.Direction = ParameterDirection.Input
cmd.Parameters.Add(paramCodCliente)
ds = New DataSet
da = New SqlDataAdapter(cmd)
da.Fill(ds, "Clientes")
tabla = ds.Tables("Clientes")
tabla.Rows(0).BeginEdit()
Console.Write("introducir el nuevo código postal del cliente:")
codigoPostal = Console.ReadLine()
tabla.Rows(0)("PostalCode") = codigoPostal
tabla.Rows(0).EndEdit()
Console.WriteLine("el nuevo código postal es: {0}",
tabla.Rows(0) ("PostalCode"))
Console.ReadLine()
End Sub

ShareVideos
Private Sub table_ColumnChanged(ByVal sender As Object, ByVal e
As System. Data.DataColumnChangeEventArgs) Handles table.ColumnChanged
If e.Column.ColumnName = "PostalCode" Then
If Not IsNumeric(e.ProposedValue) Then
e.Row.CancelEdit()
End If
End If
End Sub
End Module

h. Eliminar datos

Hay dos soluciones disponibles. Puede borrar una fila o eliminarla. El matiz es sutil entre
estas dos soluciones:

La eliminación de una fila se hace con el método Remove, que retira definitivamente el
DataRow de la colección Rows del DataTable. Esta eliminación es definitiva.

El método Deleted solo marca la fila para eliminarla posteriormente. El estado de la fila
pasa a Deleted y solo en el momento de la validación de las modificaciones se eliminar
realmente la fila de la colección Rows del DataTable. Si se cancelan las modificaciones, la
fila se queda en la colección Rows.

El método Remove es un método de la colección Rows (actúa directamente en su


contenido), mientras que el método Delete es un método de la clase DataRow (solo cambia
una propiedad de la fila).

’ borra la fila
table.Rows(1).Delete()
’ elimina la fila
table.Rows.Remove(table.Rows(1))

i. Validar o cancelar las modificaciones

Hasta ahora, las modificaciones efectuadas en una fila son temporales; todavía es posible
volver a la versión anterior o, al contrario, validar de manera definitiva las modificaciones
en las filas (pero no en la base). Los métodos AcceptChanges o RejectChanges permiten
respectivamente la validación o la anulación de las modificaciones. Se pueden aplicar a un
DataRow individual, un DataTable o un DataSet entero. Cuando el método AcceptChanges
se ejecuta, se realizan las siguientes acciones:

 El método EndEdit es llamado implícitamente para la fila.


 Si el estado de la fila era Added o Modified, se convierte en Unchanged y la versión
Current se vuelve a copiar en la versión Original.
 Si el estado de la fila era Deleted, se elimina la fila.

El método RejectChanges ejecuta las siguientes acciones:

ShareVideos
 El método CancelEdit es llamado implícitamente para la fila.
 Si el estado de la fila era Deleted o Modified, se convierte en Unchanged y la
versión Original se vuelve a copiar en la versión Current.
 Si el estado de la fila era Added, se elimina la fila.

Si existen restricciones de clave exterior, la acción del método AcceptChanges o


RejectChanges se propaga a las filas hijos en función de la propiedad AcceptRejectRule de
la restricción.

j. Filtrar y ordenar datos

A menudo es necesario limitar la cantidad de datos visibles en un DataTable o incluso


modificar el orden de las filas. La primera solución que viene a la mente consiste en crear
una consulta SQL con una restricción o una cláusula ORDER BY. Pero eso es olvidar que
estamos en un modo de funcionamiento desconectado y que es deseable limitar los accesos
a la base de datos o incluso puede ser que la base no está disponible. Por lo tanto solo
debemos utilizar los datos disponibles, teniendo cuidado de no perderlos. La clase el
DataView nos va a ser muy útil para solucionar nuestros problemas, ya que nos va a servir
para modificar la visión de los datos en el DataTable sin riesgo para los propios datos.
Puede haber varios DataView para un mismo DataTable, que corresponden a puntos de
vista diferentes del DataTable. Prácticamente todas las operaciones realizables en un
DataTable se pueden efectuar a través de un DataView.

Hay dos soluciones disponibles para obtener una DataView:

 Crear una instancia gracias a uno de los constructores.


 Utilizar la instancia por defecto facilitada por la propiedad DefaultView.

El primer constructor utilizable espera como parámetro el DataTable a partir del cual se
genera el DataView. En este caso, no hay ningún filtro ni tampoco ordenación efectuada en
los datos visibles por el DataView. Se obtiene un resultado equivalente al utilizar la
propiedad DefaultView de un DataTable.

EL segundo constructor permite especificar un filtro, un orden de ordenación y la versión


de las filas afectadas. Para ser visibles en el DataView, las filas deberán cumplir todos estos
criterios. También se pueden modificar los diferentes criterios con tres propiedades.

RowFilter

Esta propiedad acepta una cadena de caracteres que representa la condición que se debe
cumplir para que una fila sea visible. Esta condición tiene una sintaxis muy parecida a las
condiciones de una cláusula WHERE. Los operadores And y Or pueden utilizarse para
asociar varias condiciones.

El siguiente ejemplo visualiza el nombre de los clientes comerciales o directores de ventas


en España:

ShareVideos
Imports System.Data.SqlClient
Module TestDataView
Dim cmd As SqlCommand
Dim ctn As SqlConnection
Dim ds As DataSet
Dim da As SqlDataAdapter
Dim tabla As DataTable
Dim fila As DataRowView
Public Sub main()
ctn = New SqlConnection()
ctn.ConnectionString = "Data Source=localhost;Initial
Catalog=Northwind;Integrated Security=true"
ctn.Open()
cmd = New SqlCommand
cmd.Connection = ctn
cmd.CommandText = " SELECT * from Customers"
ds = New DataSet
da = New SqlDataAdapter(cmd)
da.Fill(ds, "Clientes")
tabla = ds.Tables("Clientes")
tabla.DefaultView.RowFilter = "Country=’España’ and
(contactTitle=’Sales Agent’ or contactTitle=’Sales Manager’)"
For Each fila In tabla.DefaultView
Console.WriteLine("nombre: {0}", fila("ContactName"))
Next
End Sub
End Module

Se puede cancelar un filtro asignando una cadena vacía a la propiedad RowFilter.

Sort

Esta propiedad acepta también una cadena de caracteres que representa el criterio o los
criterios utilizados para la ordenación. La sintaxis es equivalente a la de la cláusula
ORDER BY.

El siguiente ejemplo muestra los clientes ordenados por país y, dentro del mismo país, por
nombre:

’ anulamos el filtro anterior


tabla.DefaultView.RowFilter = ""
’ todas las filas son ahora visibles
’ se añade un criterio de ordenación
tabla.DefaultView.Sort = "Country ASC,ContactName ASC"
For Each fila In tabla.DefaultView
Console.WriteLine("País: {0}" & vbTab & vbTab & " nombre :
{1}",
fila("Country"), fila("ContactName"))
Next

RowStateFilter

ShareVideos
Esta propiedad determina el estado de las filas y qué versión de la fila es visible en el
DataView. Existen ocho posibilidades:

CurrentRows

Presenta la versión Current de todas las filas añadidas, modificadas o no.

Added

Presenta la versión Current de todas las filas añadidas.

Deleted

Presenta la versión Original de todas las filas borradas.

ModifiedCurrent

Presenta la versión Current de todas las filas modificadas.

ModifiedOriginal

Presenta la versión Original de todas las filas modificadas.

None

Ninguna fila.

OriginalRows

Presenta la versión Original de todas las filas modificadas, eliminadas o no modificadas.

Unchanged

Presenta la versión Current de todas las filas no modificadas.

El siguiente ejemplo elimina dos filas y las muestra a través de un filtro:

’ se eliminan dos filas


table.Rows(2).Delete()
table.Rows(5).Delete()
’ se anula el filtro
tabla.DefaultView.RowFilter = ""
’ se muestra la versión original de las filas eliminadas
tabla.DefaultView.RowStateFilter = DataViewRowState.Deleted
For Each fila In tabla.DefaultView
Console.WriteLine("País: {0}" & vbTab & vbTab & " nombre :
{1}",
fila("Country"), fila("ContactName"))
Next

ShareVideos
k. Buscar datos

La búsqueda se puede realizar con los dos métodos Find y FindRows. Para que funcionen,
es obligatorio haber ordenado los datos con la propiedad Sort.

Find

Este método devuelve el índice de la primera fila correspondiente al criterio de búsqueda.


Si no se encuentra ninguna fila, este método devuelve -1. Recibe como parámetro el valor
buscado. Este valor se busca en el campo utilizado como criterio de ordenación. Si el
criterio de ordenación está compuesto por muchos campos, es necesario pasar al método
Find una matriz de objetos que contenga los valores buscados para cada campo del criterio
de ordenación en el orden de aparición en la propiedad Sort.

Este método se usa a menudo para buscar una fila a partir de la clave primaria.

Imports System.Data.SqlClient
Module T

Introducción
Después de muchos años de evolución, los lenguajes orientados a objetos se han hecho
ineludibles en el desarrollo informático. De manera paralela, los sistemas de
almacenamiento también han evolucionado sobre dos ejes: las bases de datos y los archivos
XML. La cohabitación entre los objetos y los datos ha sido más o menos mejorada
añadiendo a los lenguajes orientados a objetos la capacidad para dialogar con los datos. Sin
embargo, esta solución no es del todo satisfactoria, ya que presenta las desventajas
siguientes:

 El lenguaje utilizado para manipular los datos suele ser muy específico de un tipo de
fuente de datos.
 Las palabras claves de este lenguaje son desconocidas para el lenguaje de
programación, que las considera como simples cadenas de caracteres; por tanto, no
hay una verificación sintáctica antes de la ejecución.
 El cambio de tipo de fuente de datos conlleva importantes modificaciones del
código y un nuevo periodo de aprendizaje para el desarrollador.
 Los tipos de datos a veces son incompatibles entre el lenguaje de programación y la
fuente de datos. En este caso, hacer las conversiones a menudo exige tiempo y, a
veces, se tornan peligrosas.

Con LINQ todo esto se convierte en malos recuerdos. Pero ¿qué se esconde detrás de estas
cuatro letras: Language Integrated Query o lenguaje de consulta integrado? Se trata de un
lenguaje de consulta que permite interrogar fuentes de datos, pero ¿que más ofrece con
respecto al bueno de SQL? La clave del misterio se sitúa en el término «integrado». En
efecto, a diferencia de otros métodos utilizados para interrogar fuentes de datos (SQL,
XPATH…), LINQ forma parte del lenguaje en el que se desarrolla la aplicación (VB,

ShareVideos
C#…). Otro punto muy importante relativo a LINQ reside en la propia sintaxis del
lenguaje. Esta será la misma, independientemente del tipo de fuente de datos consultado:
tabla, colección, base de datos, archivo XML, dataset... El último punto importante de esta
presentación de LINQ se refiere a los datos manipulados. La aplicación está desarrollada
con un lenguaje orientado a objetos, y resulta que LINQ también manipula objetos. Así, no
es necesario realizar manualmente las operaciones de conversión. Si son necesarias, LINQ
las realizará automáticamente. Después de este breve vistazo, veamos ahora la sintaxis de
LINQ.

Sintaxis del lenguaje LINQ


Antes de detallar la sintaxis de LINQ, vamos a estudiar un primer ejemplo muy sencillo. En
los ejemplos de este capítulo, utilizaremos como fuente de datos dos listas, completadas
con instancias de las clases del siguiente diagrama.

Los datos utilizados para crear estas instancias de clase son extraídos de la base de datos
Northwind. Están ubicados en un archivo de texto que el código lee para crear las instancias
de clase en memoria. A continuación está el extracto de código que permite realizar estas
operaciones.

Dim listaPedidos As List(Of Pedido)


Dim listaClientes As List(Of Cliente)
Sub Main()
listaPedidos = New List(Of Pedido)
listaClientes = New List(Of Cliente)
Dim co As Pedido
Dim cl As Cliente
Dim f As IO.StreamReader
Dim fila As String
Dim col As String()
Dim nombre As String = ""
f = New IO.StreamReader("c:\data.txt", IO.FileMode.Open)
Do
fila = f.ReadLine
If Not IsNothing(fila) Then
col = fila.Split(New Char() {vbTab})
If nombre <> col(0) Then
nombre = col(0)
cl = New Cliente(With {.nombre = col(0),
.direccionFacturacion = New Direccion With {.calle = col(1),ciudad =
col(2),.codigoPostal = col (3)}, .telefono = col(4)
listaClientes.Add(cl)
End If
co = New Pedido() With {.codigoPedido = col(5), .fechaPedido =
col(6), .envio = col(7), .direccionDeEntrega = New Direccion With
{.calle = col(8), .ciudad = col(0), .codigoPostal = col(10)}}
listaPedidos.Add(co)
co.Cliente = cl
cl.Pedidos.Add(co)

ShareVideos
End If
Loop Until fila Is Nothing
f.Close()
End Sub

En los siguientes ejemplos, supondremos que esta porción de código ya ha sido ejecutada
para rellenar las dos listas. Este boceto de proyecto está disponible para su descarga en el
sitio del editor.

1. Primeras consultas LINQ

Son necesarias tres etapas para manipular una consulta:

 La obtención de los datos.


 La creación de la propia consulta.
 La ejecución de la consulta.

El primer paso es muy sencillo: una clase debe implementar la interfaz genérica
IEnumerable(T) para ser utilizada como una fuente de datos. Es el caso de muchas clases
del Framework .NET que son directamente utilizables en consultas LINQ.

Para las fuentes de datos que no implementan esta interfaz, como por ejemplo un
documento XML, hay clases auxiliares que permiten hacerlas compatibles con LINQ.

La mayor parte del trabajo está constituido por la segunda etapa: la creación de la propia
consulta.

En la consulta, vamos a indicar qué datos deseamos obtener de la fuente de datos, cómo
serán ordenados, agrupados o estructurados.

La consulta contiene tres cláusulas:

 From: indica el origen de los datos.


 Where: especifica las condiciones que garantizan que los datos se incluyen en los
valores de retorno.
 Select: indica la información que se devuelve de la fuente de datos.

Así, presentamos a continuación nuestra primera consulta LINQ.

Dim consulta = From unCliente In listaClientes _


Where unCliente.apellido Like "A*" _
Select unCliente

Se suele almacenar una consulta LINQ en una variable y posteriormente se ejecuta. Es


importante recordar que la variable que contiene la consulta no ejecuta ninguna acción y no
devuelve ningún dato. Simplemente almacena la definición de la consulta. La ejecución de

ShareVideos
la consulta solo tiene lugar cuando uno se interesa por los datos que devuelve. En el
siguiente ejemplo se muestra cómo navegar por los resultados de un bucle For Each.

For Each un cliente In consulta


Console.WriteLine(uncliente.apellido)
Next

Este mecanismo permite ejecutar varias veces la misma consulta sin tener que volver a
definirla para cada ejecución.

Sin embargo, en algunos casos, la variable contiene el resultado de la consulta, y no la


propia consulta. Es el caso, por ejemplo, cuando hay un cálculo de agregado en la consulta.
En el siguiente ejemplo, buscamos cuántos clientes tenemos cuyo apellido empieza con la
letra A. En este caso, el resultado de la consulta es un simple entero que se calcula a partir
de la definición de la consulta.

Dim numClientes = (From unCliente In listaClientes _


Where unCliente.apellido Like "A*" _
Select unCliente).Count
Console.WriteLine(numClientes)

También puede acceder a múltiples fuentes de datos utilizando la cláusula From. Para ello,
la palabra clave Join permite combinar los datos de diferentes fuentes. El siguiente ejemplo
busca los clientes cuyo apellido empieza por una A y recupera la fecha de todos los pedidos
de cada uno de ellos.

Dim consulta3 = From unCliente In listaClientes _


Join unPedido In listaPedidos _
On unCliente.apellido Equals unPedido.Cliente.apellido _
Where unCliente.apellido Like "A*" _
Select apellidoCli = unCliente.apellido, _
fechaPdo = unPedido.fechaPedido

For Each r In consulta3


Console.WriteLine(r.apellidoCli & " " & r.fechaPdo)
Next

Este código merece un comentario acerca de la cláusula Select. Deseamos obtener el


apellido del cliente y la fecha de los pedidos, o sea una cadena de caracteres y una fecha.
No es necesario utilizar una instancia de la clase Cliente y una instancia de la clase Pedido
solo para estos dos datos. El compilador genera, por tanto, una clase anónima para estos dos
datos con una propiedad apellidoCli y una propiedad fechaPdo. El tipo de la variable r,
utilizada en el bucle For Each para recorrer el resultado de la consulta, será determinado
implícitamente para corresponderse con el tipo anónimo creado por el compilador.

2. Los operadores de consulta

Los operadores que permiten la creación de consultas LINQ se pueden clasificar en ocho
categorías:

ShareVideos
 Ordenar datos.
 Operaciones sobre conjuntos de datos.
 Filtrar datos.
 Proyección.
 Particiones.
 Unión, agrupación de datos.
 Cuantificadores.
 Agregación.

Para escribir consultas LINQ eficaces, conviene conocer correctamente estos operadores.
Por lo tanto, vamos a verlos en detalle con muchos ejemplos.

a. Ordenar datos

Es muy fácil obtener los resultados de una consulta ordenados según uno o varios criterios.
Gracias al operador Order By, podemos indicar la propiedad por la que vamos a ordenar.
La siguiente consulta ordena los clientes según su número de pedidos.

Dim ConsultaOrdenada1 = From unCliente In listaClientes _


Order By unCliente.Pedidos.Count _
Select unCliente
For Each unCliente In ConsultaOrdenada1
Console.WriteLine(unCliente.apellido & "numero de pedidos:"
& unCliente.Pedidos.Count)
Next

Por defecto, el orden se aplica de manera ascendente. Para obtener los mejores clientes al
principio de la lista, es preferible utilizar la palabra clave Descending después del criterio
de ordenación.

Dim ConsultaOrdenada2 = From unCliente In listaClientes _


Order By unCliente.Pedidos.Count Descending
_
Select unCliente
For Each unCliente In ConsultaOrdenada2
Console.WriteLine(unCliente.apellido & "num pedidos:"
& unCliente.Pedidos.Count)
Next

Se pueden indicar varios criterios de ordenación para eliminar las ambigüedades cuando
dos propiedades tienen el mismo valor. Se deben separar con comas los criterios de
ordenación en la consulta. La consulta ordena los clientes según el número de pedidos en
orden descendente y luego según el apellido del cliente en orden ascendente, en caso de que
el número de pedidos sea el mismo.

Dim ConsultaOrdenada3 = From unCliente In listaClientes _


Order By unCliente.Pedidos.Count
Descending,
unCliente.apellido Ascending _
Select unCliente

ShareVideos
For Each unCliente In ConsultaOrdenada3
Console.WriteLine(unCliente.apellido & "num pedidos:"
& unCliente.Pedidos.Count)
Next

b. Operaciones en conjuntos de datos

Existe un operador disponible en esta categoría que permite la eliminación de los


duplicados durante la búsqueda de información. La palabra clave Distinct ubicada al final
de la cláusula Select indica que los duplicados serán eliminados. El conjunto de los
elementos de la cláusula Select se tiene en cuenta para la eliminación de duplicados. La
siguiente consulta determina las diferentes ciudades donde tenemos clientes. Si tenemos
varios clientes en la misma ciudad, esta solo se listará una vez.

Dim consultaJuntos = From unCliente In listaClientes _


Order By unCliente.direccionFacturacion.ciudad _
Select unCliente.direccionFacturacion.ciudad Distinct
For Each ciudad In consultaJuntos
Console.WriteLine(ciudad)
Next

c. Filtrar datos

El filtrado consiste en reducir el número de elementos devueltos por la consulta. Con la


ayuda de la cláusula Where, se agregan a la consulta una o más expresiones, que deben
facilitar un booleano durante la evaluación de la consulta. Se pueden utilizar los operadores
de comparación estándar de Visual Basic para construir la expresión. El uso de una cadena
de caracteres en una cláusula Where requiere una pequeña precisión. Si bien podemos
utilizar el operador = como criterio de filtrado para comparar una cadena de caracteres, el
método Equals ofrece más funcionalidades. En efecto, con el operador = debe haber una
igualdad estricta con distinción entre mayúsculas y minúsculas durante el test. El método
Equals es más flexible, ya que permite indicar cómo se debe hacer la comparación y
eventualmente ignorar la distinción entre minúsculas y mayúsculas, como en el siguiente
ejemplo.

Dim consultaFiltro = From unCliente In listaClientes _


Where unCliente.direccionFacturacion.ciudad.Equals("barcelona",
StringComparison.OrdinalIgnoreCase) _
Select unCliente
For Each unCliente In consultaFiltro
Console.WriteLine(unCliente.apellido)
Next

d. Proyección

Una operación de proyección corresponde a la transformación de un objeto en una nueva


forma. Esta nueva forma está constituida por la unión de propiedades de objetos
especificada en la cláusula Select. Al utilizar la proyección, se puede crear
automáticamente un nuevo tipo construido a partir de cada proyecto. Usted puede proyectar
directamente una propiedad, o bien ejecutar una función que toma como parámetro la

ShareVideos
propiedad. En este caso, se utiliza el resultado de la función como valor para la propiedad
del objeto creado. También puede proyectar el objeto original sin modificarlo.

Dim consultaProyeccion = From unCliente In listaClientes _


Select apellidoCli = unCliente.apellido.ToUpper, ciudadCli =
unCliente.direccionFacturacion.ciudad.ToLower

For Each r In consultaProyeccion


Console.WriteLine(r.apellidoCli & " " & r.ciudadCli)
Next

Las proyecciones también pueden realizarse indicando varias cláusulas From en la consulta.
En este caso, el resultado de la consulta hace que cada objeto de cada una de las fuentes de
datos se corresponda con todos los objetos de las otras fuentes.

Dim consultaProyeccion1 = From unCliente In listaClientes _


From unPedido In listaPedidos _
Select cli = unCliente, cmd = unPedido
For Each r In consultaProyeccion1
Console.WriteLine(r.cli.apellido & " " & r.cmd.fechaPedido)
Next

Este tipo de operación conduce rápidamente a una explosión combinatoria. En efecto, el


número de objetos en el resultado de la consulta es igual al producto de la cantidad de
objetos en cada una de las fuentes de datos. Un filtrado que permita restringir el número de
objetos en el resultado suele ser una buena idea con este tipo de proyección.

Dim consultaProyeccion2 = From unCliente In listaClientes _


From unPedido In listaPedidos _
Where unCliente.Equals(unPedido.Cliente) _
Select cli = unCliente, cmd = unPedido

For Each r In consultaProyeccion2


Console.WriteLine(r.cli.apellido & " " & r.cmd.fechaPedido)
Next

e. Particiones

La partición consiste en dividir en dos partes un conjunto de datos y en devolver una de las
dos partes. El límite de la partición puede ser absoluto, y en este caso expresar la cantidad
de objetos, o condicional. Se utilizan dos cláusulas para la partición:

 Skip indica que se desea obtener la segunda parte de la lista (en realidad se «salta»
los objetos ubicados al principio);
 Take indica que se desea obtener el principio de la lista sin tener en cuenta los
registros del final de lista.

Veamos cómo utilizar estos dos operadores con la sintaxis absoluta y la sintaxis
condicional.

ShareVideos
La siguiente consulta permite obtener la lista de los diez peores clientes (basándose en el
número de pedidos).

Dim consultaParticion = From unCliente In listaClientes _


Order By unCliente.Pedidos.Count _
Skip listaClientes.Count - 10 _
Select unCliente
For Each unCliente In consultaParticion
Console.WriteLine(unCliente.apellido)
Next

Para ilustrar la sintaxis condicional, buscamos ahora todos los clientes que tienen un
número de pedidos inferior o igual a 5.

Dim consultaParticion1 = From unCliente In listaClientes _


Order By unCliente.Pedidos.Count Descending _
Skip While unCliente.Pedidos.Count > 5 _
Select unCliente
For Each unCliente In consultaParticion1
Console.WriteLine(unCliente.apellido & " " &
unCliente.Pedidos.Count)
Next

Para recompensar a nuestros clientes fieles, buscamos ahora los diez mejores entre ellos.

Dim consultaParticion2 = From unCliente In listaClientes _


Order By unCliente.Pedidos.Count Descending _
Take 10 _
Select unCliente
For Each unCliente In consultaParticion2
Console.WriteLine(unCliente.apellido)
Next

Para terminar, buscamos los clientes que han pasado al menos diez pedidos.

Dim consultaParticion3 = From unCliente In listaClientes _


Order By unCliente.Pedidos.Count Descending _
Take While unCliente.Pedidos.Count >= 10 _
Select unCliente
For Each unCliente In consultaParticion3
Console.WriteLine(unCliente.apellido & " " &
unCliente.Pedidos.Count)
Next

f. Unión, agrupación de datos

La unión de dos fuentes de datos corresponde a la asociación de una de las fuentes de datos
con los objetos de la otra fuente de datos con la que tienen una propiedad común. En
programación orientada a objetos, las uniones permiten reemplazar asociaciones
incompletas. En el ejemplo que utilizamos desde el principio de este capítulo, la clase
Cliente contiene una propiedad que permite obtener la lista de los pedidos de un cliente
(Pedidos) y la clase Pedido contiene un atributo que permite referenciar el cliente que ha

ShareVideos
pasado el pedido (Cliente). En nuestro caso, la asociación es bidireccional. Si por omisión
la propiedad Pedidos de la clase Cliente no existe, debemos recorrer la lista de pedidos y
evaluar cada uno de ellos para obtener todos los pedidos de un cliente específico. Le
corresponde a la unión realizar este trabajo. La siguiente consulta obtiene los pedidos de
cada cliente.

Dim consultaJoin = From unCliente In listaClientes _


Join unPedido In listaPedidos On unCliente Equals unPedido.Cliente _
Select unCliente, unPedido
For Each r In consultaJoin
Console.WriteLine(r.unCliente.apellido & " " &
r.unPedido.fechaPedido)
Next

Para cada pedido se repiten los datos relativos al cliente. Una solución más eficaz consiste
en ejecutar la consulta para que añada a cada cliente la lista de sus pedidos. La cláusula
Group Join permite realizar esta agrupación. En este caso, es también necesario especificar
con la cláusula Into el nombre de la propiedad utilizada para acceder al reagrupamiento, en
nuestro caso la lista de pedidos del cliente. Cabe observar que para nosotros esta propiedad
se duplicará (con la que ya teníamos prevista en nuestra clase Cliente).

Dim consultaGroupJoin = From unCliente In listaClientes _


Group Join unPedido In listaPedidos On unCliente Equals unPedido.Cliente
_
Into PedidosCliente = Group _
Select unCliente, PedidosCliente
For Each r In consultaGroupJoin
Console.WriteLine(r.unCliente.apellido)
For Each c In r.PedidosCliente
Console.WriteLine(vbTab & c.fechaPedido)
Next
Next

También se puede llevar a cabo una agrupación sin realizar la unión entre dos fuentes de
datos. Por ejemplo, podemos buscar para cada ciudad la lista de los clientes y residentes.
Para ello, la cláusula Group By Into es ideal. Solo es necesario indicar que deseamos
agrupar los clientes con su ciudad de residencia, como clave de agrupación, e indicar el
nombre de la propiedad que se generará para contener la agrupación. La consulta genera
entonces, durante su ejecución, una lista de instancias de clases que contiene dos
propiedades:

 El nombre de la ciudad.
 La lista de los clientes gracias a la propiedad indicada en la consulta.

Dim consultaGroup = From unCliente In listaClientes _


Group unCliente By unCliente.direccionFacturacion.ciudad
_
Into ClientesPorCiudad = Group _
Select ciudad, ClientesPorCiudad
For Each r In consultaGroup
Console.WriteLine(r.ciudad)

ShareVideos
For Each c In r.ClientesPorCiudad
Console.WriteLine(vbTab & c.apellido)
Next
Next

g. Cuantificadores

Los cuantificadores se utilizan para controlar si en una lista al menos un elemento cumple
una condición, o si todos los elementos la cumplen. Proporcionan el resultado del control
en forma de un booleano que en general se utiliza en la cláusula Where. Como ejemplo,
buscaremos todos los clientes cuyos pedidos han sido realizados en 2007.

Dim consultaCuantificar = From unCliente In listaClientes _


Where (Aggregate cmd In unCliente.Pedidos Into All
(cmd.fechaPedido.Year = 2007)) _
Select unCliente
For Each unCliente In consultaCuantificar
Console.WriteLine(unCliente.apellido)
For Each c In unCliente.Pedidos
Console.WriteLine(vbTab & c.fechaPedido)
Next
Next

La segunda versión permite la búsqueda de clientes que hayan hecho al menos un pedido
durante 2008.

Dim consultaCuantificar1 = From unCliente In listaClientes _


Where (Aggregate cmd In unCliente.Pedidos Into Any
(cmd.fechaPedido.Year = 2008)) _
Select unCliente

For Each unCliente In consultaCuantificar1


Console.WriteLine(unCliente.apellido)
For Each c In unCliente.Pedidos
Console.WriteLine(vbTab & c.fechaPedido)
Next
Next

h. Agregación

Se utilizan las operaciones de agregación para el cálculo de un valor único desde valores
contenidos en una lista de elementos. Las operaciones más corrientes son:

 El cálculo del promedio.


 La búsqueda de un máximo.
 La búsqueda de un mínimo.
 El cálculo de un total.

El siguiente ejemplo aplica estos cuatro operadores sobre los gastos de envío de todos los
pedidos.

Dim promedioEnvio = Aggregate unPedido In listaPedidos Into

ShareVideos
Average(unPedido.envio)
Console.WriteLine("promedio de envío: " & promedioEnvio)
Dim maxiEnvio = Aggregate unPedido In listaPedidos Into
Max(unPedido.envio)
Console.WriteLine("envío máximo: " & maxiEnvio)
Dim miniEnvio = Aggregate unPedido In listaPedidos Into
Min(unPedido.envio)
Console.WriteLine("envío mínimo: " & miniEnvio)
Dim totalEnvio = Aggregate unPedido In listaPedidos Into
Sum(unPedido.envio)
Console.WriteLine("total envío: " & totalEnvio)

Este código también es un buen ejemplo de consulta de ejecución inmediata, ya que para
obtener el resultado se debe recorrer obligatoriamente la lista desde el primero hasta el
último elemento.

Después de haber estudiado la sintaxis del lenguaje, vamos a ver ahora cómo utilizarlo en
asociación con una base de datos.

LINQ hacia SQL


Como hemos visto en los párrafos anteriores, el dominio preferido por LINQ es el mundo
de los objetos. Permite manipular perfectamente las listas, los objetos y sus propiedades.
Sin embargo, estos elementos presentan un grave obstáculo: desaparecerán de manera
inexorable en cuanto termine la aplicación. La solución más utilizada para paliar este
problema consiste en confiar a una base de datos el trabajo de asegurar la persistencia de
los datos después de cerrar la aplicación. Para el diálogo con una base de datos, se utiliza a
menudo el lenguaje SQL. Aunque las principales palabras claves de estos dos lenguajes son
similares, sus sintaxis no son compatibles. Las consultas LINQ se transforman
automáticamente en sus homólogas SQL para realizar los tratamientos. Pero se debe tener
en cuenta otro problema. Hasta ahora hemos manipulado, por medio de consultas LINQ,
objetos y solo objetos. El concepto objeto es totalmente extraño para una base de datos. Por
lo tanto, hay que encontrar una solución para que LINQ pueda acceder a los datos. La clave
del enigma consiste simplemente en crear clases para representar, en la aplicación, los datos
presentes en la base de datos. Esta técnica se llama mapeo de objeto relacional, y es la
primera etapa en la utilización de LINQ con una base de datos. Por lo tanto, vamos a ver
cómo crear estas clases.

1. El mapeo de objeto relacional

Existen tres soluciones para generar las clases que representan los datos almacenados en la
base de datos:

 Crear las clases manualmente como si se tratase de cualquier otra clase de su


aplicación, utilizando un editor de código. Esta solución es muy fastidiosa y se suele
utilizar para las modificaciones mínimas de clases existentes.
 Utilizar la herramienta en línea de comando SQLMetal.

ShareVideos
 Utilizar el diseñador Objeto/Relacional en modo gráfico.

a. SQLMetal

Esta herramienta está disponible desde una ventana de comando del entorno Visual Studio.
Las opciones indicadas en la línea de comando permiten configurar el funcionamiento. Las
opciones disponibles tratan de:

 La generación del código fuente de las clases y de los atributos de mapeo a partir de
una base de datos.
 La generación de un archivo intermedio de mapeo (.dbml) a partir de una base de
datos.
 La generación de las clases y de los atributos de mapeo a partir de un archivo de
mapeo.

Cada opción debe venir precedida por un carácter ’/’ y seguida por el carácter ’:’ y el valor
de la opción si es necesario.

Las opciones de conexión:

/server: <nombre del servidor>

Indica el nombre o la dirección IP del servidor de base de datos.

/database: <nombre de la base de datos>

Indica el nombre de la base de datos a partir de la que se efectuará la generación.

/user: <usuario de conexión>

Indica la cuenta de usuario con la que se abrirá la conexión hacia la base de datos. Si no se
especifica esta opción, se utilizará la autenticación de Windows.

/password: <contraseña>

Indica la contraseña asociada a la cuenta utilizada para establecer la conexión.

/conn: <cadena de conexión>

Se puede utilizar en lugar de las cuatro opciones anteriores, para facilitar los datos relativos
a la conexión hacia el servidor de base de datos.

timeout: <segundos>

Indica la duración máxima durante la cual SQLMetal intenta establecer la conexión hacia la
base de datos. Un valor igual a cero indica una duración ilimitada.

ShareVideos
Las opciones de salida:

/dbml :<nombre del archivo>

Genera un archivo de mapeo.

/codigo:<nombre del archivo>

Genera el código fuente de las clases en el archivo indicado.

Las opciones de generación:

/lenguaje:< vb o csharp>

Indica el lenguaje en el que se generará el código. Las dos opciones válidas son vb para
Visual Basic y csharp para C#.

/namespace:<nombre>

Indica el espacio de nombres en el que se generarán las clases. Por defecto, no hay un
espacio de nombres.

/context:<nombre>

Especifica el nombre del datacontext generado. Por defecto, se deduce este nombre del
nombre de la base de datos.

/entitybase:<nombre>

Especifica la clase base de las clases generadas. Por defecto, las clases generadas no tienen
clase base.

Para terminar, la última información que se debe facilitar corresponde al nombre del
archivo de mapeo a partir del cual se realizará la generación de las clases. Esta información
es inútil si la generación se ejecuta directamente desde la base de datos.

Le presentamos a continuación los usos más corrientes de esta herramienta.

Generación de clases en Visual Basic de la base Northwind situada en el equipo local:

SqlMetal /server:localhost /database:northwind /dbml :nw.dbml

Como el código generado es demasiado voluminoso para listarlo aquí (unas 3.500 líneas),
veamos el diagrama siguiente de las clases generadas.

ShareVideos
Tenemos la clase Northwind, que hereda de la clase DataContext y que rápidamente nos va
a servir para que LINQ pueda dialogar con la base de datos. También tenemos una clase
generada para cada una de las matrices de la base de datos. Trabajaremos con las instancias
de estas clases en nuestra aplicación.

Generación del archivo de mapeo de la base Northwind situada en el ordenador local:

SqlMetal /server:localhost /database:northwind /dbml:nw.dbml

Este comando genera un archivo XML del que vemos un extracto:

<Table Name="dbo.Customers" Member="Customers">


<Type Name="Customers">
<Column Name="CustomerID" Type="System.String"
DbType="NChar(5) NOT NULL"
IsPrimaryKey="true" CanBeNull="false" />
<Column Name="CompanyName" Type="System.String"
DbType="NVarChar(40)
NOT NULL" CanBeNull="false" />
<Column Name="ContactName" Type="System.String"
DbType="NVarChar(30)"
CanBeNull="true" />
<Column Name="ContactTitle" Type="System.String"
DbType="NVarChar(30)"
CanBeNull="true" />
<Column Name="Address" Type="System.String" DbType="NVarChar(60)"
CanBeNull="true" />
<Column Name="City" Type="System.String" DbType="NVarChar(15)"
CanBeNull="true" />
<Column Name="Region" Type="System.String" DbType="NVarChar(15)"
CanBeNull="true" />
<Column Name="PostalCode" Type="System.String"
DbType="NVarChar(10)"
CanBeNull="true" />
<Column Name="Country" Type="System.String" DbType="NVarChar(15)"
CanBeNull="true" />
<Column Name="Phone" Type="System.String" DbType="NVarChar(24)"
CanBeNull="true" />
<Column Name="Fax" Type="System.String" DbType="NVarChar(24)"
CanBeNull="true" />
<Association Name="FK_CustomerCustomerDemo_Customers" Member=
"CustomerCustomerDemo" OtherKey="CustomerID" Type="CustomerCustomerDemo"
DeleteRule="NO ACTION" />
<Association Name="FK_Orders_Customers" Member="Orders" OtherKey=
"CustomerID" Type="Orders" DeleteRule="NO ACTION" />
</Type>
</Table>

Se puede modificar este archivo para, por ejemplo, cambiar el nombre de las clases y de las
propiedades asociadas a los datos que proceden de la base de datos. En el siguiente
ejemplo, hemos traducido los nombres.

<Table Name="dbo.Cliente" Member="Cliente">


<Type Name="Cliente">

ShareVideos
<Column Name="CustomerID" Member="CódigoCliente"
Storage="CustomerID"
Type="System.String" DbType="NChar(5) NOT NULL" IsPrimaryKey="true"
CanBeNull="false" />
<Column Name="CompanyName" Member="NombreEmpresa"
Storage="CompanyName"
Type="System.String" DbType="NVarChar(40) NOT NULL" CanBeNull="false" />
<Column Name="ContactName" Member="NombreContacto"
Storage="ContactName"
Type="System.String" DbType="NVarChar(30)" CanBeNull="true" />
<Column Name="ContactTitle" Member="Funcion"
Storage="ContactTitle"
Type="System.String" DbType="NVarChar(30)" CanBeNull="true" />
<Column Name="Address" Member=Direccion" Storage="Address"
Type="System.String" DbType="NVarChar(60)" CanBeNull="true" />
<Column Name="City" Member="Ciudad"
Storage="City"Type="System.String"
DbType="NVarChar(15)" CanBeNull="true" />
<Column Name="Region" Type="System.String" DbType="NVarChar(15)"
CanBeNull="true" />
<Column Name="PostalCode" Member="CódigoPostal"
Storage="PostalCode"
Type="System.String" DbType="NVarChar(10)" CanBeNull="true" />
<Column Name="Country" Member="Pais" Storage="Country"
Type="System.String" DbType="NVarChar(15)" CanBeNull="true" />
<Column Name="Phone" Member="Telefono" Storage="_Phone"
Type="System.String"
DbType="NVarChar(24)" CanBeNull="true" />
<Column Name="Fax" Type="System.String" DbType="NVarChar(24)"
CanBeNull="true" />
<Association Name="Cliente_CustomerCustomersDemo" Member=
"CustomerCustomerDemo" ThisKey="CodigoCliente" OtherKey="CustomerID"
Type="CustomerCustomerDemo" />
<Association Name="Cliente_Orders" Member="Orders"
ThisKey="CodigoCliente"
OtherKey= "CustomerID" Type="Orders" />
</Type>
</Table>

En este ejemplo, con objeto de poder utilizar nombres de propiedades diferentes de los
nombres de columnas en la base de datos, hemos añadido el atributo Member a cada
etiqueta <Column> para especificar el nombre de la propiedad y el atributo storage para
indicar el nombre de la variable interna de la clase que contendrá la información.

Ahora podemos generar el código a partir del archivo de mapeo modificado con el siguiente
comando:

SqlMetal /code:nw.vb /language:vb nw.dbml

Veamos la clase generada para comprobar que nuestras modificaciones se han tenido en
cuenta.

ShareVideos
Esta herramienta es muy fácil de utilizar, pero presenta el pequeño inconveniente de poder
generar las clases solo para la totalidad de una base de datos. Además, las modificaciones
se deben hacer manualmente, sea en el código fuente generado o en el archivo de mapeo
intermedio. Para la generación y la personalización de algunas clases, es preferible utilizar
el diseñador Objeto/Relacional integrado en Visual Studio.

b. Diseñador Objeto/Relacional

El diseñador Objeto/Relacional ofrece una solución muy práctica para crear el modelo
objeto de una aplicación que representa los datos disponibles en una base de datos.
También permite la creación de procedimientos y funciones que autorizan la utilización de
los procedimientos almacenados y funciones presentes en la base de datos. Sin embargo,
comporta algunas limitaciones:

 Solo las bases de datos SQL Server 2000, SQL Server 2005, SQL Server Express y
SQL Server 2008 son compatibles.
 El mapeo solo es posible entre una clase y una tabla, lo que significa que no se
puede crear una clase para representar el resultado de una unión entre varias tablas.
 El diseñador funciona en «sentido único», ya que en el código generado solo se
plasman las modificaciones efectuadas en él. Si se modifica el código manualmente,
el diseñador no tiene en cuenta las modificaciones. Y lo que es peor, si se
introducen cambios en el diseñador después de haber hecho cambios manuales en el
código, estos cambios se pierden al en el momento de guardar el diseñador, ya que,
en ese caso, el código se regenera automáticamente. La solución consiste en crear
una clase parcial en un archivo independiente del que utiliza el diseñador.

El diseñador Objeto/Relacional se abre automáticamente cuando se agrega un elemento de


tipo LINQ to SQL Classes. Esta herramienta también se abre cuando se agrega un archivo
.dbml a un proyecto. Cuando se abre, la superficie del diseñador se separa en dos partes. La
zona de la izquierda contiene las clases asociadas a las tablas, mientras que la zona de la
derecha contiene los procedimientos y las funciones asociadas a los procedimientos
almacenados.

El conjunto representa el DataContext generado.

Agregar clases

Puede crear las clases que representan las matrices de una base de datos arrastrando y
soltando una o varias matrices desde el explorador de servidores hacia la parte izquierda del
diseñador. El primer elemento agregado al diseñador Objeto/Relacional se usa para
configurar las propiedades de la conexión del DataContext. Cuando se añade otro elemento
procedente de otra base de datos, un cuadro de diálogo le pregunta si desea sustituir la
conexión existente. Si acepta la modificación, las clases ya presentes en el diseñador no se
podrán utilizar. La adición de una matriz genera el código necesario para que el

ShareVideos
DataContext inicialice las propiedades de una instancia de la clase a partir de la
información presente en una fila de la base de datos. También añade el código necesario
para que las modificaciones aportadas a las propiedades de la instancia se puedan volcar en
la base de datos. El diseñador se basa en la estructura de la tabla y en la clave primaria para
efectuar las actualizaciones.

Si lo desea, también puede indicar cómo deben efectuarse las actualizaciones. Para ello,
cada clase posee tres propiedades: Insert, Update, Delete. Por defecto se inicializan estas
propiedades con el valor Utilizar el runtime para indicar que el código encargado de las
actualizaciones se genera automáticamente. Para modificar este comportamiento, puede
asignar a estas propiedades procedimientos almacenados. Un cuadro de diálogo permite la
configuración de estas propiedades.

Después de seleccionar el procedimiento almacenado, deberá señalar cómo se indican los


parámetros esperados en entrada por el procedimiento almacenado. Los valores disponibles
corresponden a las diferentes propiedades de la clase. Para cada propiedad, está disponible
la versión actual o de origen.

Agregar asociaciones

Después de haber arrastrado varias tablas al diseñador, es posible crear asociaciones entre
algunas de ellas. Las asociaciones son muy similares a las relaciones entre tablas en una
base de datos. De hecho, si existe en la base de datos una relación de clave exterior entre
dos tablas, se creará automáticamente una asociación cuando estas dos tablas se agreguen al
diseñador.

Para agregar manualmente una asociación, debe utilizar el menú contextual del diseñador
Objeto/Relacional.

Un cuadro de diálogo le propone entonces configurar la relación. Deberá elegir la clase


padre y la clase hijo de la relación. La clase padre es la clase que se encuentra en el extremo
’uno’ de una relación uno a varios, mientras que la clase hijo representa el extremo ’varios’
de la relación. Por ejemplo, en la asociación entre las clases Product y Category, la clase
Category representa el lado ’uno’ de la relación y la clase Product el lado ’varios’. En
efecto, un producto pertenece a una categoría y una categoría contiene varios productos. A
continuación, deberá indicar para cada una de las clases la propiedad o las propiedades que
van a participar en la relación. Conviene comprobar que las propiedades que participan en
la asociación sean del mismo tipo en cada extremo de la asociación. Después de la creación
de la asociación, puede configurar algunas propiedades que no están disponibles en el
momento de la creación.

ShareVideos
 Cardinality: determina si la asociación es de tipo uno a varios (one-to-many) o de
tipo uno a uno (one-to-one).
 Child Property: indica si una propiedad debe ser creada en la clase padre para
referenciar la información de la clase hijo. El tipo de esta propiedad viene
determinado por el tipo de la clase hijo y la cardinalidad. Si la cardinalidad es uno a
uno, la propiedad es una simple referencia hacia una instancia de la clase
correspondiente. Si la cardinalidad es uno a varios, la propiedad es una colección de
instancias de la clase correspondiente.
 Las propiedades Name permiten identificar las propiedades creadas para realizar la
asociación.

Agregar métodos

Los procedimientos almacenados y las funciones pueden agregarse al diseñador


Objeto/Relacional para ser transformados en métodos del DataContext. La llamada de estos
métodos provocará la ejecución del procedimiento almacenado, o de la función, por el
servidor de base de datos. Si el procedimiento almacenado espera parámetros de entrada, se
deberán suministrar al método durante la ejecución. Es muy sencillo agregar un método al
DataContext, arrastrándolo y soltando entre el explorador de servidores y el diseñador
Objeto/Relacional. Sin embargo, hay que tener cuidado al agregar un procedimiento
almacenado o una función, ya que la ubicación donde tendrá lugar el desplazamiento
determina el tipo de retorno del método generado. Si se desplaza el elemento hacia la zona
de la derecha del diseñador, el tipo de retorno será generado automáticamente.

En cambio, si se desplaza el elemento hacia una clase existente del diseñador, el tipo de
retorno corresponderá a esta clase siempre que la información devuelta por el
procedimiento almacenado sea compatible con esta clase. Vamos a hacer unas
manipulaciones con el procedimiento almacenado [Ten Most Expensive Products], cuyo
código es el siguiente:

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go

CREATE procedure [dbo].[Ten Most Expensive Products] AS


SET ROWCOUNT 10
SELECT Products.ProductName AS
TenMostExpensiveProducts,Products.UnitPrice
FROM Products
ORDER BY Products.UnitPrice DESC

Este procedimiento devuelve el nombre y el precio de los diez productos más caros. Si
intentamos añadir este procedimiento al DataContext arrastrándolo y soltando hasta la
superficie de la clase Product, obtenemos el siguiente mensaje.

ShareVideos
En efecto, los elementos devueltos por el procedimiento almacenado no son productos, sino
simplemente el nombre y el precio del producto.

Por el contrario, si arrastramos y movemos hacia la zona derecha del diseñador, la


operación se realiza sin problema. La siguiente función se añade al DataContext.

Public Function Ten_Most_Expensive_Products() As ISingleResult


(Of Ten_Most_Expensive_ProductsResult)

Dim result As IExecuteResult = Me.ExecuteMethodCall(Me,


CType(MethodInfo. GetCurrentMethod,MethodInfo))

Return CType(result.ReturnValue,ISingleResult(Of Ten_Most_Expensive_


ProductsResult))
End Function

Esta función no devuelve una lista de productos, sino una lista de


Ten_Most_Expensive_ProductsResult, que corresponde a una clase generada
automáticamente en función de la información devuelta por el procedimiento almacenado.

Veamos la estructura.

Para que la clase Product pueda utilizarse como tipo de retorno para la función, estamos
obligados a modificar ligeramente el procedimiento almacenado para que devuelva
productos, y no solamente el nombre y el precio del producto.

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go

ALTER procedure [dbo].[Ten Most Expensive Products] AS


SET ROWCOUNT 10
SELECT *
FROM Products
ORDER BY Products.UnitPrice DESC

Ahora podemos volver a hacer la misma operación y obtener la siguiente función, que, esta
vez, devuelve una lista de productos.

Public Function Ten_Most_Expensive_Products() As ISingleResult(Of


Product)
Dim result As IExecuteResult = Me.ExecuteMethodCall(Me,
CType(MethodInfo.
GetCurrentMethod,MethodInfo))
Return CType(result.ReturnValue,ISingleResult(Of Product))
End Function

Herencia de clases

ShareVideos
Como cualquier clase, las clases generadas por el Diseñador Objeto/Relacional pueden
utilizar la herencia. Por el contrario, para las bases de datos, es una noción desconocida.
Hay que recurrir a algunos trucos para simular esta técnica en una base de datos. La
solución que se suele utilizar consiste en crear una tabla única que contendrá a la vez los
datos de los objetos de la clase básica y los de la subclase. Se añade una columna adicional
a la tabla a modo de discriminador. En función del valor de esta columna, es fácil
determinar si se debe representar la fila con una instancia de la clase de base o una instancia
de la derivada. Vamos a modificar la tabla Products para poder aplicar esta técnica.
Añadimos a la tabla una columna llamada Perecedera de tipo entero. Esta columna nos va a
servir de discriminador. Si el valor que contiene es igual a 1, se trata de un producto no
perecedero. Si el valor es igual a 2, se trata de un producto perecedero. En este caso, la
segunda columna, llamada DLC, de tipo fecha, contiene la fecha límite de consumo del
producto. Para los productos no perecederos, esta columna no contiene ningún valor (null).
Modifique los datos de algunos productos para que se conviertan en productos perecederos
(Camenbert Pierrot, Caracoles de Borgoña, Mascarpone Fabioli…).

Ahora que la base de datos está lista, podemos añadir las clases al DataContext. La primera
etapa consiste en añadir la tabla que constituye la clase de base. Luego agregamos un
segundo ejemplar de esa tabla y cambiamos el nombre de la clase correspondiente, que se
convertirá en la clase derivada. A continuación agregamos, utilizando la caja de
herramientas, una relación de herencia entre las dos clases que dibujamos desde la clase
hija hacia la clase padre. En cada una de las clases eliminamos las propiedades inútiles,
manteniendo, por ejemplo, la propiedad DLC en la clase derivada y eliminándola en la
clase base. Una vez seleccionada la relación de herencia en el diagrama, debemos modificar
sus propiedades.

 Indique, mediante la propiedad Discriminator Property, la propiedad necesaria para


distinguir entre una instancia de la clase base y una instancia de la clase subclase.
 Configure las propiedades Base Class Discriminator Value y Derived Class
Discriminator Value con los valores de la propiedad configurada anteriormente que
representa una instancia de la clase base y una instancia de la subclase,
respectivamente.
 La propiedad Inheritance Default especifica qué clase se utilizará si el discriminador
contiene un valor desconocido.

Obtenemos las siguientes clases:

Ahora que nuestras clases están disponibles, veamos cómo utilizarlas mediante consultas
LINQ.

c. Utilización de consultas LINQ a SQL

Las consultas LINQ a SQL utilizan de manera rigurosa la misma sintaxis que las que
hemos estudiado en la sección Sintaxis del lenguaje LINQ. La única pequeña diferencia

ShareVideos
radica en que los datos son extraídos, en este caso, de la base de datos y transformados en
instancias de clases a partir de los datos de mapeo. El diálogo con la base de datos está
enteramente a cargo del DataContext. Por lo tanto, tenemos que crear una instancia del
DataContext porque es por su intermediación que tendremos los datos disponibles para la
ejecución de la consulta LINQ. A continuación presentamos nuestra primera consulta LINQ
hacia la base de datos.

Dim dc As NorthWind
dc = New NorthWind
Dim consulta = From unCliente In dc.Customers _
Where unCliente.ContactName Like "A*" _
Select unCliente

For Each Cliente In consulta


Console.WriteLine(Cliente.ContactName)
Next

En este código, la clase NorthWind corresponde al DataContext, que es el puente para que
los datos estén disponibles para la consulta LINQ. Pero ¿cómo se seleccionan los datos?

En realidad, el método más natural para obtener datos procedentes de una base de datos
consiste en pedir a esta que ejecute una consulta SQL. Efectivamente, esta solución es la
utilizada por LINQ. Para verificarlo, podemos pedirle al DataContext (NorthWind en
nuestro caso) que muestre en la consola el código SQL que genera automáticamente. Para
ello, simplemente inicialicemos la propiedad Log del DataContext con la salida de la
consola, antes de la creación de la consulta LINQ.

Dim dc As NorthWind
dc = New NorthWind
dc.Log = Console.Out
Dim consulta = From unCliente In dc.Customers _
Where unCliente.ContactName Like ”A*” _
Select unCliente

For Each Cliente In consulta


Console.WriteLine(Cliente.ContactName)
Next

Durante la ejecución, obtenemos la visualización siguiente:

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName],


[t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region],
[t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[ContactName] LIKE @p0
-- @p0: Input NVarChar (Size = 2; Prec = 0; Scale = 0) [A%]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build:
3.5.21022.8

Ana Trujillo
Antonio Moreno
Ann Devon

ShareVideos
Aria Cruz
André Fonseca
Annette Roulet
Alexander Feuer
Alejandra Camino
Art Braunschweiger
Anabela Domingues

Efectivamente, contamos con una consulta SQL con parámetros, que ha sido creada
automáticamente por el DataContext. Esta consulta no es muy complicada y se hubiese
podido escribir de forma fácil utilizando directamente ADO.NET. Intentemos ejecutar otra
consulta que nos permita obtener las fechas de pedidos de cada uno de los clientes.

Dim consultaGroupJoin = From unCliente In dc.Customers _


Group Join unPedido In dc.Orders On unCliente Equals unPedido.Customer _
Into PedidosCliente = Group _
Select unCliente, PedidosCliente
For Each r In consultaGroupJoin
Console.WriteLine(r.unCliente.ContactName)
For Each c In r.PedidosCliente
Console.WriteLine(vbTab & c.OrderDate)
Next
Next

Este es el código SQL generado para la ejecución de esta consulta:

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName],


[t0].[Contact Title], [t0].[Address], [t0].[City], [t0].[Region],
[t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax],
[t1].[OrderID], [t1].[CustomerID] AS [CustomerID 2],
[t1].[EmployeeID], [t1].[OrderDate], [t1].[RequiredDate],
[t1].[ShippedDate], [t1].[ShipVia], [t1].[Freight], [t1].[ShipName],
[t1].[ShipAddress], [t1].[ShipCity], [t1].[ShipRegion],
[t1].[ShipPostalCode], [t1].[ShipCountry], (
SELECT COUNT(*)
FROM [dbo].[Orders] AS [t2]
WHERE [t0].[CustomerID] = [t2].[CustomerID]
) AS [value]
FROM [dbo].[Customers] AS [t0]
LEFT OUTER JOIN [dbo].[Orders] AS [t1] ON [t0].[CustomerID] =
[t1].[CustomerID]
ORDER BY [t0].[CustomerID], [t1].[OrderID]

Esto empieza a complicarse de manera seria. Escribir directamente en SQL una consulta
como esta requeriría ciertamente el dominio de este lenguaje, mientras que, en cambio, la
sintaxis LINQ resulta muy sencilla. Aquí, justamente, es donde puede encontrarse la
potencia de LINQ a SQL.

Esta facilidad no se limita a la extracción de datos desde la base de datos, ya que LINQ a
SQL también es capaz de gestionar las actualizaciones de los datos hacia la base de datos.

ShareVideos
d. Actualización de datos

La actualización de la base de datos se realiza también de manera muy sencilla,


manipulando objetos y sin escribir la menor línea de SQL.

Modificación de datos existentes

La primera etapa consiste en obtener los datos que se desea modificar ejecutando una
consulta de selección ordinaria. Una vez que los datos están disponibles en forma de
instancias de clases, podemos simplemente modificar las propiedades de estas instancias.
Para transferir las modificaciones hacia la base de datos, basta con pedirle al DataContext
que las propague hacia la base de datos. Vamos a probar esta técnica desplazando nuestros
clientes barceloneses a Valencia.

Dim clientesBarceloneses = From unCliente In dc.Customers _


Where unCliente.City = "Barcelona" _
Select unCliente

For Each unCliente In clientesBarceloneses


unCliente.City = "Valencia"
unCliente.PostalCode = "46000"
Next
dc.SubmitChanges()

En este código, es la instrucción SubmitChanges del DataContext la que provoca la


actualización de la base de datos ejecutando automáticamente la siguiente consulta SQL
Update para cada objeto que ha sido modificado.

UPDATE [dbo].[Customers]
SET [City] = @p10, [PostalCode] = @p11
WHERE ([CustomerID] = @p0) AND ([CompanyName] = @p1) AND ([ContactName] =
@p2)
AND ([ContactTitle] = @p3) AND ([Address] = @p4) AND ([City] = @p5)
AND ([Region]IS NULL) AND ([PostalCode] = @p6) AND ([Country] = @p7)
AND ([Phone] = @p8) AND ([Fax] = @p9)
-- @p0: Input NChar (Size = 5; Prec = 0; Scale = 0) [FRANR]
-- @p1: Input NVarChar (Size = 19; Prec = 0; Scale = 0) [España
restauración]
-- @p2: Input NVarChar (Size = 14; Prec = 0; Scale = 0) [Carine Schmitt]
-- @p3: Input NVarChar (Size = 17; Prec = 0; Scale = 0) [Marketing
Manager]
-- @p4: Input NVarChar (Size = 13; Prec = 0; Scale = 0) [calle real 54]
-- @p5: Input NVarChar (Size = 9; Prec = 0; Scale = 0) [Barcelona]
-- @p6: Input NVarChar (Size = 5; Prec = 0; Scale = 0) [08000]
-- @p7: Input NVarChar (Size = 6; Prec = 0; Scale = 0) [España]
-- @p8: Input NVarChar (Size = 11; Prec = 0; Scale = 0) [40.32.21.21]
-- @p9: Input NVarChar (Size = 11; Prec = 0; Scale = 0) [40.32.21.20]
-- @p10: Input NVarChar (Size = 8; Prec = 0; Scale = 0) [Valencia]
-- @p11: Input NVarChar (Size = 5; Prec = 0; Scale = 0) [46000]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build:
3.5.21022.8

ShareVideos
Eliminación de datos

Como para la modificación, debemos obtener previamente los elementos que deseamos
eliminar ejecutando una consulta de selección, y luego indicar cuáles de ellos deseamos
suprimir. Para ello, llamamos al método DeleteOnSubmit de la tabla a la que pertenece el
elemento, pasándole como parámetro el objeto que deseamos eliminar. Para confirmar las
eliminaciones, debemos invocar el método SubmitChanges del DataContext. Vamos a
probar esta técnica eliminando los clientes brasileños de la base de datos.

Dim borrarCliente = From unCliente In dc.Customers _


Where unCliente.Country = "Brasil" _
Select unCliente
For Each unCliente In borrarCliente
dc.Customers.DeleteOnSubmit(unCliente)
Next
dc.SubmitChanges()

Al ejecutan este código, obtenemos esta magnifica Excepción.

En nuestra precipitación, hemos olvidado un pequeño detalle. En la base de datos, las


matrices Customer, Order y OrderDetail están vinculadas por restricciones de clave
externa. Por lo tanto, es imposible eliminar un cliente si todavía posee pedidos, y de la
misma manera es imposible eliminar un pedido si contiene todavía filas de pedido. El
problema viene del hecho de que LINQ no es capaz de gestionar las eliminaciones en
cascada. Para resolver nuestro problema, tenemos dos soluciones:

 Activar la regla ON DELETE CASCADE en las restricciones de clave extranjera.


 Gestionar nosotros mismos la eliminación de los objetos antes de la eliminación de
los objetos padres.

Esta solución es la que vamos a utilizar. Ya que nuestro modelo objeto está diseñado
correctamente, esta solución es muy fácil de poner en marcha. En efecto, en la clase
Customers, tenemos la colección Orders, que representa los pedidos del cliente. Del mismo
modo en la clase Orders, tenemos la colección Order_Details, que representa todas las filas
de un pedido. Solo hace falta ejecutar tres bucles anidados que van a suprimir las filas de
cada pedido, los pedidos de cada cliente y finalmente los propios clientes.

Dim borrarCliente = From unCliente In dc.Customers _


Where unCliente.Country = "Brasil" _
Select unCliente
For Each unCliente In borrarCliente
For Each unPedido In unCliente.Orders
For Each unaLinea In unPedido.Order_Details
dc.Order_Details.DeleteOnSubmit(unaFila)
Next
dc.Orders.DeleteOnSubmit(unPedido)
Next
dc.Customers.DeleteOnSubmit(unCliente)

ShareVideos
Next
dc.SubmitChanges()

Con esta solución, ya no hay problemas y nuestros clientes brasileños están borrados
correctamente de la base de datos.

Agregar datos

La inclusión de datos se realiza en tres etapas. Primero es necesario crear una instancia de
la clase que representa los datos que deseamos insertar en la base de datos. Luego se
inicializan las propiedades de esta instancia con los valores que queremos agregar en la
base de datos. El objeto configurado debe insertarse en la tabla del DataContext.
Finalmente las modificaciones son transferidas hacia la base de datos. Para ilustrar estas
etapas, vamos a añadir un nuevo cliente a la base de datos.

Dim nuevoCliente As Customer


nuevoCliente = New Customer
With nuevoCliente
.CustomerID = "MGARCI"
.ContactName = "Miguel García"
.CompanyName = "ENI"
.ContactTitle = "Formador"
.Country = "España"
.City = "Salamanca"
.Address = "Calle Lido 3"
.Fax = "937.15.22.14"
.Phone = "937.25.88.91"
.PostalCode = "37001"
End With
dc.Customers.InsertOnSubmit(nuevoCliente)
dc.SubmitChanges()

e. Conflictos de las actualizaciones

Ocurre a menudo que varios usuarios trabajan simultáneamente con la misma base de datos.
Se pueden producir conflictos cuando los mismos registros de la base de datos son
actualizados por varios usuarios. LINQ propone un mecanismo que permite tratar este
problema. Este mecanismo se descompone en cuatro etapas:

 Configurar en qué datos de la base de datos deben controlarse los conflictos.


 Detectar la aparición de un conflicto.
 Obtener información relativa al conflicto.
 Resolver el conflicto.

Configuración de las clases para la detección de conflictos

Durante la creación de las clases con el diseñador Objeto/Relacional, podemos indicar, para
cada propiedad, si debe ser incluida en el mecanismo de detección de conflictos. Cada
miembro de la clase generada posee una propiedad Update Check a la que podemos asignar
tres valores diferentes:

ShareVideos
 Siempre: la detección de conflictos está siempre activa para este elemento.
 WhenChanged: activa la detección únicamente si el valor ha sido modificado.
 Nunca: no tener en cuenta este elemento para la detección de conflictos.

Por defecto, todas las propiedades se utilizan para la detección de conflictos.

Detección de conflictos

Los conflictos surgen durante el traslado de la información hacia la base de datos. Por lo
tanto, debemos intervenir a este nivel. Para ello, durante la invocación del método
SubmitChanges del DataContext, indicamos con un parámetro cómo debe comportarse el
mecanismo de detección de conflictos. Dos soluciones son posibles:

 FailOnFirstConflict: señala el problema tan pronto como se produce el primer


conflicto.
 ContinueOnConflict: trata de hacer todas las actualizaciones e indica al final si está
teniendo lugar un conflicto.

Si un conflicto es detectado, se activa una excepción tipo ChangeConflictException. La


invocación del método SubmitChanges debe estar dentro de un bloque Try Catch para
atrapar la excepción.

Obtener la información relativa a los conflictos

En el bloque Catch podemos obtener información sobre los conflictos recorriendo la


colección ChangeConflicts del DataContext. Esta colección contiene uno o más objetos del
tipo ObjectChangeConflict. La propiedad Object nos permite obtener una referencia del
elemento que originó el problema. La propiedad MemberConflicts facilita, por suparte, la
lista de todos los miembros de este objeto que han originado el problema.

Tenemos a nuestra disposición el valor original de cada uno ellos en el momento de la


creación de la instancia de la clase a partir de la información de la base de datos, el valor
actual de la instancia de la clase y el valor actual en la base de datos.

Resolver los conflictos

Para resolver los conflictos sobrevenidos durante la actualización de la base de datos, se


pueden considerar tres hipótesis.

 Reemplazar los valores de las propiedades en conflicto con la información presente


en la base de datos. Para esto, se debe llamar al método Resolve del objeto
ObjectChangeConflict pasándole la constante Overwrite CurrentValues.
 Reemplazar los valores de la base de datos por la información contenida en las
propiedades del objeto. De la misma manera que en la solución anterior, debemos
llamar al método Resolve del objeto ObjectChangeConflict pasándole esta vez la
constante KeepCurrentValues.

ShareVideos
 Fusionar las propiedades del objeto con la información de la base de datos. Se
modifica la información de la base de datos solo si la propiedad correspondiente al
objeto ha sido modificada. En este caso, se debe llamar al método Resolve con la
constante KeepChanges.

El siguiente código le permite probar estas diferentes soluciones reemplazando


simplemente la constante durante la llamada del método Resolve.

Dim cl As Customer
Dim rqt = From unCliente In dc.Customers _
Where unCliente.CustomerID = "BOLID" _
Select unCliente

For Each cl In rqt


cl.City = "Barcelona"
Next
Console.WriteLine("modificar el código postal del cliente BOLID en
la base y luego presionar una tecla")
Console.ReadLine()
Try
dc.SubmitChanges(ConflictMode.FailOnFirstConflict)
Catch ex As ChangeConflictException
For Each o As ObjectChangeConflict In dc.ChangeConflicts
o.Resolve(RefreshMode.KeepChanges)
’ o.Resolve(RefreshMode.KeepCurrentValues)
’ o.Resolve(RefreshMode.OverwriteCurrentValues)
Next
End Try
Console.WriteLine("el objeto cliente:")
Console.WriteLine("city:" & cl.City)
Console.WriteLine("postalCode:" & cl.PostalCode)
For Each cli In rqt
Console.WriteLine("el cliente en la base:")
Console.WriteLine("city:" & cli.City)
Console.WriteLine("postalCode:" & cli.PostalCode)
Next

Presentación
El lenguaje XML (eXtensible Markup Language) es un lenguaje que sirve para representar
datos. Permite encapsular cualquier tipo de datos representándolos en forma de árbol. Los
datos se escriben en etiquetas o en forma de atributos. Este formato permite escribir datos,
pero no darles formato ni manipularlos. Se utiliza principalmente para hacer posible el
intercambio de datos entre aplicaciones e incluso entre sistemas diferentes. También se
utiliza a menudo como formato de almacenamiento para los parámetros de configuración de
una aplicación. Visual Studio y Windows lo utilizan habitualmente con este fin. Este
lenguaje ha sido diseñado por W3C (World Wide Web Consortium). Por lo tanto, es en el
sitio http://www.w3.org/XML donde podrá obtener el detalle de las especificaciones de este
lenguaje.

ShareVideos
El lenguaje XML se confunde a menudo con el lenguaje HTML. Aunque presentan
similitudes, estos dos lenguajes no tienen la misma finalidad. A continuación presentamos
los puntos comunes entre los lenguajes XML y HTML:

 Los dos lenguajes se presentan como «solo texto».


 Se representa el contenido de los documentos a través de etiquetas.
 Estas etiquetas pueden tener atributos.
 Se pueden anidar las etiquetas las unas en las otras.
 Ambos lenguajes provienen de una misma base: el SGML (Standard Generalized
Markup Language).

El lenguaje XML se diferencia del lenguaje HTML en los puntos siguientes:

 El lenguaje XML permite la creación de sus propias etiquetas.


 Las herramientas encargadas del tratamiento gestionan la sintaxis de manera más
rigurosa.
 HTML es un lenguaje diseñado para la presentación de los datos. En cambio, XML
se utiliza para la descripción de los datos.

Para poder ser manipulados fácilmente, los datos XML se deben confiar a un procesador
XML.

Un procesador XML es un módulo de software especialmente escrito para manipular XML.


La utilización de un módulo externo para el tratamiento XML se explica por la complejidad
que representa el desarrollo de un procesador XML, totalmente funcional. Efectivamente:
para que un procesador XML sea considerado totalmente funcional, su funcionamiento
debe seguir las evoluciones del lenguaje definidas por el W3C. Por lo tanto, es importante
visitar regularmente el sitio de Microsoft para verificar si existe una versión más reciente
del procesador XML que la que está instalada en su máquina.

Estructura de un documento XML


Antes de manipular documentos XML a partir de Visual Basic, es importante entender bien
la estructura de este tipo de documentos. Los siguientes párrafos presentan las nociones
elementales que se deben conocer antes de lanzarse a la utilización de documentos XML.

1. Componentes de un documento XML

Un documento XML se puede formar a partir de los siguientes elementos:

Instrucción de tratamiento

Las instrucciones de tratamiento permiten incorporar en un documento XML la


información destinada al procesador XML o a otras aplicaciones que deban manipular el

ShareVideos
documento. Se utilizan estas instrucciones para facilitar una instrucción especial a una
aplicación que trabaja en el documento.

Se inserta la instrucción de tratamiento en el documento con la siguiente sintaxis:

< ?nombreAplicacion instruccion ?>

La primera parte es el nombre de la aplicación a la que se destina esta instrucción. La


segunda parte es el texto de la instrucción.

Un documento XML contiene en general una instrucción de tratamiento especial para


definir la versión de XML que corresponde al documento y la codificación de los caracteres
que este utiliza.

<?xml version="1.0" encoding="utf-8" ?>

Comentarios

Los comentarios sirven para incluir en el documento información destinada a los usuarios
de este. El procesador XML o las aplicaciones que utilizan el documento los ignoran. No se
deben incorporar a una etiqueta.

La siguiente sintaxis se debe utilizar para ubicar un comentario en el documento.

<!--esto es un comentario-->

En el interior del comentario, la utilización de los caracteres -- está prohibida:

Caracteres reservados

Algunos caracteres están reservados para el lenguaje XML, como por ejemplo el carácter &
del siguiente ejemplo:

Para poder utilizar estos caracteres en un documento XML, debe sustituirlos por la
siguiente sintaxis:

Carácter Utilizar en lugar de


& &amp;
< &lt;
> &gt;
’ &apos;

ShareVideos
" &quot; Por lo tanto, la sintaxis correcta es:

<menu>queso &amp; postre</menu>

Se pueden incorporar secuencias de caracteres más largas utilizando una sección


CDATA. La sintaxis es la siguiente:

<![CDATA[{ Select * from postres where precio < 10} and calorías > 500]]>

Con esta sintaxis cualquier carácter se puede utilizar sin precaución particular.

Elementos XML

Un elemento XML es un contenedor que incluye datos y otros elementos. Está compuesto
por una etiqueta de principio y otra de fin. La sintaxis de un elemento XML es la siguiente:

<NombreElemento>contenido</NombreElemento>

Los elementos deben respetar algunas reglas relativas a su forma:

 Los nombres de los elementos no pueden contener espacios.


 No pueden empezar por un número o un signo de puntuación.
 No pueden empezar con xml (tanto si la letra es mayúscula como minúscula).
 Deben empezar justo después del signo >, sin espacios.
 Las etiquetas de principio y fin deben tener la misma caja (mayúsculas/minúsculas).
 Un documento XML debe contener al menos un elemento: el elemento raíz.
 Todos los elementos que siguen al elemento raíz deben quedar anidados en este.
 Si un elemento no tiene contenido, puede estar constituido únicamente por una
etiqueta de fin.
 Solo el elemento raíz debe tener una etiqueta de principio y otra de fin, incluso si no
tiene contenido.

Ejemplo

<?xml version="1.0" encoding="utf-8" ?>


<restaurante>
<menu>
<entrantes>
<nombre>rábanos</nombre>
<nombre>pasta</nombre>
<nombre>salchichón</nombre>
</entrantes>
<platos>
<nombre>paella</nombre>
<nombre>fideuá</nombre>
<nombre>cuscús</nombre>
</platos>
<quesos>
<nombre>manchego</nombre>

ShareVideos
<nombre>tetilla</nombre>
<nombre>cabrales</nombre>
</quesos>
<postres>
<nombre>helado</nombre>
<nombre>tarta</nombre>
<nombre>crema catalana</nombre>
</postres>
</menu>
</restaurante>

Atributos de elementos

Se utilizan los atributos de elementos para calificar un elemento. Se ubican en la etiqueta de


principio del elemento. Como los elementos, deben seguir ciertas reglas:

 Un atributo está compuesto por un nombre y una asignación de valor.


 Un elemento puede contener un número cualquiera de atributos.
 Los nombres de atributos están separados por espacios.
 Un nombre de atributo solo puede aparecer una vez en un elemento.
 Un nombre de atributo puede aparecer en varios elementos.
 Un nombre de atributo no puede contener espacios.
 La asignación de un valor a un atributo se hace con el signo igual, seguido por el
valor entre comillas dobles.

Ejemplo

<?xml version="1.0" encoding="utf-8" ?>


<restaurante>
<menu type="gastronomico">
<entrantes>
<número calorías="50">rábanos</número>
<número calorías="300">pasta</número>
<número calorías="350">salchichón</número>
</entrantes>
<platos>
<número calorías="1000">paella</número>
<número calorías="2000">fideuá</número>
<número calorías="1700">cuscús</número>
</platos>
<quesos>
<número calorías="240">manchego</número>
<número calorías="300">tetilla</número>
<número calorías="120">cabrales</número>
</quesos>
<postres>
<número calorías="340" sabor="chocolate">helado</número>
<número calorías="250" frutas="manzanas">tarta</número>
<número calorías="400">crema catalana</número>
</postres>
</menu>
</restaurante>

ShareVideos
Espacios de nombres

Un espacio de nombres es un conjunto de nombres de elementos identificados por una


única referencia. Permiten evitar las confusiones cuando unos datos XML se fusionan a
partir de diferentes fuentes.

Veamos el siguiente ejemplo, que podría ser un archivo de configuración de una aplicación:

<?xml version="1.0" encoding="utf-8" ?>


<aplicacion>
<menu nombre="archivo">
<entrantes>nuevo</entrantes>
<entrantes>abrir</entrantes>
<entrantes>cerrar</entrantes>
</menu>
<menu nombre="edicion">
<entrantes>copiar</entrantes>
<entrantes>cortar</entrantes>
<entrantes>pegar</entrantes>
</menu>
</aplicacion>

En este archivo, tenemos elementos ya definidos en otro archivo. Es obvio que los
elementos menu y entrantes no tienen el mismo significado que en el archivo utilizado
anteriormente. Para evitar cualquier ambigüedad, hay que agregar a cada archivo una
definición de espacio de nombres que haga que cada elemento sea único. La definición de
un espacio de nombres se efectúa con el atributo xmlns seguido por un prefijo y por el
identificador del espacio de nombres.

La sintaxis es la siguiente para cada uno de nuestros dos archivos:

<restaurante xmlns:rest="http://www.eni-escuela.es/restaurante">
<application xmlns:appli="http://www.eni-escuela.es/configappli">

Es muy importante que los identificadores de espacio de nombres sean únicos si desea
intercambiar datos con otras personas. Por eso, es corriente utilizar el nombre del dominio
de la empresa en el identificador (se supone que este es único). Con esta modificación,
podemos utilizar en el mismo archivo elementos de menu y entrantes agregando ante el
prefijo el espacio de nombres en el que tienen un significado.

<fusion xmlns:appli="http://www.eni-escuela.es/configappli" xmlns:resto=


"http://www.eni-escuela.es/restaurante">
<appli:menu nombre="archivo">
<appli:entrantes>registrar</appli:entrantes>
</appli:menu>
<resto:menu nombre="economico">
<resto:entrantes>aguacate</resto:entrantes>
</resto:menu>
</fusion>

ShareVideos
2. Documento bien formado y documento válido

Gracias al lenguaje XML, tenemos la posibilidad de crear fácilmente documentos


estructurados y comprensibles. También existen dos nociones que permiten verificar la
calidad de un documento XML: un documento puede estar bien formado y un documento
puede ser válido.

a. Documento bien formado

Un documento está bien formado si obedece a las reglas sintácticas del lenguaje XML.
Estas reglas son mucho menos estrictas que las reglas de validez. Gestionan las atribuciones
de nombres, las creaciones y los anidamientos de elementos. Para poder ser tratado por un
procesador XML, un documento debe estar bien formado. Si el procesador detecta un error,
detiene inmediatamente el tratamiento del documento.

b. Documento válido

Un documento válido es un documento XML al cual se vincula una DTD, o un esquema


XSD, (definición del tipo de documento) y que respeta todas las reglas de construcción
definidas en esta última. Cuando un procesador XML analiza el documento, busca en la
DTD, o en el esquema XSD, una definición para cualquier elemento, atributo y entidad de
este documento. En cuanto encuentra un error, detiene el tratamiento.

Manipulación de un documento XML


El tratamiento de un documento XML en una aplicación VB.NET se hace utilizando DOM
(Document Object Model). El DOM le permite leer, manipular y modificar un documento
XML con un programa. Este último rige la representación en memoria de los datos XML,
aunque los verdaderos datos XML estén almacenados de manera lineal cuando se
encuentran en un archivo o proceden de otro objeto.

Por ejemplo, el siguiente documento:

<?xml version="1.0"?>
<restaurante>
<menu precio="10">
<entrantes>rábanos</entrantes>
<plato>fideuá</plato>
<postre>helado</postre>
</menu>
<vinos>
<tinto>rioja</tinto>
<blanco>moscatel</blanco>
</vinos>
</restaurante>

está representado en esta forma en la memoria de una aplicación:

ShareVideos
En la estructura de un documento XML, cada círculo de esta ilustración representa un nodo,
llamado objeto XmlNode, que es el objeto básico del árbol DOM. La clase XmlDocument
se encarga de los métodos destinados a ejecutar operaciones en el documento en su
conjunto, por ejemplo para cargarlo en memoria o grabarlo en forma de archivo. Los
objetos XmlNode comportan un conjunto de métodos y de propiedades, así como
características básicas bien definidas. A continuación, presentamos algunas de estas
características:

 Un nodo solo puede poseer un nodo padre, que es el nodo situado justo encima de
él.
 El único nodo que no tiene padre es la raíz del documento, ya que se trata del nodo
de primer nivel que contiene el propio documento y los fragmentos de documento.
 La mayoría de los nodos pueden comportar varios nodos hijos que son los nodos
situados directamente debajo de ellos.
 Los nodos situados en el mismo nivel, representados en el diagrama por los nodos
menú y vinos, son nodos hermanos.

Una de las características del DOM es su manera de gestionar los atributos. Los atributos
no son nodos que forman parte de las relaciones padre-hijo y hermano. Se consideran como
una propiedad del nodo y están constituidos por un par, compuesto por un nombre y un
valor.

En nuestro ejemplo, precio="10" asociado al elemento menú, la palabra precio corresponde


al nombre y el valor del atributo precio es 10. Para extraer el atributo precio="10" del nodo
menú, se llama al método GetAttribute cuando el cursor se encuentra en el nodo menú.

Para los ejemplos siguientes, utilizaremos este documento XML:

<?xml version="1.0" encoding="utf-8" ?>


<restaurante>
<menu tipo="gastronomico">
<entrantes>
<número calorías="50">rábanos</número>
<número calorías="300">pasta</número>
<número calorías="350">salchichón</número>
</entrantes>
<platos>
<número calorías="1000">paella</número>
<número calorías="2000">cocido</número>
<número calorías="1700">cuscús</número>
</platos>
<quesos>
<número calorías="240">manchego</número>
<número calorías="300">tetilla</número>
<número calorías="120">cabrales</número>
</quesos>
<postres>
<número calorías="170" sabor="chocolate">helado</número>

ShareVideos
<número calorías="125" frutas="manzanas">tarta</número>
<número calorías="200">crema catalana</número>
</postres>
</menu>
<menu type="economico">
<entrantes>
<número calorías="50">pan</número>
</entrantes>
<platos>
<número calorías="1700">jamón</número>
</platos>
<quesos>
<número calorías="240">manchego</número>
</quesos>
<postres>
<número calorías="340" sabor="sorbete">helado</número>
</postres>
</menu>
</restaurante>

1. Utilización de DOM

La primera etapa durante la utilización de DOM consiste en cargar el documento XML en


un árbol de nodos DOM. Para ello, usted debe declarar un objeto XmlDocument y luego
utilizar el método Load para rellenar este objeto a partir de un archivo XML.

dim doc as XmlDocument


doc = New Xml.XmlDocument()
doc.Load("restaurante.xml")

También es posible cargar datos XML a partir de una cadena de caracteres. En este caso,
debe utilizar el método LoadXML facilitándole la cadena de caracteres que contiene los
datos XML.

Una vez cargados los datos XML en el árbol, puede localizar nodos especiales con el fin de
manipularlos o modificarlos. El método GetElementsByTagName permite obtener un
objeto XmlNodeList, que contiene los nodos respectivos. Entonces puede obtener los
atributos del nodo utilizando la propiedad Attributes o comprobar si poseen nodos hijos con
la propiedad HasChildNodes. Si es el caso, tiene acceso a estos nodos gracias a la
propiedad ChildNodes en forma de un objeto XmlNodeList.

El siguiente ejemplo busca los nodos menú en el árbol y muestra el atributo tipo.

menus = doc.GetElementsByTagName("menu")
For Each unMenu In menus
Console.WriteLine(unMenu.Attributes("tipo").Value)
Next

También se pueden modificar las características de los nodos añadiéndoles un atributo. Los
nodos pueden, por ejemplo, recibir un atributo precio.

ShareVideos
menus = doc.GetElementsByTagName("menu")
Dim att As XmlAttribute
For Each unMenu In menus
If unMenu.Attributes("tipo").Value = "gastronomico" Then
att = doc.CreateAttribute("precio")
att.Value = "50 euros"
unMenu.Attributes.Append(att)
End If
If unMenu.Attributes("tipo").Value = "economico" Then
att = doc.CreateAttribute("precio")
att.Value = "15 euros"
unMenu.Attributes.Append(att)
End If
Next

También es posible añadir nodos hijos a nodos que existen en el árbol, creando instancias
de la clase XmlNode y uniéndolos a su nodo padre. El siguiente ejemplo añade un
digestivo al menú gastronomico.

menus = doc.GetElementsByTagName("menu")
Dim att As XmlAttribute
For Each unMenu In menus
If unMenu.Attributes("tipo").Value = "gastronomico" Then
Dim n1, n2, n3 As XmlNode
n1 = doc.CreateNode(XmlNodeType.Element, "digestivo", "")
n2 = doc.CreateNode(XmlNodeType.Element, "nombre", "")
n3 = doc.CreateNode(XmlNodeType.Text, "", "")
n3.Value = "Cognac"
n2.AppendChild(n3)
n1.AppendChild(n2)
unMenu.AppendChild(n1)
End If
Next

Después de la ejecución de los dos ejemplos anteriores, el documento XML debe tener la
siguiente forma:

<?xml version="1.0" encoding="Windows-1252"?>


<restaurante>
<menu tipo="gastronomico" precio="50 €">
<entrantes>
<número calorías="50">rábanos</número>
<número calorías="300">pasta</número>
<número calorías="350">salchichón</número>
</entrantes>
<platos>
<número calorías="1000">paella</número>
<número calorías="2000">fideuá</número>
<número calorías="1700">cuscús</número>
</platos>
<quesos>
<número calorías="240">manchego</número>
<número calorías="300">tetilla</número>
<número calorías="120">cabrales</número>
</quesos>

ShareVideos
<postres>
<número calorías="340" sabor="chocolate">helado</número>
<número calorías="250" frutas="manzanas">tarta</número>
<número calorías="400">crema catalana</número>
</postres>
<digestivo>
<número>Cognac</número>
</digestivo>
</menu>
<menu tipo="economico" precio="15 €">
<entrantes>
<número calorías="50">pan</número>
</entrantes>
<platos>
<número calorías="1700">jamón</número>
</platos>
<quesos>
<número calorías="240">manchego</número>
</quesos>
<postres>
<número calorías="340" sabor="sorbete">helado</número>
</postres>
</menu>
</restaurante>

En realidad, solo se modifica la representación en memoria del documento XML. Si desea


conservar las modificaciones, hay que guardar el documento en un archivo para asegurar la
persistencia de los datos. Para ello, debe utilizar el método Save de la clase XmlDocument,
y darle el nombre del archivo en el que desea efectuar la copia de seguridad.

doc.Save("rest2.xml")

2. Utilización de XPath

La principal finalidad de XPath consiste en definir la manera de buscar partes de un


documento XML. El nombre XPath viene de la utilización de una escritura de tipo path,
como en los shells DOS y UNIX. El objetivo consiste en desplazarse en el interior de la
estructura jerárquica de un documento XML como en un árbol de directorios. Para tener
una idea del interés de XPath, podríamos decir que es el equivalente al lenguaje SQL para
un documento XML. Pero la comparación se acaba aqui, porque ¡la sintaxis no tiene nada
que ver!

a. Búsqueda en un documento XML

Para buscar un elemento en un documento XML, la primera etapa consiste en crear una
instancia de la clase XPathNavigator. Esta instancia de clase debe conocer el documento en
el que deberá hacer las búsquedas. Por eso es el propio documento el que, mediante el
método CreateNavigator, va a facilitar esta instancia de clase.

Dim navegador As XPathNavigator = document.CreateNavigator()

ShareVideos
A partir de esta instancia, vamos a poder lanzar búsquedas en el documento con la ayuda
del método Select. Este método utiliza como parámetro una cadena de caracteres que
contiene la ruta XPath de búsqueda. Después de la ejecución, obtenemos un objeto
XPathNodeIterator, que permite recorrer la lista de los nodos encontrados.

El siguiente ejemplo busca en el documento rest.xml los entrantes disponibles en los


diferentes menús:

Dim documento As XmlDocument = New XmlDocument ()


documento.Load("rest.xml")

Dim navegador As XPathNavigator = documento.CreateNavigator()


Dim nodos As XPathNodeIterator = navegador.Select("/restaurante/
menu/ entrantes")
While nodos.MoveNext()
Console.WriteLine(nodos.Current.OuterXml)
Console.WriteLine()
End While

Obtenemos el siguiente resultado:

<entrantes>
<número calorías="50">rábanos</número>
<número calorías="300">pasta</número>
<número calorías="350">salchichón</número>
</entrantes>

<entrantes>
<número calorías="50">pan</número>
</entrantes>

También es posible añadir a la consulta XPath unos criterios de selección sobre el valor de
ciertos atributos.

El siguiente ejemplo busca los postres del menú gastronomico con menos de 350 calorías.

Dim documento As XmlDocument = New XmlDocument ()


documento.Load("rest.xml")
Dim navegador As XPathNavigator = documento.CreateNavigator()
Dim nodos As XPathNodeIterator =
navegador.Select("/restaurante/menu[@tipo=
’gastronomico’]/postres/número @calorías <350 ]")
While nodos.MoveNext()
Console.WriteLine(nodos.Current.Value)
Console.WriteLine()
End While

b. Modificación de los datos de un documento XML

Después de haber encontrado un elemento en el árbol de un documento, por supuesto es


posible modificar su valor.

ShareVideos
El siguiente ejemplo disminuye un 50 % las calorías de cada postre del menú
gastronomico.

Dim documento As XmlDocument = New XmlDocument()


documento.Load("rest.xml")
Dim navegador As XPathNavigator = documento.CreateNavigator()
Dim nodos As XPathNodeIterator =
navegador.Select("/restaurante/menu
[ @tipo=’gastronomico’]/postres/nombre")

While nodos.MoveNext()
nodos.Current.MoveToAttribute("calorías", "")
nodos.Current.SetValue(nodos.Current.Value * 0.5)
End While
documento.Save("rest.xml")

A continuación presentamos el contenido del archivo después de la ejecución del código


anterior.

<?xml version="1.0" encoding="utf-8" ?>


<restaurante>
<menu tipo="gastronomico">
<entrantes>
<número calorías="50">rábanos</número>
<número calorías="300">pasta</número>
<número calorías="350">salchichón</número>
</entrantes>
<platos>
<número calorías="1000">paella</número>
<número calorías="2000">cocido</número>
<número calorías="1700">cuscús</número>
</platos>
<quesos>
<número calorías="240">manchego</número>
<número calorías="300">tetilla</número>
<número calorías="120">cabrales</número>
</quesos>
<postres>
<número calorías="170" sabor="chocolatee">helado</número>
<número calorías="125" frutas="manzanas">tarta</número>
<número calorías="200">crema catalana</número>
</postres>
</menu>
<menu tipo="economico">
<entrantes>
<número calorías="50">pan</número>
</entrantes>
<platos>
<número calorías="1700">jamón</número>
</platos>
<quesos>
<número calorías="240">manchego</número>
</quesos>
<postres>
<número calorías="340" sabor="sorbete">helado</número>

ShareVideos
</postres>
</menu>
</restaurante>

c. Agregar un nodo a un documento XML

Después de la búsqueda de un nodo en un documento, es posible añadirle nodos hijos y


nodos hermanos. Los métodos InsertAfter e InsertBefore añaden un nodo hermano después
o antes del nodo actual. El método AppendChild agrega un nodo hijo al nodo actual.

El siguiente ejemplo agrega un nuevo postre al menú gastronomico.

Dim documento As XmlDocument = New XmlDocument()


documento.Load("rest.xml")
Dim navegador As XPathNavigator = documento.CreateNavigator()
Dim nodos As XPathNodeIterator =
navegador.Select("/restaurante/menu[@tipo=
’ gastronomico’]/postres") nodos.MoveNext()
nodos.Current.AppendChild("<número calorías=’800’>crepes</número>")
documento.Save("rest.xml")

Después de la ejecución de este código, el documento se convierte en:

<?xml version="1.0" encoding="utf-8" ?>


<restaurante>
<menu tipo="gastronomico">
<entrantes>
<número calorías="50">rábanos</número>
<número calorías="300">pasta</número>
<número calorías="350">salchichón</número>
</entrantes>
<platos>
<número calorías="1000">paella</número>
<número calorías="2000">cocido</número>
<número calorías="1700">cuscús</número>
</platos>
<quesos>
<número calorías="240">manchego</número>
<número calorías="300">tetilla</número>
<número calorías="120">cabrales</número>
</quesos>
<postres>
<número calorías="340" sabor="chocolate">helado</número>
<número calorías="250" frutas="manzanas">tarta</número>
<número calorías="400">crema catalana</número>
<número calorías="800">crepes</número>
</postres>
</menu>
<menu tipo="economico">
<entrantes>
<número calorías="50">pan</número>
</entrantes>
<platos>
<número calorías="1700">jamón</número>

ShareVideos
</platos>
<quesos>
<número calorías="240">manchego</número>
</quesos>
<postres>
<número calorías="340" sabor="sorbete">helado</número>
</postres>
</menu>
</restaurante>

3. Uso de LINQ to XML

El uso de LINQ puede ser una alternativa a DOM y XPath para manejar documentos XML.
El principio de funcionamiento es prácticamente igual al de DOM, ya que, para poder
trabajar con un documento XML, en primer lugar es necesario cargarlo en memoria como
una arborescencia de objetos. Las clases necesarias para esta representación están
disponibles en la biblioteca System.Xml.Linq. Por tanto, es imprescindible añadir a su
proyecto una referencia a esta biblioteca.

El documento XML que se va a tratar se carga en memoria a través de una llamada al


método Load de la clase XDocument:

Dim doc As Xdocument


doc = XDocument.Load("c:\restaurante.xml")

Ahora el contenido del archivo está disponible en memoria en forma de instancias de las
clases XElement y XAttribute, relacionadas con la instancia de la clase XDocument que se
ha creado.

A las instancias se puede acceder con la propiedad Elements del objeto XDocument.

El siguiente ejemplo carga el archivo restaurante.xml y muestra los elementos extraídos


del archivo.

Dim doc As Xdocument


doc = XDocument.Load("c:\restaurante.xml")
Dim el As Xelement
For Each el In doc.Elements
Console.WriteLine(el)
Next

Obtenemos el siguiente resultado, que corresponde al contenido de nuestro archivo:

<restaurante>
<menu tipo="gastronomico">
<entrantes>
<número calorías="50">rábanos</número>
<número calorías="300">pasta</número>
<número calorías="350">salchichón</número>
</entrantes>
<platos>

ShareVideos
<número calorías="1000">paella</número>
<número calorías="2000">cocido</número>
<número calorías="1700">cuscús</número>
</platos>
<quesos>
<número calorías="240">manchego</número>
<número calorías="300">tetilla</número>
<número calorías="120">cabrales</número>
</quesos>
<postres>
<número calorías="340" sabor="chocolate">helado</número>
<número calorías="250" frutas="manzanas">tarta</número>
<número calorías="400">crema catalana</número>
</postres>
</menu>
<menu tipo="economico">
<entrantes>
<número calorías="50">pan</número>
</entrantes>
<platos>
<número calorías="1700">jamón</número>
</platos>
<quesos>
<número calorías="240">manchego</número>
</quesos>
<postres>
<número calorías="340" sabor="sorbete">helado</número>
</postres>
</menu>
</restaurante>

La raíz del documento forma parte de los elementos que se extraen del archivo.

También es posible extraer directamente el elemento raíz en forma de un objeto XElement


utilizando el método Load de la classe XElement.

Dim raiz As Xelement


raiz = XElement.Load("c:\restaurante.xml")
For Each el In raiz.Elements
Console.WriteLine(el)
Next

En este caso, obtenemos un objeto XElement, que representa al elemento raíz. Su propiedad
Elements contiene los elementos hijo del contenido del archivo XML (sin elemento raíz).

<menu tipo="gastronomico">
<entrantes>
<número calorías="50">rábanos</número>
<número calorías="300">pasta</número>
<número calorías="350">salchichón</número>
</entrantes>
<platos>
<número calorías="1000">paella</número>
<número calorías="2000">cocido</número>
<número calorías="1700">cuscús</número>

ShareVideos
</platos>
<quesos>
<número calorías="240">manchego</número>
<número calorías="300">tetilla</número>
<número calorías="120">cabrales</número>
</quesos>
<postres>
<número calorías="340" sabor="chocolate">helado</número>
<número calorías="250" frutas="manzanas">tarta</número>
<número calorías="400">crema catalana</número>
</postres>
</menu>
<menu tipo="economico">
<entrantes>
<número calorías="50">pan</número>
</entrantes>
<platos>
<número calorías="1700">jamón</número>
</platos>
<quesos>
<número calorías="240">manchego</número>
</quesos>
<postres>
<número calorías="340" sabor="sorbete">helado</número>
</postres>
</menu>

El objetivo de LINQ era hacer consultas y extraer contenido de un documento XML. Por
este motivo, vamos a ver cómo escribir una consulta LINQ en un documento XML. Para
empezar, vamos a buscar las entrantes disponibles en los diferentes menús.

Después de cargar el archivo en un objeto XDocument, vamos a utilizar una consulta LINQ
para buscar la lista de los elementos cuyo número es entrantes.

Dim doc As Xdocument


doc = XDocument.Load("c:\restaurante.xml")

Dim listaEntrantes As IEnumerable(Of Xelement)


listaEntrantes = From ent In doc.Root.Descendants("entrantes")
Select ent

Dim s As Xelement
For Each s In listaEntrantes
Console.WriteLine(s)
Next

A continuación se muestra el resultado de la ejecución, que corresponde a lo que


esperábamos.

<entrantes>
<número calorías="50">rábano</número>
<número calorías="300">pasta</número>

ShareVideos
<número calorías="350">salchichón</número>
</entrantes>
<entrantes>
<número calorías="50">pan</número>
</entrantes>

En el segundo ejemplo, vamos a buscar los alimentos con un número de calorías superior a
1.000.

Como en el ejemplo anterior, vamos a buscar los elementos por nombre y luego
aplicaremos una restricción sobre el atributo calorías.

Dim alimentos As IEnumerable(Of XElement) =


From al In doc.Root.Descendants("número")
Where al.@calorías > 1000
Select al

For Each s In alimentos


Console.WriteLine(s)
Next

Obtenemos la siguiente lista, que corresponde a los alimentos más energéticos.

<número calorías="2000">cocido</número>
<número calorías="1700">cuscús</número>
<número calorías="1700">jamón</número>

Introducción
Ahora que su aplicación está terminada, probada, depurada y, por lo tanto, funciona sin
problemas, es el momento de pensar en la manera de ponerla a disposición de los usuarios.
Hay dos soluciones posibles:

 La tecnología de implementación Windows Installer. La aplicación es empaquetada


en uno o varios archivos que luego se distribuyen a los usuarios. Estos ejecutan el
archivo Setup.exe para instalar la aplicación.
 La implementación ClickOnce. Con esta solución, la publicación de los archivos de
la aplicación se hace de forma centralizada y el usuario instala o ejecuta la
aplicación a partir de esta ubicación.

A continuación vamos a detallar cada una de estas técnicas de implementación.

Implementación con Windows Installer


Microsoft Windows Installer es un servicio de instalación y configuración de aplicaciones
disponible en todos los sistemas operativos de Microsoft. El principio básico del
funcionamiento de Windows Installer reside en la agrupación en un único elemento de

ShareVideos
todos los datos e instrucciones necesarios para la implementación de una aplicación.
Representa una evolución importante respecto a los procedimientos de instalación clásicos,
que consistían principalmente en facilitar el conjunto de los archivos necesarios para el
funcionamiento correcto de la aplicación y un script encargado de copiar esos archivos en
el disco duro de la máquina.

Con Windows Installer, el sistema conserva un rastro de todas las operaciones efectuadas
durante la instalación: directorios creados, archivos copiados, entradas del registro
modificadas, etc. Estos datos se utilizan durante la desinstalación de la aplicación,
momento en que Windows Installer efectúa las operaciones inversas. Sin embargo, se
realiza un control para garantizar que ninguna otra aplicación necesite un archivo, una clave
de registro o un componente que esté a punto de ser eliminado. Esta verificación permite
asegurar que la eliminación de una aplicación no conlleva problemas de funcionamiento en
otra aplicación.

Windows Installer también gestiona la reparación de una aplicación al reinstalar


automáticamente los archivos que faltan que hubieran podido ser eliminados, por error, a
manos del usuario.

El procedimiento de instalación se efectúa en el interior de una transacción garantizando


que la aplicación será instalada completamente o que, en caso de fallo durante la
instalación, el sistema volverá su estado inicial.

En realidad, los procedimientos de instalación son verdaderas aplicaciones. De hecho, son


gestionados por Visual Studio como proyectos normales.

La versión 2012 de Visual Studio ya no integra la herramienta que permitía crear un


proyecto de instalación. Esto se tiene que hacer a través de una herramienta externa que se
pueda incluir dentro de Visual Basic. Por lo tanto, antes de crear un proyecto de instalación,
vamos a ver cómo obtener esta herramienta e integrarla en Visual Studio.

1. Instalación de InstallShield Limited Edition

La primera etapa consiste en descargar el programa de instalación del producto. Esta


descarga se realizar a través de un enlace a la página web que está disponible en la opción
de menú Archivo - Nuevo proyecto y activando la regla Instalado - Plantillas - Otros
tipos de proyectos - Instalación e implementación - Habilitar InstallShield Limited
Edition.

Aparecerá la siguiente página.

El enlace Vaya al sitio web de descarga le permite acceder al sitio web de la empresa
FLEXERA Software, que garantiza la distribución del software. Debe rellenar el formulario
de inscripción antes de poder descargar el producto. Hay que proporcionar una dirección de

ShareVideos
correo electrónico válida porque se enviará un código a esta dirección para activar el
producto antes de usarlo por primera vez. Puede ejecutar la instalación directamente a partir
del sitio de descarga o copiar localmente el archivo y después ejecutar la instalación a partir
de la copia local. Esta última opción es mejor porque facilita la vuelta atrás en caso de
producirse un incidente, sin tener que descargar de nuevo el archivo. Es preferible que
Visual Studio esté cerrado durante el procedimiento de instalación. En caso contrario, será
necesario un reinicio. Lo primero que hace el programa de instalación es actualizar su
sistema para integrar los componentes que necesita InstallShield.

Después debe aceptar el contrato de licencia y seleccionar el directorio de instalación para


InstallShield, antes de copiar los archivos. Cuando termina la copia de archivos, la siguiente
pantalla le informa del éxito de la instalación.

La instalación todavía no ha terminado completamente, ya que falta la etapa de activación


del producto, que se realizará durante la creación del primer proyecto InstallShield Limited
Edition. Se le pedirá introducir su código de activación o continuar con una versión de
evaluación, limitada en el tiempo.

Si elige la opción de activación, la siguiente pantalla le permite introducir la clave de


producto que se le ha comunicado a la dirección de correo electrónico, cuando se inscribió
para la descarga del producto.

Esta operación de activación necesita una conexión a Internet activa. La instalación de


InstallShield Limited Edition ha terminado y solo falta descubrir su uso durante la creación
de un proyecto de desarrollo.

2. Creación de un proyecto de instalación

El método de creación de un proyecto de instalación es idéntico al utilizado por cualquier


otro tipo de proyecto de Visual Studio. En el menú Archivo, seleccione Agregar, y luego
Nuevo proyecto. En el cuadro de diálogo de Agregar un proyecto, seleccione Otros tipos
de proyecto y después InstallShield Limited Edition Project. El proyecto se añade
entonces a la solución actual. El proyecto para el que desea crear un programa de
instalación y el proyecto InstallShield deben formar parte de la misma solución Visual
Studio.

Un asistente le permite cubrir las diferentes etapas de la configuración del proyecto de


instalación.

ShareVideos
Cada etapa cubre un aspecto concreto del funcionamiento del programa de instalación. Con esta
versión Limited Edition de InstallShield, algunas funcionalidades u opciones están bloqueadas.
Estas se identifican con el icono .

a. Información general

La primera etapa del asistente recoge la información general de la aplicación.

En esta pantalla debe proporcionar la siguiente información

 El nombre de su empresa.
 El nombre de la aplicación.
 El número de versión de la aplicación.
 El sitio web donde está disponible la información adicional de la aplicación.
 El icono asociado a la aplicación.

Esta información se utiliza durante la instalación de la aplicación. También la utiliza la


funcionalidad correspondiente a la opción Desinstalar o cambiar un programa, del panel
de control de Windows.

b. Requisitos previos

La siguiente etapa va a permitir especificar los requerimientos de la aplicación en relación


con la configuración del puesto en el que se va a instalar.

Puede especificar las diferentes versiones de Windows que permiten la instalación de la


aplicación. Si durante la instalación de la aplicación no se detecta en la máquina ninguno de
los sistemas operativos permitidos, se muestra el siguiente cuadro de diálogo y se detiene la
instalación.

Si se respeta la primera condición respecto a la presencia de una versión particular, se


realiza una segunda comprobación para controlar la presencia de una o varias aplicaciones
en el puesto. Como sucede con la primera verificación, si falta alguno de los elementos
necesarios, se muestra un cuadro de diálogo y la instalación termina justo después del cierre
de este cuadro de diálogo. En este caso, no se realiza ninguna modificación en el puesto
cliente.

ShareVideos
La siguiente etapa no es configurable con la version Limited Edition de InstallShield. Por
tanto, pasamos a la etapa número cuatro, que es la más importante del asistente.

c. Archivos de la aplicación

Esta etapa es verdaderamente primordial para el correcto funcionamiento de la aplicación,


ya que define qué se va a instalar y dónde.

Esta pantalla se compone de dos paneles:

 Un panel de navegación a la izquierda.


 Una panel de información a la derecha.

El panel de navegación contiene la lista jerárquica de las carpetas que corresponden al


sistema de archivos del ordenador de instalación. Los nombres de las carpetas corresponden
a las carpetas de Windows estándar. Los tres botones de la pestaña de la derecha permiten
agregar diferentes elementos. En la carpeta de aplicación, puede agregar por ejemplo una
subcarpeta, un archivo o la opción utilizada más a menudo: una salida del proyecto (los
archivos creados por la compilación de un proyecto). En esta etapa, podrá indicar el
proyecto que desea instalar. El siguiente cuadro de diálogo le permite elegir el proyecto y
elementos que quiere instalar en los puestos de los clientes.

Para que la aplicación pueda ejecutarse en el ordenador cliente, se debe seleccionar como
mínimo la opción Resultado principal. Por el contrario, la opción Archivos de código
fuente se utiliza raramente.

d. Accesos directos hacia la aplicación

Para facilitar la ejecución de la aplicación por parte del usuario, es deseable proporcionar
accesos directos que eviten tener que buscar el ejecutable en el árbol del sistema de
archivos. Esto es lo que ofrece la siguiente pantalla del asistente.

Al menos es necesario definir un acceso directo hacia la aplicación en el menú Inicio. Esto
se representa mediante el elemento Launch de la lista. Para cada acceso directo nuevo,
puede elegir su emplazamiento, bien en el menú Inicio o en el Escritorio (o los dos).
Observe que, si se añade una carpeta con archivos ejecutables durante la etapa anterior,
estos accesos directos se crean automáticamente hacia esos ejecutables. Si se trata de
simples herramientas que necesita la aplicación, posiblemente sea mejor eliminarlos para
que no se pueda acceder directamente a ellas.

ShareVideos
e. Información del registro

Esta es la penúltima etapa y le permite especificar las modificaciones que se deben realizar
en el registro durante la instalación de la aplicación. Si no existe ninguna clave en el
registro de la máquina durante la implementación, se añade durante la instalación. Es
posible agregar claves debajo de cualquier clave de nivel superior en el editor del registro.

Para agregar una clave en el registro, previamente debe seleccionar un nodo de nivel
superior, o una subclave, y en el menú contextual escoger la opción New - Key. Se debe
volver a nombrar la clave.

Se puede eliminar una clave simplemente con la opción Delete del menú contextual. Sin
embargo, hay que ser prudente, ya que la supresión de una clave conlleva la supresión de
todas las subclaves y valores contenidos en ella. Se visualiza un mensaje de aviso para
alertarle de esta peligrosa situación y para pedirle que confirme su elección.

También es posible especificar valores para las nuevas claves o modificar los valores
existentes. Podrá añadir valores de tipo cadena, binario DWORD, Multi String y
expandable String. Durante la instalación, estos valores están escritos en el registro. Los
valores existentes son sustituidos por valores específicos en el programa de instalación.

Es posible agregar claves y valores de registro a un proyecto de implementación


importando un archivo de registro (.reg). Esto le permitirá copiar una sección completa de
un registro existente de una sola vez para ganar tiempo. Los archivos de registro pueden
crearse con la ayuda de herramientas como el editor del registro de Windows (regedit.exe).
Esta solución es muy práctica para transferir al puesto de los usuarios una porción de
registro recuperada en el puesto utilizado para el desarrollo de la aplicación. El menú
contextual disponible en el elemento del Registro del ordenador de destino propone la
opción Importar, que permite realizar esta operación. Solo tiene que elegir el archivo
(.reg) que contiene los datos que desea importar.

f. Configuración de los cuadros de diálogo

Durante la instalación de la aplicación, se muestra una sucesión de cuadros de diálogo con


objeto de recoger la información necesaria para la instalación de la aplicación. Esta última
etapa permite actuar sobre la apariencia de estos cuadros de diálogo.

En algunos casos, será necesario proporcionar un archivo externo con la información que se
debe visualizar en el cuadro de diálogo. Por ejemplo, este es el caso para la opción que

ShareVideos
controla la visualización del contrato de licencia de la aplicación, que necesita el archivo
con formato .rft que contiene el contrato de licencia.

Como resultado de estas diferentes etapas, el proyecto de despliegue debe estar


correctamente configurado. Solo falta generar el proyecto con la solución completa.

Implementación con ClickOnce


ClickOnce es una tecnología de implementación que permite crear aplicaciones Windows
cuya actualización puede ser efectuada automáticamente. La instalación de este tipo de
aplicación se efectúa con un mínimo de intervención por parte del usuario. Esta técnica
simplifica la etapa de implementación, que a veces se transforma en un verdadero
rompecabezas. A menudo nos encontramos con los siguientes problemas durante la
implementación de una aplicación:

Actualización de la aplicación

Con un método de despliegue clásico, cuando está disponible una nueva versión de la
aplicación, el usuario debe, en general, reinstalar la aplicación para aprovechar esa
actualización. La tecnología ClickOnce es capaz de facilitar las actualizaciones
automáticamente. En este caso, solo se descargan las partes de la aplicación que han
cambiado, y luego la aplicación completa y actualizada se reinstala automáticamente desde
una nueva carpeta.

Componentes compartidos

Las aplicaciones dependen a menudo de componentes compartidos; de ahí la existencia de


un riesgo de conflicto de versiones. En el caso de una implementación con ClickOnce, cada
aplicación es autónoma y no puede interferir con las otras.

Autorización de seguridad

En general, la instalación de una aplicación con un método clásico exige autorizaciones


administrativas en el puesto de trabajo donde se efectúa la instalación. La implementación
con ClickOnce autoriza a los usuarios que no tienen privilegios administrativos a efectuar
la instalación y solo les atribuye las autorizaciones de seguridad de acceso al código
necesarias para el buen funcionamiento de la aplicación.

A veces, todas estas exigencias han conducido a los desarrolladores a elegir una tecnología
Web en lugar de aplicaciones Windows clásicas, simplemente para beneficiarse de las
facilidades de implementación de este tipo de aplicaciones. La contrapartida de esta
elección se encuentra en una reactividad menor de la aplicación y en una interfaz de usuario
menos elaborada. La tecnología ClickOnce hace que la implementación de aplicaciones
Windows sea tan sencilla como la implementación de aplicaciones Web. Cualquier

ShareVideos
aplicación de consola o de Windows Formes se puede publicar con ClickOnce. Hay tres
técnicas de publicación disponibles:

 A partir de una página Web.


 A partir de una compartición de archivos de red.
 A partir de un soporte como CD-Rom o DVD.

La ejecución de la aplicación dispone de dos variantes. Se puede instalar en el ordenador de


un usuario y ejecutarse incluso si no tiene conexión. También se puede ejecutar únicamente
en modo en línea sin instalar ningún elemento de manera permanente en el ordenador. Las
aplicaciones ClickOnce están aisladas unas de otras, y la instalación o la ejecución de una
aplicación no puede interrumpir aplicaciones existentes. Por defecto, las aplicaciones
ClickOnce se ejecutan en las zonas de seguridad de Internet o de la Intranet. En función de
las necesidades, la aplicación puede exigir autorizaciones de seguridad más elevadas.

La actualización de la aplicación también puede tener varios modos de funcionamiento.


Pueden ser automáticos; en este caso, la aplicación verifica en cada inicio si hay
actualizaciones disponibles, y luego las instala automáticamente. El usuario puede
comprobar de forma manual la existencia de una actualización y decidir o no su instalación.
El administrador puede convertir en obligatoria la instalación de una actualización.

1. Principio de funcionamiento de ClickOnce

El mecanismo de implementación de ClickOnce se basa en dos archivos XML llamados


manifiestos:

 Un manifiesto de aplicación.
 Un manifiesto de implementación.

El manifiesto de aplicación describe la propia aplicación, los ensamblados y los archivos


que la componen, las dependencias, las autorizaciones requeridas para la ejecución y la
ubicación donde están las actualizaciones disponibles.

El manifiesto de implementación describe cómo se implemetará la aplicación, incluyendo


la ubicación del manifiesto de aplicación y la versión de la aplicación que deben ejecutar
los clientes. Estos dos archivos son generados por Visual Studio.

El manifiesto de implementación se copia en la ubicación de la implementación. Esta


ubicación puede ser un servidor Web, un directorio compartido en la red o soportes
móviles, como un CD-Rom. El manifiesto de aplicación y todos los archivos de la
aplicación también se copian en una ubicación de implementación específica en el
manifiesto de implementación. Se pueden copiar estos archivos en la misma ubicación o en
dos ubicaciones distintas. Visual Studio también se encarga de las copias de estos archivos.

Después de la implementación de la aplicación en la ubicación de la implementación, los


usuarios pueden descargar e instalar la aplicación haciendo clic en el icono que representa

ShareVideos
el archivo del manifiesto de implementación disponible en una página Web o en una
carpeta. El usuario solo verá un cuadro de diálogo que le pide confirmar la instalación.

Después de la validación, la instalación continúa y se lanza la aplicación sin otra


intervención. Si la aplicación requiere autorizaciones de ejecución más elevadas, el cuadro
de diálogo pide al usuario que conceda las autorizaciones para que la instalación pueda
proseguir.

La aplicación se añade al menú Inicio del usuario y a la sección Agregar o quitar


programas del Panel de control. A diferencia de otras tecnologías de implementación, no
se añade nada a la carpeta Program Files, en la base de registro o en el escritorio. Además,
no es necesario ningún derecho de administración para la instalación.

Cuando crea una versión actualizada de la aplicación, también debe generar un nuevo
manifiesto de aplicación y copiar los archivos en la ubicación de la implementación, en
general una carpeta similar a la carpeta de la implementación original. También se debe
actualizar el manifiesto para que apunte hacia la ubicación de la nueva versión de la
aplicación.

2. Los diferentes métodos de implementación

Para implementar una aplicación ClickOnce, hay tres estrategias posibles. La estrategia que
elija depende principalmente del tipo de aplicación que va a implementar. Las tres
estrategias son las siguientes:

 Instalación desde la Web o una red compartida.


 Instalación desde un CD-Rom.
 Arranque de la aplicación desde la Web o una red compartida.

Instalación desde la Web o una red compartida

Esta estrategia permite implementar la aplicación en un servidor Web o una red compartida.
Cuando un usuario final desea instalar la aplicación, debe hacer clic en un icono de una
página Web o doble clic en un icono de archivos compartidos. Luego se descarga, instala y
arranca la aplicación en el ordenador del usuario. Algunos elementos se añaden al menú
Inicio y al grupo Agregar o quitar programas en el Panel de control.

Puesto que esta estrategia depende de la conexión de red, funciona de manera óptima para
las aplicaciones implementadas por usuarios con acceso a una red local o una conexión
rápida a Internet.

Instalación desde un CD-Rom

Esta estrategia permite implementar la aplicación en un soporte móvil, como un CD-Rom o


un DVD. Como para la opción anterior, cuando el usuario elige instalar la aplicación, esta

ShareVideos
será instalada, lanzada y algunos elementos se agregarán al menú Inicio y al grupo
Agregar o quitar programas en el Panel de control.

Esta estrategia funciona mejor en el caso de aplicaciones implementadas en ordenadores de


usuarios sin conexión a la red o cuya conexión a Internet es lenta. Como la aplicación está
instalada a partir de un soporte móvil, no es necesaria ninguna conexión para la instalación;
sin embargo, sí lo es para las actualizaciones de la aplicación.

Arranque de la aplicación desde la Web o una red compartida

Esta estrategia es similar a la primera, excepto por que la aplicación actúa como una
aplicación Web. La aplicación se ejecuta cuando el usuario hace clic en un hipervínculo de
una página Web (o doble clic en un recurso compartido). Cuando los usuarios cierran la
aplicación, esta ya no está disponible en el ordenador local. Ningún elemento se añade al
menú Inicio o al grupo Agregar o quitar programas en el Panel de control.
Técnicamente, la aplicación se descarga e instala en un caché de la aplicación del
ordenador local, de la misma manera que una aplicación Web se descarga en el caché Web.
Como para el caché Web, los archivos son eliminados del caché de la aplicación al final de
la utilización. Sin embargo, el usuario tiene la impresión de que la aplicación se ejecuta
desde la Web o la red compartida.

Esta estrategia es preferible para las aplicaciones que se utilizan poco.

3. Las actualizaciones de la aplicación

ClickOnce puede facilitar automáticamente las actualizaciones de la aplicación. Una


aplicación ClickOnce lee periódicamente su archivo manifiesto de implementación para
comprobar si las actualizaciones de la aplicación están disponibles. Si está disponible, la
nueva versión de la aplicación se descarga y ejecuta. Por razones de eficacia, solo se
descargan los archivos modificados.

Hay tres estrategias básicas posibles para las actualizaciones:

 La verificación de las actualizaciones durante el arranque de la aplicación.


 La verificación de las actualizaciones después del arranque de la aplicación
(ejecutada en un thread de segundo plano).
 La presentación de una interfaz de usuario destinada a las actualizaciones.

También se puede determinar la frecuencia de verificación de las actualizaciones que debe


efectuar la aplicación o configurar una actualización obligatoria. Las actualizaciones de
aplicación exigen una conexión a la red. En ausencia de una conexión, la aplicación se
ejecuta sin verificar las actualizaciones, sea cual sea la estrategia de actualización elegida.

Verificación de las actualizaciones después del arranque

ShareVideos
Por defecto, la aplicación intenta localizar y leer el archivo manifiesto de implementación
en segundo plano durante su ejecución. Si una actualización está disponible, se invitará al
usuario a descargar e instalar la actualización durante la próxima ejecución.

Esta estrategia se adapta particularmente a las conexiones de banda ancha restringida o a las
aplicaciones voluminosas que puedan necesitar descargas largas.

Verificación de las actualizaciones durante el arranque

Con esta estrategia, la aplicación intenta localizar y leer el archivo manifiesto de


implementación en cada lanzamiento. Si una actualización está disponible, se descargará y
ejecutará. En caso contrario, se ejecutará la versión existente de la aplicación.

Esta estrategia se adapta bien a las conexiones de banda ancha. El plazo necesario para
iniciar la aplicación puede ser inaceptable en conexiones que no sean de banda ancha.

Actualizaciones obligatorias

A veces es necesario obligar a los usuarios a ejecutar una versión actualizada de la


aplicación si, por ejemplo, se ha modificado un recurso que pueda perturbar el
funcionamiento de su antigua versión. En este caso, puede marcar la actualización como
obligatoria y, por lo tanto, impedir la ejecución de una versión más antigua de la aplicación.
Se debe asociar esta estrategia con la verificación de las actualizaciones durante el
arranque.

Intervalos de actualización

En el marco de las actualizaciones automáticas, se puede especificar la frecuencia de


verificación de las actualizaciones. Por ejemplo, puede desear una verificación con cada
ejecución de la aplicación, una vez a la semana o una vez al mes. Si ninguna conexión red
está disponible en el momento especificado para la verificación, esta se efectúa con la
próxima ejecución de la aplicación.

Bloqueo de las actualizaciones

También es posible hacer que su aplicación no se actualice nunca. Puede, por ejemplo,
implementar una aplicación sencilla que no necesite ser actualizada, pero utilizar la ventaja
que ofrece ClickOnce para su instalación.

4. Puesta en marcha de la publicación ClickOnce

La publicación de una aplicación con la tecnología ClickOnce está facilitada por un


asistente que permite recoger la mayoría de los datos necesarios para la implementación.
Este asistente está disponible en el explorador de soluciones al elegir la opción Publicar
del menú contextual del proyecto que se va a implementar. Sin embargo, algunas opciones

ShareVideos
de la implementación no son gestionadas por este asistente y deben configurarse
manualmente a través el cuadro de diálogo de las propiedades del proyecto.

La primera etapa del asistente consiste en configurar la ubicación donde se debe hacer la
publicación.

Esta ubicación puede ser:

 Un directorio de la máquina.
 Un directorio compartido en otra máquina indicando una ruta UNC de la siguiente
manera \\nombre de la máquina\nombre del directorio. Es necesario tener la
autorización de escribir sobre la partición para que la publicación se pueda realizar.
 El servidor Web IIS de la máquina en el que previamente se habrá agregado un
directorio virtual para acoger los archivos.
 Un servidor FTP cuya información de conexión debe facilitar usando el cuadro de
diálogo siguiente:

Debe indicar:

 La dirección IP o el nombre del servidor FTP.


 El número del puerto utilizado para contactar con el servidor (en general, 21).
 El directorio del servidor en el que se efectuará la copia de los archivos. Necesitará
tener la autorización de escritura en este directorio.
 Si se protege con un firewall, asegúrese de activar la opción Modo pasivo.
 Si usted se conecta de manera anónima o, en caso contrario, el nombre de usuario y
la contraseña utilizados para la conexión.

La segunda etapa determina cómo van a instalar la aplicación los usuarios.

Las opciones posibles son:

 Desde un sitio Web cuya URL se debe indicar.


 Desde un recurso compartido cuya ruta de acceso UNC deberá especificar. Por
supuesto, los usuarios deben tener derecho de lectura en el recurso compartido. El
derecho de escritura no es obligatorio e incluso muy desaconsejado.
 Desde un CD-Rom o DVD que usted proporcionará. La creación de este soporte no
la realiza el asistente y se debe efectuar con una aplicación de grabación externa.

La última etapa visualiza un resumen de los datos seleccionados y permite lanzar la


publicación con el botón Finalizar.

ShareVideos
Al final de la instalación, se abre una página HTML en la ubicación utilizada durante la
publicación que permite el arranque de la instalación o la ejecución de la aplicación.

Las opciones de implementación más específicas se deben configurar con la sección


Publicar de las propiedades del proyecto. Este cuadro de diálogo reanuda las propiedades
configuradas por el asistente de publicación.

Los botones Archivos de aplicación, Requisitos previos, Actualizaciones y Opciones


permiten dar el último toque a los ajustes.

El botón Archivos de aplicación muestra el siguiente cuadro de diálogo relativo a los


archivos que constituyen la aplicación.

El estado de la publicación de cada archivo se puede configurar con tres valores diferentes:

 Incluir: el archivo estará disponible para los usuarios en el soporte de


implementación.
 Excluir: el archivo no se copia en el soporte de implementación.
 Archivo de datos: el archivo contiene datos necesarios para el correcto
funcionamiento de la aplicación y será incluido en la publicación.

El botón Requisitos previos se utiliza para configurar los elementos necesarios para el
funcionamiento de la aplicación.

Puede optar por crear un programa de instalación para los componentes que requiere el
funcionamiento de la aplicación marcando la casilla Crear programa de instalación para
instalar los componentes necesarios. Se debe elegir los componentes correspondientes en
la lista presentada. También debe indicar desde qué ubicación se instalarán estos
componentes. Hay tres opciones posibles:

 Desde el sitio Web del proveedor del componente.


 Desde la misma ubicación que la utilizada para instalar la aplicación.
 Desde la ubicación indicada.

La configuración de las actualizaciones prevista durante la utilización del asistente puede


ser modificada con el botón Actualizaciones.

ShareVideos
La casilla La aplicación debe buscar actualizaciones especifica que la aplicación debe
verificar la disponibilidad de actualizaciones en el momento de su instalación. Si selecciona
esta opción, las otras opciones estarán disponibles. Permiten elegir el momento en el que
tendrá lugar la verificación de la disponibilidad de una actualización. La opción Antes de
que se inicie la aplicación indica que la aplicación debe verificar la disponibilidad de las
actualizaciones antes del arranque. Esto garantiza que los usuarios conectados a la red
siempre disponen de la versión más reciente de la aplicación. Esta opción puede ralentizar
el arranque de la aplicación en el caso de que existan actualizaciones disponibles. La
opción Después de que se inicie la aplicación planifica la ejecución de la actualización
durante el próximo arranque de la aplicación. La frecuencia de las actualizaciones también
se puede indicar en horas, días o semanas, o bien ejecutarse cada vez que arranca la
aplicación. También puede indicar la ubicación a partir de la cual están disponibles las
actualizaciones, si esta es diferente de la ubicación de instalación.

El último botón sirve para configurar varias opciones de implementación.

Las opciones siguientes están disponibles:

Idioma de publicación

Especifica el idioma (y los parámetros regionales) en el que se publica la aplicación.

Nombre del editor

Especifica el nombre del editor de la aplicación. Si esta zona está vacía, se usará el valor de
la propiedad RegisteredOrganization del ordenador. Si este valor es nulo, se utiliza el
nombre del proyecto utilizado.

Nombre del producto

Especifica el nombre del editor de la aplicación. Si esta zona está vacía, se utiliza el nombre
del ensamblado.

Dirección URL de soporte

Especifica un sitio Web que contiene datos de soporte para la aplicación. La especificación
de esta URL es facultativa. Pero si se utiliza, esta URL aparece en la entrada Agregar o
quitar programas de la aplicación en el Panel de control de Windows.

Implementación de la página Web

Especifica un nombre para la página Web de implementación. El nombre del archivo por
defecto es Publish.htm.

ShareVideos
Generar automáticamente la página Web de implementación después de cada
publicación

Si esta opción está seleccionada, el proceso de publicación genera una página Web de
implementación en cada publicación. Esta opción solo está disponible si se especifica una
página Web de implementación.

Abrir la página Web de implementación después de publicar

Si se selecciona esta opción, la página Web de implementación generada automáticamente


se abre después de la publicación.

Bloquear la aplicación para que no se active mediante una dirección URL

Si esta opción está desactivada, la aplicación se ejecuta automáticamente después de la


instalación. Si está activada, el usuario deberá arrancar la aplicación desde el acceso directo
del programa en el menú Inicio.

Utilizar la extensión de archivo .deploy

Si está opción está seleccionada, el archivo de implementación utiliza la extensión .deploy.


Algunos servidores Web están configurados para bloquear, por razones de seguridad, los
archivos que no suelen estar presentes en un sitio Web. Por ejemplo, los archivos que
llevan las extensiones siguientes se pueden bloquear: .dll, .config, .mdf. Las aplicaciones
Windows suelen contener archivos con algunas de estas extensiones. Si un usuario intenta
ejecutar una aplicación ClickOnce que accede a un archivo bloqueado en un servidor Web,
se produce un error. En vez de desbloquear todas las extensiones del archivo, se publica
cada archivo de aplicación por defecto con una extensión de archivo «.deploy». Si la opción
se utiliza, el servidor Web solo debe estar configurado para desbloquear las tres extensiones
de archivos siguientes:

 .application
 .manifest
 .deploy

Permitir que se pasen los parámetros de la dirección URL a la aplicación

Por defecto, esta opción está desactivada. Si esta opción está activada, la aplicación será
capaz de acceder y tratar los datos de los parámetros de la URL.

En las instalaciones desde CD, el programa de instalación se inicia automáticamente


al insertar el CD

Si esta opción está seleccionada, se añade un archivo Autorun.inf a la raíz del soporte para
las aplicaciones ClickOnce que se instalan desde un CD-Rom o DVD-Rom.

ShareVideos
Comprobar los archivos cargados en un servidor web

Si esta opción está activada, el proceso de publicación descarga cada archivo para verificar
que efectivamente se pueden descargar, y le informará de los archivos que no se pueden
descargar.

Usar el manifiesto de la aplicación para la información de confianza

Cuando esta opción está seleccionada, puede firmar de nuevo el manifiesto de la aplicación
con la ayuda de un certificado que contiene sus propios datos.

ShareVideos

También podría gustarte