isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙...

42
! " # $ % &’’(

Transcript of isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙...

Page 1: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

1

Introducción a la Programación para Windows con Visual C++

Ignacio Alvarez GarcíaIng. De Sistemas y Automática

Universidad de OviedoEnero de 2005

Page 2: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

1

INDICE1. Programación orientada a entornos gráficos ....................................................................... 12. Introducción a la programación orientada a objetos con C++ ........................................ 4

2.1 Clases y objetos ............................................................................................................... 52.1.1 Funciones constructor y destructor......................................................................... 6

2.2 Clases derivadas............................................................................................................... 72.3 Sobrecarga de funciones ................................................................................................ 8

2.3.1 Sobrecarga de operadores ......................................................................................... 92.4 Funciones virtuales ......................................................................................................... 92.5 Otras características interesantes de C++................................................................. 10

2.5.1 Declaración de variables locales............................................................................. 102.5.2 El puntero this .......................................................................................................... 102.5.3 Polimorfismo ............................................................................................................ 102.5.4 Parámetros por defecto ........................................................................................... 112.5.5 Creación y eliminación dinámica de objetos ........................................................ 122.5.6 Operador referencia (&).......................................................................................... 12

3. Programación en entorno Windows ................................................................................... 143.1 Ventanas......................................................................................................................... 143.2 Servicios del sistema ..................................................................................................... 15

4. Programación con la librería MFC en Visual C++........................................................... 154.1 Tipos de aplicación....................................................................................................... 154.2 Creación del marco de programa ............................................................................... 154.3 Programación de la arquitectura documento/vista.................................................. 18

4.3.1 Implementación del documento ............................................................................ 194.3.2 Implementación de la vista ..................................................................................... 21

4.3.2.1 Funciones y clases para el dibujo .................................................................. 234.3.2.2 Seleccionando diferentes características de dibujo..................................... 244.3.2.3 Escala de dibujo............................................................................................... 254.3.2.4 Funciones auxiliares de la ventana y la vista................................................ 26

4.3.3 Enlazando eventos con el código que los sirve ................................................... 264.3.4 Crear y llamar a cuadros de diálogo....................................................................... 29

4.3.4.1 Creando el cuadro de diálogo con el editor de recursos ........................... 294.3.4.2 Creación de la clase y de las variables miembro asociadas a controles ... 314.3.4.3 Llamada al cuadro de diálogo........................................................................ 324.3.4.4 Cuadros de diálogo con comportamiento dinámico.................................. 334.3.4.5 Inicializando controles en la clase diálogo................................................... 354.3.4.6 Cuadros de diálogo predefinidos .................................................................. 36

4.3.5 Eventos temporizados............................................................................................. 364.3.5.1 Evitando el parpadeo...................................................................................... 37

4.4 Programando aplicaciones MDI y basadas en diálogo............................................ 394.5 Utilizar hilos de procesamiento .................................................................................. 39

Page 3: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

1

1. Programación orientada a entornos gráficosLa programación para entornos gráficos difiere sustancialmente de la programación orientada a interfaz de tipo texto (modo consola). Mientras que en modo consola el programador organiza de forma secuencial tanto las instrucciones de cálculo como las de interacción con el usuario (printf, scanf, getchar, etc.), en un entorno gráfico no está definido el orden exacto en que el usuario interactuará con el programa (ej: pulsar una u otra opción de menú, maximizar la ventana, pulsar un botón, cambiar un texto, etc.). Por ello, la forma de organizar un programa para ambos entornos es bastante distinta (figura 1).En modo consola se van intercalando las sentencias de cálculo y las de interacción con el usuario en la secuencia que desea el programador. Sin embargo, en modo gráfico se ejecuta un bucle permanentemente: se espera por un evento (del usuario o del sistema), se ejecuta el código asociado a ese evento, y se vuelve a esperar el siguiente. Los eventos pueden ser variados: se ha pulsado un botón del ratón, se ha pulsado una tecla, se ha seleccionado una opción de menú, se ha creado una ventana, se ha cambiado el tamaño de la ventana, etc. Además, no se puede definir a priori el orden de los eventos sino que depende del usuario.

SentenciasCálculo y E/S

Alternativa

SentenciasCálculo y E/S

SentenciasCálculo y E/S

Bucle

SentenciasCálculo y E/S

Estilo de programaen modo consola

Estilo de programaen modo gráfico

Esperaevento

Sentenciasev1

Según evento

Sentenciasev2

Sentenciasev3

Sentenciasev4

SentenciasInicialización

Sentenciasev Terminar

Figura 1

Dentro de los diferentes eventos en modo gráfico, es importante comprender los asociados al dibujo de la ventana. A diferencia del modo consola, en que se manda escribir algo en la consola (printf) y esa escritura es ‘permanente’ (esto es, no es necesario refrescarla), en

Page 4: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

2

modo gráfico será necesario redibujar la ventana completa o parte de ella cuando sea requerido, y este requerimiento puede provenir del programa o del sistema (p. ej., la ventana estaba siendo tapada por otra y lo ha dejado de estar), lo cual es indicado mediante un evento. Por ello, en modo gráfico es necesario que el programa tenga almacenado todos los datos necesarios para poder redibujar todo el contenido de la ventana en cualquier momento en que sea recibido al evento ‘redibujar’.

Para realizar la programación de las ventanas y de sus eventos, el Sistema Operativo con entorno gráfico (en nuestro caso Windows) proporciona una serie de funciones en una librería. Ese conjunto de funciones se suele denominar API (Application Programming Interface), que en Windows se llama SDK (Software Development Kit). Las funciones ahí contenidas servirán para gestionar ventanas (crear, redimensionar, cerrar, etc.) de diferentes tipos (normal, menú, botón, cuadro de diálogo, cuadro de texto, lista de selección, etc.), obtener eventos, realizar acciones de dibujo, etc.

Un API es un conjunto de funciones muy extenso, ya que son necesarias muchas y muy variadas funciones para gestionar el entorno de ventanas. Además, muchas de las funcionalidades son muy repetitivas a lo largo de los programas (ej. crear ventana principal con opciones de menú), requiriendo un conjunto de llamadas al API relativamente complejo y pesado incluso para el programa más sencillo.

El siguiente ejemplo muestra, simplemente para ilustrar su complejidad, el código para la programación de una ventana sin ninguna funcionalidad adicional.

Page 5: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

3

#include <windows.h>

const char g_szClassName[] = "myWindowClass";

// Step 4: the Window Procedure (se llama cada vez que se despacha un mensaje en el bucle principal de programa)LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){ switch(msg) {

case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0;}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ WNDCLASSEX wc; HWND hwnd; MSG Msg;

//Step 1: Registering the Window Class wc.cbSize = sizeof(WNDCLASSEX); wc.style = 0; wc.lpfnWndProc = WndProc;

wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wc.lpszMenuName = NULL; wc.lpszClassName = g_szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

if(!RegisterClassEx(&wc)) { MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; }

// Step 2: Creating the Window hwnd = CreateWindowEx(WS_EX_CLIENTEDGE,g_szClassName,"The title of my window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, hInstance, NULL);

if(hwnd == NULL) { MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; }

ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

// Step 3: The Message Loop while(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam;}

Ejemplo.c

Page 6: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

4

Para facilitar este trabajo, muchos entornos de desarrollo (en nuestro caso Visual C++) proporcionan una forma más sencilla de acceder al API, mediante una librería de clases que encapsula la mayor parte de la complejidad, y sólo deja como tarea al programador la realización de las partes específicas de su programa. La librería de clases en Visual C++ se llama MFC (Microsoft Foundation Classes). Así, por ejemplo, la creación de la ventana principal estará encapsulada en una serie de objetos que hacen que se pueda crear el marco del programa ¡sin escribir una sola línea de código adicional a las clases ya disponibles!

Para ayudar aún más, los entornos de desarrollo suelen disponer de utilidades que permiten situar de forma gráfica los elementos de interfaz (menús, botones, cuadros de texto, etc.), e incluso enlazarlos con las funciones de servicio de sus eventos de una forma gráfica e intuitiva. De esta manera, el programador se ahorra de escribir gran cantidad de código (se ahorra no sólo tiempo, sino también errores) y, además, la parte ‘a la vista’ de su programa es muy clara y concisa.

Dado que los elementos de interfaz gráfico se pueden describir y utilizar mucho más fácilmente como objetos, lo habitual es que se utilice la programación orientada a objetos para la programación de estos interfaces.

En lo que sigue de este documento, por lo tanto, se describirán por este orden de una forma muy sucinta:

• Los principios de la programación orientada a objetos.• Los diferentes elementos que aparecen en un programa orientado a entorno

gráfico, particularizando para Windows.• La programación mediante la librería de clases MFC sobre Visual C++ para

entorno Windows.

2. Introducción a la programación orientada a objetos con C++

La programación orientada a objetos permite trasladar a los programas la forma de pensar que tenemos en la solución o descripción de problemas en general. Normalmente nos referimos a entidades (objetos), que solemos clasificar en grupos con características y funcionamiento comunes (clases), y de las cuales nos interesan ciertas características (propiedades) y su funcionamiento (métodos).

