I. Définition du problème

Comme je l'ai dit en introduction, la conversion entre chaine de caractères et type natif est centrale dans nos programmes. Les contrôles Windows travaillent avec des chaines de caractères tout en pouvant représenter une saisie numérique, voire décimale.

La représentation interne de ces données visuelles étant le plus souvent stockée en interne dans leur type natif. Vient alors à se poser le problème de la conversion entre le contrôle et sa variable.

La majorité des posts s'y rapportant sur nos forums se résume souvent à ces questions :

  • Comment faire pour convertir une chaine en entier, double, float, etc. ? ;
  • Comment formater une chaine à partir d'un entier, double, float, etc. ? ;
  • Comment récupérer un entier, double float, etc., à partir d'un contrôle, et bien sûr l'inverse qui consistera à mettre à jour le contrôle à partir d'un type natif.

Pour procéder, nous disposons de plusieurs techniques issues de différentes bibliothèques :
Celles du C, les bibliothèques standards du C++, mais aussi quelques apports intéressants avec le projet BOOST.

J’ai aussi fait attention à ce que l'ensemble des codes présentés fonctionnent en UNICODE.
Enfin je ne pouvais ignorer le C++/CLI, je consacrerai donc une rubrique pour les deux sens de conversion.

C'est ce que nous allons découvrir maintenant.

II. Transformation d'une CString ou du contenu d'un contrôle vers un type natif

II-A. La Bibliothèque C

La bibliothèque C fournit un ensemble de fonctions permettant la transformation de chaine en type de données et inversement.

Un post général y est consacré dans la faq Visual C++, aussi je ne vais pas m'attarder dessus, je préfère donner la priorité au traitement C++ du sujet :

II-B. Du côté des MFC

Les MFC comme l'API 32 permettent avec GetDlgItemInt la récupération du contenu d'un contrôle sous forme d'entier.

Mais quid des autres types long, float, double ?

Pour ces autres types, il faudrait attacher au contrôle une variable correspondante au type de donnée souhaité et appeler la méthode UpdateData(TRUE) pour disposer de sa valeur.

Voyons maintenant une méthode de transformation d'une chaine dans son type natif mettant en œuvre la bibliothèque standard C++ (standard library).

II-C. La bibliothèque standard du C++ (SL)

Commençons par convertir une chaine vers un type spécifié :

 
TéléchargerCacher/Afficher le codeSélectionnez

Utilisation :

 
TéléchargerCacher/Afficher le codeSélectionnez

La version associée à un contrôle MFC

La récupération du contenu d'un contrôle dans son type natif : entier, double, float, long ne cause pas plus de problèmes en modifiant un peu le code précédent on obtient :

 
TéléchargerCacher/Afficher le codeSélectionnez

Ces exemples s'appuient sur la classe istringstream et utilisent l'opérateur surchargé >> pour effectuer la conversion.

Ce type de fonction se nomme fonction modèle, elle permet de rester générique par rapport au type T utilisé.

II-D. Le Projet BOOST

Voyons maintenant ce que propose le projet BOOST.

Il fournit une classe de conversion lexical_cast qui permet la conversion de chaines représentant des nombres en base 10.

Pour utiliser l'exemple qui va suivre, vous devrez télécharger le projet BOOST disponible sur SourceForge. La dernière version stable est la 1.33.1

Vous pouvez aussi utiliser la version graphique de distribution de BOOST avec un outil distribué par Boost Consulting ,voir aussi le tutoriel Installer et utiliser Boost sous Windows avec Visual C++ 2005.

Commençons par convertir une chaine vers un type spécifié.

L'utilisation de lexical_cast est confondante de simplicité :

 
TéléchargerCacher/Afficher le codeSélectionnez

Plus besoin de flux, la séquence peut être utilisée directement.

Néanmoins on peut toujours encapsuler le traitement dans une fonction modèle :

 
TéléchargerCacher/Afficher le codeSélectionnez

Le même exemple donnera :

 
TéléchargerCacher/Afficher le codeSélectionnez

La version associée à un contrôle MFC :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
template<typename T>
bool BoostFromCtrl( const CWnd & Ctrl, T & Dest )
{
    CString Str;
    Ctrl.GetWindowText(Str);
    try
        {
                Dest = boost::lexical_cast<T>(static_cast<LPCTSTR>(Str));
        }
        catch(boost::bad_lexical_cast &e)
        {
#ifdef _DEBUG
                TRACE("Mauvaise conversion : %s",static_cast<const char *>(e.what()));
#endif
                return false;
        }
        return true;
}

