MFC Introduction



1.Creation d'une application MFC avec AppWizard

Les manip sous Visual C++ :
Visual C++ génère un squelette d'une application qui est prêt à être compilé .
Si on le compile sans rien, on a une application composé de :

2.Architecture d'une application de base


Objet
MFC Objet
Utilisation et rôle
Interaction avec les autres objets
Application
CWinApp

Gère tous les objets de l'application

Keeps a list of document templates
Document template
CDocTemplat Creates and manages documents.

Manages a list of views on its data.
Document
CDocument
Objet pour stocker des données
Manages a list of views on its data.
View
CView
Manages user interaction with a document.
Attached to a document. Owned by a frame window.
Frame window
CFrameWnd
Frames a view.
Owns a view that is attached to a document

3.Document


Le modèle de programmation des MFC sépare le stockage d'une donnée interne (Document) de son affichage (View).
Et un Document est objet qui gère, stocke des données et s'occupe de la sérialisation (chargement et sauvegarde des données sur le disque).

3.1. Les responsabilités pour l'implémentation

Au programmeur de faire
Les MFC s'en occupe
Derive a document class from class CDocument
Provide many document services through class CDocument.

Add data members to your class et function members


Implement application-specific initialization and cleanup of your document’s data. Call the appropriate initialization and cleanup functions at the right times.
Override CDocument’s Serialize member function to specify how your data is read and written.
Provide implementations of File Open, Save, and Save As that call your Serialize override to read and write your data.


Conformément à cela, l'AppWizard a généré les objets composant cette implémentation :

Objets créés
dérivent des MFC
CSribbleApp

CWinApp

CScribbleDoc
CDocument

CScribbleView CView
CScribbleMainFrame

CMDIFrameWnd
CScribbleChildFrame

CMDIChildWnd
CAboutDlg
CDialog

3.2. qq éléments utiles d'API CDocument

SetModifiedFlag()

is a member function of class CDocument. It marks the document as changed so the framework will prompt the user to save the document when it closes.

DeleteContents()
destroy the document’s data (à overrider)
UpdateAllViews(View*) Cause all views of the data to be updated.
argument = pointer to the view that modified the document, NULL si c'est le document lui même.


4. Représentation graphique d'un document

Une View (ici CScribbleView) affiche les données d'un Document (CScribbleDoc) et répond aux actions de souris, touches claviers, commandes menu, et aux autres actions liés au travail sur le Document ouvert.

Le travail à faire :
- spécifier comment la View représente les données du document
- spécifier ce que la vue doit faire en réponse à une action de l'utilisateur

4.1. Les responsabilités pour l'implémentation

Au programmeur de faire
Les MFC s'en occupe
Derive a view class from class CView (ou CScrollView si on veut pouvoir scroller). Other view classes are available as well. Class CView and its derived classes provide view services.
Implement your view’s OnDraw member function.
C'est le framework qui appelle la fonction OnDraw quand il le faut (quand le contexte d'affichage le demande)

Map Windows messages and commands to member functions of your view. The framework calls your message-handler member functions in response to the corresponding Windows messages.

4.2. Gestion de la souris et connexion avec les actions éventuelles à effectuer

Windows envoi un message d'action utilisateur à la fenêtre active.
La View consulte son "gestionnaire d'actions" pour savoir s'il a une fonction qui réagit à ce message d'action. Si c'est le cas, il invoque la fonction en question.

Autrement dit, il invoque une méthode bien particulière (ex : OnMouseMove() lorsque la souris bouge) s'il elle est implémentée par le programmeur.


Les évenements Souris et Handler (= fonctions) correspondant :

Action de l'utilisateur
Evenement
Handler
Presse le bouton gauche
WM_LBUTTONDOWN

CScribbleView::OnLButtonDown
Bouge la souris
(whether or not mouse button is pressed)
WM_MOUSEMOVE

CScribbleView::OnMouseMove
Relache le bouton gauche
WM_LBUTTONUP

CScribbleView::OnLButtonUp

(CScribbleView étant ici la classe View qui représente un Document)

