Indice

He aquí un compendio de preguntas y problemas varios que he visto una y otra vez en proyectos finales. Espero que les sirva. Si algo no se entiende o hay algún tópico que agregar/completar, pueden escribir a zaskar_84<arroba>yahoo.com.ar.

Preguntas genéricas

Preguntas/Problemas comunes sobre ZinjaI y wxWidgets

Topicos "avanzados" sobre ZinjaI y wxWidgets


Esto se publica bajo licencia Creative Commons (CC BY-SA). Por Pablo Novara, con colaboraciones de Pablo Abratte. Última actualización: 28/02/2017.



Respuestas:


¿Tengo que dividir mi código en muchos archivos cpp y h? ¿Puedo escribir todo junto en el mismo archivo?


Separar en cpp y h tiene varios objetivos:
Volver al indice

¿Qué va en los .h y qué en los .cpp?


La respuesta rápida es que en los .h debería ir todo lo que no ocupa lugar en el ejecutable, sino que solo está para darle indicaciones al compilador sobre cómo son las cosas. Por ejemplo, el prototipo de una función es un aviso al compilador para que no se queje cuando la invoquemos y sepa cómo se usa, pero lo que realmente se compila es su implementación, por esto el prototipo va en el .h y su implementación en el .cpp. De igual forma, la declaración de una clase con sus atributos y prototipos de métodos solo sirve para indicarle al compilador que forma tendrá cuando la necesite, va en el .h; pero declarar un objeto de esa clase o implementar una función sí ocupan lugar, así que van en los cpps. Además, si declaramos algo que realmente se compile en un .h, y después hacemos un #include de ese .h desde dos .cpps diferentes del mismo proyecto tenemos compilado dos veces lo mismo, por ejemplo, la misma función, así que a la hora de enlazar no sabrá cual de las dos elegir y generará un error similar a "multiple definition of 'foo'...".

La excepción a esta regla aparece con los templates. No se debe colocar la implementación de los templates en su propio .cpp y querer compilarlo, porque hasta que no se invoque (cosa que se hará desde otro .cpp y en la compilación del primero no interviene) no se sabe qué especializaciones se necesitan. Por esto, al compilar el .cpp del template probablemente no compile en realidad ninguna implementación. Los templates entonces se suelen colocar enteros en el .h para que cada .cpp que lo incluye genere en su compilación la especialización que necesita.

El .h suele estar rodeado por directivas de preprocesador como "#ifndef ALGO", "#define ALGO", "#endif". Esto se hace para evitar que el archivo se incluya dos veces. Por ejemplo, si se tiene una clase ListaClientes y otra ListaProductos que manejan los clientes y productos de un software de gestión a través de listas STL, es probable que ambas clases incluyan a la cabecera <list> para declarar objetos list entre sus atributos y argumentos. Entonces, si un programa cliente usa las dos clases, hace los dos #includes e incluye indirectamente dos veces la cabecera que contiene la declaración de la clase string. Si el compilador la procesara dos veces generaría un error. Si la cabecera tiene la estructura de directivas de preprocesador mencionada, la primera vez el "#ifndef _LIST_H_" resulta verdadero (pregunta si no está definida la constante de preprocesador _LIST_H_), y entonces define la constante y procesa la clase, mientras que la segunda el #ifndef resulta falso porque la constante ya fué definida en la primer pasada, y entonces saltea el contenido del archivo evitando el error. El nombre de la constante se define con las mismas reglas que un nombre de clase o variable, pero se suelen colocar en mayúsculas por convención.

En el .cpp suelen estar los #includes necesarios, incluyendo el include del .h homónimo. Siempre es preferible colocar los #include en el .cpp para que la compilación sea más rápida. Por ejemplo, si una clase maneja archivos en sus métodos, pero no tiene ningún atributo ni argumento de tipo fstream, el include de dicha clase puede colocarse en el .cpp. Pero si una clase tiene un atributo de tipo vector, el #include debe colocarse sí o sí en el .h para poder definir el atributo. La excepción ocurre cuando el atributo o argumento es un puntero, en cuyo caso basta con una forward declaration de la clase (ejemplo: "class Persona;") para poder declarar el puntero. Por último, siempre conviene colocar primero los includes de bibliotecas externas al proyecto (entre < y >) y luego los correspondientes a archivos del proyecto (entre comillas).

Volver al indice

¿Cómo elegir qué tipo de archivos (binarios o de texto) y de qué forma utilizarlos en mi proyecto?

Depende de los datos que maneje el programa. Hay que considerar dos aspectos: el tipo de archivo, y la forma de lectura y escritura del mismo.

Las bases de datos en las que se trabaja frecuentemente usualmente se guardan en archivos binarios, ya que es más rápido y fácil encontrar y modificar registros. Los archivos de texto se usan generalmente para guardar configuraciones o generar informes. Sin embargo, en un binario los campos deben tener longitud fija. Si se requiere longitud variable se debe utilizar archivos de texto, o más de un archivo binario, o una combinación de ambos. Por ejemplo, para guardar una lista de pacientes de una clínica (nombre, dni, telefono, obra social) es conveniente utilizar un binario. Sin embargo, no es conveniente incluir el historial del paciente en el binario. Una solución es guardar cada historial en un archivo de texto separado, y colocar en el struct que se guarda en el binario con los datos del paciente, el nombre de su archivo de historial si es necesario (podría ser por ejemplo, su dni mas la extensión ".txt", en cuyo caso no hace falta guardarlo).

Otra aspecto importante a considerar es si la base de datos estará en memoria, o se trabajará directamente desde el archivo. Cuando son bases de tamaños razonables sobre las cuales hay que hacer muchas operaciones frecuentes, conviene tenerlas en memoria (por ejemplo en un vector stl) para que su acceso sea más rápido y más fácil. Entonces, el programa debe leer la base sólo una vez al inicio y luego trabajará con la información del vector. La unica contra es que si la información se actualiza solo en memoria y el archivo se escribe una vez al finalizar el programa, se puede perder información (si se corta la luz, el programa falla, etc) por lo que es conveniente reescribir el archivo volcando el contenido del vector luego de una operación importante. Por otro lado, cuando la base de datos crece indefinidamente (ejemplo, historial de alquileres de un videoclub, registros de venta de un almacen, etc) puede resultar cada vez más costoso este mecanismo (más tiempo de lectura, más uso de memoria), por lo que en esos casos es aconsejable trabajar directamente sobre el archivo. Supongamos por ejemplo un videoclub. .. la lista de socios y peliculas se manejaria con un vector, porque es mas rapido trabajar en memoria que en archivos y mas facil para el programador utilizando contenedores stl (agregar, quitar, borrar, ordenar, etc). La cantidad de clientes y películas es más o menos conocida y no crece indiscriminadamente con el tiempo. Ademas, son datos que se requieren a cada rato. Por otro lado, los registros de alquileres no se cargarían en memoria, porque si se guardan todos, ese archivo va a a crecer por siempre, ya que todos los dias agregaria numerosas entradas. En ese caso cargar todo en un vector o en una lista es innecesario (puede haber años de alquileres acumulados), puede ocupar mucha memoria y es lento. Ahi se usaría el archivo directamente cuando se requiera una búsqueda. Opcionalmente, se podría llevar un control paralelo donde solo se cargue en memoria los alquielres pendientes de devolución y las películas reservadas que son los datos que se necesitan con frecuencia. En todos los casos se utilizarían archivos binarios.

