This post was published in french under Architecture Document-Vue, SDI.

Dans une application document-vue, les données de l’application sont représentées par un objet document et les visions de ces données sont representées par un ou plusieurs objets-vue. Les objets document et vue collaborent pour traiter les saisies utilisateur et dessinent des représentations textuelles et/ou graphiques des données qui en résultent. La classe MFC CDocument est la classe de base des objets document, alors que la classe CView et ses dérivées sont les classes de bases des objets vue.

La fenêtre principale de l’application, dont le comportement est décrit par les classes MFC CFrameWnd (ou CMDIFrameWnd) ne sert plus de point focal pour le traitement des messages. Elle sert principalement de conteneur pour des vues, des barres d’outils, des barres d’état et d’autres objets.

Avantages:

  • modularité des objets et clarté dans la division des tâches logicielles
  • fournit en standart certaines tâches, comme l’enregistrement et l’impression
  • large gamme, en général, de fonctionnalités intégrées sans devoir écrire de code supplémentaire

Il y a deux types d’application document-vue. Les applications SDI (Single Document Interface) permettent d’ouvrir un seul document à la fois. Les applications MDI (Multiple Document Interface) permettent de travailler sur plusieurs documents à la fois.

Liaison d’un document avec ses vues

Les MFC intégrent un mécanisme permettant de lier les vues à un document. Pour ce faire, un objet document gére (automatiquement) une listes de pointeurs vers des vues qui le sont associées. De même, un objet vue possède un membre pointant vers le document associé. Par ailleurs, chaque fenêtre cadre possède un pointeur désignant la vue active.

Le but de ce tutorial est de vous expliquer le code que génére Visual C++6 dans le cas d’une application SDI. Ainsi nous créerons un projet MFC AppWizard exécutable nommé “sdi” avec les paramètres suivant dans AppWizard :

  • Single document,Support de l’architecture Document-Vue (étape 1);
  • pas de support de base de données (étape 2) ;
  • pas de prise en charge d’OLE et d’Active X (étape 3) ;
  • decocher les cases dans l’étape 4 ;
  • étape 5, options par défaut

J’appellerai dans la suite les classes de l’application, CMyApp, CmyView, CMyDoc.

La portée globale de l’objet application

///////////////////////////////////////////////////////////////////////////// // The one and only CMyApp object
CMyApp theApp;

L’objet application créé a une portée globale. Une fois l’objet theApp créé, la fonction WinMain() est appelée. Elle appelle à son tour deux fonctions membres de l’objet theApp :

  • InitInstance() qui assure toute initialisation necéssaire de l’application ;
  • Run() qui prend en charge la gestion initiale des messages Windows. Nous ne verrons pas cette fonction dans le WorkSpace puisqu’elle est virtuelle et n’est pas à être redéfini.

CMyApp

InitInstance

Utilité de InitInstance

Comme nous l’avons dit, l’objectif de InitInstance est de donner à l’application l’occasion de s’initialiser elle-même. La valeur retournée par InitInstance détermine ce que doit faire ensuite larchitecture d’application. Si InitInstance retourne FALSE, alors l’application s’arrête ; TRUE, elle continue. InitInstance est l’endroit idéal pour effectuer des initialisations devant intervenir au démarrage.

On a ainsi en partie :

CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMainFrame),
// main SDI frame window
RUNTIME_CLASS(CMyView));
AddDocTemplate(pDocTemplate);
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo)) return FALSE;
// The one and only window has been initialized, so show and update it.
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;

Regardons d’abord :

CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMainFrame),
// main SDI frame window
RUNTIME_CLASS(CMyView));

Ces instructions créent un modèle de document SDI à partir de la classe CSingleDocTemplate. Ce modèle de document identifie la classe document employée por représenter les documents de l’application, la classe de fenêtre cadre englobant les vues des documents et la classe de la vue utilisée pour afficher les données du document.

Détail sur le constructeur CSingleDocTemplate

Le modèle de document contient un ID de ressource dont l’architecture d’application se sert pour charger l’interface utilisateur. Ici, c’est IDR_MAINFRAME, l’ID de ressource utilisé par AppWizard. En regardant les ressources, notre application charge :

  • l’icône de l’application ;
  • les accélérateurs ;
  • le menu.

