Depuis la sortie de .NET en 2002, Microsoft semblait avoir un peu délaissé le C++ même s’il est toujours disponible dans Visual Studio, à la fois dans sa composante C++ managé permettant de générer des composants .NET et dans sa composante « native » traditionnelle. L’automne dernier, Microsoft a annoncé un ensemble de nouveautés pour cette composante native qui montre qu’il n’en est rien ou tout au moins que Microsoft a écouté la communauté des développeurs C++ qui reste assez importante, surtout chez les éditeurs de logiciels. L’objectif de cet article est de faire un tour d’horizon de ces nouveautés permettant de comprendre ce qu’elles peuvent apporter aux développeurs C++ et comment les mettre en œuvre. Ces composants s’installent au-dessus de Visual Studio .NET 2008, ils sont en version beta au moment de la rédaction de cet article mais devraient être disponibles en version définitive au printemps 2008.
Le MFC Feature Pack est un ensemble de classes permettant (enfin ?) aux développeurs MFC d'offrir à leurs utilisateurs des interfaces utilisateur modernes au prix d'une complexité de développement raisonnable. Par exemple, il a toujours été possible d'incorporer dans une application des boutons avec du texte et une image… mais le développeur C++ doit pour ceci redévelopper son propre contrôle Bouton à partir de 0. Les classes du MFCFP apportent des fonctions sur plusieurs axes :
Pour illustrer ces nouveautés, voici quelques copies d'écrans d'exemples fournis avec le Feature Pack :
Avant de passer à la mise en œuvre concrète de ces éléments, voici une liste plus complète des nouvelles fonctions proposées :
Lors de l'installation du Feature Pack, l'assistant de création de projets MFC est mis à jour pour permettre de créer un projet dans lequel l'essentiel des éléments de structure seront mis en place. Les nouvelles options apparaissent dans l'étape " Application Type " :
Le squelette de projet généré par cet assistant ne diffère pas fondamentalement de ce dont on a l'habitude. Ainsi, dans le cas où on choisit une application de type MDI supportant l'architecture Document/Vue, on retrouve les classes habituelles de ce type de projet, c'est-à-dire :
Les documents et vues ne sont donc pas modifiés, les nouveautés du Feature Pack sont prises en charge par les classes d' " infrastructure " comme l'application et la frame principale. Beaucoup de fonctions sont prises en charge par des classes de type " Manager " dont voici les principales :
Un aspect intéressant de ce gestionnaire est la possibilité de basculer dynamiquement d'un style à l'autre : le choix effectué au moment du développement n'est donc pas définitif et peut être par exemple modifié au lancement de l'application en fonction de son contexte d'utilisation, ou selon un choix de l'utilisateur.
Voici par exemple le code permettant de passer au style Visual Studio .NET 2005 :
CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerVS2005));
Remarque : le passage d'une barre d'outils à un Ruban n'est pas une simple question de style mais repose sur l'utilisation de classes différentes.
Aujourd'hui, il n'y a pas de Concepteur Graphique (" Designer ") pour créer un Ruban dans une application MFC et il faut donc passer par du code. La classe de la fenêtre principale doit être dérivée de CFrameWnd car certaines méthodes de la classe CMFCRibbonBar (par exemple OnPostRecalcLayout) s'attendent à trouver une fenêtre parent de ce type. Lorsqu'on crée une application avec l'assistant MFC, celui-ci génère le code permettant de créer un Ruban minimaliste qu'on peut ensuite modifier en réécrivant la méthode InitializeRibbon de la frameprincipale. Comme il a été dit, il faut une classe dérivant de CFrameWnd mais le support de l'architecture Document/Vue n'est pas nécessaire. Voici les deux étapes de l'assistant MFC grâce auxquelles on obtient un ruban :
Quelles sont les principales composantes d'un ruban ?
Tout d'abord, on retrouve généralement un bouton en haut à gauche qui permet de faire afficher le menu " Fichier ". Ce bouton est un CMFCRibbonApplicationButton qui se paramètre de la façon suivante :
m_MainButton.SetImage(IDB_MAIN); // l'image m_MainButton.SetText(_T("\nf")); // le raccourci clavier m_MainButton.SetToolTipText(_T("Menu fichier");// bulle d'aide m_wndRibbonBar.SetApplicationButton(&m_MainButton, CSize (45, 45));
Pour ajouter les éléments du menu, il faut paramétrer la " catégorie " principale (la notion de catégorie est expliquée plus loin) pour indiquer la source des images. Chaque élément est ensuite ajouté au menu:
CMFCRibbonMainPanel* pMainPanel = m_wndRibbonBar.AddMainCategory(strTemp, IDB_FILESMALL, IDB_FILELARGE); pMainPanel->Add(new CMFCRibbonButton(ID_FILE_NEW, _T("&New", 0, 0));
Pour créer un sous-menu comme celui de l'impression, on ajoute simplement les éléments au bouton correspondant :
CMFCRibbonButton* pBtnPrint = new CMFCRibbonButton(ID_FILE_PRINT,_T("&Imprimer"),6,6); pBtnPrint->AddSubItem(new CMFCRibbonLabel(_T("Options d'impression")); // Titre pBtnPrint->AddSubItem(new CMFCRibbonButton(ID_FILE_PRINT_DIRECT, _T("Impression rapide"), 7, 7, TRUE));
A côté du bouton principal se trouvent des petites icônes qui représentent les " commandes rapides ". Elles s'ajoutent simplement de la manière suivante :
CList<UINT, UINT> lstQATCmds; lstQATCmds.AddTail(ID_FILE_NEW); lstQATCmds.AddTail(ID_FILE_OPEN); lstQATCmds.AddTail(ID_FILE_SAVE); m_wndRibbonBar.SetQuickAccessCommands(lstQATCmds);
Avant de passer au cœur du ruban, analysons sa partie droite : celle-ci est constituée du bouton « à propos » et d’un menu permettant de choisir le style visuel de l’application. Tous les deux s’ajoutent en créant des CMFCRibbonButton comme précédemment et en les ajoutant directement à la barre du ruban. Voici le code correspondant au menu style :
CMFCRibbonButton* pVisualStyleButton = new CMFCRibbonButton(-1, _T("Style"), -1, -1);
pVisualStyleButton->SetMenu(IDR_THEME_MENU, FALSE, TRUE);
pVisualStyleButton->SetToolTipText(_T("Modifie le style visuel"));
pVisualStyleButton->SetDescription(_T("Choisit parmi les styles Office"));
m_wndRibbonBar.AddToTabs(pVisualStyleButton);
Passons maintenant au cœur du ruban. La première notion importante est la catégorie, elle définit un onglet au niveau du ruban. Dans notre application exemple, deux catégories ont été définies " Home " et " Calculatrice ".
CMFCRibbonButton* pVisualStyleButton = new CMFCRibbonButton(-1, _T("Style"), -1, -1);
pVisualStyleButton->SetMenu(IDR_THEME_MENU, FALSE, TRUE);
pVisualStyleButton->SetToolTipText(_T("Modifie le style visuel"));
pVisualStyleButton->SetDescription(_T("Choisit parmi les styles Office"));
m_wndRibbonBar.AddToTabs(pVisualStyleButton);
A l'intérieur d'une catégorie, les éléments sont regroupés en panneaux (" Panels ") qui contiennent quant à eux les éléments manipulés directement par l'utilisateur : boutons, combo-boxes, sous-menus, etc. Le principe de création des conteneurs est le suivant : le conteneur parent crée l'objet demandé et retourne l'instance C++ correspondante, ce qui permet au développeur de la paramétrer (ajout des sous-éléments) :
CMFCRibbonCategory* pCatFonctions = m_wndRibbonBar.AddCategory(_T("Calculatrice"), 0, 0);
CMFCRibbonPanel* pPanel = pCatFonctions->AddPanel(_T("Saisie"));
La création des sous-éléments se fait d'une manière différente : on crée l'objet, on le paramètre, puis on l'ajoute à son conteneur. De nombreuses classes CMFCRibbonXXX sont définies et répondent à la fois à des besoins génériques (CMFCRibbonButton, CMFCRibbonLabel, CMFCRibbonComboBox…) ou offrent des services précis comme un sélecteur de couleur (CMFCRibbonColorButton) ou de police de caractères (CMFCRibbonFontComboBox). On peut noter au passage une approche similaire pour les classes de contrôles " hors ruban " où on va trouver aussi bien un CMFCButton qu'un CMFCColorButton.
Ajouter un bouton simple est facile :
pPanel->Add(new CMFCRibbonButton(IDC_COSINUS, _T("Cosinus")));
Une gallerie est un ensemble d'images permettant un choix visuel et il y en a beaucoup dans Office car elles facilitent la prévisualisation de l'effet d'une commande : pour les créer, il suffit d'indiquer la ressource de type bitmap à utiliser ainsi que la largeur de chaque élément :
m_pGallery = new CMFCRibbonGallery(IDC_GALLERIE, _T("Forme"), -1, -1, IDB_BITMAP1,48); m_pGallery->SetButtonMode(); m_pGallery->SetAlwaysLargeImage(); pPanelView->Add(m_pGallery);Maintenant que nous avons vu comment ajouter les divers éléments graphiques au ruban, comment peut-on en recevoir les événements associés ? Tout simplement la manière habituelle avec les MFC : en passant par les " MESSAGE_MAP ". En effet, chaque élément de ruban possède un identifiant au même titre que les éléments d'interface habituels comme les contrôles dans les formulaires, dans les barres d'outils ou les éléments de menus.
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx) … ON_COMMAND(IDC_FONT, OnFont) END_MESSAGE_MAP() void CMainFrame::OnFont() { const CMFCFontInfo *pFont = m_pFontButton->GetFontDesc(); m_wndView.SetFont(pFont); }
Ceci va conclure ce tour d'horizon des composants graphiques du Feature Pack de Visual Studio .NET 2008. Pour l'instant, la documentation est encore partielle mais le nombre important d'exemples compense cet inconvénient. Par exemple, RibbonGadget est une application qui illustre assez largement les possibilités du ruban et des classes correspondantes.
L'autre composant du Feature Pack est un ensemble d'extensions à la bibliothèque standard C++ appelé TR1. Que signifie TR1 ? " Technical Report 1 " c'est le nom du document présentant des propositions d'ajout au standard C++. Même s'il ne s'agit que de propositions, elles sont d'une utilité certaine pour le développeur C++ et c'est pour cela qu'une implémentation en est ajoutée à Visual Studio .NET 2008. Les ajouts du TR1 consistent principalement dans des wrappers de pointeurs, de références et de fonctions, de nouveaux conteneurs de données et des classes de gestion des expressions régulières.
La principale difficulté rencontrée pendant le développement d'applications C++ est la gestion de la mémoire. Le programmeur doit être rigoureux et savoir exactement quand libérer les objets qu'il a alloués sous peine de fuites mémoire ou de plantages. Cette difficulté est une des principales raisons du succès des langages comme C#, Visual Basic ou Java car ces langages soulagent le programmeur de ces détails techniques et lui permettent de plus se concentrer sur les aspects fonctionnels et algorithmiques de son application.
En fait, la difficulté de gestion de la mémoire est principalement liée au cycle de vie de l'objet manipulé. Par exemple, les objets créés puis libérés au cours d'un calcul sont somme toute assez simples à gérer ; la principale difficulté ici est liée aux sorties anticipées du calcul, typiquement lorsqu'une erreur est rencontrée. Le développeur alors le choix de gérer manuellement la libération de ses ressources, ou de se baser sur le mécanisme des Smart Pointers : un smart pointer est une classe C++ s'appuie sur la capacité de C+ de libérer automatiquement les objets créés sur la pile pour libérer des objets alloués autrement (par exemple mais pas exclusivement à l'aide de new) et qui demandent une libération explicite.
En revanche, il existe des cas où les objets alloués ont une durée de vie qui n'est pas facile à déterminer à l'avance : par exemple si l'objet est partagé entre plusieurs parties relativement indépendantes de l'application. Dans ces cas là, on s'appuie généralement sur un mécanisme de comptage de référence. Au passage, on se rappellera que le comptage de référence est le mécanisme utilisé par COM pour la gestion de la durée de vie des composants car on est effectivement dans une situation où un objet peut être partagé par plusieurs " clients " indépendants.
La classe shared_ptr du TR1 a pour objectif de simplifier la gestion des ressources partagées et de fiabiliser ainsi les applications C++. Cette classe est un Template, afin de conserver le typage fort offert par C++ : shared_ptr<T> est un pointeur partagé vers une instance de la classe T. Voici quelques points importants sur les shared-ptr :
Le premier contact d'un développeur avec les expressions régulières est souvent lié à des opérations de recherche et remplacement dans son éditeur de code favori. Elles sont cependant aussi utilisées au sein même des applications, typiquement pour effectuer une validation de données : est-ce que la chaîne reçue correspond bien à une adresse de messagerie ? A un numéro de carte bleue ? Evidemment, les expressions régulières ne permettent qu'une vérification syntaxique mais celle-ci est une première étape indispensable.
Lorsqu'on a une chaîne de caractères à analyser, on peut écrire le code correspondant à la main (on le base souvent sur un automate à état) mais ceci est fastidieux et source d'erreurs. Les expressions régulières permettent de s'intéresser au " quoi " , i.e. " quelle est la forme d'une adresse email ? " plutôt qu'au " comment ".
La classe regex du TR1 s'appuie sur les mécanismes habituels de la STL pour offrir au développeur à la fois une approche générique et simple d'emploi. La grammaire utilisée par défaut par cette classe est celle définie dans la norme ECMA-262 utilisée dans Javascript.
Trois possibilités de traitements sont proposées par regex :
Exemple d'utilisation de match : vérification de la validité d'un email (simplifié : uniquement des minuscules et pas de chiffres)
#include <iostream> #include <regex> using namespace std; using namespace std::tr1; int main(int argc, char* argv[]) { const char *tabEmails[] = { "billg@microsoft.com", "billg@microsoft", "billg.microsoft.com", "bill.gates@microsoft.com", ".gates@microsoft.com" }; const regex r("[a-z][a-z\\.]+@[a-z]+\\.[a-z]+"); for (int i=0;i<sizeof(tabEmails)/sizeof(tabEmails[0]);i++) { cout << tabEmails[i] << " : " << (regex_match(tabEmails[i], r) ? "OUI" : "NON") << endl; } return 0; }
La description de toutes les possibilités offertes par le Feature Pack pour Visual Studio .NET 2008 pourrait remplir un livre entier et n'était pas l'objectif de cet article. Celui-ci avait pour objectif de donner aux développeurs C++ envie d'aller un peu plus loin en téléchargeant ce Feature Pack mais aussi et surtout de leur montrer que Microsoft n'a pas du tout abandonné le C++ et mais continue à améliorer ce composant essentiel de Visual Studio afin d'améliorer la productivité des développeurs et la richesse des applications développées en C++ et les prochaines versions de Visual Studio continueront dans cette voie.
Références du Feature Pack:
Feature Pack beta : http://www.microsoft.com/downloads/details.aspx?FamilyId=D466226B-8DAB-445F-A7B4-448B326C48E7&displaylang=en
Documentation sur le site MSDN : http://msdn2.microsoft.com/en-us/library/bb982354.aspx
Documentation téléchargeable (.CHM) : http://www.microsoft.com/downloads/details.aspx?FamilyId=0D805D4E-2DC2-47C7-8818-A9F59DE4CD9B&displaylang=en