Además, al relacionar unos objetos con otros para diseñar sistemas complejos, solemos utilizar la encapsulación, esto es, permitimos que los objetos interactúen entre sí sólo a través de las características y funcionalidades expuestas al exterior, mientras que las características y funcionalidades internas de cada uno son ignoradas por el resto.

El lenguaje C++ es una extensión a C para soportar la programación orientada a objetos (POO). Lo que sigue no es un manual de C++, sino una muy breve introducción para ser capaz de manejar los objetos, propiedades y métodos de las clases de MFC. Para una documentación más exhaustiva se sugiere la lectura de “Aprenda C++ como si estuviera en 1º”, de la Universidad de Navarra, y descargable en Internet.

Para escribir código en C++ se pondrá la extensión ‘.cpp’ a los archivos de código fuente.

Page 7: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

5

2.1 Clases y objetosEl elemento fundamental de la POO es la clase. Una clase es una definición de propiedades y métodos para un conjunto de entidades que comparten características comunes.

En C++, una clase se define de manera similar a una estructura, pero utilizando la palabra clave ‘class’ en lugar de ‘struct’, y pudiendo declarar como integrantes de la misma tanto variables (propiedades) como métodos (funciones que manipulan dichas variables; sólo se indica su prototipo en la declaración de la clase).

Por ejemplo, para la manipulación de números complejos podemos crear una clase Complejo, que incluiría como variables la parte real e imaginaria, y como métodos el cálculo del módulo, argumento, la suma , multiplicación y división de otro complejo, la exponenciación, etc. Tanto las variables como los métodos pueden ser públicos (accesibles desde dentro y fuera del objeto), privados (accesibles sólo desde las funciones de la clase) o protegidos (accesibles desde las funciones de la clase y de sus derivadas).

Las sentencias anteriores contienen la declaración de una clase. Para que tengafuncionalidad, se necesita:

� Escribir el cuerpo de las funciones de la clase Complejo. Para ello, se escribe el prototipo de la función, indicando que pertenece a la clase, y a continuación su cuerpo:

tipodevuelto clase::funcion(params) {}

� Crear variables de tipo Complejo, y utilizar las variables y métodos deseados para cada una de esas variables. Cuando se accede a un método, éste utiliza las variables de la instancia (variable, objeto) sobre la que se aplica.

...class Complejo{// Attributespublic:

float re,im;

// Operationspublic:

float Modulo();float Arg();void AnadirReal(float valor);...

} ;...

EjemploPOO.cpp

Page 8: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

6

Tras la ejecución del código anterior, ‘a’ será el complejo 11+4j, y ‘b’ será el complejo 5+14j.

Mediante el uso de variables y funciones miembro públicas y privadas se puede conseguir que desde el exterior a los objetos de la clase Complejo sólo se pueda acceder a las funcionalidades deseadas, resultando por tanto el comportamiento de dichos objetos una caja negra que sólo puede interactuar con otros elementos de la forma que haya previsto su programador.

Para facilitar la realización de programas orientados a objetos, es habitual que para cada clase diferente se utilicen 2 archivos, uno de cabecera (.h) y otro de implementación (.cpp). En el archivo de cabecera se coloca la definición de la clase, y en el archivo de implementación el cuerpo de las funciones. Para el ejemplo anterior, lo usual sería dividir el código en 3 archivos:

class Complejo{// Attributes...} ;

#include "complejo.h"

float Complejo::Modulo(){...}

void Complejo::AnadirReal(float v){...}...

#include "complejo.h"

main(){

Complejo a,b;float z;

a.re=3; a.im=4;b.re= a.Modulo();b.im=a.im+10;a.AnadirReal(8);

}

2.1.1 Funciones constructor y destructorExisten dos funciones especiales que se pueden añadir a las clases: constructor y destructor. El constructor es una función que se llama exactamente igual que la clase y que no

...class Complejo{...} ;

float Complejo::Modulo(){

float result;result=sqrt(re*re+im*im);

}...void Complejo::AnadirReal(float valor){

re+=valor;}...

main(){

Complejo a,b;float z;

a.re=3; a.im=4; b.re= a.Modulo(); b.im=a.im+10;

a.AnadirReal(8);}

EjemploPOO.cpp

Complejo.h Complejo.cpp EjemploPOO.cpp

Page 9: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

7

devuelve nada (ni siquiera void). El constructor es llamado automáticamente por C++ cuando se crea un nuevo objeto de la clase. Además, puede haber varios constructores con diferentes parámetros. Para el caso de los complejos, podríamos hacer un constructor que dé el valor por defecto 0+0j, y otro que dé un valor inicial dado desde el exterior:

Tras la ejecución del código anterior, ‘a’ será el complejo 0+0j (se ha utilizado el constructor sin parámetros), y ‘b’ será el complejo 5+8j.

De la misma manera existe la función destructor, que es llamada de forma automática por C++ cuando deja de existir el objeto. El destructor se declara como el constructor con una tilde (~) delante.

2.2 Clases derivadasDado que múltiples clases pueden compartir elementos comunes, la POO se suele realizar de forma jerarquizada, escribiendo clases ‘base’ con las partes comunes y derivando a partir de ellas clases más especializadas con otros elementos específicos. Una clase ‘derivada’ de otra hereda todas sus características, esto es, variables y funciones miembro, además de poder añadir las suyas propias. Un objeto de la clase derivada podrá acceder a todas las variables y funciones declaradas en ella, así como a las variables y funciones públicas o protegidas de la clase base. Asimismo, de la nueva clase puede derivarse otra, etc., formándose una jerarquía de clases.Para derivar una clase de otra se indica en su declaración:

class clasederivada : public clasebase{

// Variables y funciones de la clase derivada} ;

#include "complejo.h"

main(){

Complejo a;Complejo b(5,8);

}

...Complejo::Complejo(){ re=0; im=0;}

Complejo::Complejo(float i_re,float i_im){ re=i_re; im=i_im;}...

...class Complejo{...

// Operationspublic:

Complejo();Complejo(float i_re,float i_im);...

} ;

Complejo.h

Complejo.cpp

EjemploPOO.cpp

Page 10: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

8

Puesto que en la misma declaración de la clase derivada se necesita conocer quién es la clase base, se debe realizar en el archivo de cabecera (.h) de la clase derivada la inclusión delarchivo de cabecera de la clase base.Por ejemplo, para realizar una serie de objetos gráficos que se puedan dibujar (línea, cuadrado, cuadrado con texto, elipse, etc.), se podría realizar una jerarquía de clases como la que sigue:

colorBordeColor

NuevoTam(tX,tY)nombre[40]char

MoverA(nueX,nueY)tamX,tamYint

Dibujar()posX,posYint

MétodosPropiedades

Deriva de:ObjetoGrafClase:

colorBordeColor

NuevoTam(tX,tY)nombre[40]char

MoverA(nueX,nueY)tamX,tamYint

Dibujar()posX,posYint

MétodosPropiedades

Deriva de:ObjetoGrafClase:

Dibujar()

MétodosPropiedades

ObjetoGrafDeriva de:LineaClase:

Dibujar()

MétodosPropiedades

ObjetoGrafDeriva de:LineaClase:

PonQuitaFondo(siono)

CambiarFondo(nue)fondoFondo

Dibujar()hayFondoint

MétodosPropiedades

ObjetoGrafDeriva de:ConFondoClase:

PonQuitaFondo(siono)

CambiarFondo(nue)fondoFondo

Dibujar()hayFondoint

MétodosPropiedades

ObjetoGrafDeriva de:ConFondoClase:

Dibujar()radioEsquinaint

MétodosPropiedades

ConFondoDeriva de:RectanguloClase:

Dibujar()radioEsquinaint

MétodosPropiedades

ConFondoDeriva de:RectanguloClase:

Dibujar()

MétodosPropiedades

ConFondoDeriva de:ElipseClase:

Dibujar()

MétodosPropiedades

ConFondoDeriva de:ElipseClase:

CambiarTexto(nue)fuenteFont

Dibujar()texto[100]char

MétodosPropiedades

ConFondoDeriva de:TextoClase:

CambiarTexto(nue)fuenteFont

Dibujar()texto[100]char

MétodosPropiedades

ConFondoDeriva de:TextoClase:

En el ejemplo anterior, la clase Texto tiene las variables miembro posX,posY, tamX, tamY, nombre, colorBorde por ser ObjetoGraf; hayFondo y fondo por ser ConFondo; radioEsquina por ser Rectangulo; y texto y fuente por ser Texto. Desde las funciones miembro de Texto se podrá acceder a aquellas variables miembro de las clases inferiores que hayan sido declaradas públicas o protegidas.Lo anterior es asimismo aplicable a las funciones miembro.

2.3 Sobrecarga de funcionesSi una clase derivada tiene definida una función idéntica (con el mismo nombre, parámetros y tipo devuelto) que en la clase base, se dice que ha sobrecargado dicha función. Cuando se llama a esta función se utiliza la de la clase derivada y no la de la base. Esto permite que las clases derivadas tengan funcionalidades diferentes a la clase base con el mismo nombre de función.

Page 11: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

9

2.3.1 Sobrecarga de operadoresDentro de las funciones que se pueden sobrecargar están los operadores. Esto quiere decir que se puede redefinir el comportamiento de un operador, por ejemplo la suma, para una clase determinada. Así, se podría hacer (ver apartado 2.5.6 para la explicación de & y apartado 2.5.2 para la explicación de this):

Tras la ejecución del código anterior, ‘a’ será el complejo 3+4j, y ‘b’ será el complejo 5+12j.

2.4 Funciones virtualesCuando se utilizan funciones sobrecargadas, en muchos casos es necesario llamar a la función de la clase derivada desde la clase base. En el ejemplo anterior, si se utiliza una tabla de objetos gráficos de forma genérica, para cada uno de ellos se utilizará la función de dibujo apropiada de la clase derivada. Cuando se desea utilizar una función sobrecargada por una clase derivada desde una referencia a la clase base, debe indicarse que esa función es virtual. El siguiente ejemplo muestra este comportamiento para los objetos gráficos (ver significado de new y delete en 2.5.5):

#include "Complejo.h"

main(){

Complejo a,b;

a.re=3; a.im=4;b.re=2; b.im=8;

b+=a;}

...Complejo& Complejo::operator +=(const Complejo& otro){ re+=otro.re; im+=otro.im; return *this;}

...

...class Complejo{...

// Operationspublic:

Complejo& operator +=(const Complejo& otro);...

} ;...

Complejo.h

Complejo.cpp

EjemploPOO.cpp

Page 12: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

10

En el ejemplo anterior, el tipo de ‘uno’ es ObjetoGraf, pero se puede crear el objeto de cualquier clase que derive de ObjetoGraf. Si el operador selecciona n==1, ‘uno’ apuntará a un objeto de tipo Texto (que también es ObjetoGraf), y si selecciona n==2 ‘uno’ apuntará a un objeto de tipo Elipse.

Si la función Dibuja() no es virtual, se llamaría a la función Dibuja() de ObjetoGraf, ya que se resuelve a qué función se llama en tiempo de compilación y ‘uno’ es de tipo ObjetoGraf.

Si la función Dibuja() es virtual, en tiempo de ejecución se comprueba el tipo actual del objeto a que apunta ‘uno’ (Texto o Elipse), y se llama a la función adecuada: Texto::Dibuja() si se había escogido n==1, o Elipse::Dibuja() si se había escogido n==2.

2.5 Otras características interesantes de C++A continuación se describen otras características del lenguaje C++ que pueden ser necesarias o útiles.

2.5.1 Declaración de variables localesEn C++ las variables locales pueden declararse en cualquier punto del programa, y no sólo al principio de un bloque de código como en C.

2.5.2 El puntero thisDentro de las funciones de una clase existe la variable implícita ‘this’, que es un puntero al objeto de la clase que se está utilizando en ese momento.

2.5.3 PolimorfismoEn C++ se pueden declarar varias funciones con el mismo nombre pero con parámetros o tipo devuelto distintos. El compilador escogerá para cada llamada la función que se corresponda con los parámetros y el tipo devuelto de cada llamada.Ejemplo:

#include "Texto.h"#include "Elipse.h"

main(){

ObjetoGraf *uno;int n;

// Pide valor de n...if (n==1)

uno=new Texto;else

uno=new Elipse;

uno->Dibuja();

delete uno;}

EjemploGraf.cpp

Page 13: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

11

2.5.4 Parámetros por defectoSe pueden declarar parámetros por defecto en una función, poniendo en su declaración (en el .h) el valor por defecto. Si esos parámetros no se pasan, se utiliza el valor por defecto.Por ejemplo, en el caso anterior se podían haber unificado las versiones 1 y 2 de Sumar:

...class Complejo{...

// Operationspublic:

void Sumar(float real,float imag=0);void Sumar(const Complejo& otro);...

} ;

#include "Complejo.h" main(){

Complejo a,b;

a.re=3; a.im=4;b.re=2; b.im=8;

a.Sumar(3,2); // Usa 1ª versión de Sumar a.Sumar(2); // Usa 2ª versión de Sumara.Sumar(b); // Usa 3ª versión de Sumar

}

...void Complejo::Sumar(float real,float imag){ re+=real; im+=imag;}

void Complejo::Sumar(float real){ re+=real;}

void Complejo::Sumar(const Complejo& otro){ re+=otro.re;

im+=otro.im;}...

...class Complejo{...

// Operationspublic:

void Sumar(float real,float imag);void Sumar(float real);void Sumar(const Complejo& otro);...

Complejo.h

Complejo.cpp

EjemploPOO.cpp

Complejo.h

Page 14: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

12

2.5.5 Creación y eliminación dinámica de objetosSi se desea crear de forma dinámica un objeto, se utiliza el operador new seguido de la clase que se desea crear. Este operador realiza la asignación dinámica de memoria para el objeto, llama al constructor del mismo, y por último devuelve un puntero al objeto creado para poderlo utilizar. O sea, equivale a un malloc(sizeof(clase)) seguido de un clase::clase(). Además, se puede hacer que en la creación llame a otro constructor pasando los parámetros del mismo.Cuando se desea eliminar un objeto creado dinámicamente se utiliza delete, que llama al destructor y luego elimina la memoria asignada.

2.5.6 Operador referencia (&)En C++ existe un nuevo operador & que se aplica a la declaración de una variable o del tipo devuelto por una función, en cuyo caso esta variable o valor devuelto se convierten en una referencia, esto es, algo así como un puntero no modificable. Así, si se hace:

#include "Complejo.h" main(){

Complejo *ptA,*ptB;

ptA=new Complejo; // *ptA vale 0+j0 (constructor por defecto)ptB=new Complejo(5,5); // *ptB vale 5+j5 (constructor con 2 parámetros)

ptA->Sumar(*ptB);

delete ptA;delete ptB;

}

#include "Complejo.h" main(){

Complejo a,b;

a.re=3; a.im=4;b.re=2; b.im=8;

a.Sumar(3,2); // Usa 1ª versión de Sumar a.Sumar(2); // Usa 1ª versión de Sumar con imag=0a.Sumar(b); // Usa 3ª versión de Sumar

}

...void Complejo::Sumar(float real,float imag){ re+=real;

im+=imag;}

void Complejo::Sumar(const Complejo& otro){ re+=otro.re; im+=otro.im;}...

Complejo.cpp

EjemploPOO.cpp

EjemploPOO.cpp

Page 15: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

13

int x;int& y=x;

‘y’ es una referencia a ‘x’, o sea, es la misma variable con otro nombre: cualquier cosa que se haga con ‘y’ se está haciendo con ‘x’ y viceversa.Como parámetros de funciones, las variables de tipo referencia sirven para indicar el mismo objeto con otro nombre (algo similar a haber pasado un puntero a la variable, con la diferencia de que no hay que acceder con *puntero y que además esa referencia es fija, no se puede hacer que apunte a otra cosa).Como valores de salida de funciones, sirven para dar una referencia al valor devuelto. En POO es muy habitual que una función pueda devolver una referencia al propio objeto (*this); ver ejemplo en 2.3.1. Ojo: no devolver una referencia a una variable local de la función, porque dicha variable desaparecerá al salir de la función.

Page 16: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

14

3. Programación en entorno WindowsEl elemento fundamental de las aplicaciones de Windows son las ventanas. Todas las aplicaciones (o casi todas) incluyen, al menos, una ventana. El API de Windows dispone de funciones para la gestión de todas sus funcionalidades.Dentro de estas funcionalidades están:

• Generar la salida (dibujar) sobre las ventanas. Existen un subconjunto de funciones del API, llamado GDI (Graphics Device Interface), que permite dibujar sobre las ventanas. Tiene la característica fundamental de ser independiente del dispositivo, lo que permite a las diferentes ventanas compartir del espacio de pantalla, enviar la salida a impresora u otros dispositivos, etc.

• Recibir y procesar las entradas del usuario, fundamentalmente a través de pulsación de teclas y movimientos y pulsaciones del ratón.

• Utilizar ventanas especiales con comportamiento predefinido, llamadas controles.• Utilizar datos especiales con informaciones específicas, como mapas de bits para

representar gráficos (bitmaps), iconos, menús, cadenas de caracteres, teclas abreviadas (aceleradores), etc. Estos datos son almacenados como ‘recursos’ asociados a la aplicación, y pueden ser fácilmente utilizados en varias.

3.1 VentanasTodas las ventanas tienen una serie de propiedades y eventos comunes, entre los que se pueden destacar:Tipo objeto Propiedades fundamentales Eventos de interésVentana Identificador

Título (caption)Tipo de ventanaPosición x,yTamaño x,y¿Es visible? ¿Está habilitada para E/S? (enabled)¿Es la ventana activa?

Ha sido creadaSe ha movidoSe ha cambiado su tamañoSe va a cerrarEl ratón se ha movidoEl ratón se ha pulsadoSe ha pulsado una teclaHay que redibujarla…

Existen diversas ventanas predefinidas de comportamiento particular, que ya tienen implementadas ciertas funcionalidades específicas para intercambiar datos con el usuario. En general, el uso de estas ventanas será muy cómodo ya que el programador sólo tiene que aportar los datos particulares de cada momento, y no debe preocuparse por implementar su comportamiento. Entre ellas caben destacar las siguientes (recuérdese que heredan todas las propiedades y métodos de las ventanas, y que los eventos básicos: tecla pulsada, ratón, redibujo, etc. ya está implementados internamente):

