Faire une application multilangage avec C++Builder

Alexandre Pottiez

Cet article montre comment faire une application multilingue sous C++ Builder. Une méthode est utilisée ici, il en existe autant que de développeurs et ne peuvent donc pas toute être montrées.
Commentez cet article : Commentez

- Titre : Faire une application multilangage avec C++Builder

- Auteur : Alexandre Pottiez

- Parution : 23 janvier 2008

- Mise à jour : 27 juin 2020

- Licence : Ce document est issu de https://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- Avant-propos

- I. Présentation de la classe, de sa structure et de la structure du fichier XML

- I-A. La classe GestionLangue

- I-B. Le fichier XML

- I-B-1. Structure de la partie « Parametre »

- I-B-2. Structure de la partie « Langues »

- I-B-3. Structure de la partie « Labels »

- II. Modifier les propriétés d'un composant

- II-A. Les propriétés de type String

- II-B. Les propriétés d'autre type

- III. Parcourir les composants enfants

- IV. Initialisation de la classe

- V. Application de test

- V-A. Création de l'interface

- V-B. Création du fichier XML

- V-B-1. La partie « Parametre »

- V-B-2. La partie « Langues »

- V-B-3. La partie « Labels »

- V-C. Implémentation de la classe GestionLangue dans l'application

- Remerciements

- Téléchargement

Avant-propos

Pour cet article, j'utilise le format XML pour le fichier de traduction. J'utilise donc la bibliothèque tinyxml, avec laquelle vous pouvez vous familiariser grâce au tutoriel de khayyam90.

Dans cet article nous verrons étape par étape comment créer une classe qui permettra à nos applications de gérer le multilangage.

I - Présentation de la classe, de sa structure et de la structure du fichier XML

Le but de cet article est donc de créer une classe qui permettra, une fois ajoutée au projet de n'importe quelle application, de gérer le multilangage.
Bien entendu, il restera à créer le fichier XML correspondant à l'application, et éventuellement à ajouter la gestion de composant spécifique dans la classe qui ne sera pas vu dans cet article.

I-A - La classe GestionLangue

Ci-dessous, le fichier .h qui va me permettre d'expliquer la structure de la classe :

//---------------------------------------------------------------------------

#ifndef GestionLangueH
#define GestionLangueH

#include <string>
#include <hash_map>
#include <vcl.h>
//---------------------------------------------------------------------------

typedef void (__fastcall(__closure*ptrOnClick))(TObject *);

class GestionLangue
{
private:
std::hash_map<int,std::string> listLangues;
std::string  fichier;
int defaut;
char separateur;
std::vector<std::string> vectProp;
void ChangeComponent(TComponent*, int);
void __fastcall ChangeProp(TComponent*, String, String);
void ChangePropStringList(TComponent*, std::string, std::string);
public :
GestionLangue(){defaut = 0;};
~GestionLangue(){;};
void Init(TMenuItem*, std::string, ptrOnClick);
void Change(int);
};
#endif

Nous allons trouver dans cette classe plusieurs fonctions, deux qui seront connues à l'extérieur de la classe et qui serviront donc de relais entre l'application et la classe, et trois secondaires, qui seront appelées par les deux précédentes.

En plus de ces fonctions, nous trouvons trois attributs :

- listLangues : une hash_map qui contient le listing des langues contenues dans le fichier XML ;

- fichier : une string qui contient le chemin complet du fichier XML de traduction ;

- defaut : un entier qui contient l'index de la langue utilisée par défaut par la classe ;

- separateur : un char qui contient le caractère de séparation lors de liste de chaine de caractère ;

- vectProp : un vecteur qui contient la liste des propriétés pouvant être modifiées sur un composant.

Descriptif des fonctions :

- GestionLangue : Constructeur, initialise l'attribut defaut à 0 ;

- ~GestionLangue : Destructeur ;

- Init : Fonction d'initialisation de la classe; configure la liste des langues disponibles dans le fichier XML, remplit le menu de langue et charge la langue par défaut ;