Les manip avec la souris dans CView :

- StartCapture() capture les mouvement de souris
- GetCapture() renvoi la CView sur laquelle la capture est effectué.
- ReleaseCapture() termine une capture.
- la variable (de type CPoint) point est la position de la souris sur le moment.

Pour détecter un mouse Drag (click gauche appyué + move) :
- dans OnLButtonDown(), on fait un StartCapture()
- dans OnLButtonMove(), on teste si GetCapture() = this (si oui, c'est qu'on fait un Drag)
- dans OnLButtonUp(), on fait un ReleaseCapture()

Ajout d'un evenement à traiter par la View

Dans la WizardBar,
  1. Sélectioner la classe dont on veut qu'il réagisse à un évenement souhaité.
  2. Click droit dans cette WizardBar, et sélectionner Add Windows Message Handler.
  3. Une boite de dialogue avec une liste d'évenement apparait,
    il suffit de prendre le bon (ex : WM_MOUSEMOVE) et
    cliquer sur Add Handler
  4. finir avec OK


La WizardBar génère alors automatiquement :

- les déclarations dans la message map ("gestionnaire d'actions"), de l'évenement ajouté
ex : ON_WM_LBUTTONDOWN()

- les déclaration des fonction (dans le fichier .h) qui seront appelé lors ce l'évenement
ex : afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

- une implétation basique de la fonction (dans le .cpp) déclarée
ex :
void CScribbleView::OnLButtonDown( UINT nFlags, CPoint point )
{
// TODO: Add your message handler code here
// and/or call default
CView::OnLButtonDown( nFlags, point );
}

Notice that ClassWizard embeds a comment reminding you what to do and adds a call to the CView::OnLButtonDown.

5. User Interface

Passer en Ressource View.

5.1. Menu Interface

Les menus sont créés automatiquement par le WizardApp. Il y a en a une pour l'appli quand aucun document n'est ouvert.
et un autre lorsqu'un document est ouvert.

- Se positionner dans Ressources View -- Menu
- Et double-cliquer le menu souhaité.


Ajouter une entrée au Menu est une opération simple :
1. ouvrir le menu et naviguer vers l'endroit où l'on souhaite l'insérer.
2. taper le texte dans l'entrée vide
3. Cela ouvre en même temps une boite de dialogue où l'on est en fait en train de taper le champ Caption.
Dans Caption, \t = tabulation, &<suivi du caractère> = touche de raccourci
ex : Clear &All\tCtrl+A = "Clear All Ctrl+A"
On peut préciser d'autres informations (ex : modifier l' ID donné automatiquement, en faire un séparateur au lieu d'une commande)

Capture du menu editor

Des ID sont déjà prédéfinit par les MFC. Et le fait d'tiliser une entréee de la liste d'ID, cela rempli automatiquement les champs Prompt .

L' ID est le paramètre le plus important car pour le framework, l'ID est la commande. On doit spécifier quel code doit être exécuté quand l'utilisateur clique sur la commande. Ce code est relié au menu par l'ID.

Il manque encore la liaison de la commande menu avec la fonction à appeler. On le fera plus loin.
Concentrons nous sur la construction de l'interface graphique.

5.2. Toolbar (barre d'icones)

Puisque le code d'une commande est associé à l' ID, un bouton et une commande menu destinés à lancer la même fonction auront le même ID !!
Ouvrir la toolbar, alors the graphics and color tools also open as part of the toolbar editor.

To delete a toolbar button :
- Drag the button off the toolbar (in the top, or normal view pane).

Ajout d'un bouton à la Toolbar :
1) Cliquer sur le bouton vide (à droite)
2) Dessiner le bouton
3) Donner son ID (surement un qui existe dejà avec une commande menu)

Ajout d'une tooltip
1) reprendre le bouton.
2) Retoucher le Prompt avec ce contenu : <message affiché au prompt>\n<texte de la tooltip>


6. Connecter un élément de l'UI avec les fonctions