Tipo objeto Propiedades fundamentales Eventos de interésBotón Se ha hecho click en el botón

Se ha hecho doble-click en el botónBotón chequeable

¿Está seleccionado? Se ha cambiado la selección

Cuadro de texto

Texto introducido Se ha cambiado el texto

Page 17: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

15

Lista de selección

Nº de opcionesCadenas con las opcionesIndice de la opción seleccionada

Se ha cambiado la selección

Lista combo Como un cuadro de texto + lista de selección

Como un cuadro de texto + lista de selección

3.2 Servicios del sistemaNo toda la funcionalidad del API de Windows está centrada en las ventanas. También dispone de funciones para realizar el resto de llamadas necesarias al sistema operativo, entre las cuales cabe destacar funciones para:

• Gestión de E/S a archivos, directorios y dispositivos.• Creación y gestión de procesos e hilos, y la sincronización entre los mismos.• Gestión de memoria: petición de asignación de memoria, compartición, etc.• Gestión de funcionalidades compartidas entre varios programas: librerías de enlace

dinámico (DLL).• Gestión de excepciones.• Comunicaciones en red.

4. Programación con la librería MFC en Visual C++La librería MFC es una librería de clases que encapsula el comportamiento más complejo del sistema de ventanas, permitiendo al programador escribir únicamente las variables y funciones que constituyen la funcionalidad específica de su programa. Por ejemplo, el comportamiento de la función main(), incluyendo el bucle de procesamiento de mensajes, está encapsulado en la clase CWinApp ya escrita, por lo que el programador simplemente tiene que escribir el código de servicio de los eventos que desea gestionar.

4.1 Tipos de aplicaciónExisten múltiples tipos de aspectos que presentan las aplicaciones en entorno Windows, aunque las más comunes son las siguientes:

• Formato SDI (Single Document Interface): una aplicación que tiene una sola ventana con menú y una zona interior en la que se puede dibujar (ej. Matlab).

• Formato MDI (Multiple Document Interface): aplicación con una ventana principal con los menús, y varias ventanas posibles en el interior, donde cada una normalmente se refiere a un documento diferente (ej. Word, Visual C++).

• Formato cuadro de diálogo: aplicación que tiene el aspecto de un cuadro de diálogo, con diferentes elementos de interfaz (ej. programas de instalación).

En lo que sigue se trabajará sobre el caso de una aplicación básica SDI. Posteriormente se comentarán las diferencias de los otros casos. Para facilitar la comprensión, se realizará un programa de ejemplo que dibuje en la pantalla una tabla de datos X/Y, siendo los datos generados por una función de cálculo cada vez que se pulse una opción de menú.

4.2 Creación del marco de programaEl entorno de programación provee una forma de generar las clases por defecto. Para ello, se debe crear un nuevo proyecto y seleccionar la opción MFC AppWizard (exe).

Page 18: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

16

A continuación, se debe seleccionar el aspecto de la aplicación. Como se ha indicado anteriormente, trabajaremos sobre el caso de una aplicación SDI.

Page 19: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

17

Las siguientes ventanas presentan distintas opciones (añadir soporte de base de datos, de controles ActiveX, de conexiones de red, etc.). Para un caso sencillo se pueden aceptar las selecciones por defecto, excepto en el paso 4, donde se debe seleccionar la patilla Advanced… para elegir, al menos, la extensión por defecto de los archivos de datos asociados a la aplicación (documentos) y el título de la ventana principal.

En los siguientes pasos se pueden aceptar las opciones por defecto, llegándose al final en que se crea el marco del programa.

Page 20: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

18

4.3 Programación de la arquitectura documento/vistaTras la ejecución de los pasos anteriores se ha creado de forma automática el marco de nuestro programa con el aspecto SDI y la arquitectura documento/vista. Este marco de programa consta de 4 clases fundamentales: aplicación, ventana principal, documento y vista, cada una de ellas con las definiciones en un archivo .h y la parte ejecutiva en un .cpp (ver figura anterior). Además de las clases anteriores se ha creado también un archivo de recursos (.rc) donde se puede editar de forma gráfica los elementos del interfaz.

Para continuar, es necesario comprender el formato de la arquitectura documento/vista dispuesta por MFC para este caso.

De las 4 clases creadas, 2 son las básicas para el programa: la clase documento (en el ejemplo se llama CEjemploDoc), que deriva de la clase CDocument implementada en MFC, y la clase vista (en el ejemplo se llama CEjemploView), que deriva de la clase CView de MFC. Conviene recordar nuevamente que se puede obtener ayuda en línea de las clases MFC pulsando F1.

En la clase documento (CEjemploDoc) implementaremos, además de los elementos que escribe automáticamente el entorno de programación, los datos que manejará nuestra aplicación y las funciones que los manipulan, excluyendo todo aquello que se refiera a la interacción con el usuario.

En la clase vista (CEjemploView) se implementará todo lo relacionado con la visualización e interacción con el usuario de la aplicación.

Page 21: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

19

De esta forma, esta arquitectura separa claramente la funcionalidad del programa, esto es, lo que queremos que haga, calcule, archive, etc., que estará en el documento; de la interfaz del programa, que se ubicará en la vista. Evidentemente, ambos objetos deberán estar relacionados: la vista debe mostrar los datos del documento, y además debe permitir al usuario modificarlos de acuerdo con las normas del interfaz.

En el ejemplo propuesto, sería lógico situar en el documento las tablas de datos X/Y, que es lo que queremos que manipule el programa, mientras que en la vista estarían datos auxiliares para el dibujo como la escala de los ejes, el color de la pluma de dibujo, etc.

Como norma a aplicar, se puede decir que se deben situar en el documento todos aquellos datos que deseamos que se almacenen en archivo para recuperar el estado del programa, mientras que se sitúan en la vista sólo aquellos aspectos que afectan a la visualización instantánea de la aplicación. En el caso de ejemplo, si consideramos que el color de dibujo es un dato que pueda ser salvado y recuperado, esto es, que forma parte de los datos básicos para que la aplicación recupere su funcionalidad, debería estar en el documento.

4.3.1 Implementación del documentoEn el ejemplo propuesto, se declararían en CEjemploDoc los datos necesarios (utilizamos tablas estáticas por mayor comodidad).

También en el documento se incluirán aquellas funciones que manipulan los datos. Por ejemplo, la función que genera un nuevo dato según el algoritmo deseado (utilizaremos una función senoidal de amplitud creciente para el ejemplo). En el archivo “.h” se declara el prototipo de la función perteneciente a la clase documento, y en el “.cpp” el código a ejecutar.

Existen funciones pre- declaradas en la clase documento que se deben rellenar: la función OnNewDocument() será llamada cada vez que se cree un nuevo documento, por lo que se implementará en ella la inicialización de los datos; la función Serialize() será llamada cada

...class CEjemploDoc : public CDocument{protected: // create from serializ ...

CEjemploDoc();DECLARE_DYNCREATE(CEjemploDoc)

// Attributespublic:

int datosX[500],datosY[500];int nDatos;float frec;

// Operationspublic:

// Overrides// ClassWizard generated ...//{{AFX_VIRTUAL(CEjemploDoc)public:virtual BOOL OnNewDocument();...

} ;

EjemploDoc.h

Page 22: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

20

vez que el usuario solicite la lectura o almacenamiento de datos en un documento, y en ella se implementará el código de lectura y escritura en archivo (ver ayuda de CArchive).

...

BOOL CEjemploDoc::OnNewDocument(){

if (!CDocument::OnNewDocument())return FALSE;

// TODO: add reinitialization ...// (SDI documents will reuse ...

nDatos=0;frec=10.0F;return TRUE;

}

////////////////////////////////////////// CEjemploDoc serialization

void CEjemploDoc::Serialize(CArchive& ar){

int i;char linea[40];

if (ar.IsStoring()){

// TODO: add storing code heresprintf(linea,"%d\n",nDatos);ar.WriteString(linea);for (i=0;i<nDatos;i++){

sprintf(linea,"%5d %5d\n", datosX[i],datosY[i]);

ar.WriteString(linea);}

}else{

// TODO: add loading code herear.ReadString(linea,40);sscanf(linea,"%d",&nDatos);for (i=0;i<nDatos && i<500;i++){

ar.ReadString(linea,40);sscanf(linea,"%d%d",

&datosX[i],&datosY[i]);}

}}

...

//////////////////////////////////////// CEjemploDoc commands

#include <math.h>

void CEjemploDoc::CalcNuevoDato(){

if (nDatos<0 || nDatos>=500)// Evita salirse de la tablareturn;

datosX[nDatos]=nDatos;datosY[nDatos]=(int) (nDatos *

sin(6.28*frec*nDatos));

nDatos++;}