La macro RUNTIME_CLASS retourne un pointeur vers une structure CRuntimeClass pour la classe spécifiée, ce qui permet à l’architecture d’application de créer des objets de cette classe en cours d’exécution. En effet, les trois arguments du constructeur de CDocTemplate` spécifient les classes fenêtre-cadre, document et vue que le document template va créer dynamiquement en réponse aux commandes de l’utilsateur comme New dans le menu File ou (New Window dans le menu Nouvelle fenetre dans une MDI).

Une fois le modèle de document créé, l’instruction :

AddDocTemplate(pDocTemplate) ;

l’ajoute à la liste des modéles de documents maintenue par l’objet application. Chaque modèle ainsi enregistré défini un type de document géré par l’application. Evidemment , les applications SDI ne gérent qu’un seul type de document, alors que les applications MDI peuvent en enregistrer plusieurs.

Traitement de la ligne de commande

CCommandLineInfo cmdInfo ;
ParseCommandLine (cmdInfo) ;

utilisent CWinApp::ParseCommandLine pour initialiser un objet CCommandLineInfo avec des valeurs qui reflètent les paramètres saisis sur la ligne de commande.

Command-line argument Command executed
app New file.
app filename Open file.
app /p filename Print file to default printer.
app /pt filename printer driver port Print file to the specified printer.
app /dde Start up and await DDE command.
app /Automation Start up as an OLE automation server.
app /Embedding Start up to edit an embedded OLE item.

Création du document, de la fenêtre cadre et de la vue

Les instructions :

if( !ProcessShellCommand(cmdInfo))
return FALSE;

” traitent “ les paramètres de la ligne de commande. Entre autres choses, ProcessShellCommand appelle CWinApp::OnFileNew pour démarrer l’application avec un document vide si aucun nom de fichier n’a été saisi sur la ligne de commmande ou bien appelle CWinApp::OpenDocumentFile pour démarrer l’appliction en chargeant le fichier spécifié. C’est pendant cette phase que l’architecture d’application crée le document, la fenetre cadre et l’objet vu à l’aide des informations stockées dans le modèle de document. ProcessShellCommand renvoie TRUE si l’initialisation s’est bien passée, FALSE sinon. Un FALSE retournée oblige InitInstance à renvoyer aussi FALSE, ce qui ferme l’application.

Affichage des fenêtres

m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();

Une fois les objets instanciés, InitInstance affiche les fenêtres cadre et vue.en appelant ShowWindow et UpdateWindow via le pointeur m_pMainWnd. m_pMainWnd, membre de CMainWindow, pointe vers l’objet fenêtre.

Une fenêtre reste invisible au début sauf si elle n’est pas créée avec l’attribut WS_VISIBLE. ShowWindow ne prend qu’un seul paramètre : un entier spécifiant si la fenêtre doit être initialement affichée sous forme réduite, zoomée ou normale. ? UpdateWindow complète le travail commencé par ShowWindow en forçant un redessin immédiate.

Son travail fini, InitInstance retourne TrUE afin que l’application puisse continuer.

Commentaires sur les autres lignes de code de InitInstance

SetRegistryKey(_T("Local AppWizard-Generated Applications"));
LoadStdProfileSettings();

SetRegistryKey dit à l’application d’utiliser la base de registre et non un fichier .INI, vestige des temps passés. De plus, SetRegistryKey créé un dossier “SDI” dans la rubrique “Local AppWizard-Generated Applications”. Par défaut et comme Microsoft le conseille, le repertoire créé est rangé dans : HKEY_CURRENT_USER\Software\ Local AppWizard-Generated Applications\sdi.

SetRegistryKey ordonne à l’aplication de stocker les derniers fichiers utilisés dans le registre. Pour créer effectivement un repertoire dans la base de registre, enlevez la macro _T.

L’appel à LoadProfileSettings charge et conserve par défaut les 4 derniers fichiers sauvés. L’architecture d’application place alors dans le menu File les noms des quatres derniers fichiers utilisés. On peut charger de 0 à 16 fichiers avec LoadProfileSettings.

LoadProfileSettings(8); conservera les huits derniers fichiers.

Objet Document

Qu’est ce qu’est un “ document “ au sens des MFC ? Dans une application document-vue, en l’occurrence une SDI ici, les données sont rangées dans un objet document appartenant à une classe dérivée de CDocument.

class CMyDoc : public CDocument

Le terme de “document “ ne désigne pas ici en exclusivité des productions de traitement de texte ou de tableurs. Le terme est beaucoup plus générique et ce document peut être une base de données, la position de pièces sur un jeu d’échec.

L’exemple classique est un logiciel de dessin. L’utilisateur, en tracant une ligne, stocke les données dans le document du type : quel sont les points extrémités de la ligne quel est la couleur de la ligne quel est l’epaisseur de la ligne La ligne déssinée par l’utilisateur sera représenté par la classe

class CLigne
{
CPoint m_point1 ;
CPoint m_point2 
; COLORREF m_couleur 
; int m_epaisseur ;
} ;

CMyDoc sera :

class CMyDoc
{
CLigne m_ligne ;
public 
: //fonction membre auxquelles d'autres objets(vues en autre) peuvent recourir pour connaître et modifier les données du document
bool Fonction_faisant_le_lien_entre_Document_et_Utilisateur() ;
} ;

Dans le contexte document-vue, le “document “ est une représentation abstraite des données d’une application.

Une classe dérivée de CDocument hérite un certain nombre de fonction membres importantes:

  • SetModifiedFlag(bool bModified = TRUE ); doit être appelé après avoir fait une modifaication au ocument. En appelant cette fonction régulièrement, on s’assure que l’utilisateur sauvra avnt de fermer le document.En général, on utilise la valeur par défaut. Cette fonction active le flag de modification
  • virtual POSITION GetFirstViewPosition( ) const; Retourne une valeur POSITION que l’on veut passer à GetNextView afin d’énumérer les vues du document.
  • virtual CView* GetNextView( POSITION& rPosition ) const; Retourne un pointeur CView vers la vue suivante dans la liste des vues associées au document
  • const CString& GetPathName( ) const; Donne le nom et le chemin du fichier associé au document (chaîne nulle si le document n’a pas encore de nom
  • const CString& GetTitle( ) const; Donne le nom du fichier associé au doument (chaîne nulle si le document n’a pas encore de nom)
  • void UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL ); OnUpdate() Met à jour toutes les vues associées au document, en appelant la fonction OnUpdate de chaque vue.
  • BOOL IsModified( ); Retourne TRUE si le document contient des données non enregistrées

Dans une appliacation SDI, UpdateAllViews ne sert pas souvent : une vue pour un document.

Redéfinir des fonctions virtuelles avec VC++

Pour redéfinir certaines fonctions virtuelles héritées par exemple de CDocument, cliquez droit sur CMyApp(si votre nom de classe est CMyApp) dans l’espace de travail/onglet ClassView et choissisez “ Add Virtual Fonction… “

De plus, CDocument admet plusieurs fonctions virtuelles qui dans la majorité des cas doivent être redéfinies pour personnaliser le comportement du document.

  • virtual BOOL OnNewDocument( ); Appelée par l’architecture d’application lors de la création d’un document.
  • virtual BOOL OnOpenDocument( LPCTSTR lpszPathName ); Appélée par l’architecture d’application lors de l’ouverture d’un document
  • virtual void DeleteContents( ); Appelée par l’architecture d’application pour effacer le contenu d’un document. A rédefinir si on veut libérer la memoire et d’autes ressources alloués au document avant sa fermeture.
  • virtual void OnCloseDocument( ); appelée lors de la fermeture du document
  • virtual BOOL OnSaveDocument( LPCTSTR lpszPathName ); appelée lors de l’enregistrement
  • virtual BOOL SaveModified( ); appelée avant la fermeture d’un document pour permettre à l’utilisateur d’enregister les dernières modifications
  • virtual void Serialize( CArchive& ar ); Appelée par l’architecture d’application pour sérialiser le document, vers ou depuis un fichier.

Importance de redefinir certaines fonctions virtuelles

Dans une application SDI, l’objet document n’est construit qu’une seule fois, et il est réutilisé chaque fois que l’on crée ou que l’on ouvre un document. Etant donné que le constructeur est exécuté une sule fois, le document doit : réaliser ces initialisation communes dans le constructeur realiser ses initialisations individuelles via OnNewDocument ou OnOpenDocument

Si vous redifinissez ces fonctions grâce au assistant de Visual C++, du code est génére en plus du type :

BOOL CMyDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;
// TODO: Add your specialized creation code here
return TRUE; 
}

Appeler les versions de la classe de base permet d’effectuer des tâches vitales d’initialisation. De toute manière, il faut toujours respecter les manières d’implementer le code, manière indiquée par les commentaires de VC++.