Alain Zanchetta

Conseil et développement logiciel

Windows, Windows Mobile (Smartphone & Pocket PC), .NET, C#, C++, WPF, Silverlight

Du nouveau pour Visual C++

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.

MFC Feature Pack

Tour d'horizon

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 :

  •  des classes de haut niveau permettant de mimer les interfaces utilisateurs des applications comme Visual Studio .NET ou Office 2007,
  • des classes représentant des contrôles " unitaires " offrant des possibilités plus riches que les contrôles de base de Windows encapsulés par les versions précédentes des MFC.

Pour illustrer ces nouveautés, voici quelques copies d'écrans d'exemples fournis avec le Feature Pack :

  •  VisualStudioDemo : cette application reprend l'essentiel des possibilités de Visual Studio en termes d'interface utilisateur. On en particulier voit sur cette copie d'écran la gestion du MSI sous forme de " Tabbed Documents " et les fenêtres dockables " Docking Pane "

  • MSOffice2007Demo : comme son nom l'indique, c'est l'interface d'Office 2007 que cet exemple copie. On peut noter en particulier le fameux Ruban Office ainsi que des pages de propriétés avec affichage des onglets sur la gauche

  •  NewControls : cet exemple permet de visualiser l'ensemble des contrôles unitaires comme les boutons

Avant de passer à la mise en œuvre concrète de ces éléments, voici une liste plus complète des nouvelles fonctions proposées :

  • Extensions des menus (sombres, animations, images, barres de menus qui peuvent être dockées…), des barres d'outils (" rebars ", texte associé aux icônes, grandes icônes, images animées au passage de la souris…
  • Ruban : comme dans Office 2007, il est personnalisable, peut contenir des sous-rubans (" panels "), des palettes…
  • " Panes " : les " Panes " sont des fenêtres redimensionnables, qui peuvent être dockées ou non.
  • Alertes sur le bureau semblables à celles affichées par Messenger ou Outlook
  • Fenêtres MDI affichées dans des groupes de " Tabbed Documents ", qui peuvent être détachés, déplacés, rattachés, etc…
  • Contrôles et boîtes de dialogue nouveaux ou enrichis, par exemple grille de propriétés à la Visual Studio
  • Fonctions de personnalisation : un simple glisser-déplacer permet de déplacer un bouton d'une barre d'outils vers un menu ou vice versa, l'utilisateur peut créer ses propres barres d'outils,etc. Toutes ces personnalisations pouvant bien sûr être sauvegardées puis restaurées.
  • Style Visuel : tous les éléments dont il a été question précédemment peuvent être affichés avec le style de Visual studio ou celui des applications Office (versions de 2000 à 2007).

Mise en oeuvre

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 :

  • Une classe application : elle contient le point d'entrée du programme et est dérivée de CWinAppEx. L'initialisation de l'application et l'enregistrement des modèles de documents est toujours réalisée dans InitInstance. CWinAppEx dérive quant à elle directement de l'habituelle CWinApp et offre les services de gestion de l'état de l'application, de sa sauvegarde et de sa restauration ainsi que l'initialisation et l'accès à un certain nombre de gestionnaires (" managers ").
  • Une classe frame principale représentant la fenêtre principale de l'application et dérivée de CMDIFrameWndEx. Cette classe communique elle-aussi avec différents gestionnaires.
  • Une classe frame fille représentant la fenêtre de chaque document et dérivée de CMDIChildWndEx
  • Une classe document dérivée de CDocument,
  • Une classe vue dérivée directement ou non de CView.

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 :

  • Gestionnaire de style visuel ou " Visualization Manager "
    Ce gestionnaire contrôle l'aspect graphique de l'application. Il sert bien évidemment aux contrôles appartenant aux MFC mais doit être aussi sollicité dans le cas de développement de contrôles personnalisés. Il permet à l'application d'avoir un rendu proche de Visual Studio ou plutôt d'une application Office :

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.

  • Gestionnaire de Docking
    Ce gestionnaire contrôle le positionnement et le statut des fenêtres dans une frame window . Tout comme le gestionnaire de style visuel, il permet au développeur de choisir le type de rendu de son interface utilisateur lors des opérations de docking/undocking de fenêtre :
    • Docking Standard : un rectangle est affiché pour matérialiser la future position de la fenêtre
    • Docking Immédiat : la fenêtre est affichée dans son intégralité
    • o Docking Smart : la fenêtre est affichée dans son intégralité et accompagnée de repères visuels qui indiquent les positions de possibles

  •  CDrawingManager : gère les tracés sophistiqués comme les ombres ou les dégradés
  • CKeyboardManager : gère les tables de raccourcis clavier
  • CMenuTearOffManager : permet l' " arrachage " d'éléments de menus
  • CMouseManager : permet à l'utilisateur d'associer des commandes aux événements souris dans une vue
  • CShellManager : permet de gérer les PIDLs (= références des objets manipulés par le Shell de Windows)
  • CTooltipManager : gère les bulles d'aides (tooltips)
  • CUserToolsManager : gère les outils personnalisables que l'utilisateur peut ajouter dans les menus

Exemple d'implémentation d'un ruban

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.
Lorsqu'un bouton déclenche le message WM_COMMAND, le handler d'événément déclaré dans la MESSAGE MAP est appelé et peut effectuer le traitement correspondant. Lorsque l'objet du ruban est plus sophistiqué qu'un simple bouton, il suffit d'en conserver le pointeur pour l'interroger lors du traitement de l'événement. Voici par exemple le traitement du choix de la police de caractères dans l'application exemple associée à cet article :
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.

Standard C++ Library TR1 Extensions

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.

Pointeurs partagés

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 shared_ptr est non intrusif : il ne demande aucune modification de la classe T pour être utilisé.
  • Le shared_ptr est basé sur le comptage de référence : lorsque le dernier shared_ptr sur un objet est libéré, celui-ci l'est aussi. Attention toutefois : le comptage de référence ne protège par contre les références cycliques !
  • Un shared_ptr<TypeDerive> peut être converti en shared_ptr<TypeParent> sans casser le comptage de références.

Expressions régulières

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 :

  • regex_match() : est-ce que la chaîne dans son ensemble correspond à l'expression régulière considérée ?
  • regex_search() : permet de parcourir les formes correspondant à l'expression régulière dans une chaîne
  • regex_replace() : permet de transformer toutes les occurrences (ou éventuellement seulement la première) d'une expression régulière dans une chaîne

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;
}

Conclusion

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