Un problema importante acerca del uso de archivos binarios aparece cuando se requiere borrar un registro. Si se utiliza un vector u otro tipo de contenedor para trabajar en memoria, el problema no aparece ya que al guardar los datos se reescribe completamente el archivo. Sin embargo, si se trabaja directamente sobre el archivo hay dos problemas: cuando se elimina un item de un vector, se deben desplazar todos los items posteriores una posición hacia arriba, y este trabajo es muy lento en un archivo; el tamaño del archivo no se puede reducir, por lo que siempre queda un registro basura al final. Una solución es agregar en el struct del dato que se guarda una bandera (booleano) que indique si el dato es útil o no. Cuando se solicita borrar un dato, simplemente se le invierte la bandera, lo cual es rápido y simple. No hay necesidad de mover datos ni cambiar el tamaño del archivo. Sin embargo, a la hora de listar resultados o buscar registros, se debe tener en cuenta que los registros que tienen su bandera en falso deben ser ignorados. El único problema de este enfoque es que los registroos borrados siguen ocupando lugar. Se puede incluir en el programa una opción para "compactar" la base de datos eliminando así estos elementos innecesarios. Esta operación es lenta y requiere el uso de archivos auxiliares, pero no es necesario ejecutarla frecuentemente (a veces una vez al mes, o una vez al año, según el nivel de uso del software).

Volver al indice

¿Dónde tiene que guardar los archivos mi programa?

Los archivos que utiliza o genera un programa nunca deben guardarse en rutas fijas. Ejemplos de rutas completas son: "C:\archivos de programa\mi_super_programa\datos.txt" (¿qué pasa si windows está en inglés y la carpeta es "Program files"?, ¿o si está en otra unidad que no sea C?) ó "D:\documents and settings\Pepe\Escritorio\informe.txt" (Sólo sirve para usuarios de nombre Pepe, y para versiones de Windows que usen "Documents and Settings"). Lo correcto es poner rutas relativas, o pedirle al sistema que nos indique la ruta completa.
Volver al indice

¿Dónde encuentro ayuda y referencia sobre el C++ estándar?

Mi pagina de cabecera para cualquier cosa relacionada a las bibliotecas estandar es http://www.cplusplus.com/reference/.

En http://en.cppreference.com/w/cpp hay también una referencia bastante completa, que incluye además los elementos del la última versión del lenguaje (marcados con C++11, como expresiones regulares, funciones lambda, manejo de hilos, etc). Lo interesante de esta segunda alternativa es que se puede descargar al disco para explorar más tarde sin conexión a internet desde este link

Volver al indice

¿Cómo utilizo las clases que ya tengo diseñadas en un nuevo proyecto?

Supongamos que tenemos una clase ya desarrollada en los archivos miclase.h y miclase.cpp y queremos utilizarla en un nuevo proyecto. Para utilizar esta clase se requiere: Si sólo realizamos el include, al compilar el proyecto se podrá compilar correctamente el objeto del cpp cliente, pero no se podrá enlazar el ejecutable (el error será del tipo undefined reference to... en gcc). En el ejecutable se debe incluir el código objeto de todos los cpps (los de las clases previamente desarrollada y los del programa cliente). Para esto, hay que asociar al proyecto los archivos de las clases. No alcanza con copiar el .cpp y el .h a la carpeta del proyecto (aunque es conveniente hacerlo de todos modos para que todos los archivos de un proyecto estén en la carpeta del mismo), sino que hay que indicarle al IDE que debe tenerlo en cuenta.

Por ejemplo, si se utiliza un proyecto en ZinjaI, hay que abrir los archivos .cpp .h (tenien abierto en ZinjaI el proyecto). Al realizar esta acción (menu Archivo->Abrir... y seleccionar los archivos) ZinjaI preguntará si se desea agregar estos archivos al proyecto. Para ver que un archivo está efectivamente en el proyecto podemos observar el árbol de proyecto (ubicado en un panel contra el margen izquierdo). Notar que si estamos utilizando ZinjaI pero no creamos un proyecto no podremos hacerlo; para crear un proyecto hay que ir al menú Archivo y seleccionar Nuevo Proyecto. Si se utiliza por ejemplo Borland Builder 6, hay que ir al Project Manager (desde el menú View), hacer click con el botón derecho sobre el ejecutable (por ejemplo Project1.exe) y seleccionar el comando Add del menú contextual.

Volver al indice

¿Cómo utilizar una misma instancia de cierta clase en todo el programa?

Es muy común que un programa orientado a objetos tengamos una o unas pocas clases que controlan todo el modelo de datos. Por ejemplo, en un software de gestión de bibliteca, podemos tener las clases libro, socio, y prestamo para representar un libro, un socio, o un prestamo; pero también las clases como AdminLibre, AdminSocios, AdminAlquileres, para gestionar las listas de libros, socios, y alquieleres respectivamente. También puede ser necesario una clase aún más global, llamada por ejemplo Bibliteca, que gestione los tres administradores y algunos detalles más. Para que nuestra aplicación sea consistente es muy probable que deba interactuar siempre con la misma instancia de Biblioteca (o las mismas tres instancias de Admin*). Por ejemplo, en una aplicación con interfaz de ventanas, todas las ventanas (probablemente implementadas en .cpp individuales) deben operar sobre la misma base de datos.

La forma más rápida de hacerlo es declarar un puntero global, que se pueda "ver" desde todo el programa. Para esto tenemos que declarar el puntero en algún archivo .cpp de la forma convencional (Biblioteca *la_biblioteca=NULL;), fuera de cualquier función; y además declararlo con la palabra clave "extern" en algún .h (extern Biblioteca *la_biblioteca). Una declaración sin extern es obligatoria en alguna parte del programa, pero debe ser en un archivo .cpp, ya que si lo hacemos en un .h e incluimos ese .h en más de un lugar (por ejemplo en dos .cpp), tendremos en realidad dos punteros. La declaración en el .h es necesaria porque de otra forma los demás .cpp no verían el puntero. Entonces, la declaración con extern en el .h le avisa al compilador que ese puntero existe, y que está definido en otro lado, que no tiene que volver a reservar memoria cada vez que algún .cpp lo incluye. Queda como responsabilidad del programador asegurarse que solo se asigne una vez una dirección de una instancia real a ese puntero (la_biblioteca = new Biblioteca(...)), y que esa asignación se realice antes de que el puntero sea requerido para operar sobre sus datos.

Una mejor alternativa desde el punto de vista del diseño es utilizar el patrón singleton. Este patrón está diseñado para garantizar que existe solo una instancia de una dada clase, y que esta será creada e inicializada antes de que algún método o función cliente la utilice. Para ello, el patrón consiste en definir un único constructor privado para que nadie pueda construir un objeto de este tipo desde fuera de la clase. La clase presenta como público un método estático que retorna el puntero a la única instancia, la cual se almacena en un atributo privado también estático. Ese método se encarga de construirla la primera vez que se lo invoque, y de retornar siempre la misma. Supongamos que queremos extender la clase Biblioteca para obtener una versión singleton, podemos hacerlo por herencia como sigue:

   class BibliotecaSingleton:public Biblioteca {
   private:
      static Singleton *instancia; // puntero a la unica instancia
      BibliotecaSingleton():Biblioteca(...){}; // constructor privado
   public:
      // Método para crear/obtener la única instancia de la clase
     static BibliotecaSingleton *ObtenerInstancia() {
         if (!instancia) instancia=new Singleton(); // si no existe la instancia, se crea
         return instancia;
   }
   };
   
   Singleton *Singleton::instancia=NULL; // esto va en el .cpp