Quand on utilise la WizardBar pour créer un message-handler function , la ClassWizard
- ajoute une nouvelle entrée dans la Message Map de la classe.
- ajoute une déclaration de fonction (dans le .h) associé à l'entrée de la message MAP.
- ajoute un squelette de l'implémentation de la fonction (dans le .cpp).
- WizardBar jumps you directly to the text editor to fill in the function template.

Important If you delete a command binding with ClassWizard , its message-map entry is deleted, but the message-handler function , and any references to it in your other code, are not deleted . You must delete those items by hand. This is for your safety; the message-handler function code, which you probably wrote, is preserved until you delete it

La difficulté réside dans le choix parmis les classes (Document, View, Frame) où placer la fonction à appeler .
Là, c'est au cas par cas, mais il faut identifier :
- la fonction agit sur qui directement : le Document ? la View ? la Frame ?
- si le fait de mettre à cet endroit la fonction, cela modifie bien tous les objets dont on s'attend de voir un changement.

ex :
une appli qui dessine des point sur une feuille blanche.
- le Document stocke en fait la liste des points dessinés
- la View représente ces points en les affichant à l'écran

On ajoute une fonction Clear-All qui efface tous les point dessinés
- le fait d'effacer tous les points, alors la liste des points dans le Document doit êter mise à zéro
- on peut se contenter de mettre à zéro la View
- mais : la view doit représenter en temps réel les données du Document !
- conclusion : on doit placer ClearAll au niveau du Document

Ce genre de décision est spécifique à une application !

Here are some guidelines:

6.1 Connexion d'une commande menu/keystroke/toolbar button avec une fonction

Se placer dans la classe "cible"

1) Click Add Windows Message Handler
The New Windows Message Handler dialog box appears.

2) In the Class or object to handle box, select l'ID (ex: ID_EDIT_CLEAR_ALL).

3) In the New Windows messages to handle list box, select COMMAND.

You can see both COMMAND and UPDATE_COMMAND_UI in the New Windows messages to handle list box.
- COMMAND : handle a command (menu command, toolbar button, keystroke)
- UPDATE_COMMAND_UI : callback for menu and button enabling/greying

These are the two events for which the framework provides ClassWizard support in creating your command handler code.
That’s why, for commands, these are always the choices you see in this list box.
In other cases, you might see other things listed — a list of Windows messages, for example, when the selected item is the name of a window or view class.

4) Click Add and Edit.
5) In the Add Member Function dialog box, click OK to accept the name OnEditClearAll.

Le Wizard génère la fonction et fait les map et se place dans l'implémentation de la fonction (encore vide) dans le .cpp, prête à être remplie.


6.2. Updating User-Interface Objects

1) Using WizardBar, open ScribbleDoc.cpp in the text editor.

2) In the WizardBar Filter combo box, select ID_EDIT_CLEAR_ALL.

3)Click the action button arrow on the right end of WizardBar and click Add Windows Message Handler.

4) In the New Windows messages to handle box, select UPDATE_COMMAND_UI. Click Add and Edit.
The Add Member Function dialog box appears, displaying a suggested name for the handler.

5) Click OK to accept the name OnUpdateEditClearAll.

6) Les actions à affectuer sont par exemple :

void CScribbleDoc::OnUpdateEditClearAll(CCmdUI* pCmdUI)
{
pCmdUI->Enable( !m_strokeList.IsEmpty( ) );
}

Ne pas oublier que la message-map "mappe" cette fonction avec les objets ayant pour identifiant l'ID dont on a décidé le mapping.
Donc, à l'exécution,. en argument de cette fonction, pCmdUI sera bien l'objet ID (menu command, bouton..) dont on s'attend.

Note: When the user opens a menu, the update handlers for all items on the menu are called before the user sees the menu displayed.
Thus it’s important not to perform a lot of processing in your update handlers.

ClassWizard wrote the following message-map entry in the document’s message map in ScribbleDoc.cpp:
ON_UPDATE_COMMAND_UI( ID_EDIT_CLEAR_ALL, OnUpdateEditClearAll )