...class CEjemploDoc : public CDocument{protected: // create from serial ...

CEjemploDoc();DECLARE_DYNCREATE(CEjemploDoc)

// Attributespublic:

int datosX[500],datosY[500];int nDatos;float frec;

// Operationspublic:

void CalcNuevoDato();

// Overrides// ClassWizard generated ...//{{AFX_VIRTUAL(CEjemploDoc)public:virtual BOOL OnNewDocument();...

} ;

EjemploDoc.h EjemploDoc.cpp

Page 23: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

21

4.3.2 Implementación de la vistaEn la clase Vista (CEjemploView) se almacenarán las variables y se escribirá el código relacionado con la interfaz de usuario. En una aplicación SDI, los elementos básicos de interfaz son la propia ventana (entradas de teclado y ratón, y dibujo de la ventana como salida), el menú de la aplicación, y las opciones de sistema de la ventana (minimizar, maximizar, cerrar, cambiar tamaño). De estos elementos, únicamente variarán de forma importante de un programa a otro el menú de la aplicación y el dibujo a realizar en la ventana.

Para diseñar el menú de la aplicación se utilizará el editor de recursos, que nos permite añadir las opciones deseadas de menú de forma gráfica viendo el aspecto final que tendrá. Para el caso del ejemplo, se añadirá un menú con 2 opciones: cambiar frecuencia, ejecutar un nuevo paso. Las figuras siguientes muestran la secuencia de pasos.

1) Seleccionar el editor de recursos (ventana izquierda) y abrir el menú principal:

2) Hacer doble clic en una opción de menú vacía para añadir un nuevo menú, indicando su texto en la opción Caption:

3) Hacer doble clic en la primera opción de submenú del anterior para añadir una opción de submenú, indicando el texto y el nombre de una constante que hará de identificador (si no se introduce el nombre de esta constante, Visual C++ asignará un nombre adecuado para ella).

Page 24: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

22

4) Id. para la segunda opción de submenú.

Para adaptar el dibujo de la ventana a lo que deseamos, se escribirá el código necesario en la función OnDraw() de la ventana, que es llamada de forma automática cada vez que se produce un evento que requiere el redibujo de la ventana.

En la función CEjemploView::OnDraw() se utilizarán 3 objetos fundamentalmente:• El objeto documento, apuntado por pDoc, donde se encuentran nuestros datos y

funciones de ejecución.• Un objeto contexto de dispositivo (CDC), apuntado por pDC, que servirá para

realizar todas las operaciones gráficas sobre la ventana: dibujar líneas, polígonos, rectángulos, elipses, texto, etc. (ver ayuda de CDC).

• Los objetos base de CEjemploView: CView y principalmente CWnd, que contienen funciones generales para todo tipo de vistas y de ventanas (ver ayuda de CWnd).

Page 25: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

23

4.3.2.1 Funciones y clases para el dibujoComo se ha indicado anteriormente, la clase CDC es la fundamental en lo relativo al dibujo de la ventana. Un CDC encapsula un ‘contexto de dispositivo’, ente de dibujo que permite que las órdenes sean independientes del dispositivo y que las traslada al dispositivo a que esté ligado (ventana, impresora, etc.) de forma transparente para el usuario. Básicamente, con el objeto de tipo CDC se puede:

• Dibujar elementos gráficos básicos: líneas rectas, arcos, polilíneas, rectángulos, elipses, texto, bitmaps (matrices de valores que representan una imagen gráfica) y regiones.

• Seleccionar las características de dibujo de los elementos anteriores: pluma para líneas y bordes de objetos (pen), relleno de líneas cerradas (brush), fuente para texto (font), juego de colores utilizado (palette).

• Utilizar varios sistemas de coordenadas y convertir entre unos y otros. En el sistema de coordenadas por defecto, la posición superior-izquierda de la zona de dibujo tiene las coordenadas (0,0); la coordenada X avanza hacia la derecha y la coordenada Y hacia abajo.

• Enviar la salida a impresora en forma de páginas.• Crear contextos de dispositivo compatibles para dibujar en segundo plano.

Ejemplos de funciones de dibujo utilizadas habitualmente de CDC son:• MoveTo() y LineTo(), para dibujar una línea recta desde un punto a otro.• Polyline() para dibujar de una sola vez un conjunto de rectas.• Arc() para dibujar un arco.• TextOut() para dibujar texto en una determinada posición.• Ellipse() para dibujar una elipse.• Rectangle() para dibujar un rectángulo.• Polygon() para dibujar un polígono cerrado.

Ver la ayuda de CDC (class members) para conocer la funcionalidad y formato de estas funciones.

...////////////////////////////////////////////////////////// CEjemploView drawing

void CEjemploView::OnDraw(CDC* pDC){

CEjemploDoc* pDoc = GetDocument();ASSERT_VALID(pDoc);// TODO: add draw code for native data here

int i;pDC->MoveTo(pDoc->datosX[0],300+pDoc->datosY[0]);for (i=0;i < pDoc->nDatos; i++)

pDC->LineTo(pDoc->datosX[i],300+pDoc->datosY[i]);

char texto[40];sprintf(texto,“Nº DATOS: %d”,pDoc->nDatos);pDC->TextOut(100,10,texto);

}

EjemploView.cpp

Page 26: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

24

Algunas de ellas necesitan el uso de clases auxiliares, como:• CPoint: encapsula la estructura POINT del SDK de Windows, en la que se

almacena la posición de un punto. Tiene dos variables miembro, x e y.• CSize: id. para la estructura SIZE del SDK, que se utiliza para el tamaño. Variables

miembro cx y cy.• CRect: id. para la estructura RECT, que se utiliza para un rectángulo. Variables

miembro left, top, right, bottom.• CString: importante clase que encapsula el funcionamiento de una cadena de

caracteres. Evita al programador tratar con la parte de asignación de memoria, uso de instrucciones de cadena, etc., encapsulando estas necesidades en el interior de la clase y ofreciendo un interfaz más intuitivo. Así, por ejemplo, esta clase redefine los operadores =, ==, +, +=, etc. que facilitan mucho el trabajo del programador. Importante ver la ayuda de CString para utilizar todas sus características. La siguiente figura muestra un ejemplo en el que se ve la mejora que supone.

4.3.2.2 Seleccionando diferentes características de dibujoEl dibujo de líneas, el relleno de objetos cerrados, y la fuente de texto son seleccionables. Para ello, el contexto de dispositivo tiene siempre asociados una pluma (pen), un relleno (brush) y una fuente (font), que pueden ser cambiados por el programador. Para seleccionar características distintas en cualquiera de estos objetos, se crea un nuevo objeto, se selecciona en el contexto de dispositivo, y a partir de ahí todo el dibujo se realiza con el nuevo.En MFC, la clase CPen encapsula a una pluma, la clase CBrush a un relleno, y la clase CFont a una fuente. El siguiente código muestra como ejemplo la utilización de una pluma distinta en el dibujo deseado. Para ello, se siguen los siguientes pasos (ver ayuda de las funciones):

• Crear una nueva pluma (pen), con las características: sólida, 2 puntos de grosor, color rojo (la macro RGB permite crear un color indicando las componentes rojo, verde y azul con valores entre 0 y 255).

• Seleccionar el objeto pluma creado en el contexto de dispositivo, obteniendo una referencia a la pluma anterior para recuperarla cuando se acabe.

// Concatenar cadenas con un // espacio en medio y comparar con // una constanteCString cad1(”NOMBRE”), cad2(”APELLIDO1”), cad3(”APELLIDO2”), todo;

todo=cad1+" "+cad2+" "+cad3;if (todo=="PEPE PEREZ PEREZ”){

...}

// Concatenar cadenas con un // espacio en medio y comparar con // una constantechar cad1[]=”NOMBRE”, cad2[]=”APELLIDO1”; cad3[]=”APELLIDO2”; char *todo;todo=(char*) malloc(strlen(cad1)+ strlen(cad2)+ strlen(cad3)+3);strcpy(todo,cad1);strcat(todo," ");strcat(todo,cad2);strcat(todo," ");strcat(todo,cad3);if (strcmp(todo, "PEPE PEREZ PEREZ")==0){

...}free(todo);

Sin CString Con CString

Page 27: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

25

• Dibujar con la nueva pluma.• Seleccionar el objeto pluma anterior cuando se ha terminado con la nueva.• Eliminar la memoria asignada por Windows al objeto pluma creado.

Se ha incluido también el uso de CString para mejorar el tratamiento del texto (no es necesario indicar su tamaño máximo a priori ni hacer cálculos para asignar memoria dinámica: lo hace la clase CString internamente).

4.3.2.3 Escala de dibujoHay 2 escalas a tener en cuenta para realizar el dibujo en la ventana: la escala de las unidades del documento (X,Y), y la escala en pixels de la ventana de dibujo (Xp,Yp). Cuando se quiere dibujar algo de una escala propia, previamente se tiene que “mapear” a la escala en pixels. El ejemplo muestra una gráfica XY que se desea dibujar de frecuencia vs tiempo, cuyas unidades son evidentemente muy distintas a pixels:

...////////////////////////////////////////////////////////// CEjemploView drawing