- Change : Charge la langue demandée ;

- ChangeComponent : Fonction récursive qui charge le contenu du fichier XML pour le composant et s'appelle elle-même pour les composants enfants ;

- ChangeProp : Fonction modifiant les propriétés de type String ;

- ChangePropStringList : Fonction modifiant les propriétés de type StringList.

I-B - Le fichier XML

Le fichier XML contiendra trois parties, la partie « Parametre », dans laquelle seront contenus les paramètres tels que le caractère de séparation de chaine de caractère multiligne ou la propriété modifiable par la classe, la partie « Langues », qui définira les langues définies dans le fichier, et la partie « Labels », qui contiendra les labels des différents composants de l'application dans toutes les langues définies dans la première partie du fichier.
Enfin, la balise principale du fichier s'appellera ici « Traductions ».

I-B-1 - Structure de la partie « Parametre »

Dans cette partie nous trouverons un attribut « Separateur » qui contiendra le caractère de séparation pour les propriétés multilignes.
Cette partie contiendra également une balise « Proprietes », qui contiendra autant de balises « Propriete » que de propriétés modifiables par la classe de gestion.
Chaque balise « Propriete » contiendra le nom d'une propriété modifiable (exemple : « Caption », « Text », « Hint »…).

I-B-2 - Structure de la partie « Langues »

Dans cette partie nous trouverons une balise « Langue » pour chaque langue contenue dans le fichier.
Chaque balise contiendra trois attributs :

- index : Nom de la balise qui correspondra à la langue dans la partie « Labels » du fichier ;

- name : Nom qui sera donné au bouton de sélection de la langue ;

- default : Définit si cette langue est la langue par défaut, 1 si oui, 0 si non (si plusieurs langues sont définies par défaut, le programme prendra la dernière trouvée).

I-B-3 - Structure de la partie « Labels »

Dans cette partie nous trouverons une balise par composant, cette balise portera le nom du composant, elle contiendra un attribut type qui indiquera le type du composant (TButton, TLabel…), et contiendra également autant de balises que de propriétés modifiables en fonction de la langue.
Ces dernières balises, se nommeront du nom de la propriété à modifier, et contiendront elles-mêmes autant de balises que de langues définies dans la première partie.
Les balises langue se nommeront de la même manière que l'attribut index de la langue définie dans la première partie et contiendront le texte de la propriété à modifier, si cette propriété est de type StringList, les éléments seront séparés par le caractère '\'.

N'oubliez pas de mettre dans cette partie les boutons de choix de langues dont le nom est indiqué dans la partie Langues de ce fichier XML, à moins que le Caption de ces boutons ne doive pas être modifié.

II - Modifier les propriétés d'un composant

II-A - Les propriétés de type String

Le méthode la plus courte et générale pour modifier les propriétés des composants sous Builder est d'utiliser la fonction SetPropValue, cette fonction permet de modifier une propriété de type AnsiString/String grâce à un pointeur sur ce composant et le nom de la propriété à modifier :

TButton* button1;
...
SetPropValue(button1, "Caption", "Test");

Nous allons donc pouvoir grâce à cette fonction écrire une méthode que l'on appellera ChangeProp.
Nous allons passer à cette méthode plusieurs paramètres, le paramètre pControl est de type TComponent*, tous les composants héritent de cette classe, ce qui permet de passer à cette fonction le pointeur de n'importe quel composant.
Attention tout de même, certaines propriétés spécifiques à certaines classes de composants sont implémentées dans des classes de plus basses couches, et ne sont donc pas connues par la classe TComponent, nous verrons plus tard comment traiter ce cas.
Le paramètre Property_Name spécifie le nom de la propriété à modifier, et le paramètre AValue la valeur à attribuer à cette propriété.
Nous avons donc une fonction qui ressemble actuellement à ceci :

void __fastcall GestionLangue::ChangeProp(TComponent* pControl, String Property_Name, String AValue)
{
SetPropValue(pControl, Property_Name, AValue);  //Modification de la propriété
}