The ON_UPDATE_COMMAND_UI macro resembles the ON_COMMAND macro for the OnEditClearAll message handler.

In addition, ClassWizard added a new member function declaration for OnUpdateEditClearAll to the CScribbleDoc class definition in ScribbleDoc.h.
The function declaration looks like this:
afx_msg void OnUpdateEditClearAll( CCmdUI* pCmdUI );

qq fonctions sur CCmdUI

Enable( BOOL )

the menu item is enabled or disabled (and dimmed or grayed)

SetCheck( BOOL )
toggle the menu item’s check mark on or off as the line thickness changes.


7. Dialog Box


Designing a dialog box requires three steps:

7.1. To create the dialog box

With your project open, from the Insert menu, click Resource .
  1. In the Insert Resource dialog box, select Dialog from the list of resource types and click New
    Scribble’s ResourceView pane opens and the dialog editor window appears, displaying a default dialog box that contains two buttons labeled OK and Cancel. The Controls toolbar also appears.
    If the property page is not displayed, right-click the dialog box, then click Properties on the menu. Click the pushpin on the property page to keep it open.
  2. In the ID box, type IDD_PEN_WIDTHS.
    This is not a predefined ID, so you can’t select it from the drop-down list.
  3. In the caption box, change the caption to Pen Widths.
    Notice that the title bar of the dialog box reflects the new caption.
  4. Save your work.

Add the Controls
1. From the Controls toolbar, add two edit box controls to the Pen Widths dialog box.
2. From the Controls toolbar, add two static text controls to contain the descriptions for the two edit controls.
For the purposes of Scribble, you won’t have to refer programmatically to the IDs of the text boxes, so you can leave them with their default values (both have the value IDC_STATIC).
3. From the Controls toolbar, add a third button to the two already present.
4. Select the third button to display its property page.
Change its ID to IDC_DEFAULT_PEN_WIDTHS and its caption to Default.
The handler for this button will reset the thick and thin pens to their default widths.

Arrange and Test Controls

Once you’ve added all the controls to the dialog box, you can also:
You can see the tab order by clicking Tab Order from the Layout menu. To change the tab order, click each control in the order you want as the tab order
If you want to see how the dialog box will look when it’s displayed, click the Test command from the Layou t menu. This displays the dialog box as it will appear in Scribble, enabling you to test aspects such as the tab order, default button, and so on. Exit Test mode by clicking either the OK or Cancel button on the dialog box or by pressing the ESC key.

7.2 Connecting a Class to a Dialog Box

To connect a class to a dialog box:
To declare the new dialog class

  • Click the arrow on the action button, located at the right end of WizardBar.
  • Click New Clas s.
    The New Class dialog box appears.
  • Fill in the selection as follows:
    • In the Class type combo box, select MFC .
    • In the Name box , type CPenWidthsDlg .
In the File name box, the name is assigned for the implementation (.cpp) file based on the characters you type.
If you wanted to change the filename, you would click the Change button.
  • In the Base class combo box, click CDialog.
  • When you click CDialog, the Dialog ID box is activated with the default IDD_PEN_WIDTHS.
  • Click OK to create the dialog class.
    This creates the class and closes the New Class dialog box.

Take a minute to examine the initial version of PenWidthsDlg.h. This file contains a declaration for CPenWidthsDlg, the class that implements the Pen Widths dialog box. At this point, the class contains two member functions: a constructor and the DoDataExchange function, which is described later on.

The file contains comment lines that begin //{{AFX_ and //}}AFX_. ClassWizard uses those comment lines to find the sections of code that it maintains. There are three such sections in the header file, each delimited by slightly different comments:
Declare a Message-Handling Function for a Dialog Box Control

Cela se fait comme pour connecter un menu command à une fonction ! (Add New Message Handler)

Note   For a dialog class , the Class or object to handle box lists the IDs of all the controls in the dialog box, not the commands in a menu. Dialog classes also handle messages differently: the message being handled is a Windows control notification message, not an application-specific command. As a result, the New Windows messages to handle box displays more than just COMMAND and UPDATE_COMMAND_UI. It displays all the messages that can be sent by the object that’s highlighted in the Class or object to handle box.

