|
| |
Programación visual con C++
Resumen: ¿Que es C++?. ¿Que es la Programación Orientada a Objetos?. El modelo de programación Windows. El concepto de proyecto (workspace). Posibilidades de programación. Filosofía de trabajo con la MFC. Las clases básicas para interfaz de usuario. Clases básicas de propósito general. Gestión de mensajes de comandos del menú. Objetos capaces de gestionar mensajes de comando de menú. Clases orientadas a acceso a base de datos.
Publicación enviada por Coria David Marcelo
1. INTRODUCCIÓN - ¿ QUE ES VISUAL C++ ?
2. CONCEPTOS PRELIMINARES
2.1. ¿ Que es C ++ ?
2.2. ¿ Que es la Programación Orientada a Objetos ?
2.3. El modelo de programación Windows
3. EL ENTORNO DE DESARROLLO - INTRODUCCIÓN
3.1. El concepto de proyecto (workspace)
3.2. Posibilidades de programación
4. EL GENERADOR DE APLICACIONES
5. CONSTRUCCIÓN DE UNA APLICACIÓN BÁSICA
6. EL ENTORNO DE DESARROLLO - VISOR DE PROYECTOS
7. LA LIBRERÍA DE CLASES MFC - INTRODUCCIÓN
7.1. Filosofía de trabajo con la MFC
7.2. Las clases básicas para interfaz de usuario
7.2.1. La clase CWinApp
7.2.2. El interfaz Documento/Vista
7.2.3. La ventana básica CWnd
7.2.4. Una ventana principal - CFrameWnd
7.2.5. Las clases Vista
7.3. Clases básicas de propósito general
7.3.1. La clase CString
7.3.2. La clase CFile
7.3.3. La clase CTime
7.3.4. La clase CRect
8. TRATAMIENTO DEL MENU
8.1. Gestión de mensajes de comandos del menú
8.2. Objetos capaces de gestionar mensajes de comando de menú
9. GESTIÓN DE DIÁLOGOS
9.1. Creación de diálogos
9.2. Tratamiento de los controles de diálogos
9.3. Mensajes de comando de controles de diálogos
9.4. Inicialización de diálogos
9.5. Un diálogo especial - CPropertySheet
10. EL INTERFAZ MDI
11. CLASES ORIENTADAS A ACCESO A BASES DE DATOS
11.1. Tratamiento mediante ODBC
11.2. Tratamiento mediante DAO
11.3. Las clases básicas
11.3.1. La clase CDatabase
11.3.2. La clase CRecordset
11.3.3. Los recordsets parametrizados
11.4. Manejo de excepciones mediante CDBException |
| |
- INTRODUCCIÓN - ¿ QUE ES VISUAL C++ ?
Como sabemos, Windows es el entorno más popular de interfaz
gráfico de usuario (GUI). Desde este punto de vista, Windows es un entorno
multitarea basado en ventanas, que representan programas, y que permite
ejecución concurrente.
Para desarrollar programas, Windows provee una librería de
rutinas y funciones (SDK - Kit de desarrollo de software) que permiten gestionar
componentes como menús, diálogos, ventanas, etc.
Visual C++ es un entorno integrado de desarrollo que permite
la programación orientada a objetos (POO) conjuntamente con el sistema de
desarrollo SDK (también denominado API) de Windows. Al ser un entorno integrado
Visual C++ incluye, entre otras, las siguientes herramientas de desarrollo:
- Editor de texto
- Compilador/Enlazador
- Depurador
- Visor de datos y dependencias (Browser)
Pero si desde el punto de vista del usuario Windows es un
sistema amigable, desde el punto de vista del desarrollador observaremos todo lo
contrario. El SDK de Windows no es mas que un complejo conjunto de funciones que
añade además numerosas definiciones de tipos de datos nuevos para cualquier
programador de C/C++ para DOS. Para solucionar este problema, Visual C++ incluye
la librería de clases MFC (Microsoft Foundation Classes) que permite crear y
gestionar de manera intuitiva componentes típicos de Windows. Esto es, la MFC es
una implementación que utiliza el API encapsulando todas las estructuras y
llamadas a funciones en objetos fáciles de utilizar. Basándose en la potencia de
la MFC, Visual C++ se convierte en un generador de programas C++ para Windows.
El objetivo del presente curso es conocer el modelo de
programación para Windows basado en la librería de clases MFC. En este documento
se destacarán ideas, conceptos y tratamientos generales, en ningún momento
pretende ser un manual completo de programación con MFC.
- CONCEPTOS PRELIMINARES
- ¿ Que es C ++ ?
Como todos sabemos, "C" es un lenguaje de alto nivel,
basado en funciones, que permite desarrollos estructurados. Entre otras
muchas características contempla la definición de estructuras de datos,
recursividad o indirecciones a datos o código (punteros).
"C ++", por su parte, es un superconjunto de "C", al
que recubre con una capa de soporte a la POO. Permite por tanto la
definición, creación y manipulación de objetos.
- ¿ Que es la Programación Orientada a Objetos ?
La POO es una nueva filosofía de programación que se
basa en la utilización de objetos. El objetivo de la POO no es sino la meta de
cualquier modelo de programación estructurada convencional: "imponer" una serie
de normas de desarrollo que aseguren y faciliten la mantenibilidad y
reusabilidad del código.
Los mecanismos básicos de la POO son: objetos,
mensajes, métodos y clases.
- Objetos. Un objeto es una entidad que tiene unos atributos
particulares (datos) y unas formas de operar sobre ellos (los métodos o
funciones miembro). Es decir, un objeto incluye, por una parte una serie de
operaciones que definen su comportamiento, y una serie de variables
manipuladas por esas funciones que definen su estado. Por ejemplo, una
ventana Windows contendrá operaciones como "maximizar" y variables como
"ancho" y "alto" de la ventana.
- Mensajes. En C++, un mensaje se corresponde con el nombre de uno de
los métodos de un objeto. Cuando se pasa un mensaje a un objeto, este
responde ejecutando el código de la función asociada.
- Método. Un método (función miembro) se implementa dentro de un
objeto y determina como tiene que actuar el objeto cuando se produce el
mensaje asociado. En C++ un método se corresponde con la definición de la
función miembro del objeto. La estructura más interna de un objeto está
oculta, de tal manera que la única conexión con el exterior son los mensajes
- Clases. Una clase es la definición de un tipo de objetos. De esta
manera, una clase "Empleado" representaría todos los empleados de una
empresa, mientras que un objeto de esa clase (también denominado instancia)
representaría a uno de esos empleados en particular.
Las principales características de la POO son:
abstracción, encapsulamiento, herencia y polimorfismo:
- Abstracción. Es el mecanismo de diseño en la POO. Nos permite
extraer de un conjunto de entidades datos y comportamientos comunes para
almacenarlos en clases.
- Encapsulamiento. Mediante esta técnica conseguiremos que cada clase
sea una caja negra, de tal manera que los objetos de esa clase se puedan
manipular como unidades básicas. Los detalles de la implementación se
encuentran dentro de la clase, mientras que desde el exterior, un objeto
será simplemente una entidad que responde a una serie de mensajes públicos
(también denominados interfaz de la clase).
- Herencia. Es el mecanismo que nos permite crear clases derivadas
(especialización) a partir de clases bases (generalización). Es decir,
podríamos tener la clase "Empleado" (clase base) y la clase "Vendedor"
derivando de la anterior. Una librería de clases (como la MFC) no es
más que un conjunto de definiciones de clases interconectadas por múltiples
relaciones de herencia.
- Polimorfismo. Esta característica nos permite disponer de múltiples
implementaciones de un mismo método de clase, dependiendo de la clase en la
que se realice. Es decir, podemos acceder a una variedad de métodos
distintos (con el mismo nombre) mediante el mismo mecanismo de acceso. En
C++ el polimorfismo se consigue mediante la definición de clases derivadas,
funciones virtuales y el uso de punteros a objetos.
Otros dos conceptos muy importantes en la POO son relativos a
la creación y destrucción de objetos. En lenguajes estructurados convencionales,
cuando se define una variable se le reserva espacio en memoria y, si no se
inicializa expresamente, se hace por defecto (por ejemplo, en C una variable
global siempre se inicializa a 0, pero una automática no, por lo que si no se
inicializa expresamente su contenido inicial será basura); por otra parte,
cuando se destruye una variable (por que se abandona el ámbito de su definición
- scope -) se libera la memoria que estaba ocupando. Si ahora hacemos el
paralelismo obligado entre variables y objetos para los lenguajes POO nos
daremos cuenta de que deben existir procedimientos especiales de construcción y
destrucción de objetos. En concreto, cada clase tiene dos funciones miembro
especiales denominadas constructor y destructor.
- Constructor -> Función miembro que es automáticamente invocada cada
vez que se define un objeto, su objetivo es la inicialización del mismo.
Toma el mismo nombre que la clase, puede recibir parámetros y podemos tener
varios constructores definidos.
- Destructor -> Función miembro invocada automáticamente cada vez que
se destruye un objeto. Su objetivo es realizar operaciones como liberación
de memoria, cerrar ficheros abiertos, etc. Toma el mismo nombre de la clase
comenzado primero por el carácter "~", no toma parámetros y no admite la
sobrecarga (sólo puede existir uno en cada clase).
En muchos casos, para las clases mas sencillas, podemos
encontrar clases que no tiene constructor o destructor, ó ninguno de los dos. En
C++, siempre existen constructores y destructores por defecto que realizan una
inicialización/liberación estándar.
- El modelo de programación Windows
El modelo de programación propuesto por Windows es totalmente
diferente al modelo de ejecución secuencial de DOS. Al ser Windows un entorno
multitarea los programas tienen que estar preparados para compartir los recursos
de la maquina (procesador, memoria, teclado, ratón …). Esto supone que Windows
ha de disponer de métodos que permitan suspender tareas para activar otras en
función de las circunstancias del momento (por ejemplo, por acción del usuario).
Pero por parte de las aplicaciones, este hecho supone que han
de cooperar en la compartición de esos recursos. Las aplicaciones Windows se
limitan a "esperar" mensajes procedentes del sistema, procesarlos y volver al
estado de espera. Este modelo de programación se conoce como "orientado al
evento".
- Mensaje. Es una notificación a la aplicación de que ha ocurrido algo
de interés y que por lo tanto debe de realizarse alguna acción específica.
El origen del mensaje puede ser el usuario (haciendo click con el ratón
dentro e una ventana), la propia aplicación (mandándose un mensaje a si
misma) o Windows (pidiendo, por ejemplo, que se repinte la ventana tras
ocultarse otra que tuviese delante). Dado que la unidad mínima de ejecución
en Windows es una ventana, los mensajes van realmente dirigidos a ellas.
- Ventana y procedimiento de ventana. En Windows, una aplicación se
representa físicamente por su ventana principal (aunque después pueda
desplegar diversas ventanas hijas). Cada una de esas ventanas dispone de una
serie de propiedades y un código asociado (lo que concuerda con el principio
de la POO, en el concepto de objeto). Al código asociado a cada ventana se
le denomina procedimiento de ventana. Es una función que recibe los
mensajes, los procesa y devuelve el control a Windows para quedar en espera.
Otra de las características específicas de Windows frente a
DOS es el uso de recursos por parte de las aplicaciones, como son iconos, menús,
mapas de bits, cursores, plantillas de diálogos, etc. Las aplicaciones Windows
disponen por tanto de recursos (gráficos generalmente) propios almacenados en lo
que se llama el fichero de recursos). El proceso de construcción de
programas en Windows incorpora una fase adicional al compilado y enlazado de los
módulos objeto y las librerías. Hay un proceso final de compilación y de
enlazado (bind) del fichero de recursos.
3. EL ENTORNO DE DESARROLLO - INTRODUCCIÓN.
El entorno de desarrollo viene representado por el icono
"Developer Studio". En él se integran entre otras las siguientes herramientas:
- Editor orientado a la codificación C/C++ (resaltando palabras claves …)
- Compilador/Enlazador incremental, que acelera el proceso de construcción
de los programas.
- Depurador visual, que permite visualizar y modificar el contenido de
variables y áreas de memoria.
- Visor de datos (browser) que permite fácilmente controlar dependencias y
referencias a funciones, datos, clases, etc. Además permite visualizar la
jerarquía de las clases utilizadas en los programas.
- Herramientas complementarias como un analizador de ventanas (Spy ++) o
un trazador de funciones MFC.
El concepto de proyecto
(workspace)
En Visual C++ la construcción de cualquier tipo de
programa se inscribe dentro del concepto de proyecto (workspace). Un
proyecto define los pasos a seguir para alcanzar la construcción de un
objetivo (un programa, una DLL, etc.), en realidad es un concepto
análogo a lo que se conoce como "makefile" en otros entornos típicos de
desarrollo en C. En realidad, Visual C++ genera para cada proyecto dos
ficheros que lo definen, el fichero de workspace (con extensión wsp) y
un makefile (con extensión mak) estándar que permitiría la utilización
del mismo proyecto en otro entorno distinto.
Desde el punto de vista funcional, el proyecto
contiene referencias a cada uno de los ficheros fuentes (C/C++, con
extensiones c y cpp respectivamente), objetos, librerías o ficheros de
recursos (extensión rc) que se deben utilizar para construir el objetivo
final del proyecto.
En definitiva, para crear cualquier programa con
Visual C++ debemos comenzar creando un proyecto para él, codificando y
añadiendo los módulos necesarios a dicho proyecto, y definiendo los
recursos asociados.
Posibilidades de programación
Cuando se crea un nuevo proyecto (desde la opción "Nuevo" del
menú "Fichero" aparece un diálogo que nos permite especificar que se cree un
nuevo workspace), lo primero que solicita el sistema es determinar el tipo de
objetivo que se persigue con este proyecto. Destacar las siguientes
posibilidades:
- Aplicación (.EXE) basada en la MFC (MFC AppWizard)
- Librería de enlace dinámico (.DLL) basada en la MFC. (MFC AppWizard)
- Aplicación (.EXE) estándar para Windows (basada en el SDK)
- Librería de enlace dinámico (.DLL) (basada en el SDK)
- Aplicación (.EXE) modelo DOS (Console application)
- Librería estática (.LIB)
Como ya hemos destacado anteriormente, el objetivo del
presente curso es el manejo de la librería MFC, por lo que nos centraremos
básicamente en el primer punto (la construcción de una DLL con MFC es totalmente
similar).
- EL GENERADOR DE APLICACIONES.
Ya sabemos que Visual C++, apoyado en la potencia de la MFC,
es capaz de convertirse en un generador de aplicaciones. Para ello dispone de
dos herramientas integradas complementarias:
- AppWizard, que es el generador de aplicaciones propiamente dicho.
Con él podremos generar esqueletos de programas para Windows basados en la
MFC.
- ClassWizard, herramienta de mantenimiento de los programas generados
con la anterior. Permite añadir o eliminar clases, modificar los
comportamientos de las mismas, etc.
Pero el código generado mediante este método presenta una
complejidad añadida a la natural de cualquier programa; junto con el código
C/C++ y el de la MFC aparecen líneas (generalmente entre comentarios) que son
totalmente necesarias para el funcionamiento de las dos herramientas anteriores,
modificar cualquiera de esas líneas de código dará muchos problemas a la hora de
utilizar ClassWizard para modificarlo. De todas maneras, este "defecto" es
bastante popular entre los usuarios de cualquier generador de código, para
cualquier lenguaje.
El formato general de los proyectos generados con estas
herramientas suele tener las siguientes características:
- Cada clase de nuestro programa dispondrá de dos ficheros: Un fichero de
cabecera (extensiones .h o .hpp) y un fichero de implementación (.cpp). El
fichero de cabecera contiene la definición de la clase (definiciones de sus
miembros datos y funciones - los mensajes -), mientras que el fichero fuente
contiene la implementación de esas funciones miembro (los métodos de la
clase).
- Un fichero de recursos (extensión .rc), aunque éste es opcional.
- Módulos objetos (.obj) y librerías estáticas (.lib) necesarias para
crear nuestro programa.
CONSTRUCCIÓN DE UNA APLICACIÓN
BÁSICA
Seguiremos los siguientes pasos:
- Crear un nuevo proyecto
. Desde el menú "Fichero", en la opción
"Nuevo".
- Seleccionar objetivo del proyecto
. Seleccionaremos
"aplicación basada en MFC"
- Nombrar el proyecto
. Visual C++ organiza los proyectos de
manera que crea un subdirectorio nuevo con el nombre de cada proyecto.
Aunque esta "regla" siempre puede modificarse, puede ser una buena forma de
control de proyectos.
En estos momentos aparecerá la secuencia de diálogos del
generador ClassWizard. Veamos cuales serían los pasos a seguir para crear
una aplicación sencilla:
- Paso 1. Permite identificar el modelo de ventana principal de
nuestra aplicación: SDI, MDI o basada en diálogo. Nosotros elegiremos SDI.
- Paso 2. Permite incorporar soporte a Bases de Datos en la
aplicación. Esto lo veremos más adelante. Seleccionaremos la opción sin
soporte a bases de datos.
- Paso 3. Relativo al soporte OLE. Igual que en el caso
anterior.
- Paso 4. Otras características de la aplicación (barra de
botones, barra de estado, controles 3D …)
- Paso 5. Generación de comentarios en el código (si/no) y
usos posibles de las MFC (como DLL o como LIB). Se recomienda la opción DLL
en cuanto al tamaño y modularidad del programa, pero deberemos asegurarnos
de distribuir la DLL junto con nuestro programa para que funcione
correctamente.
- Paso 6. Permite modificar el nombre de las clases MFC que
se van a generar, además de especificar los ficheros en los que se
implementa y la clase base de la que derivan. Los nombres generados por
AppWizard suelen ser bastantes significativos.
A partir de este momento da comienzo la generación del código
definido antes. Como se habrá observado, el nombre por defecto de las clases
generadas tiene mucho que ver con el nombre que le hayamos dado al proyecto. De
esta manera, si hubiésemos llamado "curso1" al proyecto tendríamos la siguiente
situación:
- Clase CCurso1App (módulos curso1.h y curso1.cpp) que representa
una aplicación Windows.
- Clase CMainFrame (ficheros mainfrm.h y mainfrm.cpp) que
representan la ventana principal de la aplicación.
- Clases CCurso1Doc y CCurso1View (ficheros
curso1doc.h/curso1doc.cpp y curso1view.h/curso1view.cpp respectivamente),
representantes de lo que se conoce en el mundo Windows como interfaz
"Documento/Vista" y que trataremos en adelante.
- Clase CAboutDlg que representa el típico diálogo de "Acerca de …"
y que ha sido generado automáticamente por AppWizard, esta clase (rompiendo
la norma habitual de la MFC) aparece definida e implementada dentro los
mismos ficheros que la clase aplicación (módulos curso1.h y curso1.cpp). En
el futuro evitaremos este tipo de construcciones.
Cada una de estas clases se revisará con detalles en
capítulos sucesivos.
- EL ENTORNO DE DESARROLLO - VISOR DE
PROYECTOS
El Visor de Proyectos es una ventana a través de la
que podemos visualizar el contenido de los proyectos, accedemos a la misma desde
el menú "Ver" y la opción "Proyecto". Está formada por 3 carpetas que nos
permiten visualizarlos desde distintos puntos de vista:
- ClassView, o Visor de clases, representa el proyecto con las
clases que lo componen. Desde aquí podremos añadir miembros datos o
funciones fácilmente.
- ResourceView, o Visor de recursos, que permite
añadir/modificar/eliminar recursos de la aplicación.
- FileView, o Visor de ficheros, que representa el proyecto
mediante la lista de ficheros fuente que lo componen.
Por último aparece también la carpeta InfoView que
permite un rápido acceso a la ayuda en línea.
Cada una de estas funcionalidades se utilizarán en función de
la tarea a realizar. La gestión y tratamiento de los recursos sólo se puede
hacer mediante ResourceView, mientras que ClassView y FileView
son casi similares en cuanto a su objetivo último: el mantenimiento del código
del programa.
- LA LIBRERÍA DE CLASES MFC - INTRODUCCIÓN.
A continuación, y siguiendo en todo momento el código de la
aplicación básica que acabamos de generar, procederemos a presentar las clases
mas utilizadas de la MFC. Esta librería está compuesta por cientos de clases
distintas, que desde el punto de vista funcional, las podemos dividir en 4
grupos:
- Clases orientadas al interfaz de usuario, representan ventanas, menús,
diálogos, etc.
- Clases de propósito general, representando ficheros, strings,
datos de fecha y hora, etc.
- Clases orientadas a bases de datos, representando tanto bases
de datos como conjuntos de registros seleccionados después de una consulta
sobre una tabla, etc.
- Clases para manejo de excepciones, para control avanzado de
errores de ejecución.
- Filosofía de trabajo con la MFC.
El hecho de utilizar una librería de clases como la MFC añade
complejidad al desarrollo e impone una serie de normas de programación a las que
regirse. La complejidad añadida deriva de la necesidad de que el programador
ahora no sólo debe controlar C/C++, sino que además debe conocer las clases de
la MFC para poder utilizar su potencia.
Entran aquí en juego algunos conceptos que ya conocemos:
- Herencia, Un buen número de las clases de la MFC son "no
instanciables", es decir, no podemos crear objetos de esa clase con lo
que no podremos utilizarla directamente. Esto significa que el programador
deberá en muchos casos derivar sus propias clases de alguna de la MFC
(estas clases "prohibidas" suelen ser abstracciones que dan pie a
polimorfismos). Además debemos saber que clase es la mas óptima para que
nos sirva de clase base; por ejemplo: se debe saber que la clase MFC
CWinApp es la que da soporte a las aplicaciones Windows, y que toda
aplicación debe derivar su propia versión de la clase para poder funcionar
(en realidad, un programa MFC comienza con la creación de un objeto de ese
tipo y finaliza con la destrucción del mismo). También deberemos saber que
si lo que queremos es obtener una ventana de diálogo, deberemos derivar
nuestra propia clase desde CDialog (que es una clase especializada
para ello), en vez de desde CWnd (que representa a una clase mas
genérica y presentaría muchos problemas), por ejemplo, CDialog soporta que
su plantilla haya sido dibujada en los recursos de la aplicación (al estilo
de un formulario de Visual Basic, …) mientras que CWnd no, lo que nos
obligaría a pintar los botones con código duro (programar su tamaño,
posición en pixeles dentro de la ventana padre …), aunque no sería
imposible. En definitiva, a la hora de derivar clases de la MFC debemos
tener en cuenta el concepto de especialización para obtener una programación
más sencilla.
- Polimorfismo. Pero la mayor potencia de la MFC reside en las
funciones polimórficas (virtuales). El mecanismo de herencia no sería útil
si no se pudiese redefinir el comportamiento por defecto de las clases de la
MFC. Por ejemplo, supongamos que hemos creado un diálogo (derivando una
clase desde CDialog) con nuestro propio diseño, supongamos que tiene unos
campos que deseamos inicializar con los datos de un empleado para que
aparezcan en pantalla, en este caso debemos ser capaces de definir nuestro
propio método de inicialización. Esto es posible gracias a que este método
(en concreto denominado OnInitDialog) es definido como virtual
dentro de la clase base CDialog, lo que da lugar a una función polimórfica.
Así, si en nuestra clase derivada de CDialog redefinimos ese método
OnInitDialog, nos aseguraremos que el procedimiento de inicialización
que se ejecutará será el nuestro. En definitiva, las dos clases (CDialog
como clase base y la nuestra como derivada) responden al mismo mensaje (OnInitDialog)
pero de distinta manera (por que no comparten el método).
Esta es la filosofía de trabajo que debemos seguir: derivar
nuestras clases de la clase MFC mas especializada en función de lo que queramos,
y utilizar las funciones virtuales que tenga para conseguir el comportamiento
deseado.
- Las clases básicas para interfaz de usuario.
Para conseguir una mejor visión del funcionamiento de
la MFC, nos basaremos en el código generado en el punto 5.
- La clase CWinApp
Representa una aplicación Windows. Cada programa debe tener
un objeto global o estático (es decir, que exista durante todo el programa) de
una clase propia derivada de CWinApp. De hecho, otra característica de la
programación basada en MFC es que el código carece de programa principal (punto
inicial de ejecución como la función "main" de C/C++ para DOS, UNIX, etc.), la
ejecución comienza con la creación del objeto CWinApp.
En realidad, es la MFC la que provee una función principal
estándar WinMain (este es el nombre que toma para las aplicaciones Windows) que
utilizarán todos los programas. Los pasos básicos que sigue son tres:
inicialización del programa, entrada en el bucle de mensajes y finalización del
programa. Veámoslo más en detalle.
- Inicialización del programa. Se obtiene en dos fases distintas:
inicialización de la aplicación e inicialización de la instancia
de la aplicación. Para entender esto tenemos que tener en cuenta que los
programas Windows pueden ser "multi-instanciables", podemos tener varios
ejemplares del mismo programa ejecutándose a la misma vez. Los ejemplos mas
claros son aplicaciones como el bloc de notas (notepad) o cualquier utilidad
de conexión remota (telnet). En definitiva, si queremos obtener programas
multi-instanciables tenemos que distinguir operaciones de inicialización que
servirían a todos los ejemplares y que por tanto se deberían realizar una
sola vez, de aquellas que son específicas a cada ejemplar (como crear cada
ejemplar su ventana principal).
- Bucle de mensajes. Es la parte central de la ejecución, no finaliza
hasta que no se cierra la ventana principal.
- Finalización de la instancia. Permitirá liberar recursos, cerrar
ficheros de trabajo, confirmar operaciones, etc.
Los detalles mas importantes de la clase CWinApp son los
siguientes:
- Miembros datos públicos
-> Destacar:
- m_pszAppName
, contiene el nombre de la aplicación.
- m_pMainWnd
, contiene un puntero (referencia) a la ventana
principal de la aplicación.
- m_hInstance
, identificador de la instancia del programa que se
está ejecutando.
- m_hPrevInstance
, identificador de la instancia previa del
programa (0 si no hay instancia previa). Sirve para saber si se deben
realizar tareas de "inicialización de aplicación" (ver arriba).
- m_lpCmdLine
, línea de comando con la que se llamó al programa
(argumentos).
- Constructor CWinApp -> Recibe un sólo parámetro con el nombre de la
aplicación (por defecto llevará NULL, nulo).
- Miembros funciones públicos -> La funcionalidad de esta
clase, como siempre, reside en funciones virtuales que se pueden redefinir;
destacan las siguientes:
- InitApplication
-> Invocada por el sistema automáticamente
cuando se ejecuta la primera instancia de un programa (cuando no existe
instancia previa).
- InitInstance
-> Invocada por el sistema para cualquier instancia
del programa. Se suelen realizar operaciones como la de creación de la
ventana principal, etc.
- Run
-> Que realiza el bucle de mensajes por defecto. En la
mayoría de los casos este bucle es suficiente, con lo que no tendremos
por que redefinirlo.
- ExitInstance
-> Invocada por el sistema después de cerrarse su
ventana principal.
Comparando el código generado para nuestra primera aplicación
con lo expuesto en este punto podemos comenzar a entender como funcionan las
cosas en el mundo MFC.
- 2 El interfaz Documento/Vista
El estándar Documento/Vista es una manera de enfocar el
desarrollo de aplicaciones para Windows. Se basa en los siguientes conceptos:
- Documento, cualquier tipo de información que se presenta sobre una
ventana, podría ser tanto una hoja de cálculo, como una base de datos o
algún tipo de gráfico.
- Vista (view), ventana que presenta el documento. Cada vista
presenta un único documento.
- Ventana marco (frame window), que contiene a la ventana vista
en su área cliente.
Figura 1.- Interfaz documento/vista
De esta manera, cualquier tipo de información se puede
interpretar como un documento, que se presenta al usuario mediante una vista,
que se inscribe dentro de una ventana marco. Mientras la vista se ocupa
solamente de representar el documento, la ventana marco se puede ocupar del
tratamiento del menú.
AppWizard genera todo su código basándose en este estándar.
Una aplicación SDI sólo podrá tener abierto un documento (la ventana principal
es también la ventana marco), mientras que una MDI podrá tener varios abiertos
simultáneamente (aquí la ventana principal puede contener varias marcos con
vistas). Sin embargo este enfoque no es el mas idóneo para un buen número de
aplicaciones Windows SDI donde la ventana principal no sirve sino como
contenedor del menú, y la representación o el manejo de los datos de la
aplicación se suele realizar a través de diálogos. Si es sin embargo mas útil en
el caso de las aplicaciones MDI donde si que las ventanas marcos se utilizan
como formularios usuales, es decir, como ventanas para presentación de datos.
Figura 2.- Detalle de aplicación MDI
Observando la figura basada en el entorno de desarrollo de
Visual C++, vemos que la ventana principal contiene dos ventanas marcos, la
primera representa en su vista el contenido del workspace que tenemos abierto,
mientras la segunda está representando un fichero de código fuente CPP.
Las clases MFC en las que se apoya el interfaz
Documento/Vista son las siguientes:
- Clase CDocument-> funcionalidad básica para documentos de usuario.
Un documento es un objeto que almacena cualquier tipo de datos. Es una clase
abstracta de la que tendremos que derivar nuestros propios documentos.
- Clase CView -> Ventana vista que presenta un documento. Es
una ventana "especial" que no contiene ni bordes ni título ni barra de menú,
orientada a circunscribirse dentro de otra ventana marco. Existen otras
clases derivadas de ésta que presentan mayor especialización:
- CScrollView, es una vista con soporte a las barras de scroll.
- CFormView, vista formulario, con controles al estilo de los
diálogos.
- CEditView, vista orientada a la edición de textos.
- CRecordView, vista orientada a la visualización de bases de
datos.
- Clase CDocTemplate
-> Clase abstracta que aporta la funcionalidad
básica para plantillas SDI o MDI. Al ser una clase abstracta no la podremos
utilizar directamente, tendremos que utilizar sus clases derivadas:
CSingleDocTemplate y CMultiDocTemplate.
- Clase CSingleDocTemplate
-> derivada de CDocTemplate
representa la plantilla de interfaz SDI. Define la relación existente entre
un documento (CDocument), una vista (CView, …) y una ventana marco que será
la ventana principal.
- Clase CMultiDocTemplate
-> derivada de CDocTemplate
representa la plantilla para interfaz MDI. Define la relación entre un
documento, su vista y la ventana marco que ventana MDI hija.
Toda aplicación que se base en el Documento/Vista debe
registrar en el sistema las plantillas de documento que soporta antes de poder
utilizarlas, lo que se realiza a través de la función CWinApp::AddDocTemplate.
Así, una aplicación SDI, que debe soportar una única plantilla de documento,
debe crear un objeto CSingleDocTemplate (donde se define la relación
entre documento, vista y ventana marco) y registrarlo mediante el método
anterior. Una vez realizado esto bastará con invocar la función
CDocTemplate::OpenDocumentFile() del objeto recién creado para conseguir que
se visualice la ventana principal de la aplicación, con su vista presentando el
documento especificado (el enfoque en aplicaciones MDI es algo distinto y se
revisará posteriormente). Estas operaciones se introducirán dentro del
InitInstance de la aplicación.
De todas maneras, en contra de lo que hemos dicho antes, no
es difícil adaptar cualquier aplicación al protocolo requerido por el interfaz
documento vista, sin mucho impacto en la misma, gracias a que la mayoría de las
funcionalidades están predefinidas por defecto.
- 2. 3. La ventana básica CWnd
Es una de las clases mas importantes
de la MFC ya que contiene las funcionalidades básicas de
todas las clases de ventanas Windows. Como siempre, al
ser una abstracción, no está muy orientada a la
utilización directa, mas bien siempre tenderemos a
utilizar alguna de sus clases derivadas, mas
especializadas, como CFrameWindow (ventana
principal de aplicación) o CDialog (para ventanas
de diálogo).
Pero su principal interés reside en
que es la clase responsable del funcionamiento del
mecanismo de paso de mensajes de Windows. Éste queda
oculto por lo que se llama en la MFC mapa de mensajes.
El mapa de mensajes de una clase de ventana recoge la
asociación entre el mensaje recibido del sistema y la
función que se ejecutará como respuesta. Todas estas
funciones de respuesta (métodos de clase) son funciones
virtuales que por tanto se pueden redefinir en todas las
clases derivadas.
Supongamos que estamos construyendo
una aplicación la cual queremos que antes de finalizar
descargue cierto contenido de memoria en un fichero.
Para ello deberíamos ser capaces de contestar
adecuadamente al mensaje WM_CLOSE que el sistema manda a
la ventana principal cuando se tiene que cerrar. El mapa
de mensajes de la clase CWnd especifica que la función
de respuesta será CWnd::OnClose(). Bastará
entonces con que en nuestra clase ventana principal
redefinamos una nueva versión de esa función heredada.
No hace falta que conozcamos de memoria todo el mapa de
mensajes por defecto de CWnd (nombres de las funciones
de respuesta, etc.), la utilización de AppWizard
automatiza todo este complejo tratamiento.
Los miembros datos y miembros
funciones de esta clase son muy numerosos y variados,
los iremos comentando según los vayamos utilizando.
7. 2. 4. Una ventana principal - CFrameWnd
Derivando de CWnd, proporciona las funcionalidades básicas de
cualquier ventana principal de aplicación. Incluye soporte de menús, barras de
estado y barra de herramientas. Las aplicaciones SDI derivarán de esta clase sus
ventanas principales. Entre sus características incluye título, menú de sistema
y borde redimensionable.
De sus miembros funciones destacan:
- Create -> Función que permite crear una ventana física y asociarla
con el objeto recién creado.
- LoadFrame -> Función análoga a la anterior, per de mas alto nivel
(exige menos parámetros) que adopta muchos comportamientos por defecto.
Derivando de CFrameWnd aparecen otras clases de ventanas mas
especializadas, son las siguientes:
- CMDIFrameWnd
-> Ventana principal para aplicaciones MDI.
- CMDIChildWnd
-> Ventanas MDI hijas (tienen la mayoría de las
propiedades de cualquier ventana principal).
2. 5.
Las clases Vista
Como ya hemos comentado, la clase CView proporciona la
funcionalidad básica para ventanas que se asocian a plantillas de documentos y
que realizan tareas de intermediarias entre el documento y el usuario. Es la
responsable de presentar la imagen del documento y de interpretar las acciones
del usuario sobre el mismo (modificaciones, etc.). Entre estas funcionalidades
cabe destacar:
- CView::OnInitialUpdate, función miembro que permite la
inicialización de la vista con los datos del documento.
- CView::OnUpdate, invocada ante cualquier modificación del documento,
permite actualizar la vista antes de que se repinte.
- CView::OnDraw, función de pintado de la vista (utilizada casi
siempre para "documentos gráficos").
- CView::GetDocument, que permite obtener una referencia al documento
que se esta representando.
Las vistas mas especializadas se encuentran entre las
siguientes clases derivadas:
- CEditView
, donde la vista es una ventana de edición de texto.
- CListView
, donde la vista es una lista.
- CFormView
, donde la vista utiliza una plantilla de diálogo de los
recursos de la aplicación (al estilo de los formularios tradicionales).
- CRecordView
, derivando de CFormView implementa además soporte para
bases de datos, permitiendo asociar estáticamente campos de tablas con
controles de la plantilla de diálogo que utiliza.
3
Clases básicas de propósito general.
Presentaremos a continuación algunas de las
clases de propósito general mas importantes y utilizadas de la
MFC.
- 3. 1 La clase CString
La clase CString representa un string de caracteres.
Cualquier programador de C/C++ sabe lo tedioso que puede ser el manejo de este
tipo de dato ya que no está reconocido como tal en el núcleo de C. Mientras que
en otros lenguajes de alto nivel el string de caracteres es un tipo de dato
autónomo que dispone de sus propios operadores (asignación, concatenación,
comparación, etc.), en C/C++ el string se representa como una colección (array)
de caracteres (que el tipo de dato básico), esto supone que cualquier operación
sobre ellos se debe realizar mediante funciones de la librería estándar de C
(strcpy, strcat, strcmp, etc.).
La principal virtud de CString es precisamente esa, facilita
el manejo de strings de caracteres automatizando todo el tratamiento óptimo de
la memoria. Destacan las siguientes funcionalidades:
- Función CString::GetLength, que permite conocer el número de
caracteres que contiene.
- Funciones CString::GetAt/CString::SetAt, que permiten acceder y
modificar respectivamente el carácter del string que aparece en una posición
dada.
- Funciones CString::MakeUpper/CString::MakeLower, que permiten
convertir a mayúsculas/minúsculas respectivamente.
- Función CString::Find, que permite conocer si un determinado
string o carácter aparece dentro de otro string.
Pero además, la clase CString tiene sobrecargados algunos de
los operadores de C/C++ lo que permite un manejo intuitivo de esta clase, son
los siguientes: asignación (mediante =), concatenación (mediante +
y +=) y comparación (operadores ==, <, <=, etc.).
- 3. 2. La clase CFile
Esta clase presenta las funcionalidades básicas para el
tratamiento de ficheros, aportando funciones de alto nivel.
- Funciones CFile::Open/CFile::Close para abrir y cerrar ficheros.
- Funciones CFile::Read/CFile::Write, para lectura y escritura
- Funciones CFile::Seek/CFile::SeekToBegin/CFile::SeekToEnd que
permiten posicionamiento directo dentro del fichero.
- Funciones CFile::GetLenght/CFile::SetLenght que permiten obtener
y cambiar el tamaño del fichero.
- Función CFile::Rename/CFile::Remove que permiten renombrar
(mover) y eliminar ficheros respectivamente.
3. 3.
La clase CTime
CTime presenta las funcionalidades básicas de tratamiento de
fechas y horas. Al estilo de como se hace en C/C++ mediante el tipo de dato
"time_t", representa un determinado instante de tiempo (con fecha, horas,
minutos y segundos). Destacan:
- Función CTime::GetCurrentTime, que obtiene el instante actual.
- Funciones de extracción que permiten acceder a cualquiera de los datos
significativos que componen un CTime: el día del mes ó el día de la semana,
el mes, el año, la hora, etc.
- Función CTime::Format, que permite construir un string formateado
a partir de un CTime.
- Operadores de asignación, suma, resta y comparación. Como complementaria
a esta clase aparece CTimeSpan que representa un intervalo de tiempo.
Las operaciones de suma se realizan con esta clase, y una resta de dos
fechas dadas siempre da como resultado un CTimeSpan.
3. 4.
La clase CRect
CRect automatiza el tratamiento de rectángulos. En Windows
este concepto se utiliza continuamente ya que las posiciones o el tamaño de las
ventanas se representan mediante los rectángulos que ocupan en la pantalla.
Un rectángulo viene representado físicamente por las
coordenadas de su esquina superior izquierda y la de su esquina inferior
derecha. Estos datos se almacenan en sus variables miembro públicas: top,
left, bottom y right.
Dispone de funciones como:
- CRect::Width/CRect::Height, que permiten saber el ancho/alto de un
rectángulo.
- CRect::PtInRect¸que permite saber si un determinado punto está
contenido dentro de un rectángulo.
TRATAMIENTO DEL MENU.
Una de las principales vías de interacción con el usuario,
además de los diálogos, son los menús. De manera general, cada ventana principal
de aplicación contiene un menú. El método que suele asociar la ventana con el
menú es la función CFrameWnd::LoadFrame, esta recibe como primer
parámetro el identificador de los recursos de la ventana principal. Serían el
menú, el icono y el título de la ventana. En suma, si creamos un menú, un icono
y un string todos con el mismo identificador, y utilizamos éste en la llamada a
LoadFrame, la ventana principal aparecerá con las características deseadas (este
es el método que se suele seguir en la MFC).
De todas maneras, la MFC proporciona otro método alternativo
que permite asociar o cambiar el menú de una ventana. Mediante la función
CWnd::SetMenu podemos realizar también esta operación.
Aunque en la mayoría de los casos el menú de una ventana es
constante durante toda la duración de la misma, puede que en algún momento
necesitemos modificarlo dinámicamente, para ello se proporciona la clase
CMenu que aporta las funcionalidades básicas para tratamiento de menús y la
función de ventana CWnd::GetMenu que devuelve una referencia al objeto
CMenu asociado con el menú de la ventana. Destacan:
- CMenu::LoadMenu, que permite asociar un objeto CMenu con un menú
diseñado previamente en los recursos de la aplicación.
- CMenu::DeleteMenu, que permite eliminar elementos (items o submenús
desplegables - popup -) de un determinado menú.
- CMenu::AppendMenu, que permite añadir items o submenús a un menú.
- CMenu::EnableMenuItem, que permite habilitar e inhabilitar items de
un menú.
- CMenu::CheckMenuItem, que permite "marcar" como seleccionados
elementos de un menú.
- CMenu::GetMenuState, que permite saber si un determinado ítem está
habilitado o seleccionado.
Pero aunque hemos repasado hasta aquí los detalles de como
asociar un menú a una ventana (CFrameWnd::LoadFrame o CWnd::SetMenu) no hemos
hecho ningún comentario de lo mas importante. ¿ Como podemos capturar las
selecciones del usuario dentro del menú y asociarles operaciones concretas ?.
Este aspecto lo veremos a continuación.
- 1. Gestión de mensajes de comandos
del menú.
Las selecciones que el usuario realice dentro de un menú
se notifican a la ventana propietaria mediante mensajes de comando
(WM_COMMAND). Cada uno de estos mensajes, cuando son recibidos por una
ventana, va acompañado del identificador del ítem de menú seleccionado de
manera que la aplicación pueda reconocer las operaciones a ejecutar. La
forma en que se puede asociar una función de respuesta a un ítem de menú es
bastante gráfica y sencilla gracias al uso de ClassWizard.
Para ello, bastará con abrir la ventana de ClassWizard
(CTRL+W o en el menú "Ver" con la opción "ClassWizard"), seleccionar la
clase que queremos que controle el mensaje en cuestión, seleccionar el ítem
de menú deseado y especificar el nombre de la función miembro que funcionará
como respuesta a ese mensaje (tras presionar el botón de "Añadir función").
Desde ese momento, ClassWizard añadirá los ficheros de cabecera e
implementación de la clase seleccionada los datos específicos de la nueva
función definida; el programador será el responsable de completar la
implementación de la función con el tratamiento adecuado.
Una característica propia de todas las aplicaciones
generadas con AppWizard es que los items de menú que no estén asociados con
ninguna función de respuesta aparecerán inhabilitados.
8. 2. Objetos capaces de gestionar mensajes de
comando de menú.
En la MFC tanto el objeto aplicación CWinApp como
cualquier ventana (objetos CWnd y derivados) es capaz de recibir mensajes de
comando procedentes del menú de la aplicación. Aunque de alguna forma lo mas
"real" es que la ventana sea la encargada de gestionarlos (ya que es la
propietaria del menú y además en la programación SDK de Windows, la
aplicación no tiene entidad como tal y son solamente las ventanas los
elementos capaces de recibir mensajes del sistema, mediante su ya conocido
procedimiento de ventana) si que es verdad que la elección siempre
depende del programador. Todo esto quiere decir que mediante ClassWizard
podemos asociar un comando del menú con una función miembro de un objeto
aplicación.
De cualquier manera lo que siempre es una práctica
aconsejable es centralizar todas las funciones de respuesta (también
conocidas como manejadores de mensajes) dentro de una misma clase.
- GESTIÓN DE DIÁLOGOS
Las ventanas de diálogos son uno de los elementos mas
utilizados para la interacción con el usuario. De manera general un diálogo es
una ventana especial que contiene en su interior ventanas hijas que son
controles (botones, listas, cajas de edición, listas desplegables, …)
identificados por un número único, además, cuando un diálogo se despliega, se
convierte en una ventana exclusiva en el sentido de que deja inhabilitada
cualquier operación con el resto de la aplicación (este efecto se conoce
realmente con el nombre de diálogos modales, aunque existe la posibilidad de
crear también diálogos no modales). Las plantillas de estos diálogos pueden
diseñarse mediante herramientas integradas en el entorno de desarrollo como es
el ya conocido ResourceView (también conocido como taller de recursos).
Es la clase CDialog, derivada de CWnd, la responsable
de proporcionar las funcionalidades básicas de esta ventana especializada.
- El constructor de la clase CDialog::CDialog, requiere dos
parámetros: el nombre de la plantilla de diálogo que utilizará (la que
hayamos diseñado en los recursos) y una referencia a la ventana padre del
diálogo (casi siempre es la ventana que pide que se visualice). También
existe un constructor por defecto (sin parámetros) orientado a la creación
de diálogos no modales.
- La función CDialog::DoModal, aplicada sobre un objeto construido
previamente permite visualizarlo en modo modal. Esta función es síncrona en
el sentido de que no devuelve el control a la función llamante hasta que no
se cierre el diálogo. Devuelve un valor que podemos utilizar para indicar
distintas opciones de cierre del diálogo (por ejemplo podemos devolver TRUE
para indicar que se presionó OK o FALSE para Cancelar).
- La función CDialog::EndDialog permite finalizar un diálogo modal,
recibe un parámetro que se corresponderá con el valor que devuelva DoModal.
- La función CDialog::Create, aplicada sobre un objeto construido
previamente, permite crear diálogos no modales. Los diálogos no modales no
se finalizan con EndDialog sino que utilizaremos la función estándar
heredada de CWnd DestroyWindow.
- Función CDialog::OnInitDialog, para inicializar el diálogo.
- Funciones CDialog::OnOk/CDialog::OnCancel que permiten realizar
las operaciones de confirmación o cancelación del objetivo del diálogo.
Pero CDialog es una clase abstracta, por lo que no la
podremos utilizar directamente (no se pueden crear objetos CDialog). El
programador deberá derivar sus propias clases desde CDialog para crear sus
propios diálogos.
- Creación de diálogos
Los pasos para crear una ventana de diálogo son los
siguientes:
- Crear la plantilla de diálogo que se va a utilizar en el fichero de
recursos. Para ello utilizaremos ResourceView y las herramientas de
diseño de diálogos. El diálogo se podrá completar con los controles
necesarios (cajas de edición, listas, botones, etc.). Cada uno de los
controles deberá llevar un identificador único.
- Asignar un identificador a la plantilla de diálogo que se haya
diseñado.
- Invocar ClassWizard
- Seleccionar botón "Añadir clase nueva"
- Definir la nueva clase. Para ello le daremos un nombre y
seleccionaremos la clase base mas adecuada. En este caso seleccionaremos la
clase CDialog. El sistema automáticamente asignará un nombre a los
ficheros de cabecera e implementación donde se codificará la clase (estos
nombres los podemos cambiar). Aparece una lista desplegable donde
seleccionaremos el identificador de la plantilla de diálogo asociado
(especificado en el paso 2).
El sistema genera entonces el esqueleto de la nueva clase, en
los ficheros especificados, añadiéndolos al proyecto. Bastará con completar el
código generado e introducir una llamada al diálogo en algún punto del programa
(mediante la función CDialog::DoModal por ejemplo).
- Tratamiento de los controles de diálogos.
La librería MFC aporta también clases representativas de los
controles típicos de Windows. Esto nos permite crearlos de manera dinámica
(mediante código) o manejarlos adecuadamente. De entre estas clases, destacan:
- CButton, representa cualquier tipo de botones (pushbuttons, radio
buttons o check boxes).
- CComboBox, representa listas desplegables.
- CListBox, representa listas.
- CEdit, representando cajas de edición.
- CStatic, representando texto estáticos.
Cada una de estas clases deriva de CWnd (no en vano son todas
ventanas especializadas) e incorpora funciones específicas que facilitan su
tratamiento (así por ejemplo las listas aportan funciones que permiten saber el
elemento que tiene seleccionado en cada momento).
Aunque un control puede aparecer dentro de cualquier ventana
(se dice que el control es una ventana hija) es en los diálogos donde mas se
utilizan. De manera general, la ventana padre debe ser capaz de manejar los
controles que la forman (rellenando una lista con las opciones disponibles,
presentando el DNI del empleado seleccionado en la caja de edición al efecto,
etc.). Para conseguir esto disponemos de dos métodos:
- Asociación estática de controles. Este método consiste en la
creación de objetos miembros de la clase de la ventana padre (el diálogo) y
su asociación con cada uno de los controles. Es decir, existe una variable
miembro dentro del diálogo que representa cada uno de los controles. De
manera general no se "mapearán" todos los controles a variables sino sólo
aquellos que necesitemos (aquellos que vayamos a manejar en el programa).
Este trabajo se realiza mediante ClassWizard dentro de su "subcarpeta"
marcada como "Variables". Así un botón se mapea a un objeto CButton, una
lista a un objeto CListBox, etc.
- Asociación dinámica de controles. Mediante este método se
consiguen referencias a los controles durante la ejecución del programa. No
existen variables miembro que se asocien a controles de manera estática
(constante) sino que conseguimos el mismo efecto cuando lo necesitamos. Para
ello disponemos de la función de ventana CWnd::GetDlgItem que recibe
como parámetro el identificador del control del que queremos obtener una
referencia. Hay que destacar que esta función es de la ventana básica CWnd,
lo que significa que cualquier ventana puede acceder a sus ventanas hijas
(controles generalmente) de la misma manera (abstracción). Además, mediante
esa función obtenemos referencias a objetos de distintas clases en función
del tipo de control que queramos, es decir, utilizamos la misma función para
acceder a botones, listas o cajas de edición. En realidad la función
GetDlgItem devuelve referencias a ventanas genéricas (devuelve punteros
a CWnd *) o lo que es lo mismo, devuelve referencias a cualquier tipo de
ventana (no tiene por que ser controles).
- Mensajes de comando de controles de diálogos.
De manera análoga a lo que comentamos con los items
de menú, los controles informan a las ventanas padres de las acciones
que el usuario realiza sobre ellos. Así los botones informan de que se
hizo "click" sobre ellos o las cajas de edición lo hacen de que se
modificó su contenido. El mecanismo utilizado es también el mensaje de
comando WM_COMMAND aunque aquí tiene un enfoque adicional.
Mientras los elementos de menú sólo informan de que
han sido seleccionados los controles pueden informar de distintos
eventos (dependiendo del tipo de control), así siempre es distinto
realizar un click sobre una lista que un doble click. En definitiva, los
mensajes de comando de los controles de diálogos incorporan una nueva
información que se conoce como notificación. Las notificaciones
recogidas por cada tipo de control son distintas, dependiendo de su
naturaleza.
La forma de añadir funciones manejadoras de mensajes
de controles a los diálogos es análoga a la que se describió para los
comandos de menú. Lo realizaremos mediante ClassWizard, pero además de
seleccionar el identificador del control tendremos que seleccionar
también la notificación a la que queremos añadir el nuevo manejador.
- Inicialización de diálogos.
Una vez que hemos visto como obtener un vínculo con
los controles que forman un diálogo hemos de conocer como inicializarlo
para que presente al usuario el aspecto seleccionado. El momento de
inicialización del diálogo viene marcado por la llegada del mensaje
WM_INITDIALOG, cuyo manejador virtual es CDialog::OnInitDialog.
En nuestra clase de ventana de diálogo redefiniremos esta función
virtual para definir nuestra propia inicialización.
- Un diálogo especial - CPropertySheet
La clase CPropertySheet presenta las funcionalidades básicas
de los diálogos basados en pestañas o subcarpetas. En la imagen se presenta un
detalle del diálogo ClassWizard que es de este estilo.
Figura 3.- Detalle de diálogo con subcarpetas.
Estos diálogos están compuestos por una serie de páginas
(subcarpetas), que vienen representadas en la MFC mediante la clase
CPropertySheet. Cada una de estas páginas es básicamente un diálogo de estilos
especiales, pero cuentan con su plantilla correspondiente en el fichero de
recursos del proyecto.
Para construir diálogos de este tipo seguiremos los
siguientes pasos:
- Generar las páginas necesarias. Para ello necesitamos diseñar las
plantillas de cada una de ellas en los recursos, y añadir una clase derivada
de CPropertyPage que las represente (de manera análoga a como se vio en el
punto 9.1)
- Añadir una clase derivada de CPropertySheet al proyecto
mediante ClassWizard.
- Asociar a esta clase las páginas correspondientes. Se realiza
mediante la función miembro CPropertySheet::AddPage, que recibe como
parámetro una referencia a la clase CPropertyPage a añadir. Estas
operaciones se suelen realizar dentro del constructor del propio objeto.
- Crear un objeto de esa clase e invocar la función
CPropertySheet::DoModal
- EL INTERFAZ MDI
El interfaz de documento múltiple es sensiblemente distinto
al ya conocido de documento simple (SDI) en el que nos hemos basada en las
exposiciones previas. Las aplicaciones MDI pueden tener abiertas simultáneamente
varias ventanas marco, conteniendo vistas que visualizan documentos. Las
diferencias principales son las siguientes:
- La ventana principal de la aplicación derivará de CMDIFrameWnd, y
su objetivo principal no es contener una vista como ventana cliente (según
el modelo SDI) sino controlar y gestionar las ventanas marcos existentes.
- Como ventana cliente de la ventana principal aparece la clase de ventana
MDICLIENT. Esta es la verdadera ventana padre de las sucesivas ventanas
marco que se vayan abriendo. La MFC no contempla ninguna clase que
represente este tipo de ventanas ya que su comportamiento por defecto es
totalmente suficiente y estándar. De hecho, a primera vista puede quedar
oculta la existencia de esta ventana cliente ya que en ningún momento
necesitamos crearla (la crean las funciones por defecto).
- Las ventanas marcos derivarán de CMDIChildWnd y son "hijas" de la
ventana MDICLIENT de la ventana principal de la aplicación (recordar que en
el modelo SDI la ventana principal era a su vez ventana marco de documento).
De esta manera, para crear la ventana principal de la
aplicación la función InitInstance invocará a la función
CFrameWnd::LoadFrame(), mientras para crear cualquier ventana marco con su vista
y documento tendremos que utilizar las funciones aportadas por los objetos
CMultiDocTemplate (como CDocTemplate::OpenDocumentFile) que hayamos
definido como plantillas soportadas por la aplicación mediante la función
CWinApp::AddDocTemplate (es decir, el mismo tratamiento descrito arriba para
aplicaciones SDI).
Obviamente, las ventanas principales de este tipo de
aplicaciones, derivadas de CMDIFrameWnd contienen funcionalidades especializadas
en la gestión de las ventanas MDI hijas, destacan las siguientes:
- CMDIFrameWnd::MDIGetActive, que permite obtener una referencia a la
ventana MDI hija (ventana marco con vista y documento) que está activa.
- Funciones como CMDIFrameWnd::MDITile que organiza en mosaico las
ventanas MDI hijas existentes, CMDIFrameWnd::MDICascade que lo hace
en cascada o CMDIFrameWnd::MDIIconArrange que alinea en el área
cliente de la ventana principal los iconos de las ventanas MDI hijas que
estén minimizadas.
La utilización del modelo SDI o MDI dependerá siempre de los
requisitos de nuestra aplicación, de todas maneras el funcionamiento de ambos
modelos es muy similar en su concepción y tratamiento gracias a las
abstracciones prestadas por la MFC.
- CLASES ORIENTADAS A ACCESO A BASES DE DATOS
La MFC proporciona clases que proveen un interfaz de
alto nivel para el tratamiento de bases de datos. Para ello se basa en
dos mecanismos distintos pero de funcionamiento y tratamiento muy
similar:
- Tratamiento mediante ODBC
El término ODBC (Open Data Base Conectivity) indica
que su objetivo es conseguir un tratamiento estándar para distintos
sistemas gestores de bases de datos (SGBD). Su funcionamiento se basa en
el uso de drivers. Estos drivers proveen un interfaz estándar de
programación, ocultando los detalles internos de funcionamiento de cada
SGBD. El estándar ODBC está muy extendido, proveen una librería de
funciones C (el API o SDK de ODBC) que se basa en un SQL estándar como
lenguaje de consulta utilizado, de manera que podemos acceder con el
mismo método a tablas dBase, Oracle o Informix (…) sin mas que disponer
del driver ODBC apropiado para cada SGBD.
Para utilizar una base de datos mediante ODBC entra
en juego lo que se denomina Origen de datos (DSN - Data source
name). Un origen de datos es una entidad identificada por su nombre que
especifica una base de datos concreta, mediante ese DSN podremos acceder
a cualquier tabla de dicha base de datos. La definición de los DSN se
consigue gracias al Administrador ODBC que se suele encontrar
dentro del Panel de Control de Windows (puede que encontremos
instalaciones en las que no aparezca referencias al ODBC ya que es una
extensión de Windows no estándar, aunque en la actualidad es instalado
por la mayoría de los SGBD mas comerciales).
- Tratamiento mediante DAO
El modelo DAO (Data Access Object) es una extensión
del ODBC que está especializada y optimizada para el acceso a bases de
datos de Microsoft Access. DAO ha sido incorporado a la MFC en la última
versión comercializada, ya que antes el tratamiento de bases de datos
Access estaba integrado dentro del propio ODBC.
Dado que el funcionamiento es totalmente análogo
(aunque las clases DAO tienen tratamientos y capacidades específicas)
pero siendo ODBC un método mucho mas general, nos centraremos en el uso
de este último para el desarrollo de este capítulo, realizando el
paralelismo oportuno con el modelo DAO cuando sea necesario.
- Las clases básicas
Dos son los conceptos que se utilizan en el
tratamiento de bases de datos mediante MFC: la base de datos (conjunto
de tablas) y el recordset (conjunto de registros de una tabla). Son
representados mediante las clases CDatabase y CRecordset.
- La clase CDatabase
Presta las funcionalidades básicas para el tratamiento de
bases de datos. Encapsula la conexión con un DSN definido dentro del
administrador ODBC que nos permitirá operar sobre la base de datos asociada.
- Función CDatabase::Connect que permite conectar un objeto
CDatabase con un DSN ODBC. Las función CDatabase::Close permite
liberar esa conexión.
- Funciones de soporte a transacciones (encapsulamiento de varias
operaciones como una operación atómica) como CDatabase::BeginTrans
(marca comienzo de una transacción), CDatabase::CommitTrans (que
cierra y confirma las operaciones realizadas) o CDatabase::Rollback
(que cierra y anula las operaciones). Obviamente, para que este enfoque
funcione el SGBD asociado al DSN debe ser transaccional, lo que podemos
averiguar mediante la función CDatabase::CanTransact.
- La función CDatabase::ExecuteSQL que ejecuta la sentencia SQL que
recibe como parámetro. Con esta función sólo se pueden realizar tareas de
mantenimiento de la base de datos como añadir tablas (CREATE TABLE …),
modificarlas (ALTER TABLE …), eliminarlas (DROP TABLE …), etc. Es decir, al
nivel de CDatabase el SQL se puede utilizar como lenguaje de mantenimiento
de datos, pero no como lenguaje de consulta (sentencias SELECT …) que se
reserva para el objeto CRecordset.
En la mayoría de los casos el trabajo se realizará sin la
necesidad de trabajar al nivel de CDatabase, ya que la mayoría de las
aplicaciones se limitan a consultar y modificar registros de tablas, operaciones
que se realizan al nivel de CRecordset. Sólo en el caso de que nuestra
aplicación tenga requisitos de mantenimiento de la base de datos
(añadir/modificar/borrar tablas, gestión de permisos, usuarios …) necesitaremos
trabajar al nivel de CDatabase, en cuyo caso bastará con crear un objeto de esa
clase, abrirlo, operar sobre él y cerrarlo; en ningún caso necesitaremos derivar
de CDatabase nuestras propias clases para bases de datos.
El análogo de CDatabase en el mundo DAO es CDaoDatabase.
- La clase CRecordset
Representa un conjunto de registros de una determinada tabla
resultado de realizar una consulta sobre ella (un SELECT del SQL), algo análogo
al concepto de cursor, es decir, siempre se asocia a una tabla de una base de
datos. Presenta restricciones como la imposibilidad de realizar consultas
compuestas (registros de mas de una tabla) o las clásicas uniones de las bases
de datos relacionales.
La clase CRecordset es abstracta de manera que
tendremos que derivar nuestros propios recordsets de ella. Entre sus miembros
destacan:
- Su constructor recibe como parámetro una referencia al objeto CDatabase
al cual pertenece la tabla asociada. Por defecto lleva un valor nulo lo que
significa que el proceso por defecto construye su propio CDatabase cuando es
necesario.
- La función CRecordset::GetDefaultConnect, que
especifica la tabla asociada al cursor y el DSN de la base de datos a la que
pertenece.
- Miembro dato CRecordset::m_strFilter, que almacena la
sentencia SELECT con la que se accede a la tabla asociada al recordset.
- Miembro dato CRecordset::m_strSort, que almacena
el(los) nombre(s) de los campos por los que se quiere ordenar los registros
seleccionados.
- Funciones como CRecordset::Open y CRecordset::Close
que permiten abrir y cerrar recordsets.
- Funciones de navegación en el recordset como
CRecordset::MoveFirst, CRecordset::MoveLast,
CRecordset::MoveNext o CRecordset::MovePrev.
- Funciones como CRecordset::AddNew que permite añadir
nuevos registros, CRecordset::Edit que permite modificar el registro
activo o CRecordset::Delete para eliminar el registro activo. En
complemento a las dos primeras funciones aparece CRecordset::Update,
función necesaria para reflejar en la tabla una inserción (AddNew) ó
modificación (Edit).
La clase análoga a CRecordset en el modelo DAO se denomina
CDaoRecordset
La creación de los recordsets necesarios se realizará
mediante ClassWizard. Para ello procederemos a añadir una nueva clase al
proyecto, en el momento de especificar la clase base seleccionaremos CRecordset.
Tras confirmar la operación el sistema nos pedirá que seleccionemos el DSN del
origen de datos ODBC (para lo que debe haber sido definido previamente en el
administrador de ODBC). Una vez realizado esto aparecerá una lista con el nombre
de todas las tablas que forman la base de datos asociada al DSN seleccionado.
Escogeremos una de ellas que se asociará al nuevo objeto recordset. El sistema
como siempre crea un esqueleto de la nueva clase que incluye, entre otras cosas,
un constructor por defecto y la definición de la conexión a la tabla (en la
función CRecordset::GetDefaultConnect). Además, esta clase incorpora un
miembro dato por cada uno de los campos de la tabla asociada, datos que
serviremos para almacenar los valores del registro actual en cada momento. Estas
variables se mapean a cada campo, relación que se define en el cuerpo de la
función CRecordset::DoFieldExchange. Esta función es implementada por
ClassWizard y solo en algunos casos tendremos que modificarla (los veremos a
continuación). Podemos destacar dos tipos de recordsets distintos:
- Tipo Snapshot, donde el recordset es una vista estática del
contenido de la tabla en el momento de su apertura. Cualquier modificación
sobre la misma no se reflejaría en el recordset hasta que no volviese a
abrirse.
- Tipo Dynaset, donde se representa una visión dinámica de las
tablas. Es mas utilizado aplicaciones que trabajan sobre bases de datos
multiusuario o compartidas, donde otras aplicaciones pueden realizar
modificaciones.
Para utilizar un determinado recordset bastará con crear un
objeto de esa clase, rellenar la información de la consulta en m_strFilter
(si no va relleno se recuperarían todos los registros de la tabla), la
información de ordenación en m_strSort (opcional) y llamar a la función
Open que realiza la consulta apropiada. Cuando el recordset haya dejado
de utilizarse invocaremos la función Close que libera los recursos de la
conexión ODBC.
11.3.3 Los
recordsets parametrizados
Uno de los mayores problemas que se le han achacado al ODBC
ha sido el excesivo tiempo de respuesta (factor por el que vio la luz el modelo
DAO). Aunque esto siempre ha dependido de la calidad del driver y del propio
SGBD lo cierto es que operaciones de apertura y cerrado sobre algunas tablas son
realmente lentas; esto, como no, se agrava en el caso de acceso a bases de datos
remotas (soportadas por múltiples SGBD’s como SQL Server, Informix net, etc.) ó
cuando se recuperan muchos registros.
Para solucionar, en alguna medida, este problema aparecen los
recordsets parametrizados. Éstos nos permiten modificar los criterios de
selección de registros de manera dinámica sin necesidad de cerrar y volver a
abrir el recordset. En su lugar se utiliza simplemente la función
CRecordset::Requery que no libera la conexión con el DSN asociado por lo que
es sensiblemente mas rápida.
Para parametrizar un determinado recordset seguiremos como
sigue:
- Incluir miembros datos dentro de la clase que nos servirán como
parámetros en las consultas. La inicialización de estos nuevos miembros la
realizaremos en el constructor de la clase, así como también inicializaremos
el miembro CRecordset::m_nParams con el número de parámetros que
hayamos definido.
- Mapear los parámetros añadidos con los campos asociados. Para
ello tendremos que modificar la función DoFieldExchange de la clase.
Una vez parametrizado, podremos modificar los criterios de
búsqueda del recordset de manera dinámica sin mas que modificar el valor de los
parámetros y refrescar el contenido del cursor mediante Requery.
La utilización de un recordset parametrizado es totalmente
análoga a la de uno estándar. Simplemente basta con rellenar el valor de los
parámetros antes de realizar la apertura de la tabla.
- Manejo de excepciones mediante CDBException
Cuando trabajamos con bases de datos se pueden producir
situaciones anómalas que provocan errores de ejecución de las aplicaciones. Por
ejemplo, el intento de abrir una determinada tabla que ha sido abierta
previamente por otro usuario de modo exclusivo, o invocar la función de pasar al
siguiente registro cuando ya se ha alcanzado el último del recordset, son casos
que provocarán una finalización anómala del programa.
El concepto de excepción permite capturar estas situaciones
especiales antes de que se produzca el error fatal, avisar al usuario y
continuar con la ejecución del programa dentro de lo posible.
De manera general, una excepción se puede producir dentro del
ámbito de cualquier función sin necesidad de que se estén manejando en ella
recordsets o bases de datos. La clase abstracta CException, representa
este concepto. De entre sus clases derivadas destacan:
- CMemoryException, para gestión de excepciones producidas por
operaciones directas sobre memoria (new, delete, …).
- CFileException, para operaciones sobre ficheros (fichero no
existente o de sólo lectura, etc.)
- CDBException y CDaoException para operaciones sobre bases de
datos ODBC y DAO respectivamente.
Cada una de estas clases incorpora funcionalidades propias,
pero su utilización es muy similar, por lo que nos centraremos sobre
CDBException que nos ocupa ahora.
La forma de utilizar las excepciones es como sigue: tenemos
que encapsular la operación que pueda producir la excepción dentro de un bloque
TRY/CATCH, que de manera general tiene el siguiente formato.
TRY
{
Operación que puede producir excepción …
}
CATCH(Clase de excepción a capturar, objeto asociado)
{
Operaciones de recuperación de la excepción …
}
La rama donde se encuentran las operaciones de recuperación de la excepción
sólo se ejecutarán en el caso de que se produzca una excepción de la clase
especificada en la sentencia CATCH, entonces el objeto que le acompaña tomará
los valores que identifican dicha excepción. Mas en concreto, para el caso de
CDBException, el ejemplo sería algo así:
CEmpleadosSet e;
e.m_strFilter = "DEPARTAMENTO = ‘VENTAS’";
TRY
{
e.Open();
}
CATCH(CDBException, e)
{
…
}
END_CATCH
Autor: Fco. Javier Rivilla Lizano
Level data, S.A. - Septiembre 1.997
Trabajo enviado por:
Coria David Marcelo
coriadavid@yahoo.com
Compartir 
Publicación enviada por Coria David Marcelo
Contactar mailto:coriadavid@yahoo.com
Código ISPN de la Publicación EpZVVkEllZoewHZgUQ
Publicado Friday 30 de January de 2004
Ultimas Publicaciones en ilustrados.com
ilustrados.com nace con el fin difundir el conocimiento publicando trabajos de investigación, monografias, tesis, presentaciones powerpoint y afines. Publicar trabajos en ilustrados.com ha alcanzado prestigio y reconocimiento internacional siendo cada vez más el número de académicos, empresas, investigadores, científicos que consultan las publicaciones de nuestro portal.
|