Le problème comme je l'ai indiqué précédemment est que la propriété à modifier n'existe pas forcément au niveau de la classe TComponent, nous allons donc vérifier son existence grâce à la fonction IsPublishedProp.
Cette fonction retourne la liste des propriétés contenue par le composant qui lui est passé en paramètre.
Suite à ce test et quelques autres, nous arrivons à une fonction sécurisée qui ne plantera pas le programme :

void __fastcall GestionLangue::ChangeProp(TComponent* pControl, String Property_Name, String AValue)
{
try
{
if (pControl != NULL)  //On teste si le pointeur sur le composant n'est pas NULL
{
//On teste si la propriété est accessible
if (IsPublishedProp(pControl, Property_Name))
{
try
{
SetPropValue(pControl, Property_Name, AValue);  //Modification de la propriété
}
catch(EPropertyError &propError){}
}
}
}
catch(...){}
}

II-B - Les propriétés d'autre type

Certaines propriétés n'étant pas du texte brut, la méthode SetPropValue ne leur convient pas et il n'existe malheureusement pas de fonction sous builder permettant de créer une méthode générique.
Nous allons voir dans cet exemple le cas des propriétés de type TStrings.

Pour traiter les propriétés de type TStrings, nous allons coder une fonction qui s'appellera ChangePropStrings.
La propriété principale utilisant les TStrings est la propriété Items, le problème de cette propriété est qu'elle ne figure pas dans une classe de base, mais directement dans les classes des composants.
Pour accéder à cette propriété nous devrons donc caster notre objet TComponent en la classe du composant qui lui convient, s’il y a beaucoup de types de composant différents cela peut vite prendre beaucoup de place, même si le code est basique, car il faut tester un par un les types de composants comportant une TStrings :

void GestionLangue::ChangePropStrings(TComponent* pControl, string AValue, string typeComponant)
{
TStrings * contenu;
//Récupération du pointeur du TStrings à modifier
if(typeComponant == "TComboBox")
contenu = (dynamic_cast <TComboBox*> (pControl))->Items;
else if(typeComponant == "TRadioGroup")
contenu = (dynamic_cast <TRadioGroup*> (pControl))->Items;
else
return;
}

Une fois que l'on a récupéré la TStrings, il n'y a plus qu'à remplacer l'ancien contenu par sa traduction.
Comme le Texte contenu dans un TStrings est divisé en plusieurs lignes, nous devons définir un caractère qui servira de séparateur pour les différentes lignes, ce caractère est défini dans la section Parametre du fichier XML.

void GestionLangue::ChangePropStrings(TComponent* pControl, string AValue, string typeComponant)
{
TStrings * contenu;
istringstream iss(AValue);
string ligne;
if(typeComponant == "TComboBox")
contenu = (dynamic_cast <TComboBox*> (pControl))->Items;
else if(typeComponant == "TRadioGroup")
contenu = (dynamic_cast <TRadioGroup*> (pControl))->Items;
else
return;
contenu->Clear();
while ( std::getline( iss, ligne, separateur ) )
{
contenu->Add(ligne.c_str());
}
}

III - Parcourir les composants enfants

Maintenant que nous avons nos fonctions pour modifier les propriétés des composants, il ne reste plus qu'a les appeler au bon moment et pour les bons composants.
Pour cela nous allons coder la fonction ChangeComponent. Cette fonction va changer les propriétés d'un composant et de ces composants enfants en fonction de la langue passée en paramètre.