Exemple pour un bouton :
  • Add Windows Message Handler
  • Class or object to handle : <ID correspondant au bouton>
  • The New Windows messages to handle : BN_CLICKED
  • Click Add and Edit  pour créer la fonction


Map the Controls to Member Variables

MFC defines a mechanism that automates the process of gathering values from a dialog box; this mechanism is called a “data map.” In the same way that a message map binds a user-interface element with a member function, a data map binds a dialog-box control with a member variable . The value of the member variable reflects the status or the contents of the control.

Map a dialog box control avec a member class

  1. From the View menu, click ClassWizard and click the Member Variables tab.
    This tab, shown in the figure below, contains a list box displaying the mapping between controls and member variables.
  2. In the Class name list, select CPenWidthsDlg .
    At the moment the box displays only the IDs for the controls because you haven’t yet specified which member variables the controls correspond to.
  3. Select IDC_THIN_PEN_WIDTH and then click the Add Variable button.
    The Add Member Variable dialog box appears.
  4. In the Member variable name box, specify m_nThinWidth as the variable name.
  5. From the Variable type list box, click int.
  6. Click OK to add the member variable to the class.
    Notice the changes in the MFC ClassWizard dialog box:
    The member name and type you specified now appear in the Control IDs list.

    Description reads “int with validation”.
Two new edit boxes (Minimum Value and Maximum Value) appear to receive the validation parameters appropriate for an integer. These correspond to the edit boxes you added to the dialog box resource.
In the Minimum Value and Maximum Value boxes, enter 1 and 20, respectively.

Click OK.

DoDataExchange(), which is a member function defined by CWnd (the base class of CDialog). The framework calls DoDataExchange whenever values have to be moved between the member variables in the class and the controls in the dialog box on screen (for example, when first displaying the dialog box on the screen or when the user closes the dialog box by clicking OK).

The DoDataExchange function is implemented using DDX and DDV function calls. A DDX (for Dialog Data eXchange) function specifies which control in the dialog box corresponds to a particular member variable and transfers the data between the two. A DDV (for Dialog Data Validation) function specifies the validation parameters for a particular member variable, ensuring that its value is legal. The DDX and DDV function calls shown in PenWidthsDlg.cpp reflect the mapping and validation parameters you specified with ClassWizard.

Notice that the DDV function call for a given member variable immediately follows the DDX function call for that variable. This is a rule you must follow if you choose to manually edit the contents of the data map.

Changer la valeur affichée dans un contrôle :

(Par exemple un bouton change la valeur affiché par un auter controle1.)
Il faut :
- que le controle1 soit relié à un member variable (vue juste au dessus).
- que le bouton soit mappé au Click de souris par un Window Message Handler (voir un point juste au dessus)

utilisé par exemple dans la fonction appelé lorsque l'on clique sur le bouton :

UpdateData(BOOL) , a member function defined by CWnd (the base class of CDialog).
The default value of this argument is TRUE, which moves data from the controls to the member variables.
A value of FALSE moves data from the member variables to the controls.


7.3 How do you open a dialog box?


With the following two steps:
  1. Declare a CPenWidthsDlg object. This doesn’t display the dialog box on the screen; it just constructs the C++ object that manages the dialog box.
  2. Complete the OnPenWidths member function handler for the Pen Widths menu command.
To specify that the Pen Widths command displays the dialog box modally, you call the DoModal member function defined by the CDialog class . (To display a modeless dialog box, you would call the Create member function of CDialog.)

The DoModal function continues executing as long as the dialog box is displayed on the screen. When the user clicks the OK or Cancel button, the DoModal function returns IDOK or IDCANCEL, respectively, and the application can continue.

To declare the DialogBox object

  1. Open ScribbleDoc.cpp in the text editor.
  2. Click the arrow on the action button on the right end of WizardBar.
  3. Click Add Windows Message Handler.
    The New Windows Message Handler dialog box appears.
  4. In the Class or object to handle list box, select ID_PEN_WIDTHS.
  5. In the New Windows messages to handle list box, select COMMAND.
  6. Click Add and Edit.
  7. In the Add Member Function dialog box, click OK to accept the candidate name “OnPenWidths.”