Utilisation :

 
TéléchargerCacher/Afficher le codeSélectionnez

À travers ces exemples nous avons découvert différentes techniques de conversion d'une chaine vers un type numérique, le contrôle de la conversion, et ce avec les différentes bibliothèques.

Pour ma part je trouve l'utilisation de boost ::lexical_cast plus séduisante, car très simple et utilisable directement.

II-E. Le C++/CLI

En C++/CLI la conversion d'une chaine managée (String) en entier ou double est relativement simple :

 
TéléchargerCacher/Afficher le codeSélectionnez

On trouvera les fonctions de conversions dans l'espace de nom System::Convert.

La conversion en double ou en int ne cause pas de problème particulier.

En cas de chaine invalide, une exception FormatException est levée, comme c'est le cas ici avec mon exemple lors de la tentative de conversion de la chaine en entier.

Autre exemple mettant en œuvre les conversions entre bases :

 
TéléchargerCacher/Afficher le codeSélectionnez

III. Conversion d'un type int,long,float,double vers une chaine de caractères

Passons maintenant à l'exercice inverse qui consiste à prendre une valeur d'un type natif et de la transformer en chaine de caractères destinée à mettre à jour un contrôle.

III-A. La Bibliothèque C

Classiquement on pourra utiliser la fonction sprintf, ou les fonctions associées au type de données : comme itoa pour la conversion d'un int vers une chaine.

Une fois la chaine constituée on mettra à jour le contrôle.

III-B. Du côté des MFC

La classe CString nous aide dans la conversion en proposant une méthode Format fonctionnant de la même manière que la fonction vsprintf, sprintf du C.

Cet exemple de la faq Visual montre comment l'utiliser : Comment convertir un entier, un double, un float, etc. en chaine de caractères ?

Toujours dans l'optique de mettre à jour directement le contrôle, pour les autres types il faudrait attacher au contrôle une variable correspondante au type natif souhaité et appeler la méthode UpdateData(TRUE) pour disposer de sa valeur.

III-C. La bibliothèque standard du C++ (SL)

Comme précédemment, le code qui suit permet de se passer de l'association d'une variable à un contrôle et donc d'affecter directement une valeur d'un type natif au contrôle désigné.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
#include <sstream> 
#include <string> 
#include <iomanip> 
#include <iostream> 

class FormatNum
{
public :
        FormatNum(){}
        template <typename T>
        FormatNum(const T&t)
        {
                operator <<(t);
        }
        template <typename T>
    FormatNum & operator << (const T& t)
    {        
        m_ss << t;
        return *this;
    }

public :
#ifdef _UNICODE
        std::wstringstream m_ss;
#else
        std::stringstream m_ss;
#endif
};

template<typename T>
void ToCtrl(CWnd & Ctrl,const T & Src,FormatNum &rFormat=FormatNum())
{ 
        rFormat << Src;

#ifdef _UNICODE
        std::wstring s=rFormat.m_ss.str();
#else
        std::string s=rFormat.m_ss.str();
#endif
    Ctrl.SetWindowText(s.c_str());
}

Utilisation :

 
TéléchargerCacher/Afficher le codeSélectionnez

Le traitement se décompose en deux parties.

La fonction modèle ToCtrl permettant la transformation du type utilisateur et l'affectation de la chaine au contrôle passée en argument.

Un objet fonction FormatNum optionnel qui permet le formatage du flux pour contrôler la conversion, l'enchainement des arguments, et qui fournit l'objet flux de conversion de la classe stringstream à la fonction modèle ToCtrl.

Voyons son utilisation dans les exemples qui suivent.

Dans le cas d'un double ou float si on veut maitriser la précision du nombre envoyé on pourra utiliser la fonction setprecision définie dans l'entête standard iomanip pour fixer le nombre de digits souhaités.

Vous pouvez bien sûr utiliser les autres fonctions et compléter le flux

Exemples

Contrôler la longueur de la chaine créée, et spécifier un caractère de remplissage.

 
TéléchargerCacher/Afficher le codeSélectionnez

