FUNDAMENTOS DE MFC.
Por: Demian Panello.
MFC, (Microsoft Foundation Classes) es una librería de clases que provee un enfoque orientado a objetos sobre la API de Windows. Se dice que MFC "envuelve" gran parte de la funcionalidad de las API de Windows. Existen cientos de clases MFC, algunas de ellas uno usa directamente otras sirven como clase base para nuestras propias clases.
En realidad MFC es un "framework", lo que significa que es más que una simple colección de objetos, sino que permite definir una estructura de aplicación y manejar el comportamiento fundamental de la misma.
Inicialmente se tiene el objeto CWinApp que representa la aplicación misma, MFC encapsula todos los aspectos de una aplicación.
Primero el framework de MFC brinda la función de entrada WinMain que requiere un programa Windows y luego esa función WinMain invoca funciones miembro de CWinApp que hace que el programa funcione. Una de las funciones que WinMain invoca es Run, que inicia el bucle de mensajes que se distribuyen a la ventana de la aplicación.
Otro punto importante del framework MFC radica en el nivel de abstracción presentado, que va incluso más allá que las API de Windows mismas. Por ejemplo la arquitectura Documento/Vista que MFC introduce y nos permite crear aplicaciones poderosas que separa de manera simple la información básica de la aplicación y sus posibles representaciones gráficas. Esta abstracción está totalmente más allá del alcance de la API de Windows. Lo mismo pasa con la creación de controles ActiveX y muchas otras características que MFC ofrece.
El framework de MFC está basado en una jerarquía de clases, (Jerarquía MFC.), diseñada para brindar soporte en un variado rango de problemáticas presentadas a la hora de escribir un programa Windows.
CObject.
La mayoría de las clases están derivadas directa o indirectamente de CObject. CObject le provee tres importantes características a toda clase derivada de él.
- Serialización.
- Información de la clase en tiempo de ejecución. (RTCI)
- Información de diagnóstico y depuración.
Serialización es el proceso por el cuál se puede guardar o cargar un objeto desde un archivo en disco. Usando la clase CObject como clase base, se pueden escribir clases cuyas instancias pueden ser guardadas o cargadas desde un archivo de manera muy simple. Tenga presente que el tipo de información representada por un objeto en particular puede ser de un alto grado de complejidad y la serialización facilita su escritura y lectura en disco.
RTCI permite obtener el nombre de la clase en tiempo de ejecución asi como otra información de la clase. Y la información de diagnóstico y depuración permite validar las instancias de la clase y depurar la información.
Funciones globales de MFC, (funciones AFX):
No todas las funciones de MFC son miembro de alguna clase, sino que hay un grupo de funciones cuyo nombre comienzan con Afx y estas funciones están disponibles en cualquier momento en cualquier lugar; no están supeditadas a un objeto en particular, por eso se dice que son "globales".
Hay decenas de funciones Afx, pero las más comunes son:
| Nombre de la función | Descripción |
| AfxAbort | Termina una aplicación abruptamente. Generalmente se debe invocar esta función cuando ocurre un error grave. |
| AfxBeginThread | Crea un nuevo proceso y lo inicia. |
| AfxEndThread | Termina un proceso. |
| AfxMessageBox | Muestra el cuadro de diálogo de Windows. |
| AfxGetApp | Devuelve un puntero al objeto CWinApp de la aplicación. |
| AfxGetAppName | Devuelve el nombre de la aplicación. |
| AfxGetMainWnd | Devuelve un puntero a la ventana de la aplicación. |
| AfxGetInstanceHandle | Devuelve un handle identificando la instancia actual de la aplicación. |
También existen decenas de macros en MFC que son muy útiles. Para ver un detalle completo y organizado por grupos siga el siguiente link a MSDN: MFC Macros and Globals.
Una aplicación MFC desde 0.
Cuando se selecciona un proyecto MFC con el Application Wizard este genera abundante código. En éste código uno puede encontrar la clase derivada de CWinApp y una o más clases derivadas de CWnd o CDialog o CFrameWnd.
El programa MFC más básico que uno puede escribir consta de una clase derivada de CWinApp y una instancia de esa clase.
La clase derivada hereda muchos datos y funciones miembro de CWinApp, algunos de esos métodos son incluso virtuales lo que permite que uno los sobreescriba para personalizar su funcionamiento.
CWinApp.
| Datos
(todos los datos miembros de las clases MFC comienzan con m_)
|
Descripción |
| m_pszAppName | Nombre de la aplicación. |
| m_lpCmdLine | Línea de comando pasada a la aplicación. |
| m_hInstance | Handle de la aplicación. |
| m_hPrevInstance | Handle a una instancia previa de la aplicación. |
| m_pMainWnd | Handle de la ventana principal. |
| m_bHelpMode | Indica si la aplicación se encuentra en Help Mode. |
| m_pszExeName | Nombre del ejecutable. |
| m_pszProfileName | Nombre del archivo INI |
| m_pszRegistryKey | Nombre de la clave del registro en lugar de usar un archivo INI. |
| Funciones (algunas) | Descripción |
| LoadCursor | Carga un cursor presente como recurso. |
| LoadStandardCursor | Carga un cursor estandar, (IDC_) (ver WINDOWS.H). |
| LoadOEMCursor | Carga un cursor estandar, (OIC_) (ver WINDOWS.H). |
| LoadIcon | Carga un icono de recurso. |
| LoadStardardIcon | Carga un icono estandar, (IDI_) (ver WINDOWS.H). |
| LoadOEMIcon | Carga un icono OEM, (OIC_) (ver WINDOWS.H). |
| ParseCommandLine | Inicializa un objeto CCommandLineInfo basado en la línea de comando. |
| ProcessShellCommand | Procesa comando de un objeto CCommandLineInfo. |
| GetProfileInt | Devuelve un entero de una entrada en el registro o archivo INI. |
| WriteProfileInt | Escribe un entero en una entrada del registro o archivo INI. |
| GetProfileString | Devuelve una cadena de una entrada del registro o archivo INI. |
| WriteProfileString | Escribe una cadena en una entrada del registro o archivo INI. |
| AddDocTemplate | Agrega una plantilla de documento a la aplicación |
| GetFirstDocTemplatePosition | Devuelve un tipo POSITION de la primer entrada en la lista de documentos. |
| GetNextDocTemplate | Devuelve el siguiente objecto CDocument de la lista de documentos. |
| OpenDocumentFile | Abre un documento por su nombre. |
| AddToRecentFileList | Agregar un item a la lista de reciente archivos abiertos. |
| CreatePrinterDC | Crea un contexto de dispositivo para imprimir. |
| GetPrinterDeviceDefaults | Devuelve la impresora predeterminada. |
| InitInstance * | Inicialización de una instancia |
| Run * | Bucle de mensajes. |
| OnIdle * | Proceso para el tiempo muerto. |
| ExitInstance * | Terminación de una instancia. |
| PreTranslateMessage * | Función para filtrar mensajes. |
| SaveAllModified * | Avisa que hay documentos sin guardar. |
| ProcessWndProcException * | Handle por defecto para la excepciones. |
| DoMessageBox * | Sobreescribiendo esta función se puede modificar AfxMessageBox. |
| ProcessMessageFilter * | Permite interceptar algunos mensajes antes que alcancen la aplicación. |
| DoWaitCursor | Coloca/quita el reloj de arena. |
| WinHelp | Abre el archivo de ayuda. |
| LoadStdProfileSettings | Carga los seteos por defectos de un archivo INI o del registro y la lista de últimos archivos abiertos. |
| SetDialogBkColor | Especifica al color de fondo para todos los diálogos de la aplicación. |
| SetRegistryKey | Hace que los seteos de la aplicación se guarden en el registro de Windows en lugar de un archivo INI. |
| EnableShellOpen | Activa la posibilida de cargar documentos con sólo arrastras un archivo. |
| RegisterShellFile | Registra todos los tipos de documentos de la aplicación con el administrador de archivos de Windows. |
| OnFileNew | Implementa comando para ID_FILE_NEW |
| OnFileOpen | Implementa comando para ID_FILE_OPEN |
| OnFilePrintSetup | Implementa comando para ID_FILE_PRINT_SETUP |
| OnContextHelp | Permite controlar CTRL+F1 en la aplicación. |
| OnHelp | Permite controlar F1 en la aplicación. |
| OnHelpIndex | Implementa comando para ID_HELP_INDEX y provee un tópico por defecto. |
| OnHelpFinder | Implementa comando para ID_HELP_FINDER e ID_DEFAULT_HELP. |
| OnHelpUsing | Implementa comando para ID_HELP_USING. |
* Métodos virtuales, (se pueden sobrescribir para personalizar su funcionamiento).
Muchas de estas funciones se usan en aplicaciones tipo Documento/Vista.
Elija un proyecto WIN32 vacío, (opción Empty Project en el AppWizard).
Luego pulse con el botón derecho del mouse sobre el icono del proyecto en el ClassView y elija New Class...