In place of the highlighted //TODO comment, add the following code:
CPenWidthsDlg dlg;
// Initialize dialog data
dlg.m_nThinWidth = m_nThinWidth;
dlg.m_nThickWidth = m_nThickWidth;

// Invoke the dialog box
if (dlg.DoModal() == IDOK)
{
// retrieve the dialog data
m_nThinWidth = dlg.m_nThinWidth;
m_nThickWidth = dlg.m_nThickWidth;

// Update the pen used by views when drawing new strokes
// to reflect the new pen widths for "thick" and "thin".
ReplacePen();
}

-  Scroll to the top of ScribbleDoc.cpp and add the following #include statement:
#include "PenWidthsDlg.h"

- Save ScribbleDoc.cpp.

When you modify ScribbleDoc.cpp, you must include PenWidthsDlg.h so the message handler has access to the dialog class you’ve created.

The OnPenWidths function:

8. Mechanisme de multi-affichage d'un document

Each view must notify the other views whenever it has modified the document. MFC provides a standard mechanism for notifying views of modifications to a document through the UpdateAllViews member function of the CDocument class.

The UpdateAllViews function traverses the list of views attached to the document. For each view in the list, the function calls the OnUpdate member function of the CView class. The OnUpdate function is where the view responds to changes in the document; the default implementation of the function invalidates the client area of the view, causing it to be repainted. The simplest way for you to use this updating mechanism in your application is to call the document’s UpdateAllViews function whenever a view modifies a document in response to a user action.

You can also perform more efficient repainting with this updating mechanism if you use the parameters of the UpdateAllViews function. Here is the declaration of UpdateAllViews:

void UpdateAllViews(CView* pSender, LPARAM lHint = 0L,
                           CObject* pHint = NULL);

The first argument identifies the view that made the modifications to the document. This is specified to keep the UpdateAllViews function from performing a redundant notification. The view that made the modifications doesn’t need to be told about them. The second two arguments are “hints.” You can use these hints to describe the modifications that the view made.

The UpdateAllViews function gives the hints to every view attached to the document by passing them as parameters to the OnUpdate member function. You can override OnUpdate to interpret those hints and update only the area of the display that corresponds to the modified portion of the document. Thus, if another view is displaying a completely different portion of the document, it doesn’t have to perform any repainting at all.

8.1. Mettre à jour des affichages

These are the basic tasks you'll do to inform other views of modifications:
  1. Define a type of hint that describes a modification to a document.
  2. When a view modifies the document, create a hint describing the modification made and pass it to UpdateAllViews .
  3. Override OnUpdate to use the hint so that only the portion of the screen corresponding to the modification gets updated.
Ici, on peut utiliser un CRect et définir la zone qui doit êter redessinée, au lieu de tout redessiner.
L'algo consiste à partir du premier point et d'étendre les bord du CRect pour pouvoir englober tous les points du Document.
Le calcul de cette zone doit être refait après chaque modif d'un tracé à l'écran (OnLButtonUp), et d'où on appelle UpdateAllViews()

8.2 Remplacer un Cview par un CScrollView

The basic tasks for adding scrolling to your application are as follows:
  1. Define a size for your documents. This can be a constant, a member stored in each document object, or a value calculated at run time, for example.
  2. Derive your view class from CScrollView instead of CView.
  3. Pass the document’s size to the SetScrollSizes member function of CScrollView whenever the size may change.
  4. Convert between logical coordinates and device coordinates if passing points between graphic device interface (GDI) and non-GDI functions.
Note   In the AppWizard – Step 6 dialog box, you have the option of changing your base class. You could have, for example, chosen CScrollView instead of CView at that point, thereby eliminating some of the steps in this procedure.

If your application supports documents of varying size, you should call SetScrollSizes immediately after the document’s size changes .
(You can do this from the OnUpdate member function of your view class.)