A la hora de utilizar la clase, se obtiene el puntero mediante este método:
BibliotecaSingleton *la_biblioteca=BibliotecaSingleton::ObtenerInstancia();.

Volver al indice

¿Cómo uso una biblioteca externa?

Para usar una biblioteca externa en un programa C/C++ se necesitan generalmente dos elementos: las cabeceras y los objetos. Las cabeceras son los archivos .h, los que vamos a invocar desde nuestro código con #include, que contienen un índice de lo que puede hacer una biblioteca (interfase). Los objetos son los binarios que contienen el código de máquina de cada función o método de la biblioteca. Estos se generan a partir de los fuentes completos, así que alternativamente una biblioteca puede proporcionarnos los fuentes en lugar de los objetos y será nuestra tarea compilarlos. Como excepción, algunas bibliotecas sólo constan de archivos cabecera y contienen todas las implementaciones en estos archivos (bibliotecas muy simples, o basadas puramente en templates. En el caso general, debemos obtener cabeceras y objetos.

Para utilizarla debemos indicarle al compilador que utilice ambas partes. Al compilar los objetos de nuestro programa cliente que hacen uso de la biblioteca el compilador necesitará encontrar las cabeceras para responder a los #include que coloquemos, por lo que debemos indicarle donde buscar estas cabeceras. Al enlazar el ejecutable el compilador necesitará los binarios de las bibliotecas para completar el proceso, por lo que debemos indicarle donde se encuentran y cuales son. Eventualmente, algunas bibliotecas requieren otros parámetros adicionales en la compilación (frecuentemente definición de constantes de preprocesador). Todo esto debe indicarse en el IDE que utilicemos al configurar el proyecto para que pueda generar el ejecutable con éxito. Por ejemplo, en ZinjaI, si vamos al cuadro de configuración de proyecto (menú Ejecutar->Opciones), encontramos en la pestaña Compilación campos para colocar la carpeta donde debe buscar las cabeceras y la lista de constantes a definir, y en la pestaña Enlazado campos para colocar la carpeta donde buscar los objetos y la lista de bibliotecas a utilizar (cuales de todos los objetos disponibles). Si no completamos bien la parte de compilación el compilador dirá que no encuentra los archivos de cabecera y que las clases o funciones de la biblioteca no están declaradas; mientras que si no completamos bien la parte de enlazado tendremos errores del tipo
undefined reference to....

Para saber cuales son los argumentos adicionales para cada compilador (por ejemplo, macros a definir) hay que leer la documentación de cada biblioteca, o buscar una plantilla de proyecto para nuestro IDE. Sin embargo, en los sistemas GNU/Linux la mayoría de las bibliotecas utilizan un sistema común que nos permite obtener los parametros para la versión que hemos instalado en nuestro sistema. Para ello utilizamos el comando pkg-config y como argumento el nombre de la biblioteca (en algunas bibliotecas se reemplaza por un comando propio como wx-config para wxWidgets). El otro argumento necesario es el tipo de parametros que necesitamos: --cflags y/o --cppflags muestran los parámetros necesarios para compilar un objeto que utiliza la biblioteca; --libs muestra los parámetros necesarios para enlazar un programa que utiliza la biblioteca. Por ejemplo, para utilizar la biblioteca GTK+ 2.0 en GNU/Linux, los comandos "pkg-config gtk+-2.0 --cflags" y "pkg-config gtk+-2.0 --libs" muestran la lista de argumentos para compilar y enlazar respectivamente. En ZinjaI, por ejemplo, las plantillas de proyectos wxWidgets utilizan este sistema en las configuraciones para GNU/Linux (en las opciones del proyecto la llamada a wx-config aparece entre acentos, esto significa no debe insertarse el texto literal, sino que debe reemplazarse por la salida de ejecutar dicho comando), mientras que en las configuraciones para Windows estan ingresadas una a una las bibliotecas y las rutas adicionales en forma fija.

Puede encontrar más ayuda sobre como configurar el uso de bibliotecas externas en ZinjaI aquí. En enlace incluye una guía paso a paso y un resúmen de los errores más comunes y sus causas.

Volver al indice

Quiero hacer un juego ¿Qué puedo usar?

Depende mucho del tipo de juego. Si el juego no requiere animaciones ni respuestas en tiempo real (ajedrez, juegos de cartas, de tablero, ahorcado, etc.) se puede utilizar los mismos toolkits gráficos que utilizamos para otras aplicaciones de escritorio (wxWidgets, QT, GTK+, Borland VCL, etc.). Sin embargo, si el juego requiere un mayor despliegue gráfico (animaciones, sprites, transiciones, etc.) se necesita otro tipo de bibliotecas. Es decir, bibliotecas orientadas al uso de recursos multimedia. Utilizar primeras las primeras es más fácil porque se encargan de gestionar los eventos, y presentan todo tipo de controles y componentes (cuadros de texto, de mensaje, diálogos de selección de archivo, etc). Utilizando una biblioteca del segundo grupo generalmente debemos hacernos cargo del bucle de eventos (un bucle que debe ejecutarse contínuamente preguntando en cada iteración por los posibles eventos para llamar a las funciones o métodos que correspondan), y no disponemos de controles básicos como cuadros de ingreso de texto (hay que programar esto desde un nivel mucho más bajo). Sin embargo, como ventaja resultan mucho más eficientes y permiten el manejo de gráficos de una forma más natural. Dentro de las cientos de posibles candidatas de este segundo grupo, recomiendo empezar por Simple Fast Multimedia Library, ya que presenta una interfaz orientada a objetos muy simple y facil de utilizar, que permite gestionar gráficos, sonido y entradas de teclado, mouse y joystick eficientemente.

Esta biblioteca se puede utilizar fácilmente en un proyecto en ZinjaI descargando el complemento desde esta dirección.

Volver al indice

Quiero hacer una aplicación que utilice la red ¿Qué necesito?

Se necesitan dos cosas: Los sockets son digamos que algo asi como el componente de software que representa una conexión por red tcp/ip o udp/ip... Se puede trabajar directamente con la api del sistema operativo (en cuyo caso se tiene que implementar manualmente un bucle de eventos, es decir preguntar a cada rato si llego un mensaje nuevo), o usar una bilbioteca y entonces es mas simple porque recibir datos en un sockets es un evento más (como hacer click en un botón o cerrar una ventana). Para trabajar de la primer forma, como ejemplo se pueden analizar/reutilizar los archivos zockets.h y zokcets.cpp de los fuentes de PSeInt. En ellos hay funciones muy muy simples para inicializar, enviar y recibir datos, que funcionan en windows y en linux sin utilizar ninguna biblioteca externa además de las bibliotecas de sockets del propio sistema operativo. Para ejemplo de lo segundo, depende de la biblioteca que se utiliza (en google se pueden encontrar miles). Para ejemplo de sockets en wxWidgets, por ejemplo, en los fuentes de la bibliotecas (los que aparecen en la categoría "Source Archives") se encuentra una carpeta samples con ejemplos simples y basicos de todos los controles. Hay uno de sockets utilizando las clase wxSocketClient (socket del programa que realiza la llamada) y wxSocketServer (socket del programa que espera y atiende las llamadas).

El tema del protocolo es un problema de la lógica del programa, es pensar qué mensaje se envía, cómo se reconoce cuando se recibe, qué estructura tiene, qué significa, etc... Como cuando se guardan datos en un archivo, que se decide qué campos van y en qué forma (orden, tamaño, cantidad); solo que en las comunicaciones los mensajes a un socket abierto pueden llegar en cualquier momento, o a veces por partes (en varios enviós si es muy grande), entonces es un poco más complicado.