void CEjemploView::OnDraw(CDC* pDC){

CEjemploDoc* pDoc = GetDocument();ASSERT_VALID(pDoc);// TODO: add draw code for native data here

int i;CPen pen(PS_SOLID,2,RGB(255,0,0)),*aPen;aPen=(CPen*) pDC->SelectObject(&pen);// Ahora todo lo que sigue utiliza la nueva plumapDC->MoveTo(pDoc->datosX[0],300+pDoc->datosY[0]);for (i=0;i < pDoc->nDatos; i++)

pDC->LineTo(pDoc->datosX[i],300+pDoc->datosY[i]);pDC->SelectObject(aPen);pen.DeleteObject();

CString texto;texto.Format(“Nº DATOS: %d”,pDoc->nDatos);pDC->TextOut(100,10,texto);

}

EjemploView.cpp

Page 28: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

26

Todas las órdenes de dibujo en la ventana se deben realizar en coordenadas de pixels (Xp,Yp) de la propia ventana. En muchos casos el dibujo se realiza proporcional al tamaño de ventana; para conocerlo, se ejecutará la orden:

CRect rect=GetClientRect();

la cual devuelve las dimensiones de la ventana de dibujo en el objeto rect.El cambio de escala necesario para dibujar datos que están en la escala X,Y se muestra en la figura siguiente:

4.3.2.4 Funciones auxiliares de la ventana y la vistaAdemás de las funciones de dibujo del CDC, CEjemploView puede utilizar funciones auxiliares de CView y CWnd, de las que deriva. De ellas, serán de la mayor utilidad las de CWnd. Es importante ver la ayuda de esta clase, porque dispone de muchas y muy variadas funciones. Por destacar algunas de las más útiles para la apariencia gráfica:

• GetClientRect(), para obtener un rectángulo con las dimensiones de la zona dedibujo de la ventana, con lo cual se puede escalar adecuadamente el dibujo.

• MessageBox(), para presentar una ventana de mensaje.• ShowWindow(), para mostrar u ocultar la ventana.• EnableWindow(), para habilitar o no la entrada (teclado y ratón) sobre la ventana.• SetWindowText(), para cambiar el título (caption) de la ventana.

4.3.3 Enlazando eventos con el código que los sirveA grandes rasgos, se puede decir que el resto del interfaz de usuario será generado sirviendo los eventos generados por el usuario o el sistema. De ellos, los más usados serán:

• Se ha pulsado una opción de menú.

Page 29: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

27

• Se ha pulsado una tecla.• Se ha movido el ratón o se ha pulsado un botón del mismo.• Se ha creado la ventana.• Se ha cerrado la ventana.• Se ha redimensionado la ventana.• Ha vencido un temporizador asociado a la ventana.

Para crear las funciones que sirven estos eventos y enlazarlas con los mismos recurriremos a la utilidad ClassWizard (menú View…ClassWizard, tecla abreviada ctrl.-W). En esta utilidad se selecciona el panel Message Maps, y se siguen los siguientes pasos:

1. Seleccionar la clase vista (cuadro combo Class name), ya que sobre ella realizaremos las acciones de interfaz.

2. En la lista de selección Object IDs, podremos escoger: identificadores de elementos de interfaz (por ejemplo el identificador de las opciones de menú que añadimos anteriormente), o bien la clase CEjemploView en tal que vista. Cuando escogemos alguno de estos elementos, la lista de selección mensajes reflejará los eventos que podemos servir para el mismo.

3. Escogemos uno de los mensajes, y a la derecha se activa el botón Add Function, que nos permite añadir una nueva función a la clase vista que sirva al evento seleccionado. En algunos casos se nos permite introducir el nombre de la función (nos sugiere un nombre adecuado según el evento que se sirve), y en otros ese nombre viene obligado.

4. Por último, el botón Edit Code nos permite acceder directamente al código de la función elegida en la clase CEjemploView.

Las siguientes figuras muestran los pasos anteriores para dotar de funcionalidad a la opción de menú CalcularSiguiente que añadimos anteriormente.

Page 30: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

28

El ClassWizard habrá añadido de forma automática el siguiente código a la clase CEjemploView:

En el archivo .h se ha declarado automáticamente la nueva función, y en la clase .cpp se ha declarado el enlace con el comando (ON_COMMAND) y se ha preparado el código para que el programador escriba el cuerpo de la función.En este último lugar es en el que añadiremos la funcionalidad deseada para esta opción de menú, que en este caso debe llamar a la función CalcNuevo() del documento y a continuación forzar el redibujo de la ventana con RedrawWindow() (si no se hace esto, sólo

...BEGIN_MESSAGE_MAP(CEjemploView, CView)

//{{AFX_MSG_MAP(CEjemploView)ON_COMMAND(ID_MISOPCIONES_CALCULARSIGUIENTE, OnMisopcionesCalcularsiguiente)//}}AFX_MSG_MAP

.../////////////////////////////////////////////////////////////////////////////// CEjemploView message handlers

void CEjemploView:: OnMisopcionesCalcularsiguiente () {

// TODO: Add your command handler code here

}

...class CEjemploView : public CView{...// Generated message map functionsprotected:

//{{AFX_MSG(CEjemploView)afx_msg void OnMisopcionesCalcularsiguiente ();//}}AFX_MSGDECLARE_MESSAGE_MAP()

};...

EjemploView.h

EjemploView.cpp

Page 31: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

29

se verán los cambios en pantalla cuando Windows detecte que es necesario redibujarla por otra causa: cambio de tamaño, había sido tapada y se vuelve a ver, etc.).

4.3.4 Crear y llamar a cuadros de diálogoUno de los modos más habituales de interfaz con el usuario es la presentación de cuadros de diálogo para el intercambio de datos. En el caso del ejemplo, cuando el usuario seleccione la opción de menú “Cambiar Frecuencia”, la acción más adecuada sería presentar un cuadro de diálogo en que se presenta la frecuencia actual, se permite cambiarla, y validar o cancelar este cambio. A continuación se muestran los pasos para este caso, que se resumen en:

• Crear un recurso de tipo cuadro de diálogo, de forma gráfica, incluyendo los elementos deseados (controles) en el mismo.

• Crear una clase derivada de CDialog (ver ayuda), de forma automática, que encapsule el comportamiento del nuevo cuadro de diálogo creado; crear en la nueva clase, de forma gráfica, variables miembro asociadas a los controles introducidos.

• En la clase vista, crear un objeto del tipo de la nueva clase, inicializar las variables miembro, llamar a la función DoModal() para que se ejecute el cuadro de diálogo, y si la misma devuelve IDOK recuperar los valores de las variables miembro.

4.3.4.1 Creando el cuadro de diálogo con el editor de recursosEl primer paso será la creación del aspecto del cuadro de diálogo, para lo cual se dispone del editor de recursos, que permite crear el cuadro de diálogo e incluir en el mismo los controles deseados (botones, cuadros de inserción de texto, listas de selección, etc.)Para el caso del ejemplo, sólo añadiremos un texto fijo y un cuadro de introducción de texto. Para seguir la secuencia, recuérdese que se abre un menú flotante pulsando el botón derecho del ratón sobre el elemento deseado.

.../////////////////////////////////////////////////////////////////////////////// CEjemploView message handlers

void CEjemploView:: OnMisopcionesCalcularsiguiente() {

// TODO: Add your command handler code here

CEjemploDoc* pDoc=GetDocument();

pDoc->CalcNuevoDato();RedrawWindow();

}

EjemploView.cpp

Page 32: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

30

1) Crear nuevo cuadro de diálogo en el panel Recursos de la ventana de proyecto.

2) Ajustar propiedades del cuadro de diálogo, al menos identificador y título.

3) Añadir los controles deseados, ajustando las propiedades de los mismos. Es importante escribir identificadores que después nos permitan reconocer fácilmente el tipo de control y su funcionalidad en nuestro diálogo (no necesario para los controles cuyas características no vamos a necesitar en tiempo de programación, por ejemplo los textos estáticos que no varían).

Page 33: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

31

4.3.4.2 Creación de la clase y de las variables miembro asociadas a controles

Una vez dibujados los controles deseados, se llama a ClassWizard (menú Ver … ClassWizard), el cual preguntará si se desea crear una nueva clase que encapsule el comportamiento del cuadro de diálogo creado. Tras contestar afirmativamente y dar un nombre adecuado a la nueva clase (ejemplo: CCambioFrecDlg), se pasa a la pestaña variables de ClassWizard y se asocian nuevas variables con los controles introducidos. Para el ejemplo, se creará una variable de tipo ‘float’ asociada al control de texto IDC_EDIT_FRECUENCIA.

1) Invocar a ClassWizard para crear nueva clase asociada al diálogo.

2) Introducir las variables miembro asociadas a los controles deseados.

En la nueva clase CCambiarFrecDlg se habrá creado una variable ‘float’ llamada m_frec, y que se asocia automáticamente con el contenido del control.

Page 34: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

32

4.3.4.3 Llamada al cuadro de diálogoPara llamar al cuadro de diálogo en el momento deseado, esto es, al pulsar la orden de menú Mis Opciones … Cambiar Frecuencia, se añade mediante ClassWizard la función que gestionará este evento en la clase CEjemploView (ver apartado 4.3.3). En esta función se escribe el código necesario: creación de cuadro de diálogo, inicialización de variables miembro, llamada a DoModal() y recuperación de variables miembro si se ha salido del diálogo con OK:

