Programación de Dispositivos Móviles
Índice. Introducción-----------------------------------------------------------------------------------------------------------------------------------------------------------------3 ¿Qué es un dispositivo móvil?------------------------------------------------------------------------------------------------------------------------móvil?------------------------------------------------------------------------------------------------------------------------------------------3 -----------------3 Historia de los dispositivos di spositivos móviles-----------------------------------------------------------------------------------------------------------------------------------móviles------------------------------------------------------------------------------------------------------------------------------------3 3 Uniad 1. Tipos de dispositivos móviles------------------------------------------------------------------------------------------------------------------------------móviles---------------------------------------------------------------------------------------------------------------------------------------------- ----6 Plataformas y lenguajes-----------------------------lenguajes-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------8 ----------------------------------8 Herramientas de desarrollo--------------------------desarrollo-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------15 ---------------------------------15 Consideraciones sobre el desarrollo. Limitaciones----------------------------------------------------------------------------------------------------------------16 Limitaciones----------------------------------------------------------------------------------------------------------------16 Usos actuales----------------------------------actuales-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------17 -------------------------------------------17 Perspectivas del futuro-------------------------------------------------------------------------------------------futuro----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -----19 Cronograma-----------------------------------------------------------------------------------------------------------------------------------------------------------------20 Unidad 2. Introducción-----------------------------------------------------------------------------------------------------------------------------------------------------------------23 Coneptos básicos de C#----------------------------------------------C#--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -----23 Mostrar números en pantalla-------------------------------------pantalla-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------24 --------------------24 Variables---------------------------------------------------------------------------------------------------------------------------------------------------------------------25 Identificadores--------------------------------------------------------------------------------------------------------------------------------------------------------------27 Comentario------------------------------------------------------------------------------------------------------------------------------------------------------------------27 Datos por el usuario: ReadLine----------------------------------------------------------------------------------------------------------------------------------------28 Tipos de datos básico----------------------------------------------------------------------------------------------------------------------------------------------------29 Tipos de datos caracter-------------------------------------------------------------------------------------------------------------------------caracter--------------------------------------------------------------------------------------------------------------------------------------------- --------- -----32 Secuencia de escape-----------------------------------escape---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --33 Toma de contacto con las cadenas de texto------------------------------------------------------------------------------------------------------------------------34 texto------------------------------------------------------------------------------------------------------------------------34 Los valores booleanos----------------------------booleanos-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------34 -------------------------------------34 If----------------------------------------------------------------------------------------------------------------------------------------If-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------35 ---------------------------------------35 Switch-----------------------------------------------------------------------------------------------------------------------Switch------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------41 41 While--------------------------------------------------------------------------------------------------------------------------------------------------------------------------42 Do…while--------------------------------------------------------------------------------------------------------------------------------------------------------------------43 For-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------45 Sentencia Break--------------------------------Break-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------47 47 Sentencia continue------------------------------------continue----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------48 Sentencia goto---------------------------------------------goto----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------48 48 Array y cadena de texto--------------------------------------------------------------------------------------------------------------------------------------------------49 texto--------------------------------------------------------------------------------------------------------------------------------------------------49 Estructuras o registros----------------------------registros-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------54 -------------------------------------54 Cadena de caracteres--------------------caracteres--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------56 Recorriendo con foreach----------------------------foreach----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------61 ----------------------------------61 Ordenaciones simples---------------------------------simples-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---63 Funciones-----------------------------------------------------------------------------------------------------------------------Funciones-------------------------------------------------------------------------------------------------------------------------------------------------------------------66 --------------------------------------------66 Variables locales y variables globales-------------------------------------------------------------------------------------------------------------------------globales-------------------------------------------------------------------------------------------------------------------------- ------70 Algunas funciones útiles------------------------------------------------------------------------------------------------------------------------------------------útiles------------------------------------------------------------------------------------------------------------------------------------------- ------74 Funciones matemáticas---------------------------------------------------------------------------matemáticas--------------------------------------------------------------------------------------------------------------------------------------------------75 ----------------------------------------------------------------------75
-3-
Programación de Dispositivos Móviles
Recursividad----------------------------------------------------------------------------------------------------------------------------------------------------------------76 Parametros de retorno de “Main”----------------------------------------------------------------------------------------------------------------------------- ---------76 Programación orientada a objetos-------------------------------------------------------------------------------------------------------------------------------------78 Objetos y clases en C#---------------------------------------------------------------------------------------------------------------------------------------------------78 La herencia. Visibilidad---------------------------------------------------------------------------------------------------------------------------------------------------80 ¿Cómo se diseñan las clases?-----------------------------------------------------------------------------------------------------------------------------------------83 La palabra “static”----------------------------------------------------------------------------------------------------------------------------------------------------------83 Constructores---------------------------------------------------------------------------------------------------------------------------------------------------------------84 Polimorfismo y sobrecarga---------------------------------------------------------------------------------------------------------------------------------------- ------86 Array de objetos------------------------------------------------------------------------------------------------------------------------------------------------------------88 Funciones virtuales. La palabra “override”---------------------------------------------------------------------------------------------------------------------------90 Llamada a un método de la clase “padre”----------------------------------------------------------------------------------------------------------------- ----------94 Sobrecarga de operadores----------------------------------------------------------------------------------------------------------------------------------------- -----95 Windows Mobile 5.0 (aplicable a Windows Mobile 6.5)----------------------------------------------------------------------------------------------------------96 Uso de Visual Studio 2005------------------------------------------------------------------------------------------------------------------------------------- ---------96 Controles en Compact Framework----------------------------------------------------------------------------------------------------------------------------- -------102 Trabajo con los controladores---------------------------------------------------------------------------------------------------------------------------------- --------107 Trabajando con imágenes------------------------------------------------------------------------------------------------------------------------------------------- ----113 ADO.NET – Acceso a Datos--------------------------------------------------------------------------------------------------------------------------------------------122 SQL Server 2005 Mobile Edition---------------------------------------------------------------------------------------------------------------------------------------123 Captura de datos-----------------------------------------------------------------------------------------------------------------------------------------------------------150 Insertar datos------------------------------------------------------------------------------------------------------------------------------------------------------- --------152 Cambiar datos---------------------------------------------------------------------------------------------------------------------------------------------------------- ----154 Borrar datos--------------------------------------------------------------------------------------------------------------------------------------------------------------- --156 Sincronización de datos--------------------------------------------------------------------------------------------------------------------------------------------------158 Empaquetado y desplegado----------------------------------------------------------------------------------------------------------------------------------- ---------169 Bibliografia-------------------------------------------------------------------------------------------------------------------------------------------------------------------175
-4-
Programación de Dispositivos Móviles
Introducción. Desde la aparición de la primera computadora comercial en 1980, el hombre ha intentado llevar consigo toda la información posible que le ayude a resolver sus problemas conforme se le vayan presentando durante el día, pero en ese tiempo era imposible por no contar con la tecnología necesaria para ello.
¿Qué es un dispositivo móvil? Los dispositivos móviles (también conocidos como computadora de mano, «Palmtop» o simplemente handheld) son aparatos de pequeño tamaño, con algunas capacidades de procesamiento, móviles o no, con conexión permanente o intermitente a una red, con memoria limitada, diseñados específicamente para una función, pero que pueden l levar a cabo otras funciones más generales. Normalmente, son versiones limitadas en prestaciones, y por tanto en funcionalidades, de las computadoras portátiles o de sobremesa. Por cierto, las computadoras portátiles no se consideran como dispositivos móviles, ya que consumen más batería y suelen ser un poco más pesados de lo que se espera de algo pensado para llevar siempre encima. Un dispositivo móvil, entonces, es todo aquel aparato que pueda contener información, tenga un sistema operativo y haga una función en particular en la que se necesita almacenar, consultar y manipular información que se encuentre en el y que además sea portable y de un tamaño muy reducido.
Historia de los dispositivos móviles El primer dispositivo móvil, para muchos, es la Newton, desarrollada y comercializada por Apple, y que estuvo a la venta entre 1993 y 1998. La Newton era un dispositivo revolucionario para su tiempo, que implementaba un sistema de reconocimiento de escritura y que podía sincronizarse con una computadora de sobremesa (de Apple, claro está). Fue tan revolucionaria, y se adelantó tanto a su tiempo, que fue un fracaso comercial, por lo que terminó retirándose del mercado.
Panel de control de la Newton Pero aunque la Newton pueda considerarse como la primera PDA, desde luego no fue el primer dispositivo portátil programable. Durante los años 80, tanto Casio como Hewlett-Packard desarrollaron y comercializaron varias calculadoras programables, que si bien no tenían la capacidad de sincronizar sus datos con una computadora de sobremesa, sí tenían capacidades gráficas, y accesorios que tal vez puedan sonar extravagantes ahora, como impresoras, o tarjetas de memoria extraíbles de 1Kb (sí, un "k"). Sirva como ejemplo la Casio FX-750P, de la que el autor de este texto conserva un ejemplar que funciona desde el año 1984, que tenía una pantalla de una sola línea, con 32 caracteres de 5x7 puntos, teclado QWERTY y teclado numérico, 4 KB de RAM, dos ranuras para tarjetas de memoria extraíbles de hasta 2 KB, y puerto serie por el que conectarla a una impresora. Y era programable en BASIC. ¿Especificaciones de risa, verdad? No tanto, si se tiene en cuenta que la configuración CLDC de J2ME sólo obliga a que el teléfono tenga 4KB de memoria, y que muchos de los móviles pre-Symbian tienen una memoria de 32 KB. Más adelante veremos con un poco más de detalle qué es eso de J2ME, y qué es la configuración CLDC, pero por ahora basta con saber que es la especificación que deben cumplir los móviles programables en java.
Casio FX-750P Las calculadoras programables de Casio rivalizaban en prestaciones con las de Hewlett-Packard. Si la FX-750P podría considerarse como el buque insignia de la marca japonesa, la serie HP48 lo era para los californianos. Las calculadores de esta serie, que se dividían en dos ramas, la S para los modelos estándar y la G para los de mayor funcionalidad, estuvo en producción entre los años 1990 y 2003. Las especificaciones comunes a todos los modelos de la gama eran una pantalla de 131x63 píxeles, un puerto de comunicaciones por infrarrojos y otro serie de 4 pines, y 512 KB de memoria. El modelo más alto de la gama, la HP 48GX, soportaba dos tarjetas de expansión de memoria, de forma que se podía llegar a acumular un total de 5MB.
HP 48G Tras la muerte de la Newton, nacieron los dos dispositivos que durante unos años dominaron el mercado: la Pilot y el PocketPC. Los dispositivos de Palm se adelantaron a los de Microsoft en un par de años. En parte por ello, su salida a producción fue un éxito de ventas, llegando incluso a acumular, en 2001, un 60 por ciento del mercado americano. Sin embargo, Palm se resintió del declive global del mercado de las PDA, comenzando un declive que coincidió prácticamente en el tiempo con la entrada en el mercado del gigante de la informática de consumo: Microsoft.
-5-
Programación de Dispositivos Móviles
Grafitti, el sistema de reconocimiento de caracteres de Palm OS, obligaba a escribir de forma determinada. En el año 2000 vio la luz el primer PocketPC, el hijo de la Newton, y que al contrario que ésta, sí ha sobrevivido hasta hoy, pese a no ser un éxito comercial de grandes dimensiones, gracias a la continuidad y el apoyo decidido de Microsoft. Los primeros PocketPC tenían como sistema operativo el llamado Windows CE 3.0. Por su nombre podría parecer que era una versión aligerada del sistema operativo más utilizado en el mundo, pero en realidad no tenía mucho que ver con éste.
Compaq iPaq 3630, el primer PocketPC de éxito La mayor razón para el éxito del PocketPC ha sido su relativa sencillez de manejo y su integración con computadoras de escritorio basados en Windows. La integración es tal que para realizar una sincronización entre ambos basta con conectar el PocketPC a la computadora con un cable. Actualmente, los PocketPC y las Palm tienen pantallas de resolución VGA, en prácticamente todos los casos incorporan protocolos de comunicaciones inalámbricos, como Bluetooth o Wifi, o unidades de GPS. Por prestaciones, son, sin duda, los hermanos mayores del mundo de la movilidad. Pero si las PDAs son las reinas en lo referente a prestaciones, en cuanto a unidades en el mercado, el rey indiscutible es el teléfono móvil. ¿Quién no tiene un teléfono móvil? En realidad, lo raro es que haya quien no tenga un teléfono totalmente funcional guardado en un cajón, porque se ha comprado otro que, en el plazo de unos meses, multiplicaba por diez o por cien las prestaciones de su antiguo terminal. Si la Newton, la Palm y el PocketPC han sido los que han abierto el camino, los que realmente han entrado con toda la fuerza posible a ocupar ese mercado han sido los teléfonos móviles. Pero ¿cuál es el motivo o los motivos para que haya tantos teléfonos? Es difícil de decir con total seguridad, pero se puede resumir principalmente en tres.
Motorola MicroTAC En primer lugar, el precio. Se pueden conseguir terminales por cero euros en ocasiones excepcionales, como al darse de alta con algún operador, pero en todo caso los precios de los teléfonos de gama baja suele ser bastante asequible. El segundo factor es más complejo, pero puede resumirse en “mira lo moderno que soy, qué móvil tengo". Hay quien lo llama techno-lust , hay quien lo llama “ganas de aparentar o de chulear a las amistades”, pero básicamente se resume en que todo el mundo quiere tener el
-6-
Programación de Dispositivos Móviles
móvil más moderno y con más funcionalidades, sobre todo si es más moderno y hace más cosas que el del vecino. Esa obligación por tener el dispositivo más moderno viene motivada también en parte por la continua mejora de la tecnología. Cada vez los móviles pueden hacer más cosas, y eso crea una doble necesidad. Por un lado, es necesario tener un dispositivo que permita hacer esas cosas, sean las que sean (grabar vídeo, escuchar mp3, ver los goles de tu equipo favorito en tiempo real). Pero eso también provoca que cada vez se intenten hacer más cosas con el teléfono, lo que vuelve a hacer necesario un terminal más potente, que a su vez creará la necesidad de contenidos de más calidad, lo que a su vez... Esto supone nuestro tercer factor: un círculo vicioso para el usuario pero que beneficia por igual a los fabricantes y a las operadoras. Sin embargo, los móviles que verdaderamente pueden considerarse equivalentes a las PDAs son los de gama alta, o smartphones. El término smartphone es engañoso. Su traducción literal sería “teléfonos inteligentes”, y se utiliza indiscriminadamente para hacer referencia a cualquier teléfono de gama alta, englobando tanto a dispositivos de la Serie 60 o superior de Symbian, como a los que funcionan bajo Windows Mobile o bajo Palm OS. De todas formas, la marca comercial SmartPhone es propiedad de Microsoft.
Sony Ericsson P910i Porque, efectivamente, hay una línea de teléfonos que funcionan bajo Windows Mobile. Por un lado, los PocketPC Phone Edition, que son híbridos de teléfono y PocketPC y por otro los llamados comercialmente SmartPhones. La diferencia entre ambos está, fundamentalmente, en la pantalla táctil. Los PocketPC Phone Edition tienen, mientras que los SmartPhones no, y que se manejan de forma similar a los móviles Symbian de gama alta. Tanto PocketPC Phone Edition como PocketPC se engloban dentro de la gama Windows Mobile de Microsoft.2 También Palm mantiene su irreductible nicho de mercado dentro de los teléfonos de gama alta: la serie Treo. Los Treo son híbridos de teléfono y PDA, con teclado QWERTY, y funcionan, salvo uno de los modelos (en concreto el Treo 700w, que utiliza PocketPC Phone Edition) bajo PalmOS.
Treo 700w En todo caso, el mercado de los teléfonos de gama alta estaba copado, hasta hace muy p oco, por los modelos con sistema Symbian. Sin embargo, en los últimos dos años hemos asistido a la i rrupción del representante por excelencia del “techno-lust” el iPhone de Apple. Si Apple fue la que abrió el camino con la Newton, es el que ha vuelto a revolucionar el mercado con el iPhone, en parte porque ha sido el único fabricante que realmente ha conseguido desarrollar un dispositivo que integrara a todos los anteriores existentes por separado (teléfono, reproductor mp3 y PDA), reinventando la forma en la que se interactúa con él.
-7-
Programación de Dispositivos Móviles
Unidad 1
Manejo del entorno de desarrollo móvil El alumno hará uso de la tecnología móvil, herramientas de la plataforma y técnicas necesarias que permitan la construcción e implementación de desarrollo de programación de aplicaciones para ejecutarse en dispositivos móviles.
1.1 Diferencia las características de los dispositivos móviles y la plataforma de desarrollo con base en tecnologías y herramientas para aplicaciones móviles. Tipos de dispositivos móviles. Dado el variado número de niveles de funcionalidad asociado con dispositivos móviles, era necesario hacer una clasificación de los mismos, por ello en el 2005, T38 y DuPont Global Mobility Innovation Team propusieron los siguientes estándares para la definición de dispositivos móviles. •
•
•
Dispositivo Móvil de Datos Limitados (Limited Data Mobile Device): teléfonos móviles clásicos. Se caracterizan por tener un pantalla pequeña de tipo texto. Ofrecen servicios de datos generalmente limitados a SMS y acceso WAP. Dispositivo Móvil de Datos Básicos (Basic Data Mobile Device): se caracterizan por tener una pantalla de mediano tamaño, menú o navegación basada en iconos, y ofrecer acceso a emails, lista de direcciones, SMS, y, en algunos casos, un navegador web básico. Un típico ejemplo de este tipo de dispositivos son los teléfonos inteligentes (“smartphones”). Dispositivo Móvil de Datos Mejorados (Enhanced Data Mobile Device): se caracterizan por tener pantallas de medianas a grandes (por encima de los 240 x 120 pixels), navegación de tipo stylus, y que ofrecen las mismas características que el "Dispositivo Móvil de Datos Básicos" (Basic Data Mobile Devices) más aplicaciones nativas como aplicaciones de Microsoft Office Mobile (Word, Excel, PowerPoint) y aplicaciones corporativas usuales, en versión móvil, como Sap, portales intranet, etc. Este tipo de dispositivos incluyen los S.O. como Windows Mobile.
A grandes rasgos, y dependiendo del tamaño los dispositivos se pueden dividir en tres clases.
1.- Teléfonos. Son los más pequeños de la casa, y por tanto los más ligeros y más transportables. En general, también son los más baratos, aunque un teléfono de gama alta puede superar en precio a muchos de sus hermanos mayores, las PDAs. Su función primordial era clara: recibir y realizar llamadas; aunque parece que dentro de poco va a comenzar a ser complicado encontrar teléfono que sirvan para eso. Funcionalidades propias de computadoras, o de dispositivos de otro tipo, como la grabación y edición de vídeo, realización de fotografías, lectura de documentos, localización en mapas, navegación por Internet, y muchas cosas más, son no sólo habituales, sino esperadas en cualquier teléfono moderno. El teléfono móvil es un dispositivo inalámbrico electrónico basado en la tecnología de ondas de radio, que tiene l a misma funcionalidad que cualquier teléfono de línea fija. Su principal característica es su portabilidad, ya que la realización de llamadas no es dependiente de ningún terminal fijo y no requiere ningún tipo de cableado para llevar a cabo la conexión a la red telefónica. Aunque su principal función es la comunicación de voz, como el teléfono convencional, su rápido desarrollo ha incorporado funciones adicionales como mensajería instantánea (sms), agenda, juegos, cámara fotográfica, agenda, acceso a Internet, reproducción de video e incluso GPS y reproductor mp3.
Motorola DynaTAC 8000X, uno de los primeros teléfonos móviles fabricados en gran cantidad. La evolución del teléfono móvil ha permitido disminuir su tamaño y peso, desde el Motorola DynaTAC, el primer teléfono móvil en 1983 que pesaba 780 gramos, a los actuales más compactos y con mayores prestaciones de servicio. Además a lo largo de estos años se ha llevado a cabo el desarrollo de baterías más pequeñas y de mayor duración, pantallas más nítidas y de colores, la incorporación de software más amigable.
-8-
Programación de Dispositivos Móviles
Inicialmente los teléfonos móviles sólo permitían realizar llamadas de voz y enviar mensajes de texto. Conforme la tecnología fue avanzando se incluyeron nuevas aplicaciones como juegos, alarma, calculadora y acceso WAP (acceso a Internet mediante páginas web especialmente diseñadas para móviles).
“Smartphones” o teléfonos inteligentes. Un “smartphone” (teléfono inteligente en español) es un dispositivo electrónico que funciona como un teléfono móvil con características similares a las de una computadora personal. Es un elemento a medio camino entre un teléfono móvil clásico y una PDA ya que permite hacer llamadas y enviar mensajes de texto como un móvil convencional pero además incluye características cercanas a las de una computadora personal. Una característica importante de casi todos los teléfonos inteligentes es que permiten la instalación de programas para incrementar el procesamiento de datos y la conectividad. Estas aplicaciones pueden ser desarrolladas por el fabricante del dispositivo, por el operador o por un tercero. Los teléfonos inteligentes se distinguen por muchas características, entre las que destacan las pantallas táctiles, un sistema operativo así como la conectividad a Internet y el acceso al correo electrónico. El completo soporte al correo electrónico parece ser una característica indispensable encontrada en todos los modelos existentes y anunciados en 2007, 2008 y 2009. Otras aplicaciones que suelen estar presentes son las cámaras integradas, la administración de contactos, el software multimedia para reproducción de música y visualización de fotos y video-clips y algunos programas de navegación así como, ocasionalmente, la habilidad de leer documentos de negocios en variedad de formatos como PDF y Microsoft Office. Una característica común a la mayoría de “smartphones” es una lista de contactos capaz de almacenar tantos contactos como la memoria libre permita, en contraste con los teléfonos clásicos que tienen un límite para el número máximo de contactos que pueden ser almacenados. Casi todos los teléfonos inteligentes también permiten al usuario instalar programas adicionales.
2.- PDAs. Organizadores electrónicos o computadoras de mano. Su nombre (PDA) significa Personal Digital Assistant (asistente personal digital), un término acuñado en sus primeros años de historia, pero que resume bien su funcionalidad principal, que es servir como organizadores, con agenda, calendario, gestión de contactos, y que posteriormente han ido creciendo, de forma que actualmente sirven tanto como aparatos en los que leer un libro como en l os que encontrarse en un mapa. La línea que los separa de los teléfonos es cada vez más difusa.
Apple Newton MessagePad 2000 Inicialmente los PDAs incluían aplicaciones estrictamente relacionadas con su función como agenda electrónica, es decir, se reducían a calendario, lista de contactos, bloc de notas y recordatorios. Con el paso de tiempo han ido evolucionando hasta los dispositivos actuales que ofertan un rango mucho más extendido de aplicaciones, como juegos, acceso al correo electrónico o la posibilidad de ver películas, crear documentos, navegar por Internet o reproducir archivos de audio. Las características del PDA moderno son pantalla sensible al tacto, conexión a una computadora para sincronización, ranura para tarjeta de memoria, y al menos Infrarrojo, Bluetooth o WiFi. La irrupción de Microsoft Windows CE (2000) y Windows Mobile (2003) en el sector los dotó de mayores capacidades multimedia y conectividad. Las PDAs de hoy en día traen multitud de comunicaciones inalámbricas (Bluetooth, WiFi, IrDA, GPS,) que los hace tremendamente atractivos hasta para cosas tan inverosímiles como su uso para domótica o como navegadores GPS.
3.- Consolas. En realidad esta categoría debería llamarse “dispositivos orientados a jugar”, porque son más que simples consolas. Los dos ejemplos actualmente en el mercado son la Sony PlayStation Portable (PSP) y la Nintendo DS, que no sólo sirven para jugar, sino que integran algunas de las funcionalidades típicas de una PDA, como reproducción de archivos multimedia, integración con agenda y calendario, o navegador de Internet.
PSP
-9-
Programación de Dispositivos Móviles
El mercado del videojuego portátil ha sufrido muchas transformaciones a lo largo de su historia. Partiendo de videojuegos electrónicos portátiles y experimentos poco exitosos de Videoconsolas portátiles a finales de los 70 y principios de los 80, pero que sentaron las bases de las futuras generaciones portátiles y el nuevo milenio donde las consolas portátiles son verdaderos centros multimedia. El sector de los videojuegos portátiles ha tenido dos épocas álgidas, en torno a la cuarta generación de consolas de sobremesa cuando las portátiles suponían un 17% del mercado de los videojuegos, y vive su máximo auge en la séptima generación de consolas suponiendo el 56% del mercado del videojuego de esa generación.
Plataformas y lenguajes soportados. Cada una de las plataformas tiene sus particularidades, no sólo en cuanto al manejo del dispositivo por el usuario, sino también a la hora de desarrollar aplicaciones para las mismas. No será lo mismo programar una aplicación para Windows Mobile que para Symbian, entre otras cosas porque no todas las plataformas soportan los mismos lenguajes de programación. Partiendo de la definición de sistema operativo: Capa compleja entre el hardware y el usuario, concebible también como una máquina virtual, que facilita al usuario o al programador las herramientas e interfaces adecuadas para realizar sus tareas informáticas, abstrayéndole de los complicados procesos necesarios para llevarlas a cabo. Podemos deducir que el uso de uno u otro S.O determinarán las capacidades multimedia de los dispositivos, y la forma de éstas de interactuar con el usuario. Existen multitud de opciones, si bien las más extendidas son Symbian, BlackBerry OS, Windows Mobile, y recientemente iPhone OS y el sistema móvil de Google, Android, además por supuesto de los dispositivos con sistema operativo Linux. Las características básicas de cada uno son las siguientes:
Windows Mobile Microsoft lanzó su propio Windows para móviles, antes conocido como Windows CE o Pocket PC, tiene una larga historia como segundón en el campo de los PDA o computadoras de bolsillo, sin embargo hace pocos meses superó por primera vez al hasta entonces líder, Palm OS. Windows Mobile es un sistema operativo escrito desde 0 y que hace uso de algunas convenciones de la interfaz de usuario del Windows de siempre. Una de las ventajas de Windows Mobile sobre sus competidores es que los programadores pueden desarrollar aplicaciones para móviles utilizando los mismos lenguajes y entornos que emplean con Windows para PC. En comparación, las aplicaciones p ara Symbian necesitan más esfuerzo de desarrollo, aunque también están optimizadas para cada modelo de teléfono.
Pantalla de inicio típica de Windows Mobile 6 Este sistema se pretende vender como una versión muy aligerada de Windows, pero en realidad no tiene mucho que ver con él. El paradigma de funcionamiento es similar, basado en ventanas, aunque éstas se comportan de forma muy diferente a como lo hacen en su hermano mayor de escritorio. Por ejemplo, al cerrar la ventana de un programa éste no se cierra realmente, sino que se sigue ejecutando como si se hubiera minimizado. El punto fuerte de estos dispositivos es que ofrecen funcionalidades similares a las de sus hermanos mayores, Por ejemplo, se pueden editar documentos de word, hojas de cálculo de excel, leer libros en formato pdf o chm, recibir y enviar correo electrónico, manejar una agenda, la libreta de contactos, sincronizar datos con el PC, navegar por internet, utilizar un GPS... en definitiva, casi lo mismo que en un PC, pero sin teclado y con la pantalla bastante pequeña.
- 10 -
Programación de Dispositivos Móviles
Versión para Windows Mobile de Microsoft Word La entrada de datos se realiza a través de la pantalla, que es táctil, y gracias al sistema de reconocimiento de escritura que implementan, que permite trabajar de una forma bastante natural y rápida. La mayoría de las aplicaciones para estos dispositivos se desarrollan en .Net, la plataforma de desarrollo de Microsoft, o directamente en C++, aunque aún quedan, como reminiscencia de sus inicios como Windows CE, algunas aplicaciones escritas en Embedded C++ o Embedded Basic, que eran dos entornos de desarrollo basados respectivamente en C++ y Basic, pero con muchas limitaciones. Pero en la actualidad casi todos los desarrollos para Windows Mobile se realizan en C++ o en .Net, basándose en el Compact Framework. El principal problema a la hora de desarrollar aplicaciones para estos dispositivos es el precio de las herramientas de desarrollo. Estas herramientas (Visual Studio, un entorno de desarrollo que soporta varios lenguajes como C#, C++, J#, JScript o ASP .Net) son de altísima calidad, pero de un precio que no está al a lcance de todos los que se quieran aventurar a escribir una aplicación para PocketPC. Por otra parte, el hecho de que Microsoft esté detrás de la plataforma, dándole todo su apoyo, se nota en la cantidad y la calidad de la documentación disponible para los desarrolladores. Newsletters, una sección sólo para dispositivos en el MSDN, blogs de los ingenieros de Microsoft que trabajan en Windows Mobile, actualizaciones de la documentación en DVDs periódicos... en resumen, mucho y muy bueno. Tal vez por eso mismo, y pese al precio del entorno de desarrollo, la competencia es muy alta.Hay muchas compañías que producen aplicaciones para Windows Mobile, y que invierten mucho dinero en su desarrollo. No es, por tanto, un nicho de mercado en el que sea sencillo introducirse. En cualquier caso, siempre que se vaya a desarrollar aplicaciones para esta plataforma, deben tenerse en cuenta las limitaciones específicas de la misma, sobre todo a la hora de intentar desarrollar interfaces que sean lo más point-and-click posibles, y que necesiten de la menor introducción de textos posible.
Symbian Si los PocketPC están basados en un sistema operativo de Microsoft llamado Windows Mobile, la gran mayoría de los teléfonos móviles funcionan gracias a un sistema operativo llamado Symbian. Hace unos años, a estos teléfonos se les llamaba con el nombre genérico de SmartPhones. Este es el sistema operativo para móviles más extendido entre “smartphones”, y por tanto el que más aplicaciones para su sistema tiene desarrolladas. Actualmente Symbian copa más del 65% del mercado de sistemas operativos. Symbian es un consorcio en el que participan los mayores fabricantes de teléfonos móviles, con Nokia a la cabeza. Sony Ericsson, BenQ (que hace poco absorbió a Siemens), Fujitsu, Lenovo, Motorola, Panasonic, Samsung o Sharp están representados en Symbian, y son por tanto, partícipes en el desarrollo y la expansión del sistema. De todos estos fabricantes, el que mayor cuota de mercado tiene es Nokia (que con algunas fluctuaciones, viene siendo desde hace años de alrededor del 40%), que además es el fabricante que primero apostó por este sistema operativo, y el que más uso hace de él. Por tanto, a partir de ahora, al referirme a teléfonos Symbian, lo haré sobre todo a teléfonos Nokia. Symbian es un sistema operativo escrito en C++, por lo que presenta muy bajo consumo de recursos del dispositivo, a la vez que se ejecuta con gran rapidez. El sistema operativo Symbian se presenta en varios "sabores". En concreto, Nokia divide sus dispositivos Symbian en tres familias, que se llaman respectivamente Serie 40, Serie 60 y Serie 80. La Serie 40 es la que agrupa a los teléfonos Symbian con pantallas más pequeñas (en general, hasta de 240x320 píxeles), y tiene ciertas limitaciones sobre las otras series, sobre todo en lo referente a la cantidad de recursos del teléfono que puede poner a disposición de las aplicaciones que se ejecuten sobre él.
- 11 -
Programación de Dispositivos Móviles
La Serie 60 es la más extendida, y es donde suelen estar los teléfonos de gama media y alta de Nokia, a los que muchas veces se hace referencia como teléfonos multimedia. Con alguna excepción, suelen tener pantallas más grandes que los de la serie 40, y suelen ser teléfonos específicamente optimizados para ejecutar aplicaciones J2ME. Su principal virtud es la capacidad que tiene el sistema para adaptar e integrar todo tipo de aplicaciones. Admite la integración de aplicaciones y, como sistema operativo, ofrece las rutinas, los protocolos de comunicación, el control de archivos y los servicios para el correcto funcionamiento de estas aplicaciones. La tecnología del sistema operativo Symbian se ha diseñado teniendo en cuenta puntos clave como el poder proporcionar la energía, memoria y gestión de entrada y salida de recursos requeridos específicamente en los dispositivos móviles. También, supone una plataforma abierta, ésta es la clave, que a úna telecomunicaciones y los estándares globales de internet. Los usuarios de Symbian señalan como principal ventaja del sistema el hecho de que exista una amplia selección de aplicaciones disponibles para todo tipo de teléfonos móviles. Destacan también la compatibilidad con los estándares de conectividad y redes como Bluetooth, WiFi, GSM, GPRS, CDMA y WCDMA.
Captura de una interfaz Series 60 La Serie 80, finalmente, es la de los llamados Communicators, esos teléfonos que se abren longitudinalmente y que esconden teclados QWERTY. En realidad son un paso intermedio entre el teléfono y la PDA. Aparte de la implementación que hace Nokia de Symbian, Sony Ericsson implementa otro sabor más de ese sistema, llamado UIQ, que se maneja por teclado o a través de una pantalla táctil. Estos dispositivos suelen implementar sistemas de reconocimiento de escritura, y los más conocidos son, como he dicho, la serie P de Sony Ericsson. Actualmente, en el mercado de los teléfonos móviles Symbian es el sistema más extendido, aunque no el único. La mayoría de los fabricantes siguen implementando sus propios sistemas, por lo que sigue sin haber una uniformidad o coherencia entre dispositivos.
Symbian UIQ Las tres grandes posibilidades tecnológicas a la hora de desarrollar aplicaciones para teléfonos móviles son C++, J2ME y Flash Lite.
C++ Es el lenguaje de programación por excelencia para aplicaciones que necesitan extraer el máximo del terminal, tanto en capacidad de procesamiento y por lo tanto en velocidad de ejecución, como en utilizar las posibilidades de hardware que ofrezca el dispositivo. El ejemplo más claro de aplicación candidata a ser realizada en C++ sería un juego de conducción, con capacidades multiusuario a través de bluetooth. Es ideal, por tanto, para aplicaciones críticas (como por ejemplo, sistemas operativos para teléfonos como Symbian). C++ es un lenguaje de programación, de los que se llaman orientados a objetos, que en realidad es una evolución del lenguaje más utilizado en la historia de la informática: el lenguaje C. Desarrollar en C++ es bastante complicado para casi cualquier programador. Hacen
- 12 -
Programación de Dispositivos Móviles
falta, por tanto, programadores de un perfil muy especializado o muy alto, y el lenguaje en sí no hace posible el desarrollo de aplicaciones con gran rapidez, con lo que los proyectos desarrollados en C++ suelen ser más caros que aquellos producidos con otros lenguajes.
J2ME J2ME es un subconjunto del lenguaje Java. ¿Pero qué es el lenguaje Java? Es un lenguaje de programación desarrollado a principios de los años noventa por James Gosling y algunos de sus colegas de Sun Microsystems. Las principales características de este lenguaje son, por un lado, que es de los llamados orientados a objetos, lo que supuestamente permite desarrollar aplicaciones más complejas con mayor facilidad, y por otro, que fue diseñado para ser independiente de la plataforma, lo que supuso una gran novedad en su momento. Sin embargo, el caso de J2ME es especial, ya que es de todo, menos independiente de la plataforma. La especificación J2ME está dividida en dos grandes grupos, dependiendo de la cantidad de funcionalidades para las que se quiera dar soporte. Esos grupos son las llamadas Configuraciones (en toda la documentación sobre J2ME se suele hacer referencia a ellas por su nombre en inglés: Configurations). Hay dos configuraciones, que dividen la plataforma en dos grandes grupos de dispositivos.
El mapa con las configuraciones y perfiles J2ME. ¿Lioso? Por un lado, los dispositivos más potentes, son los que soportan la Configuración CDC. Durante mucho tiempo en este grupo sólo cabían las PDAs (dispositivos como la Palm, por ejemplo), dadas las exigencias de memoria y tamaño de pantalla para cumplir con la especificación. La otra configuración, la llamada CLDC, es la que agrupaba a los dispositivos con menor capacidad de procesamiento, y es donde siempre han estado los móviles. Actualmente, Nokia, el mayor fabricante de teléfonos móviles, está en proceso de portar toda su gama de teléfonos Symbian a la Configuración CDC, un indicador más de cómo el hardware va mejorando progresivamente en prestaciones. Dentro de cada una de las configuraciones, no todos los dispositivos soportan las mismas funcionalidades. Éstas está divididas en los llamados Perfiles (o en su nombre original, Profiles). Los dos perfiles de la CLDC son el MIDP 1.0 y el MIDP 2.0. Como podría suponerse, el perfil MIDP 2.0 incorpora librerías que no aparecen en los teléfonos MIDP 1.0, con las que se pueden implementar aplicaciones que accedan a la cámara, que puedan capturar imágenes, vídeos, que accedan a servicios web, etc. Los dispositivos que se adhieran a la especificación de cada uno de esos perfiles tienen que dar soporte a una serie de funcionalidades obligatoriamente. Esas funcionalidades vienen empaquetadas en lo que se llama especificaciones. Algunas de esas especificaciones son
- 13 -
Programación de Dispositivos Móviles
obligatorias para poder decir que el dispositivo soporta uno u otro de los perfiles, mientras que otras son opcionales y pueden o no ser implementadas por el fabricante. Por ejemplo, un teléfono que implemente gráficos 3D deberá hacerlo soportando la especificación jsr-184. ¿Pero por qué es todo tan complicado? ¿Porqué es necesaria tanta especificación, perfiles, etc?. Para asegurar un mínimo de compatibilidad entre dispositivos desarrollados por fabricantes dispares, y para asegurar al programador un mínimo común denominador en el que su aplicación funcione. En todo caso, la adhesión a una especificación no asegura que las cosas funciones igual y bien en todo tipo de dispositivos, por eso es importante testear pronto y testear a menudo, y en el mayor número de dispositivos posibles. Además, debe tenerse en cuenta que ninguna especificación obliga a un tamaño de pantalla determinado. Por tanto, hay que ser extremadamente cuidadoso en delegar la presentación en pantalla en el propio dispositivo. Lo cual tampoco es necesariamente un problema, ya que la propia plataforma funciona así, dejando al dispositivo que sea el que pinte las cosas. Eso tiene como ventaja que si se hace bien, siempre saldrá bien, y como inconveniente, que no se puede suponer que las aplicaciones se vayan a ver igual en dos teléfonos distintos. Pero donde es más importante esa limitación es a la hora de desarrollar juegos, a que suelen ser a pantalla completa, y con elementos de interfaz propios, y en los que se suele buscar a toda costa la consistencia entre dispositivos. En todo caso J2ME es la plataforma de desarrollo para móviles más extendida, porque no está limitada sólo a los teléfonos con sistema Symbian, porque durante mucho tiempo ha sido la única disponible para los desarrolladores, y porque algunos años en los que no ha sido la única, programar en J2ME era la única forma de implementar muchas funcionalidades. De hecho, aún hoy, sigue siendo la plataforma más extendida, con mayor número de desarrolladores, y con mayor número de aplicaciones disponibles.
Flash Lite Flash Lite es un adaptación de la plataforma Flash para dispositivos móviles. Actualmente coexisten varias versiones, Flash Lite 1.1, Flash Lite 2.0 y Flash Lite 2.0 Flash Lite 1.1, es la versión más extendida, sobre todo en Asia, y basada en la versión 4 de player de flash. Y es la más extendida por dos razones primordiales; en primer lugar porque es la que más tiempo lleva disponible, y en segundo lugar porque es la que necesita de hardware menos potente para su ejecución.
Emulador incorporado en Adobe Flash 8 Al estar basado en la versión 4 del player de flash, sólo soporta la sintaxis de ActionScript de Flash 4, una sintaxis bastante extraña y alejada de las convenciones de programación modernas. Esa forma de programar necesita de vinculaciones muy fuertes entre el código y los gráficos, por lo que es muy difícil que los desarrolladores que no estén acostumbrados a ella se puedan adaptar con facilidad. Además, para desarrollar cualquier aplicación con cierta interactividad, termina siendo necesario repartir el código entre multitud de elementos gráficos, por lo que la complejidad del desarrollo crece exponencialmente con la complejidad de la aplicación. Por no hablar del coste de mantenimiento. Tampoco se pueden obviar las limitaciones propias de flash Lite 1.1. Por ejemplo, no se pueden guardar datos en la memoria del teléfono, no es nada fácil cargar datos en tiempo de ejecución, y directamente no es posible cargar imágenes, por ejemplo. Pese a todo, al igual que para crear aplicaciones complejas, Flash Lite 1.1 no es, desde luego, la mejor herramienta, sí es ideal para realizar otro tipo de aplicaciones, como fondos de pantalla animados, salvapantallas, o incluso micro juegos muy sencillos. ¿Por qué? Precisamente por estar basado en Flash 4, que permite desarrollar animaciones con interactividad sencilla en tiempos mucho menores que cualquier otra plataforma. Todo cambió, sin embargo, con la llegada de Flash Lite 2.0. En primer lugar, Flash Lite 2.0 está basado en el Flash Player 7. En realidad, la funcionalidad que permite es muy similar a la de cualquier aplicación flash basada en esa versión del player. Por tanto, ahora es posible guardar datos en la memoria del teléfono o cargar ficheros XML con estructuras de datos complejas a través de la conexión GPS. Para el programador la mejora ha sido también sustancial, ya que puede aplicar las técnicas modernas de programación:
- 14 -
Programación de Dispositivos Móviles
programación orientada a objetos, patrones de diseño, aplicaciones dirigidas por eventos… Ahora es posible desarrollar una aplicación Flash Lite de la misma forma en la que se haría una J2ME, por ejemplo. Aunque las mejoras son sustanciales, también hay un aspecto en el que Flash Lite 2 cojea, y es los altos requerimientos de hardware que necesita para su ejecución. Por ahora sólo está disponible para teléfonos de la gama alta de la Serie 60 de Nokia. Flash Lite 3 es la evolución lógica de la plataforma, soporta vídeo incluyendo H.264, y un entorno de desarrollo rápido. Sin embargo, al menos en Asia, donde al menos en cuestión de dispositivos se está a la cabeza del desarrollo mundial, y por varios cuerpos de distancia, la implantación de la plataforma es mínima.
iPhone OS El iPhone, el último e n llegar, rompe también con l as líneas maestras en cuanto a desarrollo se refiere, marcadas por sus antecesores. El sistema operativo utilizado es una versión aligerada de Mac OS X, el sistema detrás de las computadoras de Apple, y por tanto, las herramientas de desarrollo que se deben utilizar son las mismas que para trabajar en escritorio. Aunque oficialmente no se puede instalar ninguna aplicación que no esté firmada por Apple ya existen formas de hacerlo, la vía oficial forma parte del iPhone Developer Program (de pago) y hay que descargar el SKD que es gratuito. iPhone dispone de un interfaz de usuario realmente interesante, la única pega es la cantidad de restricciones que tiene, aunque quizás Apple se dé cuenta que para triunfar mucho más es mejor liberar y dar libertad a su sistema. Aunque su tiempo de vida es corto ya copa casi el 7% d el mercado.
Objetive-C El lenguaje que debe utilizarse para desarrollar aplicaciones nativas para iPhone OS es Objective-C, un superset de C (una especie de C enriquecido), de forma que el desarrollador se apoya en un extenso Framework orientado a objetos, escalable y altamente modular, llamado Cocoa. En realidad, Cocoa es una colección de frameworks, que proporcionan todas las piezas necesarias para construir una aplicación: desde elementos de interfaz hasta gestión de tráfico de red.
Una aplicación para iPhone, casualmente desarrollada por el autor. Pese a lo modular y extenso de los frameworks, el mayor problema con el que se encuentran los recién llegados a la plataforma es que la curva de aprendizaje de Objective-C es bastante acusada. Especialmente si, además, se viene de un entorno de desarrollo Windows, ya que no sólo hay que cambiar de lenguaje, sino de forma de desarrollo por completo. Además, para poder desarrollar aplicaciones para iPhone es necesario estar registrado (previo pago) como desarrollador con Apple, para así poder obtener los certificados digitales necesarios para que las aplicaciones funcionen en los dispositivos. Por si fuera poco, la única forma de poner esas aplicaciones en esos dispositivos, es a través de una tienda de aplicaciones controlada por Apple.
Blackberry OS BlackBerry es un sistema operativo multitarea que está arrasando en la escena empresarial, en especial por sus servicios para correo y teclado QWERTY. Actualmente BlackBerry OS cuenta con un 11% del mercado. BlackBerry aparece en el mercado justo en el momento en que comenzaba a demandarse un sistema operativo que permitiera utilizar de una forma fácil, cómoda y rápida los servicios de correo electrónico. Hoy en día es también proveedor de servicios de correo electrónico a dispositivos que no son BlackBerry, gracias al programa BlackBerry Connect. Así, en líneas generales, en un dispositivo BlackBerry es posible redactar, enviar y recibir todo tipo de mensajes de correo electrónico, al igual que en el programa que se utiliza en ua computadora. Además, es posible realizar y contestar a las llamadas que se emitan a través de la red de telefonía móvil, lo que permite sustituir el
- 15 -
Programación de Dispositivos Móviles
teléfono móvil. También, como evolución lógica, los dispositivos de este fabricante permiten la navegación por internet en páginas HTML o WAP y tienen la capacidad de enviar o recibir mensajes SMS. Por lo demás, este sistema operativo incorpora múltiples aplicaciones y programas que convierten a los dispositivos en completos organizadores de bolsillo con funciones de calendario, libreta de direcciones, bloc de notas, lista de tareas, entre otras.
Android Android es un sistema operativo para teléfonos, basado en el núcleo de Linux, y que, aunque disponible para cualquier fabricante como open-source, actualmente es el motor de los dispositivos comercializados por Google (en el momento de escribir este texto, sólo existe un dispositivo, el conocido en Asia como HTC Dream, y en el resto del mundo como G1). Android también proporciona al desarrollador un completo Framework Java, un intento de normalización de la selva de especificaciones en que se ha convertido J2ME, que al igual que en el caso del iPhone, está orientado a facilitar y hacer más rapido el desarrollo, por un lado, y a proporcionar un “look and feel” específico y reconocible de la plataforma. También se proporciona una tienda de aplicaciones, donde se pueden vender desarrollos comerciales. En este momento, aún está por ver si la plataforma termina por alcanzar el momento suficiente como para terminar despegando. Google promete una plataforma de desarrollo gratuita, flexible, económica en el desarrollo de aplicaciones y simple, diferenciada de los estándares que ofrecen Microsoft o Symbian.
Palm OS Convertido en el auténtico líder del mercado desde su aparición en 1996, comenzó a ceder protagonismo con la llegada del nuevo siglo, hasta que en 2003 el fabricante se vio en la necesidad de dividirse y la plataforma pasó a formar parte de la nueva empresa denominada PalmSource, que en 2005 fue adquirida por la compañía japonesa Access. Los motivos de este retroceso en cuanto a ingresos de la compañía son fáciles de imaginar, la lenta pero imparable penetración de Windows Mobile, la evolución de decenas de modelos de teléfonos móviles inteligentes (la mayoría con Symbian) y la aparición de la plataforma y dispositivos BlackBerry comenzaron a dejar en segundo plano a las PDA que no incorporaban telefonía móvil. Esta plataforma ha evolucionado desde la clásica interfaz de un asistente de mano, a incorporar todo tipo de programas y funcionalidades, como teléfono, correo electrónico, mensajería y navegación en internet, además de compatibilidad con los archivos de Office, música, vídeo y fotografías. En la actualidad ya se desarrollan dispositivos Palm Treo que incorporan el sistema operativo Windows Mobile. El sistema operativo Palm OS, parece que se encamina a su plena operabilidad en Linux, lo que le augura un futuro prometedor.
Comparativa El Kernel de un sistema operativo es el núcleo del mismo, el software responsable de facilitar a los distintos programas acceso seguro a la computadora o, en forma más básica, es el encargado de gestionar recursos, a través de servicios de llamada al sistema. Por ello, es importante conocer qué núcleo utiliza cada uno de los sistemas operativos de esta comparativa.
La principal diferencia entre un kernel de libre distribución y uno propietario radica en que los de libre distribución como Linux cuenta con una amplia y experimentada comunidad de desarrolladores, gracias a los cuales se detectan rápidamente agujeros de seguridad, fallos, etc. y se realizan mejoras tanto para solucionar estos problemas como para adaptarse a los nuevos tiempos. En cerrados o propietarios, es más costoso encontrar errores y mejorarlos, ya que deben ser los propios desarrolladores del sistema los que detecten y realicen las mejoras, por lo que deben dedicarse más recursos a investigación en estos sistemas consiguiente aumento del coste del mismo.
Adaptabilidad. La capacidad o facilidad para poder adaptarlo a diferentes terminales o en diferentes máquinas. En este sentido, Android es el que mayor adaptabilidad presenta, ya que cada vez se está empleando en más dispositivos, no sólo teléfonos móviles, sino también en netbooks y como sistema empotrado. En cambio el resto de sistemas operativos tienen una adaptabilidad algo menor y más complicada.
- 16 -
Programación de Dispositivos Móviles
Conectividad. Hoy en día, para poder sacar el máximo partido a todas las funcionalidades que ofrecen cada uno de ellos es indispensable contar con acceso a Internet. En este sentido, se valora enormemente el hecho de que cuenten con acceso WiFi a Internet, así como conectividad 3G que permitan conectarse a Internet desde cualquier lugar.
Actividad 1: Diferencia de plataformas
Distingue mediante una investigación, las plataformas orientadas a desarrollo móvil donde considere: La descripción de la arquitectura. Sistema operativo que utiliza. Lenguajes de Interfaces de servicios. Lenguajes de desarrollo y bibliotecas. Base de datos que soporta. Emuladores con los que cuenta. • • • • • •
Elabora una tabla comparativa de cada plataforma encontrada donde plasme las diferencias. Presenta la tabla comparativa elaborada y emite un reporte de la actividad.
Actividad 2: Diferenciación de dispositivos móviles y características de tipos de aplicaciones
Diferenciación de Dispositivos Móviles Distingue mediante una investigación los dispositivos móviles donde se pueda implementar una solución, con base en: El tipo de sistema operativo que soporta. La base de datos que soporta. • •
Diferenciación de características de tipos de aplicaciones Distingue mediante una investigación las características de los tipos y usos de aplicaciones móvil que se puedan implementar: En Aplicaciones para conectividad con dispositivos presentes en red local o Internet describiendo: • - Cobertura de la red para obtener una comunicación aceptable. - Uso de Repositorio de datos locales. En Aplicaciones orientadas a dispositivos móviles seleccionando entre: - Sitio Web. - Aplicación específica para el aparato. •
Elabora una tabla comparativa de cada dispositivo y aplicación, anexando también las plataformas encontradas de la actividad anterior donde plasme las diferencias. Menciona también las conveniencias e inconveniencias del uso de cada dispositivo, aplicación y plataforma en usos con equipos móviles Emite un reporte de la actividad realizada.
1.2 Utiliza el entorno de desarrollo de programación móvil de acuerdo a los componentes y herramientas del lenguaje para la construcción de aplicaciones. Herramientas de desarrollo. Las herramientas de desarrollo a utilizar dependerán de la plataforma para la que hayas planteado tu aplicación. Si tu aplicación debe funcionar en dispositivos Windows Mobile, deberás utilizar el Visual Studio de Microsoft, un entorno de desarrollo bastante caro, como ya hemos dicho, pero que es casi sin discusión el mejor de todos los disponibles, y para el que vas a contar con más documentación de la que puedas llegar a absorver. Deberás basar tu aplicación, o bien en código C++ nativo, con lo que necesitarás unos conocimientos técnicos bastante elevados antes de comenzar el desarrollo, o bien apoyándote en el .Net Compact Framework, que es un subconjunto del .Net Framework, pudiendo programar en C#. Para dispositivos Symbian, la opción más extendida es, como hemos visto, J2ME. Las herramientas de desarrollo para J2ME no están tan refinadas como las de Windows Mobile, pero aún así, hay un entorno específico, el NetBeans Mobility Pack, y plugins y add-ons para los IDES java más populares: Eclipse e IDEA. Los tres entornos, una vez instalados los plugin correspondientes, permiten desarrollar, empaquetar y testear aplicaciones J2ME en los emuladores incorporados. Pero nunca debe olvidarse que los emuladores no son el dispositivo real, sino, como su nombre indica, emulaciones del dispositivo que están corriendo en una máquina que es varios órdenes de magnitud más rápida. Por tanto, lo que en un emulador funciona bien, puede que en el dispositivo no funcione tan bien.
- 17 -
Programación de Dispositivos Móviles
Por eso, siempre que se desarrolle para un dispositivo móvil, pero sobre todo si se está haciendo en J2ME o Flash Lite, hay que p robar los programas en el dispositivo según se están desarrollando. Y cuanto antes se empiece a probar, mejor, de esa forma los posibles problemas aflorarán antes, y serán más fáciles de resolver. En el caso del iPhone será necesario un Mac con procesador Intel (no existe SDK para Windows ni para Linux), y aprender un lenguaje y una metodología de desarrollo completamente nueva y, a día de hoy, no excesivamente documentada. Pero a la hora de decidir sobre la tecnología a utilizar para desarrollar una aplicación destinada a ser ejecutada en un dispositivo móvil, conviene preguntarse, entre otras, al menos un par de las siguientes preguntas: ¿A quién va dirigida la aplicación?. ¿Es una aplicación para todo tipo de usuarios, o es una aplicación empresarial?. ¿Los usuarios van a tener un entrenamiento específico para manejarla? ¿Se está desarrollando un juego o una aplicación?. La diferencia es muy importante, sobre todo si es un juego, más aún si se toma en consideración también la pregunta anterior. Dependiendo del tipo de usuario al que está dirigido se puede suponer el tipo de dispositivo para el que se deberá hacer el desarrollo. ¿Hay algún requerimiento previo?. Tal vez sea una pregunta obvia, pero si el cliente sólo quiere dar soporte a dispositivos Symbian… ¿Qué tal tus conocimientos técnicos? A todos nos gusta aprender nuevas tecnologías, pero a la hora de sacar adelante un proyecto es mejor atenerse a lo ya conocido y a aquello en lo que se es eficaz y productivo. ¿Qué presupuesto tienes? Al final, ése es el factor determinante, que terminará de resolver cualquier duda que tengas.
Consideraciones sobre el desarrollo. Limitaciones. Hay dos limitaciones fundamentales a que tener en cuenta a la hora de desarrollar un aplicación para ser ejecutada en un dispositivo móvil, y son las particularidades de hardware y las de la conexión. Aunque los dispositivos cada vez tienen más memoria y más capacidad de proceso, siguen siendo hermanos pequeños de las computadoras de escritorio. Por tanto, debe tenerse especial cuidado con no realizar demasiadas animaciones, ni gastar ciclos de proceso en procedimientos que no sean estrictamente necesarios. La pantalla es pequeña, mal iluminada, y se suele mirar en movimiento, como ya hemos visto con anterioridad, por lo que podemos aprovechar eso en nuestro beneficio. Por ello la entrada de datos debe ser lo más sencilla posible. En el caso de las PDAs, se realizará escribiendo en la pantalla con un lápiz, para que sea el dispositivo el q ue realice el reconocimiento de la escritura. Aunque ya no hace falta escribir de forma especial, como ocurría con el sistema de reconocimiento de escritura de las primeras Pilot, llamado Graffiti, para que el dispositivo entienda la letra, tampoco se puede ser demasiado descuidado con la caligrafía. En los móviles, sin embargo, la entrada de datos se va a hacer, lo más probablemente, con una sola mano, y en condiciones no muy apropiadas. El teclado, en la mayoría de los casos, tiene sólo 10 teclas útiles, que se van a utilizar en movimiento. En cualquiera de los dos casos, hay que intentar, por tanto, formatear las pantallas de entrada de datos para que se pueda realizar el mayor número de operaciones o bien punteando encima de botones, en el caso de las PDAs, o bien navegando con el joystick y confirmando con alguno de los botones principales en el caso de los teléfonos; evitando en lo posible que el usuario tenga que dirigirse al teclado. Siempre es preferible que haya, por ejemplo, un desplegable en el que elegir entre “Calle”, “Vía”, “Avenida” y “Paseo”, a que haya un campo de texto en el que se tenga que escribir, letra a letra, “Calle”. Además, no hay que olvidar que los interfaces de los distintos dispositivos son desesperantemente heterogéneos. Además, ni el tamaño de las pantallas, ni el tamaño ni la ubicación de las teclas ayuda a la hora de introducir datos por el usuario. Por eso, siempre hay que intentar desarrollar los interfaces de las aplicaciones de forma que no sean necesarias muchas pulsaciones de teclas, e intentar que, cuando sea necesario pulsar alguna tecla sea alguna de las softkeys, o teclas especiales. En el caso de los dispositivos móviles, más que en cualquier otro, menos es más. No sólo menos es más, sino que la colocación de los elementos de ese menos son los que van a marcar la diferencia. En realidad, no es sólo una cuestión de colocación en pantalla, sino de organización de la información a presentar, de lo que se llama arquitectura de la información. La premisa es bastante sencilla: dadas las dificultades para la introducción de datos, hay que intentar simplificarla al máximo. Para ello, es muy importante organizar el interfaz de la aplicación de forma que los procesos de entrada de datos sean lo más naturales posibles, y que el usuario nunca pase por pantallas cuya presentación no sea estrictamente necesaria. Pero en realidad esas precauciones no hay que tomarlas sólo a la hora de introducir datos, sino también a la hora de presentar información, y a la hora de estructurar la navegación por la aplicación. Normalmente es mucho más efectivo presentar la información agrupada de forma jerárquica, para de ese modo discriminar cuanto antes qué es lo que va a necesitar ver el usuario, y no mostrarle nada que no le resulte estrictamente necesario. En cuanto a las conexiones, lo primero que hay que tener siempre en cuenta es que, al contrario que en las aplicaciones web estándar, no están siempre disponibles. Por tanto, nunca se puede suponer que el dato que es necesario para poder seguir adelante en la ejecución de la aplicación, va a poderse obtener a través de una conexión a Internet.
- 18 -
Programación de Dispositivos Móviles
Además, debe tenerse en cuenta que antes de realizar una conexión a cualquier servicio tarificable, el dispositivo va a solicitar confirmación al usuario, y que éste puede denegarla, lo cual es perfectamente posible, dado el precio de las conexiones, que son facturables por peso, por cantidad de bytes enviados y descargados, no por tiempo, por lo que siempre hay que optimizar todo lo posible lo que se vaya a enviar en uno u otro sentido. Por si no fueran dificultades suficientes, la latencia de la red, es decir, el tiempo de espera entre una petición de datos y el comienzo de la llegada de la respuesta, es mucho mayor que la de cualquier aplicación web normal. Por tanto, hay que ser muy cuidadosos a la hora de informar al usuario sobre lo que está ocurriendo en todo momento, si se está e n espera de datos, si ya se están recibiendo, dando siempre, si es posible, estimaciones sobre el tiempo que resta para la terminación de todas las tareas. Tampoco se puede suponer que la ejecución de la aplicación sea lineal y completa. La función básica de muchos dispositivos es emitir y recibir llamadas telefónicas. Por tanto, nuestra aplicación debe ser capaz de comportarse adecuadamente al recibir una llamada, entre otras cosas porque la función de teléfono tiene asignada la prioridad más alta de todas las del dispositivo, es decir, se va a parar la ejecución de cualquier aplicación cuando entre una llamada nueva. Se debe tener prevista una forma de congelar el estado de la aplicación justo antes de recibir la llamada, para que cuando ésta termine, y tras la confirmación del usuario, se pueda volver a ese estado. Por tanto, si es un juego, todos los elementos del mismo deben permanecer estáticos hasta que la llamada termine y el usuario confirme que desea continuar con la aplicación. En general, hay que intentar probar las aplicaciones en el dispositivo para el que se vayan a desarrollar cuanto antes. De hecho, hay que probar mucho, y comenzar a probar lo antes posible. Nunca hay que fiarse de emuladores, entre otras cosas, porque se ejecutan en hardware cuya capacidad de procesamiento es superior a la del dispositivo en varios órdenes de magnitud. Por tanto, al desarrollar para dispositivos móviles, es muy importante comenzar a probar pronto, y probar a menudo, a fin de evitar sorpresas desagradables.
Usos actuales. Durante todo este documento hemos estado hablando de las plataformas, los lenguajes de programación, los sistemas operativos, pero en realidad, ¿para qué sirven? ¿Qué se puede hacer con un móvil? ¿Qué se está haciendo actualmente?. Actualmente, el desarrollo para dispositivos móviles se puede dividir en dos categorías. Por un lado, el desarrollo web puro y duro (webs hechas para ser navegadas desde móviles), y por otro, la programación de aplicaciones para ser ejecutadas en el propio móvil. No hay que olvidar que, actualmente, casi cualquier dispositivo implementa algún protocolo de comunicación, sea éste WI-FI o Bluetooth, por lo que es sencillo que, si no es capaz de conectar a Internet por sí mismo, como haría un teléfono, pueda hacerlo emparejándose con algún otro dispositivo que sí pueda. Dicho de otra forma, con un teléfono se puede uno conectar a Internet. Con un PocketPC o una Palm es probable que no, pero se puede hacer sin muchos problemas emparejándolo con un teléfono. Actualmente hay varios portales de servicios basados en web que proporcionan interfaces específicos para ser navegados desde dispositivos móviles. Compra de entradas para espectáculos, consulta de callejeros, de mapas de carreteras, obtención de información sobre restaurantes o farmacias de la zona en la que se encuentra el usuario, son algunos de los servicios más extendidos.
Metro, un programa multiplataforma con planos de Metro de varias ciudades de todo el mundo El apartado de las aplicaciones ejecutables en el teléfono está sin duda dominado por los microjuegos. Las limitaciones gráficas y sonoras, sobre todo de los teléfonos, no sólo no son un problema sino que ayudan a proporcionar a los juegos cierto aire “retro”, de máquina recreativa de los 80, que les hace más atractivos. Su bajo peso, además, les hace ideales para ser descargados directamente al teléfono mediante envío de sms a números especiales, por ejemplo, por lo que su sistema de distribución tiene todas las ventajas de las distribuciones virales. Además de los minijuegos, otro campo en el que están surgiendo muchas oportunidades para los desarrolladores es en la construcción de aplicaciones basadas en servicios de localización, utilizando datos de posicionamiento de unidades GPS, y combinando esos datos con los obtenidos sobre el entorno del usuario desde un servicio web.
- 19 -
Programación de Dispositivos Móviles
Tom Tom Mobile en un Symbian Serie 60 También se está trabajando bastante en aplicaciones que puedan servir como apoyo a personas con minusvalía, tanto visual como auditiva. Sistemas de localización, de reconocimiento óptico, sistemas de traducción de y a Braille portátiles… Por otro lado, los teléfonos son pequeños terminales con completas capacidades multimedia, e integran cámaras fotográficas que son capaces de grabar vídeo y reproductores multimedia que no tienen nada que envidiar a los desarrollados para las computadoras de escritorio. De esas cámaras integradas han salido algunas de las fotografías o vídeos más famosos en los últimos meses, como los de los atentados de Londres, o los de algunos niños pegando a sus compañeros de clase.
Acceso a flickr.com desde Opera Mobile Sin embargo, pese al amplio abanico de funcionalidades de los dispositivos, pese a su enorme índice de penetración en el mercado, pese a las cantidades de dinero que se alrededor de las descargas de contenidos multimedia o del envío de sms, aún no se ha producido el despegue definitivo del desarrollo de aplicaciones. Hablamos mucho de lo cercano que está el despegue definitivo de las aplicaciones móviles, de lo que se ve a poder hacer con los terminales de última generación y las conexiones de banda ancha, pero lo cierto es que ese discurso se lleva escuchando, casi sin alteraciones, desde hace ya varios años. Lo cierto es que el mercado mueve millones anualmente en descargas de contenidos multimedia, como tonos o fondos de pantalla. Es muy sencillo descargarse un tono polifónico enviando un sms, y al realizarse el pago no en dinero contante y sonante, sino descontando el importe del saldo o de la próxima factura, tampoco se tiene sensación de estar realizando un gasto real. Sin embargo, el mismo tono puede editarse en una computadora de sobremesa, convertirlo en un formato compatible con el teléfono, y enviarlo a éste por BlueTooth, por ejemplo, con lo que el coste final sería cero. El porqué se gasta tanto dinero en servicios que puede realizar uno mismo con un poco de trabajo a los mandos de su computadora, mientras otros, que han costado millones de euros en desarrollo, como por ejemplo la videoconferencia por 3G, prácticamente no se utilizan, es uno de los misterios más grandes de este nuevo siglo, al menos para los que han desarrollado esos servicios. Sin embargo, estudiando con detenimiento las pautas de comportamiento del usuario medio, entendiendo como tal el que no tiene porqué saber lo que quiere decir Symbian, parece evidente que las últimas novedades tecnológicas no son más que soluciones en busca de un problema. Lo que parece demandar el mercado son servicios y aplicaciones que le simplifiquen la vida, que no requieran de mucha intervención por su parte para funcionar correctamente.
- 20 -
Programación de Dispositivos Móviles
Merece consideración aparte el caso del iPhone, por múltiples razones. La existencia de un mercado cerrado, controlado por el fabricante y la necesidad de pasar por un proceso de certificación (de pago), no han impedido que ha día de hoy, haya más de 15.000 aplicaciones disponibles. Sin embargo, hay varios riesgos asociados a un crecimiento tan rápido de una plataforma. El riesgo de implosión es muy alto, y dada la gran rapidez con la que aparecen aplicaciones nuevas, es muy difícil que un desarrollador se plantee trabajar en una aplicación por un tiempo considerable, lo que hace que el porcentaje de a plicaciones que realmente pueden considerarse como “killer” es mínimo.
Perspectiva del futuro. Es muy difícil predecir el futuro. Más aún cuando estamos intentando predecir el futuro de un campo en el que la innovación es l a máxima, lo que todos los implicados buscan, ya sean fabricantes de terminales, desarrolladores de aplicaciones, u operadoras y proveedores de servicios. Pero sí que tenemos unos cuantos datos con los que atrevernos a aventurar una previsión para el futuro cercano. En primer lugar, el hardware va a seguir evolucionando. Según la Ley de Moore, cada 18 meses la potencia de las computadoras se duplica. Esta ley es igual de aplicable para un PC de escritorio, para la supercomputadora Mare Nostrum, para un PocketPC o para un móvil. Como decía Don Hilarión, la ciencia adelanta que es una barbaridad. Por tanto, podemos esperar un avance considerable en la capacidad de proceso de los dispositivos en un plazo relativamente corto, un avance que además va a ser constante, se va a seguir produciendo a lo largo del tiempo. Y a más capacidad de proceso, mayor calidad de los contenidos. Así pues, lo primero que podemos esperar es que los juegos sean más complejos, con mejores gráficos y mejor sonido, por ejemplo, o aplicaciones complejas con integración de gráficos 3D (aplicaciones sobre medicina o astronomía, por ejemplo). Pero mejor hardware no sólo trae consigo aplicaciones de más calidad, sino mejores posibilidades de comunicación e interacción entre dispositivos y por lo tanto entre los dueños de los mismos. Bluetooth de alta velocidad, Wi-Fi, voz ip, comunicaciones por push... son varias las tecnologías que van a servir para mejorar la comunicación y el intercambio de datos entre dispositivos. Nadie quiere quedarse sin al menos una parte del pastel del futuro. Mientras algunos están desarrollando pantallas interactivas que se puedan enrollar para transportar, otros están trabajando en interfaces de usuario que parten de paradigmas totalmente distintos a los que estamos acostumbrados a utilizar y otros están trabajando en el dispositivo que los u na a todos. Esa última rama de investigación, la ya famosa convergencia, puede ser una de las que más frutos dé en un futuro cercano. La idea detrás de esa línea de desarrollo es el intentar buscar un dispositivo que pueda realizar las funciones de todos los que habitualmente solemos utilizar: reproductor mp3, teléfono, agenda electrónica, incluso navegador GPS. Todo en un único aparato, de dimensiones reducidas, pero con una pantalla lo suficientemente grande como para ser funcional. El penúltimo intento fue el Origami de Microsoft, pero han sido varios los que lo han intentado previamente, sin mucho éxito: BenQ, Sony-Ericsson, Apple… Sin embargo, sí parece que los netbooks, especialmente en Asia, donde es raro ver otra cosa, están cuajando en el mercado. Si bien parece claro que los dispositivos, en un futuro cercano, van a tender a converger a un único super-dispositivo, ¿qué se puede esperar de cómo serán por tanto las aplicaciones del futuro? Siempre es complicado adivinar lo que va a ocurrir, pero puede esperarse un aumento de las aplicaciones con cierto componente social, al igual que está ocurriendo en la web, que permitan interactuar a los usuarios, jugar en grupo, compartir archivos multimedia con facilidad. También es de suponer que cada vez haya más aplicaciones especializadas en la gestión y reproducción de archivos multimedia, sobre todo música y vídeo, aplicaciones integradas con servicios de descarga a través del PC d e escritorio. En todo caso, lo que único que parece seguro es que cada vez va a haber más dispositivos móviles circulando por las calles en manos de sus dueños, dispositivos con mayores capacidades de proceso y multimedia. Una oportunidad que sería una pena dejar pasar.
Actividad 3: Establecimiento de objetivo y tecnología a utilizar en programación orientada a móvil
El alumno propone un desarrollo de aplicación móvil de un caso de la vida real, donde cuestiona cual es el problema a resolver, tomando en cuenta que elementos pueden ser transformados a datos manejables con un dispositivo móvil. Se apoya también en casos de uso con equipo móvil como sigue: Ventas ambulantes Inspecciones de proyectos. Toma de Inventarios. Empresas de Servicios. Toma de encuestas o censos. Consultas de cuentas bancarias. Navegación red de Internet. • • • • • • •
Definición de objetivo
- 21 -
Programación de Dispositivos Móviles
Define el objetivo de la aplicación Web orientada a móvil, de manera concreta e incluyendo los requerimientos específicos del caso en cuestión. Elabora a detalle una propuesta de proyecto de implantación de la solución móvil, incluyendo sus fases: Planeación y análisis de la propuesta. Diseño de la aplicación móvil. Elaboración de las rutinas de programación. Prueba piloto de la solución. Puesta a punto y operación de la aplicación. • • • • •
Determinación de tecnología a utilizar Propone en forma detallada la tecnología a utilizar (arquitectura, plataforma, emulador de dispositivo y equipo móvil), considerando: El objetivo definido de la propuesta de solución móvil. Las características técnicas del dispositivo móvil. Emuladores para equipos móviles que soporta la arquitectura. Componentes de la plataforma de desarrollo. Enumera adicionalmente las ventajas que se pueden tener con el desarrollo de esta propuesta y el uso de la tecnología seleccionada. • • • • •
Presenta la propuesta establecida y emite un reporte de la actividad.
Actividad 4: Elaboración del plan de trabajo para la implementación de propuesta de solución establecida 1. PLAN DE TRABAJO Un plan de trabajo es un documento donde se en listan las actividades, las fechas en las que se realizarán, los recursos necesarios y las personas responsables. El primer paso es la definición de los objetivos, estos son el punto de partida en el proceso de Planeación, estos nos determinarán las actividades que necesitaremos realizar para lograr los objetivos propuestos. Las siguientes preguntas te servirán de gu ía para elaborar tu planeación: ¿Cuál es el objetivo de mi proyecto? ¿Qué actividades específicas necesito realizar para alcanzar los objetivos y responder a mis preguntas? ¿En qué secuencia se llevarán a cabo? ¿Cuándo estará terminada cada actividad? ¿Qué productos concretos obtendré en cada actividad? ¿Qué recursos necesito? • • • • • •
El resultado de este proceso es un plan de actividades y tareas a ejecutarse en un periodo determinado. Realizar tu plan y estar revisándolo constantemente te permite tener retroalimentación útil y permanente de las acciones, así podrás verificar que vas en la dirección indicada para llegar a los resultados esperados y no perderás tiempo haciendo actividades secundarias. Una herramienta útil para elaborar un plan de trabajo es el "cronograma".
CRONOGRAMA
El cronograma también conocido como Gráfica de Gantt, es el resumen de un plan de trabajo en forma gráfica, que muestra las actividades más importantes en orden cronológico, así como la fecha de inicio y termino en que se llevarán a cabo, la persona que es responsable de efectuarlas, los recursos necesarios y los productos esperados. La presentación del cronograma puede variar, una opción es:
- 22 -
Programación de Dispositivos Móviles
ELABORACION DEL PLAN DE TRABAJO Y CRONOGRAMA • • • • •
• •
• • • •
Escribe el tema de tu proyecto: Escribe el objetivo (s): Elabora una lista con las actividades que necesitas hacer para alcanzar tu objetivo. Revisa el cronograma del curso revisa fechas y etapas. En Excel elabora tu formato para cronograma y en base a los tiempos marcados en el cronograma del curso, agrupa tus actividades por etapas y asígnales fecha de inicio y termino así recursos y los productos. Guarda tu archivo en la carpeta de proyecto como cronograma. Utiliza las opciones de formato para dar claridad a tu cronograma y que se convierta a una herramienta útil para utilizar durante el semestre, permitiéndote realizar tus actividades en tiempo y lograr los productos de cada. Coloca en la parte superior del cronograma tu tema y tu(s) objetivo(s) para no perderlos de vista. Al final del cronograma abre un cuadro de texto y escribe ¿Qué te aporta esta actividad? ¿Qué utilidad tiene o podrá tener? Imprime tu plan de trabajo, revisa su formato y contenido, realiza los cambios que creas conveniente y vuelve a imprimir 2 copias. Una copia deberás entregarla al final de la clase y la otra es para que puedas guiar tus actividades a partir de hoy.
2. Elabora el plan de trabajo o calendario para la implementación de la propuesta de solución para equipo móvil, definiendo: orden, tiempos, holgura y actividades tomando en cuenta las fases de: Diseño de la aplicación móvil. Elaboración de las rutinas de programación. Prueba piloto de la solución. Puesta a punto u operación de la aplicación. 3. Grafica las fases de la implementación de la propuesta de solución. 4. Presenta la grafica o plan de trabajo elaborado y emite un reporte de la actividad. • • • •
Actividad 5: Elaboración de cotización para implementación de solución móvil
1. Estima costos por cada fase del plan de trabajo del proyecto de implementación de solución móvil establecida, mediante dos escenarios de cotización. (Apoyarse en el ejemplo anexo). EJEMPLO DE COTIZACIÓN
ESCENARIO 1 Descripción
Concepto
Licenciamiento
Incluye: Licencia de las herramientas o lenguajes .NET Instalación de las de las herramientas o lenguajes .NET Planeación y análisis de la propuesta. Diseño de la aplicación móvil. Elaboración de las rutinas de programación. Prueba piloto de la solución. Puesta en punto y arranque. - CONSULTORIA 200HRS. (100 pesos /HORA) CURSO EN EL MANEJO DE LA APLICACIÓN •
Costo en m $22,300.00 pesos + IVA Correspondiente
•
Desarrollo de la implementación
• • •
$30,000.00 pesos + IVA Correspondiente
• •
Formación Capacitación
- 23 -
$5,000.00 pesos + IVA
Programación de Dispositivos Móviles
(OPCIONAL) Opcional. Infraestructura.
WEB ORIENTADA A MOVIL Incluye: Servidor dedicado Instalación y Soporte técnico‐ 1 año. • •
N
ESCENARIO 2 Descripción
Concepto
Licenciamiento
Incluye: Licencia de las herramientas o lenguajes .NET Instalación de las de las herramientas o lenguajes .NET Planeación y análisis de la propuesta. Diseño de la aplicación móvil. Elaboración de las rutinas de programación. Prueba piloto de la solución. Puesta en punto y arranque. Incluye: Servidor dedicado Instalación y Soporte técnico‐ 1 año.
•
Correspondiente $30,052.00 + IVA Correspondiente
Costo en m $22,300.00 pesos + IVA Correspondiente
•
Desarrollo de la implementación
• • •
$30,000.00 pesos + IVA Correspondiente
• •
(OPCIONAL) Opcional. Infraestructura.
• •
$30,052.00 + IVA Correspondiente
FORMA DE PAGO Concepto
Porcentaje
50% 50%
Anticipo Al finalizar el servicio VALIDEZ DE LA PROPUESTA
Esta propuesta es válida por 30 días contados a partir de la fecha de presentación al cliente. Una vez aprobadas las propuestas de diseño, los cambios tendrán un costo adicional. ACUERDO DE CONFIDENCIALIDAD
La Empresa, se compromete a mantener en estricta confidencialidad y reserva toda información, así como acuerdos promociónales y comerciales establecidos con cualquier empresa o dependencia, incluso previo a la implantación de la solución. 2. Elaboración de Cotización Escenario 1 Establece la estimación de costos en una tabla incluyendo lo siguiente: Estimación de costos por adquisición y licenciamiento de la plataforma. Estimación de costos por servicios por implementación de la aplicación. Estimación de costos por consultoría y capacitación en el uso de la aplicación. • • •
Escenario 2 Establece la estimación de costos en una tabla incluyendo lo siguiente: Estimación de costos por adquisición y licenciamiento de la plataforma. Estimación de costos por servicios por implementación de la aplicación. • •
3. Planteamiento de escenarios de opción de financiamiento para el cliente. 4. Presenta la cotización elaborada y emite un reporte de la actividad.
- 24 -
Programación de Dispositivos Móviles
Unidad 2
Implementación de la aplicación para dispositivos móviles
El alumno realizará la implementación del desarrollo de soluciones Web y de gestión de datos orientadas a móviles considerando los procesos de diseño, elaboración y configuración de sitios Web; que conlleven al acceso de datos e información en línea o tiempo real con múltiples usos.
2.1 Construye aplicaciones informáticas Web que puedan ejecutarse en un entorno móvil por medio de la plataforma y emuladores de dispositivos móviles específicos, a fin de acceder información comercial, de servicios o personal. Introducción Para los dispositivos móviles, como ya se menciona se pueden utilizar Java (J2ME), Flash Lite y Microsoft .Net, por lo que utilizaremos éste último para la realización de aplicaciones para dispositivos móviles, por lo que vamos a utilizar el lenguaje C# (C sharp) para ello, por lo que en la unidad primero se dirá que es C#, cómo funciona y por último como se crean aplicaciones para móviles mediante este lengua C# es un lenguaje de programación de ordenadores. Se trata de un lenguaje moderno, evolucionado a partir de C y C++, y con una sintaxis muy similar a la d e Java. Los programas creados con C# no suelen ser tan rápidos como los creados con C, pero a cambio la productividad del programador es mucho mayor. Se trata de un lenguaje creado por Microsoft para crear programas para su plataforma .NET, pero estandarizado posteriormente posteriormente por ECMA y por ISO, y del que existe una implementación alternativa de "código abierto", el "proyecto Mono", que está disponible para Windows, Linux, Mac OS X y otros sistemas operativos. Los pasos que seguiremos para crear un programa en C# serán: Escribir el programa en lenguaje C# (fichero fuente), con cualquier editor de textos. Compilarlo con nuestro compilador. Esto creará un "fichero ejecutable". Lanzar el fichero ejecutable. La mayoría de los compiladores actuales permiten dar todos estos pasos desde un único entorno, en el que escribimos nuestros programas, los compilamos, y los depuramos en caso de que exista algún fallo. Mientras que en lenguaje C se puede escribir un programa de esta manera: #include void main() { printf ("Hola"); } En C# hay que dar todavia mas pasos para conseguir lo mismo: public class Ejemplo01 { public static void Main() { System .Console.WriteLine("Hola"); } }
Conceptos básicos de C# Vamos con un primer ejemplo de programa en C#, posiblemente el más sencillo de los que "hacen algo útil". Se trata de escribir un texto en pantalla. Vamos a verlo ahora con más detalle: public class Ejemplo01 { public static void Main Main() () { System. System.Console Console..WriteLine WriteLine(("Hola" "Hola")); } } Esto escribe "Hola" en la pantalla. Pero hay mucho alrededor de ese "Hola", y vamos a comentarlo antes de proseguir, aunque muchos de los detalles se irán aclarando más adelante. En este primer análisis, iremos de dentro hacia fuera:
- 25 -
Programación de Dispositivos Móviles
WriteLine("Hola"); - "Hola" es el texto que queremos escribir, y WriteLine es la orden encargada de escribir (Write) una línea (Line) de texto en pantalla. • Console.WriteLine("Hola"); Console.WriteLine("H ola"); porque WriteLine es una orden d e manejo de la "consola" (la pantalla "negra" en modo texto del sistema operativo). • System.Console.WriteLine("Hola"); porque las órdenes relacionadas con el manejo de consola (Console) pertenecen a la categoría de sistema (System). • Las llaves { y } se usan para delimitar un bloque de programa. En nuestro caso, se trata del bloque principal del programa. • public static void Main() - Main indica cual es "el cuerpo del programa", la parte principal (un programa puede estar dividido en varios fragmentos, como veremos más adelante). Todos los programas tienen que tener un bloque "Main". Los detalles de por qué hay que poner delante "public static void" y de por qué se pone después un paréntesis vacío los iremos aclarando más tarde. De momento, deberemos memorizar que ésa será la forma correcta de escribir "Main". • public class Ejemplo01 - de momento pensaremos que "Ejemplo01" es e l nombre de nuestro programa. Una línea como esa deberá existir también siempre en nuestros programas, y eso de "public class" será obligatorio. Nuevamente, aplazamos para más tarde los detalles sobre qué quiere decir "class" y por qué debe ser "public". •
Como se puede ver, mucha parte de este programa todavía es casi un "acto de fé" para nosotros. Debemos creernos que "se debe hacer así". Poco a poco iremos detallando el por qué de "public", de "static", de "void", de "class"... Por ahora nos limitaremos a "rellenar" el cuerpo del programa para entender los conceptos básicos de programación. Sólo un par de cosas más antes de seguir adelante: Cada orden de C# debe terminar con un punto y coma (;) • C# es un lenguaje de formato libre, de modo que puede haber varias órdenes en una misma línea, u órdenes separadas por varias líneas o espacios entre medias. Lo que realmente indica dónde termina una orden y donde empieza la siguiente son los puntos y coma. Por ese motivo, el programa anterior se podría haber escrito también así (aunque no es aconsejable, porque puede resultar menos legible): •
public class Ejemplo01 { public static void Main Main() () { System. System .Console Console..WriteLine WriteLine(("Hola" "Hola")); } }
De hecho, hay dos formas especialmente frecuentes de colocar la llave de comienzo, y yo usaré ambas indistintamente. Una es como hemos hecho en el primer ejemplo: situar la llave de apertura en una línea, sola, y justo encima de la llave de cierre correspondiente. Esto es lo que muchos autores llaman el "estilo C". La segunda forma habitual es situándola a continuación del nombre del bloque que comienza (el "estilo Java"), así: •
public class Ejemplo01 { public static void Main Main(){ (){ System. System.Console Console..WriteLine WriteLine(("Hola" "Hola")); } } (esta es la forma que yo emplearé preferentemente en este texto cuando estemos trabajando con fuentes de mayor tamaño, para que ocupe un poco menos de espacio). La gran mayoría de las órdenes que encontraremos en el lenguaje C# son palabras en inglés o abreviaturas de éstas. Pero hay que tener en cuenta que C# distingue entre mayúsculas y minúsculas, por lo que "WriteLine" es una palabra reconocida, pero "writeLine", "WRITELINE" o "Writeline" no lo son. •
Mostrar números enteros en pantalla Cuando queremos escribir un texto "tal cual", como en el ejemplo anterior, lo encerramos entre comillas. Pero no siempre querremos escribir textos prefijados. En muchos casos, se tratará de a lgo que habrá que calcular. El ejemplo más sencillo es el de una operación matemática. La forma de realizarla es sencilla: no usar comillas en WriteLine. Entonces, C# intentará analizar el contenido para ver qué quiere decir. Por ejemplo, para sumar 3 y 4 bastaría hacer: public class Ejemplo01suma {
- 26 -
Programación de Dispositivos Móviles
public static void Main Main() () { System. System .Console Console. .WriteLine WriteLine( (3+4); } }
Ejercicio propuesto: crea un programa que diga el resultado de sumar 118 y 56.
Operaciones aritméticas básicas Está claro que el símbolo de la suma será un +, y p odemos esperar cual será el de la resta, pero alguna de las operaciones matemáticas habituales tiene símbolos menos intuitivos. Veamos cuales son los más importantes: Operad Ope rador or Ope Operac ración ión + Suma Resta, negación * Multiplicación / División % Resto de la división ("módulo")
Orden de prioridad de los operadores Sencillo: • • • • •
En primer lugar se realizarán las operaciones indicadas entre paréntesis. Luego la negación. Después las multiplicaciones, divisiones y el resto de la división. Finalmente, las sumas y las restas. En caso de tener igual prioridad, se analizan de izquierda a derecha.
Introducción a los problemas de desbordamiento El espacio del que disponemos para almacenar los números es limitado. Si el resultado de una operación es un número "demasiado grande", obtendremos un mensaje de error o un resultado erróneo. Por eso en los primeros ejemplos usaremos números pequeños. Más adelante veremos a qué se debe realmente este problema y cómo evitarlo. Como anticipo, el siguiente programa ni siquiera compila, porque el compilador sabe que el resultado va a ser "demasiado grande": public class Ejemplo01multiplic { public static void Main Main() () { System. System .Console Console. .WriteLine WriteLine( (10000000 10000000* *10000000 10000000) ); } }
Variables. Introducción a las variables: int Las variables son algo que no contiene un valor predeterminado, un espacio de memoria al que nosotros asignamos un nombre y en el que podremos almacenar datos. El primer ejemplo nos permitía escribir "Hola". El segundo nos permitía sumar dos números que habíamos prefijado en nuestro programa. Pero esto tampoco es "lo habitual", sino que esos números dependerán de valores que haya tecleado el usuario o de cálculos anteriores. Por eso necesitaremos usar variables, en las que guardemos los datos con l os que vamos a trabajar y también los resultados temporales. Vamos a ver como ejemplo l o que haríamos para sumar dos números enteros que fijásemos en el programa.
Definición de variables: números enteros Para usar una cierta variable primero hay que declararla: indicar su nombre y el tipo de datos que q uerremos guardar. El primer tipo de datos que usaremos serán números enteros (sin decimales), que se indican con "int" (abreviatura del inglés "integer"). Después de esta palabra se indica el nombre que tendrá la variable: int primerNumero;
Esa orden reserva espacio para almacenar un número entero, que podrá tomar distintos valores, y al que nos referiremos con el nombre "primerNumero".
- 27 -
Programación de Dispositivos Móviles
Asignación de valores Podemos darle un valor a esa variable durante el programa haciendo primerNumero = 234;
O también podemos darles un valor inicial ("inicializarlas") antes de que empiece el programa, en el mismo momento en que las definimos: int primerNumero = 234;
O incluso podemos definir e inicializar más de una variable a la vez int primerNumero = 234, segundoNumero = 567;
(esta línea reserva espacio para dos variables, que usaremos para almacenar números enteros; una de ellas se llama primerNumero y tiene como valor inicial 234 y la otra se llama segundoNumero y tiene como valor inicial 567). Después ya podemos hacer operaciones con las variables, igual que las hacíamos con los números: suma = primerNumero + segundoNumero;
Mostrar el valor de una variable en pantalla Una vez que sabemos cómo mostrar un número en pantalla, es sencillo mostrar el valor de una variable. Para un número h acíamos cosas como System.Console.WriteLine(3+4);
pero si se trata de una variable es idéntico: System.Console.WriteLine(suma);
O bien, si queremos mostrar un texto además del valor de la variable, podemos indicar el texto entre comillas, detallando con {0} en qué parte del texto queremos que aparezca el valor de la variable, de la siguiente forma: System.Console.WriteLine("La suma es {0}", suma);
Si se trata de más de una variable, indicaremos todas ellas tras el texto, y detallaremos dónde debe aparecer cada una de ellas, usando {0}, {1} y así sucesivamente: System.Console.WriteLine("La suma de {0} y {1} es {2}" , primerNumero, segundoNumero, suma);
Ya sabemos todo lo suficiente para crear nuestro programa que sume dos números usando variables: public class Ejemplo02 { public static void Main() { int primerNumero; int segundoNumero; int suma; primerNumero = 234; segundoNumero = 567; suma = primerNumero + segundoNumero; System.Console.WriteLine("La suma de {0} y {1} es {2}" , primerNumero, segundoNumero, suma); } }
Repasemos lo que hace: •
(Nos saltamos todavía los detalles de qué quieren decir "public", "class", "static" y "void").
- 28 -
Programación de Dispositivos Móviles
Main() indica donde comienza el cuerpo del programa, que se delimita entre llaves { y } • int primerNumero; reserva espacio para guardar un número entero, al que llamaremos primerNumero. • int segundoNumero; reserva espacio para guardar otro número entero, al que llamaremos segundoNumero. • int suma; reserva espacio para guardar un tercer número entero, al que llamaremos suma. • primerNumero = 234; da el valor del primer número que queremos sumar • segundoNumero = 567; da el valor del segundo número que queremos sumar • suma = primerNumero + segundoNumero; halla la suma de esos dos números y la guarda en otra variable, en vez de mostrarla directamente en pantalla. • System.Console.WriteLine("La suma de {0} y {1} es {2}", primerNumero, segundoNumero, suma); muestra en pantalla el texto y los valores de las tres variables (los dos números iniciales y su suma). •
Ejercicio propuesto:
Hacer un programa que realice la siguiente operación: que sume los dos primeros números de dos variables y el resultado lo multiplique por el tercer número de una tercer variable, mostrando el resultado en pantalla mediante una cuarta variable. Nota: la operación se debe de realizar en una sola línea.
Identificadores Estos nombres de variable (lo que se conoce como "identificadores") pueden estar formados por letras, números o el símbolo de subrayado (_) y deben comenzar por letra o subrayado. No deben tener espacios entre medias, y hay que recordar que las vocales acentuadas y la eñe son problemáticas, porque no son letras "estándar" en todos los idiomas. Por eso, no son nombres de variable válidos: 1numero un numero Año1 MásDatos
(empieza por número) (contiene un espacio) (tiene una eñe) (tiene una vocal acentuada)
Tampoco podremos usar como identificadores las palabras reservadas de C#. Por ejemplo, la palabra "int" se refiere a que cierta variable guardará un número entero, así que esa palabra "int" no la podremos usar tampoco como nombre de variable (pero no vamos a incluir ahora una lista de palabras reservadas de C#, ya nos iremos encontrando con ellas). De momento, intentaremos usar nombres de variables que a nosotros nos resulten claros, y que no parezca que puedan ser alguna orden de C#. Hay que recordar que en C# las mayúsculas y minúsculas se consideran diferentes, de modo que si intentamos hacer PrimerNumero = 0; primernumero = 0;
o cualquier variación similar, el compilador protestará y nos dirá que no conoce esa variable, porque la habíamos declarado como int primerNumero;
Comentarios Podemos escribir comentarios, que el compilador ignora, pero que pueden servir para aclararnos cosas a nosotros. Se escriben entre /* y */: int suma;
/* Porque guardaré el valor de la suma para usarlo más tarde */
Es conveniente escribir comentarios que aclaren la misión de las partes de nuestros programas que puedan resultar menos claras a simple vista. Incluso suele ser aconsejable que el programa comience con un comentario, que nos recuerde qué hace el programa sin que necesitemos mirarlo de arriba a abajo. Un ejemplo casi exagerado: /* ---- Ejemplo en C#: sumar dos números prefijados ---- */ public class Ejemplo02b { public static void Main()
- 29 -
Programación de Dispositivos Móviles
{ int primerNumero = 234; int segundoNumero = 567; int suma; /* Guardaré el valor para usarlo más tarde */
/* Primero calculo la suma */ suma = primerNumero + segundoNumero;
/* Y después muestro su valor */ System.Console.WriteLine("La suma de {0} y {1} es {2}" , primerNumero, segundoNumero, suma); } }
Un comentario puede empezar en una línea y terminar en o tra distinta, así: /*
Esto es un comentario que ocupa más de una línea
*/
También es posible declarar otro tipo de comentarios, que comienzan con doble barra y terminan cuando se acaba la línea (estos comentarios, claramente, no podrán ocupar más de una línea). Son los "comentarios al estilo de C++": //
Este es un comentario "al estilo C++"
Datos por el usuario: ReadLine Si queremos que sea el usuario de nuestro programa quien teclee los valores, necesitamos una nueva orden, que nos permita leer desde teclado. Pues bien, al igual que tenemos System.Console.WriteLine ("escribir línea), también existe System.Console.ReadLine ("leer línea"). Para leer textos, haríamos texto = System.Console.ReadLine();
pero eso ocurrirá en el próximo tema, cuando veamos cómo manejar textos. De momento, nosotros sólo sabemos manipular números enteros, así que deberemos convertir ese dato a un número entero, usando Convert.ToInt32: primerNumero = System.Convert.ToInt32(System.Console.ReadLine());
Un ejemplo de programa que sume dos números tecleados por el usuario sería: public class Ejemplo03 { public static void Main() { int primerNumero; int segundoNumero; int suma; System.Console.WriteLine("Introduce el primer número"); primerNumero = System.Convert.ToInt32( System.Console.ReadLine()); System.Console.WriteLine("Introduce el segundo número" ); segundoNumero = System.Convert.ToInt32( System.Console.ReadLine()); suma = primerNumero + segundoNumero; System.Console.WriteLine("La suma de {0} y {1} es {2}" , primerNumero, segundoNumero, suma); } }
Va siendo hora de hacer una pequeña mejora: no es necesario repetir "System." al principio de la mayoría de las órdenes que tienen que ver con el sistema (por ahora, las de consola y las de conversión), si al principio del programa utilizamos "using System": using System; public class Ejemplo04 { public static void Main() {
- 30 -
Programación de Dispositivos Móviles
int primerNumero; int segundoNumero; int suma; Console.WriteLine("Introduce el primer número"); primerNumero = Convert.ToInt32(Console.ReadLine()); Console.WriteLine("Introduce el segundo número" ); segundoNumero = Convert.ToInt32(Console.ReadLine()); suma = primerNumero + segundoNumero; Console.WriteLine("La suma de {0} y {1} es {2}" , primerNumero, segundoNumero, suma); } }
Ejercicios propuestos: 1.
2. 3.
El usuario tecleará dos números (x e y), y el programa deberá calcular cual es el resultado de su división y el resto de esa división. El usuario tecleará dos números (a y b), y el programa mostrará el resultado de la operación (a+b)*(a-b) y el resultado de la operación a2-b2. Pedir al usuario un número y mostrar su tabla de multiplicar. Por ejemplo, si el número es el 3, debería escribirse algo como:
3 x 0 = 0 3 x 1 = 3 ... 3 x 10 = 30
Tipos de datos básicos Tipo de datos entero Hemos hablado de números enteros, de cómo realizar operaciones sencillas y de cómo usar variables para reservar espacio y poder trabajar con datos cuyo valor n o sabemos de antemano. Empieza a ser el momento de refinar, de dar más detalles. El primer "matiz" importante que nos hemos saltado es el tamaño de los números que podemos emplear, así como su signo (positivo o negativo). Por ejemplo, un dato de tipo "int" puede guardar números de hasta unas nueve cifras, tanto positivos como negativos, y ocupa 4 bytes en memoria. Pero no es la única opción. Por ejemplo, si queremos guardar la edad de una persona, no necesitamos usar números negativos, y nos bastaría con 3 cifras, así que es de suponer que existirá algún tipo de datos más adecuado, que desperdicie menos memoria. También existe el caso contrario: un banco puede necesitar manejar números con más de 9 cifras, así que un dato "int" se les quedaría corto. Siendo estrictos, si hablamos de valores monetarios, necesitaríamos usar decimales, pero eso lo d ejamos para el siguiente apartado.
Tipos de datos para números enteros Los tipos de datos enteros que podemos usar en C#, junto con el espacio que ocupan en memoria y el rango de valores que os permiten almacenar son: Nombre Del Tipo Tamaño (bytes) Rango de valores sbyte 1 -128 a 127 byte 1 0 a 255 short 2 -32768 a 32767 ushort 2 0 a 65535 int 4 -2147483648 a 2147483647 uint 4 0 a 4294967295 long 8 -9223372036854775808 a 9223372036854775807 ulong 8 0 a 18446744073709551615 Como se puede observar en esta tabla, el tipo de dato más razonable para guardar edades sería "byte", que permite valores entre 0 y 255, y ocupa 3 bytes menos que un "int".
Conversiones de cadena a entero - 31 -
Programación de Dispositivos Móviles
Si queremos obtener estos datos a partir de una cadena de texto, no siempre nos servirá Convert.ToInt32, porque no todos los datos son enteros de 32 bits (4 bytes). Para datos de tipo "byte" usaríamos Convert.ToByte (sin signo) y ToSByte (con signo), para datos de 2 bytes tenemos ToInt16 (con signo) y ToUInt16 (sin signo), y para los de 8 bytes existen ToInt64 (con signo) y ToUInt64 (sin signo).
Incremento y decremento Conocemos la forma de realizar las operaciones aritméticas más habituales. Peor también existe una operación que es muy frecuente cuando se crean programas, y q ue no tiene un símbolo específico para representarla en matemáticas: incrementar el valor de una variable en una unidad: a = a + 1;
Pues bien, en C#, existe una notación más compacta para esta operación, y para la opuesta (el decremento): a++; a--;
es lo mismo que es lo mismo que
a = a+1; a = a-1;
Pero esto tiene más misterio todavía del que puede parecer en un primer vistazo: podemos distinguir entre "preincremento" y "postincremento". En C# es posible hacer asignaciones como b = a++;
Así, si "a" valía 2, lo que esta instrucción hace es dar a "b" el valor de "a" y aumentar el valor de "a". Por tanto, al final tenemos que b=2 y a=3 (postincremento: se incrementa "a" tras asignar su valor). En cambio, si escribimos b = ++a;
y "a" valía 2, primero aumentamos "a" y luego los asignamos a "b" (preincremento), de modo que a=3 y b=3. Por supuesto, también podemos distinguir postdecremento (a--) y predecremento (--a). Y ya que estamos hablando de las asignaciones, hay que comentar que en C# es posible hacer asignaciones múltiples: a = b = c = 1;
Operaciones abreviadas: += Pero aún hay más. Tenemos incluso formas reducidas de escribir cosas como "a = a+5". Allá van a a a a a
+= -= *= /= %=
b b b b b
; ; ; ; ;
es es es es es
lo lo lo lo lo
mismo mismo mismo mismo mismo
que que que que que
a a a a a
= = = = =
a+b; a-b; a*b; a/b; a%b;
Tipo de datos real Cuando queremos almacenar datos con decimales, no nos sirve el tipo de datos "int". Necesitamos otro tipo de datos que sí esté preparado para guardar números "reales" (con decimales). En el mundo de la informática hay dos formas de trabajar con números reales: Coma fija: el número máximo de cifras decimales está fijado de antemano, y el número de cifras enteras también. Por ejemplo, con un formato de 3 cifras enteras y 4 cifras decimales, el número 3,75 se almacenaría correctamente, el número 970,4361 también, pero el 5,678642 se guardaría como 5 ,6786 (se perdería a partir de l a cuarta cifra decimal) y el 1010 no se podría guardar (tiene más d e 3 cifras enteras). • Coma flotante: el número de decimales y de cifras enteras permitido es variable, lo que importa es el número de cifras significativas (a partir del último 0). Por ejemplo, con 5 cifras significativas se podrían almacenar números como el 13405000000 o como el 0,0000007349 pero no se guardaría correctamente el 12,0000034, que se redondearía a un número cercano. •
Simple y doble precisión Tenemos tres tamaños para elegir, según si queremos guardar números con mayor cantidad de cifras o con menos. Para números con pocas cifras significativas (un máximo de 7, lo que se conoce como "un dato real de simple precisión") existe el tipo "float" y para números
- 32 -
Programación de Dispositivos Móviles
que necesiten más precisión (unas 15 cifras, "doble precisión") tenemos el tipo "double". En C# existe un tercer tipo de números reales, con mayor precisión todavía, que es el tipo "decimal": float Tamaño en bits 32 Valor más pequeño -1,5•10-45 Valor más grande 3,4•1038 Cifras significativas 7
double 64 5,0•10-324 1,7•10308 15-16
decimal 128 1,0•10-28 7,9•1028 28-29
Para definirlos, se hace igual que en el caso de los números enteros: float x;
o bien, si queremos dar un valor inicial en el momento de definirlos (recordando que para las cifras decimales no debemos usar una coma, sino un punto): float x = 12.56;
Mostrar en pantalla números reales Al igual que hacíamos con los enteros, podemos leer como cadena de texto, y convertir cuando vayamos a realizar operaciones aritméticas. Ahora usaremos Convert.ToDouble cuando se trate de un dato de doble precisión y Convert.ToSingle cuando sea un dato de simple precisión (float): using System; public class Ejemplo05 { public static void Main() { float primerNumero; float segundoNumero; float suma; Console.WriteLine("Introduce el primer número"); primerNumero = Convert.ToSingle(Console.ReadLine()); Console.WriteLine("Introduce el segundo número" ); segundoNumero = Convert.ToSingle(Console.ReadLine()); suma = primerNumero + segundoNumero; Console.WriteLine("La suma de {0} y {1} es {2}" , primerNumero, segundoNumero, suma); } }
Cuidado al probar este programa: aunque en el fuente debemos escribir los decimales usando un punto, como 123.456, al poner el ejecutable en marcha parte del trabajo se le encarga al sistema operativo, de modo que si éste sabe que en nuestro país se usa la coma para los decimales, considere la coma el separador correcto y no el punto, como ocurre si introducimos estos datos en la versión española de Windows XP: ejemplo05 Introduce el primer número 23,6 Introduce el segundo número 34.2 La suma de 23,6 y 342 es 365,6
Formatear números En más de una ocasión nos interesará formatear los números para mostrar una cierta cantidad de decimales: por ejemplo, nos puede interesar que una cifra que corresponde a dinero se muestre siempre con dos cifras decimales, o que una nota se muestre redondeada, sin decimales. Una forma de conseguirlo es crear una cadena de texto a partir del número, usando "ToString". A esta orden se le puede indicar un dato adicional, que es el formato numérico que queremos usar, por ejemplo: suma.ToString("0.00")
- 33 -
Programación de Dispositivos Móviles
Algunos de los códigos de formato que se pueden usar son: Un cero (0) indica una posición en la que debe aparecer un número, y se mostrará un 0 si no hay ninguno. • Una almohadilla (#) indica una posición en la que puede aparecer un número, y no se escribirá nada si no hay número. • Un punto (.) indica la posición en la que d eberá aparecer la coma decimal. • Alternativamente, se pueden usar otros formatos abreviados: por ejemplo, N2 quiere decir "con dos cifras decimales" y N5 es "con cinco cifras decimales". •
Vamos a probarlos en un ejemplo: using System; public class Ejemplo06 { public static void Main() { double numero = 12.34; Console.WriteLine( Console.WriteLine( Console.WriteLine( Console.WriteLine( Console.WriteLine( Console.WriteLine(
numero.ToString("N1") ); numero.ToString("N3") ); numero.ToString("0.0") ); numero.ToString("0.000") ); numero.ToString("#.#") ); numero.ToString("#.###") );
} }
El resultado de este ejemplo sería: 12,3 12,340 12,3 12,340 12,3 12,34
Como se puede ver, ocurre lo siguiente: Si indicamos menos decimales de los que tiene el número, se redondea. • Si indicamos más decimales de los que tiene el número, se mostrarán ceros si usamos como formato Nx o 0.000, y no se mostrará nada si usamos #.### • Si indicamos menos cifras antes de la coma decimal de las que realmente tiene el número, aun así se muestran todas ellas. •
Ejercicios propuestos:
4. El usuario de nuestro programa podrá teclear dos números enteros. El programa deberá mostrar el resultado de dividir 5.
el primer número entre el segundo, utilizando dos cifras decimales. Crear un programa que use tres variables x,y,z. Las tres serán números reales, y nos bastará con dos cifras decimales. Deberá pedir al usuario los valores para las tres variables y mostrar en pantalla el valor de x 2 + y - z (con exactamente dos cifras decimales).
Tipo de datos carácter También tenemos un tipo de datos que nos permite almacenar una única letra, el tipo "char": char letra;
Asignar valores es sencillo: el valor se indica entre comillas simples letra = 'a';
- 34 -
Programación de Dispositivos Móviles
Para leer valores desde teclado, lo podemos hacer de forma similar a los casos anteriores: leemos toda una frase con ReadLine y convertimos a tipo "char" usando Convert.ToChar: letra = Convert.ToChar(Console.ReadLine());
Así, un programa que de un valor inicial a una l etra, la muestre, lea una nueva letra tecleada por el usuario, y la muestre, podría ser: using System; public class Ejemplo07 { public static void Main() { char letra; letra = 'a'; Console.WriteLine("La letra es {0}", letra); Console.WriteLine("Introduce una nueva letra"); letra = Convert.ToChar(Console.ReadLine()); Console.WriteLine("Ahora la letra es {0}", letra); } }
Secuencias de escape: \n y otras. Como hemos visto, los textos que aparecen en pantalla se escriben con WriteLine, indicados entre paréntesis y entre comillas dobles. Entonces surge una dificultad: ¿cómo escribimos una comilla doble en pantalla? La forma de conseguirlo es usando ciertos caracteres especiales, lo que se conoce como "secuencias de escape". Existen ciertos caracteres especiales que se pueden escribir después de una barra invertida (\) y que nos permiten conseguir escribir esas comillas dobles y algún otro carácter poco habitual. Por ejemplo, con \" se escribirán unas comillas dobles, y con \' unas comillas simples, o con \n se avanzará a la línea siguiente de pantalla. Estas secuencias especiales son las siguientes: Secuencia Significado \a Emite un pitido \b Retroceso (permite borrar el último carácter) \f Avance de página (expulsa una hoja en la impresora) \n Avanza de línea (salta a la línea siguiente) \r Retorno de carro (va al principio de la línea) \t Salto de tabulación horizontal \v Salto de tabulación vertical \' Muestra una comilla simple \" Muestra una comilla doble \\ Muestra una barra invertida \0 Carácter nulo (NULL) Vamos a ver un ejemplo que use los más habituales: using System; public class Ejemplo08 { public static void Main() { Console.WriteLine("Esta es una frase"); Console.WriteLine(); Console.WriteLine(); Console.WriteLine("y esta es otra, separada dos lineas" ); Console.WriteLine("\n\nJuguemos mas:\n\notro salto"); Console.WriteLine("Comillas dobles: \" y simples \', y barra \\"); } }
- 35 -
Programación de Dispositivos Móviles
Su resultado sería este: Esta es una frase y esta es otra, separada dos lineas Juguemos mas: otro salto Comillas dobles: " y simples ', y barra \
En algunas ocasiones puede ser incómodo manipular estas secuencias de escape. Por ejemplo, cuando usemos estructuras de directorios: c:\\datos\\ejemplos\\curso\\ejemplo1. En este caso, se puede usar una arroba (@) antes del texto sin las barras invertidas: ruta = @"c:\datos\ejemplos\curso\ejemplo1"
En este caso, el problema está si aparecen comillas en medio de la cadena. Para solucionarlo, se duplican las comillas, así: orden = @"copy ""documento de ejemplo"" f:"
Toma de contacto con las cadenas de texto Las cadenas de texto son tan fáciles de manejar como los demás tipos de datos que hemos visto, con apenas tres diferencias: • • •
Se declaran con "string". Si queremos dar un valor inicial, éste se indica entre comillas dobles. Cuando leemos con ReadLine, no hace falta convertir el valor obtenido.
Así, un ejemplo que diera un valor a un "string", lo mostrara (entre comillas, para practicar las secuencias de escape que hemos visto en el apartado anterior) y leyera un valor tecleado por el usuario podría ser: using System; public class Ejemplo09 { public static void Main() { string frase; frase = "Hola, como estas?"; Console.WriteLine("La frase es \"{0}\"", frase); Console.WriteLine("Introduce una nueva frase"); frase = Console.ReadLine(); Console.WriteLine("Ahora la frase es \"{0}\"", frase); } }
Se pueden hacer muchas más operaciones sobre cadenas de texto: convertir a mayúsculas o a minúsculas, eliminar espacios, cambiar una subcadena por otra, dividir en trozos, etc. Pero ya volveremos a las cadenas más adelante, cuando conozcamos las estructuras de control básicas.
Ejercicios propuestos: 6.
Realizar un programa el cúal se deben de introducir los datos del usuario en las variables que se van a declarar a continuacón: Nombre, Domicilio y Teléfono de tipo string; Sexo de carácter; Edad de tipo numérico; después, mostrar el valor de las variables en pantalla.
Los valores "booleanos" En C# tenemos también un tipo de datos llamado "booleano" ("bool"), que puede tomar dos valores: verdadero o falso: bool encontrado; encontrado = true;
- 36 -
Programación de Dispositivos Móviles
Este tipo de datos hará que podamos escribir de forma sencilla algunas condiciones que podrían resultar complejas, pero eso lo veremos en el tema 3.
Estructuras de control
Estructuras alternativas if Vamos a ver cómo podemos comprobar si se cumplen condiciones. La primera construcción que usaremos será "si ... entonces ...". El formato es if (condición) sentencia;
Vamos a verlo con un ejemplo: /*---------------------------*/ /* Ejemplo en C# nº 10: */ /* ejemplo10.cs */ /* */ /* Condiciones con if */ /*---------------------------*/ using System; public class Ejemplo10 { public static void Main() { int numero; Console.WriteLine("Introduce un número"); numero = Convert.ToInt32(Console.ReadLine()); if (numero>0) Console.WriteLine("El número es positivo."); } }
Este programa pide un número al usuario. Si es positivo (mayor que 0), escribe en pantalla "El número es positivo."; si es negativo o cero, no hace nada. Como se ve en el ejemplo, para comprobar si un valor numérico es mayor que otro, usamos el símbolo ">". Para ver si dos valores son iguales, usaremos dos símbolos de "igual": if (numero==0). Las demás posibilidades las veremos algo más adelante. En todos los casos, la condición que queremos comprobar deberá indicarse entre paréntesis. Este programa comienza por un comentario que nos recuerda de qué se trata. Como nuestros fuentes irán siendo cada vez más complejos, a partir de ahora incluiremos comentarios que nos permitan recordar de un vistazo qué pretendíamos hacer.
if y sentencias compuestas Habíamos dicho que el formato básico de "if" es if (condición) sentencia; Esa "sentencia" que se ejecuta si se cumple la condición puede ser una sentencia simple o una compuesta. Las sentencias compuestas se forman agrupando varias sentencias simples entre llaves ( { y } ), como en este ejemplo: /*---------------------------*/ /* Ejemplo en C# nº 11: */ /* ejemplo11.cs */ /* */ /* Condiciones con if (2) */ /* Sentencias compuestas */ /*---------------------------*/ using System; public class Ejemplo11 { public static void Main() { int numero; Console.WriteLine("Introduce un número"); numero = Convert.ToInt32(Console.ReadLine());
- 37 -
Programación de Dispositivos Móviles
if (numero>0) { Console.WriteLine("El número es positivo."); Console.WriteLine("Recuerde que también puede usar negativos." ); } /* Aqui acaba el "if" */ } }
/* Aqui acaba "Main" */ /* Aqui acaba "Ejemplo11" */
En este caso, si el número es negativo, se hacen dos cosas: escribir un texto y luego... ¡escribir otro! (Claramente, en este ejemplo, esos dos "WriteLine" podrían ser uno solo, en el que los dos textos estuvieran separados por un "\n"; más adelante iremos encontrando casos en lo que necesitemos hacer cosas "más serias" dentro de una sentencia compuesta). Como se ve en este ejemplo, cada nuevo "bloque" se suele escribir un poco más a la derecha que los anteriores, para que sea fácil ver dónde comienza y termina cada sección de un programa. Por ejemplo, el contenido de "Ejemplo11" está un poco más a la derecha que la cabecera "public class Ejemplo11", y el contenido de "Main" algo más a la derecha, y la sentencia compuesta que se debe realizar si se cumple la condición del "if" está algo más a la derecha que la orden "if". Este "sangrado" del texto se suele llamar "escritura indentada". Un tamaño habitual para el sangrado es de 4 espacios, aunque en este texto habitualmente usaremos sólo dos espacios, para no llegar al margen derecho del papel con mucha facilidad.
Operadores relacionales: <, <=, >, >=, ==, != Hemos visto que el símbolo ">" es el que se usa para comprobar si un número es mayor que otro. El símbolo de "menor que" también es sencillo, pero los demás son un poco menos evidentes, así que vamos a verlos: Operador Operación < Menor que > Mayor que <= Menor o igual que >= Mayor o igual que == Igual a != No igual a (distinto de) Y un ejemplo, que diga si un número NO ES cero: /*---------------------------*/ /* Ejemplo en C# nº 12: */ /* ejemplo12.cs */ /* */ /* Condiciones con if (3) */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo12 { public static void Main() { int numero; Console.WriteLine("Introduce un número"); numero = Convert.ToInt32(Console.ReadLine()); if (numero != 0) Console.WriteLine("El número no es cero."); } }
Ejercicios propuestos: 7.
8.
Crear un programa que multiplique dos números enteros de la siguiente forma: pedirá al usuario un primer número entero. Si el número que se que teclee es 0, escribirá en pantalla "El producto de 0 por cualquier número es 0". Si se ha tecleado un número distinto de cero, se pedirá al usuario un segundo número y se mostrará el producto de ambos. Crear un programa que pida al usuario dos números reales. Si el segundo no es cero, mostrará el resultado de dividir entre el primero y el segundo. Por el contrario, si el segundo n úmero es cero, escribirá "Error: No se puede dividir entre cero".
- 38 -
Programación de Dispositivos Móviles
if-else Podemos indicar lo que queremos que ocurra en caso de que no se cumpla la condición, usando la orden "else" (en caso contrario), así: /*---------------------------*/ /* Ejemplo en C# nº 13: */ /* ejemplo13.cs */ /* */ /* Condiciones con if (4) */ /*---------------------------*/ using System; public class Ejemplo13 { public static void Main() { int numero; Console.WriteLine("Introduce un número"); numero = Convert.ToInt32(Console.ReadLine()); if (numero > 0) Console.WriteLine("El número es positivo."); else Console.WriteLine("El número es cero o negativo."); } }
Podríamos intentar evitar el uso de "else" si utilizamos un "if" a continuación de otro, así: /*---------------------------*/ /* Ejemplo en C# nº 14: */ /* ejemplo14.cs */ /* */ /* Condiciones con if (5) */ /*---------------------------*/ using System; public class Ejemplo14 { public static void Main() { int numero; Console.WriteLine("Introduce un número"); numero = Convert.ToInt32(Console.ReadLine()); if (numero > 0) Console.WriteLine("El número es positivo."); if (numero <= 0) Console.WriteLine("El número es cero o negativo."); } }
Pero el comportamiento no es el mismo: en el primer caso (ejemplo 13) se mira si el valor es positivo; si no lo es, se pasa a la segunda orden, pero si lo es, el programa ya ha terminado. En el segundo caso (ejemplo 14), aunque el número sea positivo, se vuelve a realizar la segunda comprobación para ver si es negativo o cero, por lo que el programa es algo más lento. Podemos enlazar los "if" usando "else", para decir "si no se cumple esta condición, mira a ver si se cumple esta otra": /*---------------------------*/ /* Ejemplo en C# nº 15: */ /* ejemplo15.cs */ /* */ /* Condiciones con if (6) */ /*---------------------------*/ using System; public class Ejemplo15 { public static void Main() {
- 39 -
Programación de Dispositivos Móviles
int numero; Console. WriteLine( Console.WriteLine ("Introduce un número") número" ); numero = Convert. Convert.ToInt32 ToInt32( (Console. Console.ReadLine ReadLine()) ()); ; if (numero > 0) Console.WriteLine Console. WriteLine( ("El número es positivo.") positivo." ); else if (numero < 0) Console.WriteLine Console. WriteLine( ("El número es negativo." ); else Console.WriteLine Console. WriteLine( ("El número es cero.") cero." ); } }
Operadores lógicos: &&, ||, ! Estas condiciones se puede encadenar con "y", "o", etc., que se indican de la siguiente forma Operador && || !
Significado Y O No
De modo que podremos escribir cosas como if ((opcion ((opcion== ==1 1) && (usuario usuario== ==2 2)) ... if ((opcion ((opcion== ==1 1) || (opcion opcion== ==3 3)) ... if ((! ((!(opcion opcion== ==opcCorrecta opcCorrecta)) )) || (tecla tecla== ==ESC ESC)) ))
...
Ejercicios propuestos: 9. Crear Crear un program programa a que pida pida una letra letra al al usuario usuario y diga diga si se trat trata a de una vocal. vocal. 10. Crear un programa programa que pida al usuario dos números enteros enteros cortos y diga si son iguales o, en caso contrario, contrario, cuál es el mayor de ellos.
El peligro de la asignación en un "if" Cuidado con el operador de igualdad: hay que recordar que el formato es "if (a==b)" Si no nos acordamos y escribimos "if (a=b)", estamos intentando asignar a "a" el valor de "b". En algunos compiladores de lenguaje C, esto podría ser un problema serio, porque se considera válido hacer una asignación dentro de un "if" (aunque la mayoría de compiladores modernos nos avisarían de que quizá estemos asignando un valor por error). En el caso del lenguaje C#, la "condición" debe ser algo cuyo resultado sea "verdadero" o "falso" (un dato de tipo "bool"), de modo que obtendríamos un mensaje de error "Cannot implicitly convert type 'int' to 'bool'" (no puedo convertir un "int" a "bool"), en casos como el del siguiente programa: /*---------------------------*/ /* Ejemplo en C# nº 16: */ /* ejemplo16.cs */ /* */ /* Condiciones con if (7) */ /* comparacion incorrecta */ /*---------------------------*/ using System System; ; public class Ejemplo16 { public static void Main Main() () { int numero; Console. WriteLine( Console.WriteLine ("Introduce un número") número" ); numero = Convert. Convert.ToInt32 ToInt32( (Console. Console.ReadLine ReadLine()) ()); ; if (numero = 0) Console.WriteLine Console. WriteLine( ("El número es cero.") cero." ); else if (numero < 0)
- 40 -
Programación de Dispositivos Móviles
Console.WriteLine Console. WriteLine( ("El número es negativo." ); else Console.WriteLine Console. WriteLine( ("El número es positivo." ); } }
Comparaciones y variables de tipo "bool" En muchas ocasiones, cuando las condiciones que aparecen dentro de un "if" sean complejas, el programa resultará más legible si empleamos variables de tipo "bool" (verdadero o falso), de forma que ciertos fragmentos de nuestro programa no sean "if ((vidas==0) || (tiempo == 0) || ((enemigos ==0) && (nivel == ultimoNivel)))" sino simplemente "if (partidaTerminada) ..." A las variables "bool" se les da un valor casi como a cualquier otra variable, con la diferencia de que en la parte derecha de la asignación aparece "true" , "false" o una comparación, así: partidaTerminada = false; partidaTerminada = (enemigos == ==0 0) && (nivel == ultimoNivel ultimoNivel) ); if (vidas == ==0 0) partidaTerminada = true;
Lo emplearemos a partir de ahora en los fuentes que usen condiciones un poco complejas. Un ejemplo que pida una letra y diga si es una vocal, una cifra numérica u otro símbolo, usando variables "bool" podría ser: /*---------------------------*/ /* Ejemplo en C# nº 17: */ /* ejemplo17.cs */ /* */ /* Condiciones con if (8) */ /* Variables bool */ /*---------------------------*/ using System System; ; public class Ejemplo17 { public static void Main Main() () { char letra; bool esVocal, esCifra; Console. WriteLine( Console.WriteLine ("Introduce una letra") letra" ); letra = Convert. Convert.ToChar ToChar( (Console. Console.ReadLine ReadLine()) ()); ; esCifra = (letra >= '0' '0') ) && (letra <= <='9' '9') ); esVocal = (letra == 'a') 'a') || (letra == 'e' 'e') ) || (letra == 'i' 'i') ) || (letra == 'o' 'o') ) || (letra == 'u' 'u') ); if (esCifra esCifra) ) Console.WriteLine Console. WriteLine( ("Es una cifra numérica.") numérica." ); else if (esVocal esVocal) ) Console.WriteLine Console. WriteLine( ("Es una vocal.") vocal." ); else Console.WriteLine Console. WriteLine( ("Es una consonante u otro número." ); } }
Operador condicional: ? En C# hay otra forma de asignar un valor según se dé u na condición o no. Es el "operador condicional" ? : que se usa nombreVariable = condicion ?
valor1 : valor2;
y equivale a decir "si se cumple la condición, toma el valor v1; si no, toma el valor v2". Un ejemplo de cómo podríamos usarlo sería numeroMayor = (a>b) ?
a : b;
que, aplicado a un programa sencillo, podría ser
- 41 -
Programación de Dispositivos Móviles
/*---------------------------*/ /* Ejemplo en C# nº 18: */ /* ejemplo18.cs */ /* */ /* El operador condicional */ /*---------------------------*/ using System System; ; public class Ejemplo18 { public static void Main Main() () { int a, b, mayor; Console. Write( Console.Write ("Escriba un número: ") " ); a = Convert. Convert.ToInt32 ToInt32( (Console. Console.ReadLine ReadLine()) ()); ; Console.Write Console. Write( ("Escriba otro: ") " ); b = Convert. Convert.ToInt32 ToInt32( (Console. Console.ReadLine ReadLine()) ()); ; mayor = (a>b) ?
a : b;
Console.WriteLine Console. WriteLine( ("El mayor de los números es {0}.", {0}." , mayor) mayor); } }
(La orden Console.Write, empleada en el ejemplo anterior, escribe un texto sin avanzar a la línea siguiente, de modo que el próximo texto que escribamos –o introduzcamos- quedará a continuación de éste). Un segundo ejemplo, que sume o reste dos números según la opción que se escoja, sería: /*---------------------------*/ /* Ejemplo en C# nº 19: */ /* ejemplo19.cs */ /* */ /* Operador condicional - 2 */ /*---------------------------*/ using System System; ; public class Ejemplo19 { public static void Main Main() () { int a, b, operacion, resultado; Console. Write( Console.Write ("Escriba un número: ") " ); a = Convert. Convert.ToInt32 ToInt32( (Console. Console.ReadLine ReadLine()) ()); ; Console.Write Console. Write( ("Escriba otro: ") " ); b = Convert. Convert.ToInt32 ToInt32( (Console. Console.ReadLine ReadLine()) ()); ; Console.Write Console. Write( ("Escriba una operación (1 = resta; otro = suma): " ); operacion = Convert. Convert.ToInt32 ToInt32( (Console. Console.ReadLine ReadLine()) ()); ; resultado = (operacion == 1) ?
a-b : a+b;
Console.WriteLine Console. WriteLine( ("El resultado es {0}. \n", resultado) resultado); } }
Ejercicios propuestos: 11. Crear Crear un progr programa ama que use el operador condicion condicional al para mostrar mostrar un el valor absoluto absoluto de un número de la sigui siguiente ente forma: si el número es positivo, se mostrará tal cual; si es negativo, se mostrará cambiado de signo. 12. Crear un programa programa que use el operador operador condicional para dar a una variable llamada llamada "iguales" (booleana) el valor "true" si los dos números que ha tecleado el usuario son iguales, o "false" si son distintos.
- 42 -
Programación de Dispositivos Móviles
switch Si queremos ver varios posibles valores, sería muy pesado tener que hacerlo con muchos "if" seguidos o encadenados. La alternativa es la orden "switch", cuya sintaxis es switch (expresión) { case valor1: sentencia1; break; case valor2: sentencia2; sentencia2b; break; ... case valorN: sentenciaN; break; default: otraSentencia; break; };
Es decir, se escribe tras "switch" la expresión a analizar, entre paréntesis. Después, tras varias órdenes "case" se indica cada uno de los valores posibles. Los pasos (porque pueden ser varios) que se deben dar si se trata de ese valor se indican a continuación, terminando con "break". Si hay que hacer algo en caso de que no se cumpla ninguna de las condiciones, se detalla tras "default". Si dos casos tienen que hacer lo mismo, se añade "goto case" a uno de ellos para indicarlo. Vamos con un ejemplo, que diga si la tecla que pulsa el usuario es una cifra numérica, un espacio u otro símbolo: /*---------------------------*/ /* Ejemplo en C# nº 20: */ /* ejemplo20.cs */ /* */ /* La orden "switch" (1) */ /*---------------------------*/ using System; public class Ejemplo20 { public static void Main() { char letra; Console.WriteLine("Introduce una letra"); letra = Convert.ToChar(Console.ReadLine()); switch (letra) { case ' ': Console.WriteLine("Espacio."); break; case '1': goto case '0'; case '2': goto case '0'; case '3': goto case '0'; case '4': goto case '0'; case '5': goto case '0'; case '6': goto case '0'; case '7': goto case '0'; case '8': goto case '0'; case '9': goto case '0'; case '0': Console.WriteLine("Dígito."); break; default: Console.WriteLine("Ni espacio ni dígito."); break; } } }
Cuidado quien venga del lenguaje C: en C se puede dejar que un caso sea manejado por el siguiente, lo que se consigue si no se usa "break", mientras que C# siempre obliga a usar "break" o "goto" en cada caso, para evitar errores difíciles de d etectar. El fragmento anterior en C habría sido:
- 43 -
Programación de Dispositivos Móviles
switch (tecla) { case ' ': printf("Espacio. \n"); break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': printf("Dígito. \n"); break; default: printf("Ni espacio ni dígito. \n"); }
En el lenguaje C, que es más antiguo, sólo se podía usar "switch" para comprobar valores de variables "simples" (numéricas y caracteres); en C#, que es un lenguaje más evolucionado, se puede usar también para comprobar valores de "strings". Por ejemplo, un programa que nos salude de forma personalizada si somos "Juan" o "Pedro" podría ser: /*---------------------------*/ /* Ejemplo en C# nº 21: */ /* ejemplo21.cs */ /* */ /* La orden "switch" (2) */ /*---------------------------*/ using System; public class Ejemplo21 { public static void Main() { string nombre; Console.WriteLine("Introduce tu nombre"); nombre = Console.ReadLine(); switch (nombre) { case "Juan": Console.WriteLine("Bienvenido, Juan."); break; case "Pedro": Console.WriteLine("Que tal estas, Pedro." ); break; default: Console.WriteLine("Procede con cautela, desconocido."); break; } } }
Ejercicios propuestos: 13. Crear un programa que lea una letra tecleada por el usuario y diga si se trata de una vocal, una cifra numérica o una consonante. 14. Crear un programa que lea una letra tecleada por el usuario y diga si se trata de un signo de puntuación, una cifra numérica o algún otro carácter.
Estructuras repetitivas Hemos visto cómo comprobar condiciones, pero no cómo hacer que una cierta parte de un programa se repita un cierto número de veces o mientras se cumpla una condición (lo que llamaremos un "bucle"). En C# tenemos varias formas de conseguirlo.
while Si queremos hacer que una sección de nuestro programa se repita mientras se cumpla una cierta condición, usaremos la orden "while". Esta orden tiene dos formatos distintos, según comprobemos la condición al principio o al final.
- 44 -
Programación de Dispositivos Móviles
En el primer caso, su sintaxis es while (condición) sentencia;
Es decir, la sentencia se repetirá mientras la condición sea cierta. Si la condición es falsa ya desde un principio, la sentencia no se ejecuta nunca. Si queremos que se repita más de una sentencia, basta agruparlas entre { y }. Un ejemplo que nos diga si cada número que tecleemos es positivo o negativo, y que pare cuando tecleemos el número 0, podría ser: /*---------------------------*/ /* Ejemplo en C# nº 22: */ /* ejemplo22.cs */ /* */ /* La orden "while" */ /*---------------------------*/ using System; public class Ejemplo22 { public static void Main() { int numero; Console.Write("Teclea un número (0 para salir): "); numero = Convert.ToInt32(Console.ReadLine()); while (numero != 0) { if (numero > 0) Console.WriteLine("Es positivo"); else Console.WriteLine("Es negativo"); Console.WriteLine("Teclea otro número (0 para salir): " ); numero = Convert.ToInt32(Console.ReadLine()); } } }
En este ejemplo, si se introduce 0 la primera vez, la condición es falsa y ni siquiera se entra al bloque del "while", terminando el programa inmediatamente.
Ejercicios propuestos: 15. Crear un programa que pida al usuario su contraseña. Deberá terminar cuando introduzca como contraseña la palabra "clave", pero volvérsela a pedir tantas veces como sea necesario. 16. Crea un programa que escriba en pantalla los números del 1 al 10, usando "while". 17. Crea un programa que escriba en pantalla los números pares del 26 al 10 (descendiendo), usando "while". 18. Crear un programa calcule cuantas cifras tiene un número entero positivo (pista: se puede hacer dividiendo varias veces entre 10).
do ... while Este es el otro formato que puede tener la orden "while": la condición se comprueba al final. El punto en que comienza a repetirse se indica con la orden "do", así: do sentencia; while (condición);
Al igual que en el caso anterior, si queremos que se repitan varias órdenes (es lo habitual), deberemos encerrarlas entre llaves. Como ejemplo, vamos a ver cómo sería el típico programa que nos pide una clave de acceso y no nos deja entrar hasta que tecleemos la clave correcta:
- 45 -
Programación de Dispositivos Móviles
/*---------------------------*/ /* Ejemplo en C# nº 23: */ /* ejemplo23.cs */ /* */ /* La orden "do..while" */ /*---------------------------*/ using System; public class Ejemplo23 { public static void Main() { int valida = 711; int clave; do { Console.Write("Introduzca su clave numérica: " ); clave = Convert.ToInt32(Console.ReadLine()); if (clave != valida) Console.WriteLine("No válida!\n"); } while (clave != valida); Console.WriteLine("Aceptada. \n"); } }
En este caso, se comprueba la condición al final, de modo que se nos preguntará la clave al menos una vez. Mientras que la respuesta que demos no sea la correcta, se nos vuelve a preguntar. Finalmente, cuando tecleamos la clave correcta, el ordenador escribe "Aceptada" y termina el programa. Si preferimos que la clave sea un texto en vez de un número, los cambios al programa son mínimos: /*---------------------------*/ /* Ejemplo en C# nº 24: */ /* ejemplo24.cs */ /* */ /* La orden "do..while" (2) */ /*---------------------------*/ using System; public class Ejemplo24 { public static void Main() { string valida = "secreto"; string clave; do { Console.Write("Introduzca su clave: "); clave = Console.ReadLine(); if (clave != valida) Console.WriteLine("No válida!\n"); } while (clave != valida); Console.WriteLine("Aceptada. \n"); } }
Ejercicios propuestos: 19. Crear un programa que pida números positivos al usuario, y vaya calculando la suma de todos ellos (terminará cuando se teclea un número negativo o cero).
- 46 -
Programación de Dispositivos Móviles
20. Crea un programa que escriba en pantalla los números del 1 al 10, usando "do..while". 21. Crea un programa que escriba en pantalla los números pares del 26 al 10 (descendiendo), usando "do..while". 22. Crea un programa que pida al usuario su nombre y su contraseña, y no le permita seguir hasta que introduzca como nombre "Pedro" y como contraseña "Peter".
for Ésta es la orden que usaremos habitualmente para crear partes del programa que se repitan un cierto número de veces. El formato de "for" es for (valorInicial; CondiciónRepetición; Incremento ) Sentencia;
Así, para contar del 1 al 10, tendríamos 1 como valor inicial, <=10 como condición de repetición, y el incremento sería de 1 en 1. Por tanto, el programa quedaría: /*---------------------------*/ /* Ejemplo en C# nº 25: */ /* ejemplo25.cs */ /* */ /* Uso básico de "for" */ /*---------------------------*/ using System; public class Ejemplo25 { public static void Main() { int contador; for (contador=1; contador<=10; contador++) Console.Write("{0} ", contador); } }
Recordemos que "contador++" es una forma abreviada de escribir "contador=contador+1", de modo que en este ejemplo aumentamos la variable de uno en uno.
Ejercicios propuestos: 23. Crear un programa que muestre los números del 15 al 5, descendiendo (pista: en cada pasada habrá que descontar 1, por ejemplo haciendo i--). 24. Crear un programa que muestre los primeros ocho n úmeros pares (pista: en cada pasada habrá que aumentar de 2 en 2, o bien mostrar el doble del valor que hace de contador). En un "for", realmente, la parte que hemos llamado "Incremento" no tiene por qué incrementar la variable, aunque ése es su uso más habitual. Es simplemente una orden que se ejecuta cuando se termine la "Sentencia" y antes de volver a comprobar si todavía se cumple la condición de repetición. Por eso, si escribimos la siguiente línea: for (contador=1; contador<=10; )
la variable "contador" no se incrementa nunca, por lo que nunca se cumplirá la condición de salida: nos quedamos encerrados dando vueltas dentro de la orden que siga al "for". Un caso todavía más exagerado de algo a lo que se entra y de lo que no se sale sería la siguiente orden: for ( ; ; )
Los bucles "for" se pueden anidar (incluir uno dentro de otro), de modo que podríamos escribir las tablas de multiplicar del 1 al 5 con:
- 47 -
Programación de Dispositivos Móviles
/*---------------------------*/ /* Ejemplo en C# nº 26: */ /* ejemplo26.cs */ /* */ /* "for" anidados */ /*---------------------------*/ using System; public class Ejemplo26 { public static void Main() { int tabla, numero; for (tabla=1; tabla<=5; tabla++) for (numero=1; numero<=10; numero++) Console.WriteLine("{0} por {1} es {2}", tabla, numero, tabla*numero); } }
En estos ejemplos que hemos visto, después de "for" había una única sentencia. Si queremos que se hagan varias cosas, basta definirlas como un bloque (una sentencia compuesta) encerrándolas entre llaves. Por ejemplo, si queremos mejorar el ejemplo anterior haciendo que deje una línea en blanco entre tabla y tabla, sería: /*---------------------------*/ /* Ejemplo en C# nº 27: */ /* ejemplo27.cs */ /* */ /* "for" anidados (2) */ /*---------------------------*/ using System; public class Ejemplo27 { public static void Main() { int tabla, numero; for (tabla=1; tabla<=5; tabla++) { for (numero=1; numero<=10; numero++) Console.WriteLine("{0} por {1} es {2}", tabla, numero, tabla*numero); Console.WriteLine(); } } }
Para "contar" no necesariamente hay que usar números. Por ejemplo, podemos contar con letras así: /*---------------------------*/ /* Ejemplo en C# nº 28: */ /* ejemplo28.cs */ /* */ /* "for" que usa "char" */ /*---------------------------*/ using System; public class Ejemplo28 { public static void Main() {
- 48 -
Programación de Dispositivos Móviles
char letra; for (letra='a'; letra<='z'; letra++) Console.Write("{0} ", letra); } }
En este caso, empezamos en la "a" y terminamos en la "z", aumentando de uno en uno. Si queremos contar de forma decreciente, o de dos en dos, o como nos interese, basta indicarlo en la condición de finalización del "for" y en la parte que lo incrementa: /*---------------------------*/ /* Ejemplo en C# nº 29: */ /* ejemplo29.cs */ /* */ /* "for" que descuenta */ /*---------------------------*/ using System; public class Ejemplo29 { public static void Main() { char letra; for (letra='z'; letra>='a'; letra--) Console.Write("{0} ", letra); } }
Ejercicios propuestos: 25. Crear un programa que muestre las letras de la Z (mayúscula) a la A (mayúscula, descendiendo). 26. Crear un programa que escriba en pantalla los números del 1 al 50 que sean múltiplos de 3 (pista: habrá que recorrer todos esos números y ver si el resto de la división entre 3 resulta 0).
Sentencia break: termina el bucle Podemos salir de un bucle "for" antes de tiempo con la orden "break": /*---------------------------*/ /* Ejemplo en C# nº 30: */ /* ejemplo30.cs */ /* */ /* "for" interrumpido con */ /* "break" */ /*---------------------------*/ using System; public class Ejemplo30 { public static void Main() { int contador; for (contador=1; contador<=10; contador++) { if (contador==5) break; Console.Write("{0} ", contador); } } }
- 49 -
Programación de Dispositivos Móviles
El resultado de este programa es: 1 2 3 4
(en cuanto se llega al valor 5, se interrumpe el "for", por lo que no se alcanza el valor 10).
Sentencia continue: fuerza la siguiente iteración Podemos saltar alguna repetición de un bucle con la orden "continue": /*---------------------------*/ /* Ejemplo en C# nº 31: */ /* ejemplo31.cs */ /* */ /* "for" interrumpido con */ /* "continue" */ /*---------------------------*/ using System; public class Ejemplo31 { public static void Main() { int contador; for (contador=1; contador<=10; contador++) { if (contador==5) continue; Console.Write("{0} ", contador); } } }
El resultado de este programa es: 1 2 3 4 6 7 8 9 10
En él podemos observar que no aparece el valor 5.
Sentencia goto El lenguaje C# también permite la orden "goto", para hacer saltos incondicionales. Su uso indisciplinado está muy mal visto, porque puede ayudar a hacer programas llenos de saltos, difíciles de seguir. Pero en casos concretos puede ser muy útil, por ejemplo, para salir de un bucle muy anidado (un "for" dentro de otro "for" que a su vez está dentro de otro "for": en este caso, "break" sólo saldría del "for" más interno). El formato de "goto" es goto donde;
y la posición de salto se indica con su nombre seguido de dos puntos (:) donde:
como en el siguiente ejemplo: /*---------------------------*/ /* Ejemplo en C# nº 32: */ /* ejemplo32.cs */ /* */ /* "for" y "goto" */ /*---------------------------*/
- 50 -
Programación de Dispositivos Móviles
using System; public class Ejemplo32 { public static void Main() { int i, j; for (i=0; i<=5; i++) for (j=0; j<=20; j+=2) { if ((i==1) && (j>=7)) goto salida; Console.WriteLine("i vale {0} y j vale {1}." , i, j); } salida: Console.Write("Fin del programa"); } }
El resultado de este programa es: i vale 0 y j vale i vale 0 y j vale i vale 0 y j vale i vale 0 y j vale i vale 0 y j vale i vale 0 y j vale i vale 0 y j vale i vale 0 y j vale i vale 0 y j vale i vale 0 y j vale i vale 0 y j vale i vale 1 y j vale i vale 1 y j vale i vale 1 y j vale i vale 1 y j vale Fin del programa
0. 2. 4. 6. 8. 10. 12. 14. 16. 18. 20. 0. 2. 4. 6.
Vemos que cuando i=1 y j>=7, se sale de los dos "for".
Ejercicios propuestos: 27. Crear un programa que dé al usuario la oportunidad de adivinar un número del 1 al 100 (prefijado en el programa) en un máximo de 6 intentos. En cada pasada deberá avisar de si se ha pasado o se ha quedado corto. 28. Crear un programa que descomponga un número (que teclee el usuario) como producto de su factores primos. Por ejemplo, 60 = 2 • 2 • 3 • 5
Arrays, estructuras y cadenas de texto Conceptos básicos sobre arrays o tablas, Definición de un array y acceso a los datos Una tabla, vector o array (que algunos autores traducen por "arreglo") es un conjunto de elementos, todos los cuales son del mismo tipo. Estos elementos tendrán todos el mismo nombre, y ocuparán un espacio contiguo en la memoria. Por ejemplo, si queremos definir un grupo de números enteros, el tipo de datos que usaremos para declararlo será "int[]". Si sabemos desde el principio cuantos datos tenemos (por e jemplo 4), les reservaremos espacio con "= new int[4]", así int[] ejemplo = new int[4];
Podemos acceder a cada uno de los valores individuales indicando su nombre (ejemplo) y el número de elemento que nos interesa, pero con una precaución: se empieza a numerar desde 0, así que en el caso anterior tendríamos 4 elementos, que serían ejemplo[0], ejemplo[1], ejemplo[2], ejemplo[3].
- 51 -
Programación de Dispositivos Móviles
Como ejemplo, vamos a definir un grupo de 5 números enteros y hallar su suma: /*---------------------------*/ /* Ejemplo en C# nº 33: */ /* ejemplo33.cs */ /* */ /* Primer ejemplo de tablas */ /*---------------------------*/ using System; public class Ejemplo33 { public static void Main() { int[] numero = new int[5]; /* Un array de 5 números enteros */ int suma; /* Un entero que será la suma */
/* Les damos valores */ numero[0] = 200; numero[1] = 150; numero[2] = 100; numero[3] = -50; numero[4] = 300; suma = numero[0] + numero[1] + numero[2] + numero[3] + numero[4]; Console.WriteLine("Su suma es {0}", suma);
/* Y hallamos la suma */
/* Nota: esta es la forma más ineficiente e incómoda */ /* Ya lo iremos mejorando */ } }
Ejercicios propuestos: 29. Un programa que pida al usuario 4 números, los memorice (utilizando una tabla), calcule su media aritmética y la muestre en pantalla. 30. Un programa que pida al usuario 5 números reales y luego los muestre en el orden contrario al que se introdujeron. 31. Un programa que pida al usuario 10 números enteros y calcule (y muestre) cuál es el mayor de ellos.
Valor inicial de un array Al igual que ocurría con las variables "normales", podemos dar valor a los elementos de una tabla al principio del programa. Será más cómodo que dar los valores uno por uno, como hemos hecho antes, pero sólo se podrá hacer si conocemos todos los valores. En este caso, los indicaremos todos entre llaves, separados por comas: /*---------------------------*/ /* Ejemplo en C# nº 34: */ /* ejemplo34.cs */ /* */ /* Segundo ejemplo de */ /* tablas */ /*---------------------------*/ using System; public class Ejemplo34 { public static void Main() { int[] numero = {200, 150, 100, -50, 300}; /* Un array de 5 números enteros */ /* Un entero que será la suma */ int suma; suma = numero[0] + numero[1] + numero[2] + numero[3] + numero[4]; /* Y hallamos la suma */ Console.WriteLine("Su suma es {0}", suma);
/* Nota: esta forma es algo menos engorrosa, pero todavía no */ /* está bien hecho. Lo seguiremos mejorando */ } }
- 52 -
Programación de Dispositivos Móviles
Recorriendo los elementos de una tabla Es de esperar que exista una forma más cómoda de acceder a varios elementos de un array, sin tener siempre que repetirlos todos, como hemos hecho en suma = numero numero[ [0] + numero numero[ [1] + numero numero[ [2] + numero numero[ [3] + numero numero[ [4];
El "truco" consistirá en emplear cualquiera de las estructuras repetitivas que ya hemos visto (while, do..while, for), por ejemplo así: /* Valor inicial de la suma */ suma = 0; for (i=0; i<= i<=4 4; i++ i++) ) /* Y hallamos la suma repetitiva */ suma += numero numero[ [i];
El fuente completo podría ser así: /*---------------------------*/ /* Ejemplo en C# nº 35: */ /* ejemplo35.cs */ /* */ /* Tercer ejemplo de */ /* tablas */ /*---------------------------*/ using System System; ; public class Ejemplo35 { public static void Main Main() () { int[] int [] numero = {200 200, , 150 150, , 100, 100, -50 50, , 300 300} }; /* Un array de 5 números enteros */ int suma; /* Un entero que será la suma */ int i; /* Para recorrer los elementos */
/* Valor inicial de la suma */ suma = 0; for (i=0; i<= i<=4 4; i++ i++) ) /* Y hallamos la suma repetitiva */ suma += numero numero[ [i]; Console.WriteLine Console. WriteLine( ("Su suma es {0}", {0}", suma) suma); } }
En este caso, que sólo sumábamos 5 números, no hemos escrito mucho menos, pero si trabajásemos con 100, 500 o 1000 números, la ganancia en comodidad sí que está clara.
Datos repetitivos introducidos por el usuario Si queremos que sea el usuario el que i ntroduzca datos a un array, usaríamos otra estructura repetitiva ("for", por ejemplo) para pedírselos: /*---------------------------*/ /* Ejemplo en C# nº 36: */ /* ejemplo36.cs */ /* */ /* Cuarto ejemplo de */ /* tablas */ /*---------------------------*/ using System System; ; public class Ejemplo36 { public static void Main Main() () { int[] numero = new int int[] int[ [5]; /* Un array de 5 números enteros */ int suma; /* Un entero que será la suma */ /* Para recorrer los elementos */ int i; for (i=0; i<= i<=4 4; i++ i++) ) /* Pedimos los datos */ { Console.Write Console. Write( ("Introduce el dato numero {0}: " ", , i+ i+1);
- 53 -
Programación de Dispositivos Móviles
numero[ numero [i] = Convert. Convert.ToInt32 ToInt32( (Console. Console.ReadLine ReadLine()) ()); ; } suma = 0; /* Valor inicial de la suma */ for (i=0; i<= i<=4 4; i++ i++) ) /* Y hallamos la suma repetitiva */ suma += numero numero[ [i]; Console.WriteLine Console. WriteLine( ("Su suma es {0}", {0}", suma) suma); } }
Ejercicios propuestos: 32. Crear un programa programa que pida al usuario 10 números y luego los muestre en orden inverso (del último último al primero). 33. Crea Crearr un programa que pida al usuario usuario 10 números, números, calcule calcule su media y luego muestre muestre los que están están por encima de la media. 34. Un programa programa que pida 10 nombres y los memorice memorice (pista: esta esta vez se trat trata a de un array de "string"). "string"). Después Después deberá pedir que se teclee un nombre y dirá si se encuentra o no entre los 10 que se han tecleado antes. Volverá a pedir otro nombre y a decir si se encuentra entre ellos, y así sucesivamente hasta que se teclee "fin". 35. Un programa que que prepare espacio espacio para un máximo máximo de 100 nombres. El usuario deberá ir introduciendo un nombre cada vez, hasta que se pulse Intro sin teclear nada, momento en el que dejarán de pedirse más nombres y se mostrará en pantalla la lista de los nombres que se han introducido.
Tablas bidimensionales Podemos declarar tablas de dos o más dimensiones. Por ejemplo, si queremos guardar datos de dos grupos de alumnos, cada uno de los cuales tiene 20 alumnos, tenemos dos o pciones: Podemos usar int datosAlumnos[40] y entonces debemos recordar que los 20 primeros datos corresponden realmente a un grupo de alumnos y los 20 siguientes a otro grupo. Es "demasiado artesanal", así que no daremos más detalles. • O bien podemos emplear int datosAlumnos[2,20] y entonces sabemos que los datos de la forma datosAlumnos[0,i] son los del primer grupo, y los datosAlumnos[1,i] son los del segundo. • Una alternativa, que puede sonar más familiar a quien ya haya programado en C es emplear int datosAlumnos[2][20] pero en C# esto no tiene exactamente el mismo significado que [2,20], sino que se trata de dos arrays, cuyos elementos a su vez son arrays de 20 elementos. De hecho, podrían ser incluso dos arrays de distinto tamaño, como veremos en el segundo ejemplo. •
En cualquier caso, si queremos indicar valores iniciales, lo haremos entre llaves, igual que si fuera una tabla de una única dimensión. Vamos a ver un primer e jemplo del uso con arrays de la forma [2,20], lo que podríamos llamar el "estilo Pascal", en el usemos tanto arrays con valores prefijados, como arrays para los que reservemos espacio con "new" y a los que demos valores más tarde: /*---------------------------*/ /* Ejemplo en C# nº 37: */ /* ejemplo37.cs */ /* */ /* Array de dos dimensiones */ /* al estilo Pascal */ /*---------------------------*/ using System System; ; public class Ejemplo37 { public static void Main Main() () { int[ int [,] notas1 = new int int[ [2,2]; // 2 bloques de 2 datos notas1[ notas1 [0,0 0,0] ] = 1; notas1[ notas1 [0, 0,1 1] = 2; notas1[ notas1 [1,0 ,0] ] = 3; notas1[ notas1 [1,1] = 4; int[ int [,] notas2 = // 2 bloques de 10 datos { {1, 2, 3, 4, 5, 6, 7, 8, 9, 10 10} }, {11 11, , 12 12, , 13 13, , 14 14, , 15 15, , 16 16, , 17 17, , 18 18, , 19 19, , 20 20} } };
- 54 -
Programación de Dispositivos Móviles
Console. WriteLine( Console.WriteLine ("La nota1 del segundo alumno del grupo 1 es {0}" , notas1[ notas1[0, 0,1 1]); ]); Console.WriteLine Console. WriteLine( ("La nota2 del tercer alumno del grupo 1 es {0}" , notas2[ notas2[0, 0,2 2]); ]); } }
Este tipo de tabla tablass de varias dimensiones dimensiones son las que se usan tamb también ién para guard guardar ar matrices, matrices, cuando se trat trata a de resolver problemas problemas matemáticos más complejos que los que hemos visto h asta ahora. La otra forma de tener arrays multidimensionales son los "arrays de arrays", que, como ya hemos comentado, y como veremos en este ejemplo, pueden tener elementos de distinto tamaño. En ese caso nos puede interesar saber su longitud, para lo que podemos usar "a.Length": /*---------------------------*/ /* Ejemplo en C# nº 38: */ /* ejemplo38.cs */ /* */ /* Array de dos dimensiones */ /* al estilo C... o casi */ /*---------------------------*/ using System System; ; public class Ejemplo38 { public static void Main Main() () {
// Array de dos dimensiones int[][] notas; int[][] notas = new int int[ [3][]; ][]; // Seran 3 bloques de datos notas[ notas [0] = new int int[ [10 10] ]; // 10 notas en un grupo notas[ notas [1] = new int int[ [15 15] ]; // 15 notas en otro grupo notas[ notas [2] = new int int[ [12 12] ]; // 12 notas en el ultimo // Damos valores de ejemplo for (int i=0;i 0;i<
// Y mostramos esos valores for (int i=0;i 0;i<
La salida de este programa sería 0 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 2 3 4 5 6 7 8 9 10 11 12 13
Ejercicios propuestos: 36. Un programa programa que al usuario dos bloques bloques de 10 números enteros enteros (usando (usando un array de dos dimensiones dimensiones). ). Después Después deberá mostrar el mayor dato que se ha introducido en cada uno de ellos.
- 55 -
Programación de Dispositivos Móviles
Estructuras o registros Definición y acceso a los datos Un registro es una agrupación de datos, los cuales no necesariamente son del mismo tipo. Se definen con la palabra "struct". La serie de datos que van a formar. En C# (al contrario que en C), primero deberemos declarar cual va a ser la estructura de nuestro registro, lo que no se puede hacer dentro de "Main". Más adelante, ya dentro de "Main", podremos declarar variables de ese nuevo tipo. Los datos que forman un "struct" pueden ser públicos o privados. Por ahora, a nosotros nos interesará que sean accesibles desde el resto de nuestro programa, por lo que les añadiremos delante la palabra "public" para indicar que queremos que sean públicos. Ya desde el cuerpo del programa, para acceder a cada uno de los datos que forman el registro, tanto si queremos leer su valor como si queremos cambiarlo, se debe indicar el nombre de la variable y el del dato (o campo) separados por un punto: /*---------------------------*/ /* Ejemplo en C# nº 39: */ /* ejemplo39.cs */ /* */ /* Registros (struct) */ /*---------------------------*/ using System; public class Ejemplo39 { struct tipoPersona { public string nombre; public char inicial; public int edad; public float nota; } public static void Main() { tipoPersona persona; persona.nombre = "Juan"; persona.inicial = 'J'; persona.edad = 20; persona.nota = 7.5f; Console.WriteLine("La edad de {0} es {1}" , persona.nombre, persona.edad); } }
Nota: La notación 7.5f se usa para detallar que se trata de un número real de simple precisión (un "float"), porque de lo contrario, 7.5 se consideraría un número de doble precisión, y al tratar de compilar obtendríamos un mensaje de error, diciendo que no se puede convertir de "double" a "float" sin pérdida de precisión. Al añadir la "f" al final, estamos diciendo "quiero que éste número se tome como un float; sé que habrá una pérdida de precisión pero es aceptable para mí”.
Ejercicio propuesto
37. Realizar un "struct" que almacene datos de una canción en formato MP3: Artista, Título, Duración (en segundos), Tamaño del fichero (en KB), el programa debe pedir los datos de una canción al usuario, almacenarlos en dicho "struct" y después mostrarlos en pantalla.
Arrays de estructuras Hemos guardado varios datos de una persona. Se pueden almacenar los de varias personas si combinamos el uso de los "struct" con las tablas (arrays) que vimos anteriormente. Por ejemplo, si queremos guardar los datos de 100 personas podríamos hacer: /*---------------------------*/ /* Ejemplo en C# nº 40: */ /* ejemplo40.cs */
- 56 -
Programación de Dispositivos Móviles
/* */ /* Array de struct */ /*---------------------------*/ using System; public class Ejemplo40 { struct tipoPersona { public string nombre; public char inicial; public int edad; public float nota; } public static void Main() { tipoPersona[] persona = new tipoPersona[100]; persona[0].nombre = "Juan"; persona[0].inicial = 'J'; persona[0].edad = 20; persona[0].nota = 7.5f; Console.WriteLine("La edad de {0} es {1}" , persona[0].nombre, persona[0].edad); persona[1].nombre = "Pedro"; Console.WriteLine("La edad de {0} es {1}" , persona[1].nombre, persona[1].edad); } }
La inicial de la primera persona sería "persona[0].inicial", y la edad del último sería "persona[99].edad". Al probar este programa obtenemos La edad de Juan es 20 La edad de Pedro es 0
Porque cuando reservamos espacio para los elementos de un "array" usando "new", sus valores se dejan "vacíos" (0 para los números, cadenas vacías para las cadenas de texto).
Ejercicio propuesto: 38. Un programa que permita guardar datos de "imágenes" (ficheros de ordenador que contengan fotografías o cualquier otro tipo de información gráfica). De cada imagen se debe guardar: nombre (texto), ancho en píxeles (por ejemplo 2000), alto en píxeles (por ejemplo, 3000), tamaño en Kb (por ejemplo 145,6). El programa debe ser capaz de almacenar hasta 700 imágenes (deberá avisar cuando su capacidad esté llena). Debe permitir las opciones: añadir una ficha nueva, ver todas las fichas (número y nombre de cada imagen), buscar la ficha que tenga un cierto nombre.
Estructuras anidadas Podemos encontrarnos con un registro que tenga varios datos, y que a su vez ocurra que uno de esos datos esté formado por varios datos más sencillos. Por ejemplo, una fecha de nacimiento podría estar formada por día, mes y año. Para hacerlo desde C#, incluiríamos un "struct" dentro de otro, así: /*---------------------------*/ /* Ejemplo en C# nº 41: */ /* ejemplo41.cs */ /* */ /* Registros anidados */ /*---------------------------*/ using System; public class Ejemplo41 {
- 57 -
Programación de Dispositivos Móviles
struct fechaNacimiento { public int dia; public int mes; public int anyo; }
struct tipoPersona { public string nombre; public char inicial; public fechaNacimiento diaDeNacimiento; public float nota; } public static void Main() { tipoPersona persona; persona.nombre = "Juan"; persona.inicial = 'J'; persona.diaDeNacimiento .dia = 15; persona.diaDeNacimiento .mes = 9; persona.nota = 7.5f; Console.WriteLine("{0} nació en el mes {1}" , persona.nombre, persona.diaDeNacimiento .mes); } }
Cadenas de caracteres Definición. Lectura desde teclado Hemos visto cómo leer cadenas de caracteres (Console.ReadLine) y cómo mostrarlas en pantalla (Console.Write), así como la forma de darles un valor(=). También podemos comparar cual es su valor, usando ==, o formar una cadena a partir de otras si las unimos con el símbolo de la suma (+): Así, un ejemplo que nos pidiese nuestro nombre y nos saludase usando todas estas posibilidades podría ser: /*---------------------------*/ /* Ejemplo en C# nº 42: */ /* ejemplo42.cs */ /* */ /* Cadenas de texto (1) */ /*---------------------------*/ using System; public class Ejemplo42 { public static void Main() { string saludo = "Hola"; string segundoSaludo; string nombre, despedida; segundoSaludo = "Que tal?"; Console.WriteLine("Dime tu nombre... "); nombre = Console.ReadLine(); Console.WriteLine("{0} {1}", saludo, nombre); Console.WriteLine(segundoSaludo ); if (nombre == "Alberto") Console.WriteLine("Dices que eres Alberto?"); else Console.WriteLine("Así que no eres Alberto?");
- 58 -
Programación de Dispositivos Móviles
despedida = "Adios " + nombre + "!"; Console.WriteLine(despedida); } }
Cómo acceder a las letras que forman una cadena Podemos leer (o modificar) una de las letras de una cadena de igual forma que leemos los elementos de cualquier array: si la cadena se llama "texto", el primer elemento será texto[0], el segundo será texto[1] y así sucesivamente. Eso sí, las cadenas en C# no se pueden modificar letra a letra: no podemos hacer texto[0]=’a’. Para eso habrá que usar una construcción auxiliar, que veremos más adelante.
Longitud de la cadena. Podemos saber cuantas letras forman una cadena con "cadena.Length". Esto permite que podamos recorrer la cadena letra por letra, usando construcciones como "for". Ejercicio propuesto: Un programa te pida tu nombre y lo muestre en pantalla separando cada letra de la siguiente con un espacio. Por ejemplo, si tu nombre es "Juan", debería aparecer en pantalla "J u a n".
Extraer una subcadena Podemos extraer parte del contenido de una cadena con "Substring", que recibe dos parámetros: la posición a partir de la que queremos empezar y la cantidad de caracteres que queremos obtener. El resultado será otra cadena: saludo = frase.Substring(0,4);
Podemos omitir el segundo número, y entonces se extraerá desde la posición indicada hasta el final de la cadena.
Buscar en una cadena Para ver si una cadena contiene un cierto texto, podemos usar IndexOf ("posición de"), que nos dice en qué posición se encuentra (o devuelve el valor -1 si no aparece): if (nombre.IndexOf("Juan") >= 0) Console.Write("Bienvenido, Juan");
Podemos añadir un segundo parámetro opcional, que es la posición a partir de la que queremos buscar: if (nombre.IndexOf("Juan", 5) >= 0) ...
La búsqueda termina al final de la cadena, salvo que indiquemos que termine antes con un tercer parámetro opcional: if (nombre.IndexOf("Juan", 5, 15) >= 0) ...
De forma similar, LastIndexOf ("última posición de") indica l a última aparición (es decir, busca de derecha a izquierda).
Otras manipulaciones de cadenas Ya hemos comentado que las cadenas en C# son inmutables, no se pueden modificar. Pero sí podemos realizar ciertas operaciones sobre ellas para obtener una nueva cadena. Por ejemplo: ToUpper() convierte a mayúsculas: nombreCorrecto = nombre.ToUpper(); • ToLower() convierte a minúsculas: password2 = password.ToLower(); • Insert(int posición, string subcadena): Insertar una subcadena en una cierta posición de la cadena inicial: nombreFormal = nombre.Insert(0,"Don"); • Remove(int posición, int cantidad): Elimina una cantidad de caracteres en cierta posición: apellidos = nombreCompleto.Remove(0,6); • Replace(string textoASustituir, string cadenaSustituta): Sustituye una cadena (todas las veces que aparezca) por otra: nombreCorregido = nombre.Replace("Pepe", "Jose"); •
Un programa que probara todas estas posibilidades podría ser así: /*---------------------------*/ /* Ejemplo en C# nº 43: */ /* ejemplo43.cs */
- 59 -
Programación de Dispositivos Móviles
/* */ /* Cadenas de texto (2) */ /*---------------------------*/ using System; public class Ejemplo43 { public static void Main() { string ejemplo = "Hola, que tal estas"; Console.WriteLine("El texto es: {0}", ejemplo); Console.WriteLine("La primera letra es: {0}", ejemplo[0]); Console.WriteLine("Las tres primeras letras son: {0}" , ejemplo.Substring(0,3)); Console.WriteLine("La longitud del texto es: {0}" , ejemplo.Length); Console.WriteLine("La posicion de \"que\" es: {0}", ejemplo.IndexOf("que")); Console.WriteLine("La ultima A esta en la posicion: {0}", ejemplo.LastIndexOf("a")); Console.WriteLine("En mayúsculas: {0}", ejemplo.ToUpper()); Console.WriteLine("En minúsculas: {0}", ejemplo.ToLower()); Console.WriteLine("Si insertamos \", tio\": {0}", ejemplo.Insert(4,", tio")); Console.WriteLine("Si borramos las 6 primeras letras: {0}" , ejemplo.Remove(0, 6)); Console.WriteLine("Si cambiamos ESTAS por ESTAMOS: {0}" , ejemplo.Replace("estas", "estamos")); } }
Y su resultado sería El texto es: Hola, que tal estas La primera letra es: H Las tres primeras letras son: Hol La longitud del texto es: 19 La posicion de "que" es: 6 La ultima A esta en la posicion: 17 En mayúsculas: HOLA, QUE TAL ESTAS En minúsculas: hola, que tal estas Si insertamos ", tio": Hola, tio, que tal estas Si borramos las 6 primeras letras: que tal estas Si cambiamos ESTAS por ESTAMOS: Hola, que tal estamos
Otra posibilidad interesante, aunque un poco más avanzada, es la de descomponer una cadena en trozos, que estén separados por una serie de delimitadores (por ejemplo, espacios o comas). Para ello se puede usar Split, que crea un array a partir de los fragmentos de la cadena, así:
- 60 -
Programación de Dispositivos Móviles
/*---------------------------*/ /* Ejemplo en C# nº 43b: */ /* ejemplo43b.cs */ /* */ /* Cadenas de texto (2b) */ /*---------------------------*/ using System; public class Ejemplo43b { public static void Main() { string ejemplo = "uno,dos.tres,cuatro" ; char [] delimitadores = {',', '.'}; int i; string [] ejemploPartido = ejemplo.Split(delimitadores ); for (i=0; i
Que mostraría en pantalla lo siguiente: Fragmento Fragmento Fragmento Fragmento
0= 1= 2= 3=
uno dos tres cuatro
Comparación de cadenas Sabemos comprobar si una cadena tiene exactamente un cierto valor, con el operador de igualdad (==), pero no sabemos comparar qué cadena es "mayor" que otra, algo que es necesario si queremos ordenar textos. El operador "mayor que" (>) que usamos con los números no se puede aplicar directamente a las cadenas. En su lugar, debemos usar "CompareTo", que devolverá un número mayor que 0 si la nuestra cadena es mayor que la que indicamos como parámetro (o un número negativo si nuestra cadena es menor, o 0 si son iguales): if (frase.CompareTo("hola") > 0) Console.WriteLine("Es mayor que hola");
También podemos comparar sin distinguir entre mayúsculas y minúsculas, usando String.Compare, al que indicamos las dos cadenas y un tercer dato "true" cuando queramos ignorar esa distinción: if (String.Compare(frase, "hola", true) > 0) Console.WriteLine("Es mayor que hola (mays o mins)");
Un programa completo de prueba podría ser así: /*---------------------------*/ /* Ejemplo en C# nº 43c: */ /* ejemplo43c.cs */ /* */ /* Cadenas de texto (2c) */ /*---------------------------*/ using System; public class Ejemplo43c { public static void Main() {
- 61 -
Programación de Dispositivos Móviles
string frase; Console.WriteLine("Escriba una palabra"); frase = Console.ReadLine();
// Compruebo si es exactamente hola if (frase == "hola") Console.WriteLine("Ha escrito hola");
// Compruebo si es mayor o menor if (frase.CompareTo("hola") > 0) Console.WriteLine("Es mayor que hola"); else if (frase.CompareTo("hola") < 0) Console.WriteLine("Es menor que hola");
// Comparo sin distinguir mayúsculas ni minúsculas bool ignorarMays = true; if (String.Compare(frase, "hola", ignorarMays) > 0) Console.WriteLine("Es mayor que hola (mays o mins)"); else if (String.Compare(frase, "hola", ignorarMays) < 0) Console.WriteLine("Es menor que hola (mays o mins)"); else Console.WriteLine("Es hola (mays o mins)"); } }
Si tecleamos una palabra como "gol", que comienza por G, que alfabéticamente está antes de la H de "hola", se nos dirá que esa palabra es menor: Escriba una palabra gol Es menor que hola Es menor que hola (mays o mins)
Si escribimos "hOLa", que coindice con "hola" salvo por las mayúsculas, una comparación normal nos dirá que es mayor (las mayúsculas se consideran "mayores" que las minúsculas), y una comparación sin considerar mayúsculas o minúsculas nos dirá que coinciden: Escriba una palabra hOLa Es mayor que hola Es hola (mays o mins)
Una cadena modificable: StringBuilder Si tenemos la necesidad de poder modificar una cadena letra a letra, no podemos usar un "string" convencional, deberemos recurrir a un "StringBuilder", que sí lo permiten pero son algo más complejos de manejar: hay de reservarles espacio con "new" (igual que hacíamos en ciertas ocasiones con los Arrays), y se pueden convertir a una cadena "convencional" usando "ToString": /*---------------------------*/ /* Ejemplo en C# nº 44: */ /* ejemplo44.cs */ /* */ /* Cadenas modificables */ /* con "StringBuilder" */ /*---------------------------*/ using System; using System.Text; // Usaremos un System.Text.StringBuilder public class Ejemplo44 { public static void Main() { StringBuilder cadenaModificable = new StringBuilder ("Hola"); cadenaModificable [0] = 'M'; Console.WriteLine("Cadena modificada: {0}", cadenaModificable ); string cadenaNormal;
- 62 -
Programación de Dispositivos Móviles
cadenaNormal = cadenaModificable. ToString(); Console.WriteLine("Cadena normal a partir de ella: {0}" , cadenaNormal ); } }
Ejercicio propuesto: 39. Un programa que pida tu nombre, tu día de nacimiento y tu mes de nacimiento y lo junte todo en una cadena, separando el nombre de la fecha por una coma y el d ía del mes por una barra inclinada, así: "Juan, nacido el 31/12".
Recorriendo con "foreach" Existe una construcción parecida a "for", pensada para recorrer ciertas estructuras de datos, como los arrays (y otras que veremos más adelante). Se usa con el formato "foreach (variable in ConjuntoDeValores)": /*---------------------------*/ /* Ejemplo en C# nº 45: */ /* ejemplo45.cs */ /* */ /* Ejemplo de "foreach" */ /*---------------------------*/ using System; public class Ejemplo45 { public static void Main() { int[] diasMes = {31, 28, 21}; foreach(int dias in diasMes) { Console.WriteLine("Dias del mes: {0}", dias); } string[] nombres = {"Alberto", "Andrés", "Antonio"}; foreach(string nombre in nombres) { Console.Write(" {0}", nombre); } Console.WriteLine(); string saludo = "Hola"; foreach(char letra in saludo) { Console.Write("{0}-", letra); } Console.WriteLine(); } }
Ejemplo completo Vamos a hacer un ejemplo completo que use tablas ("arrays"), registros ("struct") y que además manipule cadenas. La idea va a ser la siguiente: Crearemos un programa que pueda almacenar datos de hasta 1000 ficheros. Para cada fichero, debe guardar los siguientes datos: Nombre del fichero, Tamaño (en KB, un número de 0 a 8.000.000.000). El programa mostrará un menú que permita al usuario las siguientes operaciones:
12345-
Añadir datos de un nuevo fichero Mostrar los nombres de todos los ficheros almacenados Mostrar ficheros que sean de más de un cierto tamaño (por ejemplo, 2000 KB). Ver todos los datos de un cierto fichero (a partir de su nombre) Salir de la aplicación (como no usamos ficheros, los datos se perderán).
- 63 -
Programación de Dispositivos Móviles
No debería resultar difícil. Vamos a ver directamente una de las formas en que se podría plantear y luego comentaremos alguna de las mejoras que se podría (incluso se debería) hacer. Una opción que podemos a tomar para resolver este problema es la de contar el número de fichas que tenemos almacenadas, y así podremos añadir de una en una. Si tenemos 0 fichas, deberemos almacenar la siguiente (la primera) en la posición 0; si tenemos dos fichas, serán la 0 y la 1, luego añadiremos en la posición 2; en general, si tenemos "n" fichas, añadiremos cada nueva ficha en la posición "n". Por otra parte, para revisar todas las fichas, recorreremos desde la posición 0 hasta la n-1, haciendo algo como for (i=0; i<=n-1; i++) { /* ... más órdenes ... */ }
o bien algo como for (i=0; i
El resto del programa no es difícil: sabemos leer y comparar textos y números, comprobar varias opciones con "switch", etc. Aun así, haremos una última consideración: hemos limitado el número de fichas a 1000, así que, si nos piden añadir, deberíamos asegurarnos antes de que todavía tenemos hueco disponible. Con todo esto, nuestro fuente quedaría así: /*---------------------------*/ /* Ejemplo en C# nº 46: */ /* ejemplo46.cs */ /* */ /* Tabla con muchos struct */ /* y menu para manejarla */ /*---------------------------*/ using System; public class Ejemplo46 { struct tipoFicha { public string nombreFich; public long tamanyo; };
/* Nombre del fichero */ /* El tamaño en bytes */
public static void Main() { /* Los datos en si */ tipoFicha[] fichas = new tipoFicha[1000]; int numeroFichas =0; /* Número de fichas que ya tenemos */ int i; /* Para bucles */ int opcion; /* La opcion del menu que elija el usuario */ string textoBuscar; /* Para cuando preguntemos al usuario */ /* Para buscar por tamaño */ long tamanyoBuscar; do {
/* Menu principal */ Console.WriteLine(); Console.WriteLine("Escoja una opción:"); Console.WriteLine("1.- Añadir datos de un nuevo fichero" ); Console.WriteLine("2.- Mostrar los nombres de todos los ficheros" ); Console.WriteLine("3.- Mostrar ficheros que sean de mas de un cierto tamaño" ); Console.WriteLine("4.- Ver datos de un fichero"); Console.WriteLine("5.- Salir"); opcion = Convert.ToInt32( Console.ReadLine() );
/* Hacemos una cosa u otra según la opción escogida */ switch(opcion){ case 1: /* Añadir un dato nuevo */ if (numeroFichas < 1000) { /* Si Console.WriteLine("Introduce el fichas[numeroFichas].nombreFich Console.WriteLine("Introduce el
queda hueco */ nombre del fichero: " ); = Console.ReadLine(); tamaño en KB: ");
- 64 -
Programación de Dispositivos Móviles
fichas[numeroFichas].tamanyo
= Convert.ToInt32( Console.ReadLine() );
/* Y ya tenemos una ficha más */ numeroFichas++; } else /* Si no hay hueco para más fichas, avisamos */ Console.WriteLine("Máximo de fichas alcanzado (1000)!" ); break; case 2: /* Mostrar todos */ for (i=0; i= tamanyoBuscar ) Console.WriteLine("Nombre: {0}; Tamaño: {1} Kb" , fichas[i].nombreFich, fichas[i].tamanyo); break; case 4: /* Ver todos los datos (pocos) de un fichero */ Console.WriteLine("¿De qué fichero quieres ver todos los datos?" ); textoBuscar = Console.ReadLine(); for (i=0; i
Funciona, y hace todo lo que tiene que hacer, pero es mejorable. Por supuesto, en un caso real es habitual que cada ficha tenga que guardar más información que sólo esos dos apartados de ejemplo que hemos previsto esta vez. Si nos muestra todos los datos en pantalla y se trata de muchos datos, puede ocurrir que aparezcan en pantalla tan rápido que no nos dé tiempo a leerlos, así que sería deseable que parase cuando se llenase la pantalla de información (por ejemplo, una pausa tras mostrar cada 25 datos). Por supuesto, se nos pueden ocurrir muchas más preguntas que hacerle sobre nuestros datos. Y además, cuando salgamos del programa se borrarán todos los datos que habíamos tecleado, pero eso es lo único "casi inevitable", porque aún no sabemos manejar ficheros.
Ejercicios propuestos: 40. Un programa que sea capaz de almacenar los datos de 50 personas: nombre, dirección, teléfono, edad (usando una tabla de structs). Deberá ir pidiendo los datos uno por uno, hasta que un nombre se introduzca vacío (se pulse Intro sin teclear nada). Entonces deberá aparecer un menú que permita: • Mostrar la lista de todos los nombres. • Mostrar las personas de una cierta edad. • Mostrar las personas cuya inicial sea la que el usuario indique. • Salir del programa • (lógicamente, este menú debe repetirse hasta que se escoja la opción de "salir").
Ordenaciones simples Es muy frecuente querer ordenar datos que tenemos en un array. Para conseguirlo, existen varios algoritmos sencillos, que no son especialmente eficientes, pero son fáciles de programar. La falta de eficiencia se refiere a que la mayoría de ellos se basan en dos bucles "for" anidados, de modo que en cada pasada quede ordenado un dato, y se dan tantas pasadas como datos existen, de modo que para un array con 1.000 datos, podrían llegar a tener que hacerse un millón de comparaciones. Existen ligeras mejoras (por ejemplo, cambiar uno de los "for" por un "while", para no repasar todos los datos si ya estaban parcialmente ordenados), así como métodos claramente más efectivos, pero más difíciles de programar, alguno de los cuales veremos más adelante.
- 65 -
Programación de Dispositivos Móviles
Veremos tres de estos métodos simples de ordenación, primero mirando la apariencia que tiene el algoritmo, y luego juntando los tres en un ejemplo que los pruebe:
Método de burbuja (Intercambiar cada pareja consecutiva que no esté ordenada) Para i=1 hasta n-1 Para j=i+1 hasta n Si A[i] > A[j] Intercambiar ( A[i], A[j])
(Nota: algunos autores hacen el bucle exterior creciente y otros decreciente, así:) Para i=n descendiendo hasta 2 Para j=2 hasta i Si A[j-1] > A[j] Intercambiar ( A[j-1], A[j])
Selección directa (En cada pasada busca el menor, y lo intercambia al final de la pasada) Para i=1 hasta n-1 menor = i Para j=i+1 hasta n Si A[j] < A[menor] menor = j Si menor <> i Intercambiar ( A[i], A[menor]) Nota: el símbolo "<>" se suele usar en pseudocódigo para indicar que un dato es distinto de otro, de modo que equivale al "!=" de C#. La penúltima línea en C# saldría a ser algo como "if (menor !=i)"
Inserción directa (Comparar cada elemento con los anteriores -que ya están ordenados- y desplazarlo hasta su posición correcta). Para i=2 hasta n j=i-1 mientras (j>=1) y (A[j] > A[j+1]) Intercambiar ( A[j], A[j+1]) j = j - 1
(Es mejorable, no intercambiando el dato que se mueve con cada elemento, sino sólo al final de cada pasada, pero no entraremos en más detalles). Un programa de prueba podría ser: /*---------------------------*/ /* Ejemplo en C# */ /* ordenar.cs */ /* */ /* Ordenaciones simples */ /*---------------------------*/ using System; public class Ordenar { public static void Main() { int[] datos = {5, 3, 14, 20, 8, 9, 1}; int i,j,datoTemporal; int n=7; // Numero de datos
- 66 -
Programación de Dispositivos Móviles
// BURBUJA // (Intercambiar cada pareja consecutiva que no esté ordenada) // Para i=1 hasta n-1 // Para j=i+1 hasta n // Si A[i] > A[j] // Intercambiar ( A[i], A[j]) Console.WriteLine("Ordenando mediante burbuja... " ); for(i=0; i < n-1; i++) { foreach (int dato in datos) // Muestro datos Console.Write("{0} ",dato); Console.WriteLine(); for(j=i+1; j < n; j++) { if (datos[i] > datos[j]) { datoTemporal = datos[i]; datos[i] = datos[j]; datos[j] = datoTemporal; } } } Console.Write("Ordenado:"); foreach (int dato in datos) // Muestro datos finales Console.Write("{0} ",dato); Console.WriteLine();
// SELECCIÓN DIRECTA: // (En cada pasada busca el menor, y lo intercambia al final de la pasada) // Para i=1 hasta n-1 // menor = i // Para j=i+1 hasta n // Si A[j] < A[menor] // menor = j // Si menor <> i // Intercambiar ( A[i], A[menor]) Console.WriteLine("\nOrdenando mediante selección directa... " ); int[] datos2 = {5, 3, 14, 20, 8, 9, 1}; for(i=0; i < n-1; i++) { foreach (int dato in datos2) // Muestro datos Console.Write("{0} ",dato); Console.WriteLine(); int menor = i; for(j=i+1; j < n; j++) if (datos2[j] < datos2[menor]) menor = j; if (i != menor) { datoTemporal = datos2[i]; datos2[i] = datos2[menor]; datos2[menor] = datoTemporal; } } Console.Write("Ordenado:"); foreach (int dato in datos2) // Muestro datos finales Console.Write("{0} ",dato); Console.WriteLine();
// // // // //
INSERCION DIRECTA: (Comparar cada elemento con los anteriores -que ya están ordenadosy desplazarlo hasta su posición correcta). Para i=2 hasta n j=i-1
- 67 -
Programación de Dispositivos Móviles
// // //
mientras (j>=1) y (A[j] > A[j+1]) Intercambiar ( A[j], A[j+1]) j = j - 1
Console.WriteLine("\nOrdenando mediante inserción directa... " ); int[] datos3 = {5, 3, 14, 20, 8, 9, 1}; for(i=1; i < n; i++) { foreach (int dato in datos3) // Muestro datos Console.Write("{0} ",dato); Console.WriteLine(); j = i-1; while ((j>=0) && (datos3[j] > datos3[j+1])) { datoTemporal = datos3[j]; datos3[j] = datos3[j+1]; datos3[j+1] = datoTemporal; j--; } } Console.Write("Ordenado:"); foreach (int dato in datos3) // Muestro datos finales Console.Write("{0} ",dato); Console.WriteLine(); } }
Y su resultado sería: Ordenando mediante burbuja... 5 3 14 20 8 9 1 1 5 14 20 8 9 3 1 3 14 20 8 9 5 1 3 5 20 14 9 8 1 3 5 8 20 14 9 1 3 5 8 9 20 14 Ordenado:1 3 5 8 9 14 20 Ordenando mediante selección directa... 5 3 14 20 8 9 1 1 3 14 20 8 9 5 1 3 14 20 8 9 5 1 3 5 20 8 9 14 1 3 5 8 20 9 14 1 3 5 8 9 20 14 Ordenado:1 3 5 8 9 14 20 Ordenando mediante inserción directa... 5 3 14 20 8 9 1 3 5 14 20 8 9 1 3 5 14 20 8 9 1 3 5 14 20 8 9 1 3 5 8 14 20 9 1 3 5 8 9 14 20 1 Ordenado:1 3 5 8 9 14 20
Funciones Diseño modular de programas: Descomposición modular Hasta ahora hemos estado pensando los pasos que deberíamos dar para resolver un cierto problema, y hemos creado programas a partir de cada uno de esos pasos. Esto es razonable cuando los problemas son sencillos, pero puede no ser la mejor forma de actuar cuando se trata de algo más complicado. A partir de ahora vamos a empezar a intentar descomponer los problemas en trozos más pequeños, que sean más fáciles de resolver. Esto nos puede suponer varias ventajas:
- 68 -
Programación de Dispositivos Móviles
Cada "trozo de programa" independiente será más fácil de programar, al realizar una función breve y concreta. • El "programa principal" será más fácil de leer, porque no necesitará contener todos los detalles de cómo se hace cada cosa. • Podremos repartir el trabajo, para que cada persona se encargue de realizar un "trozo de programa", y finalmente se integrará el trabajo individual de cada persona. •
Esos "trozos" de programa son lo que suele llamar "subrutinas", "procedimientos" o "funciones". En el lenguaje C y sus derivados, el nombre que más se usa es el de funciones.
Conceptos básicos sobre funciones En C#, al igual que en C y los demás lenguajes derivados de él, todos los "trozos de programa" son funciones, incluyendo el propio cuerpo de programa, Main. De hecho, la forma básica de definir una función será indicando su nombre seguido de unos paréntesis vacíos, como hacíamos con "Main", y precediéndolo por ciertas palabras reservadas, como "public static void", cuyo significado iremos viendo muy pronto. Después, entre llaves indicaremos todos los pasos que queremos que dé ese "trozo de programa". Por ejemplo, podríamos crear una función llamada "saludar", que escribiera varios mensajes en la pantalla: public static void saludar() { Console.WriteLine("Bienvenido al programa"); Console.WriteLine(" de ejemplo"); Console.WriteLine("Espero que estés bien"); }
Ahora desde dentro del cuerpo de nuestro programa, podríamos "llamar" a esa función: public static void Main() { saludar(); … }
Así conseguimos que nuestro programa principal sea más fácil de leer. Un detalle importante: tanto la función habitual "Main" como la nueva función "Saludar" serían parte de nuestra "class", es decir, el fuente completo sería así: /*---------------------------*/ /* Ejemplo en C# nº 47: */ /* ejemplo47.cs */ /* */ /* Funcion "saludar" */ /*---------------------------*/ using System; public class Ejemplo47 { public static void Saludar() { Console.WriteLine("Bienvenido al programa"); Console.WriteLine(" de ejemplo"); Console.WriteLine("Espero que estés bien" ); } public static void Main() { Saludar(); Console.WriteLine("Nada más por hoy..."); } }
- 69 -
Programación de Dispositivos Móviles
Como ejemplo más detallado, la parte principal de una agenda podría ser simplemente: leerDatosDeFichero (); do { mostrarMenu(); pedirOpcion(); switch( opcion ) { case 1: buscarDatos(); break; case 2: modificarDatos (); break; case 3: anadirDatos(); break; …
Parámetros de una función Es muy frecuente que nos interese además indicarle a nuestra función ciertos datos especiales con los que queremos que trabaje. Por ejemplo, si escribimos en pantalla números reales con frecuencia, nos puede resultar útil que nos los muestre con el formato que nos interese. Lo podríamos hacer así: public static void escribeNumeroReal ( float n ) { Console.WriteLine( n.ToString("#.###") ); }
Y esta función se podría usar desde el cuerpo de nuestro programa así: escribeNumeroReal (2.3f);
(recordemos que el sufijo "f" es para indicar al compilador que trate ese número como un "float", porque de lo contrario, al ver que tiene cifras decimales, lo tomaría como "double", que permite mayor precisión... pero a cambio nosotros tendríamos un mensaje de error en nuestro programa, diciendo que estamos dando un dato "double" a una función que espera un "float"). El programa completo podría quedar así: /*---------------------------*/ /* Ejemplo en C# nº 48: */ /* ejemplo48.cs */ /* */ /* Funcion */ /* "escribeNumeroReal" */ /*---------------------------*/ using System; public class Ejemplo48 { public static void escribeNumeroReal ( float n ) { Console.WriteLine( n.ToString("#.###") ); } public static void Main() { float x; x= 5.1f; Console.WriteLine("El primer numero real es: "); escribeNumeroReal (x); Console.WriteLine(" y otro distinto es: " ); escribeNumeroReal (2.3f); } }
Estos datos adicionales que indicamos a la función es lo que llamaremos sus "parámetros". Como se ve en el ejemplo, tenemos que indicar un nombre para cada parámetro (puede haber varios) y el tipo de datos que corresponde a ese parámetro. Si hay más de un parámetro, deberemos indicar el tipo y el nombre para cada uno de ellos, y separarlos entre comas:
- 70 -
Programación de Dispositivos Móviles
public static void sumar ( int x, int y ) { ... }
Valor devuelto por una función. El valor "void". Cuando queremos dejar claro que una función no tiene que devolver ningún valor, podemos hacerlo indicando al principio que el tipo de datos va a ser "void" (nulo), como hacíamos hasta ahora con "Main" y como hicimos con nuestra función "saludar". Pero eso no es lo que ocurre con las funciones matemáticas que estamos acostumbrados a manejar: sí devuelven un valor, el resultado de una operación. De igual modo, para nosotros también será habitual que queramos que nuestra función realice una serie de cálculos y nos "devuelva" (return, en inglés) el resultado de esos cálculos, para poderlo usar desde cualquier otra parte de nuestro programa. Por ejemplo, podríamos crear una función para elevar un número entero al cuadrado así: public static int cuadrado ( int n ) { return n*n; }
y podríamos usar el resultado de esa función como si se tratara de un número o de una variable, así: resultado = cuadrado( 5 );
Un programa más detallado de e jemplo podría ser: /*---------------------------*/ /* Ejemplo en C# nº 49: */ /* ejemplo49.cs */ /* */ /* Funcion "cuadrado" */ /*---------------------------*/ using System; public class Ejemplo49 { public static int cuadrado ( int n ) { return n*n; }
public static void Main() { int numero; int resultado; numero= 5; resultado = cuadrado(numero); Console.WriteLine("El cuadrado del numero {0} es {1}", numero, resultado); Console.WriteLine(" y el de 3 es {0}", cuadrado(3)); } }
Podemos hacer una función que nos diga cual es el mayor de dos números reales así: public static float mayor ( float n1, float n2 ) { if (n1 > n2) return n1; else return n2;
- 71 -
Programación de Dispositivos Móviles
}
Ejercicios propuestos: 42. Crear una función que calcule el cubo de un número real (float). El resultado deberá ser otro número real. Probar esta función para calcular el cubo de 3.2 y el de 5. 43. Crear una función que calcule cual es el menor de dos números enteros. El resultado será otro número entero.
Variables locales y variables globales Hasta ahora, hemos declarado las variables dentro de "Main". Ahora nuestros programas tienen varios "bloques", así que se comportarán de forma distinta según donde d eclaremos las variables. Las variables se pueden declarar dentro de un bloque (una función), y entonces sólo ese bloque las conocerá, no se podrán usar desde ningún otro bloque del programa. Es lo que llamaremos "variables locales". Por el contrario, si declaramos una variable al comienzo del programa, fuera de todos los "bloques" de programa, será una "variable global", a la que se podrá acceder desde cualquier parte. Vamos a verlo con un ejemplo. Crearemos una función que calcule la potencia de un número entero (un número elevado a otro), y el cuerpo del programa que la use. La forma de conseguir elevar un número a otro será a base de multiplicaciones, es decir: 3 elevado a 5
=
3 • 3 • 3 • 3 • 3
(multiplicamos 5 veces el 3 por sí mismo). En general, como nos pueden pedir cosas como "6 elevado a 100" (o en general números que pueden ser grandes), usaremos la orden "for" para multiplicar tantas veces como haga falta: /*---------------------------*/ /* Ejemplo en C# nº 50: */ /* ejemplo50.cs */ /* */ /* Ejemplo de función con */ /* variables locales */ /*---------------------------*/ using System; public class Ejemplo50 { public static int potencia(int nBase, int nExponente) { int temporal = 1; /* Valor que voy hallando */ /* Para bucles */ int i; for(i=1; i<=nExponente; i++) temporal *= nBase;
/* Multiplico "n" veces */ /* Y calculo el valor temporal */
return temporal;
/* Tras las multiplicaciones, */ /* obtengo el valor que buscaba */
} public static void Main() { int num1, num2;
Console.WriteLine("Introduzca la base: "); num1 = Convert.ToInt32( Console.ReadLine() ); Console.WriteLine("Introduzca el exponente: "); num2 = Convert.ToInt32( Console.ReadLine() ); Console.WriteLine("{0} elevado a {1} vale {2}", num1, num2, potencia(num1,num2));
- 72 -
Programación de Dispositivos Móviles
} }
En este caso, las variables "temporal" e "i" son locales a la función "potencia": para "Main" no existen. Si en "Main" intentáramos hacer i=5; obtendríamos un mensaje de error. De igual modo, "num1" y "num2" son locales para "main": desde la función "potencia" no podemos acceder a su valor (ni para leerlo ni para modificarlo), sólo desde "main". En general, deberemos intentar que la mayor cantidad de variables posible sean locales (lo ideal sería que todas lo fueran). Así hacemos que cada parte del programa trabaje con sus propios datos, y ayudamos a evitar que un error en un trozo de programa pueda afectar al resto. La forma correcta de pasar datos entre distintos trozos de programa es usando los parámetros de cada función, como en el anterior ejemplo.
Ejercicios propuestos: 44. Crear una función "escribirTablaMultiplicar", que reciba como parámetro un número entero, y escriba la tabla de multiplicar de ese número (por ejemplo, para el 3 deberá llegar desde 3x0=0 hasta 3x10=30). 45. Crear una función "esPrimo", que reciba un número y devuelva el valor booleano "true" si es un número primo o "false" en caso contrario. 46. Crear una función que reciba una cadena y una letra, y devuelva la cantidad de veces que dicha letra aparece en la cadena. Por ejemplo, si la cadena es "Barcelona" y la letra es 'a', debería devolver 2 (aparece 2 veces). 47. Crear una función que reciba un numero cualquiera y que devuelva como resultado la suma de sus dígitos. Por ejemplo, si el número fuera 123 la suma sería 6. 48. Crear una función que reciba una letra y un número, y escriba un "triángulo" formado por esa letra, que tenga como anchura inicial la que se ha indicado. Por ejemplo, si la letra es * y la anchura es 4, debería escribir **** *** ** *
Los conflictos de nombres en las variables ¿Qué ocurre si damos el mismo nombre a dos variables locales? Vamos a comprobarlo con un ejemplo: /*---------------------------*/ /* Ejemplo en C# nº 51: */ /* ejemplo51.cs */ /* */ /* Dos variables locales */ /* con el mismo nombre */ /*---------------------------*/ using System; public class Ejemplo51 { public static void cambiaN() { int n = 7; n ++; } public static void Main() { int n = 5; Console.WriteLine("n vale {0}", n); cambiaN(); Console.WriteLine("Ahora n vale {0}", n); } }
- 73 -
Programación de Dispositivos Móviles
El resultado de este programa es: n vale 5 Ahora n vale 5
¿Por qué? Sencillo: tenemos una variable local dentro de "duplica" y otra dentro de "main". El hecho de que las dos tengan el mismo nombre no afecta al funcionamiento del programa, siguen siendo distintas. Si la variable es "global", declarada fuera de estas funciones, sí será accesible por todas ellas: /*---------------------------*/ /* Ejemplo en C# nº 52: */ /* ejemplo52.cs */ /* */ /* Una variable global */ /*---------------------------*/ using System; public class Ejemplo52 { static int n = 7; public static void cambiaN() { n ++; } public static void Main() { Console.WriteLine("n vale {0}", n); cambiaN(); Console.WriteLine("Ahora n vale {0}", n); } }
Dentro de poco, hablaremos de por qué cada uno de los bloques de nuestro programa, e incluso las "variables globales", tienen delante la palabra "static". Será cuando tratemos la "Programación Orientada a Objetos", en el próximo tema.
Modificando parámetros Podemos modificar el valor de un dato que recibamos como parámetro, pero posiblemente el resultado no será el que esperamos. Vamos a verlo con un ejemplo: /*---------------------------*/ /* Ejemplo en C# nº 53: */ /* ejemplo53.cs */ /* */ /* Modificar una variable */ /* recibida como parámetro */ /*---------------------------*/ using System; public class Ejemplo53 { public static void duplica(int x) { Console.WriteLine(" El valor recibido vale {0}", x); x = x * 2; Console.WriteLine(" y ahora vale {0}", x); } public static void Main() { int n = 5; Console.WriteLine("n vale {0}", n); duplica(n); Console.WriteLine("Ahora n vale {0}", n); }
- 74 -
Programación de Dispositivos Móviles
}
El resultado de este programa será: n vale 5 El valor recibido vale 5 y ahora vale 10 Ahora n vale 5
Vemos que al salir de la función, los cambios que hagamos a esa variable que se ha recibido como parámetro no se conservan. Esto se debe a que, si no indicamos otra cosa, los parámetros "se pasan por valor", es decir, la función no recibe los datos originales, sino una copia de ellos. Si modificamos algo, estamos cambiando una copia de los datos originales, no dichos datos. Si queremos que los cambios se conserven, basta con hacer un pequeño cambio: indicar que la variable se va a pasar "por referencia", lo que se indica usando la palabra "ref", tanto en la declaración de la función como en la llamada, así: /*---------------------------*/ /* Ejemplo en C# nº 54: */ /* ejemplo54.cs */ /* */ /* Modificar una variable */ /* recibida como parámetro */ /*---------------------------*/ using System; public class Ejemplo54 { public static void duplica(ref int x) { Console.WriteLine(" El valor recibido vale {0}", x); x = x * 2; Console.WriteLine(" y ahora vale {0}", x); } public static void Main() { int n = 5; Console.WriteLine("n vale {0}", n); duplica(ref n); Console.WriteLine("Ahora n vale {0}", n); } }
En este caso sí se modifica la variable n: n vale 5 El valor recibido vale 5 y ahora vale 10 Ahora n vale 10
El hecho de poder modificar valores que se reciban como parámetros abre una posibilidad que no se podría conseguir de otra forma: con "return" sólo se puede devolver un valor de una función, pero con parámetros pasados por referencia podríamos devolver más de un dato. Por ejemplo, podríamos crear una función que intercambiara los valores de dos variables: public static void intercambia(ref int x, ref int y)
La posibilidad de pasar parámetros por valor y por referencia existe en la mayoría de lenguajes de programación. En el caso de C# existe alguna posibilidad adicional que no existe en otros lenguajes, como los "parámetros de salida". Las veremos más a delante.
- 75 -
Programación de Dispositivos Móviles
El orden no importa En algunos lenguajes, una función debe estar declarada antes de usarse. Esto no es necesario en C#. Por ejemplo, podríamos rescribir el fuente anterior, de modo que "Main" aparezca en primer lugar y "duplica" aparezca después, y seguiría compilando y funcionando igual: /*---------------------------*/ /* Ejemplo en C# nº 55: */ /* ejemplo55.cs */ /* */ /* Función tras Main */ /*---------------------------*/ using System; public class Ejemplo55 { public static void Main() { int n = 5; Console.WriteLine("n vale {0}", n); duplica(ref n); Console.WriteLine("Ahora n vale {0}", n); } public static void duplica(ref int x) { Console.WriteLine(" El valor recibido vale {0}", x); x = x * 2; Console.WriteLine(" y ahora vale {0}", x); } }
Algunas funciones útiles Números aleatorios En un programa de gestión o una utilidad que nos ayuda a administrar un sistema, no es habitual que podamos permitir que las cosas ocurran al azar. Pero los juegos se encuentran muchas veces entre los ejercicios de programación más completos, y para un juego sí suele ser conveniente que haya algo de azar, para que una partida no sea e xactamente igual a la anterior. Generar números al azar ("números aleatorios") usando C# no es difícil: debemos crear un objeto de tipo "Random", y luego llamaremos a "Next" para obtener valores entre dos extremos: // Creamos un objeto random Random r = new Random();
// Generamos un número entre dos valores dados int aleatorio = r.Next(1, 100);
Podemos hacer que sea realmente un poco más aleatorio si en la primera orden le indicamos que tome como semilla el instante actual: Random r = new Random(DateTime.Now.Millisecond );
De hecho, una forma muy simple de obtener un número "casi al azar" entre 0 y 999 es tomar las milésimas de segundo de la hora actual: int falsoAleatorio = DateTime.Now.Millisecond;
Vamos a ver un ejemplo, que muestre en pantalla un número al azar entre 1 y 1 0: /*---------------------------*/ /* Ejemplo en C# nº 56: */ /* ejemplo56.cs */ /* */ /* Números al azar */ /*---------------------------*/
- 76 -
Programación de Dispositivos Móviles
using System; public class Ejemplo56 { public static void Main() { Random r = new Random(DateTime.Now.Millisecond); int aleatorio = r.Next(1, 10); Console.WriteLine("Un número entre 1 y 10: {0}" , aleatorio); } }
Ejercicios propuestos: 49. Crear un programa que genere un número al azar entre 1 y 100. El usuario tendrá 6 oportunidades para acertarlo.
Funciones matemáticas En C# tenemos muchas funciones matemáticas predefinidas, como: • • • • • • • • • • • • • • • • • • •
Abs(x): Valor absoluto Acos(x): Arco coseno Asin(x): Arco seno Atan(x): Arco tangente Atan2(y,x): Arco tangente de y/x (por si x o y son 0) Ceiling(x): El valor entero superior a x y más cercano a él Cos(x): Coseno Cosh(x): Coseno hiperbólico Exp(x): Exponencial de x (e elevado a x) Floor(x): El mayor valor entero que es menor que x Log(x): Logaritmo natural (o neperiano, en base "e") Log10(x): Logaritmo en base 10 Pow(x,y): x elevado a y Round(x, cifras): Redondea un número Sin(x): Seno Sinh(x): Seno hiperbólico Sqrt(x): Raíz cuadrada Tan(x): Tangente Tanh(x): Tangente hiperbólica
(casi todos ellos usan parámetros X e Y de tipo "double") y una serie de constantes como • •
E, el número "e", con un valor de 2.71828... PI, el número "Pi", 3.14159...
Todas ellas se usan precedidas por "Math." La mayoría de ellas son específicas para ciertos problemas matemáticos, especialmente si interviene la trigonometría o si hay que usar logaritmos o exponenciales. Pero vamos a destacar las que sí pueden resultar útiles en situaciones más variadas: • • •
La raíz cuadrada de 4 se calcularía haciendo x = Math.Sqrt(4); La potencia: para elevar 2 al cubo haríamos y = Math.Pow(2, 3); El valor absoluto: para trabajar sólo con números positivos usaríamos n = Math.Abs(x);
- 77 -
Programación de Dispositivos Móviles
Pero hay muchas más funciones… Pero en C# hay muchas más funciones de lo que parece. De hecho, salvo algunas palabras reservadas (int, float, string, if, switch, for, do, while...), gran parte de lo que hasta ahora hemos llamado "órdenes", son realmente "funciones", como Console.ReadLine o Console.WriteLine. Nos iremos encontrando con otras funciones a medida que avancemos.
Recursividad Una función recursiva es aquella que se define a partir de ella misma. Dentro de las matemáticas tenemos varios ejemplos. Uno clásico es el "factorial de un número": n! = n • (n-1) • (n-2) • ... • 3 • 2 • 1
(por ejemplo, el factorial de 4 es 4 • 3 • 2 • 1 = 24) Si pensamos que el factorial de n-1 es (n-1)! = (n-1) • (n-2) • (n-3) • ... • 3 • 2 • 1
Entonces podemos escribir el factorial de un número a partir del factorial del siguiente número: n! = n • (n-1)!
Esta es la definición recursiva del factorial, ni más ni menos. Esto, programando, se haría: /*---------------------------*/ /* Ejemplo en C# nº 57: */ /* ejemplo57.cs */ /* */ /* Funciones recursivas: */ /* factorial */ /*---------------------------*/ using System; public class Ejemplo57 { public static long fact(int n) { if (n==1) // Aseguramos que termine return 1; return n * fact (n-1); // Si no es 1, sigue la recursión }
public static void Main() { int num; Console.WriteLine("Introduzca un número entero: " ); num = System.Convert.ToInt32(System.Console.ReadLine()); Console.WriteLine("Su factorial es: {0}", fact(num)); } }
Dos consideraciones importantes: Atención a la primera parte de la función recursiva: es MUY IMPORTANTE comprobar que hay salida de la función, para que nuestro programa no se quede dando vueltas todo el tiempo y deje el ordenador (o la tarea actual) "colgado". • Los factoriales crecen rápidamente, así que no conviene poner números grandes: el factorial de 16 es 2.004.189.184, luego a partir de 17 podemos obtener resultados erróneos, si usamos números enteros "normales". •
¿Qué utilidad tiene esto? Pues más de la que p arece: muchos problemas complicados se pueden expresar a partir de otro más sencillo. En muchos de esos casos, ese problema se podrá expresar de forma recursiva. Más adelante veremos algún o tro ejemplo.
- 78 -
Programación de Dispositivos Móviles
Parámetros y valor de retorno de "Main" Es muy frecuente que un programa que usamos desde la "línea de comandos" tenga ciertas opciones que le indicamos como argumentos. Por ejemplo, bajo Linux o cualquier otro sistema operativo de la familia Unix, podemos ver la lista detallada de ficheros que terminan en .c haciendo ls –l *.c
En este caso, la orden sería "ls", y las dos opciones (argumentos o parámetros) que le indicamos son "-l" y "*.c". La orden equivalente en MsDos y en el intérprete de comandos de Windows sería dir *.c
Ahora la orden sería "dir", y el parámetro es "*.c". Pues bien, estas opciones que se le pasan al programa se pueden leer desde C#. Se hace indicando un parámetro especial en Main, un array de strings: static void Main(string[] args)
Para conocer esos parámetros lo haríamos de la misma forma que se recorre habitualmente un array cuyo tamaño no conocemos: con un "for" que termine en la longitud ("Length") del array:
for (int i = 0; i < args.Length; i++) { System.Console.WriteLine("El parametro {0} es: {1}", i, args[i]); }
Por otra parte, si queremos que nuestro programa se interrumpa en un cierto punto, podemos usar la orden "Environment.Exit". Su manejo habitual es algo como Environment. Exit(1);
Es decir, entre paréntesis indicamos un cierto código, que suele ser (por convenio) un 0 si no ha habido ningún error, u otro código distinto en caso de que sí exista algún error. Este valor se podría comprobar desde el sistema operativo. Por ejemplo, en MsDos y Windows se lee con "IF ERRORLEVEL", así: IF ERRORLEVEL 1 ECHO Ha habido un error en el programa
Una forma alternativa de que "Main" indique errores al sistema operativo es no declarándolo como "void", sino como "int", y empleando entonces la orden "return" cuando nos interese: public static int Main(string[] args) { ... return 1; }
Un ejemplo que pusiera todo esto en prueba podría ser: /*---------------------------*/ /* Ejemplo en C# nº 58: */ /* ejemplo58.cs */ /* */ /* Parámetros y valor de */ /* retorno de "Main" */ /*---------------------------*/
- 79 -
Programación de Dispositivos Móviles
using System; public class Ejemplo58 { public static int Main(string[] args) { Console.WriteLine("Parámetros: {0}", args.Length); for (int i = 0; i < args.Length; i++) { Console.WriteLine("El parámetro {0} es: {1}", i, args[i]); } if (args.Length == 0) { Console.WriteLine("Escriba algún parámetro!"); Environment. Exit(1); } return 0; } }
Programación orientada a objetos ¿Por qué los objetos? Hasta ahora hemos estado "cuadriculando" todo para obtener algoritmos: tratábamos de convertir cualquier cosa en una función que pudiéramos emplear en nuestros programas. Cuando teníamos claros los pasos que había que dar, buscábamos las variables necesarias para dar esos pasos. Pero no todo lo que nos rodea es tan fácil de cuadricular. Supongamos por ejemplo que tenemos que introducir datos sobre una puerta en nuestro programa. ¿Nos limitamos a programar los procedimientos AbrirPuerta y CerrarPuerta? Al menos, deberíamos ir a la zona de declaración de variables, y allí guardaríamos otras datos como su tamaño, color, etc. No está mal, pero es "antinatural": una puerta es un conjunto: no podemos separar su color de su tamaño, o de la forma en que debemos abrirla o cerrarla. Sus características son tanto las físicas (lo que hasta ahora llamábamos variables) como sus comportamientos en distintas circunstancias (lo que para nosotros eran las funciones). Todo ello va unido, formando un "objeto". Por otra parte, si tenemos que explicar a alguien lo que es el portón de un garaje, y ese alguien no lo ha visto nunca, pero conoce cómo es la puerta de su casa, le podemos decir "se parece a una puerta de una casa, pero es más grande para que quepan los coches, está hecha de metal en vez de madera...". Es decir, podemos describir unos objetos a partir de lo que conocemos de otros. Finalmente, conviene recordar que "abrir" no se refiere sólo a una puerta. También podemos hablar de abrir una ventana o un libro, por ejemplo. Con esto, hemos comentado casi sin saberlo las tres características más importantes de la Programación Orientada a Objetos (OOP): Encapsulación: No podemos separar los comportamientos de las características de un objeto. Los comportamientos serán funciones, que en OOP llamaremos métodos. Las características de un objeto serán variables, como las que hemos usado siempre (las llamaremos atributos). La apariencia de un objeto en C#, como veremos un poco más adelante, recordará a un registro o "struct". • Herencia: Unos objetos pueden heredar métodos y datos de otros. Esto hace más fácil definir objetos nuevos a partir de otros que ya teníamos anteriormente (como ocurría con el portón y la puerta) y facilitará la reescritura de los programas, pudiendo aprovechar buena parte de los anteriores... si están bien diseñados. • Polimorfismo: Un mismo nombre de un método puede hacer referencia a comportamientos distintos (como abrir una puerta o un libro). Igual ocurre para los datos: el peso de una puerta y el de un portón los podemos llamar de igual forma, pero obviamente no valdrán lo mismo. •
Otro concepto importante es el de "clase": Una clase es un conjunto de objetos que tienen características comunes. Por ejemplo, tanto mi puerta como la de mi vecino son puertas, es decir, ambas son objetos que pertenecen a la clase "puerta". De igual modo, tanto un Ford Focus como un Honda Civic o un Toyota Corolla son objetos concretos que pertenecen a la clase "coche".
- 80 -
Programación de Dispositivos Móviles
Objetos y clases en C# Vamos con los detalles. Las clases en C# se definen de forma parecida a los registros (struct), sólo que ahora también incluirán funciones. Así, la clase "Puerta" que mencionábamos antes se podría declarar así: public class Puerta { int ancho; int alto; int color; bool abierta;
// // // //
Ancho en centimetros Alto en centimetros Color en formato RGB Abierta o cerrada
public void Abrir() { abierta = true; } public void Cerrar() { abierta = false; } public void MostrarEstado () { Console.WriteLine("Ancho: {0}", ancho); Console.WriteLine("Alto: {0}", alto); Console.WriteLine("Color: {0}", color); Console.WriteLine("Abierta: {0}", abierta); } } // Final de la clase Puerta
Como se puede observar, los objetos de la clase "Puerta" tendrán un ancho, un alto, un color, y un estado (abierta o no abierta), y además se podrán abrir o cerrar (y además, nos pueden "mostrar su estado, para comprobar que todo funciona correctamente). Para declarar estos objetos que pertenecen a la clase "Puerta", usaremos la palabra "new", igual que hacíamos con los "arrays": Puerta p = new Puerta(); p.Abrir(); p.MostrarEstado ();
Vamos a completar un programa de prueba que use un objeto de esta clase (una "Puerta"): /*---------------------------*/ /* Ejemplo en C# nº 59: */ /* ejemplo59.cs */ /* */ /* Primer ejemplo de clases */ /*---------------------------*/ using System; public class Puerta { int ancho; int alto; int color; bool abierta;
// // // //
Ancho en centimetros Alto en centimetros Color en formato RGB Abierta o cerrada
public void Abrir() { abierta = true; } public void Cerrar() {
- 81 -
Programación de Dispositivos Móviles
abierta = false; } public void MostrarEstado () { Console.WriteLine("Ancho: {0}", ancho); Console.WriteLine("Alto: {0}", alto); Console.WriteLine("Color: {0}", color); Console.WriteLine("Abierta: {0}", abierta); } } // Final de la clase Puerta
public class Ejemplo59 { public static void Main() { Puerta p = new Puerta(); Console.WriteLine("Valores iniciales..."); p.MostrarEstado (); Console.WriteLine("\nVamos a abrir..."); p.Abrir(); p.MostrarEstado (); } }
Este fuente ya no contiene una única clase (class), como todos nuestros ejemplos anteriores, sino dos clases distintas: • •
La clase "Puerta", que son los nuevos objetos con los que vamos a practicar. La clase "Ejemplo59", que representa a nuestra aplicación.
El resultado de ese programa es el siguiente: Valores iniciales... Ancho: 0 Alto: 0 Color: 0 Abierta: False Vamos a abrir... Ancho: 0 Alto: 0 Color: 0 Abierta: True
Se puede ver que en C#, al contrario que en otros lenguajes, las variables que forman parte de una clase (los "atributos") tienen un valor inicial predefinido: 0 para los números, una cadena vacía para las cadenas de texto, o "False" para los datos booleanos. Vemos también que se accede a los métodos y a los datos precediendo el nombre de cada uno por el nombre de la variable y por un punto, como hacíamos con los registros (struct). Aun así, en nuestro caso no podemos hacer directamente "p.abierta = true", por dos motivos: El atributo "abierta" no tiene delante la palabra "public"; por lo que no es público, sino privado, y no será accesible desde otras clases (en nuestro caso, desde Ejemplo59). • Los puristas de la Programación Orientada a Objetos recomiendan que no se acceda directamente a los atributos, sino que siempre se modifiquen usando métodos auxiliares (por ejemplo, nuestro "Abrir"), y que se lea su valor también usando una función. Esto es lo que se conoce como "ocultación de datos". Supondrá ventajas como que podremos cambiar los detalles internos de nuestra clase sin que afecte a su uso. •
También puede desconcertar que en "Main" aparezca la palabra "static", mientras que no lo hace en los métodos de la clase "Puerta". Veremos este detalle un poco más adelante.
- 82 -
Programación de Dispositivos Móviles
La herencia. Visibilidad Vamos a ver ahora cómo definir una nueva clase de objetos a partir de otra ya existente. Por ejemplo, vamos a crear una clase "Porton" a partir de la clase "Puerta". Un portón tendrá las mismas características que una puerta (ancho, alto, color, abierto o no), pero además se podrá bloquear, lo que supondrá un nuevo atributo y nuevos métodos para bloquear y desbloquear: public class Porton: Puerta { bool bloqueada; public void Bloquear() { bloqueada = true; } public void Desbloquear() { bloqueada = false; }
Con "public class Porton: Puerta" indicamos que Porton debe "heredar" todo lo que ya habíamos definido para Puerta. Por eso, no hace falta indicar nuevamente que un Portón tendrá un cierto ancho, o un color, o que se puede abrir: todo eso lo tiene por ser un "descendiente" de Puerta. No tenemos por qué heredar todo; también podemos "redefinir" algo que ya existía. Por ejemplo, nos puede interesar que "MostrarEstado" ahora nos diga también si la puerta está bloqueada. Para eso, basta con volverlo a declarar y añadir la palabra "new" para indicar al compilador de C# que sabemos que ya existe ese método y que sabemos seguro que lo queremos redefinir: public new void MostrarEstado () { Console.WriteLine("Ancho: {0}", ancho); Console.WriteLine("Alto: {0}", alto); Console.WriteLine("Color: {0}", color); Console.WriteLine("Abierta: {0}", abierta); Console.WriteLine("Bloqueada: {0}", bloqueada); }
Aun así, esto todavía no funciona: los atributos de una Puerta, como el "ancho" y el "alto" estaban declarados como "privados" (es lo que se considera si no decimos los contrario), por lo que no son accesibles desde ninguna otra clase, ni siquiera desde Porton. La solución más razonable no es declararlos como "public", porque no queremos que sean accesibles desde cualquier sitio. Sólo querríamos que esos datos estuvieran disponibles para todos los tipos de Puerta, incluyendo sus "descendientes", como un Porton. Esto se puede conseguir usando otro método de acceso: "protected". Todo lo que declaremos como "protected" será accesible por las clases derivadas de la actual, pero por nadie más: public class Puerta { protected protected protected protected
int ancho; int alto; int color; bool abierta;
// // // //
Ancho en centimetros Alto en centimetros Color en formato RGB Abierta o cerrada
public void Abrir() ...
(Si quisiéramos dejar claro que algún elemento de una clase debe ser totalmente privado, podemos usar la palabra "private", en vez de "public" o "protected"). Un fuente completo que declarase la clase Puerta, la clase Porton a partir de ella, y que además contuviese un pequeño "Main" de prueba podría ser: /*---------------------------*/ /* Ejemplo en C# nº 60: */ /* ejemplo60.cs */
- 83 -
Programación de Dispositivos Móviles
/* */ /* Segundo ejemplo de */ /* clases: herencia */ /*---------------------------*/ using System;
// ------------------------------public class Puerta { protected protected protected protected
int ancho; int alto; int color; bool abierta;
// // // //
Ancho en centimetros Alto en centimetros Color en formato RGB Abierta o cerrada
public void Abrir() { abierta = true; } public void Cerrar() { abierta = false; } public void MostrarEstado () { Console.WriteLine("Ancho: {0}", ancho); Console.WriteLine("Alto: {0}", alto); Console.WriteLine("Color: {0}", color); Console.WriteLine("Abierta: {0}", abierta); } } // Final de la clase Puerta
// ------------------------------public class Porton: Puerta { bool bloqueada; public void Bloquear() { bloqueada = true; } public void Desbloquear() { bloqueada = false; } public new void MostrarEstado () { Console.WriteLine("Ancho: {0}", ancho); Console.WriteLine("Alto: {0}", alto); Console.WriteLine("Color: {0}", color); Console.WriteLine("Abierta: {0}", abierta); Console.WriteLine("Bloqueada: {0}", bloqueada); } } // Final de la clase Porton
// ------------------------------public class Ejemplo60 { public static void Main() { Porton p = new Porton();
- 84 -
Programación de Dispositivos Móviles
Console. WriteLine( Console.WriteLine ("Valores iniciales...") iniciales..." ); p.MostrarEstado p. MostrarEstado (); (); Console. WriteLine( Console.WriteLine ("\nVamos a bloquear y a abrir..." ); p.Bloquear p. Bloquear() (); ; p.MostrarEstado p. MostrarEstado (); (); Console. WriteLine( Console.WriteLine ("\nVamos a desbloquear y a abrir..." ); p.Abrir p. Abrir() (); ; p.Desbloquear p. Desbloquear() (); ; p.MostrarEstado p. MostrarEstado (); (); } }
¿Cómo se diseñan las clases? En estos primeros ejemplos, hemos "pensado" qué objetos necesitaríamos, y hemos empezado a teclear directamente para implementarlos. Esto no es lo habitual. Normalmente, se usan herramientas gráficas que nos ayuden a visualizar las clases y las relaciones que existen entre ellas. También se puede dibujar directamente en papel para aclararnos las ideas, pero el empleo de herramientas informáticas tiene una ventaja adicional: algunas de ellas nos permiten generar automáticamente un esqueleto del programa. La metodología más extendida actualmente para diseñar estos objetos y sus interacciones (además de otras muchas cosas) se conoce como UML (Unified Modelling Language, lenguaje de modelado unificado). El estándar UML propone distintos tipos de diagramas para representar los posibles "casos de uso" de una aplicación, la secuencia de acciones que se debe seguir, las clases que la van a integrar (es lo que a nosotros nos interesa en este momento), etc. Vamos a ver la apariencia que tendría un "diagrama de clases". En concreto, vamos a ver un ejemplo usando ArgoUML, que es una herramienta gratuita de modelado UML, que está creada en Java, por lo que se puede utilizar desde multitud de sistemas operativos. Ampliando el ejemplo anterior, vamos a suponer que queremos hacer un sistema "domótico", para automatizar ciertas funciones en una casa: apertura y cierre de ventanas y puertas, encendido de calefacción, etc. Las ideas iniciales de las que partiremos son: • • • •
La casa es el conjunto ("agregación") de varios elementos: puertas, ventanas y calefactores. Cada puerta se puede abrir y cerrar. Cada ventana se puede abrir, cerrar. Además, las ventanas tienen persianas, que se pueden subir y bajar. Cada calefactor puede encenderse, apagarse o se puede programar para que trabaje a una cierta temperatura.
Con estas posibilidades básicas, el diagrama de clases podría ser así:
Este diagrama es una forma más simple de ver las clases existentes y las relaciones entre ellas. Si generamos las clases a partir del diagrama, tendremos parte del trabajo hecho: ya "sólo" nos quedará rellenar los detalles de métodos como "Abrir", pero el esqueleto de todas las clases ya estará "escrito" para nosotros.
- 85 -
Programación de Dispositivos Móviles
La palabra "static" Desde un principio, nos hemos encontrado con que "Main" siempre iba acompañado de la palabra "static". En cambio, los métodos (funciones) que pertenecen a nuestros objetos no l os estamos declarando como "static". Vamos a ver el motivo: La palabra "static" delante de un atributo (una variable) de una clase, indica que es una "variable de clase", es decir, que su valor es el mismo para todos los objetos de la clase. Por ejemplo, si hablamos de coches convencionales, podríamos suponer que el atributo "numeroDeRuedas" va a valer 4 para cualquier objeto que pertenezca a esa clase (cualquier coches). Por eso, se podría declarar como "static". De igual modo, si un método (una función) está precedido por la palabra "static", indica que es un "método de clase", es decir, un método que se podría usar sin necesidad de declarar ningún objeto de la clase. Por ejemplo, si queremos que se pueda usar la función "BorrarPantalla" de una clase "Hardware" sin n ecesidad de crear primero un objeto perteneciente a esa clase, lo podríamos conseguir así: public class Hardware { ... public static void BorrarPantalla () { ... }
que desde dentro de "Main" (incluso perteneciente a otra clase) se usaría con el nombre de la clase delante: public class Juego { ... public ComienzoPartida () { Hardware.BorrarPantalla Hardware. BorrarPantalla (); (); ...
Desde una función "static" no se puede llamar a otras funciones que no lo sean. Por eso, como nuestro "Main" debe ser static, deberemos siempre elegir entre: Que todas las demás funciones de nuestro fuente también estén declaradas como "static", por lo que podrán ser utilizadas desde "Main". • Declarar un objeto de la clase correspondiente, y entonces sí podremos acceder a sus métodos desde "Main": •
public class Ejemplo { ... public LanzarJuego () Juego j = new Juego Juego() (); ; j.ComienzoPartida j. ComienzoPartida (); (); ...
{
Constructores y destructores. Hemos visto que al declarar una clase, se dan valores por defecto para los atributos. Por ejemplo, para un número entero, se le da el valor 0. Pero puede ocurrir que nosotros deseemos dar valores iniciales que no sean cero. Esto se puede conseguir declarando un "constructor" para la clase. Un constructor constructor es una funci función ón especial, especial, que se pone en marc marcha ha cuando se crea un objeto de una clase, y se suel suele e usar para dar esos valores iniciales, para reservar memoria si fuera necesario, etc. Se declara usando el mismo nombre que el de la clase, y sin ningún tipo de retorno. Por ejemplo, un "constructor" para la clase Puerta que le diera los valores iniciales de 100 para el ancho, 200 para el alto, etc., podría ser así: public Puerta Puerta() () { ancho = 100 100; ; alto = 200 200; ;
- 86 -
Programación de Dispositivos Móviles
color = 0xFFFFFF; abierta = false; }
Podemos tener más de un constructor, cada uno con distintos parámetros. Por ejemplo, puede haber otro constructor que nos permita indicar el ancho y el alto: public Puerta Puerta( (int an, int al al) ) { ancho = an; alto = al; color = 0xFFFFFF; abierta = false; }
Ahora, si declaramos un objeto de la clase puerta con "Puerta p = new Puerta();" tendrá de ancho 100 y de alto 200, mientras que si lo declaramos con "Puerta p2 = new Puerta(90,220);" tendrá 90 como ancho y 220 como alto. Un programa de ejemplo que usara estos dos constructores para crear dos puertas con características iniciales distintas podría ser: /*---------------------------*/ /* Ejemplo en C# nº 61: */ /* ejemplo61.cs */ /* */ /* Tercer ejemplo de clases */ /* Constructores */ /*---------------------------*/ using System System; ; public class Puerta { int ancho; int alto; int color; bool abierta;
// // // //
Ancho en centimetros Alto en centimetros Color en formato RGB Abierta o cerrada
public Puerta Puerta() () { ancho = 100 100; ; alto = 200 200; ; color = 0xFFFFFF; abierta = false; } public Puerta Puerta( (int an, int al al) ) { ancho = an; alto = al; color = 0xFFFFFF; abierta = false; } public void Abrir Abrir() () { abierta = true; } public void Cerrar Cerrar() () { abierta = false; } public void MostrarEstado () { Console.WriteLine Console. WriteLine( ("Ancho: {0}", {0}" , ancho) ancho); Console.WriteLine Console. WriteLine( ("Alto: {0}", {0}", alto) alto); Console.WriteLine Console. WriteLine( ("Color: {0}", {0}" , color) color);
- 87 -
Programación de Dispositivos Móviles
Console.WriteLine("Abierta: {0}", abierta); } } // Final de la clase Puerta
public class Ejemplo61 { public static void Main() { Puerta p = new Puerta(); Puerta p2 = new Puerta(90,220); Console.WriteLine("Valores iniciales..."); p.MostrarEstado (); Console.WriteLine("\nVamos a abrir..."); p.Abrir(); p.MostrarEstado (); Console.WriteLine("Para la segunda puerta..."); p2.MostrarEstado (); } }
Nota: al igual que existen los "constructores", también podemos indicar un "destructor" para una clase, que se encargue de liberar la memoria que pudiéramos haber reservado en nuestra clase (no es nuestro caso, porque aún no sabemos manejar memoria dinámica) o para cerrar ficheros abiertos (que tampoco sabemos). Un "destructor" se llama igual que la clase, pero precedido por el símbolo "~", no tiene tipo de retorno, y no necesita ser "public", como ocurre en este ejemplo: ~Puerta() {
// Liberar memoria // Cerrar ficheros }
Polimorfismo y sobrecarga Esos dos constructores "Puerta()" y "Puerta(int ancho, int alto)", que se llaman igual pero reciben distintos parámetros, y se comportan de forma que puede ser distinta, son ejemplos de "polimorfismo" (funciones que tienen el mismo nombre, pero distintos parámetros, y que quizá no se comporten de igual forma). Un concepto muy relacionado con el polimorfismo es el de "sobrecarga": dos funciones están sobrecargadas cuando se llaman igual, reciben el mismo número de parámetros, pero se aplican a objetos d istintos, así: puerta.Abrir (); libro.Abrir ();
En este caso, la función "Abrir" está sobrecargada: se usa tanto para referirnos a abrir un libro como para abrir una puerta. Se trata de dos acciones que no son exactamente iguales, que se aplican a objetos distintos, pero que se llaman igual.
Constructores Cuando creamos objetos de una clase derivada, antes de llamar a su constructor se llama a los constructores de las clases base, empezando por la más general y terminando por la más específica. Por ejemplo, si creamos una clase "GatoSiamés", que deriva de una clase "Gato", que a su vez procede de una clase "Animal", el orden de ejecución de los constructores sería: Animal, Gato, GatoSiames, como se ve en este ejemplo: /*---------------------------*/ /* Ejemplo en C# nº 62: */ /* ejemplo62.cs */ /* */ /* Cuarto ejemplo de clases */ /* Constructores y herencia */ /*---------------------------*/
- 88 -
Programación de Dispositivos Móviles
using System; public class Animal { public Animal() { Console.WriteLine("Ha nacido un animal"); } }
// -----------------public class Perro: Animal { public Perro() { Console.WriteLine("Ha nacido un perro"); } }
// -----------------public class Gato: Animal { public Gato() { Console.WriteLine("Ha nacido un gato"); } }
// -----------------public class GatoSiames: Gato { public GatoSiames() { Console.WriteLine("Ha nacido un gato siamés"); } }
// -----------------public class Ejemplo62 { public static void Main() { Animal a1 = new Animal(); GatoSiames a2 = new GatoSiames(); Perro a3 = new Perro(); Gato a4 = new Gato(); } }
El resultado de este programa es: Ha Ha Ha Ha Ha Ha Ha Ha
nacido nacido nacido nacido nacido nacido nacido nacido
un un un un un un un un
animal animal gato gato siamés animal perro animal gato
- 89 -
Programación de Dispositivos Móviles
Ejercicio propuesto: 50. Crear un único fuente que contenga las siguientes clases: o Una clase Trabajador, cuyo constructor escriba en pantalla "Soy un trabajador". o Una clase Programador, que derive de Trabajador, cuyo constructor escriba en pantalla "Soy programador". o Una clase Analista, que derive de Trabajador, cuyo constructor escriba en pantalla "Soy analista". o Una clase Ingeniero, que derive d e Trabajador, cuyo constructor escriba en pantalla "Soy ingeniero". o Una clase IngenieroInformatico, que derive de Ingeniero, cuyo constructor escriba en pantalla "Soy ingeniero informático". o Una clase "PruebaDeTrabajadores", que cree un objeto perteneciente a cada una de esas clases.
Arrays de objetos Es muy frecuente que no nos baste con tener un objeto de cada clase, sino que necesitemos manipular varios objetos pertenecientes a la misma clase. En ese caso, deberemos reservar memoria primero para el array, y luego para cada uno de los elementos. Por ejemplo, podríamos tener un array de 5 perros, que crearíamos de esta forma: Perro[] misPerros = new Perro[5]; for (byte i = 0; i < 5; i ++) misPerros[i] = new Perro();
Un fuente completo de ejemplo podría ser /*---------------------------*/ /* Ejemplo en C# nº 63: */ /* ejemplo63.cs */ /* */ /* Quinto ejemplo de clases */ /* Array de objetos */ /*---------------------------*/ using System; public class Animal { public Animal() { Console.WriteLine("Ha nacido un animal"); } }
// -----------------public class Perro: Animal { public Perro() { Console.WriteLine("Ha nacido un perro"); } }
// -----------------public class Ejemplo63 {
- 90 -
Programación de Dispositivos Móviles
public static void Main() { Perro[] misPerros = new Perro[5]; for (byte i = 0; i < 5; i ++) misPerros[i] = new Perro(); } }
y su salida en pantalla, parecida a la del ejemplo anterior, sería Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha
nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido
un un un un un un un un un un
animal perro animal perro animal perro animal perro animal perro
Además, existe una peculiaridad curiosa: podemos crear un array de "Animales", pero luego indicar que unos de ellos son perros, otros gatos, etc., Animal[] misAnimales = new Animal[3]; misAnimales[0] = new Perro(); misAnimales[1] = new Gato(); misAnimales[2] = new GatoSiames();
Un ejemplo más detallado: /*---------------------------*/ /* Ejemplo en C# nº 64: */ /* ejemplo64.cs */ /* */ /* Ejemplo de clases */ /* Array de objetos de */ /* varias subclases */ /*---------------------------*/ using System; public class Animal { public Animal() { Console.WriteLine("Ha nacido un animal"); } }
// -----------------public class Perro: Animal { public Perro() { Console.WriteLine("Ha nacido un perro"); } }
// -----------------public class Gato: Animal {
- 91 -
Programación de Dispositivos Móviles
public Gato() { Console.WriteLine("Ha nacido un gato"); } }
// -----------------public class GatoSiames: Gato { public GatoSiames() { Console.WriteLine("Ha nacido un gato siamés"); } }
// -----------------public class Ejemplo64 { public static void Main() { Animal[] misAnimales = new Animal[8]; misAnimales[0] = new Perro(); misAnimales[1] = new Gato(); misAnimales[2] = new GatoSiames(); for (byte i=3; i<7; i++) misAnimales[i] = new Perro(); misAnimales[7] = new Animal(); } }
La salida de este programa sería: Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha Ha
nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido nacido
un un un un un un un un un un un un un un un un
animal perro animal gato animal gato gato siamés animal perro animal perro animal perro animal perro animal
Funciones virtuales. La palabra "override" En el ejemplo anterior hemos visto cómo crear un array de objetos, usando sólo la clase base, pero insertando realmente objetos de cada una de las clases derivadas que nos interesaba, y hemos visto que los constructores se llaman correctamente... pero con los métodos puede haber problemas. Vamos a verlo con un ejemplo, que en vez de tener constructores va a tener un único método "Hablar", que se redefine en cada una de las clases hijas, y después comentaremos qué ocurre al ejecutarlo: /*---------------------------*/ /* Ejemplo en C# nº 65: */
- 92 -
Programación de Dispositivos Móviles
/* ejemplo65.cs */ /* */ /* Ejemplo de clases */ /* Array de objetos de */ /* varias subclases con */ /* metodos */ /*---------------------------*/ using System; public class Animal { public void Hablar() { Console.WriteLine("Estoy comunicándome..."); } }
// -----------------public class Perro: Animal { public new void Hablar() { Console.WriteLine("Guau!"); } }
// -----------------public class Gato: Animal { public new void Hablar() { Console.WriteLine("Miauuu"); } }
// -----------------public class Ejemplo65 { public static void Main() {
// Primero creamos un animal de cada tipo Perro miPerro = new Perro(); Gato miGato = new Gato(); Animal miAnimal = new Animal(); miPerro.Hablar(); miGato.Hablar(); miAnimal.Hablar();
// Linea en blanco, por legibilidad Console.WriteLine();
// Ahora los creamos desde un array Animal[] misAnimales = new Animal[3]; misAnimales[0] = new Perro(); misAnimales[1] = new Gato(); misAnimales[2] = new Animal(); misAnimales[0].Hablar(); misAnimales[1].Hablar(); misAnimales[2].Hablar();
- 93 -
Programación de Dispositivos Móviles
} }
La salida de este programa es: Guau! Miauuu Estoy comunicándome... Estoy comunicándome... Estoy comunicándome... Estoy comunicándome...
La primera parte era de esperar: si creamos un perro, debería decir "Guau", un gato debería decir "Miau" y un animal genérico debería comunicarse. Eso es lo que se consigue con este fragmento: Perro miPerro = new Perro(); Gato miGato = new Gato(); Animal miAnimal = new Animal(); miPerro.Hablar(); miGato.Hablar(); miAnimal.Hablar();
En cambio, si creamos un array de animales, no se comporta correctamente, a pesar de que después digamos que el primer elemento del array es un perro: Animal[] misAnimales = new Animal[3]; misAnimales[0] = new Perro(); misAnimales[1] = new Gato(); misAnimales[2] = new Animal(); misAnimales[0].Hablar(); misAnimales[1].Hablar(); misAnimales[2].Hablar();
Es decir, como la clase base es "Animal", el primer elemento hace lo que corresponde a un Animal genérico (decir "Estoy comunicándome"), a pesar de que hayamos dicho que se trata de un Perro. Generalmente, no será esto lo que queramos. Sería interesante no necesitar crear un array de perros y otros de gatos, sino p oder crear un array de animales, y que contuviera animales de distintos tipos. Para conseguir este comportamiento, debemos indicar a nuestro compilador que el método "Hablar" que se usa en la clase Animal puede que sea redefinido por otras clases hijas, y que en ese caso debe prevalecer lo que indiquen las clases hijas. La forma de hacerlo es declarando ese método "Hablar" como "virtual", y empleando en las clases hijas la palabra "override" en vez de "new", así: /*---------------------------*/ /* Ejemplo en C# nº 66: */ /* ejemplo66.cs */ /* */ /* Ejemplo de clases */ /* Array de objetos de */ /* varias subclases con */ /* metodos virtuales */ /*---------------------------*/ using System; public class Animal {
- 94 -
Programación de Dispositivos Móviles
public virtual void Hablar() { Console.WriteLine("Estoy comunicándome..."); } }
// -----------------public class Perro: Animal { public override void Hablar() { Console.WriteLine("Guau!"); } }
// -----------------public class Gato: Animal { public override void Hablar() { Console.WriteLine("Miauuu"); } }
// -----------------public class Ejemplo66 { public static void Main() {
// Primero creamos un animal de cada tipo Perro miPerro = new Perro(); Gato miGato = new Gato(); Animal miAnimal = new Animal(); miPerro.Hablar(); miGato.Hablar(); miAnimal.Hablar();
// Linea en blanco, por legibilidad Console.WriteLine();
// Ahora los creamos desde un array Animal[] misAnimales = new Animal[3]; misAnimales[0] = new Perro(); misAnimales[1] = new Gato(); misAnimales[2] = new Animal(); misAnimales[0].Hablar(); misAnimales[1].Hablar(); misAnimales[2].Hablar(); } }
El resultado de este programa ya sí es el que posiblemente deseábamos: tenemos un array de animales, pero cada uno "Habla" como corresponde a su especie: Guau! Miauuu Estoy comunicándome... Guau!
- 95 -
Programación de Dispositivos Móviles
Miauuu Estoy comunicándome...
Llamando a un método de la clase "padre" Puede ocurrir que en un método de una clase hija no nos interese redefinir por completo las posibilidades del método equivalente, sino ampliarlas. En ese caso, no hace falta que volvamos a teclear todo lo que hacía el método de la clase base, sino que podemos llamarlo directamente, precediéndolo de la palabra "base". Por ejemplo, podemos hacer que un Gato Siamés hable igual que un Gato normal, pero diciendo "Pfff" después, así: public new void Hablar() { base.Hablar(); Console.WriteLine("Pfff"); }
Este podría ser un fuente completo: /*---------------------------*/ /* Ejemplo en C# nº 67: */ /* ejemplo67.cs */ /* */ /* Ejemplo de clases */ /* Llamar a la superclase */ /*---------------------------*/ using System; public class Animal { }
// -----------------public class Gato: Animal { public void Hablar() { Console.WriteLine("Miauuu"); } }
// -----------------public class GatoSiames: Gato { public new void Hablar() { base.Hablar(); Console.WriteLine("Pfff"); } }
// -----------------public class Ejemplo67 { public static void Main() { Gato miGato = new Gato(); GatoSiames miGato2 = new GatoSiames(); miGato.Hablar(); Console.WriteLine(); miGato2.Hablar();
// Linea en blanco
}
- 96 -
Programación de Dispositivos Móviles
}
Su resultado sería Miauuu Miauuu Pfff
Sobrecarga de operadores Los "operadores" son los símbolos que se emplean para indicar ciertas operaciones. Por ejemplo, el operador "+" se usa para indicar que queremos sumar dos números. Pues bien, en C# se puede "sobrecargar" operadores, es decir, redefinir su significado, para poder sumar (por ejemplo) objetos que nosotros hayamos creado, de forma más cómoda y legible. Por ejemplo, para sumar dos matrices, en vez de hacer algo como "matriz3 = suma( matriz1, matriz2 )" podríamos hacer simplemente " matriz3 = matriz1 + matriz2" No entraremos en detalle, pero la idea está en que redefiniríamos un método llamado "operador +", y que devolvería un dato del tipo con el que estamos (por ejemplo, una Matriz) y recibiría dos datos de ese mismo tipo como parámetros: public static Matriz operator +(Matriz mat1, Matriz mat2) { Matriz nuevaMatriz = new Matriz(); for (int x=0; x < tamanyo; x++) for (int y=0; y < tamanyo; y++) nuevaMatriz[x, y] = mat1[x, y] + mat2[x, y]; return nuevaMatriz; }
Desde "Main", calcularíamos una matriz como suma de otras dos haciendo simplemente Matriz matriz3 = matriz1 + matriz2;
- 97 -
Programación de Dispositivos Móviles
Windows Mobile 5.0 (aplicable a Windows Mobile 6.5) Windows Mobile 5.0 y Windows Mobile 6.5 corresponden a la siguiente generación de Windows para dispositivos móviles. Con Windows Mobile llegan nuevas herramientas, APIS y tecnologías para el desarrollo de aplicaciones nativas o manejadas. No obstante, la guerra por los móviles se ha recrudecido en los últimos años con la aparición del dispositivo móvil iPhone de Apple, por su entorno de desarrollo propietario XCode, y por el empuje hacia esta área que está llevando a cabo Google con sus dispositivos móviles y con Android como sistema operativo abierto sobre el cual desarrollar aplicaciones. Se puede decir ya, que estamos frente a una nueva área de lucha, el mercado de los dispositivos móviles, que se popularizó especialmente con la Palm y los PocketPC y que ahora tiene por protagonistas a los dispositivos móviles más puros. Siempre y sin perder de vista, la web como telón de fondo. Microsoft ha renovado por ello Windows Mobile 6.5 y se prepara para el asalto a lo que es la nueva guerra tecnológica de la que los usuarios y los programadores seremos los más beneficiados. Aún y así, Windows Mobile tal y como lo conocemos aún, no tiene los días contados como muchos quieren hacer ver, y tiene todavía un presente muy interesante, que obviamente cambiará en los próximos años debido a los cambios tecnológicos y las demandas cambiantes.
Uso de Visual Studio 2005 Cuadro de herramientas
El cuadro o barra de herramientas de Visual Studio 2010, nos permite utilizar los distintos componentes que tenemos a nuestra disposición. En visual Studio 2010 tenemos una gran cantidad de controles dispuestos en distintas categorias. En la figura 1 podemos ver la barra de herramientas de Visual Studio 2010.
Cuadro de Herramientas de Visual Studio 2005 (aplicable a Visual Studio 2010) Cuando iniciamos un nuevo proyecto en Visual Studio 2005 este cuadro de herramientas queda rellenado con los controles que tenemos disponibles para el tipo de proyecto que hemos seleccionado. En la figura 2 se muestra la caja de herramientas con los controles disponibles para un poryecto WinForm de Compact Framework.
- 98 -
Programación de Dispositivos Móviles
Cuadro de Herramientas de Visual Studio (aplicable a Visual Studio 2010)
Explorador de bases de datos Desde el explorador de bases de datos un programador puede acceder a los distintos recursos del sistema. El principal y más importante recurso es el que tiene que ver con la bases de datos. Ya sea Sql Server Sql Server Mobile Edition o Microsoft Access ,etc.
Explorador de servidores en Visual Studio 2005 (aplicable a Visual Studio 2010) Desde esta misma ventana podemos crear nuevas bases de datos y/o manejar las bases de datos existentes. En la Figura 2 se muestra el proceso para agregar una nueva conexión de Microsoft SQL Server Mobile Edition.
- 99 -
Programación de Dispositivos Móviles
Nueva conexión en el explorador de servidores en Visual Studio 2005 (aplicable a Visual Studio 2010)
Explorador de soluciones Podríamos decir que un proyecto en Visual Studio 2010 equivale a una solución sin embargo esto no es cierto. Una solución puede constar de uno o varios proyectos de cualquier tipo o de recursos varios. Los conceptos de proyecto y solución son sencillos de entender y para nada debe hacernos pensar que es algo complejo y dificil de dominar. En la Figura podemos ver el explorador de soluciones en Visual Studio 2010 con dos poryectos en una solución, un proyecto de WindowsForms para un dispositivo móvil y un proyecto de biblioteca de clases.
Explorador de soluciones en Visual Studio 2005 (aplicable a Visual Studio 2010) Si queremos añadir un nuevo elemento a un proyecto únicamente tenemos que hacer clic con el botón derecho encima del proyecto en el explorador de soluciones e ir a Agregar elemento.
Nota: Para abrir un recurso de la solución, basta con situarnos en el recurso determinado, por ejemplo un formulario Windows de nombre Form1.cs y hacer doble clic sobre él. El recurso se abrirá automáticamente en Visual Studio. Además, en Visual Studio 2010 sabremos en todo momento sobre qué recurso estamos trabajando en un momento dado
Propiedades
La ventana de propiedades nos permitirá acceder a las propiedades de los objetos seleccionados tal y como se muestra en la Figura.
- 100 -
Programación de Dispositivos Móviles
Ventana de propiedades en Visual Studio 2005 (aplicable a Visual Studio 2010) Nota: Para acceder a las propiedades de un objeto únicamente es necesario seleccionarlo y presionar la tecla F4
Menús y barras de botones Respecto de los menús y barras de botones del entorno Visual Studio 2005, son muchas las opciones que tenemos disponibles. Las barras de botones son configurables con lo que podemos elegir aquellas que deseemos que siempre estén visibles al arrancar el entorno. El contenido del menu varía en función del elemento que tengamos seleccionado y se mostrarán las partes que sean relevantes para el. Algunas de estas opciones que tenemos en los menús también las tendremos disponibles usando los menús contextuales y como es de esperar también son diferentes según el e lemento seleccionado.
Menús y barras de botones en Visual Studio 2005 (aplicable a Visual Studio 2010) Nota: Para acceder a las propiedades de un objeto únicamente es necesario seleccionarlo y presionar la tecla F4
Device Emulator Manager El 'Manager' de los emuladores ( Device Emulator Manager ) nos permité trabajar con los distinos emuladores existentes en nuestro sistema, desde emuladores de Phone Edition 2003-SE o WM 5.0 hasta dispositivos Windows CE 5.0. Este 'Manager' nos va a permitir trabajar con los emuladores como si realmente lo estuviéramos haciendo con un dispositivo real. Para activar esta herramienta únicamente tenemos que dirigirnos al menu 'Tools' ( Herramientas ) tal y como podemos observar en la Figura 1.
- 101 -
Programación de Dispositivos Móviles
Activación de 'Device Emulator Manager' Una vez que activado el 'Device Emulator Manager' este nos muestra los distintos emuladores que tenemos a nuestra disposición en el sistema puediendo activar uno o otro o varios emuladores a la vez.
Device Emulator manager Para activar un emulador basta con hacer clic con el botón derecho sobre el emulador a activar y en el menú contextual seleccionar 'Conectar'.
- 102 -
Programación de Dispositivos Móviles
Conectando un emulador con Device Emulator Manager Una Vez activado el emulador seleccionado tendremos a nuestra disposición la posibilidad de conectarlo con Microsoft Active Sync de igual forma que lo haríamos con un dispositivo real lo que nos proporcionará la posibilidad de realizar todas las tareas que pudiéramos hacer con un dispositivo conectado al PC.
Cradle de un emulador Una vez activado el emulador ya lo tenemos a nuestra disposición y en el puede probarse el resultado de nuestros desarrollos de un forma rápida y sencialla.
Emulador Otra de las posibilidades que nos ofrece Visual Studio 2010 es la de agregar ciertos parámetros de configuración de nuestros emuladores de tal forma que podemos por ejemplo, especificar por ejemplo el grupo de favoritos, el estado de Bluetooth etc.
- 103 -
Programación de Dispositivos Móviles
Una vez tengamos listo nuestro archivo de configuración nos dirigimos a Visual Studio 2005 y en el menú Herramientas-Opciones y en la opción Herramientas de dispositivos seleccionamos el emulador para el cual queremos proveer una determinada configuración.
Provisionamiento de configuración con Visual Studio 2005 (aplicable a Visual Studio 2010) Una vez tengamos seleccionado el emulador para el cual queremos proveer una configuración específica hacemos clic en el botón 'Propiedades' y establecemos la ruta del archivo de configuración que deseamos aplicar seleccionando el mismo en el botón Configurar.
Provisionamiento de configuración con Visual Studio 2005 (aplicable a Visual Studio 2010)
Controles en Compact Framework Una vez familiarizados con el entorno de diseño de nuestra herramienta de trabajo el siguiente pasó será el de conocer el amplio y variado conjunto de controles de los que disponemos en Visual Studio 2005. En nuestro entorno de desarrollo encontraremos direrentes grupos de controles o componentes dispuestos para ser utilizados tal y como se muestra en la figura.
- 104 -
Programación de Dispositivos Móviles
Figura 1. Grupo de controles en Visual Studio 2005 (aplicable a Visual Studio 2010) Estos controles se dividen en los grupos representandos en la figura anterior. A continuación veremos los más representativos.
Datos El grupo 'Datos' corresponde con el grupo cuyos controles tiene relación directa con los componentes de acceso a datos, tal y como se muestra en la Figura.
Cuadro de Herramientas de Visual Studio 2005, grupo de datos (aplicable a Visual Studio 2010) Para muchos desarrolladores los controles, componentes y métodos de acceso a datos son la principal dificultad a la hora de desarrollar una aplicación. No se preocupe, aprenderemos a utilizarlos a base de práctica y lo que es más importante los dominaremos rápidamente. A modo de introducción vemos un ejemplo del control DataGrid situado en el grupo de datos,en el vemos como mostrar los contactos del dispositivo móvil en el control y su resultado en un dispositivo PocketPC y SmartPhone.
- 105 -
Programación de Dispositivos Móviles
Ejemplo de control DataGrid en PocketPC
Ejemplo de control DataGrid en SmartPhone
Componentes
Visual Studio 2010 trae consigo un conjunto variados de componentes.En la Figura puede ver estos componentes.
Cuadro de Herramientas de Visual Studio 2005, grupos de componentes (aplicable a Visual Studio 2010)
- 106 -
Programación de Dispositivos Móviles
El comportamiento de los componentes es distinto al de los controles comunes. Si hacemos por ejemplo doble clic en el componente Timer para insertarlo en el formulario, este quedará dispuesto en la parte inferior del mismo tal y com se puede observar en la Figura 2. Los componentes no son visibles en tiempo de ejecución al contrario que los controles.
Componente Timer
Controles Comunes Con este nombre, se aglutinan los controles más generales y variados que podemos utilizar en nuestras aplicaciones. Sería algo así, como el resto de controles y componentes no contenidos en ninguna de las secciones que hemos visto anteriormente, aunque esto no es siempre así. Por esa razón si encuentra dos controles o componentes iguales en dos o más secciones, no lo tenga en consideración. En esta solapa, encontraremos por lo tanto los controles que utilizamos con más frecuencia. En la Figura podemos observar alguno de estos controles.
Grupo de controles comunes El abanico de controles en esta sección es muy amplio pudiéndose encontrar en esta sección los controles necesarios para realizar la mayor parte de las aplicaciones. Cada uno de los controles tiene unas características y cualidades determinadas. Sólo a base de práctica, aprenderemos a utilizarlos y lo único que debemos saber, es cuál de ellos usar en un momento dado.
Trabajo con los controles En este momento ya conocemos un poco más nuestra herramienta y la serie de ayudas que esta nos proporcionará en nuestros desarrollos por lo que ya estamos en disposición de empezar el trabajo, en este módulo veremos como usar los diversos controles que tenemos a nuestra disposición y como aprovechar sus características en nuestros desarrollos. Aprenderemos como usar los controles disponibles en Visual Studio 2005 así como a crear nuestros propios controles si ninguno de los existentes se ajusta a nuestras necesidades. Para terminar veremos algunas consideraciones a tener en cuenta en el trabajo con los controles en Visual Studio 2005.
Dominando los controles en el entorno de trabajo En las lecciones anteriores hemos visto como colocar un control en un formulario, pero quizás no sepamos como manejarlos de forma eficiente en nuestro formulario. Para ello vamos a realizar un sencillo ejemplo. Creamos un nuevo projecto para dispositivos móviles e insertamos en el formulario 3 controles Etiqueta tal y como puede verse en la Figura.
- 107 -
Programación de Dispositivos Móviles
Controles en Visual Studio 2005 (aplicable a Visual Studio 2010) Como podemos observar estos controles están dispuestos de una forma desordenada, ya que al insertarlos por ejemplo haciendo doble clic tres veces sobre un control Label, estos quedan dispuestos automáticamente en el formulario. Nuestra primera tarea va a ser la de alinear estos controles, para ello seleccionamos los tres controles tal y como se puede ver en la Figura.
Selección de controles en Visual Studio 2005 (aplicable a Visual Studio 2010) Una vez seleccionados los tres controles en el menú de Visual Studio 2010 seleccionamos Formato > Alinear > Lados Izquierdos tal y como se puede ver en la Figura.
- 108 -
Programación de Dispositivos Móviles
Alineando los controles Una vez realizado podemos ver como estos controles ya están alineados en sus lados izquierdos como puede verse en la Figura 4. De igual forma podemos hacer uso del resto de opciones que se nos permiten.
Controles alineados Este menú de formato nos permite además una serie de opciones que nos facilitaran el trabajo con los controles, establecer los espaciados (Horizontal o Vertical) entre los controles, marcar el mismo tamaño de los controles etc.
Truco: Suponiendo que tengamos tres controles Label de diferentes tamaños y que queramos que todos tengan el mismo tamaño que el segundo de sus controles, seleccionaremos siempre como primer control, el control que queremos como base de tamaño para el resto de controles, y posteriormente con la tecla Ctrl seleccionaremos uno a uno el resto de controles.
Creación de controles en tiempo de ejecución Prácticamente todo en .Net son objetos, los controles también, así que para añadir un control en un formulario en tiempo de ejecución, deberemos hacerlo tratándolo como un objeto. El siguiente ejemplo de código crea un control Etiqueta en tiempo de ejecución
- 109 -
Programación de Dispositivos Móviles
private void CreateControl() { //Creamos el objeto control ( Etiqueta ) Label lblControl = new Label(); //Establecemos propiedades para el control lblControl.Name = "MiControlEtiqueta"; lblControl.Top = 70; lblControl.Left = 30; lblControl.Width = 30; lblControl.Height = 30; lblControl.Text = "Texto Etiqueta"; //Añadimos el control etiqueta al formulario this.Controls.Add(lblControl); } En la Figura podemos ver el resultado de la ejecución del código anterior:
Controles en Visual Studio 2005 (aplicable a Visual Studio 2010) En el ejemplo anterior hemos usado las propiedades Top y Left para establecer la posición donde queremos que se aloje el control dentro del formulario y las propiedades Width y Height para establecer el tamaño del mismo. Una forma más rápida de escribir el tip anterior es el uso de Bounds para establecer estos parámetros, en el siguiente código se puede ver como.
private void CreateControl() { //Creamos el objeto control ( Etiqueta ) Label lblControl = new Label(); //Establecemos propiedades para el control lblControl.Name = "MiControlEtiqueta"; lblControl.Bounds = new Rectangle(7, 14, 70, 30); lblControl.Text = "Texto Etiqueta"; //Añadimos el control etiqueta al formulario this.Controls.Add(lblControl); }
- 110 -
Programación de Dispositivos Móviles
Hasta ahora hemos visto como crear controles en tiempo de ejecución, sin embargo en muchas ocasiones es necesario además asignar un evento al control creado dinámicamente. Como ejemplo de esto último crearemos un control TextBox dinámicamente y manejaremos el evento 'TextChanged' que se produce cuando la propiedad Text del control cambia.
namespace DeviceApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); CreateControl(); } private void CreateControl() { //Creamos el objeto control ( Etiqueta ) TextBox txtControl = new TextBox(); //Establecemos propiedades para el control txtControl.Name = "MiControlCajaDeTexto"; txtControl.Bounds = new Rectangle(7, 14, 70, 30); txtControl.Text = "Texto Etiqueta"; //Agregamos una manejador para el evento txtControl.TextChanged += new EventHandler(txtControl_TextChanged); //Añadimos el control etiqueta al formulario this.Controls.Add(txtControl); } void txtControl_TextChanged(object sender, EventArgs e) { //Manejamos el vento ((TextBox)sender).Text = ((TextBox)sender).Text.ToUpper(); } }
}
Creación de nuevos controles En ocasiones la variedad de controles que nos proporciona Visual Studio 2005 no es suficiente, por lo que se tendrá que recurrir a la creación de controles propios. Esta no es una tarea complicada ya que el entorno nos proporciona lo necesario para realizarla, además de una gran facilidad para su posterior uso en nuestras aplicaciones. La mejor manera de ver todo esto es mediante un ejemplo, partimos de una aplicación nueva para dispositivos móviles en este caso para WM 5.0 Pocket PC ó superior en la que vamos a crear un nuevo control. En el 'Explorador de soluciones' seleccionamos el proyecto y hacemos clic con el botón d erecho y seleccionamos 'Agregar nuevo control de usuario' tal y como puede verse en la Figura.
- 111 -
Programación de Dispositivos Móviles
Agregar un nuevo control de usuario al proyecto Una vez hecho esto escribimos el nombre que queremos dar a nuestro nuevo control en la ventana que se nos muestra a continuación, ver Figura.
Figura 2. Estableciendo el nombre para el nuevo control de usuario Ya podemos empezar a trabajar en nuestro control, en este ejemplo haremos un simple control de validación de los datos de usuario y contraseña para insertarlo posteriormente en un formulario. Lo primero que se nos presenta es el área de trabajo, ver Figura dónde podemos trabajar, insertando controles y añadiendo funcionalidad a nuestro control de usuario.
- 112 -
Programación de Dispositivos Móviles
Área de trabajo de nuestro control de usuario Una vez insertado los controles y aplicada la funcionalidad que necesitemos ya tendremos listo el nuevo control para ser usado dentro de nuestras aplicaciones, como ser verá despues, de una forma rápida y sencilla. El resultado final de nuestro control de usuario y el código del mismo ser puede observar a continuación.
Control de usuario terminado
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Text; using System.Windows.Forms; namespace DeviceApplication1 { public partial class MyUserControl : UserControl { private bool acceso = true; /// /// Obtiene o establece un valor que indica si se va a p ermitir el acceso /// public bool Acceso { get { return acceso; } set { acceso = value; } }
}
public MyUserControl() { InitializeComponent(); }
}
- 113 -
Programación de Dispositivos Móviles
Como se puede observar en el código an terior hemos incluído una propiedad (Acceso) con la finalidad de mostrar como esta una vez que el control es insertado de un formulario de nuestra aplicación es accesible mediante la ventana de propiedades d e Visual Studio 2005 una vez seleccionado el control. En este momento ya tenemos creado nuestro nuevo control, ahora solamente queda ver como podemos hacer uso de el. Una vez terminada la realización del control el entorno de trabajo crea una nueva entrada en la caja de herramientas, ver Figura, donde podremos ver este nuevo control y arrastrarlo al formulario para su uso.
Control de usuario disponible en la caja de herramientas
Uso del control de usuario en un formulario Tal y como comentábamos en un párrafo anterior las propiedades del control son accesibles desde la ventana de propiedades del entorno, en nuestro caso hemos creado una propiedad 'Acceso' de tipo boleano que podremos modificar en esta ventana.
- 114 -
Programación de Dispositivos Móviles
Ventana de propiedades del control de usuario Hemos visto en este capítulo como en el caso de necesitar un nuevo control para una aplicación el entorno de trabajo nos facilita en enorme medida la realización del mismo, desde la creación del mismo, simplemente añadiendo un nuevo elemento de tipo UserControl a nuestro proyecto, hasta la utilización en la aplicación arrastrando este control desde la caja de herramientas. Además sin necesidad de escribir ninguna linea de código adicional podemos tener acceso a las propiedades de nuestro control desde la ventana de propiedades, lo que sin lugar a dudas nos va a proporcionar una gran ventaja a la hora de trabajar con el.
Trabajando con imágenes En este capítulo veremos las partes más generales en ele uso y generación de imágenes y gráficos en Compact Framework. Veremos un ejemplo sobre el uso de DirectX y nos a dentraremos más profundamente en el desarrollo de imágenes y gráficos con GDI+.
Dibujando lineas con GDI+ Lo primero que aprenderemos será como dibujar lineas simples con GDI+, empezemos por el principio, una linea en un plano es representada por dos puntos, estos puntos están definidos por sus coordenadas X e Y, el primer punto representará el origen de la linea y el segundo punto el fin de la misma. Una vez que esta idea la tenemos clara veremos con la representación de una linea mediante GDI+ es una tarea sencilla en Compact Framework.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing.Drawing2D; using System.Drawing; using System.Text; using System.Windows.Forms; namespace DeviceApplication1 { public partial class Form1 : Form { public Form1()
- 115 -
Programación de Dispositivos Móviles
{ InitializeComponent(); } protected override void OnPaint(PaintEventArgs e) { DrawLine(e); base.OnPaint(e); } private void DrawLine(PaintEventArgs pea) { Pen lapiz = new Pen(Color.Blue, 10f); pea.Graphics.DrawLine( lapiz, 0, (int)(this.ClientSize.Height / 2), this.ClientSize.Width, (int)(this.ClientSize.Height / 2)); } } } Para la realización de esta tarea lo primero que debemos hacer tal y como se puede o bservar en el ejemplo de código siguiente es crear el 'Lapiz' con el que deseamos pintar nuestra linea, en el especificamos el color asignado al mismo y el ancho que desemos para nuestra líenea. Una vez que hemos creado el 'Lapiz' con el que deseamos pintar la linea llamamos al método DrawLine especificando los puntos de origen y final de la misma. El resultado puede observarse en la Figura.
Ejemplo de una linea pintada con GDI+ Truco: GDI+ en .Net Compact Framework incluye muchas de las funcionalidades de .NET Framework pero no todas están soportadas.
Otra de las posibilidades que tenemos es la de dibujar un camino de lineas especificando una serie de puntos que formarán la ruta a seguir. El proceso es igual de sencillo que en el caso anterior, basta con especificar el 'Lapiz' con el que se desea pintar y el conjunto de puntos que formarán la ruta como puede verse en el ejemplo de código siguiente.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Text;
- 116 -
Programación de Dispositivos Móviles
using System.Windows.Forms; namespace DeviceApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } protected override void OnPaint(PaintEventArgs e) { DrawRute(e); base.OnPaint(e); } private void DrawRute(PaintEventArgs pea) { Pen Lapiz = new Pen(Color.Red, 5f); Point[] puntos = new Point[] { new Point(15, 40), new Point(120, 40), new Point(120, 40), new Point(15, 7) }; pea.Graphics.DrawLines(Lapiz,puntos); } }
}
El resultado del código anterior puede verse en la Figura.
Ejemplo de una camino de lineas
Representando polígonos con GDI+ La representanción de figuras como polígonos se asemaja a la representación de lineas usando GDI+, a continuación veremos algunos ejemplos de esto. Para realizar la reprensentación de un polígono nuestra primera tarea es definir el 'Lápiz' con el que deseamos trazar sus lineas y ponsteriormente, tal y como se puede ver en el ejemplo de código siguiente, especificar los puntos que representan al polígono.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Text; using System.Windows.Forms; namespace DeviceApplication1 {
- 117 -
Programación de Dispositivos Móviles
public partial class Form1 : Form { public Form1() { InitializeComponent(); } protected override void OnPaint(PaintEventArgs e) { DrawPolygon(e); base.OnPaint(e); } private void DrawPolygon(PaintEventArgs pea) { //Creamos el lápiz con el que deseamos pintar el polígono Pen lapiz = new Pen(Color.Blue); Point[] puntosPoligono = new Point[] { new Point(30, 30), new Point(60, 30), new Point(30, 60), new Point(60, 60) }; pea.Graphics.DrawPolygon(lapiz, puntosPoligono); } } } En la Figura podemos ver el resultado de la ejecución del código anterior:
Dibujando polígonos con GDI+ GDI+ nos ofrece además la posibilidad de rellenar estas figuras de una forma sencilla, como veremos la mayoria de los métodos que usemos en GDI+ tienen asociado su método Fill que nos permitirá rellenar la forma que estemos representando. En el ejemplo de código siguiente veremos un ejemplo de esto.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Text; using System.Windows.Forms; namespace DeviceApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); }
- 118 -
Programación de Dispositivos Móviles
protected override void OnPaint(PaintEventArgs e) { DrawPolygon(e); base.OnPaint(e); }
}
private void DrawPolygon(PaintEventArgs pea) { //Creamos el lápiz con el que deseamos pintar el polígono Pen lapiz = new Pen(Color.Blue); Point[] puntosPoligono = new Point[] { new Point(30, 30), new Point(60, 30), new Point(30, 60), new Point(60, 60) }; pea.Graphics.FillPolygon(new SolidBrush(Color.Yellow), puntosPoligono); }
} En la Figura podemos ver el resultado de la ejecución del código anterior.
Dibujando polígonos con GDI+ Si lo que queremos es rellenar un polígono pero realizar el borde de un color distinto llamaremos a los dos métodos FillPolygon y DrawPolygon.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Text; using System.Windows.Forms; namespace DeviceApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } protected override void OnPaint(PaintEventArgs e) { DrawPolygon(e); base.OnPaint(e);
- 119 -
Programación de Dispositivos Móviles
} private void DrawPolygon(PaintEventArgs pea) { //Creamos el lápiz con el que deseamos pintar el polígono Pen lapiz = new Pen(Color.Blue); Point[] puntosPoligono = new Point[] { new Point(30, 30), new Point(60, 30), new Point(30, 60), new Point(60, 60) }; pea.Graphics.FillPolygon(new SolidBrush(Color.Yellow), puntosPoligono); } } } El resultado del código anterior se puede observar en la Figura.
Dibujando polígonos con GDI+
Dibujando cadenas de texto con GDI+
El método DrawString nos va a permitir representar cadenas de forma gráfica, al igual que dibujar lineas o polígonos esta es una tarea que no implica demasiada dificultad. En los siguientes ejemplos veremos como realizar esto, la primera tarea será la de especificar la fuente y la 'brocha' de la cadena de texto a pintar.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Text; using System.Windows.Forms; namespace DeviceApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } protected override void OnPaint(PaintEventArgs e) { PintarTexto(e); base.OnPaint(e); } private void PintarTexto(PaintEventArgs pea)
- 120 -
Programación de Dispositivos Móviles
{ //Creamos la fuente Font fuente = new Font("Arial", 8f, FontStyle.Regular); //Creamos la brocha SolidBrush brocha = new SolidBrush(Color.Red);
}
SizeF sizeTexto = pea.Graphics.MeasureString("Hola Mundo", fuente); RectangleF marcoTexto = new RectangleF(30, 30, sizeTexto.Width, sizeTexto.Height); pea.Graphics.DrawString(" Hola Mundo", fuente, brocha, marcoTexto);
} }
En el ejemplo anterior hemos visto además como se hacía uso del método MeasureString con la finalidad de obtener el tamaño de dibujar el texto especificado con la fuente dada, esto nos facilita la creación del marco en el cual se va a realizar el dibujo. Hemos usado además como 'brocha' la clase SolidBrush, si nuestra intención fuera la de dibujar esa cadena de texto usando una imagen de fondo podríamos haber usado TextureBrush especificando dicha imagen de forma similar. El resultado del ejemplo anterior lo podemos ver en la Figura.
Dibujando cadenas de texto con GDI+ Nota: Si bien toda l a fuerza de GDI+ no está contemplada en Compact Framework esta nos aporta la funcionalidad necesaria para nuestros desarrollos en dispositivos móviles
DirectX-Direct3D en Compact Framework
Tal y como dijimos en la introducción de este módulo no entraremos en detalle acerca del desarrollo sobre DirectX en Compact Framework si bien, hemos de hacer notar que esta posibilidad está a nuestro alcance. El espacio de nombre Microsoft.WindowsMobile.DirectX nos expone esta funcionalidad sobre la cual podemos incorporar nuestros desarrollos en DirectX a dispositivos móviles de una forma sencilla. Como ejemplo veremos un sencillo uso de Direct3D.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms;
- 121 -
Programación de Dispositivos Móviles
using System.Runtime.InteropServices; using Microsoft.WindowsMobile.DirectX; using Microsoft.WindowsMobile.DirectX.Direct3D; namespace DirectX { public partial class Form1 : Form { Device device = null; VertexBuffer vertexBuffer = null; PresentParameters presentParams = new PresentParameters(); public Form1() { InitializeComponent(); this.Text = "Hola Mundo"; this.MinimizeBox = false; } public bool InitializeGraphics() { presentParams.Windowed = true; presentParams.SwapEffect = SwapEffect.Discard; device = new Device(0, DeviceType.Default, this, CreateFlags.None, presentParams); device.DeviceReset += new System.EventHandler( this.OnResetDevice); this.OnCreateDevice(device, null); this.OnResetDevice(device, null); return true; } void OnCreateDevice(object sender, EventArgs e) { Pool vertexBufferPool; Caps caps; Device dev = (Device)sender; // Obtenemos las capacidades del dispositivo caps = dev.DeviceCaps; if (caps.SurfaceCaps.SupportsVidVertexBuffer) vertexBufferPool = Pool.VideoMemory; else vertexBufferPool = Pool.SystemMemory; // Creamos el buffer vertexBuffer = new VertexBuffer( typeof (CustomVertex.PositionColored), 3, dev, 0, CustomVertex.PositionColored.Format, vertexBufferPool); vertexBuffer.Created += new System.EventHandler( this.OnCreateVertexBuffer); this.OnCreateVertexBuffer(vertexBuffer, null); } void OnResetDevice(object sender, EventArgs e) { Device dev = (Device)sender; dev.RenderState.CullMode = Cull.None; dev.RenderState.Lighting = false; } void OnCreateVertexBuffer(object sender, EventArgs e) { VertexBuffer vb = (VertexBuffer)sender; CustomVertex.PositionColored[] verts = (CustomVertex.PositionColored[])vb.Lock(0, 0); verts[0].X = -1.0f; verts[0].Y = -1.0f; verts[0].Z = 0.0f; verts[0].Color = System.Drawing.Color.DarkGoldenrod.ToArgb(); verts[1].X = 1.0f; verts[1].Y = -1.0f;
- 122 -
Programación de Dispositivos Móviles
verts[1].Z = 0.0f; verts[1].Color = System.Drawing.Color.MediumOrchid.ToArgb(); verts[2].X = 0.0f; verts[2].Y = 1.0f; verts[2].Z = 0.0f; verts[2].Color = System.Drawing.Color.Cornsilk.ToArgb(); vb.Unlock(); } private void Render() { if (device != null) { //Limpia el buffer con el color azul device.Clear(ClearFlags.Target, System.Drawing.Color.Blue, 1.0f, 0); //Comienza la escena device.BeginScene(); device.SetStreamSource(0, vertexBuffer, 0); device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); //Finalizar la escena device.EndScene(); device.Present(); } } protected override void OnPaint(PaintEventArgs e) { this.Render(); this.Invalidate(); } protected override void OnKeyPress(System.Windows.Forms.KeyPressEventArgs e) { // Esc if ((int)(byte)e.KeyChar == (int)System.Windows.Forms.Keys.Escape) this.Close(); } protected override void OnPaintBackground(PaintEventArgs e) { //NO SE LLAMA A LA CASE PADRE } } } El resultado del ejemplo de código anterior se puede observar en la Figura.
- 123 -
Programación de Dispositivos Móviles
Trabajando con DirectX en dispositivos móviles
ADO.NET - Acceso a Datos En este módulo aprenderemos a trabajar con datos y fuentes de datos en dispositivos móviles. Sin lugar a dudas este es el punto en el que los desarrolladores encuentran sus mayores dificultades, pero no se preocupe, en este tutorial se abordaran las cuestiones más importantes acerca del trabajo con fuentes de datos en dispositivos móviles.
¿Qué hay dentro de ADO.NET? Dentro de ADO.NET tenemos dos partes importantes. La primera de ellas es la que corresponde con el nombre de espacio System.Data y que constituye los objetos y clases globales de ADO.NET. La otra parte es la que corresponde con los objetos que permiten el acceso a datos a una determinada fuente de datos desde ADO.NET y que utilizan así mismo, las clases del nombre de espacio System.Data. Esta última parte, queda constituida por las clases y objetos de los diferentes proveedores de acceso a datos como se muestra en la Figura.
Visión general de ADO.NET Para resumir de alguna forma lo que estamos comentando, diremos que el trabajo de conexión con la base de datos, la ejecución de una instrucción SQL determinada, una vista, etc., la realiza el proveedor de acceso a datos. Recuperar esos datos para tratarlos, manipularlos o volcarlos a un determinado control o dispositivo, es acción de la capa superior que corresponde con el nombre de espacio System.Data. A continuación veremos todo esto con más detalle y comprenderemos de una forma más clara cada una de las partes que componen el modelo de trabajo con ADO.NET.
¿Qué nos permite realmente ADO.NET cuando trabajamos con XML? El entorno de Microsoft .NET Compact Framework nos proporciona el trabajo con estándares y con ello, la posibilidad de trabajar con diferentes tipos de aplicaciones, entornos, sistemas operativos y lenguajes sin necesidad de conocer lo que hay al otro lado de nuestra aplicación. XML es sin lugar a dudas, el lenguaje de etiquetas por excelencia, válido para llevar a cabo esta tarea sin tener un impacto relevante cuando trabajamos con diferentes soluciones en entornos dispares. Tanto la posibilidad de trabajar con Servicios Web XML como con documentos e información en XML, sobre todo al trabajar con fuentes de datos en ADO.NET, nos proporciona a los desarrolladores las posibilidades necesarias que nos permite hacer que la información con la que trabajamos, pueda ser tratada entre diferentes sistemas o entornos, sin que por ello nos preocupemos de lo que hay al otro lado.
SQL Mobile 2005 Mobile Edition (ó superior)
SQL Server 2005 Mobile Edition (ó superior) tiene varios alias, también llamado Sql Mobile y SQL CE 3.0 (ó SQL CE 3.5 para la última versión) es el sucesor de SQL CE, aunque cabe decir que no es simplemente una extensión de este, ya que supone una auténtica revolución en las bases de datos para dispositivos móviles. A continuación veremos alguno de los puntos a tener en cuenta en este novedoso motor de bases de datos.
- 124 -
Programación de Dispositivos Móviles
Sql Mobile se presenta como una plataforma común para distintos dispositivos como SmartPhone, PocketPC y TabletPC de tal forma que los desarrolladores podrán reutilizar el mismo código en distintas plataformas sin necesidad de realizar ningún cambio. Sql Mobile permite la sincronización de datos contra Sql Server bajo el protocolo 'HTTP', además estos datos a sincronizar se transmiten comprimidos lo que permite ahorrar tráfico de red, cuestión muy importante si estas conexiones son GPRS, la compresión de datos además aumenta la rapidez de los procesos de sincronización. Sql Mobile tiene soporte multi-usuario lo que permitira que en un mismo dispositivo varias aplicaciones se conecten a una base de datos al mismo tiempo. Otra de las grandes ventajas que nos proporciona SQL Sever 2005 Mobile Edition (ó superior) es el manejo de las bases de datos en un d ispositivo móvil mediante Sql Server Managment Studio. Desde el punto de vista del desarrollo, el que nos ocupa, el acceso a Sql Mobile está garantizado completamente mediante código manejado lo que nos proporcionará una fuerza extraordinaria. En este módulo aprenderemos a manejar Sql Mobile desde distintos dispositivos móviles, la creación de una base de datos o el manejo de la misma tanto en entorno desconectado como conectado serán los temas a tratar.
El concepto de DataBinding Antés de que veamos prácticamente los temas de acceso a datos es conveniente tener claro algunos conceptos, uno de ellos el de DataBinding que pasaremos a introducir brevemente a continuación y con el que nos encontraremos a menudo en este módulo.
El uso de DataBind El DataBind nos va a proporcionar de una forma rápida y sencilla una método para rellenar determinados controles con los datos contenidos en una base de datos. Muchos controles y clases proporcionan este método al que le asignaremos un conjunto de datos con los que el usuario podrá interactuar de forma directa. Si bien esto no es un concepto complicado es algo que veremos con mucha frecuencia y conviente tenerlo claro, durante este módulo veremos muchos ejemplos de uso y veremos como realmete facilita las tareas de desarrollo.
SQL Server 2005 Mobile Edition Una vez instalada la versión de Sql Server 2005 Mobile Edition (ó superior) podremos conectarnos a ella, bien para crear una nueva base de datos o acceder a alguna existente desde SQL Server Management Studio, para ello y tal como puede verse en la Figura seleccionamos en el tipo de servidor Sql Server Mobile especificando la ruta de la base de datos a la que nos queremos conectar o bien si lo que pretendemos es crear una nueva.
Conectando con Sql Mobile
- 125 -
Programación de Dispositivos Móviles
Conectando con Sql Mobile Tal y como hemos dicho una vez seleccionado el tipo de servidor al que nos queremos conectar podemos acceder a una base de datos existente almacenada en nuestro equipo o bien vía ActiveSync o crear una nueva base de datos. En este capítulo realizaremos paso a paso las dos opciones.
Creando una nueva base de datos para Sql Mobile Seleccionando la opción ,ver Figura anterior , se nos presentara un cuadro de diálogo en el que especificaremos la ruta y el nombre de la nueva base de datos a crear, y la posibilidad de establecer una contraseña de acceso a dicha base de datos.
Creando una nueva base de datos en Sql Server 2005 Mobile Edition o superior (aplicable a Visual Studio 2010) En este punto ya estamos en disposición de trabajar con nuestra base de datos Sql Mobile desde Sql Server Management Studio de tradicional, pudiendo modificar la estructura de la base de datos, la realización de consultas etc..
Trabajando con una base de datos de Sql Server 2005 Mobile Edition o superior (Aplicable a Visual Studio 2010)
- 126 -
Programación de Dispositivos Móviles
Accediendo a una base de datos en un dispositivo móvil Ya hemos aprendido a crear una nueva base de datos con Sql Server 2005 Mobile Edition o superior, ahora vamos a ver como conectarnos a una base de datos existente, para ello lo que haremos será ver como podemos acceder a una base de datos de un dispositivo móvil. El primer paso es la sincronización vía ActiveSync del dispositivo móvil.
Sincronización del dispositivo móvil Una vez sincronizado nuestro dispositivo móvil solamente nos queda seleccionar en el panel de conexión y buscar en el sistema de archivos del dispositivo móvil (el cual es accesible gracias a la sincronización del dispositivo) la base de datos a la que nos queremos conectar.
Seleccionando la base de datos en el dispositivo móvil
Manejando la base de datos en el dispositivo móvil
- 127 -
Programación de Dispositivos Móviles
En este punto ya sabemos como crear y acceder a una base de datos desde Sql Server 2005 Mobile Edition (ó superior) de una forma realmente sencilla. En siguientes capítulos aprenderemos a trabajar con la base de datos desde el dispositivo móvil.
Trabajando con SQL Server Mobile
En el anterior capítulo hemos visto como como crear o acceder a una base de datos Sql Mobile, sin embargo aún no hemos aprendido como usar estas desde nuestras aplicaciones. Por ello, vamos a ver como integrar en nuestras aplicaciones una base de datos y posteriormente veremos como acceder a ella. Empezaremos por crear un nuevo proyecto para dispositivos móviles en Visual Studio 2005, para el ejemplo crearemos un nuevo proyecto para SmartPhone WM 5.0. Este proyecto necesitamos que trabaje con una base de datos que llamaremos cursoCF y que constará de una tabla llamada Usuarios. Una vez creado el proyecto arrancamos Sql Server Management Studio seleccionamos Sql Mobile como tipo de servidor y marcamos la opción de crear nueva base de datos. Estableceremos la ruta de esta nueva base de datos dentro d el directorio de nuestro proyecto.
Nueva base de datos de Sql Server 2005 Mobile Edition o superior (aplicable a Visual Studio 2010) A continuación creamos la tabla 'Usuarios' para nuestra base de datos tal y como podemos observar en la Figura.
Creación de la tabla Usuarios
- 128 -
Programación de Dispositivos Móviles
Ya hemos creado la base de datos y la tabla con la que vamos a trabajar y la hemos situado en el directorio del proyecto para dispositivos móviles por lo que estamos en disposición de empezar a trabajar con ella. En el explorador de soluciones de Visual Studio 2005, marcando la opción de mostrar todos los archivos (' Show All Files') podremos ver el archivo 'cursoCF.sdf' correspondiente a nuestra base de datos
Visualización de la base de datos cursoCF Para trabajar con ella únicamente nos queda incluirla en el proyecto, para lo cual situaremos el cursor encima del archivo y haremos clic con el botón derecho y seleccionaremos en el menún contextual 'Incluir en el Proyecto'.
Incluyendo la base de datos en el proyecto A continuación se nos presentará un pequeño asistente que nos permite seleccionar un conjunto de tablas,vistas para los que deseamos crear entidades, más adelante trataremos este tema por lo que para empezar seleccionaremos 'Cancelar'. Una vez hecho esto ya tenemos incluída en nuestra solución la base de datos y las referencias necesarias para trabajar con ella tal y como podemos observar en la Figura.
- 129 -
Programación de Dispositivos Móviles
Asistente de creación de entidades
Explorador de soluciones con la base de datos incluída en el proyecto Ahora solamente nos queda un último paso, si bien ya tenemos incluida la base de datos en el proyecto si nosotros hicieramos uso de ella y probaramos nuestra aplicación en el emulador comprobaríamos como esta realmente no se incluyó, para resolver esto una vez incluída tendremos que especificar que este archivo se copie al directorio de salida. Para ello en la ventana de propiedades, una vez seleccionado cursoCF.sdf establecemos la propiedad 'Copy To Outout Directory' a 'Copy if Newer'.
Establecer lar propiedad Copy To Output Directory
- 130 -
Programación de Dispositivos Móviles
Con este último paso ya hemos realizado todo lo necesario para trabajar con esta base de datos de Sql Server 2005 Mobile Edition (ó superior). En los siguientes capítulos veremos como trabajar con esta base de datos tanto de forma conectada como de forma desconectada. Además veremos que son y como trabajar con DataSet tipados.
Acceso conectado a la base de datos
En las lecciones anteriores hemos visto como trabajar con Sql Server 2005 Mobile Edition (ó superior) para crear nuestras bases de datos para dispositivos móviles, acceder a ellas y como incluirlas dentro de un proyecto para dispositivos móviles. Aún así no hemos visto nada acerca del manejo de estas desde el dispositivo. En los siguientes capítulos aprenderemos a manejar nuestras bases de datos de forma conectada, veremos las ventajas que esto nos ofrece y como resolver los posibles problemas con los que nos podamos encontrar.
Conociendo el objeto DataReader
El objeto DataReader nos permitirá establecer una conexión con una fuente de datos y permanecer conectados a ella. Este objeto posee una serie de peculiaridades que es muy importante conocer para tener claro como funciona realmente.
DataReader es de solo lectura Lo que hemos dicho anteriormente requiere que esta conexión se establezca en un modo de solo lectura, es decir, únicamente podemos leer datos de la fuente de datos a la que estemos conectados pero no modificar o insertar nuevos datos.
DataReader se maneja en una sola dirección El objeto DataReader solamente permite que nos desplacemos por los datos en una única dirección,sin vuelta atrás. Además solamente podemos usar el objeto DataReader por conexiones establecidas en una sentencia SQL por ejemplo, pero no podemos variar estas.
DataReader es rápido Debido a su naturaleza y características este objeto es rápido a la hora de trabajar con datos. Otra de sus ventajas es que consume pocos recursos y memoria.
Analizando el flujo de trabajo del objeto DataReader Para trabajar con el objeto DataReader, se utilizarán los siguientes objetos. • • •
Connection Command DataReader
Flujo de conectividad
Un primer contacto con el objeto DataReader
A continuación veremos un ejemplo sencillo acerca del manejo del objeto DataReader Un ejemplo simple para entenderlo mejor
En el siguiente ejemplo vamos a ver como usar el objeto DataReader para conectarnos a la base de datos creada en el módulo anterior y que teníamos incluída en el proyecto. Nuestra primera tarea será la de definir nuestro objeto Connection, para ello debermos definir primero la cadena de conexión a la base de datos que queremos conectarnos. La cadena de conexión para Sql Mobile se define de la siguiente forma:
"DataSource=RutaBaseDatos" dónde RutaBaseDatos representa la ruta a la base de datos. Lógicamente, nosotros debemos de disponer de una método para averiguar la ruta de esta y para ello nos vamos a servir del especio de nombres System.IO, puesto que la base de datos la hemos incluído en el
- 131 -
Programación de Dispositivos Móviles
proyecto y esta se copiará al dispositivo móvil al lado de nuestro ejecutable, bastaría con conocer el directorio dónde esta la aplicación para poder conocer la ruta a nuestra base de datos. Esto lo podemos realizar de la siguiente forma.
//Creamos la cadena de conexion string pathDir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); string pathDB = System.IO.Path.Combine(pathDir, "cursoCF.sdf"); string connectionString = string.Empty; connectionString = string.Format(@"DataSource={0}",pathDB);
Mediante System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase obtenemos la ruta de la aplicacion que se esta ejecutando, de esta forma si esta ruta se la pasamos a GetDirectoryName obtendremos el directorio del ejecutable de la aplicación que era realmente lo que estábamos buscando. Una vez que tenemos nuestra cadena de conexión ya podemos crear nuestro objeto 'Connection'.
//Creamos el objeto conexion SqlCeConnection conn = new SqlCeConnection(connectionString);
La idea de este ejemplo es la de recuperar los datos de la tabla 'usuarios' e incluirlos en un objeto ListView usando el objeto DataReader para ello, el diseño de nuestra aplicación se puede ver en la Figura.
Diseño de la aplicación Ahora únicamente nos queda crear el objeto 'Command' que contendrá la consulta a realizar y el objeto DataReader mediante el cual leeremos los datos, recuerde que es de solo lectura y que únicamente se puede recorrer en una dirección.
//Creamos el objeto comando SqlCeCommand cmd = conn.CreateCommand(); cmd.CommandText = " select * from usuarios"; //Abrimos la conexion
- 132 -
Programación de Dispositivos Móviles
if (conn.State == System.Data.ConnectionState.Closed) conn.Open(); //Creamos el objeto DataReader SqlCeDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); Ya que vamos a trabajar de forma conectada, lógicamente será necesario 'abrir' la conexión antes de realizar ninguna tarea. Como puede observar mediante el objeto comando hemos creado un nuevo objeto DataReader ( SqlCeDataReader ) el cual nos servirá para llenar el control con los datos que deseamos obtener. En el siguiente ejemplo de código veremos como recuperar los datos y rellenar el control.
ListViewItem listViewItem; while (dr.Read()) { listViewItem = new ListViewItem(); listViewItem.Text = dr["usuario"].ToString(); listViewItem.SubItems.Add(dr["password"].ToString()); //Añadimos el item al ListView listView1.Items.Add(listViewItem); } Como puede observarse mediante el objeto dr accedemos a cada una de las columnas de la fila en la que esta situado el objeto DataReader, una vez leídas en la siguiente iteración del bucle el objeto DataReader se posiciona en la siguiente fila si esta existe y realizamos la misma tarea. El código completo y el resultado del ejemplo se puede observar a continuación.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Data; using System.Data.SqlServerCe; namespace SqlMobileExample1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void menuItemExit_Click(object sender, EventArgs e) { Application.Exit(); } private void menuItemLoadData_Click(object sender, EventArgs e) { //Creamos la cadena de conexion string pathDir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); string pathDB = System.IO.Path.Combine(pathDir, "cursoCF.sdf"); string connectionString = string.Empty; connectionString = string.Format(@"DataSource={0}",pathDB);
- 133 -
Programación de Dispositivos Móviles
//Creamos el objeto conexion SqlCeConnection conn = new SqlCeConnection(connectionString); //Creamos el objeto comando SqlCeCommand cmd = conn.CreateCommand(); cmd.CommandText = " select * from usuarios"; //Abrimos la conexion if (conn.State == System.Data.ConnectionState.Closed) conn.Open(); //Creamos el objeto DataReader SqlCeDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection); ListViewItem listViewItem; while (dr.Read()) { listViewItem = new ListViewItem(); listViewItem.Text = dr["usuario"].ToString(); listViewItem.SubItems.Add(dr["password"].ToString());
}
//Añadimos el item al ListView listView1.Items.Add(listViewItem);
} }
}
Imagen de la aplicación Nota: Recuerde que al incluir la base de datos en el proyecto se agregaron automáticamente las referencias necesarias System.Data y System.Data.SqlServerCE, en el caso de que estás no estén presentes debe agregarlas.
- 134 -
Programación de Dispositivos Móviles
Conociendo el objeto SqlCeResultSet
En los capitulos anteriores hemos visto y trabajado con el objeto DataReader ,el cual nos aporta un gran rendimiento en nuestras consultas a una base de datos, sin embargo, este objeto poseía varios inconvenientes, era un objeto de solo-lectura y además únicamente permitía moverse en una dirección, hacia adelante. Si lo que queremos es poseer una conexión a la base de datos y poder modificar o insertar elementos, además de podernos mover líbremente lo que usaremos sera el objeto SqlCeResultSet del cual hablaremos detalladamente en este y en el próximo capítulo.
SqlCeResultSet se podría definir de la siguiente manera, " SqlCeResultSet representa un cursor actualizable, en el que nos podemos mover en ambas direcciones y que además es enlazable " . Este objeto nos va a proporcionar una gran ayuda a la hora de trabajar con nuestra base de datos ya que no necesita de una gran cantidad de código ni tampoco la duplicación de datos en el dispositivo que tendríamos si usaramos un DataSet (en los capítulos siguientes hablaremos del objeto DataSet y del acceso desconectado a bases de datos). A lo largo del siguiente capítulo veremos como como trabajar con este objeto tanto para obtener una serie de datos de una cosulta, insertar nuevos registros mediante este objeto y como enlazar diversos controles mediante SqlCeResultSetView.
Un primer contacto con el objeto SqlCeResultSet
Al igual que hicimos con el objeto DataReader la mejor manera de aprender es con la práctica, por lo tanto, a lo largo de este capítulo veremos mediante sencillos ejemplos como manejar este objeto con e l fin de sacarle el mayor provecho posible. Un ejemplo Sencillo para entenderlo mejor
Para nuestro ejemplo seguiremos trabajando con la base de datos creada al comienzo de este módulo. Nuestra primera tarea será la de definir nuestro objeto Connection, para ello debermos definir primero la cadena de conexión a la base de datos que queremos conectarnos. La cadena de conexión para Sql Mobile se define de la siguiente forma:
"DataSource=RutaBaseDatos" dónde RutaBaseDatos representa la ruta a la base de datos. Lógicamente, nosotros debemos de disponer de una método para averiguar la ruta de esta y para ello nos vamos a servir del especio de nombres System.IO, puesto que la base de datos la hemos incluído en el proyecto y esta se copiará al dispositivo móvil al lado de nuestro ejecutable, bastaría con conocer el directorio dónde esta la aplicación para poder conocer la ruta a nuestra base de datos. Esto lo podemos realizar de la siguiente forma.
//Creamos la cadena de conexion string pathDir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); string pathDB = System.IO.Path.Combine(pathDir, "cursoCF.sdf"); string connectionString = string.Empty; connectionString = string.Format(@"DataSource={0}",pathDB);
Mediante System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase obtenemos la ruta de la aplicacion que se esta ejecutando, de esta forma si esta ruta se la pasamos a GetDirectoryName obtendremos el directorio del ejecutable de la aplicación que era realmente lo que estábamos buscando. Una vez que tenemos nuestra cadena de conexión ya podemos crear nuestro objeto 'Connection'.
//Creamos el objeto conexion SqlCeConnection conn = new SqlCeConnection(connectionString);
- 135 -
Programación de Dispositivos Móviles
En el siguiente ejemplo veremos como realmente es posible navegar en ambas direcciones mediante el objeto SqlCeResultSet y además veremos como actualizar e insertar nuevos registros. El aspecto de nuestra aplicación lo podemos ver en la Figura.
Diseño de la aplicación En las opciones del menú introduciremos el código necesario para navegar por los distintos registros usando para ello el objeto SqlCeResultSet.
Menú de navegación En este punto ya sabemos que queremos que haga nuestra aplicación pero aún no hemos visto nada acercar del objeto que nos ocupa, SqlCeResultSet, que es lo que haremos y explicaremos a continuación. En el siguiente código veremos como obtener un objeto SqlCeResultSet como resultado de un comando contra la base de datos.
private void LoadData() { //Creamos la cadena de conexion string pathDir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); string pathDB = System.IO.Path.Combine(pathDir, "cursoCF.sdf"); string connectionString = string.Empty; connectionString = string.Format(@"DataSource={0}", pathDB); //Creamos el objeto conexion conn = new SqlCeConnection(connectionString); //Abrimos la conexion if ( conn.State == ConnectionState.Closed ) conn.Open(); //Ejecutamos un comando SqlCeCommand cmd = conn.CreateCommand(); cmd.CommandType = CommandType.Text; cmd.CommandText = "SELECT usuario,password FROM usuarios"; try { //Obtenemos el objeto SqlCeResultSet
- 136 -
Programación de Dispositivos Móviles
this.resultSet = cmd.ExecuteResultSet(ResultSetOptions.Scrollable); } catch (SqlCeException __ERR) { //Manejar el error } } El método ExecuteResultSet del objeto comando ( SqlCeCommand ) nos permite obtener nuestro objeto de trabajo, además nos permite especificar opciones sobre su comportamiento mediante el parámetro ResultSetOptions pasado al método. Estas opciones son: • • • • •
Insensitive None Updatable Scrollable Sensitive
La opción Scrollable nos va a permitir navegar en ambos sentidos por la fuente de datos, que es en este caso lo que queremos. Por otro lado las opciones Sensitive e Insensitive establecen si el objeto SqlCeResultSet detecta cambios o no hechos en la fuente de datos. Nuestra aplicación además deseamos que permita actualizar datos por lo que también deberemos marcar la opción Updatable, para ello basta con modificar el parámetro del método ExecuteResultSet y establecerlo de la siguiente manera.
//Obtenemos el objeto SqlCeResultSet this.resultSet = cmd.ExecuteResultSet(ResultSetOptions.Scrollable|ResultSetOptions.Updatable); En estos momentos ya sabemos como crear nuestro objeto SqlCeResultSet, recordar aquí que estamos trabajando de forma conectada, por lo que mientras queramos hacer uso de este objeto la conexión siempre debe estar abierta. A continuación nuestra tarea es la implementación de las opciones del menú, empezando por los elementos Siguiente y Anterior . En el siguiente código veremos como navegar con el objeto SqlCeResultSet.
private void menuItemSiguiente_Click(object sender, EventArgs e) { //Mostrar el siguiente elemento if (this.resultSet != null) { if (this.resultSet.Read()) { txtUsuario.Text = this.resultSet["usuario"].ToString(); txtPassword.Text = this.resultSet["password"].ToString(); } } } private void menuItemAnterior_Click(object sender, EventArgs e) { //Mostrar el elemento anterior if (this.resultSet != null) { if (this.resultSet.ReadPrevious()) { txtUsuario.Text = this.resultSet["usuario"].ToString(); txtPassword.Text = this.resultSet["password"].ToString(); } } }
- 137 -
Programación de Dispositivos Móviles
Cómo podemos observar la tarea es sencilla, es suficiente con usar los métodos que el objeto SqlCeResultSet nos proporciana para ello, estos métodos de navegación los vemos con más detalle en la siguiente lista. • • • • • •
Read: Posiciona el cursor en el siguiente registro si este existe ReadFirst: Posiciona el cursor en el primer elemento ReadLast: Posiciona el cursor en el último elemento ReadPrevious: Posiciona el cursor en el elemento anterior si existe ReadRelative: Posiciona el cursor un número de posiciones especificas a partir de la posición actual ReadAbsolute: Posiciona el cursor en un posición especifica
Hasta este punto hemos visto como obtener un objeto SqlCeResultSet y como efectivamente podemos navegar en ambas direcciones de forma conectada, vamos por lo tanto a ver como podemos actualizar un registro o como añadir nuevos registros. En el siguiente ejemplo vemos como actualizar uno de los registros, bastará con modificar los datos del registro e invocar al método Update para que estos cambios se vean reflejados automáticamente en la base de datos.
private void menuItemActualizar_Click(object sender, EventArgs e) { if (this.resultSet != null) { if (this.resultSet.Updatable) { this.resultSet.SetString(0, txtUsuario.Text); this.resultSet.SetString(1, txtPassword.Text); this.resultSet.Update(); } } } El objeto SqlCeUpdatableRecord es el que nos va a permitir crear un nuevo registro e insertarlo a la base de datos una vez establecido sus valores, a continuación vemos como:
private void menuItemNew_Click(object sender, EventArgs e) { SqlCeUpdatableRecord NuevoResgistro = this.resultSet.CreateRecord(); NuevoResgistro.SetString(0, txtUsuario.Text); NuevoResgistro.SetString(1, txtPassword.Text); this.resultSet.Insert(NuevoResgistro); } En la Figura 3 podemos ver la aplicación funcionando.
- 138 -
Programación de Dispositivos Móviles
La aplicación de ejemplo funcionando
DataBinding con el objeto S qlCeResultSet Como explicamos en la introducción del módulo, DataBinding es el proceso de enlace entre una fuente de datos y los controles. El objeto SqlCeResultSet nos permite enlazar una vista del mismo a los controles de nuestras aplicaciones sin necesitar muchas lineas de código para realizar dicha tarea, esto se consigue gracias al objeto ResultSetView que nos va a proporcionar una interfaz entre el objeto SqlCeResultSet y los controles. ResultSetView nos va a permitir obtener una vista de los registros especificando bien el nombre de las columnas que queremos que se enlacen o bien la posición que estas ocupan. En el siguente ejemplo vemos como enlazar un ResultSetView con un control DataGrid
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Data; using System.Data.SqlServerCe; namespace SqlMobileExample1 { public partial class Form1 : Form { private SqlCeResultSet resultSet; private SqlCeConnection conn; public Form1() { InitializeComponent(); LoadData(); } private void menuItemExit_Click(object sender, EventArgs e) { Application.Exit(); } private void LoadData() { //Creamos la cadena de conexion string pathDir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); string pathDB = System.IO.Path.Combine(pathDir, "cursoCF.sdf"); string connectionString = string.Empty;
- 139 -
Programación de Dispositivos Móviles
connectionString = string.Format(@"DataSource={0}", pathDB); //Creamos el objeto conexion conn = new SqlCeConnection(connectionString); //Abrimos la conexion if ( conn.State == ConnectionState.Closed ) conn.Open(); //Ejecutamos un comando SqlCeCommand cmd = conn.CreateCommand(); cmd.CommandType = CommandType.Text; cmd.CommandText = "SELECT usuario,password FROM usuarios"; try { //Obtenemos el objeto SqlCeResultSet this.resultSet = cmd.ExecuteResultSet(ResultSetOptions.Scrollable | ResultSetOptions.Updatable); } catch (SqlCeException) { //Manejar el error } } private void menuItemBindData_Click(object sender, EventArgs e) { ResultSetView rview = this.resultSet.ResultSetView; rview.Ordinals = new int[] { 0, 1 }; this.dataGrid1.DataSource = rview; } } } El resultado puede verse a continuación En la Figura podemos ver la aplicación funcionando.
Enlazando el objeto SqlCeResultSet
Acceso desconectado.
En anteriores capítulos hemos visto como crear nuestras base de datos con Sql Mobile Edition y posteriormente trabajar con ellas de forma conectada. Al igual que en .Net Framework también existe la posibilidad de trabajar d e forma desconectada de las mismas haciendo uso del objeto DataSet y de los componentes de acceso a datos que nos facilitarán esta tarea como los objetos DataAdapter .
- 140 -
Programación de Dispositivos Móviles
Esquema general de la estructura desconectada de acceso a datos
La clase DataSet está pensada y diseñada para trabajar con fuentes de datos desconectadas. Indudablemente, en este punto, debemos tener clara la estructura general de como funciona el acceso desconectado con fuentes de datos. En la siguiente figura podemos ver una imagen del esquema que nos ocupa.
Esquema general de la estrucura de acceso desconectado
Connection,DataAdapter y DataSet Tal y como podemos observar en la figura para comunicarnos con una fuente de datos siempre necesitaremos establecer una conexión con dicha fuente, independientemente de si esta va a perdurar o no con el tiempo. El objeto Connection es el que nos permite realizar esta tarea. El objeto DataSet por otro lado nos va a permitir recoger la información de la fuente de datos y almacenarla en memoria para posteriormente trabajar con estos datos en nuestras aplicaciones. En medio de estos dos objetos nos encontraremos con el objeto DataAdapter el cual es el nexo de unión entre ellos.
Conociendo el objeto DataAdapter Como explicamos en el anterior capítulo, el objeto DataAdatper es el nexo de unión entre un objeto conexión y un DataSet. Este objeto no solamente nos va a proporcionar la ejecución de sentencias SQL para obtener datos sino que también nos va a permitir alterar el contenido de una base de datos y/o de sus tablas. Un ejemplo práctico para entenderlo mejor
Tal y como hemos hecho en todo el tutorial, la mejor forma de aprender es realizando ejemplos prácticos. En este caso veremos como conectarnos a una base de datos Sql Mobile y ver como trabajar en un ambiente desconectado.
Utilizando las clases de .Net Compact Framework En este ejemplo veremos como trabajar de forma desconectada utilizando las clase de .Net Compact Framework para conectarnos a una base de datos de Sql Mobile, una vez creada la base de datos y agregada al proyecto de dispositivo móvil, tal y como hemos realizado en anteriores capítulos lo que haremos será crear un DataSet y 'poblarlo' con los datos de una tabla de la base de datos. Una vez hecho esto, podremos trabajar con ellos directamente sin necesidad de estar conectados a la base de datos. La primera tarea será la de in cluir las referencias necesarias, estas son System.Data y System.Data.SqlServerCE tal y como podemos ver en la figura siguiente.
- 141 -
Programación de Dispositivos Móviles
Agregando las referencias necesarios Una vez agregadas las referencias necesarias a nuestro proyecto iremos a la sección de código y escribiremos las siguientes instrucciones.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Data; using System.Data.SqlServerCe; using System.IO; namespace SqlDataAdapter { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { string _connString = string.Empty; _connString = @"Data Source=\Program Files\SqlDataAdapter\MyDatabase#1.sdf"; //Creamos el objeto conexion SqlCeConnection conn = new SqlCeConnection(_connString); //Creamos el objeto DataAdatper SqlCeDataAdapter adapter = new SqlCeDataAdapter("SELECT id,nombre,apellidos FROM usuarios", conn); //Creamos el objeto DataSet DataSet ds = new DataSet("ds"); //Poblamos el objeto DataSet usando el adaptador try { adapter.Fill(ds, "usuarios"); //Enlazamos los datos del dataSet a un objeto ListBox listBox1.DataSource = ds.Tables["usuarios"];
- 142 -
Programación de Dispositivos Móviles
listBox1.DisplayMember = "nombre"; listBox1.ValueMember = "id"; adapter = null; } catch (SqlCeException __ERR) { MessageBox.Show(__ERR.Message.ToString()); } } } } Tal y como se puede observar en el código anterior la tarea es realmente sencilla, es suficiente con crear una nueva conexión y un nuevo objeto DataSet que contendrá los datos que recueremos de la base de datos, tal y como dijimos el objeto DataAdatper es el nexo de unión y nos permitira especificar la sentencia SQL y la conexión con la que queremos trabajar. Una vez hecho esto, mediante su método Fill podremos entonces poblar el DataSet. El resultado del ejemplo anterior puede verse en la Figura.
Ejemplo de uso de DataAdapter
Utilizando componentes de .NET Compact Framework De igual forma que en el ejemplo anterior es posible realizar esta tarea de una forma más rápida utilizando los componentes de SqlServerCE, lo cual nos permitirá realizar la misma tarea pero de forma visual. Para ello únicamente tendríamos que incorporar estos componentes a nuestra caja de herramientas. Veremos como realizar esta tarea a continuación. Una vez seleccionada la 'pestaña' de la caja de herramientas en la que deseamis incorporar los controls, haremos clic con e boton derecho en la misma y seleccionaremos 'Elegir elementos',ver Figura.
Agregando elementos a la caja de herramientas
- 143 -
Programación de Dispositivos Móviles
Una vez hecho esto seleccionaremos los componentes que deseemos agregar, en nuestro caso los componentes de SqlServerCE
Seleccionando los componentes a agregar
Insertando datos usando DataAdapter
Hasta ahora únicamente hemos visto como usar el objeto DataAdapter para seleccionar un conjunto de datos de la base de datos y mostrarlos en pantalla, no sabemos aún nada de como insertar,eliminar o actualizar dichos datos. En este y los p róximos capítulos veremos como realizar estas tareas. Como sabemos el objecto DataAdapter es el nexo de unión entre una fuente de datos, una base de datos, y un objeto DataSet. Los datos de una tabla o de varias tablas del DataSet pueden ser modificados, eliminados o bien podemos insertar nuevos datos, por lo tanto también tenemos métodos para 'comprometer' estos cambios en el DataSet a la base de datos por medio del adaptador. En este primer capítulo veremos como realizar la inserción de datos en un DataSet y como volcar estos datos a la base de datos. Un ejemplo para entenderlo mejor
En el siguiente ejemplo veremos como realizar esta tarea, para ello usaremos la base de datos del capítulo anterior. Nuestra primera tarea será la de crear los objetos con los que vamos a trabajar, estos son, el objeto conexión, el objeto adaptador, el objeto DataSet y el comando que nos va a permitir insertar nuevos elementos.
SqlCeConnection conn; SqlCeDataAdapter adapter; SqlCeCommand insertCommand; DataSet ds; Una vez creados vamos a inicializarlos convenientemente mediante un método que hemos llamado InitAdapter.
private void InitAdapter() { string _connString = string.Empty; _connString = @"Data Source=\Program Files\SqlDataAdapter\MyDatabase#1.sdf"; //Creamos el objeto conexion this.conn = new SqlCeConnection(_connString);
- 144 -
Programación de Dispositivos Móviles
//Creamos el objeto DataAdatper this.adapter = new SqlCeDataAdapter("SELECT id,nombre,apellidos FROM usuarios",this.conn); //Creamos el objeto DataSet this.ds = new DataSet("ds"); //Creamos el objecto comando para insertar nuevos datos this.insertCommand = new SqlCeCommand("INSERT INTO usuarios(nombre,apellidos) VALUES(@nombre,@apellidos)", this.conn); //Creamos los parametros del comando SqlCeParameter paramNombre = new SqlCeParameter("@nombre", SqlDbType.NVarChar,100,"nombre"); SqlCeParameter paramApellidos = new SqlCeParameter("@apellidos", SqlDbType.NVarChar,100,"apellidos"); //Agregamos los parametros al comando this.insertCommand.Parameters.Add(paramNombre); this.insertCommand.Parameters.Add(paramApellidos); //Agregamos el comando de insercion al adaptador this.adapter.InsertCommand = this.insertCommand; } Como se puede observar en el capítulo anterior una vez inicializada la conexión y el objeto adaptador hemos construído nuestro objeto SqlCeCommand que nos permitirá manejar las inserciones, una vez iniciado el comando Sql se han agregado al mismo los parámetros necesarios y se ha establecido este comando en el adaptador como el comando de inserción a utilizar en la propiedad InsertCommand. Para comprobar que todo funciona correctamente, añadimos un nuevo registro al DataSet y llamamos al método Update del adaptador el cual comprometerá los cambios en la base de datos.
private void menuItemInsertar_Click(object sender, EventArgs e) { //Creamos una nueva fila para la tabla y la agregamos al dataSet DataRow row = this.ds.Tables["usuarios"].NewRow(); row["nombre"] = "nuevo nombre"; row["apellidos"] = "nuevos apellidos"; this.ds.Tables[0].Rows.Add(row); //Hacemos que los cambios se comprometan en la base de datos this.adapter.Update(this.ds); } El código completo del ejemplo puede verse a continuación
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Data; using System.Data.SqlServerCe; using System.IO; namespace SqlDataAdapter { public partial class Form1 : Form
- 145 -
Programación de Dispositivos Móviles
{ SqlCeConnection conn; SqlCeDataAdapter adapter; SqlCeCommand insertCommand; DataSet ds; public Form1() { InitializeComponent(); InitAdapter(); } private void InitAdapter() { string _connString = string.Empty; _connString = @"Data Source=\Program Files\SqlDataAdapter\MyDatabase#1.sdf"; //Creamos el objeto conexion this.conn = new SqlCeConnection(_connString); //Creamos el objeto DataAdatper this.adapter = new SqlCeDataAdapter("SELECT id,nombre,apellidos FROM usuarios",this.conn); //Creamos el objeto DataSet this.ds = new DataSet("ds"); //Creamos el objecto comando para insertar nuevos datos this.insertCommand = new SqlCeCommand("INSERT INTO usuarios(nombre,apellidos) VALUES(@nombre,@apellidos)", this.conn); //Creamos los parametros del comando SqlCeParameter paramNombre = new SqlCeParameter("@nombre", SqlDbType.NVarChar,100,"nombre"); SqlCeParameter paramApellidos = new SqlCeParameter("@apellidos", SqlDbType.NVarChar,100,"apellidos"); //Agregamos los parametros al comando this.insertCommand.Parameters.Add(paramNombre); this.insertCommand.Parameters.Add(paramApellidos); //Agregamos el comando de insercion al a daptador this.adapter.InsertCommand = this.insertCommand; } private void Form1_Load(object sender, EventArgs e) { try { adapter.Fill(ds, "usuarios"); //Enlazamos los datos del dataSet a un objeto ListBox listBox1.DataSource = ds.Tables["usuarios"]; listBox1.DisplayMember = "nombre"; listBox1.ValueMember = "id"; } catch (SqlCeException __ERR) { MessageBox.Show(__ERR.Message.ToString()); } } private void menuItemInsertar_Click(object sender, EventArgs e) { //Creamos una nueva fila para la tabla y la agregamos al dataSet DataRow row = this.ds.Tables["usuarios"].NewRow(); row["nombre"] = "nuevo nombre"; row["apellidos"] = "nuevos apellidos"; this.ds.Tables["usuarios"].Rows.Add(row);
- 146 -
Programación de Dispositivos Móviles
//Hacemos que los cambios se comprometan en la base de datos this.adapter.Update(this.ds.Tables["usuarios"]); }
}
} A continuación vemos en la primera figura nuestra aplicación inicialmente y en la segunda Figura una vez insertado el elemeto y comprometido el DataSet en la base de datos.
Ejemplo antes de insertar un elemento por medio del objeto DataAdatper
Ejemplo después de insertar un elemento por medio del objeto DataAdatper
- 147 -
Programación de Dispositivos Móviles
Actualizando datos con el objeto DataAdapter Siguiendo el ejemplo anterior, en el que hemos visto como construir y usar un objeto DataAdapter para insertar datos, veremos ahora como actualizar datos. El proceso es exáctamente igual que el anterior, únicamente consiste en crear el comando de actualización y asignarlo en la propiedad UpdateCommand del adaptador, este comando será llamado por el adaptador cuando necesite actualizar alguno de los registros del DataSet. En el siguiente ejemplo veremos como construir el comando de actualización, esta tarea la haremos en el método anterior de inicialización del DataAdapter ( InitAdapter, véase el capítulo anterior ).
private void InitAdapter() { string _connString = string.Empty; _connString = @"Data Source=\Program Files\SqlDataAdapter\MyDatabase#1.sdf"; //Creamos el objeto conexion this.conn = new SqlCeConnection(_connString); //Creamos el objeto DataAdatper this.adapter = new SqlCeDataAdapter("SELECT id,nombre,apellidos FROM usuarios",this.conn); //Creamos el objeto DataSet this.ds = new DataSet("ds"); //Creamos el objecto comando para insertar nuevos datos this.insertCommand = new SqlCeCommand("INSERT INTO usuarios(nombre,apellidos) VALUES(@nombre,@apellidos)", this.conn); //Creamos los parametros del comando SqlCeParameter paramNombre = new SqlCeParameter("@nombre", SqlDbType.NVarChar,100,"nombre"); SqlCeParameter paramApellidos = new SqlCeParameter("@apellidos", SqlDbType.NVarChar,100,"apellidos"); //Agregamos los parametros al comando this.insertCommand.Parameters.Add(paramNombre); this.insertCommand.Parameters.Add(paramApellidos); //Agregamos el comando de insercion al adaptador this.adapter.InsertCommand = this.insertCommand; //Creamos el objeto comando para actualizar los registros modificados en el DataSet this.updateCommand = new SqlCeCommand(" UPDATE usuarios SET nombre=@nombre,apellidos=@apellidos WHERE ( id = @id )", this.conn); //Creamos los parametros para el comando de actualizacion SqlCeParameter paramUpdateNombre = new SqlCeParameter("@nombre", SqlDbType.NVarChar, 100, "nombre"); SqlCeParameter paramUpdateApellidos = new SqlCeParameter("@apellidos", SqlDbType.NVarChar, 100, "apellidos"); SqlCeParameter paramUpdateId = new SqlCeParameter("@id", SqlDbType.Int, 4, "id"); paramUpdateId.SourceVersion = DataRowVersion.Original; //Agregamos los parametros al comando this.updateCommand.Parameters.Add(paramUpdateNombre); this.updateCommand.Parameters.Add(paramUpdateApellidos); this.updateCommand.Parameters.Add(paramUpdateId); //Agregamos el comando al adaptador this.adapter.UpdateCommand = this.updateCommand; } Tal y como se puede observar la tarea es exáctamente la misma, creación del objeto comando, asignación de los parámetros del mismo y establecer este comando en el adaptador p ara realizar la función específica. Para comprobar que todo funciona correctamente usaremos un método que permita modificar un registro y posteriormente utilizar el adaptador para comprometer estos cambios en la base de datos.
- 148 -
Programación de Dispositivos Móviles
private void menuItemInsertar_Click(object sender, EventArgs e) { //Modificamos el nombre del primer registro de la tabla de usuarios this.ds.Tables["usuarios"].Rows[0]["nombre"] = "nombre Modificado"; //Hacemos que los cambios se comprometan en la base de datos this.adapter.Update(this.ds,"usuarios"); } En la Figura podemos ver el resultado una vez modificado un elemento.
Ejemplo de actualización de datos usando DataAdapter
Eliminando datos usando el objeto DataAdapter
En este punto ya sabemos como usar el objeto DataAdapter para insertar y actualizar los datos de un DataSet en la base de datos, solamente nos queda ver como hacemos para eliminar registros. Como seguramente ya os imaginais el proceso es exactamente el mismo, creación del comando usado para realizar eliminaciones en la base de datos y asignación del mismo al adaptador. Seguiremos por lo tanto el ejemplo de lo s capítulos anteriores agregando esta capacidad al adaptador.
private void InitAdapter() { string _connString = string.Empty; _connString = @"Data Source=\Program Files\SqlDataAdapter\MyDatabase#1.sdf"; //Creamos el objeto conexion this.conn = new SqlCeConnection(_connString); //Creamos el objeto DataAdatper this.adapter = new SqlCeDataAdapter("SELECT id,nombre,apellidos FROM usuarios",this.conn); //Creamos el objeto DataSet this.ds = new DataSet("ds"); //Creamos el objecto comando para insertar nuevos datos this.insertCommand = new SqlCeCommand("INSERT INTO usuarios(nombre,apellidos) VALUES(@nombre,@apellidos)", this.conn); //Creamos los parametros del comando
- 149 -
Programación de Dispositivos Móviles
SqlCeParameter paramNombre = new SqlCeParameter("@nombre", SqlDbType.NVarChar,100,"nombre"); SqlCeParameter paramApellidos = new SqlCeParameter("@apellidos", SqlDbType.NVarChar,100,"apellidos"); //Agregamos los parametros al comando this.insertCommand.Parameters.Add(paramNombre); this.insertCommand.Parameters.Add(paramApellidos); //Agregamos el comando de insercion al adaptador this.adapter.InsertCommand = this.insertCommand; //Creamos el objeto comando para actualizar los registros modificados en el DataSet this.updateCommand = new SqlCeCommand(" UPDATE usuarios SET nombre=@nombre,apellidos=@apellidos WHERE ( id = @id )", this.conn); //Creamos los parametros para el comando de actualizacion SqlCeParameter paramUpdateNombre = new SqlCeParameter("@nombre", SqlDbType.NVarChar, 100, "nombre"); SqlCeParameter paramUpdateApellidos = new SqlCeParameter("@apellidos", SqlDbType.NVarChar, 100, "apellidos"); SqlCeParameter paramUpdateId = new SqlCeParameter("@id", SqlDbType.Int, 4, "id"); paramUpdateId.SourceVersion = DataRowVersion.Original; //Agregamos los parametros al comando this.updateCommand.Parameters.Add(paramUpdateNombre); this.updateCommand.Parameters.Add(paramUpdateApellidos); this.updateCommand.Parameters.Add(paramUpdateId); //Agregamos el comando al adaptador this.adapter.UpdateCommand = this.updateCommand; //Creamos el objeto comando para eliminar los registros eliminados en el DataSet this.deleteCommand = new SqlCeCommand("DELETE FROM usuarios WHERE (id = @id)" , this.conn); //Creamos los parámetros de eliminación SqlCeParameter paramDeleteId = new SqlCeParameter("@id", SqlDbType.Int, 4, "id"); paramDeleteId.SourceVersion = DataRowVersion.Original; //Agregamos los parametros al comando this.deleteCommand.Parameters.Add(paramDeleteId); //Agregamos el comando al adaptador this.adapter.DeleteCommand = this.deleteCommand; } Para probarlo eliminamos la primera fila de la tabla de usuarios del DataSet y llamamos al método Update del adaptador tal y como puede verse en la siguientes lineas de código.
private void menuItemEliminar_Click(object sender, EventArgs e) { //Eliminamos el primer registro de la tabla this.ds.Tables["usuarios"].Rows[0].Delete(); //Hacemos que los cambios se comprometan en la base de datos this.adapter.Update(this.ds, "usuarios"); } En la siguiente figura puede verse el resultado
- 150 -
Programación de Dispositivos Móviles
Ejemplo de eliminación de datos usando DataAdapter
- 151 -
Programación de Dispositivos Móviles
Captura de Datos con DATAREADER con SQLCERESULSET Se necesita hacer un formulario con dos etiquetas, dos Texbox, un botón y un DataGrid. using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; using System.Data.Common; using System.Data.SqlServerCe; namespace Captura { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void InsertarRegistro() { //Especificamos el origen de datos string ruta = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); string connectionString = string.Empty; connectionString = string.Format(@"DataSource={0}", Path.Combine(ruta,"db.sdf")); //conectamos la base de datos SqlCeConnection oCon = new SqlCeConnection(connectionString); SqlCeCommand command = new SqlCeCommand ("SELECT usuario,password FROM tabla", oCon); if (oCon.State == ConnectionState.Closed) oCon.Open(); //creamos un dataset SqlCeDataAdapter oDA = new SqlCeDataAdapter("SELECT usuario,password FROM tabla", oCon); DataSet oDS = new DataSet("tabla"); //se crea el objeto resulset para movernos entre los registros SqlCeResultSet resulSet = command.ExecuteResultSet(ResultSetOptions.Updatable); try { //se insertan los registros SqlCeUpdatableRecord record = resulSet.CreateRecord(); record[0] = usuario.Text; record[1] = password.Text; ; resulSet.Insert(record); //Rellenamos el dataset oDA.Fill(oDS);
} catch (SqlCeException ex) { Console.WriteLine(ex.Message); }
}
finally { //Asignamos al datasource del datagrid nuestro dataset oDA.Dispose(); this.dataGrid1.DataSource = oDS.Tables[0].DefaultView; //cerramos la base de datos oCon.Dispose(); oCon.Close(); }
- 152 -
Programación de Dispositivos Móviles
}
private void btnGuardar_Click(object sender, EventArgs e) { InsertarRegistro(); }
- 153 -
Programación de Dispositivos Móviles
Insertar datos en una base de datos con DataAdapter. Se tiene que realizar un formulario con dos etiquetas, dos TextBox y un ListBoxt. Utilizando uno de los botones de menú. using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; using System.Data.Common; using System.Data.SqlServerCe; namespace InsertarDatos { public partial class Form1 : Form { SqlCeConnection conn; SqlCeDataAdapter adapter; SqlCeCommand insertCommand; DataSet ds; public Form1() { InitializeComponent(); IniciarAdapter(); } private void IniciarAdapter() { //Especificamos el origen de datos string ruta = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); string conexion = string.Empty; conexion = string.Format(@"DataSource={0}", Path.Combine(ruta, "db.sdf")); //Creamos el objeto conexion this.conn = new SqlCeConnection(conexion); //Creamos el objeto DataAdatper this.adapter = new SqlCeDataAdapter("SELECT id,usuario,password FROM tabla", this.conn); //Creamos el objeto DataSet this.ds = new DataSet("ds"); //Creamos el objecto comando para insertar nuevos datos this.insertCommand = new SqlCeCommand("INSERT INTO tabla(usuario,password) VALUES(@usuario,@password)", this.conn); //Creamos los parametros del comando SqlCeParameter paramUsuario = new SqlCeParameter("@usuario", SqlDbType.NVarChar, 100, "usuario"); SqlCeParameter paramPassword = new SqlCeParameter("@password", SqlDbType.NVarChar, 100, "password"); //Agregamos los parametros al comando this.insertCommand.Parameters.Add(paramUsuario); this.insertCommand.Parameters.Add(paramPassword); //Agregamos el comando de insercion al adaptador this.adapter.InsertCommand = this.insertCommand; } private void Form1_Load(object sender, EventArgs e) { try { adapter.Fill(ds, "tabla"); //Enlazamos los datos del dataSet a u n objeto ListBox listBox1.DataSource = ds.Tables["tabla"]; listBox1.DisplayMember = "usuario"; listBox1.ValueMember = "id"; } catch (SqlCeException ex) { MessageBox.Show(ex.Message.ToString()); } } private void menuItem1_Click(object sender, EventArgs e) {
- 154 -
Programación de Dispositivos Móviles
conn.Open(); //Creamos una nueva fila para la tabla y la agregamos al dataSet DataRow row = this.ds.Tables["tabla"].NewRow(); row["usuario"] = Usuario.Text; row["password"] = Password.Text; this.ds.Tables["tabla"].Rows.Add(row); //Hacemos que los cambios se comprometan en la base de datos this.adapter.Update(this.ds.Tables["tabla"]); conn.Close(); Usuario.Text = ""; Password.Text = ""; Usuario.Focus(); } } }
- 155 -
Programación de Dispositivos Móviles
Cambiar datos en una base de datos, se utiliza solamente un Label, un TextBoxt y un ListBoxt, además de el MenuItem. using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; using System.Data.Common; using System.Data.SqlServerCe; namespace Cambios { public partial class Form1 : Form { SqlCeConnection conn; SqlCeDataAdapter adapter; SqlCeCommand updateCommand; DataSet ds; public Form1() { InitializeComponent(); IniciarAdapter(); } private void IniciarAdapter() { //Especificamos el origen de datos string ruta = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); string conexion = string.Empty; conexion = string.Format(@"DataSource={0}", Path.Combine(ruta, "db.sdf")); //Creamos el objeto conexion this.conn = new SqlCeConnection(conexion); //Creamos el objeto DataAdatper this.adapter = new SqlCeDataAdapter("SELECT id,usuario,password FROM tabla", this.conn); //Creamos el objeto DataSet this.ds = new DataSet("ds"); //Creamos el objeto comando para actualizar los registros modificados en el DataSet this.updateCommand = new SqlCeCommand(" UPDATE tabla SET usuario=@usuario,password=@password WHERE ( id = @id )", this.conn); //Creamos los parametros para el comando de actualizacion SqlCeParameter paramUpdateUsuario = new SqlCeParameter("@usuario", SqlDbType.NVarChar, 100, "usuario"); SqlCeParameter paramUpdatePassword = new SqlCeParameter("@password", SqlDbType.NVarChar, 100, "password"); SqlCeParameter paramUpdateId = new SqlCeParameter("@id", SqlDbType.Int, 4, "id"); paramUpdateId.SourceVersion = DataRowVersion.Original; //Agregamos los parametros al comando this.updateCommand.Parameters.Add(paramUpdateUsuario); this.updateCommand.Parameters.Add(paramUpdatePassword); this.updateCommand.Parameters.Add(paramUpdateId); //Agregamos el comando al adaptador this.adapter.UpdateCommand = this.updateCommand; } private void Form1_Load(object sender, EventArgs e) { try {
- 156 -
Programación de Dispositivos Móviles
adapter.Fill(ds, "tabla"); //Enlazamos los datos del dataSet a u n objeto ListBox listBox1.DataSource = ds.Tables["tabla"]; listBox1.DisplayMember = "usuario"; listBox1.ValueMember = "id"; } catch (SqlCeException ex) { MessageBox.Show(ex.Message.ToString()); } } private void menuItem1_Click(object sender, EventArgs e) { conn.Open(); //Modificamos el usuario del primer registro de la tabla int a; a = Convert.ToInt32(Id.Text); this.ds.Tables["tabla"].Rows[a]["usuario"] = Usuario.Text; //Hacemos que los cambios se comprometan en la base de datos this.adapter.Update(this.ds, "tabla"); conn.Close(); Usuario.Text = ""; Id.Text = ""; Id.Focus(); } } }
- 157 -
Programación de Dispositivos Móviles
Borrar datos en una base de datos, se utiliza solamente un Label, un TextBoxt y un ListBoxt, además de el MenuItem. using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; using System.Data.SqlServerCe; namespace Borrar { public partial class Form1 : Form { SqlCeConnection conn; SqlCeDataAdapter adapter; SqlCeCommand deleteCommand; DataSet ds; public Form1() { InitializeComponent(); IniciarAdapter(); } private void IniciarAdapter() { //Especificamos el origen de datos string ruta = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); string conexion = string.Empty; conexion = string.Format(@"DataSource={0}", Path.Combine(ruta, "db.sdf")); //Creamos el objeto conexion this.conn = new SqlCeConnection(conexion); //Creamos el objeto DataAdatper this.adapter = new SqlCeDataAdapter("SELECT id,usuario,password FROM tabla", this.conn); //Creamos el objeto DataSet this.ds = new DataSet("ds"); //Creamos el objeto comando para eliminar los registros eliminados en el DataSet this.deleteCommand = new SqlCeCommand("DELETE FROM tabla WHERE (id = @id)", this.conn); //Creamos los parámetros de eliminación SqlCeParameter paramDeleteId = new SqlCeParameter("@id", SqlDbType.Int, 4, "id"); paramDeleteId.SourceVersion = DataRowVersion.Original; //Agregamos los parametros al comando this.deleteCommand.Parameters.Add(paramDeleteId); //Agregamos el comando al adaptador this.adapter.DeleteCommand = this.deleteCommand; } private void menuItem1_Click(object sender, EventArgs e) { conn.Open(); //Modificamos el usuario del primer registro de la tabla int a; a = Convert.ToInt32(Orden.Text); this.ds.Tables["tabla"].Rows[a].Delete();
- 158 -
Programación de Dispositivos Móviles
//Hacemos que los cambios se comprometan en la base de datos this.adapter.Update(this.ds, "tabla"); conn.Close(); Orden.Text = ""; Orden.Focus(); } private void Form1_Load(object sender, EventArgs e) { try { adapter.Fill(ds, "tabla"); //Enlazamos los datos del dataSet a u n objeto ListBox listBox1.DataSource = ds.Tables["tabla"]; listBox1.DisplayMember = "usuario"; listBox1.ValueMember = "id"; } catch (SqlCeException ex) { MessageBox.Show(ex.Message.ToString()); } } }
}
- 159 -
Programación de Dispositivos Móviles
Sincronización de datos La sincronización de datos entre un dispositivo móvil y una base de datos remota es un proceso complicado que requiere de un estudio exhaustivo para realizar una buena elección de las técnicas a utilizar. En este módulo trataremos de desglosar diferentes técnicas y de realizar casos prácticos que nos ayuden en la toma de esas decisiones.
Sincronización Con la llegada de Sql Server 2005 Mobile Edition o superior, la arquitectura del sistema utiliza el agente móvil del cliente SQL para tener acceso a la base de datos local a través del agente móvil de SQL. Sin embargo, para la sincronización con una base de datos remota es necesaria una petición desde el cliente móvil al servidor SQL con los servicios que proporciona IIS ( Internet Information Services ), el cliente móvil entonces puede utilizar OLE DB para acceder al proveedor, el cual responderá con los datos solicitados usando la misma cadena de objetos.La Figura ilustra esta situación.
Ilustración del proceso de sincronización
RDA - Remote Data Access RDA permite mantener la sincronización entre una base de datos en un dispositivo móvil y una base de datos remota, sin necesitar una conexión constante ( este tipo de conexiones se denominan Loosley Coupled connection ). Una vez que se han recuperado los datos del servidor remoto, estos son almacenados y tratados en el dispositivo móvil mediante el motor de Sql CE. Los datos almacenados así como sus cambios e inserciones pueden ser llevados de nuevo al servidor remoto. Estas dos operaciones se denominan Pull y Push y pasaremos a explicarlas con más detalle a continuación.
Operación PULL El proceso de Pull recupera los datos de la base de datos remota y los almacena en la base de datos del dispositivo móvil. Esta operación crea el esquema de la tabla y almacena los datos demandados en ella. Es importante destacar que para que el proceso de Pull se realice correctamente en la base de datos del dispositivo móvil NO debe de existir una tabla con el mismo nombre que la demandada en la operación. Las operaciones de Pull pueden realizarse de distinta forma dependiendo del propósito de la misma. •
TrackingOff No se proporciona seguimiento a los datos replicados, ademas las restricciones de la base de datos remota no son tenidos en cuenta para la creación del mismo en el dispositivo móvil.
•
TrackingOn Con esta opción si se proporciona seguimiento a los datos replicados, para ello la tabla replicada no solo consta de los campos que se traen de la base de datos remota sino que además se añade una serie de campos para proporcionar este seguimiento. Las restricciones como claves primarias son tenidas en cuenta y replicadas en la base de datos del dispositivo móvil pero los índices no se tienen en cuenta
•
TrackingOffWithIndexes Al igual que TrackingOff, no se proporciona seguimiento de los datos replicados aunque en este caso las restricciones de integridad referencial si son añadidas a la tabla de la base de datos del dispositivo móvil.
- 160 -
Programación de Dispositivos Móviles
•
TrackingOnWithIndexes Igual que TrackingOn con la diferencia de que en este caso si se añaden los índices.
Operación Push El proceso de Push actualiza los cambios realizados en la base de datos del dispositivo móvil en la base de datos remota. Si durante esta transacción ocurre algún tipo de fallo, esta es capáz de realizar un 'RolledBack' a su estado original. Existen 2 formas de tratar la operación de Push. •
RdaBatchingOn El proceso de Push se realiza en una única transacción
•
RdaBatchingOff Este es el valor por defecto, mediante esta opción cada fila que se lleva a la base de datos remota se ejecuta en una transacción.
Observaciones
Uno de los problemas que surgen cuando se realizan operaciones de Push en el servidor remoto es el tratamiento de las claves primarias. En el caso de sincronización mediante Merge Replication, que veremos en el siguiente capítulo, este problema es de fácil solución mediante IDENTITIES usando la opción de Sql Sever 2005 o superior NOT FOR REPLICATION. Desgraciadamente con RDA no podemos emplear esta solución por lo que para garantizar un correcto funcionamiento usando RDA debemos de garantizar que cada cliente sea capaz de crear identificadores únicos y que estos no se puedan imitar en el resto de clientes. Un caso práctico con RDA
Veremos a continuación como implementar RDA con un ejemplo sencillo, empezaremos por crear una nueva base de datos en Sql Server 2005 o superior, esta base de datos la llamaremos RemoteDB y constará de una única tabla, usuarios, con los campos id, nombre y apellidos tal y como puede verse en la siguiente figura.
Nueva base de datos de SQL Server 2005 o superior (aplicable a SQL Server 2008 ó superior)
Configuración de IIS y NTFS
La configuración del servicio para Internet Information Services ( IIS) requiere de los siguientes pasos:
1. Creamos un directorio virtual en IIS; 2. Especificamos el alias para ese directorio virtual; 3. Especificamos la ruta de acceso al contenido para ese directorio virtual. En este directorio debe existir una copia del Agente de 4. 5.
SQL Server CE; por defecto, haremos que este directorio virtual apunte a (para SQL Server 2005 Mobile Edition) C:\Archivos de programa\Microsoft SQL Server 2005 Mobile Edition\Server Concedemos el permiso de ejecución para este directorio; Configuración de la autentificación de IIS: SqlServerCE soporta 3 métodos de autentificación: o Acceso Anónimo
- 161 -
Programación de Dispositivos Móviles
o o
Basic Authentication Integrated Windows
Una vez hecho esto es necesario además agregar permisos de lectura-escritura NTFS al agente sqlcesa30.dll. Una vez hecho esto ya tendremos acceso al agente de servicios mediante la URL domain/virtual-Dir/sqlcesa30.dll .
Implementación del dispositivo móvil
El acceso mediante RDA al agente de servicios de SQL Server se proporciona mediante el objeto SqlCeRemoteDataAccess. A este objeto es necesario proporcionarle los parámetros de autentificación al servicio IIS y la URL del servicio, además de proporcionarle la cadena de conexión a la base de datos Sql Ce del dispositivo móvil.
Inicialización del cliente RDA
private void ConfigureRDA() { ceRDA = new SqlCeRemoteDataAccess(); //Configuracion del acceso remoto ceRDA.InternetLogin = string.Empty; ceRDA.InternetPassword = string.Empty; ceRDA.InternetUrl = "http://192.168.2.5/RemoteDB/sqlcesa30.dll"; string localDbConnectionString = string.Empty; CreateLocalDb(ref localDbConnectionString); ceRDA.LocalConnectionString = localDbConnectionString; } Una vez iniciado el objeto SqlCeRemoteDataAccess con los valores de autentificación y la ruta de acceso se invoca al método CreateLocalDb el cual crea una nueva base de datos de SqlCe, esto se puede ver en el siguiente código.
private void CreateLocalDb(ref string localPath) { SqlCeEngine engineCe = new SqlCeEngine(); //Path de la base de datos string pathDB = string.Empty; pathDB = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); pathDB = Path.Combine(pathDB, "rda.sdf"); //Creamos la base de datos vacía if (!File.Exists(pathDB)) { localPath = string.Format("Data Source={0}",pathDB); engineCe.LocalConnectionString = localPath; engineCe.CreateDatabase(); } } Una vez hecho esto ya solamente nos queda ver como invocar las operaciones de Pull y/o Push de datos. Para nuestro ejemplo situaremos un control DataGrid en el formulario y veremos como rellenarlo con los datos del servidor remoto. En la figura se puede ver el diseño de nuestra aplicación.
- 162 -
Programación de Dispositivos Móviles
Diseño de la aplicación Los métodos de Pull y Push necesitan además de la cadena de conexión de la instancia del Sql Server a la que nos vamos a conectar y los datos de autentificación tal y como puede verse a continuación.
private void menuItem1_Click(object sender, EventArgs e) { //Implementación de Pull if (this.ceRDA != null) { try { string _oleConnectionString = @"Provider=sqloledb;Data Source=PORSOURCECODE\YUKON;Initial Catalog=RemoteDB;User id=sa;password=*****"; ceRDA.Pull("usuarios", "select * from usuarios", _oleConnectionString,RdaTrackOption.TrackingOn); } catch (SqlCeException __ERR) { MessageBox.Show(__ERR.Message.ToString()); } } } private void menuItem2_Click(object sender, EventArgs e) { //Implementacion de Push if (this.ceRDA != null) { try { string _oleConnectionString = @"Provider=sqloledb;Data Source=PORSOURCECODE\YUKON;Initial Catalog=RemoteDB;User id=sa;password=******"; ceRDA.Push("usuarios", _oleConnectionString, RdaBatchOption.BatchingOn); } catch (SqlCeException __ERR) { MessageBox.Show(__ERR.Message.ToString()); } } } A continuación puede verse en código completo de nuestro ejemplo
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data;
- 163 -
Programación de Dispositivos Móviles
using System.Drawing; using System.Text; using System.Windows.Forms; using System.Data.SqlServerCe; using System.IO; namespace RDA { public partial class Form1 : Form { SqlCeRemoteDataAccess ceRDA; string localConnectionString = string.Empty; public Form1() { InitializeComponent(); ConfigureRDA(); } private void ConfigureRDA() { ceRDA = new SqlCeRemoteDataAccess(); //Configuracion del acceso remoto ceRDA.InternetLogin = string.Empty; ceRDA.InternetPassword = string.Empty; ceRDA.InternetUrl = "http:"//192.168.2.5/RemoteDB/sqlcesa30.dll"; CreateLocalDb(); ceRDA.LocalConnectionString = this.localConnectionString; } private void CreateLocalDb() { SqlCeEngine engineCe = new SqlCeEngine(); //Path de la base de datos string pathDB = string.Empty; pathDB = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); pathDB = Path.Combine(pathDB, "rda.sdf"); //Creamos la base de datos vacía if (!File.Exists(pathDB)) { this.localConnectionString = string.Format("Data Source={0}",pathDB); engineCe.LocalConnectionString = this.localConnectionString; engineCe.CreateDatabase(); } } private void menuItem1_Click(object sender, EventArgs e) { //Implementación de Pull if (this.ceRDA != null) { try { string _oleConnectionString = @"Provider=sqloledb;Data Source=PORSOURCECODE\YUKON;Initial Catalog=RemoteDB;User id=sa;password=yukon2005beta"; ceRDA.Pull("usuarios", "select * from usuarios", _oleConnectionString,RdaTrackOption.TrackingOn); } catch (SqlCeException __ERR) { MessageBox.Show(__ERR.Message.ToString()); } } }
- 164 -
Programación de Dispositivos Móviles
private void menuItem2_Click(object sender, EventArgs e) { //Implementacion de Push if (this.ceRDA != null) { try { string _oleConnectionString = @"Provider=sqloledb;Data Source=PORSOURCECODE\YUKON;Initial Catalog=RemoteDB;User id=sa;password=yukon2005beta"; ceRDA.Push("usuarios", _oleConnectionString, RdaBatchOption.BatchingOn); } catch (SqlCeException __ERR) { MessageBox.Show(__ERR.Message.ToString()); } } } } }
Merge Replication Merge Replication es una técnica ideal en dispositivos móviles ya que aporta autonomía e independencia al dispositivo a la vez que facilita el sincronismo de los datos cuando deseen ser volcados al servidor. Dentro de esta técnica de sincronización hemos de distinguir entre Publicadores y Subscriptores. Los publicadores envían los datos y estos son recibidos por los Subscriptores, en el caso que nos ocupa, el publicador es la base de datos remota y el subscriptor es la base de datos del dispositivo móvil. En un entorno real, los datos tanto en local como en la base de datos cambian con el tiempo, empleando este modelo, la sincronización de los datos se realiza tanto en el servidor remoto como en el dispositivo móvil recuperando datos nuevos o las modificaciones de los datos existentes. Si bien es cierto que el despliegue y el mantenimiento de Merge Replication requiere de mucho trabajo, esta técnicas posee ciertas ventajas que se deben tener muy en cuenta: • • •
La replicación posee características para resolver los conflictos de sincronización Permite la sincronización de datos de múltiples tablas en 'un tiempo' Permite llevar un seguimiento de cada publicación
Es importante comprender y tener en cuenta que la sincronización mediante Merge Replication crea una cantidad de carga notable en el servidor. Cuando una base de datos se agrega como Publicador, la Metadata de dicha base de datos se modifica creando distintos 'Disparadores' y 'Procedimientos Almacenados' para facilitar la sincronización y la resolución de conflictos. Adicionalmente a todas las tablas replicadas se les añade una nueva columna de tipo ROWGUIDCOL con el fin de mantener las tablas y de proporcionar a cada fila de un identificador único. Esta nueva columna en la tabla causa un aumento del tráfico de la memoria, por ejemplo en una tabla con únicamente 32 registros, el aumento de añadir el ROWGUIDCOL es de 1Kb = ( 16 bytes en el registro + 16 bytes en el índice ) * 32. Para reducir el coste de la replicación, es recomendable reducir la cantidad de datos que se van a sincronizar fijando los filtros de los datos que se van a publicar. Como comentamos, en RDA no está soportado el uso de IDENTITIES; sin embargo en 'Merge' Replication su uso es una buena técnica de creación de claves primarias. Las columnas en SQL Server pueden ser marcadas con la restricción not for replication la cual permite inserciones de valores para un identity fuera del seed actual. El método a seguir consistirá en asignar rangos de identities al Publicador y a cada Subscriptor, estableciendo la restricción not for replication en la tabla de SQL Server. Un caso práctico con Merge Replication
En el anterior capítulo vimos un caso práctico sobre como implementar RDA ( Remote Data Access ) para ello usamos una base de datos que habíamos llamado RemoteDB, en este caso implementaremos el mecanismo de sincronización Merge Replication, para ello veremos los pasos uno a uno. 1.
Abrimos Microsoft SQL Server 2005 (ó superior) y nos conectamos al servidor. Desplegamos la ventana de Explorador de Objetos ( Object Explorer ).
2. 3. Desplegamos la pestaña Replication y en la subcarpeta hacemos doble clic para crear una nueva publicación:
- 165 -
Programación de Dispositivos Móviles
Creando la publicación 4. 5. 6. 7. 8. 9. 10. 11. 12.
Comenzamos el asistente para crear la publicación Seleccionamos la base de datos que deseamos publicar En el tipo de publicación seleccionamos 'Merge Publication' Seleccionamos Sql Server Mobile Edition en el tipo de Subscriptor Seleccionamos las tablas o vistas que deseemos publicar Especificamos los filtros, en caso de que se quiera, de las tablas a publicar Especificamos el comportamiento de SnapShop Agent Especificamos la cuenta bajo la cual va a funcionar el agente de SnapShop Especificación del nombre de la publicación
Creando la publicación Una vez que hemos terminado el asistente de publicación hacemos con el botón derecho del ratón clic en la publicación y seleccionamos el asistente de Sincronización Web
- 166 -
Programación de Dispositivos Móviles
Creando la publicación 13. Seleccionamos si deseamos crear un nuevo directorio virtual o usar uno existente, en nuestro caso seleccionamos el que habíamos creado para implementar RDA en el capítulo anterior.
Creando la publicación 14. Especificamos la autentificación de los clientes de la publicación, nosotros no vamos a requerir nombre de usuario ni contraseña
- 167 -
Programación de Dispositivos Móviles
Creando la publicación Con este último asistente hemos terminado la primera parte de nuestro trabajo, ahora tendremos que agregar la subscripción para esta publicación para ello nos conectamos desde Sql Server Managment Studio a servidor de Sql Mobile y seleccionamos la base de datos que será subscriptora de la publicación que acabos de crear.
Creando la subscripción (aplicable a SQL Server 2008 ó superior) En el Object Explorer expandimos Replications y en Subscriptors hacemos clic con el botón derecho para agregar un nuevo Subscriptor,seleccionamos el Servidor y la Publicación para la cual vamos a ser Subscriptores .
- 168 -
Programación de Dispositivos Móviles
Creando la subscripción Una vez terminado el asistente ya estamos en disposición de crear nuestra aplicación para dispositivos móviles que implemente Merge Replication.
Creando la subscripción A continuación pasamos a ver un ejemplo de código en el que se hace uso de Merge Replication, lo primero que vamos a hacer es configurar los parámetros necesarios para el uso de esta técnica y posteriormente sincronizar la base de datos del dispositivo móvil con el servidor remoto.
private void menuItem1_Click(object sender, EventArgs e) { repl = new SqlCeReplication(); repl.InternetUrl = @"http:"//192.168.2.5/RemoteDb/sqlcesa30.dll"; repl.InternetLogin = string.Empty; repl.InternetPassword = string.Empty;
- 169 -
Programación de Dispositivos Móviles
repl.Publisher = @"PORSOURCECODE\YUKON"; repl.PublisherDatabase = @"RemoteDB"; repl.PublisherSecurityMode = SecurityType.DBAuthentication; repl.PublisherLogin = @"sa"; repl.PublisherPassword = @"<...>"; repl.ExchangeType = ExchangeType.BiDirectional; repl.Publication = @"RemoteDbPublication"; repl.Subscriber = @"RemoteDbSubcriptions"; string pathDb = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); pathDb = Path.Combine(pathDb, "merge.sdf"); PathLocalDB = string.Format("Data Source={0}", pathDb); repl.SubscriberConnectionString = PathLocalDB; try { repl.AddSubscription(AddOption.ExistingDatabase); repl.Synchronize(); } catch (SqlCeException e) { MessageBox.Show(e.ToString()); } } Con esto ya tendremos sincronizada en nuestra base de datos de dispositivo móvil la base de datos del servidor remoto, y tal como se puede observar en la siguiente figura efectivamente la base de datos remota ademas de poseer los campos y registros sincronizados posee una serie de columnas nuevas que permitirán la resolución de conflictos.
Creando la subscripción El código completo de nuestro sencillo ejemplo puede verse a continuación.
- 170 -
Programación de Dispositivos Móviles
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Data.SqlServerCe; using System.IO; namespace Merge { public partial class Form1 : Form { SqlCeReplication repl; string PathLocalDB = string.Empty; public Form1() { InitializeComponent(); } private void menuItem1_Click(object sender, EventArgs e) { repl = new SqlCeReplication(); repl.InternetUrl = @"http:"//192.168.2.5/RemoteDb/sqlcesa30.dll"; repl.InternetLogin = string.Empty; repl.InternetPassword = string.Empty; repl.Publisher = @"PORSOURCECODE\YUKON"; repl.PublisherDatabase = @"RemoteDB"; repl.PublisherSecurityMode = SecurityType.DBAuthentication; repl.PublisherLogin = @"sa"; repl.PublisherPassword = @"<...>"; repl.ExchangeType = ExchangeType.BiDirectional; repl.Publication = @"RemoteDbPublication"; repl.Subscriber = @"RemoteDbSubcriptions"; string pathDb = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); pathDb = Path.Combine(pathDb, "merge.sdf"); PathLocalDB = string.Format("Data Source={0}", pathDb); repl.SubscriberConnectionString = PathLocalDB; try { //repl.AddSubscription(AddOption.ExistingDatabase); repl.Synchronize(); } catch (SqlCeException __ERR) { MessageBox.Show(__ERR.Message.ToString()); } } }
}
Empaquetado y desplegado
Llegados a este punto del curso ya sabemos como usar Visual Studio 2005 para la creación de aplicaciones para dispositivos móviles, además hemos aprendido a trabajar con Sql Ce como motor de bases de datos para estos dispositvos, incluso hemos visto un módudo de sincronización de datos con un servidor Sql Server remoto. Aún nos queda algo, que por sencillo, no deja de ser muy importante. Ya tengo mi aplicación, pero ¿cómo hago para instalarla en distintos dispositivos?. En este módulo veremos los pasos necesarios para crear un instalador de nuestras aplicaciones de tal forma que con un sencillo clic se pueda instalar en un dispositivo móvil.
- 171 -
Programación de Dispositivos Móviles
Creación de instaladores A continuación veremos paso a paso como construir instaladores para nuestras aplicaciones para dispositivos móviles de una forma sencilla. Supongamos que ya tenemos nuestra aplicación terminada, la llamaremos MSDNApp y queremos crear un instalador para poder redistribuirla, lo primero que tendremos que realizar será crear el archivo de tipo .cab (Cabinet) que contendrá el ejecutable, referencias y recursos necesarios de la misma.
Creando el .CAB de nuestra aplicación
Para crear el archivo .CAB de nuestra aplicación basta con agregar a nuestra solución un nuevo proyecto de tipo Smart Device CAB Project, tal y como puede verse en la siguiente figura.
Agregando un nuevo proyecto de tipo Cab Project Una vez hecho esto agregamos el proyecto que deseamos empaquetar en el archivo .CAB y si es necesario aquellos archivos que necesitemos llevar, para ello nos situamos en el explorador de soluciones encima del proyecto de SmartDeviceCab, hacemos clic con el botón derecho del ratón y seleccionamos en el menú contextual la opción Add->Project Output o bien Add->File si es necesario.
Agregando elementos al archivo .CAB
- 172 -
Programación de Dispositivos Móviles
Agregando elementos al archivo .CAB En este momento si generamos la solución ya tendremos creado nuestro archivo de empaquetado .cab para nuestra aplicación, estaría situado dentro de directorio Debug o Release dependiendo de la configuración de la solución. Este archivo .cab podría ser llevado a cualquier dispositivo y ejecutándolo se iniciaría el instalador. Sin embargo esto no es lo que queremos, nuestro objetivo es que con un solo clic en el instalador este sea capaz de iniciar el instalador de aplicaciones de ActiveSync para que realice todo el trabajo. En los siguientes puntos veremos como hacer esto.
Creando el instalador de la aplicación
Para crear el instalador agregaremos un nuevo proyecto de tipo Setup tal y como puede verse en la siguiente figura.
Agregando elementos al archivo .CAB A este proyecto agregamos la salida del proyecto creado anteriormente para generar el archivo .CAB y un archivo Setup.ini que será usado para la instalación y que tendrá la siguiente estructura
- 173 -
Programación de Dispositivos Móviles
[CEAppManager] Version = 1.0 Component = MsdnTest [MsdnTest] Description = Descripción de la aplicacion CabFiles = NombreCab.cab Este archivo .ini será usando por la herramienta CeAppMngr.exe para instalar el archivo .cab en el dispositivo, ¿ Como se realiza esta acción ?. Para hacer que la herramienta CeAppMngr.exe ejecute el archivo .ini e instale el empaquetado .cab de nuestra aplicación en el dispositivo móvil necesitaremos crear una acción persinalizada de instalación.
Nota: La herramienta CeAppMng.exe es parte de ActiveSync, por lo que este último es un requisito obligatorio
Creación de la acción de instalación Para crear la acción de instalación crearemos un nuevo proyecto vacio en el que agregaremos un nuevo elemento de tipo Installer Class.
ando la acción de instalación
- 174 -
Cre
Programación de Dispositivos Móviles
Creando la acción de instalación Lo que tendremos que hacer es recuperar la información de la ruta de la herramienta CeAppMngr.exe para poder llamarla con el fin de que ejecute el archivo .INI creado anteriormente. El código para esta acción personalizada puede verse a continuación.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration.Install; using Microsoft.Win32; using System.IO; using System.Windows.Forms; using System.Reflection; using System.Diagnostics; namespace CEAppMngrAction { [RunInstaller(true)] public partial class CeAppMngrInstall : Installer { public CeAppMngrInstall() { InitializeComponent(); } private void CeAppMngrInstall_AfterInstall(object sender, InstallEventArgs e) { string _lsPathSetupIni = string.Empty; _lsPathSetupIni = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Setup.ini");
}
if (File.Exists(_lsPathSetupIni)) RunCeAppManager(_lsPathSetupIni); else MessageBox.Show(" No se encuentra en archivo Setup.ini", "MsdnTest", MessageBoxButtons.OK, MessageBoxIcon.Error);
private void CeAppMngrInstall_AfterUninstall(object sender, InstallEventArgs e)
- 175 -
Programación de Dispositivos Móviles
{ RunCeAppManager(null); } private void RunCeAppManager(string arg) { //Obtener la ruta de ceappManager string pathCEManager = string.Empty; string argsCeManager = string.Empty; RegistryKey keyCeAppManager = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\CEAPPMGR.EXE" ); if (keyCeAppManager != null) { pathCEManager = keyCeAppManager.GetValue("").ToString(); argsCeManager = (arg != null) ? string.Format("\"{0}\"", arg) : string.Empty; Process.Start(string.Format("\"{0}\"", pathCEManager), argsCeManager); } else MessageBox.Show(" No se ha encontrado Active Sync, por favor instale primero este componente", "MsdnTest", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } Una vez creada nuestra acción personalizada simplemente hay que agregarla al proyecto Setup, para ello en el explorador de soluciones en el proyecto hacemos clic con el botón derecho y seleccionamos Ver->Acciones personalizadas.
Creando la acción de instalación En la vista de acciones personalizadas seleccionamos la entrada Install y en el menu contextual hacemos clic en ' Agregar Acción Personalizada'.
Creando la acción de instalación En este paso seleccionamos la salida del proyecto de acción personalizada que habíamos creado.
- 176 -