En la ventana de New Class:

Cree la clase CMiAplicacion derivada de CWinApp, acepte esta ventana y el mensaje de advertencia que continua.
Luego en el archivo de definición de la clase escriba lo indicado a continuación.
| #include <afxwin.h>
// Se incluye el archivo de definición de MFC. class CMiAplicacion : public CWinApp { public: virtual BOOL InitInstance(); // Como se pretende realizar algo al momento iniciar la instancia se agrega este método. CMiAplicacion(); virtual ~CMiAplicacion(); }; |
Y en el archivo CPP de la clase agregue el método InitInstace y una instancia de la nueva clase.
| #include "MiAplicacion.h" #include "MiVentana.h" CMiAplicacion app; // Se crea una instancia de la clase ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CMiAplicacion::CMiAplicacion() { } CMiAplicacion::~CMiAplicacion() { } // Método sobrescrito. BOOL CMiAplicacion::InitInstance() { AfxMessageBox("Hola Mundo"); // ¡Hola Mundo! return FALSE; // Se retorna FALSE porque se desea que la aplicación no continue. } |
Falta algo más para que la aplicación funcione, y es indicarle al compilador que use la librería MFC de forma compartida o estática.
Vaya a Project -> Settings y especifique en Microsof Foundation Classes la opción Use MFC in a shared DLL, acepte la ventana y construya la aplicación.
Ejecute el programa y obtendrá el archi famoso mensaje "Hola Mundo".
Acaba de construir la aplicación MFC más básica de todas.
Al menos una ventanita
Note que justamente el método InitInstance sobrescrito podría hacer algo más que mostrar un cuadro de diálogo con el mensaje "Hola Mundo", podría por ejemplo, al menos, crear una ventana y mostrarla.
CFrameWnd.
La clase CWnd de MFC y sus derivadas, (ver Jerarquía MFC.), proveen una interface orientada a objetos para las ventanas que una aplicación crea. Entonces incorpore al proyecto una nueva clase llamada CMiVentana, (o el nombre que más le guste), derivada de CFrameWnd, (tal como hizo con la clase de la aplicación).
En el archivo de definición, (MiVentana.h) escriba:
| #include
<afxwin.h> // Se incluye MFC class CMiVentana : public CFrameWnd { public: CMiVentana (); virtual ~CMiVentana (); }; |
Escriba en el archivo de implementación, (MiVentana.cpp), en el constructor:
| #include
"CMiVentana .h" CMiVentana ::CMiVentana () { Create (NULL, _T ("Mi Ventana")); } CMiVentana ::~CMiVentana() { } |
Y modifique el archivo de implementación de la aplicación para que incluya el archivo de definición de la ventana #include "MiVentana.h" y modifique InitInstance como sigue:
| BOOL CMiAplicacion::InitInstance () { m_pMainWnd = new CMiVentana ; // Se crea un objeto CMiVentana m_pMainWnd->ShowWindow (m_nCmdShow); // Se muestra la ventana. m_pMainWnd->UpdateWindow (); // Se invoca UpdateWindow. return TRUE; // Ahora el método InitInstance debe retornar TRUE porque la aplicación continúa. } |
Ahora, en lugar de mostrar un mensaje "Hola Mundo", la aplicación crea una ventana y guarda la instancia en el dato miembro m_pMainWnd. La ejecución de esta línea, (la que crea la ventana con new), da a lugar a que en ese instante se ejecute el constructor de la clase CMiVentana el cual invoca el método Create().
Luego se muestra la ventana con ShowWindow() y se llama a UpdateWindow() para que se procese el mensaje WM_PAINT el cual ahora NO estamos procesando porque nuestra ventana aún no procesa mensajes.
El método InitInstance() ahora debe retornar TRUE para que la aplicación continue e inicie el bucle de mensaje que envia mensajes a nuestra ventana. No obstante nuestra ventana deja pasar todos los mensaje.
Puede construir y probar la aplicación, obtendrá una ventana típica de Windows.
La ventana CMiVentana es derivada de CFrameWnd que a su vez ha sido derivada de CWnd.
El objeto aplicación crea una ventana, creando un objeto CMiVentana que a su vez en el constructor invoca un el método Create() o CreateEx().
Create (NULL, _T ("Mi Ventana"));
_T es una macro que hace que los caracteres de la cadena "Mi Ventana" sean neutrales, esto es importante si se deasea que la aplicación compile como UNICODE, (no es algo muy util ahora esto y puede quitar esa macro e igual va a funcionar el programa). Create() es una función miembro de CFrameWnd y que hemos heredado entre muchísimas otras cuando derivamos nuestra clase CMiVentana de CFrameWnd.
La función Create() tiene 8 parámetros de los cuales 6 admiten valores por defecto, y nuestro ejemplo sólo define valores para los primeros 2. El segundo es el título de la ventana.
Para un completo desarrollo de todos los parámetros remítase a la ayuda MSDN para CFrameWnd::Create().
El mapa de mensajes.
Para finalizar este articulo, y lograr conseguir escribir una aplicación MFC desde 0 que sea similar a lo generado por el Application Wizard, faltaría agregarle a nuestra ventana algo que permita procesar los mensajes que la aplicación le envía y además deseamos procesar.
Supongamos que queremos que en el centro de la ventana, (la región cliente), se imprima el texto "Hola Mundo", para variar. : )
Bueno, una ventana se pinta muchas veces mientras se está mostrando, es más, se pinta cada vez que llega el mensaje WM_PAINT, y esto ocurre cuando la ventana se muestra por primera vez, cuando se mueve la ventana, es ocultada por otra ventana, etc. Por lo tanto necesitamos un handle de mensaje, o sea una función que contenga el código que se ejecutará cada vez que llega el mensaje WM_PAINT.
Para ésto MFC implementa el mapa de mensajes definiendo por medio de algunas macros cuál es la función handle del X mensaje de Windows.
Para agregarle esta capacidad a nuestra ventana edite el archivo de definición agregando antes de la llave de cierre de la clase la siguiente línea:
DECLARE_MESSAGE_MAP ()
Sin ";".
Y además el siguiente prototipo:
protected:
afx_msg void OnPaint ();
Luego edite el archivo de implementación, (MiVentana.cpp), agregando al principio, (luego del #include), las siguientes líneas:
BEGIN_MESSAGE_MAP (CMiVentana, CFrameWnd)
ON_WM_PAINT ()
END_MESSAGE_MAP ()
Todas sin ";".
Y la implementación de OnPaint() al final:
| void
CMiVentana::OnPaint () { CPaintDC dc (this); CRect rect; GetClientRect (&rect); dc.DrawText (_T ("Hola Mundo"), -1, &rect, DT_SINGLELINE ¦ DT_CENTER ¦ DT_VCENTER); } |
Listo, ahora puede construir la aplicación y correr el programa. Obtendrá la misma ventana anterior pero ahora imprime el texto "Hola Mundo" centrado en la región cliente.
El mapa de mensaje es un mecanismo por el cual uno puede "redireccionar" los mensajes que desea procesar a funciones miembro de la clase sólo necesita saber que mensaje quiere, (todos son de la forma WM_), y agregar un método similar al OnPaint y una entrada en el mapa de mensajes.
MFC provee una macro tipo ON_WM_MENSAJE para muchos mensaje de Windows. Por ejemplo si desea agregar un handle para el mensaje WM_LBUTTONDOWN debería incluir una entrada en el mapa de mensajes como:
ON_WM_LBUTTONDOWN()
Y además una función:
afx_msg void OnLButtonDown (UINT nFlags, CPoint point)
La documentación de MFC le informa cuántos y cuáles parámetros son los que necesita y que información traen.
Luego en la función OnLButtonDown() escribiría el código que desea se ejecute al pulsar el botón izquierdo del mouse.
En caso que no exista la macro MFC para el mensaje que desea procesar, se puede usar la macro ON_MESSAGE para agregar la entrada al mapa de mensajes y además especificar cuál es la función handle de dicho mensaje. Pero esto sería para un futuro análisis.
Resumiendo:
Todo esto es lo necesario para entender el código que el Application Wizard genera, por lo menos en lo que a la estructura más fundamental de una aplicación respecta. De esta forma se adquiere un mayor conocimiento de la aplicación MFC que se está desarrollando.
Hasta el próximo artículo.!!