void GestionLangue::ChangeComponent(TComponent* prmComposant, int prmLangue)
{
TiXmlElement *elem;
TiXmlElement *caption;
TiXmlDocument doc(fichier.c_str());
TiXmlHandle hdl(&doc);
string type;
unsigned int i;
int j;
if((doc.LoadFile()) && (prmComposant->Name != ""))
{
for(i=0;i<vectProp.size();i++)
{
elem = hdl.FirstChildElement().FirstChildElement("Labels").FirstChildElement(prmComposant->Name.c_str()).
FirstChildElement(vectProp[i].c_str()).FirstChildElement(listLangues[prmLangue].c_str()).
ToElement();
if(elem != NULL)
if(elem->GetText())
{
if(vectProp[i] == "Items")
{
if((caption = hdl.FirstChildElement().FirstChildElement("Labels").FirstChildElement(prmComposant->Name.c_str()).ToElement()) != NULL)
ChangePropStringList(prmComposant,elem->GetText(),caption->Attribute("type"));
}else
ChangeProp(prmComposant,vectProp[i].c_str(),elem->GetText());
}
}
for(j=0;j<prmComposant->ComponentCount;j++)
{
ChangeComponent(prmComposant->Components[j],prmLangue);
}
}
}

Dans le code ci-dessus, nous appelons bien les fonctions de changement de propriété pour tous les composants et leurs enfants, excepté un dernier cas particulier que sont les composants des menus.
En effet, les sous-menus, qui sont les mêmes composants que les menus, ne sont pas considérés comme des composants enfants proprement dits, nous devrons donc ajouter à cette fonction un petit bout de code spécifique pour modifier le texte de tous les sous-menus.

void GestionLangue::ChangeComponent(TComponent* prmComposant, int prmLangue)
{
TiXmlElement *elem;
TiXmlElement *caption;
TiXmlDocument doc(fichier.c_str());
TiXmlHandle hdl(&doc);
string type;
unsigned int i;
int j;
if((doc.LoadFile()) && (prmComposant->Name != ""))
{
for(i=0;i<vectProp.size();i++)
{
elem = hdl.FirstChildElement().FirstChildElement("Labels").FirstChildElement(prmComposant->Name.c_str()).
FirstChildElement(vectProp[i].c_str()).FirstChildElement(listLangues[prmLangue].c_str()).
ToElement();
if(elem != NULL)
if(elem->GetText())
{
if(vectProp[i] == "Items")
{
if((caption = hdl.FirstChildElement().FirstChildElement("Labels").FirstChildElement(prmComposant->Name.c_str()).ToElement()) != NULL)
ChangePropStringList(prmComposant,elem->GetText(),caption->Attribute("type"));
}else
ChangeProp(prmComposant,vectProp[i].c_str(),elem->GetText());
}
}
for(j=0;j<prmComposant->ComponentCount;j++)
{
ChangeComponent(prmComposant->Components[j],prmLangue);
}
if((elem = hdl.FirstChildElement().FirstChildElement("Labels").FirstChildElement(prmComposant->Name.c_str()).ToElement()) != NULL)
{
type = elem->Attribute("type");
if(type == "TMenuItem")
{
TMenuItem *pMenuItem = dynamic_cast< TMenuItem* > (prmComposant);
for(j=0;j<pMenuItem->Count;j++)
{
ChangeComponent(pMenuItem->Items[j],prmLangue);
}
}
}
}
}

Tous les composants enfants étant parcourus par la fonction ChangeComponent, il ne nous reste plus qu'à définir l'appel initial de cette fonction.
Cet appel sera fait par la fonction Change qui fera office d'interface entre l'application proprement dite et la classe de gestion de la langue.
Le composant parent à tous les composants d'une application est le composant TApplication. Ce composant définit toutes les propriétés de l'application (icône, chemin d'accès à l'exécutable…).
Ce composant TApplication a pour enfants toutes les Forms, modules de données… de l'application.

void GestionLangue::Change(int prmLangue)
{
int i;
for(i=0;i<Application->ComponentCount;i++)
{
ChangeComponent(Application->Components[i],prmLangue);
}
}

IV - Initialisation de la classe

L'initialisation de la classe est nécessaire pour permettre de définir les options qui seront utilisées lors des changements de langue.
Ces options sont par exemple le caractère séparateur pour les champs multilignes, la liste des propriétés modifiées avec ce fichier XML, la liste des langues contenues dans le fichier XML, et la langue par défaut.
Nous allons donc créer une fonction Init qui va récupérer ces informations dans le fichier XML.