Volver al indice

¿Qué pongo en el instalador?

Hay que poner todo lo que sea necesario para que el programa funcione:
  1. Ejecutable (fundamental) y dependencias (ver mas abajo)
  2. Imagenes, iconos, textos, archivos de ayuda, lo que sea que tu programa cargue
  3. Bases de dato en blanco o de ejemplo si las necesita
Respecto al ejecutable:

En todos los IDEs se puede elegir entre una configuracion Debug y una Release (en ZinjaI, menu Ejecutar->Opciones, y elegir en el combo de arriba a la izquierda; en Builder, menu Project->Options y en la parte inferior de la pestaña Compiler hay dos botones: Debug y Full Release). El ejecutable que se genera con la configuración Debug no esta optimizado (trucos que usa el compilador para que funcione más rapido) y tiene información de depuracion (que no hace falta si no se va a utilizar un depurarador). Entonces, para el usuario final, hay que compilar con Release, ya que el ejecutable va a ser mas chico y va a andar mas rapido. Los demas temporales que se encuentran dentro de las carpetas release/debug (archivos .o) no van en el instalador, ya que son solo archivos intermedios que luego pasan a formar parte del ejecutable.

Otro tema de interés es el uso de bilibotecas externas. Si se usa enalazado dinamico, el programa necesita las bibliotecas (archivos .dll en Windows, .so en GNU/Linux), y entonces el instalador las tiene que copiar (en Windows, en la carpeta del tu programa o en System32). Si se usa por ejemplo alguno de los productos de Borland, por defecto se enlaza dinámicamente asi que hay que copiar dlls (y bpls?) o cambiar las opciones para que compile estáticamente (poner las bibliotecas dentro del todo en el exe). Si se usa ZinjaI y wxWidgets no se necesita nada porque la compilación de wxWidgets que incluye ZinjaI es estática. Si se utiliza Builder, en las opciones de proyecto (menu Project->Options) hay que desactivar la opción "Use dynamic RTL" de la pestaña Linker y la opción "Build with runtime packages" de la pestaña Packages.

Otra explicación similar más reciente

Volver al indice

¿Cómo convierto entre cadenas (char*,string,...) y números (int,float,...)?