...#include "CambiaFrecDlg.h" // Para que conozca a la clase del diálogo

void CEjemploView::OnMisopcionesCambiarfrecuencia() {

// TODO: Add your command handler code here

CEjemploDoc* pDoc=GetDocument(); // Obtiene puntero al documentoCCambiaFrecDlg dlg; // Crea diálogo (no muestra aún)

dlg.m_frec=pDoc->frec; // Inicializa variables del diálogoif (dlg.DoModal()==IDOK) // Presenta el diálogo. Si termina con{ // la pulsación de OK

pDoc->frec=dlg.m_frec; // recupera las variables del diálogo}

}

...CCambiaFrecDlg::CCambiaFrecDlg(CWnd* pParent /*=NULL*/)

: CDialog(CCambiaFrecDlg::IDD, pParent){

//{{AFX_DATA_INIT(CCambiaFrecDlg)m_frec = 0.0f;//}}AFX_DATA_INIT

}

void CCambiaFrecDlg::DoDataExchange(CDataExchange* pDX){

CDialog::DoDataExchange(pDX);//{{AFX_DATA_MAP(CCambiaFrecDlg)DDX_Text(pDX, IDC_EDIT_FRECUENCIA, m_frec);//}}AFX_DATA_MAP

}...

...////////////////////////////////////// CCambiaFrecDlg dialog

class CCambiaFrecDlg : public CDialog{// Constructionpublic:

CCambiaFrecDlg(CWnd* pParent = NULL); // standardconstructor

// Dialog Data//{{AFX_DATA(CCambiaFrecDlg)enum { IDD = IDD_DIALOG_FRECUENCIA };float m_frec;//}}AFX_DATA

...

CambiarFrecDlg.h

CambiarFrecDlg.cpp

CEjemploView.cpp

Page 35: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

33

4.3.4.4 Cuadros de diálogo con comportamiento dinámicoEn ocasiones, desearemos añadir mayores funcionalidades a un cuadro de diálogo, por ejemplo, que al pulsar una opción se seleccionen unos elementos del cuadro y se deseleccionen otros, o que al cambiar un valor se actualicen otros, etc. Es posible hacerlo de forma sencilla añadiendo funciones que sirvan eventos del diálogo. Los pasos son los mismos que los vistos en 4.3.3 para la vista, pero seleccionando la clase de diálogo como destino.Como ejemplo, primero añadiremos un botón (+) que hace que el valor de la frecuencia se incremente en 1:

1) En la ventana de edición gráfica del recurso cuadro de diálogo, añadimos un botón con identificador IDC_BUTTON_MAS y texto ‘+’ (ver apartado 4.3.4.1).

2) En el ClassWizard seleccionamos el panel MessageMaps, clase CCambiaFrecDlg, identificador IDC_BUTTON_MAS, mensaje BN_CLICKED, y añadimos una función para servir este evento, que llamaremos OnButtonMas().

3) Escribimos el código en esta función, que debe incrementar en 1 la variable miembro m_frec. Es importante tener en cuenta que dicha variable no es actualizada automáticamente a partir del valor del cuadro de texto, ni el cuadro de texto se actualiza automáticamente al cambiar el valor de la variable, sino que es necesario llamar a la función UpdateData() del diálogo para que se sincronicen los contenidos. UpdateData(TRUE) actualiza el valor de la variable con el contenido del diálogo, mientras que UpdateData(FALSE) actualiza el valor del diálogo con el de la variable. El código adecuado para la función OnButtonMas() será, por tanto:

A continuación, escribiremos el código necesario para evitar que se pueda seleccionar OK si el valor de la frecuencia introducida no es mayor que cero. Para ello, será necesario poder acceder al control ‘botón OK’, lo cual se realiza creando una variable miembro en ClassWizard que sea del tipo Control y no valor:

.../////////////////////////////////////////////////////////////////////////////// CCambiaFrecDlg message handlers

void CCambiaFrecDlg::OnButtonMas() {

// TODO: Add your control notification handler code here

UpdateData(TRUE); // m_frec contiene el valor que haya en el diálogom_frec++; // m_frec vale 1 másUpdateData(FALSE); // Se presenta en el diálogo el nuevo valor de m_frec

}

CambiaFrecDlg.cpp

Page 36: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

34

De esta manera, se ha creado en la clase CCambiarFrecDlg una nueva variable miembro llamada ‘m_botonOK’ de tipo CButton. El tipo depende del tipo de control (CEdit para cuadro de texto, CListBox para lista de selección, etc.). En la ayuda de cada uno de estos tipos se pueden ver las funciones y variables miembro sobre las que se puede actuar (ej: la función AddString() para un CListBox permite añadir una nueva opción seleccionable). Como estos objetos derivan de CWnd, dado que son ventanas, también se pueden usar las funciones de CWnd.

En nuestro caso, se creará una nueva función que sirva el evento EN_CHANGE del identificador IDC_EDIT_FRECUENCIA (cuadro de texto de edición de la frecuencia) de la clase CCambiarFrecDlg.

Page 37: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

35

El código de esta función habilita o inhabilita el control OK, a través de la variable m_botonOK creada anteriormente, en función del valor de la variable m_frec:

4.3.4.5 Inicializando controles en la clase diálogoComo se ha visto, la inicialización las variables miembro de la clase diálogo asociadas a los controles se puede hacer después de declarar una variable de la clase diálogo y antes de llamar a DoModal(). Sin embargo, las necesarias inicializaciones relacionadas con los controles (por ejemplo, añadir entradas a una lista de selección) no es posible realizarlas en ese momento, puesto que antes de llamar a DoModal() no existe la ventana diálogo y por tanto tampoco ninguno de los controles (aunque exista una variable asociada a un control, no existe aún el control en sí mismo y por tanto no se pueden utilizar funciones del mismo).Por ejemplo, si se añade al diálogo como se ha visto anteriormente un control de tipo lista de selección (ListBox) y se ha creado una variable de tipo Control asociado a él (CListBox m_lista), no sería válido el siguiente código que añade opciones a dicha lista (ver ayuda de CListBox):

Para solucionar este problema, cuando se deben inicializar controles se servirá el evento WM_INITDIALOG del diálogo correspondiente: cuando se alcanza esta función ya existe el diálogo y sus controles, y sólo falta que se muestren en pantalla.

...#include "CambiaFrecDlg.h" // Para que conozca a la clase del diálogo

void CEjemploView::OnMisopcionesCambiarfrecuencia() {

// TODO: Add your command handler code here

CEjemploDoc* pDoc=GetDocument(); // Obtiene puntero al documentoCCambiaFrecDlg dlg; // Crea diálogo (no muestra aún)

dlg.m_frec=pDoc->frec; // Inicializa variables del diálogodlg.m_lista.AddString("Opcion 1"); // INCORRECTO: aunque compila biendlg.m_lista.AddString("Opcion 2"); // falla en tiempo de ejecución porquedlg.m_lista.AddString("Opcion 3"); // no existe aún el control asociado

// a m_listaif (dlg.DoModal()==IDOK) // Presenta el diálogo. Si termina con{ // la pulsación de OK

pDoc->frec=dlg.m_frec; // recupera las variables del diálogo}

}

...void CCambiaFrecDlg::OnChangeEditFrecuencia() {

// TODO: If this is a RICHEDIT control, the control will not// send this notification unless you override the CDialog::OnInitDialog()// function and call CRichEditCtrl().SetEventMask()// with the ENM_CHANGE flag ORed into the mask.

// TODO: Add your control notification handler code hereUpdateData(TRUE); // m_frec contiene el valor que haya en el diálogom_botonOK.EnableWindow(m_frec > 0); // Si m_frec > 0 habilita control OK,

// si m_frec <=0 lo deshabilita.}

CambiaFrecDlg.cpp

CEjemploView.cpp

Page 38: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

36

Si la inicialización de estos controles es dependiente de valores del documento o de la vista, se crearán de forma manual variables en el diálogo que apunten a dichos valores. Estas variables sí pueden ser inicializadas antes de la llamada a DoModal(), y por tanto estarán disponibles en OnInitDialog() para inicializar los controles.

4.3.4.6 Cuadros de diálogo predefinidosExisten una serie de cuadros de diálogo que ya implementan funcionalidades típicas necesarias en muchos programas, por lo que no es necesario crear el diálogo para estos casos: simplemente será necesario el paso de creación del objeto y llamada a DoModal() indicado en 4.3.4.3. Los cuadros de diálogo predefinidos derivan de CCommonDialog. Los más utilizados son (ver ayuda para cada uno):

• CFileDialog para la selección de un archivo dentro del entorno de unidades y carpetas.

• CFontDialog para la selección de una fuente de texto.• CColorDialog para la selección de un color.• CFindReplaceDialog para la búsqueda y reemplazo de texto en el documento.• CPageSetupDialog y CPrintDialog para la impresión.

4.3.5 Eventos temporizadosDentro de los eventos que puede recibir una ventana (en nuestro caso CEjemploView), uno de los más interesantes es el evento de temporización. Un temporizador permite generar un evento a intervalos regulares, lo cual puede ser utilizado por una aplicación para ejecutar código temporizado, para dividir una operación larga en otras más cortas mientras se pueden servir otros eventos, etc.Para ello, se crea un temporizador con SetTimer(), se debe servir el evento WM_TIMER de la ventana, y se termina el temporizador con KillTimer().Para ilustrarlo, añadimos una opción de menú a la vista que permita llamar a la función cálculo cada, por ejemplo, 0.1 segundos. Los pasos serán:

1) Añadir opción “Iniciar auto” con el editor de recursos de menú, crear con ClassWizard una función en la clase vista que sirva a esta nueva opción, y editar el código de la misma. En este código iniciamos un temporizador de 0.1 segundos (100 milisegundos) que lleve el identificador 1500 (por ejemplo).