void GestionLangue::Init(string prmFichier)
{
TiXmlElement *elem;
TiXmlElement *caption;
TiXmlDocument doc(prmFichier.c_str());
TiXmlHandle hdl(&doc);
string langueXml;
string nameXml;
string propriete;
bool defautXml;
int i = 0;
fichier = prmFichier;
if(doc.LoadFile())
{
elem = hdl.FirstChildElement().FirstChildElement("Parametre").ToElement();
if(elem != NULL)
separateur = elem->Attribute("Separateur")[0];
elem = hdl.FirstChildElement().FirstChildElement("Parametre").FirstChildElement("Proprietes").FirstChildElement("Propriete").ToElement();
while(elem != NULL)
{
propriete = elem->GetText() ;
vectProp.push_back(propriete);
elem = elem->NextSiblingElement("Propriete");
}
elem = hdl.FirstChildElement().FirstChildElement("Langues").FirstChildElement("Langue").ToElement();
listLangues.clear();
while(elem != NULL)
{
langueXml = elem->Attribute("index");
defautXml = (string(elem->Attribute("default")) == string("1"));
nameXml   = elem->Attribute("name");
listLangues[i] = langueXml;
if(defautXml == true)
defaut = i;
elem = elem->NextSiblingElement("Langue");
i++;
}
}
Change(defaut);
}

Cela fait, il nous reste une chose à initialiser, qui n'est pas la classe, mais qui est l'application en elle-même.
En effet, pour pouvoir modifier la langue, il faut des boutons permettant de choisir la langue, nous allons donc ajouter à la fonction Init deux paramètres, un pointeur sur le menu parent auquel nous allons ajouter le sous-menu langue, et un pointeur sur l'événement OnClick à affecter à ces boutons.
Pour permettre de passer un événement en paramètre de la fonction, nous allons déclarer le type ptrOnClick au début du .h de la classe.
Ce type correspond au type des fonctions événement OnClick de BCB.

Déclaration du type ptrOnClick

typedef void (__fastcall(__closure*ptrOnClick))(TObject *);

Fonction Init avec l'ajout des boutons de langue

void GestionLangue::Init(TMenuItem* prmMenu, string prmFichier, ptrOnClick prmFonctionClick)
{
TiXmlElement *elem;
TiXmlElement *caption;
TiXmlDocument doc(prmFichier.c_str());
TiXmlHandle hdl(&doc);
string langueXml;
string nameXml;
string propriete;
TMenuItem * menuLangue;
bool defautXml;
int i = 0;
fichier = prmFichier;
if(doc.LoadFile())
{
elem = hdl.FirstChildElement().FirstChildElement("Parametre").ToElement();
if(elem != NULL)
separateur = elem->Attribute("Separateur")[0];
elem = hdl.FirstChildElement().FirstChildElement("Parametre").FirstChildElement("Proprietes").FirstChildElement("Propriete").ToElement();
while(elem != NULL)
{
propriete = elem->GetText() ;
vectProp.push_back(propriete);
elem = elem->NextSiblingElement("Propriete");
}
elem = hdl.FirstChildElement().FirstChildElement("Langues").FirstChildElement("Langue").ToElement();
listLangues.clear();
while(elem != NULL)
{
langueXml = elem->Attribute("index");
defautXml = (string(elem->Attribute("default")) == string("1"));
nameXml   = elem->Attribute("name");
listLangues[i] = langueXml;
if(defautXml == true)
defaut = i;
menuLangue = new TMenuItem(prmMenu);
menuLangue->Name = nameXml.c_str();
menuLangue->Tag = i;
menuLangue->OnClick = (TNotifyEvent)prmFonctionClick;
prmMenu->Add(menuLangue);
elem = elem->NextSiblingElement("Langue");
i++;
}
}
Change(defaut);
}

V - Application de test

Maintenant que la classe de gestion de langues est finie, nous allons créer une petite application de test qui utilisera cette classe.

V-A - Création de l'interface

En premier lieu nous allons créer l'interface.
Dans l'exemple ci-dessous j'ai placé une diversité assez importante de composants, ce qui permet de rencontrer tous les cas de figure gérés et décrits précédemment lors de l'écriture de la classe GestionLangue :