Cet exemple impose une précision de 4 digits, une chaine de 10 caractères remplie avec des '0'.

Dans le même ordre d'idée, on pourra fixer la base de conversion …

 
TéléchargerCacher/Afficher le codeSélectionnez

Enfin ajouter du texte devant la conversion :

 
TéléchargerCacher/Afficher le codeSélectionnez

Ou encore une syntaxe plus aérée :

 
TéléchargerCacher/Afficher le codeSélectionnez

Vous noterez aussi l'utilisation optionnelle de la spécification du format de conversion (FormatNum).

On pourra compléter notre traitement par une fonction de conversion vers une CString ou string de la STL.

 
TéléchargerCacher/Afficher le codeSélectionnez

Utilisation :

 
TéléchargerCacher/Afficher le codeSélectionnez

III-D. Le Projet BOOST

BOOST fournit la bibliothèque Boost Format contenant une classe format permettant de réaliser les conversions, de plus elle autorise les spécifications de formats comme printf du C.

 
TéléchargerCacher/Afficher le codeSélectionnez

Boost Format peut être utilisée de plusieurs manières (exemples ci-dessus).

  • En remplacement d'arguments : chaque nombre entouré de % doit être remplacé en utilisant l'opérateur %.
  • En spécifiant un format : on utilisera alors les mêmes spécifications de format disponibles avec la fonction printf du C.

Je n'ai montré que deux aspects de cette bibliothèque qui possède de nombreuses possibilités.
N'hésitez pas à consulter la documentation en ligne avec les exemples associés : http://www.boost.org/libs/format/index.html.

L'intérêt de Boost Format dans un projet MFC peut sembler plus limité puisque nous disposons de la méthode Format de la classe Cstring.

Elle sera beaucoup plus utile dans des projets C++ standards, ou dans la réalisation de bibliothèques devant se passer des MFC.

III-E. Le C++/CLI

La conversion d'un type natif vers une string se fait très naturellement en C++/CLI :

 
TéléchargerCacher/Afficher le codeSélectionnez

d.ToString() mérite quelques explications :

  • en C++/CLI tous les types classiques sont des alias d'objet du framework .net, un int est donc un objet qui hérite de la classe System::Objet ;
  • celle-ci possède (entre autres) la méthode ToString() qui permet de convertir l'objet en chaine de caractère.

Autre avantage en C++/CLI les types classiques étant des objets ils sont initialisés à zéro.

Une autre possibilité intéressante qui est démontrée dans les dernières lignes de mon exemple :

  • un littéral numérique peut être considéré comme un objet s'il est entouré de parenthèses, alors la méthode ToString peut être appliquée…

Cette forme de conversion a l'avantage d'être simple, mais elle ne permet de contrôler le format de la chaine obtenue.

On utilisera alors String::Format.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
    using namespace System;    
    using namespace System::Globalization;
 
    Str=String::Format(CultureInfo::CurrentCulture,
        "(C) Currency: . . . . . . . . {0:C}\n" +
        "(D) Decimal:. . . . . . . . . {0,4:0}\n" +
        "(E) Scientific: . . . . . . . {1:E}\n" +
        "(F) Fixed point:. . . . . . . {1:F}\n" +
        "(G) General:. . . . . . . . . {0:G}\n" +
        "    (default):. . . . . . . . {0} (default = 'G')\n" +
        "(N) Number: . . . . . . . . . {0:N}\n" +
        "(P) Percent:. . . . . . . . . {1:P}\n" +
        "(R) Round-trip: . . . . . . . {1:R}\n" +
        "(X) Hexadecimal:. . . . . . . {0:X}\n",
        100, 3.14107);
    Console::WriteLine(Str);
    
    Str=String::Format( "3.14107->: {0:#,#.##;}", 3.14107);//3.14
    Console::WriteLine(Str);    
    Str=String::Format( "3.555->: {0:#,#.##;}", 3.555);//3.56
    Console::WriteLine(Str);    
    Str=String::Format( "100->: {0,5:00###}", 100 );//00100
    Console::WriteLine(Str);

Le format se décompose de la manière suivante : {index[,alignment][:formatString]}
Vous retrouverez l'ensemble des descripteurs de format dans la documentation MSDN.

IV. Conclusion

Il ne vous reste plus qu'à adopter la solution qui convient le mieux à vos besoins ou à la situation.