Graphical Templates - Big
Transcription
Graphical Templates - Big
Développement graphique de programme Template de Keygen V 1.0 Neitsa [FRET] Introduction ................................................................................................................................ 3 Finalité.................................................................................................................................... 3 Convention d’écriture............................................................................................................. 4 I Préparation ............................................................................................................................... 6 1.1 Outils et applications requis ............................................................................................. 7 1.1.1 Programmes utilisés .................................................................................................. 7 1.1.2 Outils & bibliothèques & add-ons............................................................................. 8 1.2 Organisation ..................................................................................................................... 9 1.2.1 Création du projet...................................................................................................... 9 1.2.2 Structure des répertoires.......................................................................................... 12 1.3 Convention de nom ........................................................................................................ 14 1.3.1 Noms des ressources ............................................................................................... 15 1.3.2 Etats des boutons..................................................................................................... 16 II Moteur minimal de notre template ....................................................................................... 17 2.1 Organisation des fichiers ................................................................................................ 18 2.2 Ressources minimales .................................................................................................... 19 2.3 Code minimal ................................................................................................................. 23 2.3.1 Le fichier main.cpp ................................................................................................. 23 2.3.2 Le fichier main.h ..................................................................................................... 28 III Région et compilation conditionnelle ................................................................................. 30 3.1 Région ............................................................................................................................ 31 3.2 Compilation conditionnelle ............................................................................................ 34 IV Implémentation des boutons ............................................................................................... 37 4.1 Boutons et évènements................................................................................................... 38 4.2 Implémentation des boutons........................................................................................... 39 4.2.1 Commutateurs pour les boutons.............................................................................. 39 4.2.2 Le code pour les boutons........................................................................................ 42 V Edit Boxes et Commandes ................................................................................................... 48 5.1 Edit Boxes ...................................................................................................................... 49 5.2 Commandes des boutons................................................................................................ 52 VI Musique Maestro ! .............................................................................................................. 54 6.1 Bibliothèque musicale..................................................................................................... 55 5.2 Implémentation de la musique ....................................................................................... 56 Notes d'optimisation & remerciements .................................................................................... 60 Introduction Ce tutorial vous propose un guide pour écrire des applications graphiques, plus exactement des templates de keygens. L’idée de ce tutorial vient du fait de ma quasi non connaissance dans l’implémentation d’applications graphiques et aussi du fait que beaucoup de demandes m’ont été faites à ce sujet. Cet essai a été pensé dans une logique séquentielle (pas à pas) pour aider les débutants de façon à leur éviter de tomber dans les pièges que j’ai pu rencontrer lors de précédents essais. Le tutorial est basé sur le C++ (procédural) sous Windows, mais si vous programmez en assembleur la conversion devrait être relativement simple. Un strict minimum de connaissance du langage C++ et de l’API Windows est demandé, le but de ce tutorial n’étant pas l’acquisition du langage ni de l’interface de programmation d’application (API) . L’IDE utilisé ici est celui de Visual studio .NET, néanmoins cela n’est pas une obligation quant à la compréhension du tutorial. Rien n’est parfait mais le code présenté dans ce tutorial a été vérifié plusieurs fois pour s’assurer qu’aucune erreur n’y avait été introduite, la même chose ayant été faites à propos du texte de cet essai. Si toutefois vous aviez des commentaires ou suggestions n’hésitez pas à m’en faire part. Finalité Le but de ce tutorial est de vous montrer en détail comment coder un template de programme graphique. Le programme affichera une fenêtre préalablement dessinée sous un éditeur graphique. Chaque bouton possédera plusieurs états, par exemple si un bouton est appuyé ou si la souris passe au dessus, la couleur de ce bouton sera différente. Le programme pourra jouer une musique de notre choix et cette dernière sera contrôlable (jouer, arrêter, pause, stop, avant, arrière). Enfin le programme offrira quelques effets graphiques (apparition progressive à l’écran, text scrolling, starfield, etc.) Convention d’écriture Vous pourrez voir d’un coup d’œil les différents concepts grâce au style d’écriture et à sa mise en forme. Concepts importants : Les concepts importants sont mis en gras à l’intérieur d’une phrase. Tips / aide : Les tips ou l’aide sont placés dans un chapitre séparé, le chapitre étant accolé à une icône aisément reconnaissable. Le tout est encadré en bleu foncé, par exemple : La taille de la boite de dialogue peut être mise à zéro en éditant manuellement le fichier de ressources « rsrc.rc », néanmoins cela n’est pas une obligation. Assurez vous tout de même que celle-ci ne soit pas plus grande que votre fichier graphique *.png. Attention : Dans le cas ou un concept est très important et peux prêter à un mauvais fonctionnement du programme s’il n’est pas respecté, l’explication adéquate est fournie accolée à l’icône d’un warning. Notez que le cadre est rouge. Attention : veillez bien à inclure votre fichier graphique *.png en tant que ressource RCDATA sans quoi cela peut poser des problèmes au programme pour retrouver le fichier dans les ressources. Code : Le code du programme est formaté avec la police « Lucida ». Les commentaires du code sont en vert et les mots clé du C++ en bleu, le tout étant encadré en vert foncé. En voici un exemple : case WM_CLOSE: case WM_DESTROY: //minimise la fenêtre ShowWindow (hWin, SW_MINIMIZE); //destruction de la boite de dialogue EndDialog (hWin,0); break; Exemple tiré de la MSDN : Ci-dessous un exemple présentant les différents paramètres d’une fonction, tirée de la MSDN. La police utilisée est « Verdana » et le fond est grisé, sans cadre. int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType ); I Préparation Ce chapitre introduit les différentes choses à savoir ou posséder avant de commencer à coder nos applications. Il est important de bien lire ce chapitre afin de savoir quels outils sont utilisés ici et quelles sont les conventions utilisées. Ces dernières sont importantes pour la compréhension de la suite du tutorial. Notez toutefois que vous n’êtes absolument pas dans l’obligation d’utiliser ou de posséder les différents outils dont il est fait état ici ! La programmation des templates graphiques requière au minimum un compilateur C++ pour Windows ainsi que le SDK., le reste vous est fourni dans l’archive. 1.1 Outils et applications requis Comme expliqué ci avant la programmation de nos templates graphiques se fera en C++. Vous pouvez utiliser n’importe quel compilateur C++ pour Windows. De plus j’utiliserais ici l’IDE de Visual Studio .NET, mais n’importe quel IDE pourra faire l’affaire (si vous utilisez VC++ 6.0 cela ne devrait poser aucun problème). Nous utiliserons le format graphique PNG (Portable Network graphic), ce choix ayant été fait car le PNG est libre de droit et assure la transparence sur les couleurs. Nous aurons besoin d’un générateur de région pour la transparence sur certaines parties du graphique. Finalement un module permettant de jouer des fichiers XM sera utiliser. Notez que nous verrons aussi comment jouer des MP3 ou d’autres formats, mais ces formats sont lourds et finalement peu recommandés. La plupart des outils présentés ici sont gratuits et/ou libres de droits sauf mention contraire. 1.1.1 Programmes utilisés C++, Compilateur & IDE - Visual Studio C++ (payant) - Alternatives gratuites: voir http://c.developpez.com/compilateurs/ - A notez que le compilateur et le SDK peuvent être obtenu gratuitement ! [Lien] Graphisme - The Gimp : http://www.gimp.org/ - IrfanView (convertisseur) : http://www.irfanview.com/ 1.1.2 Outils & bibliothèques & add-ons Générateur de région L’excellent outil de Richard de Oude, RgnCreator : http://www.codeproject.com/gdi/rgncreator.asp RgnCreator : This program is provided courtesy of Data Dynamics. PNG Lib Bibliothèque pour le format PNG (originellement en asm) par Thomas Bleeker [MadWizard] http://www.madwizard.org/view.php?page=downloads PNGLib : © 2003 by Thomas Bleeker [MadWizard]. Bibliothèque pour fichiers musicaux : miniFMOD Cette bibliothèque nous permettra de jouer des fichiers musicaux notamment ceux au format XM. La version utilisée est une version modifiée de MiniFmod (d’origine, cette bibliothèque ne supporte pas la mise en pause d’un morceau de musique…). MiniFmod [original] : http://www.fmod.org/files/minifmod170.zip MiniFmod : Firelight Technologies Pty, Ltd. All rights reserved. Packer UPX peut être utilisé pour réduire la taille de l’exécutable, non comme une protection en soi. http://upx.sourceforge.net/ UPX : Copyright (C) Markus F.X.J. Oberhumer & László Molnár Musique Différent morceaux de musique au format XM (eXtended Module). Le format XM à l’avantage d’être très petit puisque la musique est recrée par un synthétiseur. http://www.modarchive.com/ 1.2 Organisation L’organisation d’un tel programme, si en apparence ne revêt pas trop de difficulté, est néanmoins importante tant le développement peut nécessiter de multiples fichiers qu’il est important de classer correctement. La structure proposée ici n’est qu’un point de départ, aussi n’oubliez pas que vous êtes seul juge de la manière la plus appropriée pour classer avec efficacité vos fichiers. 1.2.1 Création du projet Point de programme sans code source, nous allons donc créer notre projet. Si vous ne possédez pas VS (Visual Studio) vous pouvez néanmoins lire ce chapitre qui vous indiquera les fichiers à créer. Démarrer VS, choisissez de créer une nouvelle solution, soit avec la page de démarrage, soit avec le menu File > Create a new solution. Choisissez de créer un projet Win32 C++, et donnez lui un nom adéquat. Vous pouvez choisir de créer un répertoire pour la solution au cas où vous feriez plusieurs template (avec un code différent, pas différente graphiquement) en cochant la case « Create directory for solution », mais cela n’est pas réellement nécessaire. Une fois votre solution nommée, cliquez sur OK. Dans la fenêtre suivante, vérifiez bien qu’il s’agit d’un programme pour Windows (Windows application) et surtout n’oubliez pas de cliquer sur la boite « empty project ». Il est important de choisir « empty project » car nous allons coder un template à base de DialogBox, or VS ne propose pas nativement de solution par défaut sur cette base. Cliquez ensuite sur « Finish ». Une fois ceci terminé l’écran vous montre l’IDE pour coder directement. Nous allons d’abord ajouter les fichiers de base nécessaires pour notre code. Dans la fenêtre de l’explorer de solution (en haut à droite) faites un click droit sur le dossier « source files », vous devriez voir apparaître ces fenêtres : Choisissez Add > Add New Item. Dans la nouvelle fenêtre qui s’ouvre : Choisissez C++ file (.cpp) et donnez lui un nom (vous n’avez pas besoin d’ajouter l’extension). Par convention, nous l’appellerons « main ». Cliquez ensuite sur « Open ». Le fichier (vide) doit alors s’ouvrir dans l’IDE. Faites la même chose pour ajouter un fichier header (.h) au projet. Clique droit sur le répertoire « header files » dans l’explorateur de solution. Nommez votre fichier header « main ». Le reste de la marche à suivre est le même que pour le fichier CPP. Recommencez le même processus pour ajouter cette fois ci un fichier « resource » (.rc) au projet. Vous devrez faire un click droit sur le dossier « ressource files » de l’explorateur de solution pour ajouter ce fichier. Nommez celui « rsrc ». Vous devriez finalement avoir ceci dans l’explorateur de solution : Notez qu’à l’ajout du fichier de ressources, VS ajoute automatiquement un fichier « resource.h », vous ne devriez pas, en temps normal, éditer ce fichier manuellement. Le fichier « resource.h » contient la définition et les caractéristiques des objets du fichier .rc. Notez qu’il faudra bien sur ne pas oublier d’inclure le fichier « resource.h » à notre projet, sans quoi aucune ressource n’est compilée dans notre exécutable. 1.2.2 Structure des répertoires A présent que nos fichiers de base sont créés nous allons générer mettre en place une structure de répertoire appropriée afin d’y placer tout les fichiers supplémentaires. Ouvrez le répertoire contenant votre solution. Pour l’instant seul les fichiers de projet, de solution et les fichiers que nous avons créés auparavant sont présents. J’appellerais désormais [RACINE] le dossier contenant votre solution. Vous créerez les dossiers suivant sous la racine de votre projet : [RACINE]\Effets\ Répertoire contenant le code des effets graphiques que nous pourrons ajouter au programme. Chaque effet prendra place dans un répertoire distinct. [RACINE]\Libs\ Répertoire contenant les bibliothèques supplémentaires pour le programme. Nous utiliserons principalement PngLib et MiniFmod. Chaque bibliothèque prendra place dans un répertoire distinct. Vous devriez donc avoir [RACINE]\libs\pnglib et [RACINE]\libs\minifmod. [RACINE]\Musique\ Répertoire qui contiendra les musiques que nous utiliserons. [RACINE]\Skins\ Répertoire contenant les « skins » (autres templates graphiques). Les skins ne sont pas utilisés, ce répertoire sert donc d’entrepôt. Chaque « skin » ira dans un répertoire séparé. Par exemple [RACINE]\Skins\FlowerPower\ pour votre belle template psychédélique ou [RACINE]\Skins\DarkAges\ pour une template sombre à souhait ;-) [RACINE]\Template Répertoire contenant la template graphique actuelle utilisée par le programme en cours. Ce répertoire contient toutes les parties de la template nécessaire à son bon fonctionnement. Le dossier template contient donc un skin sortie du même dossier. [RACINE]\Template\Main\ La fenêtre graphique principale du programme est située ici. On y mettra aussi l’icône du programme. [RACINE]\Template\Boutons\général\ Contient les fichiers .png les boutons généraux, tel que maximiser, minimiser, fermer mais aussi « generate » ou « about » par exemple. [RACINE]\Template\Boutons\Musique\ Contient les boutons nécessaires à la musique, comme « play », « stop », etc. [RACINE]\Template\Misc\ Tout autre graphisme n’entrant pas dans les catégories ci-dessus. A présent votre dossier [RACINE] devrait ressembler à ceci : Si vous êtes sous un autre environnement que VS .NET il est tout à fait possible que certains fichiers soient différents ou non présents. 1.3 Convention de nom Maintenant que tout est en place, nous allons voir la convention (toute personnelle) pour nommer les différents « objets » compris dans notre template. En premier nous verrons les conventions pour nommer les ressources et deuxièmement les qualificatifs qui s’y appliquent le cas échéant. 1.3.1 Noms des ressources IDD_ xxx Notre boite de dialogue principale. IDR_PNG_xxx Toutes les ressources graphiques PNG commencent ainsi (IDR pour Ressource). Par exemple, IDR_PNG_BG (ressource PNG principale qui donne son apparence à la fenêtre). IDI_xxx L’icône du programme se nomme ainsi. IDC_Bxxx Les boutons généraux commencent par cet identifiant, par exemple IDC_BMAXIMIZE. Voici une liste des boutons généraux (5 lettres pour la fonction): o IDC_BCLOSE fermer o IDC_BMINIM minimiser o IDC_BMAXIM maximiser o IDC_BGENER générer o IDC_BABOUT about IDC_Mxxx Les boutons pour la musique ont cet identifiant, par exemple IDC_MPLAY. Voici une liste des boutons (en 4 lettres pour la fonction). o IDC_MPLAY jouer o IDC_MSTOP arrêter o IDC_MPAUS pause o IDC_MREWI en arrière o IDC_MFORW en avant 1.3.2 Etats des boutons Chaque bouton aura 3 états possibles, état qui changera son apparence graphique. A coté du qualificatif (4 lettres) vous trouverez l’état du bouton utilisé. Ces qualificatifs s’ajoutent aux noms des boutons. _NORM Normal _PRESS Appuyer _OVER Au dessus Ainsi le bouton « fermer » dans son état normal s’appellera : IDC_BCLOSE_NORM A présent tout est prêt pour coder une template minimale de notre application. II Moteur minimal de notre template A présent qu’une bonne partie des définitions est mise en place nous allons coder le moteur minimal du programme de façon à afficher un « background » graphique au format PNG. Nous n’utiliserons aucun bouton, ni aucune musique pour l’instant. Nous continuerons d’ajouter du code sur cette base par la suite. Avant cela nous verrons comment se présentent les fichiers du projet et ce qu’ils contiennent mais aussi comment ajouter des ressources à notre programme. 2.1 Organisation des fichiers Nous allons voir comment s’organisent les fichiers de notre projet de template graphique. Le fichier main.cpp contient uniquement le code de base de l’application. Le fichier main.h contient les directives pour inclure les fichiers d’en-têtes (.h) nécessaires à la compilation, ainsi que les bibliothèques (.lib). On y placera également les variables globales et les déclarations de fonctions (prototypes). Le fichier rsrc.rc contient, comme nous l’avons vu, les différentes définitions des ressources. Pour afficher correctement notre PNG nous devrons inclure dans notre programme la bibliothèque PNG (png.lib) ainsi que le fichier d’en-tête « pnglib.h ». La DLL « png.dll » devra se trouver absolument dans le même répertoire que le programme lorsque celui-ci sera lancé. Pour ajouter un fichier, ici le fichier header existant dans [RACINE]\libs\Pnglib\ : Faites un click droit sur le dossier voulu dans l’explorateur de solution et choisissez les options comme montrées ci-dessous. Choisissez alors le fichier « pnglib.h » dans le dossier adéquat. 2.2 Ressources minimales Nous allons commencer par créer notre boite de dialogue, qui ne sera jamais affichée en tant que telle mais qui est toutefois nécessaire. Ce sera une « fausse » boite de dialogue où seul notre graphique en PNG sera visible. Double cliquez sur le fichier « rsrc.rc » dans l’explorateur de solution ou cliquez simplement sur l’explorateur de ressources (« resource view »). Faites un click droit sur le dossier « rsrc.rc » (cf. photo ci-dessus) et choisissez « add resource ». Dans la nouvelle fenêtre qui apparaît, choisissez « Dialog » puis cliquez sur « New ». Une nouvelle page dans l’IDE apparaît alors, vous permettant d’éditer le fichier de dialogue. Dans un premier temps, supprimez tous les boutons qui s’y trouvent (sélectionnez les et faites « suppr. »). Dans la fenêtre des propriétés de la boite de dialogue, repérez les styles suivant et mettez les à la bonne valeur : o BORDER : NONE o SYSMENU : FALSE Vous devriez obtenir une fenêtre totalement vide et gris uniforme. Si toutefois vous n’aviez pas d’IDE ou que les manipulations était différentes, vous devriez obtenir ceci dans le fichier « rsrc.rc » (éditez le avec un éditeur de texte). // // Dialog // IDD_DIALOG1 DIALOGEX 0, 0, 186, 95 STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN END Si votre PNG principal devait être plus petit que notre boite de dialogue par défaut, assurez vous de retoucher la taille de celle-ci de façon à ce qu’elle soit plus petite que votre graphique. Typiquement vous pouvez retoucher cette ligne si vous ne disposez pas d’IDE : IDD_DIALOG1 DIALOGEX 0, 0, 0, 0 Attention : Si vous disposez d’un IDE la retouche manuelle du fichier « rsrc.rc » peut causer certains problèmes. A ne faire donc que si vous savez ce que vous faites. Vous aurez bien sur notez que notre boit de dialogue s’appelle IDD_DIALOG1, nous ne changerons pas son nom. A présent nous allons insérer notre template graphique pour la fenêtre principale. Faites un click droit sur le dossier « rsrc.rc » dans l’explorateur de ressources. Choisissez ensuite « Add Resources » et dans la nouvelle boite de dialogue qui apparaît, choisissez « Import » : Dans la fenêtre qui s’ouvre allez dans le dossier [RACINE]\Template\Main\ pour y rechercher le background principal de l’application. Etant donné que le fichier est au format PNG, l’explorateur ne le montrera pas. Choisissez bien de montrer tous les types de fichiers : Ensuite tapez dans le champs disponible : RCDATA Faites alors OK. Le fichier est alors intégré en tant que fichier binaire (RCDATA : Raw Content) à l’application dans la section des ressources. Renommé l’identifiant en IDR_PNG_BG. Pour cela, dans la fenêtre « ressource view », cliquez sur le fichier et allez dans sa fenêtre propriétés où vous pourrez le renommer : Nous pouvons à présent importer l’icône dans les ressources. Faites un click droit sur le dossier rsrc.rc et choisissez cette fois ci d’importer une icône en cliquant à droite sur le type approprié puis en choisissant à nouveau « import ». L’icône doit se trouver dans le dossier [RACINE]\Template\Main\ et n’oubliez pas, dans l’explorateur de fichier, de choisir le type *.ico pour voir le fichier. Nous laisserons à l’icône l’identifiant IDI_ICON1. Le fichier « rsrc.rc » devrait contenir ceci, si vous l’éditer manuellement : /////////////////////////////////////////////////////////////////////////// // // // RCDATA // IDR_PNG_BG RCDATA "Template\\Main\\bg.png" /////////////////////////////////////////////////////////////////////////// // // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ICON1 ICON "Template\\Main\\main.ico" A présent nos ressources contiennent le minimum pour notre application. Nous allons pouvoir coder notre programme. 2.3 Code minimal Nous allons maintenant étudier le code minimal requis pour faire fonctionner notre application, aussi bien le code « en lui-même » mais aussi les déclarations des fichiers d’entêtes. 2.3.1 Le fichier main.cpp Voyons à présent le fichier « main.cpp ». Rappelez vous que ce fichier ne contient que du code et que les données globales (visibles pour toute l’application) se situent dans le fichier « main.h ». Il suffira donc d’inclure ce dernier avec la directive #include au tout début du code pour que les fichiers soient visibles depuis notre fichier .cpp. Dans un premier temps nous allons seulement voir le squelette de base sur lequel nous broderons peu à peu par la suite. Le code du programme commence bien sur par la fonction principale, la fonction WinMain, point d’entrée de l’application : int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { hInst = hInstance; InitCommonControls (); //obtient les dimensions de l'écran ScreenWidth = GetSystemMetrics (SM_CXSCREEN); ScreenHeigth = GetSystemMetrics (SM_CYSCREEN); //appel la boite de dialogue et sa procédure DialogBoxParam(hInstance,(LPCTSTR)IDD_DIALOG1,NULL,(DLGPROC)MainProc, NULL); ExitProcess (NULL); return 0; } On obtient d’abord les dimensions de l’écran en largeur (ScreenWidth) et hauteur (ScreenHeigth). Ces dimensions serviront plus tard à placer le programme a centre de l’écran. L’application est réellement lancée par DialogBoxParam qui permet d’appeler la procédure de dialogue MainProc. Voici un code schématique des différent cas utilisé dans notre template de base. Les commentaires vous indiqueront ce que font ces différents cas à l’intérieur de la procédure. LRESULT CALLBACK MainProc(HWND hWin,UINT message,WPARAM wParam,LPARAM lParam) { //variable locales switch(message) { case WM_INITDIALOG: { //initialisation du programme break; } case WM_PAINT: { //peinture de la fenêtre break; } case WM_COMMAND: Select=LOWORD(wParam); switch(Select) { // rien pour l'instant (code pour l’appui des boutons) break; } break; case WM_LBUTTONDOWN: { //code pour déplacement de la fenêtre avec la souris break; } case WM_CLOSE: case WM_DESTROY: // Destruction ou fermeture de la fenêtre break; default: return FALSE; } return TRUE; } Commençons par voir, toujours dans la procédure MainProc, le cas WM_INITDIALOG, premier cas à être appelé puisqu’il s’agit de l’initialisation. case WM_INITDIALOG: { //charge l'icone et l'affiche HICON hIcon = LoadIcon (hInst, (LPCSTR)IDI_ICON1); SendMessage (hWin, WM_SETICON, ICON_BIG, (LPARAM)hIcon); // affiche le titre de l'application dans la taskbar SendMessage (hWin, WM_SETTEXT, NULL, (LPARAM) lpszDialogCaption ); Ici le code procède au chargement de l’icône et à l’affichage du titre de l’application dans la barre des tâches (TaskBar). L’icône est visible dans la TaskBar mais aussi en faisant ALT+TAB grâce au paramètre ICON_BIG. Vient ensuite la fonction qui charge le PNG et le transforme en interne en bitmap : //charge le PNG hBmpMain = PNG_GetResource (IDR_PNG_BG, hInst, hWin, &StructPngDim); La fonction est déclarée dans « pnglib.h » comme ceci : HBITMAP CALLBACK PNG_GetResource ( int ResourceId, HINSTANCE hInst, HWND hWin, LPPNGDIMENSION pStruct); On lui passe donc en premier lieu l’ID de la ressource à extraire, ici le graphique au format PNG de notre fenêtre principale. Ensuite le hInstance de notre application (obtenu dans la fonction WinMain), puis le hWin qui est le handle de notre fenêtre principale. LPPNGDIMENSION est une structure qui renferme seulement deux paramètres, la largeur et la hauteur du PNG, vous devrez donc passer un pointeur vers une structure de ce type. La définition de la structure se trouve dans le même fichier : typedef struct { int pngHeight; int pngWidht; }PNGDimension, * LPPNGDIMENSION; En sortie de fonction nous obtenons un handle de bitmap (HBITMAP) et la structure (StructPngDim de type PNGDIMENSION dans notre cas) passée en argument est remplie avec les dimension du PNG. La suite du code servira uniquement à centrer la fenêtre, grâce aux informations obtenues sur la taille de l’écran et les dimensions du PNG. //centre la dialog box à l'écran StartPosX = ((ScreenWidth - StructPngDim.pngWidht) >> 1); StartPosY = ((ScreenHeigth - StructPngDim.pngHeight) >> 1); SetWindowPos (hWin, HWND_TOPMOST, StartPosX, StartPosY, StructPngDim.pngWidht, StructPngDim.pngHeight, SWP_HIDEWINDOW ); //petit effet d'apparition ShowWindow (hWin, SW_MINIMIZE); ShowWindow (hWin, SW_SHOWNORMAL); break; Notez l’opérateur ‘>>’ avec l’opérande 1 (un) qui correspond dans notre cas à une division par 2. Le reste du code est assez parlant de lui-même. Le petit effet d’apparition correspond au fait que le programme semble venir de la barre des tâches quand il démarre…Rien de transcendant mais toujours agréable. Si vous ne comprenez pas un traître mot de ce qui vient d’être dit ici, fermez ce tutorial, reprenez quelque cours sur l’API win32 et rouvrez à nouveau cet essai… En cas de doute sur une API n’hésitez pas à compulser la MSDN si le code vous parait familier mais vous ne comprenez pas la fonction d’une API ou un de ses paramètres. En effet il eut été difficile d’expliquer par le détail chacune d’elle… Vient ensuite le cas WM_PAINT appelé juste après le cas d’initialisation : case WM_PAINT: { // yo soy picasso :p // préparation au fonction GDI hDC = BeginPaint (hWin, &ps); //création du DC (device context) hBitmapDc = CreateCompatibleDC (hDC); //sélectionne le graphique principal dans le DC hOldBitmap = SelectObject (hBitmapDc , hBmpMain); //transfère des couleurs du hBitmap au hDC BitBlt (hDC, 0, 0, StructPngDim.pngWidht, StructPngDim.pngHeight, hBitmapDc, 0, 0, SRCCOPY); // selectionne le nouvel objet SelectObject (hBitmapDc, hOldBitmap); //effacement du DC DeleteDC (hBitmapDc); // fin des fonctions de paint EndPaint (hWin, &ps); break; } Encore une fois le code est relativement parlant, même s’il peut paraître confus à ceux qui ne pratiquent pas les fonctions GDI (graphisme primaire sous Windows). Le code prépare tout d’abord l’application à l’utilisation de fonctions GDI par le biais de BeginPaint. Ensuite vient la création d’un DC (device context) qui servira à l’affichage de notre graphique principal pour la fenêtre. SelectObject sélectionne le handle retourné par la fonction de la DLL (Png_GetResource) dans le DC. BitBlt copie directement le graphique de la fenêtre dans le DC principal. Le dernier SelectObject sélectionne le DC principal, puis DeleteDC ferme le DC relative au graphique mémoire (le graphique de notre application est déjà dans le DC principal puisqu’il y a été copié par BitBlt). Enfin EndPaint termine les opérations. Nous n’étudierons pas le cas WM_COMMAND qui traite les interactions avec les contrôle de l’utilisateur (bouton ou boite d’édition, etc.) étant donné que nous n’en mettrons pas pour l’instant. Ceci sera vu dans une prochaine partie. Le cas WM_LBUTTONDOWN est traité lorsque l’utilisateur appui sur le bouton gauche de la souris et que la fenêtre principale à le focus (sur la fenêtre principale si vous préférez). case WM_LBUTTONDOWN: { ReleaseCapture (); SendMessage (hWin, WM_NCLBUTTONDOWN, HTCAPTION, NULL); break; } Sans ce code nous ne pourrions pas déplacer notre fenêtre car celle-ci ne dispose pas de barre de titre. Comment cela se fait-il? Un petit passage de la MSDN nous éclaire sur ce point : The WM_LBUTTONDOWN message is posted when the user presses the left mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse. En outre il est important de comprendre la fonction du message WM_NCLBUTTONDOWN : The WM_NCLBUTTONDOWN message is posted when the user presses the left mouse button while the cursor is within the nonclient area of a window. This message is posted to the window that contains the cursor. If a window has captured the mouse, this message is not posted. Ce message permet simplement de simuler l’appui comme si nous faisions un click gauche de souris sur la barre de titre. Viennent ensuite les deux cas de fermeture de l’application : case WM_CLOSE: case WM_DESTROY: //minimise la fenêtre ShowWindow (hWin, SW_MINIMIZE); // destruction de la boite de dialogue EndDialog (hWin,0); break; On minimise la fenêtre (simple effet de fermeture) puis on appelle Endialog qui finira le traitement dans la procédure de dialogue (MainProc) et mettra fin à la fonction DialogBoxParam et appellera ainsi la fonction ExitProcess. 2.3.2 Le fichier main.h Le fichier.h, décrit plus haut, est relativement simple. Schématiquement, il se présente ainsi : Indication des bibliothèques supplémentaires #pragma comment (lib, "comctl32.lib") // requis pour initcommoncontrols #pragma comment (lib, "libs/pnglib/png.lib") // requis pour le format Png Cette notation permet d’éviter d’utiliser la ligne de commande du préprocesseur ainsi que de l’éditeur de lien. Il suffit simplement de spécifier les mots clés en bleu et le nom ainsi que l’emplacement de la bibliothèque entre guillemets. Inclusion des fichiers d’entête généraux et spécifiques On trouve ici, dans l’ordre, les fichiers nécessaires pour le développement Windows, les fichiers d’inclusion du C++ standard et finalement les fichiers propres à notre programme. Les variables globales et les déclarations de fonctions Finalement on déclare les variables visibles à travers tout le programme (à la différence des variables locales) ainsi que les déclarations de fonctions (le prototypage de fonctions). Voilà qui clos notre première partie générale, relativement longue certes mais nous n’y reviendrons pas. Assurez vous de bien comprendre chaque point abordé ici, car il ne sera plus fait référence aux points qui y ont été vus. Le code source de cette partie se trouve dans le dossier "Template1" et est bien sur présent dans l’archive. N.B : le code compile parfaitement mais vous obtiendrez un Warning de compilation dû au fait que le cas WM_COMMAND ne comporte pas de « switch » pour l’instant. Ceci est sans importance, le programme fonctionne très bien comme cela. III Région et compilation conditionnelle Nous allons voir dans ce chapitre la création de région qui permettra de rendre la bordure de notre graphique invisible. Nous verrons aussi les bases de la compilation conditionnelle qui nous permettra de ne compiler que les parties de code et les fichiers que nous désirons voir effectivement mis dans l’exécutable. 3.1 Région Comme vous l’aurez sûrement remarqué la template1 affiche une bordure rose peut agréable à l’œil. En fait c’est la couleur choisie par ++Meat (l’excellent graphiste de FRET qui a fait cette template) pour la transparence. Pour enlever cette bordure il faut utiliser une API spécifique : HRGN ExtCreateRegion( CONST XFORM *lpXform, // transformation data DWORD nCount, // size of region data CONST RGNDATA *lpRgnData // region data buffer ); Nous ne pouvons pas utiliser l’API CreateRegion car notre région n’a pas une forme standard définie (telle qu’un carré, un cercle ou une ellipse par exemple) nous utilisons donc la forme étendue de cette API. Voici les paramètres de l’API que nous utiliserons : lpXform [in] Pointer to an XFORM structure that defines the transformation to be performed on the region. If this pointer is NULL, the identity transformation is used. nCount [in] Specifies the number of bytes pointed to by lpRgnData. lpRgnData [in] Pointer to a RGNDATA structure that contains the region data in logical units. Le paramètre lpXform ne nous est d’aucune utilité, il sert au cas ou nous voudrions agrandir, mettre en miroir, couper, ou faire une rotation sur notre région. Le paramètre lpRgnData qui est un pointeur vers une structure de type RGNDATA est par contre très important. Cette structure renferme tout ce qu’il nous faut pour produire notre région. Je n’entrerais pas plus avant dans les détails de création d’une région de forme complexe par le biais de la programmation (cela fera peut être l’objet d’un tutorial séparé) car cela demande une bonne connaissance des en-têtes de fichiers images, de certaines structures et de diverses manipulations mathématiques. Pour faire plus simple nous allons utiliser un outil qui fera ce travail pour nous. Celui-ci est situé dans le dossier « tools » de l’archive, il s’agit de RegionGenerator (« RGNerator.exe ») par Richard de Oude. Démarrez ce programme, et ouvrez l’image bg.png située dans le dossier [RACINE]\Template\Main en cliquant sur « Browse ». Choisissez ensuite « Pick », vous voyez alors l’image apparaître dans une fenêtre. Pour choisir la couleur qui sera désormais transparente faites un click droit sur cette couleur (ici le rose, tout en haut de la template). Faites OK, puis sur la fenêtre principale, cliquez sur le bouton « Create » à gauche. Vous pouvez alors sauver le fichier de région (*.rgn), dans le même dossier «Main », que nous appellerons dorénavant « template.rgn ». Insérez le fichier de région généré à l’instant (template.rgn) dans les ressources de notre programme et ce dans le même dossier que le PNG principal (donc en tant que Raw content c'est à dire RCDATA) et nommez le : IDR_PNG_REG A présent voyons le code nécessaire à la création de notre région (placé dans WM_INITDIALOG): //transparence HRSRC hRsrc = FindResource (hInst, (LPCSTR)IDR_PNG_REG, RT_RCDATA); HGLOBAL hGlob= LoadResource (hInst, hRsrc); DWORD RegionSize = SizeofResource (hInst, hRsrc); LPVOID lpRegion = LockResource (hGlob); HRGN hRegion = ExtCreateRegion (NULL, RegionSize, (RGNDATA *)lpRegion); SetWindowRgn (hWin, hRegion, TRUE); Les APIs FindResource et LoadResource permettent d’avoir un pointeur interne sur notre ressource. SizeOfResource nous retourne sa taille (nécessaire pour le paramètre nCount de ExtCreateRegion) et LockResource nous retourne un pointeur sur la ressource avec lequel nous pouvons travailler (contrairement au pointeur interne). ExtCreateRegion créé la région à partir de notre fichier ressource (template.rgn) et enfin SetWindowRgn rend la région effective sur notre graphique. A présent la bordure rose n’existe plus et ce qui la remplace est tout simplement transparent. N’oubliez pas, lorsque vous créez vous-même vos graphiques de définir une couleur pour la transparence qui n’est utilisée nulle part ailleurs sur votre dessin (à moins que vous désiriez la rendre explicitement transparente) ! Sinon cette couleur sera effectivement transparente… Notez que la couleur rose pure (de part son codage hexadécimal aisément repérable 0xFF00FF) est souvent utilisée pour ce faire. [En tout cas ça n'est pas un effet de mode :p] Vous pouvez à présent compiler cette deuxième template pour voir de quoi il retourne exactement. Comme stipulé auparavant la bordure rose n’existe plus. Nous allons à présent voir un aspect important des templates, la compilation conditionnelle. 3.2 Compilation conditionnelle La compilation conditionnelle est un aspect important des templates. Imaginons que votre graphique principal n’ait pas besoin de région (tout simplement parce qu’il serait carré par exemple) le code nécessaire à la mise en place d’une région, que nous venons de voir précédemment, serait inutile. Même chose si par exemple votre template ne dispose pas de musique, quel serait alors l’intérêt d’avoir du code pour gérer la musique ou d’inclure les fichiers de musique… C’est là qu’entre en jeux la compilation conditionnelle. Nous allons donc coder une template comprenant tout le code nécessaire (même s’il n’est pas utilisé) et nous utiliserons ensuite des « commutateurs » permettant au compilateur de savoir ce qu’il doit ou non compiler. La compilation sous condition s’effectue au moyen des mots clés #ifdef ou #ifndef et #endif. Par exemple : #ifdef blabla //compile cette partie si « blabla » est défini #endif Ou encore : #ifndef blabla //compile cette partie seulement si « blabla » n’est PAS défini #endif Définissons pour l’exemple un commutateur (« switch » en anglais) pour l’utilisation des régions. Nous nommerons celui-ci USEREGION. #ifdef USEREGION // Code pour la transparence en utilisant une région #endif Le code situé entre les mots clés ci-dessus n’est compilé que si USEREGION est défini. Comment définir ce mot clé ? Nous allons le définir dans les symboles de ressources. Dans l’explorateur de ressources, faites un click droit sur le dossier « rsrc.rc » et cliquez sur « Resource Symbols ». Dans la nouvelle fenêtre qui s’ouvre choisissez « NEW » mettez comme nom USEREGION et comme ID 1000 (l’ID importe peu, choisissez en un qui n’est pas déjà utilisé). Vous pouvez alors voir votre symbole pris en compte dans les ressources. Maintenant nous allons nous intéressez à la compilation conditionnelles des fichiers. Pour notre exemple, si nous n’utilisons pas de régions, le fichier « template.rgn » ne sert à rien. Pour le compiler sous condition, faites un click gauche sur ce fichier dans l’explorateur de ressources et choisissez l’onglet « Properties ». Spécifiez ensuite « USEREGION » en face de condition. Vous pourrez alors voir que le nom de votre de ressources se retrouve accolé avec la condition de sa compilation sous l’éditeur de ressources. Si vous désirez ne pas compiler le code dépendant de USEREGION vous devrez éditez le « switch » voulu, en changeant simplement son nom (en rajoutant un underscore avant ou après ou en spécifiant un nom facilement reconnaissable pour savoir si le switch est utilisé ou non), par exemple : USEREGION_NON Pour changer le nom du symbole refaites la même opération que ci-dessus, mais choisissez « Change » au lieu de « New ». Vous pouvez laisser USEREGION tel quel et compiler la template n°2, vous devriez voir sans problème la région qui est maintenant transparente. Au contraire si vous mettez autre chose que USEREGION la région n’est plus prise en compte et la bordure rose est visible. Le code de ce chapitre est disponible dans le dossier « template2 ». IV Implémentation des boutons Notre template commence à ressembler à quelque chose, toutefois il est nécessaire d’améliorer l’interface avec l’utilisateur. Par exemple, pour quitter l’application, faire ALT+F4 n’est définitivement pas le moyen le plus aisé. C’est pour cette raison que nous allons implémenter les boutons sous formes graphiques, mais d’une manière spécifique. En effet comme nous avons déjà vu les régions dans un chapitre précédent, nous allons continuer avec ces dernières en les utilisant encore une fois pour nos boutons, mais cette fois-ci avec des événements… Notez que pour l’exemple, seul le bouton « Generate » sera explicité, les autres boutons relevant des mêmes manipulations il aurait été superflu de les traiter. Remarquez enfin que les boutons sont au format bitmap (.bmp) et non au format .png. Il eut été relativement simple d’implémenter ceux-ci au format PNG, toutefois leur petite taille et la compression avec UPX rend le format PNG peut performant dans ce cas là. Toutefois cela reste à l’appréciation du programmeur qui devra faire la part entre la taille de l’exécutable et les performances (le format BMP est natif sous Windows alors que le format PNG nécessite du code supplémentaire pour être chargé). 4.1 Boutons et évènements Rappelez vous, pour faire une jolie template graphique nous utilisons une fenêtre avec un dessin sous la forme d’un PNG. Chaque bouton se voit doter de plusieurs états, soit il est au repos, ou alors il réagit à la souris, c'est-à-dire au survol et à l’appui. Ainsi plusieurs évènements sont possibles : o Le curseur est en dehors de l’aire du bouton Etat Repos o Le curseur entre dans l’aire du bouton Etat survol o Le bouton gauche de la souris est appuyé dans l’aire du bouton Etat appuyé o Le bouton gauche est relâché dans l’aire du bouton Exécute action + Etat survol o Le curseur quitte l’aire du bouton Etat Repos Peut être vous demandez-vous pourquoi l’action n’est exécutée que lorsque le bouton est relâché et non lorsqu’il est appuyé ! En fait c’est très simple, et Windows fonctionne ainsi lui aussi. Pour preuve, avec votre souris appuyez sur la croix de fermeture d’un programme (en haut à droite) et tout en gardant le bouton de la souris appuyé, sortez de l’aire du bouton représentant cette croix : résultat, le programme ne se ferme pas, c’est donc l’action « bouton de la souris relâché dans l’aire du bouton croix » qui exécute l’action de fermeture. Au niveau IHM (Interfaçage Homme Machine) ce type d’interaction est souhaitable car l’être humain n’étant pas exempt d’erreur, il peut toujours vouloir changer d’avis après avoir appuyer sur le bouton de fermeture… 4.2 Implémentation des boutons Notre fenêtre principale étant déjà faite nous pouvons penser à l’implémentation des boutons. Chaque bouton sera implémenté comme une fenêtre enfant (child window), ainsi notre template sera un peu comme une fenêtre principale disposant de plusieurs fenêtres enfants. Chacune de ces fenêtres enfants sera créer dynamiquement avec l’API CreateWindowEx. Ainsi toutes les commandes arrivent via notre fenêtre principale, nous devrons devront donc séparer les traitements et commandes relatifs aux boutons de ceux relatifs à la fenêtre principale. De ce fait nous aurons donc une procédure dédiée spécifiquement aux boutons. Notez enfin qu’au démarrage de notre programme tous les boutons auront le style « Repos ». Nous allons utiliser la technique de la compilation conditionnelle vue précédemment pour faire fonctionner les boutons. 4.2.1 Commutateurs pour les boutons Nous allons créer un fichier d’entête (.h) qui contiendra toutes les directives nécessaires à la compilation conditionnelles, que ce soit pour les boutons ou d’autre paramètres. J’ai choisi d’appeler ce fichier « switch.h ». Nous définirons dans ce fichier les aires (surface exprimées avec la structure RECT) correspondantes à chaque bouton, chacune de ces surface étant soumises à une directive conditionnelle de compilation. Par exemple pour le bouton « generate » : //commutateurs pour les boutons #ifdef USE_GENERATE_BUTTON RECT BTN_GENERATE_RECT = {78, 404,78+132, 404+57}; #endif Notez que les coordonnées sont bien sûr propres à chaque templates… Il faudra que vous notiez clairement où ce situe les points de chacun des boutons ou que votre graphiste vous les donnes… Si vous ne saisissez pas vraiment à quoi correspondent ces chiffres, en voilà une illustration : Notez simplement que la structure RECT ne nécessite que deux points, chaque point ayant deux coordonnées : Le premier point en haut à gauche se définit par l’axe des X et l’axe des Y (ici X = 78 et Y = 404). Le second point, en bas à droite se définit de même manière, soit 78 + 132 (largeur du bouton) et 404 + 57 (Hauteur du bouton). La notation avec addition n’est absolument pas indispensable et 78+132 aurait pu être remplacé par 210. Voici la structure RECT telle que définie dans la MSDN : typedef struct _RECT { LONG left; LONG top; LONG right; LONG bottom; } RECT, *PRECT; Il ne nous reste plus qu’à définir les symboles de ressources. Pour ce faire dans l’explorateur de ressources, faites un click droit sur le dossier « rsrc.rc » et cliquez sur « Resource Symbols » (cf. chapitre 3.2, inclusion de symboles, voir ici). On ajoutera le symbole USE_GENERATE_BUTTON et un symbole pour la fenêtre enfant nommé simplement IDC_BGENER (voir chapitre 1.3 pour les conventions de nom adoptée). Cet ID « général » sera utilisé lors de la création de la fenêtre. Les trois symboles pour les fichiers bitmaps seront ajoutés comme suit : Incluez les trois fichiers bitmaps (situés dans \Template\boutons\general) représentant le bouton « generate » dans ces trois états (voir ici, mais choisissez bien « IMPORT » puis « BITMAP »). Les trois états sont : normal, survolé et pressé. Vous pouvez les inclure un par un ou les trois à la fois (ce qui demandera de regarder lequel correspond à quoi). Dans le cas où vous les incluriez un par un : choisissez le bouton nommé « gen_n.bmp » (generate normal), un fois inclus le fichier s’ouvre dans l’IDE et VC++ à la fâcheuse manie de le nommer à sa convenance (IDB_BITMAP1) et de lui assigner un ID par défaut… Pour changer cela, cliquer sur le fichier bitmap dans l’explorateur de ressource : Ensuite regardez ses propriétés et changez son ID en IDC_BGENER_NORM. Si vous souhaitez changer aussi le numéro (par exemple ici j’ai choisi le numéro d’ID 1102, vous pouvez ajouter « =XXX » où XXX est un entier) : N’oubliez pas non plus de spécifier la condition de compilation dans le champ adéquat (Condition). Dans l’explorateur de ressources, le fichier bitmap à désormais le bon ID (et le cas échéant le bon numéro). Vous pouvez réitérer l’opération pour les deux autres fichiers BITMAP. Une fois tout cela fait pour le bouton « generate » vous pouvez recommencer l’opération pour chacun des boutons dont vous disposez. Rappelez vous que cette opération, bien que fastidieuse, ne sera nécessaire qu’une seule fois, pour votre premier template, ensuite vous n’aurez que quelques paramètres à changer ! En résumé : - Chaque bouton est une fenêtre enfant qui sera créée avec CreateWindowEx. - Chaque boutons dispose de trois images : Repos, Survolé, Appuyé. - Chaque bouton est compilé conditionnellement suivant l’utilisation d’un « switch » [exemple : USE_GENERATE_BOUTON] - Chaque bouton dispose d’une surface, que nous donnons avec la structure RECT. - Chaque bouton dispose d’un ID général [exemple : IDC_BGENER] - Nous mettons en ressource trois bitmaps représentant le bouton dans ses trois états, chaque état se voit doter d’un ID, composé de l’ID général et de l’état [exemple : IDC_BGENER_OVER]. 4.2.2 Le code pour les boutons Nous allons voir dans ce sous chapitre le code nécessaire à l’implémentation des boutons. Dans un premier temps nous définirons les variables nécessaires au code. Ensuite il faudra charger les bitmaps des boutons, créer les fenêtres supportant les boutons et finalement « sous classer » (SubClassing) les procédures des boutons. A présent que tout est en place concernant nos boutons nous devons définir les handles de notre fenêtre « generate » qui comme nous l’avons dit précédemment est une fenêtre enfant de la fenêtre principale. Nous aurons besoin pour ceci de quatre handles (un handle de fenêtre et trois handles de bitmap) que nous définirons encore une fois dans le fichier d’en tête dévolu à la compilation conditionnelle (switch.h) : //commutateurs pour les boutons #ifdef USE_GENERATE_BUTTON RECT BTN_GENERATE_RECT = {78, 404, 78+132, 404+57}; HWND hWin_Generate; HBITMAP hBmp_GENER_NORM; HBITMAP hBmp_GENER_OVER; HBITMAP hBmp_GENER_PRESS; #endif Les HBITMAP sont obtenus par le chargement des bitmaps en ressource avec l’API LoadBitmap et le HWND est obtenu avec la création de fenêtre par l’API CreateWindowEx. Avant d’aller plus loin, nous allons créer un nouveau fichier CPP pour le code des boutons (chargement des BMP, création des fenêtres, SubClassing). Nommez ce fichier comme bon vous semble, pour ma part je l’ai appelé « Buttons Handling.cpp » (gestion des boutons). L’en tête du fichier inclura le fichier d’en tête principal (« main.h ») et le fichier d’en-tête de compilation conditionnelle (« switch.h »). // // Fichier de gestion des boutons // #include "main.h" #include "switch.h" 4.2.2.1 Chargement des Bitmaps L’opération de chargements de états des bitmaps est relativement simple et ne nécessite pas de grandes explications, voyez par vous-même : //Chargement des bitmaps des boutons void LoadButtons (HINSTANCE hInstance) { #ifdef USE_GENERATE_BUTTON hBmp_GENER_NORM = LoadBitmap (hInstance, (LPCSTR)IDC_BGENER_NORM); hBmp_GENER_OVER = LoadBitmap (hInstance, (LPCSTR)IDC_BGENER_OVER); hBmp_GENER_PRESS = LoadBitmap (hInstance, (LPCSTR)IDC_BGENER_PRESS); #endif } Il ne faut pas oublier, encore une fois la compilation conditionnelle ! On charge simplement les trois bitmaps représentant les trois états du bouton « generate » dans l’exemple cidessus. Les handles de bitmaps (HBITMAP) ont été déclarés précédemment dans le fichier « switch.h ». Reste à savoir où nous devrons appeler cette procédure… En tout état de cause, avant la création de la fenêtre principale (et des fenêtres enfants). On peut donc soit le faire dans WinMain (avant DialogBoxParam bien sûr !), soit au début de WM_INITDIALOG. Pour ma part, j’ai choisi de la faire au début de WinMain (fichier main.cpp) : InitCommonControls (); //chargement des images des boutons LoadButtons (hInst); //obtient les dimensions de l'écran ScreenWidth = GetSystemMetrics (SM_CXSCREEN); 4.2.2.2 Création des fenêtres Avant tout, voici le code de la procédure de création de nos fenêtres (encore une fois, seul le bouton « generate » est explicité). //créations des fenêtres de boutons void CreateButtons (HWND hWin) { #ifdef USE_GENERATE_BUTTON hWin_Generate = CreateWindowEx (0, "STATIC", // fenêtre (control) de type Static 0, WS_CHILD | WS_VISIBLE | SS_BITMAP | SS_NOTIFY, BTN_GENERATE_RECT.left, // position de départ (X) BTN_GENERATE_RECT.top, // position de départ (Y) 0, // largeur donnée par le bitmap lui-même 0, // même chose pour la hauteur hWin, // Handle de la fenêtre principale (HMENU)IDC_BGENER, // ID de la fenêtre (ou control) hInst, 0); //on associe l'image au control (image de type "repos" ou position "normale") SendDlgItemMessage (hWin, IDC_BGENER, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp_GENER_NORM); //on indique que l'on subclass notre control OldButtonProc = (WNDPROC)SetWindowLong (hWin_Generate, GWL_WNDPROC, (LONG)ButtonProc); #endif } Le code est assez parlant de lui-même encore une fois. Remarquez simplement que l’on utilise un contrôle de type « STATIC ». La position du bouton est donnée par notre structure RECT (cf. fichier « switch.h »). On donne finalement le handle de la fenêtre principale et l’ID de notre fenêtre bouton (ici IDC_BGENER). Ensuite on associe l’image de type « bouton au repos » ou « bouton position normale » à notre contrôle en passant le HBITMAP (obtenu avec notre procédure « LoadButtons ») par l’intermédiaire de l’API SendDlgITemMessage. Finalement on indique que l’on va « subclasser » notre contrôle par l’intermédiaire de SetWindowLong. Notez que l’on passe en paramètre une procédure (ici ButtonProc) qui sera notre procédure de « subclassing ». L’API SetWindowLong retourne l’adresse de l’ancienne procédure de gestion de la fenêtre, adresse qu’il faudra sauvegarder. Dans notre exemple nous la sauvegardons dans « OldButtonProc ». Cette variable est définie en variable globale dans le fichier « ButtonsHandling.cpp ». 4.2.2.3 SubClassing des fenêtres // Subclass procedure LRESULT APIENTRY ButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { #ifdef USE_GENERATE_BUTTON if (hWnd == hWin_Generate) { if (uMsg==WM_MOUSEMOVE && wParam!= MK_LBUTTON) //affiche image "bouton survolé" si souris bouge et //bouton gauche souris n'est pas enfoncé SendMessage (hWin_Generate, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp_GENER_OVER); else if (uMsg==WM_LBUTTONDOWN) //affiche image "bouton appuyé" si bouton gauche enfoncé SendMessage (hWin_Generate, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp_GENER_PRESS); else if (uMsg==WM_LBUTTONUP) { //affiche bouton "normal" si bouton relâché SendMessage (hWin_Generate, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp_GENER_NORM); //envoi un "bouton gauche appuyé" à l'ancienne procédure return CallWindowProc (OldButtonProc, hWnd, WM_LBUTTONDOWN, wParam, lParam); } else //si aucun cas traité, appel l'ancienne procédure normalement return CallWindowProc (OldButtonProc, hWnd, uMsg, wParam, lParam); } #endif } Après cette vue du code, peut être vous posez vous la question concernant l’utilité du SubClassing… Le SubClassing permet en effet de gérer les messages pour un contrôle (une fenêtre enfant) en particulier. Sans entrer dans les détails, il est important de savoir que, par exemple, le message WM_MOUSEMOVE (dans la procédure de fenêtre principale) n’est utile que pour cette même fenêtre principale, en aucun cas il n’est utile pour les fenêtre enfants. Ainsi, dès lors que l’on cherche à gérer les messages d’un ou plusieurs contrôles en particulier, il est important de les subclasser. D’une manière générale, cela peut se résumer ainsi : Dés lors que le curseur est dans la surface (cf. structure RECT) d’un bouton, c’est la procédure de SubClassing qui gère les messages suivant les critères suivant. - Identifie le handle du bouton. - Quel est l’évènement ? Accorde le bitmap suivant l’évènement. - Si le bouton gauche est relâché (WM_LBUTTONUP) dans la surface du bouton, envoi un message (WM_LBUTTONDOWN) à la procédure de la fenêtre principale Mise en place de l’action. Une fois le WM_LBUTTONDOWN envoyé à notre procédure de fenêtre principale, celuici est reçu via WM_COMMAND où il pourra être traité ! (Nous verrons plus tard le traitement de ce message). Tout dernier point, il nous reste à implémenter un message WM_MOUSEMOVE dans la procédure de la fenêtre principale, sans quoi, lorsque le curseur quitte la surface du bouton, celui reste en position « survolée » et non en position repos. Il est en effet impossible de traiter, dans la procédure de « SubClassing » le cas où le curseur quitte la zone du bouton, c’est pourquoi il est nécessaire de le traiter dans la procédure principale (fichier « main.cpp ») : case WM_MOUSEMOVE: #ifdef USE_GENERATE_BUTTON SendMessage (hWin_Generate, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp_GENER_NORM); #endif Dernier point à régler, concernant les variables déclares dans le fichier « switch .h» mais utilisée dans le fichier « main.cpp ». Attention : Dans notre cas, pour utiliser correctement ces variables vous devrez les déclarer avec le mot clé « extern » ! A Présent nous disposons d’un programme avec des boutons possédant plusieurs états suivant l’action qui est faites à la souris. Je vous invite vivement à regarder le code de plus près, le code de ce chapitre étant présent dans le dossier « template3 ». N’hésitez surtout pas à expérimenter avec les différents paramètres ! Dans le prochain chapitre nous allons nous concentrer sur les commandes des boutons ainsi que sur les deux edit boxes. V Edit Boxes et Commandes Bien que les champs des deux edit boxes soient visibles sur notre template, il faudra tout de même créer dynamiquement les contrôles nécessaires. De plus un peu de fantaisie et de couleur ne faisant pas de mal, nous verrons comment créer notre propre fonte (police) pour les champs et comment appliquer un peu de couleur au texte. Une fois ceci fait nous pourrons nous pencher sur l’implémentation des divers messages de commandes relatifs à l’application. En effet nos boutons ne sont toujours pas effectifs et bien que l’on puisse appuyer dessus il ne se passe rien ! 5.1 Edit Boxes Le graphique de notre programme contient deux edit boxes mais celles-ci ne sont que graphiques : Nous allons dans un premier temps créer les deux edit boxes dynamiquement. Pour ce faire créez un fichier « EditBoxes.cpp », incluez le fichier d’en tête « main.h » et créer un fichier « EditBoxes.h » que vous inclurez dans ce nouveau fichier C++. //fichier EditBoxes.cpp //création des EditBoxes #include "main.h" #include "EditBoxes.h" extern HINSTANCE hInst; void CreateEditBoxes (HWND hWin) {...} Dans un premier temps nous commençons par créer dynamiquement une fonte (police) pour nos EditBoxes : //créé la fonte pour les deux edit boxes HFONT hEditFont = CreateFont(16,0,0,0,FW_BOLD,TRUE,0,0,DEFAULT_CHARSET,0,0,0,0,0); Ici nous créons une fonte grasse (BOLD), italique et ayant les attribut du jeu de caractère par défaut. Le système choisira lui-même la fonte se rapprochant le plus de ces spécifications étant donné que le dernier paramètre ne spécifie aucune police prédéfinie. Je vous invite à regarder les paramètres de cette API avec plus de détails dans la MSDN. Ensuite nous créons une couleur pour l’arrière plan des EditBoxes : //créé une couleur pour le background hEditBrush = CreateSolidBrush (EDIT_BACKGROUND); Le paramètre EDIT_BACKGROUND aura été défini par nos soins dans le fichier « main.h » (en effet ce paramètre sera réutilisé dans le fichier de code « main.cpp ») : //define globales (fichier main.h) #define EDIT_BACKGROUND RGB(255, 255, 255) #define EDIT_TEXTCOLOR RGB(233, 181, 170) Notez l’utilisation de la macro “RGB” permettant d’utiliser le codage de couleur de 0 à 255 sur chaque composante lumineuse [Rouge, Vert, Bleu]. Ici les trois valeurs à 255 correspondent à un blanc pur. Ensuite on crée dynamiquement nos deux EditBoxes (seul le code de la première est exposé) et on associe à chacune la fonte créée précédemment: //création de l'edit box "NAME" hEditName = CreateWindowEx (0, "EDIT", 0, WS_CHILD | WS_VISIBLE , EDIT_NAME_RECT.left, EDIT_NAME_RECT.top, EDIT_WIDTH, EDIT_HEIGTH, hWin, (HMENU)IDC_EDIT_NAME, hInst,0); //on associe la fonte à l'EditBox SendMessage ( hEditName, WM_SETFONT, (WPARAM)hEditFont, 0); Le fichier d’en-tête “EditBoxes.h” ne comprend que le strict minimum pour la création de nos deux EditBoxes : //fichier d'en-tête pour les EditBoxes #define EDIT_WIDTH 123 // largeur d'une EditBox #define EDIT_HEIGTH 19 // Hauteur d'une EditBox //cordonnées des EditBoxes RECT EDIT_NAME_RECT = {28, 354, 28 + EDIT_WIDTH, 354 + EDIT_HEIGTH}; RECT EDIT_SERIAL_RECT = {36, 380, 36 + EDIT_WIDTH, 380 + EDIT_HEIGTH}; //handle des EditBoxes HWND hEditName; HWND hEditSerial; //HBRUSH (couleur de fond) pour les deux EditBoxes HBRUSH hEditBrush; Les différentes coordonnées (structure RECT) ainsi que la largeur et la hauteur de nos EditBoxes auront été calculées en fonction du graphique principal. Notez aussi que les IDs des deux EditBoxes ont été mis dans le fichier de ressource, toutefois que leur inclusion dans le fichier « EditBoxes.h » n’aurait posé aucun problème : #define IDC_EDIT_NAME #define IDC_EDIT_SERIAL 3000 4000 Finalement il nous reste un peu de code à mettre en place dans le fichier « main.cpp », dans notre procédure de dialogue principale, plus spécifiquement dans le cas WM_CTLCOLOREDIT qui prend en charge la couleur des EditBoxes : case WM_CTLCOLOREDIT: //applique la couleur de fond pour les EditBoxes SetBkColor ((HDC)wParam, EDIT_BACKGROUND); //applique la couleur du texte pour les EditBoxes SetTextColor ((HDC)wParam, EDIT_TEXTCOLOR); //retourne OBLIGATOIREMENT la HBRUSH des EditBoxes return (LRESULT)hEditBrush; Notez que hEditBrush est déclaré en “extern” dans le fichier « main.cpp ». A présent nos EditBoxes fonctionnent mais les appuis sur les boutons ne produisent rien. Nous allons y remédier dans le chapitre suivant. 5.2 Commandes des boutons Dans ce chapitre nous allons voir comment implémenter les appuis sur les différents boutons. Lorsqu’un bouton est pressé, il envoi un message (en l’occurrence WM_COMMAND) à la procédure de dialogue de sa fenêtre principale. L’identificateur (ID) du bouton est reçu dans mot de poids faible (Low Word) de la variable wParam. Voici un exemple avec le bouton de fermeture qui envoie un WM_CLOSE : case WM_COMMAND: Select=LOWORD(wParam); switch(Select) { case IDC_BCLOSE: SendMessage(hWin, WM_CLOSE, 0, 0); break; default: break; } break; Toutefois nous ne devons pas oublier, encore une fois, la compilation conditionnelle que nous avons appliquée à nos boutons. Nous devons donc enclore chaque bouton dans des commutateurs conditionnels. Le même exemple avec le bouton de fermeture, mais cette fois ci avec un commutateur de compilation conditionnel : case WM_COMMAND: Select=LOWORD(wParam); switch(Select) { #ifdef USE_CLOSE_BUTTON case IDC_BCLOSE: SendMessage(hWin, WM_CLOSE, 0, 0); break; #endif default: break; } break; Voici au final à quoi ressemble notre implémentation des boutons de fermeture (« close »), « minimiser » et « generate » pour l’instant : case WM_COMMAND: Select=LOWORD(wParam); switch(Select) { #ifdef USE_CLOSE_BUTTON case IDC_BCLOSE: SendMessage(hWin, WM_CLOSE, 0, 0); break; #endif #ifdef USE_MINIMIZE_BUTTON case IDC_BMINIM: ShowWindow (hWin, SW_MINIMIZE); break; #endif #ifdef USE_GENERATE_BUTTON case IDC_BGENER: GenerateRoutine (hWin); break; #endif default: break; } break; La routine « GenerateRoutine » qui consiste simplement à inverser le nom entré a été implémentée dans un fichier à part (« KeyRoutine.cpp ») dans un simple but de démonstration. Les boutons pour la musique vont être mis en oeuvre dans la grande partie suivante, dédiée spécifiquement à la musique, toutefois leur fonctionnement général reste le même. Le code final de ce chapitre est présent dans le dossier « template4 ». Nous disposons à présent d’un programme fonctionnel mais qui n’a toujours pas de musique… VI Musique Maestro ! Notre programme fonctionne plutôt bien, mais les boutons dédiés à la musique sont bien sûr inopérants. Nous allons voir dans ce chapitre comment jouer des fichiers musicaux et comment gérer efficacement les appuis sur les différents boutons en charge de la musique. Pour jouer des fichiers XM j’ai choisi la bibliothèque MiniFmod qui à la particularité d’être une bibliothèque statique (c'est-à-dire qui est incluse dans le programme et ne nécessite donc pas de DLL). Malheureusement cette bibliothèque ne disposait pas de fonction de pause, qu’il a fallut ajouter mais comme elle n’était pas implémentée pour cela, un temps de latence (de l’ordre de la demi seconde) est nécessaire pour mettre un fichier en pause. 6.1 Bibliothèque musicale Comme il a été dit auparavant, la bibliothèque utilisée est une version de MiniFmod modifiée. L'interfaçage de cette bibliothèque se fait grâce aux fonctions présentent dans le fichier "FmodHandling.cpp". Voici les différents prototypes de fonctions : o bool Music_Init (void); o bool Music_Load (int RessourceID); o bool Music_Play (void); o bool Music_Unload (void); o bool Music_Pause (void); o bool Music_Stop (void); Vous n'avez pas à vous souciez de l'implémentation de ces fonctions n'y à celle des fonctions de rappels (memtell, memopen, etc.) présentes dans ce fichier. Seul leur utilisation vous sera nécessaire ! La bibliothèque (minifmod.lib) et son fichier d'en tête (minifmod.h) se trouvent dans le répertoire [RACINE]\libs\minifmod. Pour utiliser la bibliothèque nous devrons donc inclure ces quelques lignes dans le fichier "main.h" : // headers spécifiques au programme: #include "libs/minifmod/minifmod.h" // header pour minifmod (jouer des fichiers musicaux) // libs supplémentaires à inclure #pragma comment (lib, "winmm.lib") // Requis pour MiniFmod #pragma comment (lib, "libs/minifmod/minifmod.lib") // requis pour MiniFmod (XM player) Il ne faudra pas oublier non plus d'exposer les prototypes de fonctions présentes dans "FmodHandling.cpp" dans le fichier "main.h". //fichier fmodhandling.cpp bool Music_Init (void); bool Music_Load(int RessourceID); bool Music_Play (void); bool Music_Unload (void); bool Music_Pause (void); bool Music_Stop (void); Je ne vous ferais pas l'affront de vous expliquer le rôle des fonctions, les noms de celles-ci sont assez équivoques ! A présent que notre bibliothèque et toutes ces dépendances sont installées, il ne nous reste plus qu'à implémenter les fonctions dans le code de notre programme. 5.2 Implémentation de la musique Après avoir mis en place ce qui était nécessaire pour utiliser la bibliothèque, il faut dans un premier temps importer notre fichier XM dans les ressources. Encore une fois il s'agit simplement de placer le fichier en temps que ressource de type RCDATA (voir ce chapitre). Ici j'ai choisi l'identifiant (ID) IDR_XM pour ce même fichier XM. Le fichier (dans notre exemple "weather.xm") ce trouve dans le dossier [RACINE]\Musique. Il a été choisi, de manière simple, de jouer le fichier dès le lancement de l'application, aussi nous mettrons le code permettant l'initialisation, le chargement et la lecture du fichier dans WM_INITDIALOG : //MUSIC ! //initialisation du système de musique Music_Init(); // charge le fichier musical Music_Load(IDR_XM); //joue le fichier ! Music_Play(); Les paramètres des fonctions sont on ne peut plus simples et d'ailleurs seul le chargement du fichier requiert un paramètre, qui n'est autre que l'ID du fichier XM. Notez que l'ordre des fonctions est bien sûr très important (initialisation puis chargement puis lecture !). Il ne nous reste plus qu'à implémenter les différentes fonctions pour les boutons. On se doute qu'un appui sur le bouton "stop" arrêtera la musique en appelant la fonction Music_Stop, qu'un appui sur le bouton "pause" appellera la fonction Music_Pause, etc. Toutefois, il est important de savoir si la musique est en train de jouer ou pas ! Pour cela nous utiliserons deux drapeaux (flags) qui nous indiquerons si la musique joue ou est en pause. C'est deux drapeau sont déclarés dans le fichier "main.cpp" comme suit : unsigned char MusicPlay = TRUE; unsigned char MusicPause = FALSE; Voici une représentation schématique du fonctionnement général : L'implémentation en elle-même, au niveau du code C++ est relativement simple et se déroule au niveau de WM_COMMAND. En voici un exemple pour la mise en pause : #ifdef USE_MUSIC_PAUSE case IDC_MPAUSE: // le fichier joue if (MusicPlay == TRUE && MusicPause == FALSE) { Music_Pause(); MusicPause = TRUE; MusicPlay = FALSE; } break; #endif La seule chose non triviale qu'il faille retenir est la suivante : Attention : Si la musique est en pause (grâce à la fonction Music_Pause), pour reprendre la lecture vous devez rappeler Music_Pause ! En effet la fonction établi un drapeau interne à MiniFmod, et l'inversion de ce drapeau n'est possible qu'en rappelant la fonction. Vous n'aurez pas besoin de rappeler Music_Play. Voici l'exemple avec la gestion du bouton Play : #ifdef USE_MUSIC_PLAY case IDC_MPLAY: //le fichier est en pause if (MusicPlay == FALSE && MusicPause == TRUE) { Music_Pause();// inverse le flag interne de pause (cela reprend la lecture) MusicPlay = TRUE; MusicPause = FALSE; } // le fichier est stoppé else if (MusicPlay == FALSE && MusicPause == FALSE) { Music_Play(); MusicPlay = TRUE; MusicPause = FALSE; } break; #endif Ensuite à la fermeture du programme (WM_CLOSE), pour être sûr de faire une sortie propre, on arrêtera la musique et on déchargera le fichier de la mémoire comme ceci : case WM_CLOSE: Music_Stop(); Music_Unload(); Vous trouverez le code de ce chapitre dans le dossier "template 5"… Vous avez à présent une application pleinement fonctionnelle ! Notes d'optimisation & remerciements Le but de ce tutorial étant la programmation basique d'une application graphique, il n'a pas été question d'y adjoindre de commentaires sur l'optimisation. Sachez toutefois qu'il est possible de faire "maigrir" le programme de plusieurs centaines de kilo-octets sans trop d'effort tout en ayant un programme performant en terme de vitesse. La première chose dont il est nécessaire de parler est cette DLL qu'il faut adjoindre au programme (png.dll). Généralement on aime les applications seules et sans dépendances, car si une DLL vient à manquer, le programme ne démarre tout simplement pas. Pour remédier à ce "problème", le moyen le plus simple est d'inclure la DLL en ressource, et de l'extraire au démarrage du programme. Vous n'aurez besoin, pour ce faire, que de ces APIs (dans l'ordre) : o FindResource o LoadResource o LockResource o SizeOfResource o CreateFile o WriteFile Ensuite le comportement du programme reste le même ! Pour la question de taille, mieux vaut "packer" l'application (avec UPX par exemple). Mais vous pouvez bien sûr choisir tout autre packer, voir un packer doublé d'un protector si vous avez des choses à cacher :p Certains packers (tel UPX) permettent de garder l'icône du programme tout en compressant la section des ressources ce qui est réellement un plus au niveau de la taille finale de l'exécutable. Regarder bien si votre packer permet de compresser la section ressource tout en préservant la visibilité de l'icône sous l'explorateur de Windows. N'oubliez pas que les compilateurs modernes disposent d'options non négligeables qui peuvent influencer la vitesse d'exécution ou la taille de l'exécutable finale voir les deux dans certains cas. Les toutes dernières générations de compilateurs réalisent réellement des merveilles dans ce domaine avec de multiples options possibles. D'un point de vue générale, essayez d'utiliser les instructions FPU ou SSE (attention le SSE2 et à plus forte raison le SSE3 ne sont pas toujours pris en charge par les CPUs !) grâce aux options de votre compilateur. Essayez de favoriser la taille et la vitesse d'exécution du code et pensez aux diverses autres optimisations (VC 8 dispose d'une option d'optimisation "cross-obj" performante). Remerciements : Ce tutorial m'aura pris au final un temps considérable et sa rédaction s'est "propagée" sur des mois, avec de nombreuses retouches… Au départ il contenait seulement quelques notes et a fini par devenir un tutorial complet… J'en suis finalement arrivé à bout :D - Je tiens tout particulièrement à remercier ++Meat pour ses fabuleux GFX et ses encouragements ! - L'équipe de la FRET et les habitants du forum pour leurs encouragements et l'amitié qu'ils me portent. - L'équipe des Modos de FC. - Les personnes que j'apprécie dans le monde du RE. [Kikooo !!!] - Ceux qui font du RE pour apprendre et surtout, surtout, surtout … pour s'amuser !!! Tutorial livré avec fautes d'orthographes ! [Cadeau !] L'auteur n'est aucunement responsable de l'utilisation qui est faite de ce tutorial ainsi que des codes sources et des binaires présentés. Tous les fichiers compris dans l'archive sont livrés SANS AUCUNE GARANTIE !