- 2 TgroupBox ;

- 2 Tlabel ;

- 1 Tedit ;

- 1 TcomboBox ;

- 1 TcheckBox ;

- 1 TradioBox ;

- 1 Tbutton ;

- 1 TmainMenu.

Fenêtre exemple de l'application test

Le menu est composé d'un sous-menu, ce qui permet également de tester toutes les situations avec un menu.

Menu avec son sous-menu

Si vous ajoutez un composant sur votre fenêtre après avoir créé votre fichier XML, n'oubliez pas de l'ajouter dans celui-ci.

V-B - Création du fichier XML

Après avoir créé l'interface, nous allons créer le fichier XML, ce fichier est composé de trois parties.
Deux utiles à l'initialisation, et la dernière qui contient les différentes traductions.
La balise principale du fichier XML n'a pas d'importance, le programme est fait de manière à passer outre les modifications de cette bannière obligatoire en XML.

V-B-1 - La partie « Parametre »

Dans cet exemple nous allons utiliser le caractère '\' pour séparer les chaines multilignes et nous nous intéressons aux propriétés Caption, Text, Hint et Items. Suivant la structure indiquée dans le paragraphe I-2-1 de ce même article, la balise Parametre du fichier XML donnera alors ceci :

<Parametre Separateur="\">
<Proprietes>
<Propriete>Caption</Propriete>
<Propriete>Text</Propriete>
<Propriete>Hint</Propriete>
<Propriete>Items</Propriete>
</Proprietes>
</Parametre>

V-B-2 - La partie « Langues »

Nous allons avoir ici notre application en deux langues, le français et l'anglais, le français sera notre langue par défaut.
Les boutons créés pour accéder aux langues s'appelleront « FrenchLang » et « EnglishLang », ce qui nous donne une section Langues comme ci-dessous dans le fichier XML :

<Langues>
<Langue index="FR" name="FrenchLang" default="1" />
<Langue index="EN" name="EnglishLang" default="0" />
</Langues>

V-B-3 - La partie « Labels »

Voici la plus grosse partie du fichier XML, celle des labels. Il faut faire très attention à l'orthographe des noms des composants, car c'est ceci qui va permettre à la classe de retrouver le bon label.
Il faut également veiller à bien renseigner le type du composant, et bien mettre l'index de la langue comme indiqué dans la partie « Langues » du fichier.
Cette partie peut être longue et fastidieuse à écrire, mais il faut savoir que dans tous les cas, quelle que soit la solution adoptée, vous aurez toujours les labels à remplir.

Voici la partie « Labels » de notre fichier exemple.
Dans cet exemple, j'ai laissé les noms des composants que BCB met par défaut, mais ils peuvent être choisis par vos soins :