2) Añadir en ClassWizard una función OnTimer() para servir el evento WM_TIMER de la clase vista. En el código de esta función se llama a la función CalculaNuevo() del documento.

void CCambiaFrecDlg::OnInitDialog() {

CDialog::OnInitDialog();

// TODO: Add extra initialization herem_lista.AddString("Opción 1");m_lista.AddString("Opción 2");m_lista.AddString("Opción 3");

return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE

}

CambiaFrecDlg.cpp

Page 39: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

37

3) Añadir opción “Fin auto” con el editor de recursos de menú, crear con ClassWizard una función en la clase vista que sirva a esta nueva opción, y editar el código de la misma. En este código paramos el temporizador.

4.3.5.1 Evitando el parpadeoUn efecto indeseado al redibujar, que aparece especialmente con los eventos temporizados, es el hecho de que, por defecto, la ventana se borra completamente y se redibuja completa en cada evento de repintado. Esto hace que, cuando el nº de elementos a dibujar en la ventana es grande, se produzca un indeseado de parpadeo.

Existen diversas formas de evitar el parpadeo, de las cuales destacamos dos:• Hacer que en el repintado se actualice únicamente una zona de la ventana. La

función RedrawWindow() acepta varios parámetros (hemos utilizado hasta ahora sus valores por defecto), entre los cuales el 1º es el rectángulo que se quiere actualizar. Si esta opción es NULL (por defecto) se actualiza toda la ventana. Si cambiamos los parámetros de la llamada a RedrawWindow() en la función de servicio del temporizador, de forma que sólo se actualice la parte de ventana correspondiente a la nueva zona a dibujar, se elimina el efecto parpadeo (dejamos sin actualizar la zona correspondiente al texto de nº de dato para que se vea el efecto: esta zona no se actualiza salvo cuando, por ejemplo, minimizamos y volvemos a restaurar la ventana, en cuyo caso la función OnDraw() ha sido llamada para toda la ventana).

...void CEjemploView::OnMisopcionesIniciarauto() {

// TODO: Add your command handler code here

SetTimer(1500,100,NULL);}

void CEjemploView::OnTimer(UINT nIDEvent) {

// TODO: Add your message handler code here and/or call defaultCEjemploDoc* pDoc=GetDocument();pDoc->CalcNuevoDato();RedrawWindow();

CView::OnTimer(nIDEvent);}

void CEjemploView::OnMisopcionesPararauto() {

// TODO: Add your command handler code here

KillTimer(1500);}

EjemploView.cpp

Page 40: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

38

• La posibilidad anterior elimina el parpadeo, pero aún se ejecutarán todas las órdenes de OnDraw() cada vez que se llame a RedrawWindow() aunque solo se actualice la zona indicada en el rectángulo. Otra opción puede ser crear un contexto de dispositivo temporal y dibujar sobre él la parte a actualizar, dejando en OnDraw() el trabajo para cuando hay que redibujar todo. En este caso, el código sería:

Importante notar que, en cualquiera de las 2 opciones, es necesario saber cómo va a hacer OnDraw() el trabajo para seleccionar la zona correcta o dibujar en el sitio adecuado. Por ello, es importante hacer funciones auxiliares (crear manualmente su prototipo en CEjemploView.h y escribir manualmente el código en CEjemploView.cpp) únicas para asegurar que en todos los casos se dibuja lo mismo.

...

void CEjemploView::OnTimer(UINT nIDEvent) {

// TODO: Add your message handler code here and/or call defaultCEjemploDoc* pDoc=GetDocument();pDoc->CalcNuevoDato();

CDC* pDC=GetDC();CPen pen(PS_SOLID,2,RGB(255,0,0)),*aPen;aPen=(CPen*) pDC->SelectObject(&pen);

if (pDoc->nDatos >= 2){

pDC->MoveTo(pDoc->datosX[pDoc->nDatos-2],300+pDoc->datosY[pDoc->nDatos-2]);pDC->LineTo(pDoc->datosX[pDoc->nDatos-1],300+pDoc->datosY[pDoc->nDatos-1]);

}

pDC->SelectObject(aPen);pen.DeleteObject();ReleaseDC(pDC);CView::OnTimer(nIDEvent);

}

...

void CEjemploView::OnTimer(UINT nIDEvent) {

// TODO: Add your message handler code here and/or call defaultCEjemploDoc* pDoc=GetDocument();pDoc->CalcNuevoDato();

int minY,maxY;minY=__min(pDoc->datosY[pDoc->nDatos-2],pDoc->datosY[pDoc->nDatos-1]);maxY=__max(pDoc->datosY[pDoc->nDatos-2],pDoc->datosY[pDoc->nDatos-1]);CRect r(pDoc->datosX[pDoc->nDatos-2]-1,300+minY-1,

pDoc->datosX[pDoc->nDatos-1]+1,300+maxY+1);RedrawWindow(&r);

CView::OnTimer(nIDEvent);}

EjemploView.cpp

EjemploView.cpp

Page 41: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

39

4.4 Programando aplicaciones MDI y basadas en diálogoA partir de lo visto anteriormente es sencillo pasar a programar aplicaciones con otros aspectos, como las de múltiples documentos (MDI), las basadas en cuadro de diálogo, o las basadas en vistas especiales.

• Para una aplicación MDI se dispondrá de múltiples objetos documento y vista, que pueden pertenecer a la misma o distintas clases, pero el funcionamiento es muy similar para cada uno. Cuando se cambia el contenido del documento se suele llamar a UpdateAllViews() en lugar de RedrawWindow() para que se actualicen todas las vistas.

• Una aplicación basada en diálogo se programa igual que el cuadro de diálogo que se ha visto en 4.3.4 y siguientes. Todos los datos son almacenados en la clase derivada de CDialog, y normalmente no se añade ninguna funcionalidad de dibujo propia, sino que se utiliza el comportamiento por defecto de los controles. Se dispone además de una función OnIntialUpdate() en la que se inicializa todo.

• También es posible crear aplicaciones SDI o MDI que incluyan vistas con funcionalidades específicas: CScrollView para una vista con barra de desplazamiento, CFormView para una vista en la que se pueden insertar controles, CEditView para una vista en la que se puede editar texto, etc. Para ello, se selecciona la clase base de la vista en el último paso de la creación del proyecto (ver figura en página 18), y se debe consultar la ayuda de la clase deseada para utilizar su funcionalidad adicional.

4.5 Utilizar hilos de procesamientoCuando se debe realizar un procesamiento largo, durante el tiempo que éste dure la aplicación no estará en disposición de recibir y procesar otros eventos, y por tanto no recibe entradas de usuario, no se redibuja, etc.Para evitar esto, se puede lanzar el procesamiento en un 2º hilo de ejecución. Este hilo se ejecuta de forma concurrente con el principal, de forma que el tiempo de procesador es dividido entre ambos: el hilo principal continúa sirviendo eventos del usuario, mientras que el secundario va procesando lo que se desee. Es Windows quien decide qué tiempo va concediendo a cada uno, sin bloquear ninguno de los dos.Para crear un 2º hilo utilizar la función CreateThread(). Ver ayuda para la forma de uso de esta función. Un ejemplo para crear un hilo que tenga acceso al documento:

Page 42: isa.uniovi.esisa.uniovi.es/~ialvarez/2007-2008/cptr/Programacion en Visual C++.pdf · ˘ ˇˆ˙ ˙ ˝ ˇ ˙ ˛ ˙ ˝ ˚ ˜ ! @ ˙ ˛ $% ˙ % ˜ ˙ ˇ ˇ ˙ ˛ % ? > 5 ˙ ˇ 7":

UNIVERSIDAD DE OVIEDODepartamento de Ingeniería Eléctrica,Electrónica, de Computadores y Sistemas

40

...

DWORD WINAPI FuncionLarga (LPVOID lpParameter){

CEjemploDoc* pDoc=(CEjemploDoc*) lpParameter;

// Procesamiento...return valor;

}

void CEjemploView::OnLanzarFuncionLarga() {

// TODO: Add your message handler code here and/or call defaultCEjemploDoc* pDoc=GetDocument();

DWORD id;if (CreateThread(NULL,65536,FuncionLarga,(LPVOID) pDoc,0,&id)==NULL){

MessageBox("No puedo lanzar función larga");Return;

}// El hilo ha sido lanzado: se ejecuta FuncionLarga de forma concurrente al// bucle de mensajes del hilo principal

}

EjemploView.cpp