Entre las funciones del C++ estándar encontramos atoi y atof (biblioteca cstdlib) para convertir desde cadenas (cstring) a enteros y flotantes respectivamente. Sin embargo, las funciones que hacen lo contrario (itoa, ftoa) no existen en el estándar. Algunos compiladores las implementan igual, pero si queremos escribir un código correcto y portable no debemos fiarnos de ello. Para realizar esta conversión de forma fácil, hay tres maneras:

  • Utilizar stringstreams: los stringstream son objetos que tienen las características de los flujos, entre ellas la posibilidad de utilizar los operadores << y >>, pero su contenido no se guarda en un archivo ni se imprime en la consola como los flujos que utilizamos más habitualmente (cin,cout,ifstream,ofstream), sino que se guarda en memoria, y se puede extraer como un objeto de tipo string. Entonces, una forma fácil para colocar un número en un string, es crear un stringstream vacio, concatenarle el int/float/double con << y luego extraer el string. Ejemplo: int x=5; stringstream ss; string res; ss<<5; res=ss.str(); // res finalmente contiene el entero x convertido a string

  • Utilizar sprintf: la función sprintf escribe en un cstring de la misma forma que lo hace printf en la consola (el cout de C). Los argumentos son, la cadena donde se escribe, el formato de la escritura y luego las variables. Para escribir un entero el formato es "%i", mientras que para escribir un flotante el formato es "%f", o "%.2f". En el segundo caso, el 2 indica que se debe escribir con 2 decimales. La ventaja de este método es que podemos agregar texto constante en el formato (por ejemplo el postfijo "$" para montos de dinero) o escribir más de una variable en la misma llamada. Ejemplo: char c[20]; double x=2.5; sprintf(c,"%.2f $",x); // escribe "2.50 $"

  • Utilizar una biblioteca: no se justifica utilizar una biblioteca sólo para la conversión, pero si de todas formas estamos utilizando alguna biblioteca (por ejemplo para la interfaz gráfica) y ésta contiene funciones o clases para simplificar esta tares podemos aprovecharlas.
    Volver al indice

    Mi programa explota, ¿Y ahora qué hago?

    Respuesta corta: apretar F5 (usar el depurador).

    Si el programa compila correctamente pero en algún momento de la ejecución se detiene de forma anormal (vemos segfault o violación de segmento en Linux, o el clasico cartel de error en Windows donde apretamos "No Enviar" sin leer, o cualquier mensaje de ese estilo) lo primero que se suele hacer es correr nuevamente el programa pero utilizando un depurador (en ZinjaI, presionando la tecla F5). Luego intentamos que el programa vuelva a detenerse repitiendo la secuencia de pasos que causa el problema. Cuando el programa se interrumpa por el error el depurador debería mostrarnos el trazado inverso (qué funciones estaban en curso cuando ocurrió el error) y habilitarnos la tabla de inspecciones para ver qué sucedió (si no está familiarizado con los conceptos básicos de depuración puede comenzar con esta guia o los tutoriales de su IDE). Lo primero que hay que buscar son índices fuera de rango (por ejemplo un índice de un arreglo de 20 elementos que vale 20 o más, o negativo), punteros con direcciones erróneas (NULL o muy bajas, incluso en el puntero this), divisiones donde el divisor pueda ser cero, etc. Hay que buscar en todos los niveles del trazado inverso que hayamos implementado. Para que esta búsqueda sea más facil siempre conviene inicializar todas las variables que no se usan inmediatamente (por ejemplo, comenzar con todos los punteros en NULL). Otra pista importante puede venir desde los warnings del compilador. Es conveniente intentar que nuestro código no genere warnings de ningún tipo ya que aunque la mayoría son inofensivos, si nos acostumbramos a ver numerosos warnings en cada compilación dejamos de prestar atención a los mismos y pasamos por alto los importantes. Finalmente, si no se encuentra el problema se recurre a la ejecución paso a paso. Se puede colocar un punto de interrupción antes de que surja el problema (por ejemplo, en la primer línea del evento donde se produce) y utilizar la ejecución paso a paso para acotar el momento. Esto también es útil cuando el programa explota de tal forma que el depurador no es capaz de obtener el trazado inverso. La habilidad para depurar es una de las habilidades más importantes y útiles para un programador, y sólo se desarrolla con la práctica.

    Volver al indice

    ¿Cómo obtengo la hora y/o la fecha del reloj del sistema?

    Entre las funciones estándar de C++ tenemos time. Esta función devuelve un valor de tipo time_t, que en la mayoría de los compiladores es un entero con la cantidad de segundos que transcurrieron desde el 1ero de enero de 1970 a las 0:00. En este valor están codificadas fecha y hora. La ventaja de esta codificación radica en que así se pueden sumar o restar (las diferencias dan en segundos). Para extraer dia, mes, horas, minutos de una variable de tipo time_t se utilizan las funciones gmtime y localtime, que convierten de un tipo time_t a un tipo tm (devuelve un puntero a una variable de tipo tm). La diferencia entre una y otra es que una utiliza la hora local mientras que otra la hora UTC. El tipo tm es una estructura que contiene los campos dia del mes, dia de la semana, mes, año, horas, minutos, segundos, etc por separado. Solo hay que tener en cuenta cómo interpretar cada campo. Por ejemplo, el día del mes va de 1 a 31, pero el mes va de 0 a 11 (entonces el 1 es febrero), y el año comienza a contar desde 1900, entonces 111 significa 2011, etc. Ejemplo de uso:
         time_t t1 = time(NULL);
         tm *ptm2 = localtime( &t1 );
         cout << "Fecha actual (local): " << ptm2->tm_mday << "/" << ptm2->tm_mon+1 << "/" << ptm2->tm_year+1900 << endl;
         cout << "Hora actual (local): " << ptm2->tm_hour << ":" << ptm2->tm_min << ":" << ptm2->tm_sec << endl;
    Todas estas funciones y tipos de dato estan definidos en la cabecera ctime.h.

    En los casos en los que el proyecto utilice alguna biblioteca o toolkit adicional, se pueden utilizar clases más simples para esta tarea que provengan de estas bibliotecas, aunque generalmente solo es recomendable hacerlo dentro de las clases asociadas directamente a la interfaz, dejando el nucleo del programa independiente de la bilbioteca si este es el caso. Un ejemplo de esto sería la clase wxDateTime, que tiene métodos para asignas fecha y hora actual, y para obtener cada una de las partes por separado y en diferentes formatos o escalas.

    Volver al indice

    ¿Qué tengo que tener en cuenta para que mi programa funcione en Windows y GNU/Linux?


    Es posible escribir un programa que se pueda compilar en varios sistemas operativos (so) sin (o casi sin) tenes que hacer ningún cambio, pero para eso hay que tener ciertas precauciones:
    Volver al indice

    ¿Cuál es la diferencia entre el manual de usuario y el manual de referencia?

    El manual de usuario de una aplicación es el manual que un usuario nuevo (que nunca utilizó la aplicación) leería para aprender a utilizarla. Usualmente incluye guías paso a paso con capturas de pantalla que explican cómo realizar las operaciones básicas (instalar el software, cargar datos, buscar datos, etc). Es conveniente organizarlo por funciones (qué se quiere hacer) y no por ventanas (ya que a priori un usuario no sabe a través de qué ventana podrá realizar determinada operación.

    El manual de referencia es el manual que consulta un usuario que ya conoces los principios básicos del programa. Se utiliza para consultar cosas más específicas sin tener que leer toda la guía del manual de usuario. Buscar en él debe ser fácil y rápido, por lo que suele presentarse en forma de glosario ordenado alfabéticamente. Los items que se incluyen en el mismo suelen ser campos o controles (obligadamente los relacionados a entrada de datos, y opcionalmente los demás) y la información que presenta es una breve descripción del campo o control, y si es para entrada de datos qué restricciones presenta (longitud máxima, si acepta solo numeros, valor máximo o mínimo, etc), o cualquier otro detalle que se considere importante.

    Debe tenerse presente que ambos manuales están destinados al usuario final de la aplicación, que en general no tendrá conocimientos de programación, por lo que las descripciones no deben incluir vocabulario propio del desarrollo (por ejemplo, no refererirse a un dato como float o double, sino como número real).

    Volver al indice

    ¿Cómo hago para que mi aplicación abra el manual (u otro documento)?

    Para abrir documentos o cualquier tipo de archivo no ejecutable (archivos pdf, doc, html, etc), se requiere de un programa. Este programa puede no ser el mismo en distintas computadoras o sistemas operativos, y aún siendo el mismo puede no estar instalado en el mismo lugar. Es por esto que no conviene utilizar código como "system("C:\archivos de programa\acrobat\reader\acroread.exe manual.pdf")" para abrir por ejemplo un manual. Cada sistema operativo tiene sus mecanismos para asociar un tipo de archivo con una aplicación elegida por el usuario para abrirlo. Cuando nuestro programa necesita abrir un documento debe solicitar al sistema operativo que lo haga para que utilice sus mecanismos para lanzar la aplicación adecuada según el tipo de archivo (en caso de no ser posible o conveniente, nuestro programa debería permitir configurar qué aplicación externa utilizar). A continuación se muestra cómo hacerlo en sistemas Windows y GNU/Linux.
    Es importante notar que en ambos casos el sistema debe tener una aplicación para abrir el tipo de documento en cuestión instalada, de lo contrario estas acciones no tendrán ningún efecto. Es recomendable usar formatos para los cuales existan aplicaciones gratuitas y livianas (utilizar pdf, y nunca doc), o que se encuentren presente por defecto en todos los sistemas operativos (html por ejemplo). Si queremos depender de aplicaciones externas debemos desarrollar en nuestra aplicación un visor de ayuda con nuestro toolkit gráfico. Por ejemplo, en wxWidgets, se incluye un control (wxHtmlWindow) para visualizar páginas HTML simples (sin uso de javascript, hojas de estilo, etc.), con el cual se puede desarrollar un visor muy básico con mínimo esfuerzo.

    Volver al indice

    ¿Dónde quedó la función main?

    Es comun cuando se utilizan bibliotecas de componentes visuales no implementar una función main. Esto se debe a que la función main en realidad la implementa la biblioteca. Este es el caso de wxWidgets. La función main ya está implementada, por lo que al utilizarla wxWidgets no podemos utilizar nuestra propia función main (de hacerlo el compilador mostrará un error del tipo "multiple definitions of..."). Cuando declaramos la Application y utilizamos la macro IMPLEMENT_APP (última linea de Application.h en la plantilla de ZinjaI) estamos diciendo cual es la clase de nuestra applicación. La función main que se encuentra dentro de wxWidgets creará una instancia de esta clase y llamará al método OnInit de la misma. Es por esto que el equivalente a la función main para una aplicación con wxWidgets es el método OnInit de la clase que hereda de wxApp. Allí debemos colocar el código de inicialización de nuestro programa (cargas bases de datos, mostrar la ventana inicial, etc.). Este método debe retornar un valor booleano. Generalmente el valor será true indicando a la función main de wxWidgets que la aplicación se inició correctamente y que debe comenzar a escuchar eventos. Si se retorna false, la aplicación finaliza inmediatamente luego de ejecutar el método OnInit.

    Volver al indice

    Dibuje mi ventana y no aparece

    Para que al iniciar una aplicación que utiliza wxWidgets el usuario vea una ventana en particular hay que:
    Nota: Es un error muy común crear una instancia a la clase base (la generada por wxFormBuilder) en lugar de la hija (la herencia hecha en ZinjaI, que debería ser donde hacemos las implementaciones que nos interesan). Verificar esto en el método OnInit si la primer ventana no aparece al ejecutar el proyecto, o si no responde a eventos que ya están programados.

    Volver al indice

    Veo mi ventana pero no hace nada cuando hago click en un boton

    Para que la ventana responda a un botón hacen falta dos cosas: El código que relaciona el evento (click en el botón) con un método, y el método en sí.

    Cuando se utiliza un diseñador visual como wxFormBuilder, el código que asocia eventos y métodos suele generarlo wxFormBuilder, por lo que sólo debemos corroborar que el método esté asignado en el diseñador (seleccionar el control y observar la pestaña Eventos del cuadro de propiedades que se muestra a la derecha de la ventana). Al implementar el método en nuestra clase heredada debemos observar que los prototipos coincidan perfectamente con el método declarado en la clase base generada por el diseñador. Esto generalmente implica que el método reciba por referencia un objeto de tipo wxCommandEvent,wxCloseEvent,wxResizeEvent,etc. según el tipo de evento generado. Otro error común en el uso de diseñadores de este tipo es crear una instancia de la clase diseñada en lugar de la clase heredada. Recordemos que al trabajar con wxFormBuilder, este no genera una clase con métodos virtuales para los eventos, de la cual debemos heredar nuestra propia clase. Es un error frecuente invocar a la clase diseñada en lugar de la clase heradada.

    Volver al indice

    ¿Dónde pongo los delete para las ventanas y controles?

    Cuando se trabaja con wxWidgets las ventanas y controles usualmente se alocan dinámicamente con el operador new, pero no se desalocan con el operador delete. Las clases de la biblioteca implementan un sistema propio para liberar la memoria que funciona de la siguiente manera: cuando queremos liberar la memoria que utiliza una ventana y destruir dicho objeto, en lugar de utilizar al operador delete se debe utilizar el método Destroy de dicho objeto; al llamar a este método wxWidgets marca el objeto para ser eliminado, pero no lo elimina inmediatamente, sino que espera a que se procesen todos los eventos pendientes y luego lo elimina. Esto tiene dos ventajas: por un lado, se evitan los problemas que podrían surgir si hay pendiente un evento de un objeto y el objeto se elimina antes que el evento se procese; por otro lado, una ventana puede eliminarse a sí misma (se puede llamar a Destroy desde un método/evento de la propia ventana). Además, al destruir un componente que es padre de otros se destruyen todos ellos. Es por esto que si bien al crear una ventana se crean uno por uno los componentes (aunque este código lo suele generar automáticamente el diseñador), para destruirla sólo se invoca al método Destroy de la ventana, pues los mecanismos internos de wxWidgets se encargaran de eliminar los componentes contenidos en dicha ventana.

    En conclusión, lo que conviene hacer para gestionar correctamente la memoria y evitar que queden cargadas ventanas que ya no se ven ni utilizan es llamar al método Destroy de la ventana. Suele ser conveniente hacerlo en el evento de cierre de la ventana (OnClose), ya que de esta forma se garantiza que siempre que la ventana deja de visualizarse se libera la memoria correspondiente. Al hacer esto, desde los demás eventos que deban eliminar la ventana (por ejemplo los botones Aceptar y Cancelar si es una cuadro de diálogo) basta con llamar el método Close, ya que este generará el evento de cierre y el evento invocará a Destroy.

    Volver al indice

    ¿Que diferencia hay entre un wxDialog y wxFrame?

    Tanto wxFrame como wxDialog heredan de wxTopLevelWindow, por lo que comparten la mayoría de las métodos. Visualmente tienen diferentes estilos por defecto (el diálogo no aparece en la barra de tareas, no tiene botón maximizar, etc), y funcionalmente la ventana es más genérica mientras que el diálogo tiene algunas pocas pero útiles funcionalidades más específicas. Usualmente la ventana principal de una aplicación será de tipo wxFrame, y las ventanas emergentes, de opciones, de mensaje, etc. serán de tipo wxDialog. Un wxDiálog es usualmente una ventana auxiliar que se muestra sobre la ventana principal, en algunos casos funcionando a la par de esta, en otros casos bloqueandola hasta su cierre. Esta es una de las principales ventajas funcionales de wxDialog. Además del método Show, para mostrarse tiene el método ShowModal. Cuando un evento muestra una ventana con el método Show, el evento continúa ejecutandose hasta finalizar y luego quedan ambas ventanas visibles (la del el evento y el nuevo cuadro de diálogo). Cuando una ventana muestra un wxDialog con ShowModal, el evento que lo hace se interrumpe hasta que el cuadro de diálogo se cierre. Es decir, el código que le sigue a la llamada no se ejecuta hasta que el diálogo no termine. Esto tiene dos ventajas: la ventana principal que deshabilitada hasta que se cierre el diálogo (lo cual a veces es deseable), y el código del evento puede incluir ahí mismo código para reaccionar a los datos que el usuario ingresa en el dialogo.

    Por ejemplo, si una ventana tiene una lista y al hacer doble click se crea una diálogo para editar un ítem de la lista, utilizando show se tienen los siguientes problemas: el diálogo debe recibir al crearse un puntero a la ventana que contienen la lista (o a la lista) para poder modificarla luego de que el usuario edite los datos y presione Aceptar; y como la ventana de la lista no se deshabilita, el usuario puede abrir varios diálogos de edición que editen el mismo ítem si no se control correctamente, lo cual genera problemas de lógica en el programa. Si se utiliza ShowModal el segundo problema se evita automáticamente ya que mientras un dialogo se muestra el usuario no puede interactuar con la lista. Además, dado que el evento del click en la lista queda a la espera de la finalización del cuadro de diálogo, el código para actualizar la lista puede incluirse en este mismo evento. Adicionalmente, el método ShowModal puede devolver un entero. Para ello el diálogo debe cerrarse mediante el método EndModal en lugar de Close, ya que EndModal permite especificar el valor de retorno. Este valor puede utilizarse por ejemplo, para saber si se debe actualizar o no la lista de la ventana principal (si el usuario cerró el cuadro de diálogo con el botón Aceptar o con Cancelar).

    Volver al indice

    ¿Por qué mis botones no se ven redondeados como en los demás programas de Windows Vista/7?

    Los programas para Windows pueden incluir un archivo xml (manifest) con cierta información sobre cómo ejecutarse (versiones bibliotecas que utiliza, niveles de seguridad, etc). Si el xml no existe, Windows muestra las ventanas y demás controles de wxWidgets sin aplicarle el "tema". Para que se vean como el resto de las aplicaciones de Windows debemos incluir este archivo e indicar en él que queremos utilizar cierta versión de la bibilioteca de Windows que dibuja estos controles. Hay dos formas de utilizar este archivo: una es colocarlo dentro del ejecutable, la otra es nombrarlo igual que el ejecutable y agrerle ".manifest" al final. Cuando Windows ejecuta el programa, primero busca dentro del ejecutable, si no lo encuentra busca en la carpeta del ejecutable. Por ejemplo, si el ejecutable es zinjai.exe, el archivo manifest sera zinjai.exe.manifest. Si se quiere colocar dentro del ejecutable, en ZinjaI se puede indicar desde la pestaña "Enlazado" del cuadro de Opciones de Ejecucion y Compilación de Proyecto (menu Ejecutar->Opciones). El contenido del archivo es el siguiente:

       <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
       <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
          <dependency>
             <dependentAssembly>
                <assemblyIdentity
                   type="win32"
                   name="Microsoft.Windows.Common-Controls"
                   version="6.0.0.0"
                   processorArchitecture="*"
                   publicKeyToken="6595b64144ccf1df"
                   language="*"
                />
             </dependentAssembly>
          </dependency>
       </assembly>

    Las plantillas de proyectos wxWidgets y wxFormBuilder de las versiones de ZinjaI posteriores a 20110610 ya incluyen este archivo en las compilaciones para Windows.

    Volver al indice

    ¿ANSI vs UNICODE?


    Volver al indice

    ¿Dónde consigo ejemplos y referencias de wxWidgets?

    La biblioteca incluye una muy completa referencia en formato html. Se puede acceder desde este enlace, descargar desde esta pagina (Current Stable Release -> Documentacion -> HTML Docs), o en ZinjaI acceder rápidamente desde el ítem "Referencia wxWidgets" del submenú "Diseñar Interfases" del menú "Herramientas" (la versión para Windows ya incluye estos documentos, en Linux hay que descargarlos y configurar la dirección en la pestaña de rutas del cuadro de preferencias).

    Para conseguir ejemplos se puede descargar los fuentes de la biblioteca (Current Stable Release -> Source Archive -> wxAll). Al descomprimir los fuentes se encuentra una carpeta "samples" que contiene ejemplos completos del uso de la mayoría de los widgets de la biblioteca. Para compilar estos ejemplos con ZinjaI pueden realizar los siguientes pasos: cerrar todos los archivos abiertos y abrir solo el .cpp del ejemplo (cada carpeta tiene un .cpp, si tuviera más de uno abrirlos todos). Crear un nuevo proyecto (Archivo->Nuevo Proyecto) en el directorio del ejemplo (seleccionar "Directorio Actual" en el asistente) con la plantilla de "wxWidgets" (no wxFormBuilder), y con los archivos abiertos (seleccionar "Utilizar los archivos abiertos" debajo de la lista de plantillas).

    Volver al indice

    ¿Cómo hago para que un control de texto (wxTextCtrl) permita ingresar sólo números o sólo letras?

    Para restringir el tipo de entrada de un wxTextCtrl puede asociarse a dicho control un objeto de tipo wxTextValidator. Este tipo de objetos se construyen especificando los caracteres permitidos y, una vez asociados al control, descartan las entrada correspondiente a cualquier caracter no permitido.

    Se puede asociar un wxTextValidator a un wxTextCtrl de dos formas: añadiendo el código necesario en algún punto de nuestro programa entre que se crea el cuadro de texto y se le da la oportunidad al usuario de utilizarlo, por ejemplo en el constructor de la ventana que lo contiene; o definiendolo en las propiedades del cuadro de texto en el diseñador wxFormBuilder.

    El siguiente código inicializa un validador especificando la constante wxFILTER_NUMERIC, la cual indica que deben permitirse únicamente caracteres numéricos, y la asocia a un control de texto:
       wxTextValidator v(wxFILTER_NUMERIC);
       miControlDeTexto->SetValidator(v);

    A partir de la asociación el control de texto descartará automáticamente toda entrada que no sea numérica.

    Para hacerlo desde el diseñador wxFormBuilder sin tener que codificar, se debe:
       1. Seleccionar el control de texto al que se le quiere aplicar la validación.
       2. Entre las propiedades del objeto, buscar la sección Propiedades y modificar:
          * validator_type: cambiar wxDefaultValidator por wxTextValidator
          * validator_style: destildar wxFILTER_NONE y seleccionar el tipo de filtro que se quiera utilizar
          * validator_variable: escribir un nuevo identificador para una variable que será atributo de la clase y que utilizará solamente el validator
          * validator_data_type: seleccionar wxString
    Los mismos pasos se pueden aplicar a muchos otros controles de entrada, variando solamente validator_data_type según el tipo de control (la ayuda que muestra el diseñador aclara para qué tipo se debe usar para cada uno).

    Volver al indice

    ¿Cómo hago para obtener una ventana que muestre los archivos y carpetas del sistema, similar a las que permiten abrir/guardar archivos en otros programas?

    Para este tipo de dialogos, depende si lo que se desea seleccionar es un archivo o un directorio:
      Para seleccionar un directorio se debe usar un objeto de la clase wxDirDialog.
      Para seleccionar uno o varios archivos, se debe utilizar un objeto de la clase wxFileDialog.
    Ambos objetos son muy similares y corresponden a diálogos, por lo cual se debe llamar a la función ShowModal() para que se muestren, y esta llamada bloquea la ventana padre hasta que se haya seleccionado un archivo/directorio. Una vez que la ventana de selección sea cerrada y el flujo del programa retorne a la ventana inicial, existen varias funciones que devuelven información sobre el ítem seleccionado.

    El siguiente código, utilizado dentro de un método de alguna ventana, inicializa y muestra un diálogo de selección de archivos:
       wxFileDialog fd(this, "Elija un archivo de texto", "C:\\", "", "*.txt");
       // el 3er y cuarto parametro son el directorio inicial y el nombre de archivo inicial respectivamente
       // el 5 parametro indica que se puede seleccionar cualquier archivo (* es como un comodin)
       // siempre y cuando termine en ".txt"
       if (fd.ShowModal()==wxID_OK) { // se compara con wxID_OK para asegurarnos de que el usuario eligió un archivo y no presionó "Cancelar"
          wxString archivo = fd.GetPath(); // devuelve el archivo seleccionado con la ruta y todo
          /** hacer algo con el archivo **/
       }

    Se puede agregar un argumento más en el constructor que consiste en una o más banderas de las indicadas en la ayuda de la clase (wxFD_* o wxDD_*), para decir si es un cuadro de abrir o guardar, si permite seleccionar más de un archivo, si permite escribir un nombre de archivo que no exista, si debe preguntar al aceptar antes de sobreescribir un archivo existente, etc.

    Volver al indice

    ¿Cómo crear mis propios controles para reutilizar?


    Volver al indice

    ¿Cómo hacer un arreglo de controles (muchos botones, cuadros de texto, etc iguales) sin tener que escribir uno por uno?


    Volver al indice

    ¿Cómo le pongo un ícono a mi aplicación?

    Primero hay que distinguir que colocarle un ícono al ejecutable (solo Windows), que es el ícono que vemos en el explorador de windows y generalmente en los accesos directos; es diferente de colocarle un ícono a una ventana (aunque podría ser el mismo).

    Para colocarle un ícono al ejecutable, hay que generar y compilar un archivo de recursos. Si utiliza ZinjaI, esto se hace automáticamente definiendo el archivo de icono (de extensión .ico) en la pestaña Enlazado del cuadro de Opciones de Compilación (menu Ejecutar->Opciones).

    Para colocar un ícono en una ventana se utiliza el método SetIcon, presente en las clases wxFrame y wxDialog. Este método recibe un objeto de tipo wxIcon. Estos objetos se pueden construir a partir de un archivo de ícono simplemente pasando la ruta del archivo en el constructor. Usualmente este código se inserta en el constructor de la ventana (es necesario agregar el include de wx/icon.h). Ejemplo: SetIcon(wxIcon("archivo.ico"));

    Alternativamente en lugar de utilizar una archivo de ícono se puede generar un archivo xpm. Un archivo xpm es una imágen codificada como código c++ (declaración de una estructura con los datos de la imágen). Podemos hacer un #include del archivo xpm y pasarle el nombre de la estructura al constructor de wxIcon. La ventaja de este método radica en que el ícono se compilará dentro del ejecutable, mientras que con el método anterior siempre tendremos que copiar el archivo de ícono además del archivo ejecutable al copiar el programa. Para generar archivos xpm o convertir de ico/png/etc a xpm podemos usar cualquier editor de imágenes que soporte este formato (por ejemplo, GIMP).

    Advertencia: en muchos sistemas Windows los íconos sólo se muestran correctamente en las ventanas si tienen un tamaño de 32x32. De lo contrario sólo se muestran en la barra de tareas.

    Volver al indice

    ¿Cómo hago para que mi aplicación muestre un icono a la derecha de la barra de tareas, al lado del reloj?

    Muchas aplicaciones pensadas para permancer abiertas muestran un ícono en la zona de notificaciones (en la barra de tareas, a la derecha, junto al reloj), que usualmente permite mostrar la ventana principal de la aplicación, o presenta un menú contextual con opciones. Para obtener estas funcionalidades con wxWidgets lo que hay que hacer es generar una clase que herede de wxTaskBarIcon. Esta nueva clase debe utilizar el método SetIcon para definir la imágen del icono. Hay dos formas de asociar eventos y métodos en una clase wxWindow: con el método Connect, o con una tabla de eventos generada por macros. En este ejemplo vamos a utilizar la segunda. Para ello, hay que agregar dentro de la clase un linea "DECLARE_EVENT_TABLE();", y luego, en el cpp, armar la tabla con las macros "BEGIN_EVENT_TABLE(nombre_de_la_clase,wxTaskBarIcon) y END_EVENT_TABLE()". Entre medio de estas dos macros, se colocan macros que comienzan con EVT_ y que asocian eventos y métodos. Sin embargo, para el caso en que se quiera desplegar un menú contextual con el click derecho, la clase base incluye un método virtual CreatePopupMenu que puede ser reimplementado en la clase heredada y que se llama automáticamente sin necesidad de las macros. Este método debe crear un wxMenu dinámicamente y retornarlo, la biblioteca se encargará de liberar la memoria cuando luego de mostrarlo.

    En el siguiente ejemplo, se crea una clase mxTaskBarIcon que toma la imagen de un icono xpm y que presenta un menu con tres opciones.

    En el archivo mxTaskBarIcon.h:
       #ifndef MXTASKBARICON_H
       #define MXTASKBARICON_H
       #include <wx/taskbar.h>
       // es necesario numeros para identificar las opciones del menu, que deben ser mayores a wxID_HIGHEST
       enum { ID_OPCION_1=wxID_HIGHEST+1, ID_OPCION_2, ID_OPCION_3 };
       // clase heredada
       class IconoEnLaBarra : public wxTaskBarIcon {
       public:
          mxTaskBarIcon();
          // metodo que construye el menu (click derecho)
          wxMenu *CreatePopupMenu();
          // metodos para las opciones del menu
          void OnOpcion1(wxCommandEvent &event);
          void OnOpcion2(wxCommandEvent &event);
          void OnOpcion3(wxCommandEvent &event);
          // metodo para el click izquierdo
          void OnClickDerecho(wxTaskBarIconEvent &evt);
          // para la tabla de eventos
          DECLARE_EVENT_TABLE();
       };
       #endif

    En el archivo mxTaskBarIcon.cpp:
       #include <wx/icon.h>
       #include <wx/menu.h>
       #include "mxTaskBarIcon.h"
       #include "icono.xpm"
       // tabla de eventos
       BEGIN_EVENT_TABLE(IconoEnLaBarra,wxTaskBarIcon)
          EVT_TASKBAR_LEFT_DOWN(IconoEnLaBarra::OnClickDerecho)
          EVT_MENU(ID_OPCION_1,IconoEnLaBarra::OnOpcion1)
          EVT_MENU(ID_OPCION_2,IconoEnLaBarra::OnOpcion2)
          EVT_MENU(ID_OPCION_3,IconoEnLaBarra::OnOpcion3)
       END_EVENT_TABLE()
       // constructor que asigna la imagen (si no es xpm, sacar el include y pasar el nombre de archivo como string al ctor. de wxIcon)
       mxTaskBarIcon::mxTaskBarIcon() { SetIcon(wxIcon(icono_xpm),"Tooltip del icono"); }
       // metodo que crea el menu
       wxMenu * mxTaskBarIcon::CreatePopupMenu ( ) {
          wxMenu *menu = new wxMenu();
          menu->Append(ID_OPCION_1,"Texto opcion 1...");
          menu->Append(ID_OPCION_2,"Texto opcion 2...");
          menu->Append(ID_OPCION_3,"Texto opcion 3...");
          return menu;
       }
       // metodos que se ejecutan cuando el usuario elije una opcion del menu
       void mxTaskBarIcon::OnOpcion1 (wxCommandEvent &event) { ... }
       void mxTaskBarIcon::OnOpcion2 (wxCommandEvent &event) { ... }
       void mxTaskBarIcon::OnOpcion3 (wxCommandEvent &event) { ... }
       // metodo para que tambien muestre el menu con el click izquierdo
       void mxTaskBarIcon::OnClickDerecho(wxTaskBarIconEvent &event) { PopupMenu(CreatePopupMenu()); }

    Volver al indice

    ¿Cómo agrego atajos de teclado para mi aplicación?

    Hay dos formas de asociar una combinación de teclas a un evento:

    Volver al indice

    ¿Cómo uso temporizadores (timers)?

    Un temporizador es un control que se encarga de generar un evento cada cierto período de tiempo. El control (wxTimer) no tiene representación visual, por lo que inicialmente puede no estar asociado a ninguna ventana. Para poder recibir sus eventos debemos asociarlo a una ventana. Esto se puede hacer mediante el constructor, que recibe el manejador de eventos de la ventana, el cual se obtiene con el método GetEventHandler. A continuación debemos asociar un método de la ventana al evento del timer utilizando el método Connect de la ventana. Finalmente, con el método Start de wxTimer, ponemos en marcha el temporizador. Start recibe dos parámetros, el tiempo (en milisegundos) y una bandera que indica si el evento debe dispararse una vez y detener el timer, o dispararse periodicamente. El método que recibe el evento debe tener como argumento un objeto de tipo wxTimerEvent por referencia.

    El siguiente código, colocado en el constructor de una ventana (VentanaPrincipal), crea un timer, asocia su evento a un método de la misma (OnTimer), y lo inicializa para generar el evento cada 1 segundo:
         wxTimer *timer = new wxTimer(GetEventHandler());
         Connect(wxEVT_TIMER,wxTimerEventHandler(VentanaPrincipal::OnTimer),NULL,this);
         timer->Start(1000,false);


    Volver al indice

    ¿Cómo compilo wxWidgets en GNU/Linux?

    Sigue estos pasos si el gestor de paquetes de tu distribución no tiene la versión de wxWidgets que necesistas (por ejemplo, en los primeros proyectos es más simple utilizar la versión ANSI pero los repositorios suelen tener la versión UNICODE):
    1. Descargar los fuentes desde http://www.wxwidgets.org/downloads/, seleccionando "Current Stable Release" y la versión wxAll o wxGTK.
    2. Descomprimir los fuentes descargados: por ejemplo, si el archivo es wxWidgets-2.8.12.tgz, desde una consola se puede hacer con "tar -xzvf wxWidgets-2.8.12.tgz". Si la extensión es .tar.bz2, cambiar "-xzvf" por "-xjvf". Si es zip, utilizar "unzip -x wxWidgets-2.8.12.zip".
    3. Abrir una consola y entrar en la carpeta descomprimida (con el comando cd).
    4. Ejecutar el script de configuración con el comando "./configure --enable-ansi --disable-unicode" (si se quiere la versión unicode omitir los 2 parámetros). Se configura para instalar en el directorio "/usr/local", para cambiar, por ejemplo a "/opt/wx" agregar "--prefix=/opt/wx". Si todo va bien y el sistema tiene el compilador y las dependencias instaladas, el script mostrará un resúmen de la configuración elegida. Si hay algún error, probablemente se deba a la falta de una biblioteca e indique cual. Hay que intentar instalar la versión de desarrollo de la misma (que termina en -dev o -devel) con el gestor de paquetes de la distribución. Si en este paso aparece algún error, se deberá a que el sistema no tiene instaladas algunas dependencias necesarias. Usualmente, el paquete faltante será "gtk+-2.0-dev", así que lo que se debe hacer es instalarlo con el gestor de paquetes de la distribución y probar nuevamente ejecutar el "configure....".
    5. Compilar la biblioteca con el comando "make", o alternativamente con "make -j 4" para compilar utilizando 4 núcleos en pcs con procesadores modernos.
    6. Instalar la biblioteca con el comando "sudo make install"
    7. Listo. Se puede intentar ejecutar el comando "wx-config --unicode=no --libs" para ver si está bien instalada (muestra una lista de archivos precedidos cada uno por "-l") o no (muestra algún mensaje de error).

    Volver al indice