<Labels>
<FenetrePrincipale type="TFenetrePrincipale">
<Caption>
<FR>Application multilangage</FR>
<EN>Application multilanguage</EN>
</Caption>
</FenetrePrincipale>
<GroupBox1 type="TGroupBox">
<Caption>
<FR>Premier groupe d'objet</FR>
<EN>First group of object</EN>
</Caption>
</GroupBox1>
<CheckBox1 type="TCheckBox">
<Caption>
<FR>Affichage de la case à cocher</FR>
<EN>Display of the checkbox</EN>
</Caption>
</CheckBox1>
<ComboBox1 type="TComboBox">
<Items>
<FR>Choix 1\Choix 2\Choix 3\Choix 4</FR>
<EN>Choice 1\Choice 2\Choice 3\Choice 4</EN>
</Items>
</ComboBox1>
<Label1 type="TLabel">
<Caption>
<FR>Affichage 1</FR>
<EN>Results 1</EN>
</Caption>
</Label1>
<Label2 type="TLabel">
<Caption>
<FR>Affichage 2</FR>
<EN>Results 2</EN>
</Caption>
</Label2>
<GroupBox2 type="TGroupBox">
<Caption>
<FR>Deuxième groupe d'objet</FR>
<EN>Second group of object</EN>
</Caption>
</GroupBox2>
<Button1 type="TButton">
<Caption>
<FR>Bouton</FR>
<EN>Button</EN>
</Caption>
</Button1>
<RadioGroup1 type="TRadioGroup">
<Items>
<FR>Premier choix\Deuxième choix\Troisième choix\Quatrième choix</FR>
<EN>First choice\Second Choice\Third choice\Fourth choice</EN>
</Items>
<Caption>
<FR>Groupe de choix</FR>
<EN>Choice group</EN>
</Caption>
</RadioGroup1>
<Langue1 type="TMenuItem">
<Caption>
<FR>Langue</FR>
<EN>Language</EN>
</Caption>
</Langue1>
<FrenchLang type="TMenuItem">
<Caption>
<FR>Français</FR>
<EN>French</EN>
</Caption>
</FrenchLang>
<EnglishLang type="TMenuItem">
<Caption>
<FR>Anglais</FR>
<EN>English</EN>
</Caption>
</EnglishLang>
<sousMenu1 type="TMenuItem">
<Caption>
<FR>Sous menu</FR>
<EN>Sub menu</EN>
</Caption>
</sousMenu1>
<sousMenu2 type="TMenuItem">
<Caption>
<FR>Sous-sous menu</FR>
<EN>Sub-sub menu</EN>
</Caption>
</sousMenu2>
</Labels>

V-C - Implémentation de la classe GestionLangue dans l'application

Rien de très compliqué dans ce paragraphe :)
Nous allons d'abord créer dans la classe de la fenêtre principale l'événement OnClick qui sera attribué aux boutons du menu de sélection des langues.
L'id de la langue est toujours enregistré dans le Tag du bouton, nous allons donc le récupérer grâce au paramètre Sender qui se trouve dans chaque fonction événement.
Le paramètre Sender contient l'adresse du composant appelant cet événement.
Nous nous retrouvons donc avec un événement OnClick de ce genre :

void __fastcall TFenetrePrincipale::LangueClick(TObject *Sender)
{
langue->Change(((TMenuItem*)Sender)->Tag);
}

langue est un pointeur sur la classe GestionLangue définie dans le .h de la classe comme ci-dessous :

private:    // Déclarations de l'utilisateur
GestionLangue * langue;

N'oubliez pas d'ajouter la déclaration de la fonction dans le .h de cette classe.

Ce code doit toujours figurer dans l'événement pour permettre à la classe de modifier les labels en fonction de la langue sélectionnée.

Vous pouvez cependant ajouter un bout de code si vous souhaitez un traitement spécifique supplémentaire.

Une fois l'événement codé, nous allons ajouter la classe GestionLangue dans le projet de l'application, et ajouter l'initialisation de celle-ci dans le constructeur de la fenêtre principale de l'application.
Nous allons donc ici instancier le pointeur de la classe GestionLangue, et appeler la fonction Init, en lui passant le composant TMainMenu qui doit contenir les boutons de sélection de la langue, le chemin du fichier XML et l'adresse de l'événement OnClick défini ci-dessus :

__fastcall TFenetrePrincipale::TFenetrePrincipale(TComponent* Owner)
: TForm(Owner)
{
AnsiString file ;
file = Application->ExeName + "\\..\\label.xml";
langue = new GestionLangue;
langue->Init(FenetrePrincipale->Langue1,file.c_str(),(ptrOnClick)&LangueClick);
}

N'oubliez pas d'ajouter les fichiers de tinyXml à votre projet, en effet, la classe GestionLangue utilisant cette librairie, si vous ne l'incluez pas à votre projet, celui-ci ne compilera pas.

Remerciements

Je remercie Sunchaser, blondelle, Crayon, et Gilles Loïse pour leur aide dans mes recherches d'optimisation du code, et bandit boy et jeepnc pour leurs relectures.

Téléchargement

Sources Borland Developper Studio 2006 : ici.
Sources C++ Builder 6 : ici.