DEVELOPPEMENT ORIENTE OBJET VISUAL C++ MFC et
Transcription
DEVELOPPEMENT ORIENTE OBJET VISUAL C++ MFC et
DEVELOPPEMENT ORIENTE OBJET VISUAL C++ MFC et AppWizards ELABORE PAR : Dr. Amine BOUFAIED www.functionx.com © Copyright 2008 1 Amine BOUFAIED Table des matières 1. – Introduction à la programmation sous Windows Différence principale entre application Win32 et Dos Caractéristiques des programmes sous Windows Structure d'un programme Windows Avantages apportés par l'utilisation des MFC et d’AppWizard 2. – Les MFC et les outils de développement proposés par Visual C++ Introduction Boîte de dialogue, boutons et zones de saisie Gestion des menus, des données et de la souris Création des menus et des barres d’outils 3. – Création d'un agenda grâce aux MFC 4. – Connexion aux sources de données … NOTER BIEN !! CE COURS EST INCOMPLET. C’EST UN DOCUMENT EN COURS DE CONSTRUCTION. 2 Amine BOUFAIED Introduction Nous allons décrire dans ce cours la conception d'une application Windows sur Visual C++ 6.0, assistée par AppWizard et les MFC. Dans un premier lieu, pour démontrer l'avantage apporté par l'utilisation des MFC, nous vous décrierons très rapidement la structure d'une application Win32 sans faire appel aux MFC. Ensuite, pour entrer dans le vif du sujet, nous présenterons les solutions fournies par Microsoft dans le cadre d'un développement rapide et efficace : AppWizard, ClassWizard. Pour cela, nous étudierons la création de Boîtes de dialogue, de boutons et zones de saisie. Aussi, nous aborderons la gestion des menus, des données et de la souris, mais aussi Création des menus et des barres d’outils Enfin, nous réaliserons un agenda grâce aux MFC. 3 Amine BOUFAIED 1er partie : Introduction à la programmation sous Windows Caractéristiques des programmes sous Windows Les programmes Windows réagissent à des événements. Le code d'une application Windows est en grande partie consacré au traitement des événements provoqués par l'intervention de l'utilisateur. Concrètement, vous aurez par exemple, une partie de code définie pour répondre à un clic sur un bouton "OK". Windows enregistre chaque événement dans un message, et place ce dernier dans une file d'attente associée au programme auquel le message est destiné. Ce message est traité par une fonction déterminée pour la gestion de ces messages. En détails, lors de l'envoi d'un message à votre programme, Windows transmet les données nécessaires à votre programme pour répondre à l'évènement, sous la forme d'arguments de cette fonction. Entrée clavier Click Bouton Gauche Souris Click Bouton Droit Souris … Windows Traitement Entrée clavier Traitement click Bouton Gauche Traitement click Bouton Droit … Données Programme Remarque : Toutes les communications entre une application et Windows font appel à l'interface de programmation d'application Windows ou API Windows. Il s'agit en fait d'un millier de fonctions fournies en standard avec Windows et destinées à être employées par votre application. Structure d'un programme Windows Sous sa forme la plus réduite, un programme écrit exclusivement à l'aide de l'API Windows comporte deux fonctions : - une fonction WinMain( ) appelée au début de l'exécution du programme et contenant son initialisation, par analogie, il s'agit de la fonction main( ) d'un programme C, - et la fonction WindowsProc ( ). Ces deux fonctions composent un programme complet, mais elles ne sont pas directement liées. En effet, WinMain( ) n'appelle pas WindowsProc( ). C'est Windows qui assure l'intégration du programme, en établissant le lien avec ces 4 Amine BOUFAIED deux fonctions : la fonction WinMain( ) communique avec Windows en appelant certaines des fonctions de l'API Windows. Il en va de même pour WinProc( ). Avantages apportés par l'utilisation des MFC et d’AppWizard La bibliothèque MFC est une adaptation de Win32 ajoutant les notions d’objet. Contrairement à Win32, l'assimilation des notions concernant la création et l'affichage d'une fenêtre, la boucle de messages et l'analyse des messages transmis à une application sont tous inclus dans le code que Visual C++ peut générer automatiquement. Visual C++ présente les fonctions de l'API Windows selon une approche orientée objet et facilite l'utilisation de l'interface (API) en proposant davantage de fonctionnalités par défaut sous une forme beaucoup plus structurée et conviviale grâce à la bibliothèque Microsoft Foundation Classes ou MFC. Visual C++ offre des outils de développement : AppWizard, ClassWizard, pour simplifier l'édition et la gestion de notre code. Nous détaillerons ces outils dans la suite de ce cours. 5 Amine BOUFAIED 2e partie : Les MFC et les outils de développement proposés par Visual C++ 1 Introduction 1.1 Objets et classes A grande échelle, une classe est comparable à une structure, à un petit détail près : une classe contient du code et des données, alors qu’une structure ne contient que des données. Poursuivons cette analogie; lorsque l’on définit une structure - par exemple la structure Adresse composée d’un champ de caractère pour le nom de la rue, et d’un champ d’entier pour le numéro de rue - l’on définit ensuite une variable du type que l’on vient de créer (Adresse), cette variable est appelée instance de la structure. Un objet n’est rien d’autre que l’instance d’une classe, au même titre que la variable l’était pour la structure. A plus fine échelle, le modèle de programmation objets est basé sur trois éléments majeurs: •l’encapsulation permet d’occulter les détails de l’implémentation d’un objet. On parle aussi de masquage de l’information. •la modularité est la propriété d’un système partitionné en éléments appelés modules. La cohérence et le faible couplage de ces modules aident à contrôler la complexité du système. •la hiérarchie est un classement des abstractions. Le mécanisme d’héritage, simple ou multiple, permet de représenter une hiérarchie d’abstractions. Pour des informations plus détaillées, consultez le site de cours en ligne fourni par l’Université Virtuelle de Tunis. 1.2 Les MFC (Microsoft Foundation Classes) Les MFC constituent un ensemble de classes prédéfinies autour desquelles s'articule la programmation Windows avec Visual C++ : l'écriture d'un programme Windows entraîne la création et l'utilisation d'objets MFC, ou d'objets de classes dérivées des MFC. Les objets créés contiennent des fonctions membres permettant la communication avec Windows, et le traitement des messages Windows. Les MFC représentent un vaste domaine et font intervenir un grand nombre de classes. Elles composent une structure complète de développement d'applications, où il vous suffit d'effectuer les opérations de personnalisation dont vous avez besoin pour adapter les programmes à vos exigences. Par convention, sachez que toutes : - les classes des MFC portent des noms qui commencent par C, comme CDocument ou CView ; - les données membres d'une classe MFC commencent par le préfixe m_. L’utilisateur du programme déclenche des événements par des actions : L’utilisateur clique sur un bouton L’utilisateur modifie la taille d’une fenêtre Etc. En fait, les événements Windows sont traduits en messages par le MFC. Ils sont liés aux éléments de l’interface graphique et sont appelés notifications. Une grande partie du travail d’un programme Windows consiste à traiter les messages et les notifications. On appelle aussi le modèle de Windows la programmation événementielle. Le nom d’un message commence généralement par WM_, et l’événement qui est l’action d’envoyer un message par ON_. 6 Amine BOUFAIED The first decision to communicate to AppWizard, as shown in Figure 1.2, is whether your application should be MDI, SDI, or dialog based. AppWizard generates different code and classes for each of these application types. FIG. 1.2 The first step in building a typical application with AppWizard is choosing the interface. The three application types to choose from are as follows: A single document interface (SDI) application, such as Notepad, has only one document open at a time. When you choose File, Open, the currently open file is closed before the new one is opened. A multiple document interface (MDI) application, such as Excel or Word, can open many documents (typically files) at once. There is a Window menu and a Close item on the File menu. It's a quirk of MFC that if you like multiple views on a single document, you must build an MDI application. A dialog-based application, such as the Character Map utility that comes with Windows and is shown in Figure 1.3, does not have a document at all. There are no menus. (If you'd like to see Character Map in action, it's usually in the Accessories folder, reached by clicking Start. You may need to install it by using Add/Remove programs under Control Panel.) Databases The second step in creating an executable Windows program with AppWizard is to choose the level of database support, as shown in Figure 1.4. FIG. 1.4 The second step to building a typical application with AppWizard is to set the database options you will use. There are four choices for database support: If you aren't writing a database application, choose None. If you want to have access to a database but don't want to derive your view from CFormView or have a Record menu, choose Header Files Only. If you want to derive your view from CFormView and have a Record menu but don't need to serialize a document, choose Database View Without File Support. You can update database records with CRecordset, an MFC class discussed in more detail in Chapter 22, "Database Access." If you want to support databases as in the previous option but also need to save a document on disk (perhaps some user options), choose Database View With File Support. Le MFC comprend : Classes représentant les éléments de l’interface graphique : fenêtres, boutons, listes déroulantes, combo box, etc… Classes utilitaires facilitant la programmation orientée objet o Point (coordonnées de la souris, dimension des fenêtres, etc…) o Date, fichiers, etc… o Enveloppes (wrappers) des fonctions systèmes Win32 Classe CObject : classe de base abstraite utilisée pour la dérivation des autres classes de MFC. Elle est l’ancêtre de la plupart des classes de MFC. Elle entrepose les informations nécessaires pour la détermination des classes (runtime classe information) Classe CWinApp : Elle représente une application Windows. Le AppWizard génère une classe dérivée de CWinApp pour chaque programme. Il existe une fonction membre très importante dans la classe CWinApp, il s’agit de InitInstance(). C’est dans cette fonction membre que l’on règle les paramètres de l’application : - indiquer le type de bibliothèque à utiliser, enregistrer/créer les clés appropriées dans le registre de Windows, enregistrer le patron document/vue, charger l’icône de l’application, etc… * Classe CWnd : classe abstraite représentant une fenêtre. En effet, dans le MFC, tous les contrôles (éléments de l’interface graphique) sont des fenêtres !. Donc, CWnd est la classe de base de tous les contrôles. Cette classe dispose d’un ensemble de fonctions membres pour réaliser la gestion et la manipulation d’une fenêtre. CWnd est 7 Amine BOUFAIED dérivée de CObject, * Classe CDialog : elle est dérivée de CWnd. Elle représente un panneau de dialogue. De plus, elle possède la capacité de gérer le traçage des contrôles déposés. Hiérarchie de la bibliothèque des classes MFC CObject CCmdTarget CWinThread CDocument CGdiObject CBrush CWnd CDC CPaintDC CClientDC CObject CFrameWnd CView CMDIFrameWnd CScrollView CMDIChildWnd CFormView CDialog 8 Amine BOUFAIED 9 Amine BOUFAIED 1.3 AppWizard, ClassWizard et MFC Dans le développement de vos programmes Windows, vous serez amené à vous servir de deux outils. - AppWizard, pour construire le code élémentaire, squelette de votre application Windows au moyen de la bibliothèque MFC. Il s’agit d’un assistant à la programmation Windows extrêmement performant, car, pour créer votre application, il vous suffit de personnaliser un programme tout prêt. Il contient même des explications sur l’endroit où vous devez insérer le code spécifique à l’application. - ClassWizard, facilite l’extension des classes générées par AppWizard comme éléments de base de votre programme Windows. Il permet d’ajouter des classes reposant sur des classes des MFC, pour la prise en charge des fonctionnalités à intégrer dans votre programme. Il convient de noter que ClassWizard ne reconnaît pas les classes ne dérivant pas des classes MFC. L’étendu des services que peuvent fournir ces outils sont conséquent, dans ce cours, nous n’en présenterons qu’une partie Les notions abordées : - Boite de dialogue : La plupart des programmes Windows font appel à des boite de dialogue pour gérer certaines de leurs entrées de données : cliquez sur une commande de menu et une boîte de dialogue s’affiche, contenant divers contrôles vous permettant de saisir des informations. Pratiquement tous les éléments de la boîte de dialogue sont des contrôles. Pour être plus précis, une boîte de dialogue est une fenêtre, et chacun de ses contrôles une fenêtres spéciales. - Les 3 différents contrôles abordés : - Contrôle statique : il contient des informations statiques, telles que titres ou instructions, ou simplement des éléments d’illustration ; - Bouton commande : comme OK ou ANNULER, permettent généralement de refermer une boite de dialogue, ou de confirmer un choix ; - Contrôle d’édition : réduit à sa simple expression, il permet de saisir et de modifier une ligne de texte. - à savoir pour la gestion des classes avec ClassWizard : Comme nous l’avons vu plus haut, Windows communique avec notre programme en lui envoyant des messages ; de même, les MFC vous permettent de définir des fonctions destinées à gérer les seuls messages qui vous intéressent, sans vous préoccuper des autres. Ces fonctions sont appelées gestionnaires de messages, ou simplement gestionnaires. Comme votre application est un programme MFC, les gestionnaires de messages sont dans tous les cas des fonctions membres de l’une des classes de votre application. Une table de correspondance des messages établit la relation entre un message donnée et la fonction qu’il est censé prendre en charge à l’intérieur de votre programme, sachant que chaque classe du programme habilitée à gérer les messages Windows en possède une. La table de correspondance des messages de la classe se présente sous la forme d’un tableau de fonctions membres prenant en charge les messages de Windows. Toutes ses entrées associent un message spécifique à une fonction ; de sorte que l’arrivée de chaque message déclenche l’appel de la fonction correspondante. AppWizard et ClassWizard créent automatiquement une table de correspondance de messages lorsque vous ajouter une classe pouvant gérer des messages. Dans cette table c’est principalement ClassWizard qui se charge des ajouts et des suppressions, même si dans certain cas vous êtes amené à modifier la table vousmême. Avec Visual C++, trois approches sont proposées pour développer des applications Windo ws : 1. Les fonctions de l’API (Application Program Interface) : cette méthode est très lourde. Elle oblige à développer de nombreuses lignes de code avant d’aboutir à un résultat concret. 2. Les MFC (Microsoft Foundation Classes) : ces classes interfacent Windows d’une manière bien plus simple que les fonctions de l’API. Le travail de programmation en est simplifié d’autant. Avec quelques lignes de code, il est possible de constituer une interface incluant les objets standard de Windows : barres 10 Amine BOUFAIED de défilement, barre d’outils, menus, boutons, etc... 3. Les MFC et les AppWizards (assistants). Cette dernière méthode est de loin la plus souple : il suffit de préciser le type de traitement à effectuer, et les assistants écrivent une grande partie du code. Lorsque la trame du programme a été générée, il ne reste plus qu’à "remplir les trous" laissés vides par les assistants pour réaliser des tâches plus spécifiques. 2 Boîte de dialogue, boutons et zones de saisie AppWizard est un assistant à la programmation Windows, il définit automatiquement les classes nécessaires pour une application MFC qui sont : la classe d’application et la classe de fenêtre, ainsi il contient des indications sur l’endroit où on doit insérer le code spécifique à l’application. On appelle l’assistant AppWizard dès la création de l’espace de travail, ainsi il donne la possibilité de choisir le type d’application SDI, MDI, ou à base de boites de dialogue. Parmi les éléments qu’on trouve dans presque toutes applications Windows on trouve : les boutons, les cases à activer, les zones de texte, les listes déroulantes… ces éléments sont connus sous le nom de contrôles, et la plupart d’entre eux sont intégrés au système d’exploitation Windows. On peut classer ces contrôles selon leurs types comme le montre le tableau suivant : Type Contrôle statique Champs de saisie Les boutons Utilisé pour… Aspect * afficher des étiquettes ou des intitulés Texte statique * permet à l’utilisateur d’entrer ou de modifier un texte L’utilisateur peut cliquer pour déclencher une action C’est un contrôle que l’utilisateur peut activer ou désactiver Il est utilisé dans un groupe de deux ou plus, et un seul bouton peut être activé à la fois Champs de saisie Bouton de commande Case à cocher Bouton radio * Elle se compose d’un champ de saisie auquel se rattache une liste de valeur * Elle affiche une liste d’éléments prédéfinis qu’on peut sélectionner Zone de liste déroulante Zone de liste Création d’une application basée dialogue Three classes have been created for you for the application called FirstMDI: CAboutDlg, a dialog class for the About dialog box CFirstDialogApp, a CWinApp class for the entire application CFirstDialogDlg, a dialog class for the entire application La programmation d’une boite de dialogue comporte l’affichage et le traitement des contrôles utilisés. Pour le traitement des contrôles utilisés dans la boite de dialogue, on peut utiliser l’assistant ClassWizard qui permet de construire la table de messages Windows que l’application pourrait recevoir, y compris les fonctions auxquelles ils devraient être passés. 11 Amine BOUFAIED D’une façon générale, la programmation d’une boite de dialogue peut se résumer par les étapes suivantes : Conception de l’interface et mise en forme de l’application : lorsqu’on ajoute un contrôle, on doit configurer ces propriétés, Exemple : Objet Propriété Valeur Bouton de commande ID Caption IDC_FERME FERME Association de variables aux contrôles : avant de commencer l’écriture du code de l’application, il faut assigner des variables à chacun des contrôles auxquels sera associée une valeur. Ajout de fonctionnalités aux contrôles : personnalisation de code de l’application. Dans la plupart des applications, l’utilisateur est sollicité pour fournir des informations de configuration, à savoir par exemple si on veut enregistrer les données avant de quitter, et dans la majorité des cas l’application ouvre une nouvelle fenêtre (fenêtre de dialogue) pour formuler de tels demandes. Windows fournit un certain nombre de fenêtres de dialogue intégrées : 1) les fenêtres de message : qui présentent un message à l’utilisateur contenant un ou plusieurs boutons pour récupérer l’entrée de l’utilisateur selon son besoin. Pour utiliser ce genre de fenêtre, il suffit d’appeler la fonction MessageBox(), à laquelle on passe le texte d’un message comme argument, ainsi que d’autres paramètres qui indiquent les boutons et l’icône à afficher dans cette fenêtre. Identifiants de combinaisons de boutons pour la fonction MessageBox() MB_ABORTRETRYIGNORE MB_OK MB_OKCANCEL MB_RETRYCANCEL MB_YESNO MB_YESNOCANCEL Identifiants d’icônes pour la fonction MessageBox() MB_ICONINFORMATION MB_ICONQUESTION MB_ICONSTOP MB_ICONEXCLAMATION Syntaxe de la fonction MessageBox() : Int MessageBox(« message à afficher », « titre de la fenêtre de message », MB_ | MB_ ) ; Pour le bouton Pour l’icône 2) Fenêtres de dialogue standards : sont utilisées dans la plupart des applications Windows pour ouvrir ou enregistrer un fichier, configurer les options d’impression, etc. 12 Amine BOUFAIED Les MFC fournissent plusieurs classes pour les fenêtres standards, citées dans le tableau suivant : Classe standards de type dialogue CFileDialog Sélection de fichier CFontDialog Sélection de police CColorDialog Sélection de couleur CPgeSetupDialog Mise en forme de pages pour l’impression CPrintDialog Impression CFindReplaceDialog Recherche et remplacement Ouvrez Visual C++ et ensuite Menu File puis New, ensuite suivez les étapes comme cela est illustré cidessous. AppWizard, Marche à suivre 13 Amine BOUFAIED Un projet a été créé, et le squelette de l’application également. Il est déjà possible de compiler et d’exécuter le projet. Manipulation 1 Compilez, buildez (correspond au linkage dans d’autres langages de programmation) et exécutez le programme. Pour ceci utilisez le menu Build ou les raccourcis claviers, respectivement CTRL-F7, F7 puis CTRL-F5. Vous avez créé une application basée sur une boîte de dialogue, vous apprendrez plus loin à créer une application dans une "vraie" fenêtre (le choix se fait au niveau de l’étape 1- Step 1 - dans la marche à suivre ci-dessus). Vous allez maintenant éditer le contenu de cette boîte de dialogue; pour ceci visualisez les ressources à l’aide de l’onglet Workspace de la fenêtre View (par défaut elle occupe la partie gauche de Visual Studio). Il est maintenant possible d’ajouter ou de supprimer des boutons, des zones de texte, des zones de saisie, etc...Les modifications qui vont suivre permettent de réaliser une application permettant d’additionner deux valeurs. Manipulation 2 Supprimez tout d’abord la zone de texte existante dans la boîte de dialogue, ensuite ajoutez trois zones de saisie (Edit Box, 4ème icone dans la barre Controls) et un bouton (6ème icone). Changez le texte écrit sur le bouton en choisissant l’option Properties (cliquez avec le bouton droit de la souris sur le bouton) et en changeant le champ Caption. Il faut maintenant déclarer des variables correspondantes aux valeurs contenues dans ces trois masques de saisies. Ceci se fait à l’aide de l’assistant ClassWizard (accessible dans le menu View ou directement à l’aide de CTRL-W). Allez sur l’onglet Member Variables, et attribuez un type (int) et un nom de variable (m_operande1, m_operande2 et m_resultat) aux trois zones de saisies (normalement identifié par IDC_EDIT1, 2 et 3). 14 Amine BOUFAIED ClassWizard, marche à suivre (suite) Une fenêtre de ce type pour chacune des trois zones IDC_EDIT Il faut également associer une action (un événement) lors de l’appui sur le bouton additionner (le bouton qui a été rajouté). Dans le MFC, le routage des messages est centralisé à l’aide de cartes de messages (Message Maps). Chaque composant d’un programme MFC disposant d’une queue de messages peut avoir une carte de message. Cela signifie que les cartes de messages sont associées à des classes dérivées de MFC. Les cartes de messages sont normalement instaurées par l’AppWizard à l’aide de macros. La syntaxe de ces macros : DECLARE_MESSAGE_MAP // placé dans la déclaration d’une classe dérivée de MFC (header file) BEGIN_MESSAGE_MAP (Projet1, CDialog ) { ON_BN_CLICKED(ID_ACTION, OnAction) } END_MESSAGE_MAP Placé dans la définition d’une classe dérivée de MFC (source file) Il existe un nombre élevé de macros pour la gestion des événements, pour cette raison on utilise le ClassWizard qui présente les événements possibles des éléments graphiques d’un objet de programmation. 15 Amine BOUFAIED Dans ClassWizard, choisissez l’onglet Message Maps (le 1er) et sélectionner IDC_BUTTON1 et BN_CLICKED puis appuyez sur Add Function , acceptez le nom qui vous est proposé et une ligne de plus apparaît sous member functions. Cliquez enfin sur Edit Code pour écrire le code à exécuter lors de l’appui sur le bouton. Le code qui suit permet de lire les valeurs dans les deux premières zones de saisies puis de les additionner et d’afficher le résultat dans la troisième zone. void CBoutonDlg::OnButton1() { // TODO: Add your control notification handler code here // UpdateData permet de lire (TRUE) ou d'écrire (FALSE) dans les zones // de saisies en mettant à jour les variables correspondantes UpdateData(TRUE); m_resultat = m_operande1 + m_operande2; UpdateData(FALSE); } Après avoir recompilé et fait l’édition des liens du programme, exécutez le. Il suffit maintenant d’entrer des valeurs dans les deux premiers champs et d’appuyer sur additionner pour obtenir le résultat. 3 Gestion des menus, des données et de la souris Dans la partie précédente, vous avez appris à créer des applications simples basées sur des boîtes de dialogues ("Dialog based"). Vous avez pu remarquer que dans le cas d’une application "Dialog based" (comme la précédente), il n’a été créé que deux classes par AppWizard : CBoutonApp et CBoutonDlg. Il est expliqué plus bas (dans le cas d’une explication plus générale) l’utilité de ces classes. Pour pouvoir exploiter des fonctions plus avancées comme les menus, la souris, le dessin ou la sauvegarde des don- nées, vous allez maintenant apprendre à créer des applications SDI (Single Document Interface). La fenêtre d’une application SDI peut-être une boîte de dialogue (à ne pas confondre avec une application "Dialog based" - comme précédemment - dans ce cas c’est la fenêtre qui est "Dialog based") ou un document. Dans le premier cas, la fenêtre est comparable à celle d’une application "Dialog based", si ce n’est qu’elle est munie d’un système de menus, et éventuellement d’une barre d’outils et/ou d’une barre d’état. Dans le second cas, la fenêtre comporte une zone qui permet de recevoir du texte, des éléments dessinés, des bitmaps, des AVI, etc... Le choix à effectuer dépend donc du type d’application à réaliser. Il faut néanmoins garder à 16 Amine BOUFAIED l’esprit que ces différences ne sont la conséquence que d’un héritage (propriété du c++) différent, et qu’il est tout à fait possible, par exemple, de dessiner dans une SDI du type boîte de dialogue en utilisant des fonctions des classes de bases (en amont des classes héritées). Les exemples qui vont suivre sont tous des applications SDI dont la fenêtre d’affichage est "Dialog based" (permettant ainsi d’utiliser l’éditeur de boîte de dialogue pour y ajouter des boutons ou des zones de textes). Manipulation 4 Créez un nouveau projet MFC AppWizard, que vous appellerez didact, de type SDI dont la fenêtre d’affichage est "Dialog Based" en vous aidant des copies d’écrans qui suivent. 17 Amine BOUFAIED Table 3: AppWizard SDI, marche à suivre C’est ici que l’on choisit SDI La classe CDidactView hérite de CFormView 18 Amine BOUFAIED Vous pouvez constater que l’assistant a créé un certain nombre de classe (plus que pour l’exercice bouton) , dont CDidactDoc (classe document) qui sert à l’archivage et CDidactView (classe view) qui représente ce que l’on voit à l’écran (donc également ce que l’on va y dessiner). Vous trouverez ci-dessous une explication non exhaustive de ces différentes classes communes à tous les projets MFC de type SDI (à l’exception du nom du projet inclus dans le nom de chaque classe : pour un projet nommé "truc", la classe document sera appelée CTrucDoc et la classe view CTrucView, etc...) Five classes have been created for you. For the application FirstSDI, they are as follows: CAboutDlg, a dialog class for the About dialog box CFirstSDIApp, a CWinApp class for the entire application CFirstSDIDoc, a document class CFirstSDIView, a view class CMainFrame, a frame class 3.1 CAboutDlg Cette classe gère la boîte de dialogue qui contient le nom ainsi que le numéro de version du programme et qui apparaît lors de la sélection dans le menu ? de la fonction A propos. Déclarée et définie dans les fichiers didact.h et didact.cpp, cette classe dérive de CDialog . 3.2 CDidactApp Cette classe gère le "comportement" des classes nécessaire à l'application, en particuliers les classes RUNTIME (temps réel) View, Doc et MainFrame. Déclarée et définie dans les fichiers didact.h et didact.cpp, cette classe dérive de CWinApp. 3.3 CMainFrame Cette classe s'occupe de la gestion de la fenêtre principale. C'est-à-dire dimensionner la taille de la fenêtre au démarrage ainsi que la gestion (et l'affichage) de la barre des menus et de la barre des outils (toolbar). Déclarée et définie dans les fichiers MainFrm.h et MainFrm.cpp, cette classe dérive de CFrameWnd. 3.4 CDidactDoc (classe Document) Cette classe gère le document (objet de la classe Document). Un document représente la partie des données que l'utilisateur ouvre avec la commande File Open et sauve avec la commande File Save. Cette classe permet des opérations standard tels que création d'un nouveau document, son chargement, et son archivage. Le programme manipulera les données en utilisant l'interface (au sens large) définie dans cette classe. Ces données sont bien évidemment accessibles aux autres classes de l’application, et en particulier à la classe View. Utilisé en interaction avec la classe View, un document peur avoir de multiple views qui lui sont associées. Quand l'utilisateur ouvre un document le programme principal crée une "view" et l'attache au document. Déclarée et définie dans les fichiers didactDoc.h et didactDoc.cpp, cette classe dérive de CDocument. 3.5 CDidactView (classe CView) Cette classe gère l'affichage (ou plus exactement des views, objets de la classe CView). Une view est attachée à un document (objet de la classe Document) et fait office d'intermédiaire entre le document et l'utilisateur : une view traduit l'image d'un document sur l'écran ou l'imprimante et interprète les entrées de l'utilisateur comme des opérations sur le document. Une view est une enfant de la fenêtre principale. Plusieurs vues peuvent se partager la fenêtre principale, comme dans le cas de fenêtre divisée (par exemple ouverture de deux documents Word et affichage des deux en même temps). Une view est responsable de l'affichage et de la modification des données du document mais pas de leurs sauvegarde. Le document fournit à la view les détails concernant les données qu'il gère. Il est possible de laisser la liberté à la view d'accéder directement aux données du document (pas très propre) ou d'y accéder à travers des fonctions définies dans la classe CDocument (préférable). Déclarée et définie dans les fichiers didactView.h et didactView.cpp, cette classe dérive de CView. 19 Amine BOUFAIED A chaque classe correspond un fichier “.h” (définition des membres de la classe et des prototypes des méthodes) et un fichier “.cpp” (corps des méthodes) Manipulation 5 Compilez, linkez et exécutez l’application Didact. (Rem: n’oubliez pas de fermer l’application après cela, sinon lors du prochain linkage vous aurez une erreur d’accès au fichier en cours d’utilisation). Vous constaterez qu’il y a quatre menus : Fichier, Edition, affichage et ?. Exercice 6 Supprimez également le menu Edition qui ne servira pas pour la suite (hint: utilisez l’éditeur de ressources et éditez IDR_MAINFRAME, ensuite sélectionnez le menu Edition et la touche Delete !). Vous allez maintenant apprendre à créer une application qui permet de dessiner à main levée sur l’écran et de choisir la couleur RGB du trait, ainsi que de sauver les paramètres de la couleur dans un fichier. Manipulation 7 Ajoutez trois champs de texte Edit Box (qui serviront à déterminer la valeur de chaque composantes RGB) à l’aide de l’éditeur de ressource. Ajoutez également trois champs de texte statique Red, Green, Blue (ceci uniquement pour l’aspect visuel afin d’avoir une description des trois champs d’édition). Ensuite à l’aide de ClassWizard (CTRL-W) et de l’onglet Member Variables faites correspondre aux trois Edit Box trois variables int : m_red, m_green, m_blue et limitez leur variation de 0 à 255 (Minimum et Maximum Value dans ClassWizard). 20 Amine BOUFAIED Vous pouvez constater que ces trois variables ont été définies dans la classe CDidactView (fichiers didactView.cpp et didactView.h). Ces trois variables sont donc propres à cette classe. Pour pouvoir les archiver, vous allez créer trois autres variables propres à la classe CDidactDoc ainsi que des fonctions pour pouvoir y accéder depuis d’autres classes (à l’aide d’un pointeur sur la classe CDidactDoc). Vous pouvez accéder aux différents fichiers composant l’application à l’aide de l’onglet FileView du Workspace (à gauche de l’écran). Déclarez les trois variables private (red, green, blue) et les six fonctions public (SetRed, GetRed, SetGreen, GetGreen, SetBlue et GetBlue) dans le fichier didactDoc.h : // didactDoc.h : interface of the CDidactDoc class // ///////////////////////////////////////////////////////////////////////////// ... class CDidactDoc : public CDocument ... // Attributes private: int Red; int Green; int Blue; public: void SetRed(int valeur_rouge); void SetGreen(int valeur_vert); void SetBlue(int valeur_bleu); int GetRed(); int GetGreen(); int GetBlue(); ... #endif // !defined(AFX_DIDACTDOC_H__BE53CD6B_9FF6_11D2_B496_000001356673__INCLUDED_ ) 21 Amine BOUFAIED Définissez maintenant les six fonctions ainsi que les valeurs par défaut des trois variables dans le fichier didactDoc.cpp : ... CDidactDoc::CDidactDoc() //Constructeur { // TODO: add one-time construction code here Red = 0; Green = 0; Blue = 0; } CDidactDoc::~CDidactDoc() //Destructeur { } void CDidactDoc::SetRed(int valeur_rouge) { Red = valeur_rouge; } void CDidactDoc::SetGreen(int valeur_vert) { Green = valeur_vert; } void CDidactDoc::SetBlue(int valeur_bleu) { Blue = valeur_bleu; } int CDidactDoc::GetRed() { return Red; } int CDidactDoc::GetGreen() { return Green; } int CDidactDoc::GetBlue() { return Blue; } BOOL CDidactDoc::OnNewDocument() { ... Remarque : On aurait pu déclarer et définir les six fonctions dans le fichier en-tête (didactDoc.h), cela aurait été plus rapide mais moins "propre". Il existe deux méthodes pour la lecture et l’écriture de données sur disque : la sérialisation des données, qui utilise classiquement les commandes du menu Fichier, et l’accès direct, qui fait appel aux fonctions de la classe CFile. La première méthode utilise la fonction Serialize(), prédéfinie par l’assistant AppWizard lors de la définition du squelette de l’application. La seconde repose sur les fonctions de la classe CFile : Open, Close, Read, Write, etc... On utilisera dans ce cours uniquement la sérialisation des données, qui est très simple à implémenter et qui permet d’utiliser directement les fonctions du menu Fichier. Ajoutez ces lignes nécessaires à l’archivage des trois variables Red, Green, Blue dans le fichier didactDoc.cpp : 22 Amine BOUFAIED ... ///////////////////////////////////////////////////////////////////////////// // CDidactDoc serialization void CDidactDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // ar ar ar TODO: add storing code here << Red; << Green; << Blue; // ar ar ar TODO: add loading code here >> Red; >> Green; >> Blue; } else { } } ... Les instructions situées sous le if sont exécutées dans le cas d’un enregistrement de données (fonction Enregistrer ou Enregistrer sous dans le menu Fichier). Celles sous le else dans le cas d’une lecture de données (fonction Ouvrir dans le menu Fichier). La variable ar représente l’archive, c’est-à-dire le fichier que vous allez lire ou dans lequel vous allez écrire. Les opérateurs surchargés << et >> indiquent le sens dans lequel transitent les données. Manipulation 8 Les trois champs de saisie ainsi que leurs variables associées (m_Red, m_Green et m_Blue) sont propres à la classe CD id ac t View et ne sont donc, pour le moment, pas du tout reliées aux variables de la classe document. Vous allez donc commencez par ajoutez un bouton (à l’aide de l’éditeur de ressource) qui servira à valider les données dans les trois champs de saisie, puis éditez le code correspondant à un clic sur ce bouton (comme dans le premier exemple d’addition à l’aide de ClassWizard CTRL W). 23 Amine BOUFAIED Maintenant vous allez créez le code associé à l’appui sur le bouton. L’appui sur le bouton devra mettre à jour les variables Red, Green et Blue (par l’intermédiaire des fonctions SetRed, SetGreen et SetBlue car les variables susmentionnées ont été déclarées en private et il n’y donc pas possible d’y accéder directement depuis d’autre classe que la classe document). Il faut pour ceci déclarer un pointeur sur la classe document dans la classe CDidactView. Donc, après l’appui sur Edit Code de la figure ci-dessus, vous vous retrouverez dans le fichier didactView.cpp et éditerez la fonction OnButton1 comme ceci : ... /////////////////////////////////////////////////////////////////////// ////// // CDidactView message handlers void CDidactView::OnButton1() { // TODO: Add your control notification handler code here // UpdateData(TRUE) met à jour m_red, m_green et m_blue avec les valeurs // contenues dans les trois masques de saisie UpdateData(TRUE); CDidactDoc *pDoc = GetDocument(); // pDoc = pointeur sur la classe document pDoc->SetRed(m_red); pDoc->SetGreen(m_green); pDoc->SetBlue(m_blue); } Afin de pouvoir retrouver les valeurs qui auront été sauvées, il faut les récupérer (c-à-d. prendre les valeurs contenues dans la classe document et les "coller" dans les masques de saisie de la classe CDidactView) lors de l’affichage de la fenêtre (l’idéal serait dans le constructeur de la classe CDidactView, mais à ce moment, le pointeur sur la classe document ne peut pas être utilisé). Manipulation 9 Vous allez donc ajouter la fonction OnDraw() dans la classe C D i d a c t V iew, à l’aide de ClassWizard (CTRL W). Pour ceci, sélectionnez l’onglet Message Map dans ClassWizard, puis sélectionnez CDidactView sous Object IDs et OnDraw sous Messages et finalement cliquez sur Add Function. 24 Amine BOUFAIED Ensuite cliquez sur Edit Code, et éditez OnDraw de la façon suivante: ... void CDidactView::OnDraw(CDC* pDC) { // TODO: Add your specialized code here and/or call the base class CDidactDoc *pDoc = GetDocument(); m_red = pDoc->GetRed(); m_green = pDoc->GetGreen(); m_blue = pDoc->GetBlue(); UpdateData(FALSE); //écrit les valeurs dans les masques de saisies } Manipulation 10 Afin de vérifier si tout marche correctement, exécutez le programme, entrez trois valeurs dans les masques de saisie (10, 20, 30 par exemple) puis validez à l’aide du bouton. Enregistrez (menu Fichier) sous essai1 et fermez le programme. Exécutez à nouveau le programme et ouvrez le fichier essai1... Understanding a Multiple Document Interface Application A multiple document interface application also has menus, and it enables the user to have more than one document open at once. This section presents the code that is generated when you choose an MDI application with no database or compound document support, but instead with a toolbar, a status bar, Help, 3D controls, source file comments, and the MFC library as a shared DLL. As with the SDI application, these are the defaults after Step 1. The focus here is on what differs from the SDI application in the previous section. Five classes have been created for you. For the application FirstMDI, they are CAboutDlg, a dialog class for the About dialog box CFirstMDIApp, a CWinApp class for the entire application CFirstMDIDoc, a document class CFirstMDIView, a view class CMainFrame, a frame class 25 Amine BOUFAIED 3.6. Les événements de la souris Selon le type de l’application, on peut avoir besoin de connaître les actions que l’utilisateur réalise au moyen de la souris : il nous faut savoir quand et où l’utilisateur a cliqué, quel bouton de la souris a été activé et quant a été relâché. En ce qui concerne les événements de la souris on remarque que ceux qui sont disponible le plus souvent sont : le clic et le double clic, mais si on observe la souris on se dit qu’il existe d’autres événements qui sont répertoriés dans le tableau suivant : Messages d’événements de la souris WM_LBUTTONDOWN Le bouton gauche a été activé WM_LBUTTONUP WM_LBUTTONDBCLK WM_RBUTTONDOWN WM_RBUTTONUP WM_RBUTTONDBCLK WM_MOUSEMOVE WM_MOUSEWHEEL Le bouton gauche a été relâché On double-cliqué sur le bouton gauche Le bouton droit a été activé Le bouton droit a été relâché On a double-cliqué sur le bouton droit La souris est déplacée à travers la fenêtre La molette de la souris est actionnée 3.7. Les événements du clavier La capture des événements du clavier est semblable à celle de la souris, mais il existe moins de messages d’événements. En effet, on trouve des messages pour l’activation d’une touche et pour le relâchement, ç savoir : WM_KEYDOWN WM_KEYUP Messages d’événements du clavier Une touche est appuyée Une touche est relâchée 3.8. Création de dessins avec la souris Parmi les actions qu’on peut réaliser avec la souris, la création des dessins. Pour pouvoir programmer le dessin de formes, on doit connaître plusieurs éléments sur l’état de la souris, en particulier : à quel moment le bouton de la souris est enfoncé puisque c’est l’indication du début de l’opération de dessin la position du curseur lorsqu’on appuie sur le bouton à quel moment la souris se déplace et la position correspondante du curseur à quel moment le bouton de la souris est relâché et quelle est la position du curseur Parmi les fonctions qui peuvent être développées, on trouve : la fonction OnMouseMove (UINT nFlags, CPoint point) pour le message WM_MOUSEMOVE la fonction OnLboutonDown (UINT nFlags, CPoint point) pour le message WM_LBOUTONDOWN Ces deux fonctions peuvent recevoir les deux paramètres : UINT nflags : c’est un ensemble d’indicateurs (drapeaux) qui sont utilisés pour déterminer si le bouton de la souris est activé (et lequel) CPoint point : il renseigne sur la position de la souris (coordonnées actuelles) 26 Amine BOUFAIED Dessin de la souris 3.9. Incorporations de graphiques, de dessins et de bitmaps Le système d’exploitation Windows offre plusieurs niveaux d’abstraction pour la création et l’utilisation de graphiques dans l’application. En effet, Microsoft a facilité cette tâche en fournissant un dispositif graphique virtuel pour toutes les applications Windows, et permettant ainsi de créer toutes sortes de graphiques dans une application. Avant de créer des graphiques, on doit tout d’abord créer le device context (contexte de périphérique) ou le DC. Le DC contient des informations sur le système, sur l’application et sur la fenêtre dans laquelle on va dessiner. Le DC est utilisé par le système d’exploitation pour connaître dans quel contexte un graphique est crée, quelle est la surface visible, et quel est l’emplacement courant sur l’écran. Le DC utilise deux ressources pour exécuter la plupart des ses fonctions graphiques : le crayon (pen), le pinceau (brush) et l’image (bitmap). Pour pouvoir expliter ces fonctionnalités, on doit définir les classes appropriées pour ces ressoures, à savoir : la classe CDC : elle offre de nombreuses fonctions permettant de dessiner différentes formes graphiques. La classe CPen : cette classe est la ressource d’outil principale pour dessiner toutes sortes de lignes à l’écran, on peut créer une instance de cette classe et spécifier le type, l’épaisseur et la couleur de la ligne Exemple : CPen lpen(PS_SOLID, 1, RGB(0, 0, 0)) ; La classe CBrush : cette classe permet de créer des pinceaux qui définissent le type de remplissage d’une surface Exemple : CBrush lsolid( RGB(0 , 0, 255)) ; La classe CBitmap : si on ajoute une image bitmap comme ressource, on peut créer une instance de cette classe et spécifier l’identifiant de ressource bitmap comme image à charger à partir d’un fichier Exemple : CBitmap lbitmap ; Lbitmap.LoadBitmap(IDB_MYBITMAP) ; Vous allez maintenant apprendre à gérer les messages de la souris pour dessiner à main levée dans l’application. Pour cela vous aurez besoin de deux messages de la souris : un lors de l’appui sur le bouton gauche de la souris et l’autre à chaque mouvement de la souris. Manipulation 11 Ajoutez deux messages dans la classe CDidactView, à l’aide de ClassWizard (CTRL W). Pour ceci, sélectionnez l’onglet Message Map dans ClassWizard, puis sélectionnez CDidactView sous Object IDs et WM_LBUTTONDOWN sous Messages et finalement cliquez sur Add Function. Répétez l’opération pour ajoutez également WM_MOUSEMOVE. 27 Amine BOUFAIED Visualisez le code généré d’une de ceux fonctions (utilisez le bouton Edit Code dans AppWizard ou allez manuellement à la fin du fichier didactView.cpp, cela produit le même résultat...). Vous constaterez que ces deux fonctions reçoivent deux paramètres : nFlags et point. Lors de l’occurrence de l’un de ces deux événe- ment, le noyau temps réel se chargera de remplir ces deux paramètres correctement : NFlags contiendra l’état des bouton de la souris (enfoncé ou non enfoncé) et point la position de la souris lors de l’occurrence du mes- sage. Pour comprendre comment cela fonctionne, imaginez que vous voulez dessiner à l’écran: 1. Vous commencez par appuyer sur le bouton gauche de la souris à une certaine coordonnée de l’écran, il faut sauver cette première coordonnée dans une variable pnt_précédent. 2. Lors du premier mouvement de la souris (le plus petit perceptible par votre ordinateur), vous devez tracer un trait entre le pnt_précédent et le point courant (à condition que le bouton de gauche soit toujours pressé, d’où l’utilité des nFlags). Ensuite le point courant devient le nouveau pnt_précédent. 3. Lors du deuxième mouvement de la souris, vous tracez un trait entre le pnt_précédent et le point courant. Ensuite le point courant devient le nouveau pnt_précédent. 4. Etc..., jusqu’à ce que le test des flags indique que le bouton de gauche n’est plus pressé. Dans ce cas, il ne faut plus tracer de trait à l’écran Remarque: OnLButtonDown n’est appelée qu’au moment de l’appui sur le bouton gauche alors que OnMouseMove l’est à chaque déplacement de la souris (même si le bouton n’est pas pressé) Créez les deux variables pnt_précédent (une pour l’abscisse l’autre pour l’ordonnée) ainsi qu’un crayon qui sera utile plus loin dans le fichier didactView.h. : ... protected: // create from serialization only CDidactView(); DECLARE_DYNCREATE(CDidactView) private: int XPrec; //Abscisse précédente int YPrec; //Ordonnée précédente CPen *Crayon; //Crayon public: ... //{{AFX_DATA(CDidactView) 28 Amine BOUFAIED Avant d’écrire le code correspondant aux deux fonctions OnLButtonDown et OnMouseMove, il est encore nécessaire de comprendre comment dessiner un trait entre deux points : 1. Définir un pointeur sur la fenêtre active (appelé Device Context). 2. Définir un crayon possédant certaines propriétés dont la couleur. 3. Déplacer le crayon à une certaine position (le crayon ne dessine pas). 4. Tracer une ligne jusqu’à un autre point (le crayon dessine). Maintenant vous savez tout ce qui est nécessaire à l’écriture du code. Editez donc les deux fonctions OnL- ButtonDown et OnMouseMove (rappel: le squelette a déjà été créé précédemment) à la fin du fichier didac- tView.cpp de la façon suivante : void CDidactView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default // Initialisation du point de départ XPrec = point.x; YPrec = point.y; //Aller chercher la couleur du crayon dans le document int pen_red, pen_green, pen_blue; CDidactDoc *pDoc = GetDocument(); pen_red = pDoc>GetRed(); pen_green = pDoc>GetGreen(); pen_blue = pDoc>GetBlue(); //Définir un nouveau crayon de la bonne couleur Crayon = new CPen(PS_SOLID, 1, RGB(pen_red,pen_green,pen_blue)); CFormView::OnLButtonDown(nFlags, point); } void CDidactView::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default // Effectue tout d'abord un test pour savoir si le bouton droit est pressé if ( (nFlags & MK_LBUTTON) == MK_LBUTTON ) { //Obtenir tout d'abord un pointeur sur la fenêtre active CDC* pDC = GetDC(); //Sélectionner le crayon pDC->SelectObject(Crayon); //Dessiner le trait allant du point précédent au point courant pDC->MoveTo(XPrec,YPrec); pDC->LineTo(point.x,point.y); //Le point courant devient le point précédent XPrec = point.x; 29 Amine BOUFAIED YPrec = point.y; } CFormView::OnMouseMove(nFlags, point); } Manipulation 12 Vous avez maintenant terminé l’application. Compilez, linkez, exécutez et amusez -vous avec... 4. – Création des menus et des barres d’outils Lorsque les 1ers terminaux ont été introduits et que les utilisateurs ont commencé à employer des applications, les développeurs de logiciels ont constaté le besoin de fournir une sorte de menu des actions qui peuvent être exécutées par l’ordinateur. Depuis, les menus ont évolué, ils respectent tous les types de conception standard permettant un emploi et un apprentissage aisé des éléments des menus. Les premiers styles de menus standardisés sont les menus déroulants ou en cascade. Il s’agit de menus dont les différentes catégories sont regroupées en barre au sommet de l’application. Si une d’elles est sélectionnée, un menu est déroulé sous les catégories et présente un certain nombre d’options qui peuvent être sélectionnées afin de déclencher des actions dans l’application. Plus tard, un autre style de menu a été développé, appelé le menu contextuel, c à d qui apparaît au milieu de l’espace de travail de l’application, il est spécifique à l’objet en cours. Menus standards et conventions : Bien qu’aucun standard n’ait été défini sur le mode construction, il existe un certain nombre de conventions à respecter lors de leur conception et de leur fabrication : utilisation de catégorie de menu à mot unique le menu fichier contient toutes les options relatives aux fichiers le menu édition contient toutes les fonctions de modification le menu affichage contient les options qui permettent de contrôler et de modifier l’aspect de l’espace de travail Création de menu : Les menus sont définis comme des ressources dans les applications Visual C++. Pour créer un menu, il suffit de suivre les étapes suivantes : créer les applications qui accueilleront les menus ajouter une ressource de type menu au projet (à l’aide de l’entête ressource View) personnaliser et ajouter les fonctionnalités au menu écriture et définition des caractéristiques des ressources de menus La définition d’un menu dans une ressource permet de modifier l’aspect physique de celui-ci. Lorsque l’utilisateur sélectionne une commande du menu, un message est généré pour gérer la fonctionnalité de cette commande. Comme toute autre ressource d’une application, on doit spécifier les caractéristiques des éléments du menu. Pour cela on choisit la classe où gérer les messages, et créer les fonctions relatives aux messages de menu Création de menu contextuel : La majorité des applications Windows possèdent des menus appelés contextuels qui sont déclenchés par l’utilisateur lorsqu’il clique le bouton droit de la souris, et qui sont souvent relatifs au contexte dans lequel ils sont ouverts. Les options qu’il présente dépendent des objets sélectionnés ou de l’élément positionné par la souris. Pour inclure un menu contextuel dans une application, on doit prévoir les fonctions suivantes : une fonction sur le message WM_RBUTTONDOWN et une fonction sur le message WM_ContextMENU. Ce dernier sera placé dans une liste d’attente une fois le bouton relâché. 30 Amine BOUFAIED Ajout de raccourci clavier : Les raccourcis clavier sont de deux types : touche rapide et combinaison de touches (utilisant généralement les touches Ctrl ou Alt). Ces raccourcis fonctionnent de manière analogue au menu. En, effet, ce sont des raccourcis définis dans une table dans l’onglet ressource au sein de l’espace de travail, chaque entrée dans la table se voit attribuer un identifiant d’objets et une combinaison de touches. 4.1. – Ajout d’une commande dans la barre d’un menu Sélectionner l’onglet « RessourceView », déplier l’option « Menu » et choisir le menu dans lequel on désire ajouter une commande. Pour créer une nouvelle commande, faire glisser la zone vide à l’endroit désiré, et taper le nom de la commande. Le « & » tapé devant une lettre du nom fera office de raccourci clavier. Faire glisser la zone vide et saisir le nom de la commande Cocher cette case rend le menu grisé et inactif Si la case n’est pas cochée, l’entrée est une commande de menu simple, dépourvue de sous-menu. 4.2. – Ajout d’une commande dans le menu “Démo” Cliquer sur la première commande du menu (encore vide) afin de la sélectionner puis remplir les options de la boite des propriétés (« Menu Item Properties »). Entrer l’ID pour identifier la commande du menu Affiche une coche en regard de la commande 31 Amine BOUFAIED Le texte tapé ici apparaît dans la barre d’état 4.3. – Prise en charge des messages de menu par ClassWizard Pour lancer ClassWizard cliquer sur le bouton droit de la souris et choisir ClassWizard. Sélectionner l’ID et le type du message, cliquer sur Add Function et choisir le nom de la méthode qui sera appelée à chaque sélection de la commande du menu (message COMMAND) ou à chaque mis à jour de la commande du menu (message UPDATE_COMMAND_UI). La sélection d’un type de message Active le bouton Add function Sélectionner l’ID du Message à traiter 32 Amine BOUFAIED Cliquer sur « Edit Code » génère le code du corps (vide) de la méthode OnElementDemo1() ///////////////////////////////////////////////////////////////////////// //// // CDemoView message handlers void CDemoView::OnElementDemo1() { // TODO: Add your command handler code here } ClassWizard incorpore également automatiquement les nouveaux gestionnaires de messages dans la table de correspondance des messages de la classe associée au menu (CDemoView). ///////////////////////////////////////////////////////////////////////// //// // CDemoView Ajout d’une ligne de code qui ouvre une « message box » quand on sélectionne la commande « Démo1 » du menu « Demo ». ///////////////////////////////////////////////////////////////////////// //// // CDemoView message handlers void CDemoView::OnElementDemo1() { // TODO: Add your command handler code here AfxMessageBox (“Démonstration n°1“) ; } 33 Amine BOUFAIED 4.4. – Ajout de boutons à une barre d’outils Sélectionner l’onglet « RessourceView », déplier l’option « Toolbar » et cliquer deux fois sur IDR_MAINFRAME. L’éditeur de ressources s’ouvre : 34 Amine BOUFAIED Cliquer deux fois sur le bouton ouvre la boite des propriétés Dessin du bouton Il est possible de sélectionner un ID associé à une action déjà existante ou de créer une n o uv e ll e a c ti on av e c ClassWizard (View dans le menu de la fenêtre principale) Dans l’exemple ci-dessus, on associe au nouveau bouton l’action de la commande « Démo1 » du menu « Demo » (ouverture d’une « message box »). 35 Amine BOUFAIED 4.5. – Ajout d’infos-bulles Il est possible d’ajouter aux boutons de la barre d’outils des zones de texte qui s’affichent lorsque le curseur de la souris est positionné quelques secondes sur l’un des boutons : ce sont les « infosbulles ». Pour cela, sélectionner l’onglet « RessourceView », déplier l’option « String Table », ajouter et cliquer deux fois sur l’ID du bouton. La fenêtre suivante apparaît : Pour ajouter une info-bulle il suffit de mettre « \n » au début de la chaîne de caractères On obtient : 36 Amine BOUFAIED 37 Amine BOUFAIED 2008 3e partie : Création d'un agenda grâce aux MFC Manipulation des données membres via UpdateData() La fonction UpdateData permet au programme de récupérer, et d'initialiser les données membres des contrôles d'une fenêtre (par exemple, le texte tapé dans un contrôle d'édition). En fournissant l'argument true à cette fonction le programme récupère l'intégralité des données entrées par l'utilisateur via l'interface graphique, et les stock dans les variables créées à cet effet. L'opération inverse est bien évidement possible en passant l'argument false à la fonction UpdateData. Dans ce cas, la valeur stockée dans la "Member Variable" est envoyée dans le contrôle lui étant rattaché. Ainsi, si nous stockions dans m_nom la chaîne de caractères "Test", et que nous appelions la fonctions UpdateData(false), le contrôle IDC_NOM aurait affiché "Test". MFC + Liste chainée = CList L'élaboration d'une liste chaînée, ainsi que le groupe de fonctions permettant de la manipuler étant une opération relativement délicate, Microsoft à inclus dans les MFC la Class CList qui simplifie grandement l'implémentation d'une liste chaînée dans une application. De plus un nombre impressionnant de méthodes permettant de manipuler cette liste chaînée est disponible. La déclaration d'une liste chaînée CList se fait de la manière suivante: CList <Type_objet,Type_transfert&> Nom_liste; Type_objet : le type d'élément qui composera la liste chaînée (par exemple CBox), Type_transfert : représente la façon dont les éléments seront injectés dans la liste chaînée, ici il s'agit d'un transfert par référence, il peut également se faire par pointeur ou par valeur (une copie donc), Nom_liste : étant le nom de l'objet désignant notre liste chaînée. Cet objet possède une batterie de méthodes permettant de manipuler la liste chaînée, en voici quelques une : Accés de tête ou de fin de liste GetHead Renvoie le premier élément de la liste GetTail Renvoie le dernier élément de la liste Opérations RemoveHead Supprime le premier élément de la liste RemoveTail Supprime le dernier élément de la liste AddHead Insert un élément en tête de liste AddTail Insert un élément en fin de liste RemoveAll Supprime tous les éléments de la liste Iteration GetHeadPosition Retourne la postion du premier élément de la liste GetTailPosition Retourne la position du dernier élément de la liste GetNext Renvoie l'élément suivant GetPrev Renvoie l'élément précédent 38 Amine BOUFAIED 2008 Récupération et modification GetAt Renvoie un élément à une position donnée SetAt Remplace l'élément d'une position par un autre Insertion InsertBefore Insert un élément juste avant celui se trouvant à la position précisée InsertAfter Insert un élément juste aprés celui se trouvant à la position précisée Recherche Find Renvoie la position d'un élément passé en paramêtre FindIndex Renvoie la position, sur une base d'index de 0, d'un élément passé en paramêtre Status GetCount Renvoie le nombre d'élement de la liste IsEmpty Test si la liste est vide (aucun élément) Pour plus d'information sur CList, consultez la MSDN de Visual C++. Développement de l’Agenda Notre programme Agenda va être créer afin d’illustrer ces nouvelles fonctionnalités Nous allons donc développer un agenda qui stockera (en mémoire uniquement) le nom, prénom, adresse et numéro de téléphone de contacts. Il sera possible d'ajouter, supprimer, modifier et de naviguer dans notre agenda. Ces contacts seront gérés en mémoire via une liste chaînée de type CList. Etape 1: Création de l'application MFC Agenda avec AppWizzard Créer une application basée dialogue nommée Agenda. Etape 2 : Insérer les contrôles de notre agendas comme nous l'avons fais avec "Hello World" 39 Amine BOUFAIED 2008 On continue, en attribuant à chaque contrôle facilement identifiable IDs choisis dans notre exemple : Contrôle d'édition pour le nom IDC_NOM Contrôle d'édition pour le prénom IDC_PRENOM Contrôle d'édition pour l’adresse IDC_ADRESSE Contrôle d'édition téléphone pour le numéro de IDC_PHONE Contrôle bouton suivant IDC_NEXT Contrôle bouton précédent IDC_PREV Contrôle bouton ajouter IDC_ADD Contrôle bouton modifier IDC_MODIFY Contrôle bouton supprimer IDC_DEL Etape 3 : Ajouter les variables membres des contrôles avec ClassWizzard (Ctrl+W) 40 Amine BOUFAIED 2008 Etape 4 : Elaboration et ajout de notre class contact dans notre projet Agenda Nos contacts seront en fait des objets d'une classe CContact ayant 4 variables membres publics de type CString : nom, prenom, adresse et tel. En voici la forme: Class CContact { public : CString nom, prenom, adresse, tel; }; Pour insérer cette classe à notre projet il suffit de cliquer sur Agenda classes dans l'onglet ClassView: 41 Amine BOUFAIED 2008 Nous choisirons ensuite l'option Generic class comme type de class, car notre classe n'est pas une classe MFC: Nous entrons le nom de notre classe, puis validons: Il nous suffit maintenant d'ajouter les 4 membres de cette classe. Pour cela, nous faisons un clic droit sur notre class CContact, et choisissont l'option Add Member Variable. 42 Amine BOUFAIED 2008 Nous recommençons cette opération pour chaque membre à ajouter dans notre classe contact. Etape 5 : Déclaration de notre liste chaînée Maintenant que nous avons notre classe CContact, nous pouvons déclarer notre liste chainée reposant sur ce type de class (CContact). Pour se faire nous allons ajouter dans la déclaration de la classe AgendaDlg les lignes de code suivantes : Nous incluons la bibliothèque « Contact.h » afin de pouvoir manipuler des objets de la classe CContact dans notre classe de dialogue, ainsi que la bibliothèque <Afxtempl.h> contenant la définition des types CList et POSITION. Nous déclarons ainsi un objet current représentant l’objet courant de la liste, un objet CList qui s'appelle list_contact qui stockera sous forme d'une liste chaînée les différents contacts que nous entrerons, ainsi qu'une variable current_pos de type POSITION représentant la position courante dans la liste chainée. Ajout d’un objet tampon nommé current Etant donné que les données récupérées avec UpdateData(true) ne sont pas retournées sous forme d'objet CContact, nous devons créer un objet current dans lequel nous stockerons les valeurs récupérées par le UpdateData. Etape 6 : Editer la méthode OnAdd Nous devons maintenant éditer le code de la fonction OnAdd pour que les informations entrées dans l'interface soient insérées sous forme d'une entité de la liste chaînée: Etape 7 : Editer la méthode OnModify 43 Amine BOUFAIED 2008 Ici nous allons utiliser la méthode SetAt pour modifier un contact à une position donnée: void CAgendaDlg :: OnModify() { UpdateData(true) ; current.nom = m_nom ; current.prenom = m_prenom ; current.adresse = m_adresse ; current.tel = m_tel ; List_contact.SetAt(current_pos, current) ; m_nom = « « ; m_prenom = « « ; m_adresse = « « ; m_tel = « « ; UpdateData(false) ; } Etape 8 : Editer la méthode OnDel Pour supprimer un élément de la liste chainée nous allons utiliser la méthode RemoveAt : Etape 9 : Editer la méthode OnNext Pour récupérer l'objet suivant dans la liste chaînée list_contact, il suffit d'appeler la méthode GetNext(position_courante). Cette méthode renvoie l'élément suivant celui se trouvant à la position position_courante. Il convient également de vérifier lors d'un appel à la fonction GetNext que nous ne "sortons pas" de la liste chaînée et accédons à des zones mémoires protégées. Le code de la méthode OnNext est donc celui ci: Etape 10 : Editer la méthode OnPrev Comme vous pouvez l'imaginer très peu de choses différencie cette méthode avec la précédente, nous appelons tous simplement la méthode GetPrev, au lieu de GetNext et nous vérifions si nous ne somme pas au début de la liste avec GetHeadPosition au lieu de GetTailPosition, le reste du code restant inchangé pour des raisons évidentes. 44 Amine BOUFAIED 2008 Etape 11 : Compiler le projet et l'exécuter Après avoir compilé le projet, nous pouvons voir que notre application fonctionne parfaitement bien, et gère toutes les options que nous voulions !!! La sérialisation : un puissant système de sauvegarde Bien que notre programme agenda fonctionne parfaitement bien, il à un handicape majeur : les données sont stockées en mémoire, résultat en quittant l'application, nous perdons l'intégralité des informations entrées. Heureusement Microsoft Visual C++ 6.0 propose une solution permettant de gérer l'intégralité des données manipuler par votre programme. Il s'agit de la sérialisation vue dans la partie précédente. Cette méthode permet de sauvegarder des objets et de les recharger en mémoire avec un minimum d'effort, toute la partie concernant la gestion du fichier de sauvegarde est géré par le processus de sérialisation. En fait tous ce qu'il nous reste à faire c'est d'implémenter cette méthode dans nos classes, et de procéder à une sauvegarde ou à un chargement des objets du programme en fonction des besoins de l'utilisateur. Nous nous servirons donc de la sérialisation pour stocker les informations manipulées par notre agenda, c'est à dire notre liste chaînée contenue dans l'objet list_contact. Une meilleure solution serait d’utiliser et de gérer des bases de données. 45 Amine BOUFAIED 2008 4ème partie Connexion aux sources de données Introduction La prise en charge des bases de données par les MFC fait appel à SQL pour formuler les requêtes et opérations exécutées sur les tables de base de données, par l'intermédiaire d'un ensemble de classes spécialisées. Les requêtes SQL sont transmises sous forme de chaînes. Si le nom d'une table comporte des espaces comme Product ID, la syntaxe SQL exige qu'il soit placé entre guillemets comme "Product ID". Ex1: SELECT "Product ID" FROM Products Or en C++ les guillemets délimitent les chaînes de caractères ce qui prête confusion si nous encadrons le nom des objets de base de données (lignes ou colonnes) par des guillemets. C'est pourquoi, lorsque nous référençons dans l'environnement Visual C++ une table de base de données ou un nom de colonne comprenant des espaces, nous devons remplacer les guillemets par les crochets. En l'occurrence, écrivons [Product ID]. Ex2: SELECT [Product ID] FROM Products Les MFC abordent les bases de données selon deux approches chacune d'elle utilisant son propre jeu de classes MFC. Il s'agit de DAO et ODBC. Mais il y en a de plus récentes comme OLE DB et ADO.NET. La première approche DAO (Data Acess Objects) propose une interface avec le moteur de base de données Jet, élément logiciel généralisé qui permet de stocker des données dans divers systèmes de gestion de base de données et de les extraire. Jet est le moteur utilisé par le SGBD Microsoft Access. Chaque fois que vous manipulez une base de données sous Access, c'est en réalité Jet qui fournit l'essentiel du travail. Jet est optimisé pour l'accès direct aux fichiers de base de données Access (.mdb), mais il vous permet également de vous connecter à toute base de données prenant en charge ODBC. Outre Microsoft Access, Jet vous permet d'accéder à des bases de données comme Oracle, dBase 5, Btrieve 6.0 et FoxPro 2.6... La seconde approche est spécifique à ODBC (Open DataBase Connectivity). Vous pouvez manier des bases de données de tout format pour lesquelles vous possédez le pilote ODBC approprié et même pour les fichiers .mdb d'Access. Le standard ODBC (Open DataBase Connectivity) Le standard ODBC de Microsoft définit aussi bien la syntaxe de SQL qu’une interface de programmation avec une base de données SQL. Une application écrite dans n’importe quel langage (C, C++, JAVA, etc…) peut accéder à n’importe quel système de gestion de base de données (SGBD) disposant d’un pilote ODBC. Visual C++ contient des pilotes ODBC pour les fichiers DBF, les bases de données mbd, les fichiers xsl,… D’autres fournisseurs de SGBD offrent leurs propres pilotes. Classes MFC pour ODBC ODBC fait appel aux cinq classes suivantes: CDatabase : Un objet de cette classe représente une connexion à votre base de données, que vous devez établir avant d'effectuer des opérations sur la base de données. Aucune classe d'espace de travail n'est utilisée avec une base de données ODBC. CRecordset : Un objet d'une classe dérivée de cette classe représente le résultat d'une opération SQL SELECT, Il s'agit du concept mentionné lors de la description de la classe CDaoRecordset CRecordView : Un objet d'une classe dérivée de cette classe permet d'afficher les informations en cours d'un jeu 46 Amine BOUFAIED 2008 de lignes associé. Il s'agit du concept mentionné lors de la description de la classe CDaoRecordView CFieldExchange : Cette classe permet l'échange de données entre la base de données et un jeu de lignes, comme pour les bases de données DAO. CDBException : Les objets de cette classe constituent des exceptions survenant dans les opérations de base de données ODBC. Les fonctions membres de CRecordset Fonctions Open AddNew Update Delete Edit IsBOF IsOEF MoveNext MoveFirst MoveLast MovePrev GetDefaultConnect GetDefaultSQL DoFieldExchange GetRecordCount GetODBCFieldCount GetODBCFieldInfo Description Ouvre le Recordset Prépare l’ajout d’un nouvel enregistrement à la table Complète une opération AddNew ou Edit en enregistrant les modifications Supprime l’enregistrement courant du Recordset Prépare la modification de l’enregistrement courant Teste si le curseur se trouve au début du Recordset Teste si le curseur se trouve à la fin du Recordset Se déplace vers l’enregistrement suivant Se déplace vers le premier enregistrement Se déplace vers le dernier enregistrement Se déplace vers l’enregistrement précédent Obtient la chaîne de connexion par défaut de la source de données sur laquelle s’appuie le RecordSet Obtient la chaîne SQL par défaut Échange des données entre les variables associées au recordset et l’état d’achèvement du comptage Obtient l’indice maximal rencontré par l’utilisateur au cours de ses déplacements dans le recordset Lit le nombre de champs du recordset Lit des informations sur les champs du recordset Premier exemple Commencer par créer la structure de la base de données sous par exemple MsAccess que vous avez vu en cours de Bases de Données. Créer les clés et les relations entre les tables. Ensuite configurer ODBC : 1. Sélectionner le pilote 2. Attribuer un nom à la source de données 3. Finalement, associer une base de données à la source 47 Amine BOUFAIED 2008 Créer l’application qui va utiliser la base de données Choisir comme projet MFC AppWizard Choisir SDI Dans la boite de dialogue sur les bases de données choisir Database view without file support Choisir la source de données Choisir les tables sur lesquelles vous voulez travailler Continuer les étapes restantes avec les options par défaut Gestion de la base de données : Dans la fenêtre de dialogue, créer votre interface qui doit contenir les champs de votre table. L’edit box de votre clé primaire doit être « read only ». Puis, associer des variables membres de la classe View à chacun des edit boxes. Ces variables membres sont de la forme m-pSet->…, où les trois points sont les variables membres de la classe Set. Ajout et suppression d’enregistrements : Les classes CRecordSet et CRecordView permettent de réaliser toutes les tâches à réaliser sur une base de données. Dans un premier, rajouter les menus de suppression et d’ajout correspondant dans le menu « enregistrement », ainsi que leurs boutons dans la barre d’outils. N’oubliez pas de connecter les boutons aux menus. 48 Amine BOUFAIED 2008 Ajout et suppression d’un enregistrement à une table : Ouvrir le fichier View.h file. Dans la section des attributs de la déclaration de la classe, rajouter les lignes suivantes : protected: BOOL m_bAdding; Double-cliquer sur le constructeur de la classe View et rajouter la ligne suivante à sa fin : m_bAdding = FALSE; Dans la fonction membre à la classe View associée à la commande d’ajout ajouter les lignes de code suivant : void CBaseView::OnRecordAdd() { // TODO: Add your command handler code here m_pSet->AddNew(); m_bAdding = TRUE; CEdit* pCtrl = (CEdit*)GetDlgItem(IDC_EMPLOYEE_ID); int result = pCtrl->SetReadOnly(FALSE); UpdateData(FALSE); } OnRecordAdd() débute par l’appel à la function member AddNew() de CEmployeeSet, la classe dérivée de CRecordSet. Ceci crée un enregistrement vide à remplir par l’utilisateur. Après que l’utilisateur crée un nouvel enregistrement, la base de données doit être mise à jour. Pour cela, m_bAdding est mis à TRUE pour finaliser l’opération d’ajout. Maintenant, comme l’utilisateur veut entrer un nouvel enregistrement, il devait être possible de changer le contenu du champ Employee ID, qui est pour le moment mis à read-only. Afin de changer l’état read-only du contrôle, le programme obtient en premier lieu un pointeur sur le contrôle avec GetDlgItem() et après appelle la fonction membre du contrôle SetReadOnly() afin de mettre l’attribut read-only à FALSE. Finalement, l’appel à UpdateData() affiche le nouvel enregistrement vide. Ensuite, choisir pour votre classe View “Add Virtual Function” et sélectionner OnMove. Cliquer sur “Add and Edit ». Enfin insérer le code suivant : BOOL C…View::OnMove(UINT nIDMoveCommand) { if (m_bAdding) { m_bAdding = FALSE; UpdateData(TRUE); if (m_pSet->CanUpdate()) m_pSet->Update(); m_pSet->Requery(); UpdateData(FALSE); CEdit* pCtrl = (CEdit*)GetDlgItem(IDC_EMPLOYEE_ID); pCtrl->SetReadOnly(TRUE); return TRUE; } else return CRecordView::OnMove(nIDMoveCommand); } 49 Amine BOUFAIED 2008 Maintenant que l’utilisateur a un enregistrement vide sur l’écran, il est simple de remplir les edit controls avec les données nécessaires. Afin d’ajouter le nouvel enregistrement à la base de données, l’utilisateur doit se déplacer vers un nouvel enregistrement, une action qui force un appel à la fonction membre OnMove() de la classe View. Quand OnMove() est appelée, la première chose que le programme fait est de vérifier la variable booléenne m_bAdding afin de voir si l’utilisateur est entrain d’ajouter un nouvel enregistrement ou non. Si m_bAdding est à FALSE, la clause du else est exécutée. Dans la clause du else, le programme appelle la version de la classe de base (CRecordView) de OnMove(), qui simplement déplace vers le prochain enregistrement. Si m_bAdding est à TRUE, le corps du if est exécuté. Alors, le programme initialise le drapeau m_bAdding et appelle ensuite UpdateData() afin de transférer les données des contrôles de la fenêtre view à la classe du recordset. Un appel à la méthode CanUpdate() du recordset determine s’il est possible ou pas de mettre à jour le data source, après quoi un appel à la fonction membre Update() du recordset rajoute le nouvel enregistrement au data source. Afin de reconstruire le recordset, le programme doit appeler la fonction membre Requery() du recordset, et après un appel à la fonction membre UpdateData() de la fenêtre view transfère les nouvelles données aux contrôles de la fenêtre. Finallement, le programme remet le champ Employee ID à read-only, avec un autre appel à GetDlgItem() et SetReadOnly(). Afin de pouvoir supprimer un élément de la table, rajouter le code suivant à la fonction membre de suppression : void C…View::OnRecordDelete() { m_pSet->Delete(); m_pSet->MoveNext(); if (m_pSet->IsEOF()) m_pSet->MoveLast(); if (m_pSet->IsBOF()) m_pSet->SetFieldNull(NULL); UpdateData(FALSE); } OnRecordDelete() appelle seulement la fonction Delete() du recordset. Quand un enregistrement est efface, un appel à MoveNext() du recordset prépare l’enregistrement suivant pour être affiché. Un problème peut apparaître quand l’enregistrement effacé était à la dernière position ou quand l’enregistrement efface est le seul enregistrement du recordset. Un appel à la fonction IsEOF() du recordset déterminera si oui ou non le recordset est à sa fin. Si l’appel à IsEOF() retourne TRUE, le recordset nécessite d’être repositionné sur le dernier enregistrement. La fonction MoveLast() du recordse s’occupe de cette tâche. Quand tous les enregistrements ont été supprimés du recordset, le pointeur d’enregistrement sera au début de l’ensemble. Le programme peut tester sur cette situation en appelant la fonction IsBOF() du recordset. Si cette fonction retourne TRUE, le programme écrit NULL dans les champs de l’enregistrement courant. Finalement, l’appel à UpdateData() affiche met à jour l’affichage dans la fenêtre view. 50 Amine BOUFAIED 2008