IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)


Février 2002

Remarques de développement avec C++ Builder 5

Partie III - par Gilles Louise


46. La classe TEdit
47. La classe TList
48. Conditions sur points d'arrêt
49. Série de touches fréquentes
50. Les menus variables
51. Gestion de fenêtres-enfants
52. Le composant ScrollBar
53. Le composant PageControl
54. Les classes TControlBar, TToolBar, TToolButton et PopupMenu
55. L'éditeur d'images
56. La classe TImageList
57. Les applications MDI
58. Le composant TActionList
59. La fonction GetCursorPos
60. Initialisation d'une application
61. Gestion des exceptions
62. La méthode StretchDraw
63. Les propriétés ClientWidth et ClientHeight
64. Création d'un exécutable indépendant


Retour à la partie I
Retour à la partie II



46. La classe TEdit

L'objet TEdit vous permet entre autres de dialoguer avec l'utilisateur. Par exemple, vous pouvez l'inviter par ce petit objet d'entrez une chaîne de caractères. Dans ce cas on intercepte la chaîne saisie après que l'utilisateur a appuyé sur la touche Entrée (code ASCII 13). Par exemple, sélectionnez l'objet Edit, il se trouve dans l'onglet standard de la palette de composants (petit logo montrant les deux lettres "ab"). Positionnez-le sur la fiche principale Form1. Dans l'inspecteur d'objets, sélectionnez "événements" et cliquez sur "OnKeyPress", C++ Builder vous crée la méthode correspondante. Saisissez la ligne suivante :

void __fastcall TForm1::Edit1KeyPress(TObject *Sender, char &Key)
{
if(Key==13) Application->MessageBox(Edit1->Text.c_str(),"OK",MB_OK);
}

Cette instruction signifie que quand l'utilisateur a appuyé sur "Entrée", on affiche le contenu de l'objet Edit dans un MessageBox. Vous remarquez qu'on entre dans cette méthode avec en main la variable Key qui est précisément le code ASCII de la touche appuyée par l'utilisateur. Tant que cette touche n'est pas Entrée (code ASCII 13), on ne fait rien, on sort immédiatement de la méthode. Notez que si vous voulez connaître les codes ASCII des touches du clavier, il vous suffit de les afficher, par exemple :

void __fastcall TForm1::Edit1KeyPress(TObject *Sender, char &Key)
{
Application->MessageBox(IntToStr(Key).c_str(),"OK",MB_OK);
}

On affiche ici le code ASCII de la touche appuyée dans un MessageBox à chaque fois qu'une touche est appuyée.

Remarquez le flag MB_OK qu'on utilise souvent à titre d'exemple dans nos MessageBox. C'est le plus courant, mais il y en a bien d'autres : MB_ABORTRETRYIGNORE, MB_OKCANCEL, MB_RETRYCANCEL, MB_YESNO, MB_YESNOCANCEL, etc. Positionnez le curseur sur le mot MessageBox et faites F1 (aide en ligne) pour plus d'informations.

Il est très souvent utile de n'accepter dans un TEdit qu'une certaine catégorie de caractères. Par exemple, si le TEdit représente un nombre entier, il faudra n'accepter que les chiffres (codes ascii compris entre 48 et 57). Pour une meilleure utilisation, on accepte aussi toujours la touche "back space" (code ascii \b) qui permet d'effacer le dernier caractère. Donc, dans le gestionnaire d'événement OnKeyPress, on annulera le caractère envoyé si ce n'est ni un chiffre ni la touche d'effacement (touche qui se trouve au-dessus de la touche Entrée et qui représente une flèche vers la gauche).

void __fastcall TForm1::Edit1KeyPress(TObject *Sender, char &Key)
{
if((Key<48||Key>57)&&Key!='\b') Key=NULL;
}

Vous voyez que la variable Key est envoyée comme référence (grâce à l'opérateur & qui exprime que la variable Key est envoyée non pas en tant que valeur mais en tant qu'adresse, cela revient à dire que son contenu est comme renvoyé au programme appelant dans le cas où Key serait modifiée à l'intérieur de la méthode). Cela permet à l'utilisateur de contredire l'événement puisque si Key (c'est-à-dire la touche appuyée) n'est ni un chiffre ni la touche d'effacement alors Key est annulée (Key=NULL;), ce qui a pour effet d'ignorer complètement la touche appuyée par l'utilisateur. Ainsi, si l'utilisateur appuie sur une lettre, celle-ci ne sera pas affichée. On s'assure ainsi qu'il n'y aura que des chiffres dans le TEdit. Notez la propriété MaxLength qui permet d'exprimer le nombre de caractères maximum qui seront acceptés dans le TEdit. Notez aussi que pour une valeur entière, on évitera d'afficher un zéro non significatif au début du TEdit. Ceci se programmera à l'événement OnChange.

void __fastcall TForm1::Edit1Change(TObject *Sender)
{
int n;
if((n=Edit1->Text.Length())!=0)
if(Edit1->Text[1]==48)
Edit1->Text=Edit1->Text.SubString(2,n-1);
}

On teste d'abord la longueur du Tedit et on en profite pour mettre cette longueur dans l'entier n. Si cet entier est non nul, on teste le premier caractère du Tedit c'est-à-dire le premier caractère de l'AnsiString Edit1->Text (n'oubliez pas que l'indice des AnsiString commence à 1). Si donc ce premier caractère est égal à 48 c'est-à-dire le code ascii du caractère zéro (if(Edit1->Text[1]==48)), on supprime ce caractère en chargeant dans Edit1->Text son propre contenu à partir du deuxième caractère sur une longueur de n-1, ce qui a pour effet de supprimer le premier caractère si celui-ci est zéro.

47. La classe TList

C'est une liste de pointeurs. On la déclare par l'opérateur new :

TList* TL = new TList;

TL étant ainsi une instance de type TList, si P est un pointeur de type char*, on ajoutera le pointeur P à la liste en écrivant :

TL->Add((char*)P);

Les pointeurs de TList étant du type void* c'est-à-dire finalement que ce sont des pointeurs indéterminés, il faut donc convertir ce pointeur en char* dans notre cas ce pourquoi il faut écrire char* entre parenthèses pour effectuer une conversion de void* en char*. Si vous voulez déclarez une zone mémoire via malloc et ajouter un pointeur vers cette zone, on écrira par exemple :

TL->Add((char*)malloc(bloc)); // où bloc est une constante arbitraire

Le ième pointeur de la liste est donné par l'expression TL->Items[i], si ce pointeur est un pointeur de type char*, le jème caractère de ce pointeur sera donné par l'expression ((char*)TL->Items[i])[j] où l'on écrit clairement que TL->Items[i] est du type char*. Pour supprimer le ième pointeur de la liste, on écrira simplement ;

TL->Delete(i);

Voici un petit exemple complet. On déclare la constante bloc à 1000 pour un premier malloc. On déclare NbBlocs à 40. On déclare ensuite TL du type Tlist et on déclare 10 zones mémoire via malloc de bloc/2 octets et à chaque fois on ajoute le pointeur à la liste. On a donc une liste de 10 pointeurs chacun pointant une zone de bloc/2 octets. Ensuite, On va réallouer la mémoire pour chacun de ces pointeurs mais on va le faire en ajoutant à chaque fois un pointeur à la liste. On a donc après cette boucle 20 pointeurs, les pointeurs de 0 à 9 contiennent maintenant les anciennes adresses devenues inutiles et les pointeurs de 10 à 19 les nouvelles suite au realloc. On supprime alors les 10 premières adresses en supprimant dix fois le pointeur d'indice 0. Au bout de 10 fois, on n'a plus que les pointeurs d'indice 0 à 9 qui sont les pointeurs nouveaux suite au realloc. On fait cette réallocation NbBlocs fois en allouant à chaque fois un bloc de plus c'est-à-dire (j+1)*bloc, et ce pour éprouver les realloc et vérifier le bon fonctionnement de tous les pointeurs. Au bout du compte, on a 10 pointeurs sur 10 zones de NbBlocs*bloc octets. On remplit toutes ces zones, le fait de pouvoir y accéder est la preuve que c'est parfaitement correct. Ensuite on libère la mémoire de ces dix pointeurs et on efface l'objet TL. Copiez-collez ce code entre les parenthèses {} du tout premier constructeur TForm1 en entrant dans C++Builder et faites F9 pour exécuter, vous constatez que ça marche parfaitement car si l'accès n'était pas autorisé, on aurait eu nécessairement un accès violation, ce qui n'est pas le cas ici.

const bloc =1000,NbBlocs=40;
int i,j;
TList* TL = new TList;
for(i=0;i<10;i++) TL->Add((char*)malloc(bloc/2));
for(j=0;j<NbBlocs;j++)
{
/* On réalloue les dix pointeurs avec une zone de (j+1)*bloc octets
comme j va de 0 à NbBlocs-1, la dernière allocation allouera
NbBlocs*bloc octets */
for(i=0;i<10;i++) TL->Add((char*)realloc(TL->Items[i],(j+1)*bloc));
// On supprime dix fois le pointeur d'indice 0
for(i=0;i<10;i++) TL->Delete(0);
}

/* À ce stade on a 10 pointeurs de NbBlocs*bloc octets, on remplit
arbitrairement toute la mémoire allouée à titre de vérification d'accès */
for(i=0;i<10;i++) for(j=0;j<NbBlocs*bloc;j++) ((char*)TL->Items[i])[j]=0;

// On libère la mémoire pour chacun des pointeurs
for(i=0;i<10;i++) free(TL->Items[i]);

// On détruit l'objet TL
delete TL;

48. Conditions sur points d'arrêt

On donne un point d'arrêt via la touche F5, la ligne pointée par le curseur devient rouge, le programme s'arrêtera à cet endroit au moment de l'exécution et vous pourrez vous lancer dans un pas à pas (notamment via la touche F8, voir menu "Exécuter" pour plus d'informations). Maintenant, En cliquant sur le bouton droit de la souris pointant une ligne marqué "point d'arrêt" (ligne rouge), vous obtenez un menu déroulant qui vous donne la possibilité d'affiner ce point d'arrêt, c'est l'option "Propriétés du point d'arrêt". En choisissant cette option, une fenêtre apparaît et vous indiquez une condition d'arrêt. Si cette condition est réalisée, l'exécution s'arrêtera et vous aurez la main pour poursuivre en pas à pas. Par exemple, si vous voulez arrêter l'exécution si une certaine variable D=2, vous écrirez dans le formulaire présenté : D==2. Il faut juste se souvenir que s'il y a plusieurs conditions, elles doivent être isolées par des parenthèses par exemple (D1==2)&(D2==3).

49. Série de touches fréquentes

Pour accéder à vos fichiers réguliers, utilisez les touches du clavier. Ainsi, si vous travaillez toujours sur le même projet (ce qui est le cas de projets de longue haleine), vous l'ouvrez par Alt+F+R+0 (Alt active le menu général, F pour fichier, R pour réouvrir et 0 pour le dernier projet ouvert). Si vous avez plusieurs fichiers cpp à ouvrir, faites Alt+V+G, là cliquez "+" et choisissez le cpp voulu (Alt pour activer le menu, V pour Voir, G pour gestionnaire). Ou encore Alt+V+U qui appelle les unités, c'est d'ailleurs peut-être un meilleur choix puisqu'on peut par ce moyen charger plusieurs fichiers en même temps en indiquant un premier fichier puis un second tout en appuyant sur la touche MAJ ce qui provoque la sélection de toute la zone.

50. Les menus variables

Pour créer des menus variables, vous avez grosso modo deux possibilités : soit vous prédéclarez vos options à l'avance et à la main à la conception de votre application, soit vous créez ces options par programme. Si vous connaissez à l'avance le maximum d'emplacements à allouer durant l'exécution, la première solution est alors préférable. C'est le cas par exemple quand vous proposez à l'utilisateur les plus récents fichiers à réouvrir, vous les rajoutez dans le menu "Fichier" qui est en général la première option du menu principal d'une application, vous décidez par exemple de proposer les dix fichiers les plus récents.

Dans ce cas, vous prédéclarez ces options dans votre menu en sélectionnant dans votre fiche Form1 le menu principal, vous cliquez sur le bouton droit de la souris, vous choisissez "concepteur de menus". Là le concepteur de menu s'affiche et sélectionne pour vous la première option "Fichier", celle précisément où l'on va rajouter des options. Cliquez la case la plus en bas pour la sélectionner, rajoutez un séparateur (dans Caption saisissez le signe moins, c'est le code pour créer une fausse option qui servira de ligne séparatrice dans votre menu) puis sélectionnez à nouveau la dernière case et saisissez n'importe quoi dans Caption par exemple la lettre a puis recommencez l'opération par exemple dix fois pour réserver dix options en proposant à Caption les lettres de a à j. En réalité, ce qu'on entre dans Caption n'a aucune importance puisque cela sera renseigné par programme, c'est simplement une astuce pour réserver l'espace mémoire associé à ces options. On a donc un menu "Fichier" avec les options classiques puis à un moment donné une ligne séparatrice suivie de dix options qui contiendront jusqu'à dix noms de fichiers les plus récemment utilisés. Ici je conseille un petit truc, c'est d'initialiser le tag de la première option à 1, celle que nous avons déclarée avec Caption=lettre a. Les tags sont initialisés à 0 à chaque création d'option de menu, il suffit de mettre à 1 celui-là. Cela va nous servir dans le programme à repérer la première option destinée à recevoir des noms de fichiers récents. Si on ne prend pas cette petite précaution, il faudrait alors mémoriser l'indice de la première option variable (sachant qu'on part de 0 c'est-à-dire que Fichier1->Items[0] est la première option du menu Fichier que C++Builder a nommé Fichier1). Mais de ce fait, il faudrait modifier le source si par la suite vous insérez une option au menu Fichier avant ces noms car tous les indices se décalent d'une unité. En utilisant le tag de la première option des noms de fichiers récents et en l'initialisant à 1, on ne changera rien même s'il vous insérez des options dans votre menu. Cherchons dans ces conditions le numéro d'options correspondant à la première option variable :

ok=true;
Prem=-1;
while(ok) if(Fichier1->Items[++Prem]->Tag==1) ok=false;

où ok est un booléen et Prem un entier. Après exécution de ce code, Prem contient l'indice de la première option variable du menu repéré par le fait que son tag est à 1, Fichier1->Items[Prem] est donc cette première option.

Ensuite, à l'initialisation, il faut rendre invisibles toutes les options et donc inaccessibles d'où :

for(i=Prem;i<Prem+MaxFic;i++) Fichier1->Items[i]-> Visible=false;

où MaxFic est le nombre maximum de fichiers autorisés à venir s'afficher dans le menu variable. Prem étant le premier indice, Prem+MaxFic-1 sera le dernier, pour tous ces indices le champ Visible sera mis à false, ce qui désactive l'espace entier réservé à ces noms de fichiers.

Imaginons maintenant que dans une TStringList, vous ayez les paramètres utilisateurs et donc notamment les plus récents fichiers utilisés, vous allez alors renseigner le menu variable en fonction de cette TStringList que j'appelle ici AllParam (tous les paramètres de votre application). Je suppose ici que toute ligne de la TStringList commençant par CodeFic="Fic " indique pour les caractères suivants le nom du fichier à inscrire au menu.

j=Prem;
i=0;
ok=true;
while(ok)
{
LigMen=AllParam->Strings[i++];
if(LigMen.SubString(1,4)==CodeFic)
{
LG=strlen(LigMen.c_str());
LigMen=IntToStr(i-1)+" "+LigMen.SubString(5,LG);
Fichier1->Items[j]-> Visible=true;
Fichier1->Items[j]-> Caption=LigMen;
Fichier1->Items[j++]-> OnClick=HistoClick;
}
if(j==MaxFic+Prem|i==(unsigned int)AllParam->Count) ok=false;
}

L'entier i pointe ici la TStringList et j les options du menu, j est donc initialisé à Prem. Pour chaque ligne de la TStingList pointée par i, je lis cette ligne dans un AnsiString LigMen (LigMen=AllParam->Strings[i++]). Si les 4 premiers caractères sont CodeFic, cela signifie (dans la codification arbitraire que j'ai proposée) qu'elle contient bien un nom de fichier à insérer au menu variable. Ici (petite astuce), j'extrais ce nom mais je rajoute au début de la chaîne son numéro d'ordre à partir de 0, ce qui fait qu'on pourra accéder à cette option en utilisant les dix chiffres du clavier de 0 à 9 car C++Builder cherche automatique un caractère de raccourci dans la propriété Caption, ici il sera tout trouvé, ce sera le chiffre ajouté en début de chaîne. Pour ce faire je calcule d'abord le nombre de caractères de la chaîne (LG=strlen(LigMen.c_str())) puis je concatène son numéro et le nom du fichier commençant à partir du 5ème caractère (LigMen=IntToStr(i-1)+" "+LigMen.SubString(5,LG)), LigMen (ligne de menu) contient maintenant ce qu'il faudra afficher dans le menu variable. Caption prend donc la valeur LigMen et l'option sera visible. Ensuite il faut indiquer au programme l'adresse de la fonction au moment où l'utilisateur choisira cette option. Ici pour toutes les options du menu, on va à la même adresse à savoir à HistoClick (Fichier1->Items[j++]-> OnClick=HistoClick). HistoClick (nom arbitraire que je choisis) est donc une fonction membre de la classe TForm1 créée par C++Builder, il ne faudra donc pas oublier de déclarer cette fonction dans le "point h" associé.

void __fastcall HistoClick(TObject *Sender);

Sinon, si vous ne faites pas cette déclaration, le compilateur vous dira [C++ Erreur] MonProj.cpp(922): E2316 '_fastcall TForm1::HistoClick(TObject *)' n'est pas un membre de 'TForm1', [C++ Erreur] MonProj.cpp(971): E2451 Symbole 'HistoClick' non défini.

Dans notre exemple, les MaxFic options rajoutées vont aller au même endroit en cas de sélection par l'utilisateur. En effet, il s'agit en fait de la même fonction à savoir traiter le fichier sélectionné. Pour savoir quelle option a été sélectionnée, on utilise la syntaxe TMenuItem *ClickedItem = dynamic_cast<TMenuItem *>(Sender);

À partir de là, ClickedItem est l'option choisie, j'en extrais de Caption le nom du fichier invoqué en lisant d'abord cette option (Choix=ClickedItem->Caption), en calculant sa longueur (LG=strlen(Choix.c_str())), en extrayant à partir de 4ème caractère car bien sûr le chiffre qu'on a nous-même ajouté à Caption n'appartient pas au nom du fichier (Choix=Choix.SubString(4,LG)). Ici je suppose qu'on va traiter le fichier par une fonction que j'appelle TraiteFic, laquelle renvoie un numéro d'erreur en cas d'erreur (sinon 0 si pas d'erreur). Je suppose que tous les messages d'erreur sont dans une TStringList que j'appelle ici MErr, j'affiche donc le message d'erreur correspondant dans ce cas. Voici donc comment pourrait se présenter la fonction :

void __fastcall TForm1::HistoClick(TObject *Sender)
{
int LG,erreur;
AnsiString Choix;
TMenuItem *ClickedItem = dynamic_cast<TMenuItem *>(Sender);

Choix=ClickedItem->Caption;
LG=strlen(Choix.c_str());
Choix=Choix.SubString(4,LG);

// Traitement du fichier
if((erreur=TraiteFic(Choix))!=0)
Application->MessageBox(MErr->Strings[erreur].c_str(),NULL,MB_ICONSTOP);
}

Notez que comme TraiteFic est appelé à modifier le menu, il sera obligatoire pour y avoir accès par programme de le déclarer comme fonction membre de TForm1 sinon vous n'aurez pas accès aux éléments. En effet, si l'utilisateur sélectionne un mauvais fichier, TraiteFic ne fera rien mais s'il en choisit un bon, ce qui sera le cas le plus fréquent, ce fichier passera en première position dans le menu variable étant le plus récemment utilisé, il faudra donc bien avoir accès au menu. Voici comment cela pourrait se passer. MemorFic reçoit le nom du fichier à mémoriser (i.e. le fichier sélectionné par l'utilisateur).

/* Mémorisation du nom du fichier dans les paramètres */
void MemorFic(AnsiString N)
{
int i=0;
bool ok=true;
AnsiString Fic=CodeFic+N;

/* Recherche et suppression s'il existe de ce nom de fichier dans la liste */
while(ok)
{
if(i==AllParam->Count) ok=false;
else
{
if(Fic==AllParam->Strings[i++])
{
/* Si trouvé on le supprime et on l'insère en première position */
AllParam->Delete(--i);
AllParam->Insert(0,Fic);
return;
}
}
}//fin du while

/* Ici on n'a pas trouvé ce nom, il est donc nouveau
Si le nb de codes "Fic " (CodeFic) est inférieur maxi on l'insère
sinon on vire celui dont l'indice est le plus élévé */
EtudeMaxFic();

/* On peut maintenant insérer ce nouveau nom, on est sûr ici
de ne jamais dépasser la maximum autorisé */
AllParam->Insert(0,Fic);
}
//---------------------------------
/* On compte le nombre de noms de fichiers mémorisés. S'il n'est pas
égal au maximum, on sort sans rien faire car on peut en ajouter un
nouveau. Sinon, on vire celui dont l'indice est le plus élévé, ce qui nous
permettra d'en insérer un plus récent */
void EtudeMaxFic(void)
{
int i,N=0;

/* On compte dans N le nombre de noms de fichiers */
for(i=0;i<AllParam->Count;i++)
if(AllParam->Strings[i].SubString(1,4)==CodeFic) N++;

/* Rien si ce n'est pas le max */
if(N!=MaxFic) return;

/* sinon on vire celui dont l'indice est le plus élevé */
i=AllParam->Count;
while(true)
{
if(AllParam->Strings[--i].SubString(1,4)==CodeFic)
{
AllParam->Delete(i);
return;
}
}
}

À ce stade nous avons une TStringList qui contient le nouvel ordre des fichiers, il suffit de les faire apparaître dans le menu variable ainsi que cela a été expliqué au début de cet alinéa.

Si maintenant vous décidez de programmer les options nouvelles par programme (et non de les prédéclarer), il faut utiliser d'autres syntaxes. Par exemple, rajoutons une option au moment de la création de la fenêtre principale :

void __fastcall TForm1::FormCreate(TObject *Sender)
{
TMenuItem *Item1 = NewItem("Essai", TextToShortCut("Ctrl+N"), false, true, Action, 0, "Item1");
Fichier1->Add(Item1);
}

Ici on a ajouté l'option Essai et elle a été associée la méthode Action à déclarer comme membre de TForm1.

void __fastcall TForm1::Action(TObject *Sender)
{
// Code associé à l'option "Essai" du menu.
}

Autre syntaxe, on déclare un pointeur sur un TmenuItem (TMenuItem* NewItem), on déclare une instance via l'opérateur new (NewItem=new TMenuItem(Fichier1)), à partir de là on accède à tous les champs de cet item nouveau via NewItem-> notamment pour Caption (nom qui apparaîtra dans le menu) et pour OnClick (action à exécuter en cas de sélection). Déclarons trois options par programme dans le menu Fichier1 associées respectivement à Action1, Action2 et Action3.

__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
TMenuItem* NewItem;

// on déclare en plus une ligne séparatrice dans le menu
NewItem=new TMenuItem(Fichier1);
NewItem->Caption="-";
Fichier1->Add(NewItem);

// Essai1 associé à Action1
NewItem=new TMenuItem(Fichier1);
NewItem->Caption="Essai1";
NewItem->OnClick=Action1;
Fichier1->Add(NewItem);

// Essai2 associé à Action2
NewItem=new TMenuItem(Fichier1);
NewItem->Caption="Essai2";
NewItem->OnClick=Action2;
Fichier1->Add(NewItem);

// Essai3 associé à Action3
NewItem=new TMenuItem(Fichier1);
NewItem->Caption="Essai3";
NewItem->OnClick=Action3;
Fichier1->Add(NewItem);
}

Les trois actions doivent être membre de TForm1 et donc déclarées dans le "point h" associé.

void __fastcall Action1(TObject *Sender);
void __fastcall Action2(TObject *Sender);
void __fastcall Action3(TObject *Sender);

Vous n'avez plus qu'à écrire le code correspondant à ces actions.

void __fastcall TForm1::Action1(TObject *Sender)
{
// Code associé à l'option "Essai1" du menu.
}

void __fastcall TForm1::Action2(TObject *Sender)
{
// Code associé à l'option "Essai2" du menu.
}

void __fastcall TForm1::Action3(TObject *Sender)
{
// Code associé à l'option "Essai3" du menu.
}

À noter cet avertissement de la documentation : "L'élément renvoyé par NewItem n'a pas de propriétaire. Vous êtes responsable de la libération de sa mémoire lorsqu'elle n'est plus nécessaire. Les méthodes Delete et Remove de TMenuItem ne libèrent pas la mémoire."

C'est donc à vous de libérer la mémoire en temps voulu et aussi (pour faire bien les choses) au sortir de l'application. Par exemple, avant d'ajouter une occurrence au menu, mémorisez son indice :

NumOp=Fichier1->Count;

Dans l'exemple précédent, cette ligne est à insérer juste après avoir inséré la ligne séparatrice. Continuons l'exemple précédent et libérons la mémoire correspondant aux trois options rajoutées, on écrira :

for(i=0;i<3;i++) delete Fichier1->Items[NumOp];

Observez ce fait amusant qu'on efface toujours l'option d'indice NumOp. En effet, après avoir supprimé celle indicée par NumOp, tout le monde remonte en sorte que la suivante à libérer a toujours pour indice NumOp (numéro d'option) et ainsi de suite.

51. Gestion de fenêtres-enfants

Quand un certain nombre (non connu à l'avance) de fenêtres-enfants sont susceptibles de s'ouvrir et de se fermer au cours de l'exécution d'une application, il faut gérer par programme les créations de ces fenêtres et de ses composants. Pour étudier les syntaxes de déclaration, nous partons d'un projet vierge et nous posons simplement un bouton dans la fenêtre principale, Form1 par défaut. L'idée est d'ouvrir une fenêtre-enfant à chaque click sur le bouton. On doit refuser l'ouverture de cette fenêtre si le nombre maximal est atteint. On inclura dans chaque fenêtre nouvelle un composant PaintBox. Dans les variables générales, on déclare ceci :

const unsigned int NbFMax=3, PBW=10000,PBH=10000, FenH=300,FenL=400;
unsigned int NbFE=0;
bool FE[NbFMax];
TForm *Form[NbFMax];
TPaintBox *PB[NbFMax];

NbFMax est un nombre maximal de fenêtres pouvant être ouvertes simultanément à un moment donné. PBW et PBH (Paint Box Width et Paint Box Height) sont les dimensions de la PaintBox que l'on va créer à chaque création de fenêtre. FenH et FenL sont les dimensions d'une fenêtre-enfant à la création. On initialise NbFE à 0, c'est le nombre de fenêtres-enfants ouvertes pour l'instant, aucune au début de l'exécution du programme. Ensuite on déclare un tableau de NbFMax booléens. Tous les éléments de ce tableau seront initialisés à false à la construction de Form1. À chaque création de fenêtre (obtenue dans notre exemple par le click du composant bouton que l'on a posé sur notre fenêtre principale), on cherchera le premier élément à false de ce tableau puis on mettra cet élément à true. Dans ces conditions, si FE[i] est true, la fenêtre numéro i est présente dans Form1 sinon elle n'existe pas. On déclare ensuite NbFMax pointeurs de TForm et NbFMax pointeurs de TPaintBox.

Comme nous le disions, on initialise à false tous les éléments du tableau FE au moment de la construction de TForm1. C'est d'ailleurs ici qu'on initialise les variables générales d'un programme. On pourrait le faire aussi à l'événement OnCreate de la fenêtre principale mais comme C++Builder propose de lui-même la méthode Form1::Form1, on l'utilise logiquement.

__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
for(int i=0;i<NbFMax;i++) FE[i]=false;
}

Voici maintenant ce qui se passe quand l'utilisateur clique sur le bouton i.e. comment nous allons créer la fenêtre-enfant demandée.

void __fastcall TForm1::Button1Click(TObject *Sender)
{
unsigned int N=-1;
if(NbFE==NbFMax)
{
Application->MessageBoxA("Refusé!",NULL,MB_OK);
return;
}

while(FE[++N]);

Form[N] = new TForm(Form1);

Form[N]->Parent=Form1;
Form[N]->Width=FenL;
Form[N]->Height=FenH;

Form[N]->HorzScrollBar->Range=PBW;
Form[N]->VertScrollBar->Range=PBH;

Form[N]->Visible=true;
Form[N]->Align=alNone;
Form[N]->Color=clWhite;
Form[N]->Caption="Fenêtre "+IntToStr(N+1);
Form[N]->OnClose=FormCloseE;
Form[N]->OnCanResize=FormCanResizeE;
Form[N]->Tag=N+1;

PB[N]= new TPaintBox(Form1);
PB[N]->Parent=Form[N];

PB[N]->Width=PBW;
PB[N]->Height=PBH;

PB[N]->Tag=N+1;

PB[N]->OnPaint=PaintE;
PB[N]->OnMouseDown=MouseDownE;

PB[N]->Visible=true;
PB[N]->Align=alClient;

FE[N]=true;
NbFE++;
}

À l'entrée de cette méthode, on commence par tester NbFE (nombre de fenêtres-enfants déjà créées et présentes à l'intérieur de Form1). Si donc NbFE est égal à NbFMax, on refuse la création de la fenêtre car nous sommes arrivés à saturation. Notez que si NbFMax est assez grand, ce cas devrait ne jamais se produire. Si NbFE est inférieur à NbFMax, on est alors sûr qu'il reste au moins un élément à false dans le tableau de booléens FE. On cherche l'indice de cet élément par la seule instruction while(FE[++N]), N étant initialisé à -1. Comme on procède par pré-incrémentation, le premier indice testé sera 0. Dès qu'un élément est trouvé faux, la boucle en while s'arrête et N sera désormais l'indice de la fenêtre que nous allons créer. N étant connu, on crée tout de suite la fenêtre par Form[N] = new TForm(Form1), ce qui signifie que le nième pointeur du tableau de pointeurs de fenêtres pointe cette fenêtre créée. On peut écrire aussi Form[N] = new TForm(this), ça marcherait tout aussi bien, les deux écritures sont équivalentes. On indique maintenant que cette fenêtre créée est parent de la fenêtre principale Form1 : Form[N]->Parent=Form1; c'est cette instruction qui fait savoir durant l'exécution que Form[N] est enfant par rapport à Form1, ce pourquoi l'enfant sera dessiné à l'intérieur du parent. Ensuite on donne les dimensions de la fenêtre-enfant Form[N]->Width=FenL; Form[N]->Height=FenH; où FenH et FenL sont des constantes arbitraires générales que nous avons déclarées. Notez que si vous voulez que la fenêtre-enfant occupe toute la place de la fenêtre-parent, il suffit d'écrire ceci (à la place des deux instructions précédentes) Form[N]->Height=Form1->ClientHeight; Form[N]->Width=Form1->ClientWidth; ce qui signifie que la hauteur de la fenêtre-enfant est égale à la hauteur de la surface cliente de la fenêtre-parent et la longueur égale à la longueur de la surface cliente. Dans ce cas, la fenêtre-enfant prend la place maximale dans son parent. Ceci nous montre que Form1->Height et Form1->Width sont les dimensions totales de Form1 alors que Form1->ClientHeight et Form1->ClientWidth sont la surface cliente excluant les bordures, c'est la zone intérieure accessible. Ensuite nous donnons les dimensions du scroll de la fenêtre-enfant par Form[N]->HorzScrollBar->Range=PBW; Form[N]->VertScrollBar->Range=PBH; où PBW et PBH sont le dimensions de la surface de la future PaintBox que l'on va créer à l'intérieur de la fenêtre-enfant. En effet, une PaintBox n'a pas de scroll, une fenêtre en a un. Si donc la PaintBox est plus grande que la fenêtre, on crée ce composant en le calant en haut à gauche de la fenêtre qui l'accueille et on active les deux scrollbars, horizontal et vertical de Form1. Le scroll sera perçu par l'utilisateur comme étant un scroll de la PaintBox, en réalité il s'agit du scroll de la fenêtre qui en tient lieu. Ensuite, il ne faut pas oublier de rendre visible la fenêtre-enfant sinon bien sûr elle n'apparaît pas à l'écran donc Form[N]->Visible=true; ensuite nous exprimons que la fenêtre-enfant est libre à l'intérieur de son parent, on pourra la déplacer et aussi changer ses dimensions visibles donc Form[N]->Align=alNone; si au lieu de alNone nous avions écrit alClient, la fenêtre-enfant aurait pris les dimensions maximales à l'intérieur de son parent et il y aurait eu impossibilité de la déplacer à l'intérieur de son parent. Nous déclarons maintenant une couleur de fond de la fenêtre-enfant Form[N]->Color=clWhite; ici, le fond de la fenêtre sera blanc. Ensuite, nous écrivons un titre à cette fenêtre-enfant qui va appraître sur la bordure en haut Form[N]->Caption="Fenêtre "+IntToStr(N+1); comme l'indice réel de fenêtre part de 0, on affiche N+1 donc pour 0 on aura fenêtre 1, pour 1 fenêtre 2 etc. Ensuite on déclare la méthode à exécuter quand la fenêtre se fermera Form[N]->OnClose=FormCloseE; cette méthode c'est FormCloseE, on utilise ici les mêmes écritures proposées par C++Builder, on rajoute simplement E pour Enfant. Comme les fenêtres créées à l'exécution ont un petit menu système par défaut, lequel se situe en haut à droite de la fenêtre, l'utilisateur pourra cliquer le x de ce menu pour fermer la fenêtre. On passera alors par la fonction membre de TForm1 FormCloseE de manière à libérer la mémoire correspondant à cette fermeture. On donne aussi la méthode à exécuter quand la fenêtre est redimensionnée Form[N]->OnCanResize=FormCanResizeE; puis on initialise le Tag de la fenêtre-enfant crée qui prend la valeur N+1 donc Form[N]->Tag=N+1; cette petite astuce nous permettra à tout moment de savoir quelle fenêtre est concernée par une action. En effet, quelle que soit la fenêtre visée, on ira toujours à la même méthode e.g. FormCloseE, il faudra donc bien savoir quelle fenêtre est concernée d'où l'utilisation du Tag qui est un nombre entier associé à un composant.

Maintenant que la fenêtre-enfant est créée avec certaines de ses propriétés initialisées par nous, on crée maintenant la PaintBox qu'on va insérer dans la fenêtre-enfant PB[N]= new TPaintBox(Form1); ce qui signifie que le Nième pointeur du tableau PB pointe cette nouvelle PaintBox. On dit maintenant au programme que cette PaintBox est un composant de la fenêtre-enfant créée PB[N]->Parent=Form[N]; i.e. le parent de la PaintBox est la fenêtre-enfant. On indique ensuite les dimensions de la PaintBox PB[N]->Width=PBW; PB[N]->Height=PBH; où PBW et PBH sont des constantes que nous avons déclarées. On initialise aussi le Tag de la PaintBox PB[N]->Tag=N+1; ainsi, à tout moment, on saura par cet entier de quelle PaintBox il s'agit en cas d'action sur elle. On indique maintenant la méthode à exécuter en cas de Paint PB[N]->OnPaint=PaintE; c'est là qu'on va dessiner dans la PaintBox. On indique aussi la méthode à exécuter quand l'utilisateur appuie sur le bouton de la souris PB[N]->OnMouseDown=MouseDownE; ensuite on dit que la PaintBox est visible PB[N]->Visible=true; elle l'est d'ailleurs par défaut, on peut ne pas écrire cette instruction. Puis on indique le type d'alignement de la PaintBox par rapport à son parent, ici il s'agira de alClient, PB[N]->Align=alClient; cela signifie que la PaintBox prend toute la place dans son parent.

La fenêtre-enfant est maintenant créée, on met donc à true le Nième élément de notre tableau de booléens FE[N]=true; et on incrémente le nombre de fenêtres-enfants présentes dans Form1, NbFE++;

Voici comment nous procédons au moment où une fenêtre se ferme. Cet événement survient parce que l'utilisateur a cliqué le petit x du menu système de la fenêtre-enfant. Au moment de la création de la fenêtre, nous avons déclaré FormCloseE comme étant la méthode à exécuter liée à l'événement OnCLose.

void __fastcall TForm1::FormCloseE(TObject *Sender, TCloseAction &Action)
{
Application->MessageBoxA("Close","OK",MB_OK);
int i=((TComponent*)Sender)->Tag-1;
delete PB[i];
delete Form[i];
FE[i]=false;
NbFE--;
}

On affiche un message fermeture. Dans une vraie application, on n'affiche évidemment rien mais cet affichage vous prouve que la méthode est exécutée. Remarquez bien la syntaxe pour lire le Tag de la fenêtre : int i=((TComponent*)Sender)->Tag-1; dans ces condition, i représente l'indice de la fenêtre à détruire. On détruit donc la PaintBox puis la fenêtre (dans cet ordre bien sûr car la PaintBox est un enfant de la fenêtre), l'élément du tableau de booléens passe à false, libérant cet indice pour une future ouverture et enfin, le nombre de fenêtres-enfants est décrémenté. Comme vous le constatez, c'est le système qui se charge de la destruction visuelle de la fenêtre mais la libération mémoire est à notre charge.

Notons qu'on peut s'épargner le tableau de booléens FE, il suffit pour cela d'initialiser les pointeurs Form[i] à NULL et de remettre chaque pointeur à NULL juste après le delete Form[i] au moment de la fermeture de la fenêtre. Ainsi pour tout i, si Form[i]==NULL, la fenêtre n'existe pas sinon elle existe, ce qui simule bien le tableau de booléens dont nous avons besoin.

Nous allons maintenant peindre à l'intérieur de chaque PaintBox. Nous avions déclaré PaintE comme méthode de l'événement OnPaint. Nous écrivons donc cette méthode qui nous permettra simplement de constater la persistance du contenu de la PaintBox ainsi que la validité du scroll.

void __fastcall TForm1::PaintE(TObject *Sender)
{
int i=((TComponent*)Sender)->Tag-1, x=20,y=20;

for(int j=0;j<30;j++)
{
AnsiString M="Fenêtre numéro "+IntToStr(i+1)+ " ("+IntToStr(j+1)+")";
PB[i]->Canvas->TextOutA(x,y,M.c_str());
x+=150;
}
}

Encore une fois, à partir du Tag du composant, on en déduit l'indice par décrémentation car l'indice part de 0 mais le Tag de 1. i étant cet indice, on peint dans PB[i]. On affiche ici le numéro de la fenêtre, on affiche trente fois ce numéro et à chaque fois un nombre allant de 1 à 30. La coordonnée en x avance à chaque itération de 150 pixels. Ceci a simplement pour vocation de montrer que chaque fenêtre connaît son numéro, et un affichage très long en x montrera que le scroll est correct.

Voici maintenant ce qui se passe sur OnMouseDown.

void __fastcall TForm1::MouseDownE(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
int i=((TComponent*)Sender)->Tag;
Application->MessageBoxA( (IntToStr(X)+" "+IntToStr(Y)+" "+IntToStr(i)).c_str()
,"OK",MB_OK);
}

On affiche simplement les coordonnées de click, cela vous prouve qu'il s'agit bien des coordonnées de la fenêtre cliquée. On affiche aussi le numéro du Tag pour montrer qu'à tout moment, on connaît la fenêtre visée par son indice.

Voici ce que nous proposons si l'utilisateur modifie la taille de la fenêtre (méthode FormCanResizeE déclarée en relation à l'événement OnCanResize).

void __fastcall TForm1::FormCanResizeE(TObject *Sender, int &NewWidth,
int &NewHeight, bool &Resize)
{

if(NewWidth==Form1->Width && NewHeight==Form1->Height-19)
{
NewWidth-=5;
NewHeight-=5;
}
}

On simule ainsi l'événement OnMaximize qui n'existe pas. La méthode nous envoie les nouvelles dimensions demandée par l'utilisateur. L'expérience montre que si NewWidth vaut Width et s'il y a une différence de 19 pixels entre la nouvelle hauteur avec la hauteur de la fenêtre, cela signifie que l'utilisateur a cliqué sur la petit carré du menu système pour maximaliser la dimension de la fenêtre. On ne fait que réajuster ces dimensions de manière à ce que la fenêtre-enfant s'encastre parfaitement dans son parent en soustrayant 5 unités sinon l'encastrement n'est pas parfait. La raison en est sans doute de nous rappeler la présence d'un scrollbar dans la fenêtre, on signifie par là que l'image n'est vue que partiellement. Si la fenêtre n'a pas de scrollbar, l'imbrication suite à une demande de maximalisation entre de la fenêtre-enfant à l'intérieur de son parent est parfaite, il n'est alors pas nécessaire d'intervenir logiciellement.

Voici maintenant ce qui se passe à la destruction de la fenêtre principale. Cet événement survient à la fin de l'exécution. Il convient de libérer la mémoire allouée.

void __fastcall TForm1::FormDestroy(TObject *Sender)
{
for(int i=0;i<NbFMax;i++)
{
if(FE[i])
{
delete PB[i];
delete Form[i];
}
}
}

Pour chaque élément booléen du tableau, on ne fait rien s'il est à false car la fenêtre correspondant à cet indice n'existe pas mais on supprime la PaintBox puis la fenêtre (dans cet ordre car la PaintBox appartient à la fenêtre) si l'élément est à true.

Bien entendu, toutes les méthodes liées aux composants sont membres de TForm1 qui apparaît donc comme suit:

class TForm1 : public TForm
{
__published: // Composants gérés par l'EDI
TButton *Button1;
void __fastcall Button1Click(TObject *Sender);
void __fastcall FormDestroy(TObject *Sender);
private: // Déclarations utilisateur
public: // Déclarations utilisateur
__fastcall TForm1(TComponent* Owner);
void __fastcall FormCloseE(TObject *Sender, TCloseAction &Action);
void __fastcall PaintE(TObject *Sender);
void __fastcall MouseDownE(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
void __fastcall FormCanResizeE(TObject *Sender, int &NewWidth,
int &NewHeight, bool &Resize);
};

Petite astuce: pour ne pas se tromper dans les arguments d'une méthode que vous voulez associez à un composant, le mieux consiste à la faire créer automatiquement par C++Builder puis procéder à un copier-coller. Par exemple, on veut rajouter l'événement OnMouseDown pour la fenêtre-enfant. On double-clique sur l'événement OnMouseDown de Form1 à seule fin d'en créer la syntaxe d'appel. Là-dessus, C++Builder vous a créé le gestionnaire d'événement mais il a aussi déclaré la méthode dans l'en-tête associé (le .h). Faisons Ctrl-F6 qui fait afficher cet entête, on peut aussi afficher l'en-tête du fichier cpp en cours par le menu surgissant (click à droite), c'est la première occurrence. Vous voyez que C++Builder a déclaré l'événement dans la section "published". Copiez-collez cette déclaration dans la section "public" et rajoutez simplement E à la fin (pour Enfant). Ensuite vous revenez au cpp, vous copiez-collez le gestionnaire créé par C++Builder, vous ajoutez également un E. Ainsi, vous avez créé un événement et vous êtes certain que la syntaxe sera correcte puisque c'est celle créée par le progiciel. Ensuite, à la compilation, vous verrez que l'événement OnMouseDown de Form1 a disparu, ce qui est logique puisque vous n'avez rien écrit à l'intérieur, vous n'aviez créé le gestionnaire d'événement OnMouseDown de Form1 que pour en avoir la syntaxe. Il en ira de même pour tout événement. S'il s'agit d'un événement associé à un composant qui ne se trouve pas sur votre fiche, rajoutez-le temporairement, créez le gestionnaire d'événement en double-cliquant sur l'événement voulu, copiez-collez la déclaration dans le .h et dans le .cpp et personnalisez le nom de la méthode, vos syntaxes seront correctes, vous pouvez ensuite virez le composant de la fiche puisqu'en réalité vous n'en n'avez pas besoin.

52. Le composant ScrollBar

Dans l'alinéa précédent, nous avons créé des fenêtres-enfants à l'intérieur desquelles on a inclus une PaintBox. On peut se poser la question de savoir en quoi cette PaintBox était utile puisqu'en réalité, toute fenêtre ayant un canvas, on dispose avec la seule fenêtre de toutes les fonctions de dessin. Cette PaintBox n'avait en fait qu'une seule utilité, celle de faire gérer automatiquement par C++Builder le scroll horizontal et vertical. En effet, la fenêtre-enfant créée avait de petites dimensions FenH et FenL, alors que la PaintBox avait une très grande surface virtuelle de dessin de PBH*PBW. Comme la PaintBox est enfant de la fenêtre (elle-même enfant de la fenêtre principale), nous avons déclaré la propriété Range des scrolls de la fenêtre-enfant à égalité avec les dimensions virtuelles de la PaintBox (Form[N]->HorzScrollBar->Range=PBW; Form[N]->VertScrollBar->Range=PBH;). C'est ce mécanisme qui fait qu'un scroll est géré automatiquement par C++Builder et c'est pourquoi la PaintBox était utile. C'est l'initialisation des propriétés Range qui rend visibles et utilisables les scrollbars.

Si maintenant vous voulez gérer vous-même le scroll, il faut alors utiliser un composant TScrollBar. Cela peut s'avérer nécessaire dans le cas où (par exemple) votre surface de dessin excède les possibilités d'une PaintBox. Une PaintBox a des dimensions et donc des coordonnées sur 15 bits, on va donc de 0 à 32767 pour les coordonnées en x et en y. Si vous vouliez gérer des coordonnées quasi illimitées (e.g. sur 32 bits), vous ne pouvez plus utiliser le scroll automatique. Dans ce cas, on se contente de déclarer une fenêtre à l'intérieur de laquelle on crée deux scrollbars, l'un horizontal, l'autre vertical et la surface de dessin n'est que la zone client de la fenêtre. Dans ce cas, la création d'une PaintBox dans la fenêtre n'est plus nécessaire puisque sa surface de dessin coïnciderait avec la zone client de la fenêtre. Cela dit, si votre programme gère ces deux modes, vous auriez intérêt malgré tout à déclarer la PaintBox même si sa dimension coïncide avec la zone client de sa fenêtre-parent et ce, pour éviter d'avoir différentes syntaxes suivant que vous dessiniez dans le canvas de la fenêtre (avec des syntaxes du type Form[N]->Canvas, je reprends ici les déclarations de l'alinéa précédent) ou dans le canvas d'une PaintBox (avec des syntaxes du type PB[N]->Canvas). Dans tous les cas, on déclarerait la PaintBox, la syntaxe d'accès est alors unique.

Reprenons les éléments de l'alinéa précédent, et voyons comment nous pourrions procéder pour gérer nous même le scroll. D'une part, on déclarerait deux fois NbFMax pointeurs vers des composants de ce type.

TScrollBar *SB[NbFMax*2];

En effet, si on autorise l'ouverture de NbFMax fenêtres-enfants, on aura 2*NbFMax pointeurs de scrollbar car pour chaque fenêtre il y aura un scrollbar horizontal et un vertical. Ainsi pour la fenêtre d'indice i, le pointeur du scrollbar horizontal sera SB[2*i] et celui du vertical sera SB[2*i+1]. Ensuite, la fenêtre d'indice N ayant été créée via Form[N] = new TForm(Form1); et certaines de ses propriétés initialisées, on crée maintenant les deux scrollbars,

SB[2*N]= new TScrollBar(Form1);
SB[2*N+1]= new TScrollBar(Form1);

On exprime ensuite que ces scrollbars appartiennent à la fenêtre-enfant créée,

SB[2*N]->Parent=Form[N];
SB[2*N+1]->Parent=Form[N];

On initialise aussi les tags pour pouvoir se repérer logiciellement,

SB[2*N]->Tag=N+1;
SB[2*N+1]->Tag=N+1;

On dit ensuite que le scrollbar d'incice 2*N est horizontal et l'autre d'indice 2*N+1 vertical,

SB[2*N]->Align=sbHorizontal;
SB[2*N+1]->Align=sbVertical;

On indique ensuite la façon dont le scrollbar se positionne par rapport à son parent, alBottom pour l'horizontal (il va donc se placer sur toute la longueur en bas de la fenêtre) et alRight pour le vertical (il va se placer à droite sur toute la hauteur de la fenêtre)

SB[2*N]->Align=alBottom;
SB[2*N+1]->Align=alRight;

On simule ainsi un double scrollbar classique mais ils sont maîtrisés logiciellement. Ensuite on associe une méthode pour l'événement OnScroll,

SB[2*N]->OnScroll=ScrollBar1ScrollEH;
SB[2*N+1]->OnScroll=ScrollBar1ScrollEV;

Ici nous créons deux méthodes, une pour le scroll horizontal et l'autre pour le vertical. On aurait pu aussi n'en créer qu'une seule et faire la distinction à l'intérieur de la méthode. Mais dans ce cas, il faudrait initialiser autrement les tags. Ici nous avons donné au tag le numéro de fenêtre, on ne peut donc pas distinguer le scrollbar horizontal du vertical puisqu'ils ont le même tag. Il faudrait que le tag contienne l'indice du tableau SB, SB[2*N]->Tag=2*N; et SB[2*N+1]->Tag=2*N+1; auquel cas, l'indice du tag nous donne et le numéro de fenêtre et la position du scrollbar invoqué, horizontal ou vertical (pair pour horizontal, impair pour vertical), dans ces conditions une seule méthode de scroll suffirait. Pour savoir si le bouton du scroll est relâché, on teste ScrollCode avec la constante scPosition et la propriété Position du scrollbar nous donne sa position. Considérons POS comme une variable générale du type unsigned int, on renseigne cette variable dans la méthode avec la position du scroll. La variable booléenne Horiz dira si la scroll est horizontal (true) ou vertical (false). Puis on crée par programme un événement paint en appelant simplement la méthode. Voici comment on pourrait programmer ces méthodes.

void __fastcall TForm1::ScrollBar1ScrollEH(TObject *Sender,
TScrollCode ScrollCode, int &ScrollPos)
{
int i=((TComponent*)Sender)->Tag-1;
if(ScrollCode==scPosition)
{
POS=SB[2*i]->Position;
Horiz=true;
PaintE(Sender);
}
}

void __fastcall TForm1::ScrollBar1ScrollEV(TObject *Sender,
TScrollCode ScrollCode, int &ScrollPos)
{
int i=((TComponent*)Sender)->Tag-1;
if(ScrollCode==scPosition)
{
POS=SB[2*i+1]->Position;
Horiz=false;
PaintE(Sender);
}
}

Dans ces conditions, la méthode Paint est appelée. On connaît l'indice de la fenêtre concernée, on connaît la position du scroll (POS), on sait s'il s'agit du scrollbar horizontal ou vertical (Horiz), en fonction de toutes ces données, on repeint la zone concernée de la fenêtre par programme.

53. Le composant PageControl

Ce composant fait partie de l'onglet Win32 de la palette de composants, il sert à gérer une série de pages sous la forme d'onglets successifs que vous pouvez sélectionner. Vous pouvez créer un certain nombre de pages d'office à la création, on procède ainsi quand on connaît d'avance le nombre d'onglets du composant. Dans ce cas, après avoir déposé ce composant sur la fenêtre principale (Form1 par défaut), on clique à droite tout en pointant le composant et on choisit "Nouvelle page" du menu surgissant. Il suffit de répéter cette opération pour avoir d'autres pages. Vous pouvez aussi partir d'un composant vide, dans ce cas vous ne faites que déposer le composant PageControl sur votre fenêtre, le reste se fera par logiciel. L'important est de bien remarquer les syntaxes. Ainsi pour créer une nouvelle page c'est-à-dire un nouvel onglet au composant on écrira :

TTabSheet *pPage = new TTabSheet(PageControl1);

où PageControl1 est le nom par défaut du composant. Pour exprimer que cette page (élément de type TTabSheet créé par l'opérateur new) appartient au composant PageControl1, on écrit :

pPage->PageControl = PageControl1;

Pour afficher dans l'onglet une chaîne on utilise comme à l'accoutumé la propriété Caption (pPage->Caption="Chaîne" où pPage est du type TTabSheet c'est-à-dire un onglet du composant PageControl1), on lui affecte un AnsiString. Le nombre de pages inclus dans le composant est égal à PageControl1->PageCount, c'est un entier, la page active à un moment donné est PageControl1->ActivePageIndex, c'est un entier qui va de 0 à n-1 (n étant le nombre de pages i.e. PageControl1->PageCount). La ième page sera accessible via PageControl1->Pages[i].

Vérifions toutes ces syntaxes. On part d'un projet vierge, on dépose le composant PageControl sur la fenêtre principale Form1. On essaie de positionner ce composant vers le haut à droite de la fenêtre, on fait en sorte qu'il ressemble à une barre horizontale assez fine, affectez par exemple la propriété Width du composant à 300, Height à 25, mettez Top à 0 et Left à 250. Dans ces conditions, PageControl1 est une sorte de barre horizontale en haut à droite de la fenêtre. Mettez maintenant deux boutons, Bouton1 et Bouton2 dans la fenêtre principale Form1. Nous allons programmer la création d'un nouvel onglet à chaque click sur Bouton1, nous allons supprimer l'onglet actif à chaque click sur Bouton2 et quand un onglet sera sélectionné, on affichera le nombre d'onglets disponibles ainsi que le numéro de l'onglet actif. On initialisera le nom des pages par un numéro à partir de 1. De plus, si aucune page n'est disponible dans le composant, on ne l'affichera pas. Au constructeur de TForm1, on se contente de dire que le composant PageControl1 n'est pas visible, ce qui est logique car au début il ne contient aucune page.

__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
PageControl1->Visible=false;
}

À l'événement OnClick du Bouton1, on crée un nouvel onglet comme suit :

void __fastcall TForm1::Button1Click(TObject *Sender)
{
PageControl1->Visible=true;
TTabSheet *pPage = new TTabSheet(PageControl1);
pPage->PageControl = PageControl1;
pPage->Caption = "Page " + IntToStr(PageControl1->PageCount);
}

On rend le composant visible puisqu'il va y avoir au moins une page. Quant au numéro de page affiché sur le composant, il n'est autre que le nombre de pages à savoir PageControl1->PageCount. Ainsi la première page aura pour nom "Page 1", la deuxième "Page 2" et ainsi de suite. Voici ce qui se passe à l'événement OnChange du composant. On va simplement afficher le nombre de pages présentes dans le composant ainsi que le numéro d'onglet sélectionné (attention, celui-ci part de 0, donc pour la page 3 sélectionnée vous lirez dans le MessageBox "et la page activée est la page numéro 2"). Ceci a pour but de montrer qu'on maîtrise parfaitement ce qui se passe et qu'on peut agir logiciellement.

void __fastcall TForm1::PageControl1Change(TObject *Sender)
{
AnsiString A;
A="Il y a "+IntToStr(PageControl1->PageCount)+ " page";
if(PageControl1->PageCount>1) A=A+"s";
A=A+" et la page activée est la page numéro "+
IntToStr(PageControl1->ActivePageIndex);
Application->MessageBox(A.c_str(),"OK",MB_OK);
}

Remarquez qu'on rajoute un "s" au mot "page" si le nombre de pages est supérieur à 1. Voici ce qui se passe quand on clique sur le Bouton2, lequel détruit la page activée à ce moment-là.

void __fastcall TForm1::Button2Click(TObject *Sender)
{
if(PageControl1->PageCount)
{
delete PageControl1->Pages[PageControl1->ActivePageIndex];
for(int i=0;i<PageControl1->PageCount;i++)
PageControl1->Pages[i]->Caption="Page"+ IntToStr(i+1);
}

if(!PageControl1->PageCount) PageControl1->Visible=false;
}

On commence par tester le nombre de pages présentes PageControl1->PageCount car bien entendu, on ne peut pas supprimer de pages s'il n'y en a aucune. Si donc PageControl1->PageCount n'est pas nul, on supprime la page active par delete. Ensuite, on renumérote les pages à partir de 1 de manière à ce que ces numéros se suivent toujours à l'affichage. Ensuite, on teste de nouveau le nombre de pages de manière à rendre le composant invisible s'il ne contient aucune page. Puisque ces pages ont été créées par l'opérateur new, il sera bon de les détruire au sortir de l'application au moment de la destruction de la fenêtre principale Form1, voici donc ce qu'on écrit à l'événement OnDestroy de Form1 :

void __fastcall TForm1::FormDestroy(TObject *Sender)
{
for(int i=0;i<PageControl1->PageCount;i++)
delete PageControl1->Pages[i];
}

Il est probable, puisque ce composant appartient à Form1, que cette précaution ne soit pas nécessaire et qu'on pourrait s'en passer. Cela dit, il est toujours bon de prendre pour habitude de supprimer par delete ce qui a été créé par new, vous êtes ainsi persuadé d'une excellente gestion mémoire. Vous remarquerez à l'exécution que deux petits boutons fléchés apparaissent à droite du composant dès que tous les onglets ne peuvent plus s'afficher en totalité, ces flèches vous permettent de naviguer en avant et en arrière. N'oubliez pas en testant à l'exécution de cliquer un onglet quelconque pour faire apparaître le message-témoin programmé. À noter enfin que la propriété ActivePageIndex est aussi en écriture, ainsi pour activer logiciellement le premier onglet on écrira PageControl1->ActivePageIndex = 0; et pour activer le dernier PageControl1->ActivePageIndex = PageControl1->PageCount-1; si vous voulez que cette initialisation se fasse à la création, dans l'inspecteur d'objets sélectionnez "PageControl1" puis choisissez la page voulue (en général la première) dans le menu déroulant proposé à la propriété ActivePage, à l'exécution du programme, la page choisie sera sélectionnée.

54. Les classes TControlBar, TToolBar, TToolButton et PopupMenu.

Les deux premiers objets sont liés puisqu'on met les toolbars dans un controlbar, le controlbar représente un certain espace réservé, le plus généralement le haut de l'écran et les toolbars sont des bandes le plus souvent horizontales et qui apportent un complément au menu sous la forme de boutons souvent associés à un petit bitmap comme image (que vous pouvez créer vous-même grâce à l'éditeur d'images de C++Builder via "Outils|Editeur d'images"). La toolbar se reconnaît par ses deux petits traits verticaux sur la gauche de la bande. Regardez le menu de Word par exemple, vous voyez clairement plusieurs toolbars. L'intérêt des toolbars est de pouvoir les placer à sa guise dans la surface réservée à cet effet par glissement et déplacement voire de les sortir du controlbar. Sous Word, mettez votre curseur sur ces deux traits horizontaux d’une toolbar quelconque, cliquez et tirez le composant vers le milieu de la fenêtre, vous constatez que la toolbar devient elle-même une petite fenêtre que vous pouvez d'ailleurs fermer, la toolbar alors disparaît (en réalité c’est sa propriété Visible qui est devenue false). En la faisant glisser de nouveau dans sa surface associée appelée controlbar, la toolbar reprend son apparence d'origine, s'encastre dans son espace réservé qu'est le controlbar et vous pouvez la disposer comme bon vous semble dans cette zone. En général, ces composants sont créés à la conception, on joue simplement sur leur visibilité, mais rien ne s'oppose à ce que ces éléments soit créés par l'opérateur new comme tout composant d'ailleurs. Sous Word par exemple, vous pouvez réafficher une toolbar disparue via "Affichage|Barre d'outils", le logiciel vous affiche là toutes les toolbars incluses dans le logiciel ainsi qu'un flag visuel indiquant si la toolbar est visible ou non.

Essayons à partir d'un nouveau projet. Entrons dans C++Builder ou bien si vous y êtes déjà faites "Fichier|Nouvelle application" ou encore, ce qui est a même chose, cliquez le petit rectangle blanc légèrement corné en haut à droite puis choisissez "Application". Vous avez comme toujours votre première fiche vide, faites immédiatement "Fichier|Enregistrer le projet sous" dans votre répertoire Essai prévu pour vos tests, donnez comme nom Unit01 et Projet01 ou tout autre indice sur deux chiffres de préférence (avec deux chiffres tous vos projets seront présentés triés au moment de leur réouverture même au-delà de 9, d'où l'intérêt d'indicer sur deux chiffres).

Sélectionnez le composant ControlBar de la palette, il se trouve dans l'onglet "Supplément", on voit deux petits traits verticaux ainsi qu'une flèche noire vers la droite. Si vous ne le voyez pas, scrollez à droite c'est-à-dire cliquez la petite flèche noire à l'extrême droite de la palette (l'onglet "supplément" étant bien sûr toujours sélectionné). Aidez vous du hint c'est-à-dire de la bulle d'aide qui s'affiche automatiquement quand on pointe un composant de la palette, là la bulle d'aide affichera "ControlBar". Cliquez donc sur le controlbar pour le sélectionner puis cliquez n'importe où dans la fenêtre Form1 pour le déposer. Notons au passage qu'après avoir sélectionné un composant en cliquant dessus, il suffit de faire F1 pour faire afficher les renseignements de ce composant notamment les propriétés et les méthodes associées.

On vient donc de déposer un ControlBar dans Form1, il se présente sous la forme d'un rectangle posé n'importe où dans la fenêtre. Or, ce que nous voulons, c'est le coller tout en haut de la fenêtre. Donc cliquez la propriété Align de l'inspecteur d'objets et choisissez alTop dans le petit menu déroulant proposé. Vous voyez qu'immédiatement, le controlbar se colle en haut de la fenêtre. Ensuite, mettez AutoSize à true. Ainsi, le controlbar se redimensionnera de lui-même en fonction du nombre de toolbars présentes dans cette surface et de leur disposition.

Sélectionnez maintenant le composant ToolBar de la palette, onglet "Win32', on le reconnaît par le fait qu'il simule un début de menu classique avec un petit rectangle blanc corné (icône "Nouveau") puis le début du logo "ouvrir" en jaune. Cliquez donc ce composant puis cliquez à l'intérieur du controlbar pour l'y déposer. Vous pouvez d'ores et déjà faire F9 pour exécuter, vous voyez clairement votre toolbar et vous pouvez la déplacer dans son espace. Ajoutons encore deux autres toolbars de la même manière en sélectionnant ce composant dans la palette et en cliquant ensuite dans le controlbar (mais non dans un toolbar déjà déposé, cliquez dans un endroit libre du controlbar). Faites F9 pour exécuter, vous voyez vos trois toolbars et vous pouvez les superposer, le controlbar s'agrandit automatiquement pour créer l'espace nécessaire car nous avons mis la propriété AutoSize à true du controlbar.

Seulement nos toolbars sont coincées dans leur espace, on ne peut pas les sortir de là. Si vous voulez autoriser la sortie possible d'une toolbar hors du controlbar, il suffit de positionner sa propriété DragKind à dkDock. La propriété DragKind n'a que deux possibilités, dkDrag et dkDock, la première interdit la sortie du composant hors du controlbar, la deuxième autorise cette possibilité. Comme DrakKind est à dkDrag par défaut, vous comprenez pourquoi vous ne pouviez jusqu'à présent la mouvoir hors du controlbar. En général, les composants ont la propriétés DragKind à dkDrag car ils n'ont aucune raison de sortir de leur espace mais pour les toolbars, on trouve les deux possibilités.

À partir de là, vous pouvez mettre ce que vous voulez dans les toolbars, en général on essaie de regrouper les fonctions par type avec une toolbar par type de fonctions, c'est la règle respectée dans tous les logiciels.

Il existe des boutons spéciaux pour les toolbars, on les obtient en sélectionnant la toolbar dans Form1 puis en cliquant à droite, choisissez dans le menu "Nouveau bouton" qui est la première occurrence de ce menu, un bouton se dessine alors. Répétez cette opération plusieurs fois pour ajouter quelques boutons à cette même toolbar. Puis, donnez à cette toolbar la propriété AutoSize à true et la propriété ShowCaptions à true également. Vous voyez que le nom des boutons apparaît maintenant, faites F9 pour essayer, vous constatez que la toolbar peut avoir diverses apparences. Cela dit, pour que le bouton réagisse visuellement, il faut cliquer dessus. Si vous voulez que la réaction ait lieu au moment où la souris passe sur le bouton, il faut positionner la propriété flat de la toolbar à true. On simule ainsi le comportement d'un menu. Vous avez le choix ici soit de créer une fonction pour le click associé soit d'afficher un menu déroulant pour simuler un autre menu que le menu principal.

Par exemple, double-cliquez sur le premier bouton et créez le gestionnaire d'événements comme suit :

void __fastcall TForm1::ToolButton1Click(TObject *Sender)
{
Application->MessageBox("Bouton1", "ok", MB_OK);
}

Un message vous sera affiché sur click du bouton.

Essayons avec un PopupMenu. Sélectionnez dans l'onglet "standard" de la palette le composant "PopupMenu" (son icône est un début de menu avec une flèche blanche) et déposez-le dans Form1. Une fois déposé, cliquez à droite sur le composant et choisissez "concepteur de menus". On vous présente un cadre avec un début de menu. Dans l'inspecteur d'objets, le curseur se trouve dans Caption, il s'agit d'entrer une option à ce nouveau menu, entrez par exemple "test1" et validez. Le concepteur de menu affiche cette occurrence et vous propose maintenant une deuxième case, cliquez cette case et saisissez dans l'inspecteur d'objets "Test2". Continuez ainsi plusieurs fois pour obtenir un menu de test. À chaque fois, vous cliquez la nouvelle case proposée dans le concepteur de menu, le curseur se trouve automatiquement dans Caption dans l'inspecteur d'objets, saisissez le nom de l'option, validez et recommencez. Pour que cette saisie soit agréable, il est préférable que l'inspecteur d'objets ne chevauche pas le concepteur de menu, ainsi vous passez facilement d'une fenêtre à l'autre. Après ces saisies, faites disparaître le concepteur de menu. Pour le rappelez, vous pourrez toujours cliquer à droite en pointant ce composant dans Form1 et en choisissant "Concepteur de menus", vous pouvez aussi sélectionner ce composant dans Form1 de manière à le sélectionner dans l'inspecteur d'objets, puis placer le curseur dans la propriété "Items", cliquez alors les points de suspensions, vous réobtenez le concepteur de menus.

Notre menu déroulant étant créé, nous allons l'associer à notre deuxième bouton. Double-cliquez ce bouton pour créer le gestionnaire d'événement associé et copiez-collez ce qui suit :

void __fastcall TForm1::ToolButton2Click(TObject *Sender)
{
int x=Form1->Left+ToolBar1->Left+ToolButton2->Left+5;
int y=Form1->Top+ToolBar1->Top+ToolButton2->Top+50;
PopupMenu1->Popup(x,y);
}

Dans cette méthode, on calcule les coordonnées où va apparaître le menu surgissant. Left et Top représentant les coordonnées par rapport au parent, on ajoute toutes ces coordonnées liées aux trois éléments (Form1, ToolBar1 et ToolButton2) et on ajuste in fine par une constante pour régler parfaitement cet affichage.

Pour évitez les complications inutiles, je vous conseille d'interdire la sortie du toolbar pour des pseudo-menus de ce genre, donc mettez sa propriété DragKind à dkDrag, c'est d'ailleurs sa position par défaut. La raison en est qu'il est difficile de calculer les coordonnées où il faudrait afficher le menu car ToolBar1->Left et ToolBar1->Top sont tous deux à zéro si la toolbar est sortie du controlbar. C'est logique puisque Left et Top sont les coordonnées par rapport à Form1, or une fois sortie la toolbar est libre par rapport à Form1, la toolbar est toujours un objet-enfant de Form1 pour les problèmes de libération mémoire mais n'a plus de liens au plan des coordonnées.

Le bouton peut-être également associé à un menu surgissant, il suffit de donner le nom du popupmenu (par défaut PopupMenu1) à la propriété PopupMenu du ToolButton2 mais dans ce cas, c'est par le click à droite qu'il apparaît. Si vous voulez simuler un menu classique, il vaut mieux procéder comme indiqué ci-dessus à savoir créer le popupmenu indépendamment et l'afficher via une instruction du type PopupMenu1->Popup(x,y) au moment du click en réglant ses coordonnées (x,y).

Les toolbuttons des toolbars peuvent aussi afficher un bitmap à titre d'icône au lieu d'un mot-clé explicatif. Utilisons notre deuxième toolbar et voyons comment procéder pour avoir de tels toolbuttons. Commençons par créer de petits bitmaps qui nous serviront d'icônes. Pour ce faire, faites "Outils|Editeurs d'images", c'est un utilitaire intégré dans C++Builder qui vous permet précisément de créer vos propres icônes en couleurs.

Notons que les toolbars peuvent, comme tout composant, être créés logiciellement à l'exécution. Par exemple, on déclare dans les variables générales un pointeur sur une toolbar 

TToolBar *pTB;

À l'initialisation, on crée la toolbar (ou à tout autre moment d'ailleurs).

__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
pTB = new TToolBar(Form1);
pTB->Parent = ControlBar1;
pTB->ShowCaptions = True;
pTB->Caption="Pages";
pTB->Color=clAqua;
pTB->AutoSize=true;
}

On suppose ici que la forme principale dispose d'un controlbar qui sert de parent au toolbar instancié. On met sa propriété ShowCaptions à true de manière à ce que les captions des futurs boutons soient affichés. On met Autosize à true, ainsi la toolbar aura la dimension adéquate en fonction du nombre de boutons créés. Voici comment ajouter un bouton à la toolbar :

TToolButton *pButton = new TToolButton(pTB);
pButton->Parent = pTB;
pButton->Caption = IntToStr(pTB->ButtonCount);
pButton->Tag=pTB->ButtonCount;
pButton->OnClick=ButtonClickX;

On déclare le bouton parent de la toolbar. Son caption est ici son numéro d'ordre ButtonCount à savoir le nombre de boutons existant dans la toolbar. On positionne le Tag du bouton avec ce numéro d'ordre. On associe l'événement OnCLick à la méthode ButtonClickX, méthode qu'il faudra déclarer dans la classe TForm1. Dans la classe TForm1, il faudra donc rajouter dans la section public :

void __fastcall ButtonClickX(TObject *Sender);

Par exemple, créons cette méthode ButtonClickX en affichant simplement le numéro du bouton cliqué. Ce numéro est ici son tag car à la création, le tag a été initialisé avec le nombre de boutons présents dans la toolbar, par conséquent le premier bouton créé a son tag à 1, le deuxième à 2 et ainsi de suite. Cet affichage prouve qu'on maîtrise la situation puisqu'on connaît le numéro du bouton cliqué, on pourrait donc exécuter telle ou telle méthode en fonction de ce numéro.

void __fastcall TForm1::ButtonClickX(TObject *Sender)
{
int i=((TComponent*)Sender)->Tag;
AnsiString A=IntToStr(i);
Application->MessageBox(A.c_str(),"ok",MB_OK);
}

Cela dit, on pourrait aussi créer une méthode spécifique pour chacun des boutons créés. En général, cela n'est pas utile, un numéro d'ordre suffit largement à un bon comportement logiciel (par exemple, on gère ainsi un affichage de pages, le bouton cliqué sera le numéro d'une page à afficher) mais bien entendu, tout est possible. À la fin de l'exécution de l'application, il ne faudra pas oublier de détruire les boutons de la toolbar puis la toolbar, dans cet ordre.

void __fastcall TForm1::FormDestroy(TObject *Sender)
{
for(int i=0;i<pTB->ButtonCount;i++)
delete pTB->Buttons[i];
delete pTB;
}

À noter qu'il ne faudra pas attribuer la valeur dkDock à la propriété DragKind de la toolbar (c'est-à-dire qu'à sa création il ne faudra pas écrire pTB->DragKind=dkDock;) car alors la toolbar pourrait sortir du controlbar et sa destruction créerait une violation d'accès si toutefois elle se trouvait effectivement hors du controlbar à ce moment-là. Je conseille donc de ne pas initialiser DragKing et donc de laisser sa valeur par défaut à savoir dkDrag qui interdit la sortie du toolbar de son controlbar parent, dans ces conditions il n'y aura pas de problème au moment du delete.

55. L'éditeur d'images

Après être entré dans l'éditeur d'images via "Outils|Editeurs d'images" (ou encore par le menu Démarrer->Programmes->BorlandC++Builder5->Éditeur d'images) pour créer de petits bitmaps qui vont nous servir d'icônes, faites "Fichier|Nouveau" et choisissez "Bitmap" dans le menu déroulant puis donnez ses dimensions par exemple 32 par 32, choisissez un bitmap sur 16 couleurs, c'est d'ailleurs la proposition par défaut. Faites plusieurs fois crlt-i (contrôle i) pour ajuster le niveau de zoom, ctrl-u (contrôle u) pour diminuer le zoom s'il était devenu trop grand pour vous. Après avoir choisi une dimension de zoom satisfaisante, dessinez quelque chose, choisissez une couleur en cliquant dans la palette de couleurs et étalez cette couleur dans le bitmap et répétez l'opération avec une autre couleur (le contenu du dessin pour ce test n'a aucune importance, il s'agit de montrer comment associer un bitmap à un toolbutton). Une fois ce dessin de test terminé, faites "Fichier|enregistrez sous", gardez pour ce test le nom de bitmap1 mais donnez votre répertoire de développement en utilisant le navigateur habituel jusqu'à vous trouver "chez vous", par exemple dans votre répertoire Essai prévu pour les tests. En enregistrant le bitmap dans ce répertoire, vous n'aurez à donner au LoadFromFile que le nom du bitmap, c'est l'avantage. Créez ainsi trois bitmaps 16 couleurs 32 par 32 nommés Bitmap1, Bitmap2 et Bitmap3. Notons au passage qu'une grande quantité de bitmaps en tous genres sont prêts à être utilisés dans le répertoire Program Files\Fichiers communs\Borland Shared\Images.

Nous avons donc trois bitmaps. Revenons à notre deuxième toolbar de l'alinéa précédent. Mettez sa propriété AutoSize à true, puis les propriétés ButtonHeight et ButtonWidth à 32 toutes deux. Ensuite cliquez à droite tout en pointant cette toolbar et choisissez "Nouveau bouton", faites cette opération trois fois, vous avez donc trois toolbuttons de dimensions 32 par 32.

Maintenant, posez le composant ImageList sur la fiche principale.

56. La classe TImageList

Ce composant se situe dans l'onglet "Win32" de la palette de composants, sélectionnez-le dans la palette et cliquez dans la fiche principale Form1 pour l'y déposer. Ensuite (car nous continuons nos tests des deux alinéas précédents), mettez ses propriétés Height et Width toutes deux à 32 puisque nos bitmaps dessinés précédemment sont de 32 par 32.

Maintenant, il faut signaler à la toolbar2 (dans notre exemple, c'est la deuxième toolbar qui va afficher les bitmaps) qu'elle est associée à cette liste d'images. Il suffit de sélectionner la propriété "Images" et de sélectionner ImageList1 dans la liste déroulante. Vous concluez facilement que vous pouvez avoir plusieurs listes d'images de ce genre et les associer à une toolbar.

Notez que quand vous avez créé les toolbuttons, C++Builder a incrémenté pour vous la propriété ImageIndex. Il a donc notifié 0 pour le premier toolbutton, 1 pour le deuxième et ainsi de suite. C'est ici qu'on affecte une image de la liste par son indice au toolbutton concerné, vous pouvez évidemment modifier cet ordre.

Le reste se fait par logiciel. Dans le constructeur de TForm1, nous allons charger ces images via trois bitmaps créés localement.

__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
Graphics::TBitmap* B1,*B2,*B3;

B1 = new Graphics::TBitmap();
B2 = new Graphics::TBitmap();
B3 = new Graphics::TBitmap();

B1->LoadFromFile("Bitmap1.bmp");
B2->LoadFromFile("Bitmap2.bmp");
B3->LoadFromFile("Bitmap3.bmp");

ImageList1->Add(B1,NULL);
ImageList1->Add(B2,NULL);
ImageList1->Add(B3,NULL);

delete B1;
delete B2;
delete B3;
}

En premier lieu on déclare trois pointeurs de bitmaps puis on crée les bitmaps par l'opérateur new, ensuite on les charge via l'instruction LoadFromFile, comme ces bitmaps ont été enregistrés dans notre répertoire de développement, nous n'avons eu à signaler que le nom de ces bitmaps sans le répertoire, C++Builder comprend qu'il s'agit du répertoire courant. Ensuite via Add, on ajoute les bitmaps à la liste d'images puis on supprime ces bitmaps dont on n'a plus besoin.

Tout est prêt, faites F9 pour compiler et exécuter, vous voyez votre toolbar avec les bitmaps que vous avez créés vous-même.

Bien entendu, ce type de syntaxe est possible si vous avez assez peu de bitmaps, si vous en avez beaucoup, il faut procéder via une boucle, c'est un peu plus élégant. Voici comment cela se programmerait. Nous ne gardons dans l'exemple suivant que trois bitmaps mais nous procédons par boucle simplement pour montrer le mécanisme.

__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{

/* Nombre d'images ici 3 pour notre test*/
const int NbBM=3;

/* Déclaration d'une TStringList qui contiendra
le nom des bitmaps */
TStringList *SL=new TStringList;

/* Déclaration d'un bitmap unique */
Graphics::TBitmap *BM = new Graphics::TBitmap();

/* On ajoute dans la StringList le nom des bitmaps*/
SL->Add("Bitmap1.bmp");
SL->Add("Bitmap2.bmp");
SL->Add("Bitmap3.bmp");

/* Pour chaque nom de la StringList, on charge notre
bitmap et on ajoute son image à la liste d'images */
for(int i=0;i<NbBM;i++)
{
BM->LoadFromFile(SL->Strings[i]);
ImageList1->Add(BM,NULL);
}

/* on supprime la stringList et le bitmap*/
delete SL;
delete BM;
}

On déclare une StringList (voir alinéa 44 de la deuxième partie) SL et un bitmap unique BM. On ajoute via Add le nom des bitmaps successifs dans la StringList. Puis on boucle. À chaque itération, on charge le bitmap dont le nom est la ième occurrence de la StringList puis on ajoute ce bitmap à la liste d'images. Ensuite on supprime et la StringList et le bitmap mais les images sont enregistrées dans la liste. Faites F9 pour compiler-exécuter, le résultat est le même que précédemment.

Autre possibilité si vous voulez vous épargner une StringList : affecter directement un tableau de chaînes avec les noms successifs de bitmaps avec une syntaxe du type char Liste[][50]={"Bitmap1.bmp","Bitmap2.bmp","Bitmap3.bmp"};

Voici comment nous pourrions procéder :

__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
const int NbBM=3;
char Liste[][50]={"Bitmap1.bmp","Bitmap2.bmp","Bitmap3.bmp"};
Graphics::TBitmap *BM = new Graphics::TBitmap();

for(int i=0;i<NbBM;i++)
{
BM->LoadFromFile(Liste[i]);
ImageList1->Add(BM,NULL);
}

delete Liste;
delete BM;
}

Ici la suite des noms s'enregistre automatiquement dans la variable Liste indicée, on boucle de la même façon que précédemment mais on lit le ième nom de la liste par Liste[i] puis on supprime cette liste de noms devenue inutile et le bitmap. Le résultat est le même à l'exécution.

Il existe une autre possibilité qui consiste à charger à la conception tous ces bitmaps, c'est d'ailleurs probablement une meilleure solution, cela permet à l'application de ne pas s'en occuper, les exemples précédents se proposaient simplement de montrer un exemple de codage car il est bon de maîtriser les syntaxes d'accès. Dans ce cas, une fois chargés dans le composant ImageList, vous n'avez plus besoin de ces bitmaps et notamment ils n'ont plus besoin de se trouver dans le répertoire par défaut puisqu'ils ne seront plus lus à l'exécution étant préchargés. Pour ce faire, créez un répertoire particulier et stockez-y tous vos bitmaps de même dimension, créez par exemple un répertoire Bitmap32x32 pour les bitmaps 32x32 et bitmap16x16 si vous avez une autre série de bitmaps en 16x16 (comme ces ressources vont se trouver maintenant à l'intérieur de l'application, un peu à la façon des constantes qui sont chargées à l'exécution, elles n'ont plus besoin de se trouver dans le répertoire par défaut). Cliquez à droite sur le composant ImageList et choisissez dans le menu surgissant l'option "Éditeur d'ImageList". Grâce à ce formulaire, vous pouvez charger les bitmaps les uns après les autres dans le composant, il suffit de cliquer "Ajouter", d'indiquer le bitmap à insérer dans la liste et de recommencer l'opération. Le logiciel décode en principe une couleur transparente, cela améliore la présentation. Normalement le choix calculé et proposé par défaut est correct mais si cela ne convient pas (c'est très rare), vous pouvez changer cette couleur transparente ou même indiquer qu'il n'y a pas de couleur transparente. En principe la couleur transparente améliore la présentation car seul le dessin s'incruste dans le bouton. Il y a un autre avantage, c'est que le bitmap s'affiche à la conception dans le bouton de la ToolBar. Si la propriété ImageIndex est correctement renseignée pour le bouton concerné, alors le dessin apparaîtra soit en sortant du formulaire soit en faisant "Appliquer". En principe, les boutons de ToolBar font plutôt 16x16. Il est donc bon au moment de créer un logiciel d'avoir un répertoire nommé par exemple Bitmap16x16 dans lequel on emmagasine nos bitmaps ayant cette dimension. On les charge un à un à la conception comme indiqué ci-dessus. Puis on indique aux boutons l'index de l'image-bitmap correspondant. Si vous avez un menu, il suffira de renseigner aussi l'index du bitmap associé pour chaque option du menu et le bitmap apparaîtra à côté de l'option visée. Voyez les logiciels en général, en faisant dérouler le menu principal, on voit parfois un petit bitmap dessiné en regard à gauche de certaines options, ce n'est pas obligatoire, si vous gardez comme index la valeur -1 qui signifie "pas de bitmap associé", il n'y aura pas de bitmap dessiné à gauche mais si vous renseignez cet index, il apparaîtra pendant la conception même. Notez aussi que cette technique est en relation avec le composant TActionList (dont nous allons parler alinéa 58). En effet, le composant très utile ActionList peut être associé à une ImageList et donc chaque action de la liste peut s'associer à un index de cette liste d'images. Or, comme à un bouton ou à une option de menu on peut associer une action (faisant partie de la liste d'actions), le bitmap se dessinera automatiquement, C++Builder renseigne par défaut lui-même les index. Ainsi on procédera dans l'ordre suivant : premièrement on enregistre les bitmaps dans ImageList, deuxièmement on déclare les actions dans ActionList en indiquant la liste d'images associée puis pour chaque action l'index de l'image correspondant, troisièmement on choisit pour un bouton l'action (propriété Action) de la liste d'actions (le bitmap se dessinera alors automatiquement), quatrièmement idem pour une fonction de menu, la propriété Action sera renseignée avec la bonne action de ActionList et donc le bitmap associé se dessinera. Nous avons un peu anticipé sur l'alinéa 58 (TActionList).

Remarquez aussi ceci. Si un bitmap vous intéresse dans un logiciel quelconque, il est très aisé de le récupérer. On commence par faire une copie d'écran. Pour ce faire, on active la fenêtre du logiciel où se trouve le bitmap à extraire puis on fait "Alt-Impr écran" (on appuie sur Alt puis sur "Impr écran" à droite de la touche F12). Par cette opération, vous avez copié dans le presse-papier la fenêtre active qui contient notre bitmap. À ce stade, on entre dans l'éditeur d'images de C++Builder (Outil->Éditeur d'images, voir alinéa 55), on crée un nouveau bitmap par Fichier->Nouveau, on choisit "Bitmap" parmi les choix possibles, on surdimensionne en choisissant comme dimension 800x600 pour copier l'image dans sa totalité, on fait "coller" ("ctrl v", contrôle v) ce qui colle l'image dans ce bitmap 800x600, on repère l'endroit où se trouve le petit bitmap à dupliquer en naviguant dans l'image et en jouant avec les zooms ("ctrl i" pour zoom avant et "crlt u" pour zoom arrière). Là, on sélectionne le sélecteur de l'éditeur, c'est le premier logo en haut à gauche qui représente un carré avec une flèche blanche, cette fonction permet d'extraire un cadre dans le bitmap 800x600, on encadre à peu près le petit bitmap à extraire (en l'entourant largement), on refait Fichier->Nouveau, on rechoisit "Bitmap", on accepte cette fois-ci les dimensions par défaut 100x100, on colle ce que nous avons copié par le sélecteur (crtl v) et là, en grossissant bien le bitmap, on extrait en réutilisant le sélecteur un bitmap de 16x16, on recrée un nouveau bitmap de 16x16, on colle ce que nous avons extrait en 16x16, on obtient ainsi le bitmap de 16x16 voulu, puis on l'enregistre dans notre répertoire prévu à cet effet. On se débarrasse ensuite des deux premiers bitmaps (800x600 et 100x100) qu'on a créés simplement pour extraitre in fine le petit bitmap de 16x16 qui lui a été enregistré dans notre répertoire. Par la suite, il suffira de l'insérer dans le composant ImageList comme indiqué ci-dessus.

57. Les applications MDI

C++Builder vous permet de développer deux types d'application, les applications SDI (Single Document Interface) et les applications MDI (Multiple Documents Interface). On reconnaît une application MDI au fait qu'il y a deux menus système, on appelle ainsi les trois petits boutons en haut à droite des fenêtres, un petit bouton avec un petit trait pour minimiser la fenêtre, un petit bouton avec deux carrés en cascade pour réduire la fenêtre et un petit bouton avec un x à l'intérieur pour fermer la fenêtre.

Ouvrez par exemple le bloc-notes appelé "Notepad", c'est un petit éditeur de texte pour prendre des notes simples, vous n'avez qu'une seule fenêtre. En faisant "Fichier|Nouveau", vous ne faites qu'effacer la fenêtre en cours mais vous n'ouvrez pas d'autre document, c'est une application SDI à menu système unique.

Ouvrez maintenant Word, il est doté de deux menus systèmes superposés, celui tout en haut est le menu système de Word lui-même et celui en dessous est le menu système de la fenêtre. Réduisez la fenêtre-texte en cliquant sur le bouton du milieu (deux carrés en cascade) du menu système de la fenêtre-texte c'est-à-dire celui qui se trouve juste en dessous du menu système de Word, vous observez que le texte se trouve maintenant dans une fenêtre à lui doté de son propre menu système et que le menu système qui se trouvait en haut a disparu. De plus, en faisant "Fichier|Nouveau", vous ouvrez vraiment une autre fenêtre dotée de son propre menu système. Word est donc une application MDI.

Nous allons voir comment procéder avec C++Builder pour obtenir un comportement de fenêtres identique à celui de Word. Créez un nouveau projet vierge et sauvegardez-le dans votre répertoire Essai de test en utilisant les noms proposés mais en les indiçant selon notre habitude unitxx et projetxx (où xx est l'indice sur deux chiffres). Vous avez donc une fiche principale Form1. La première chose à faire est d'indiquer que notre application sera du type MDI, il suffit pour ce faire d'affecter la propriété FormStyle avec la valeur fsMDIForm proposée dans le menu déroulant. Par défaut, cette propriété est fsNormal, c'est la valeur utilisée pour les applications SDI.

Ensuite une application MDI doit obligatoirement avoir un menu principal. La raison en est que le menu système des futures fenêtres-enfants se dessinera dans ce même menu sur la droite (comme c'est le cas par exemple sous Word ou toute autre application MDI). Si vous n'aviez pas de menu principal, cela poserait problème car alors, en cas de maximalisation d'une fenêtre-enfant, les menus systèmes de deux fenêtres parent et enfant se superposeraient et on ne pourrait plus distinguer les fenêtres. Donc sélectionnez "MainMenu" de la palette de composants (onglet "Standard") et posez-le dans Form1. Créez ensuite un menu de test, la méthode est la même que pour les popupmenus, voyez l'alinéa 54 à ce sujet. Faites F9 pour compilez-exécuter, vous voyez que la présentation est légèrement différente, la zone client de la fenêtre principale est encadrée.

Nous allons maintenant programmer la création de fenêtres-enfants mais avant nous allons déclarer un tableau F de pointeurs de fiche ainsi qu'une constante NbFenMax pour contrôler le nombre de fenêtres maximum que l'on peut ouvrir. Voici donc à quoi ressemble le début de notre unité cpp.

#include <vcl.h>
#pragma hdrstop

#include "Unit04.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
const int NbFenMax=3;
TForm1 *Form1;
TForm *F[NbFenMax];

Ceci vous montre l'endroit où vous pouvez déclarer vos variables générales, ici F notre tableau de pointeurs de fiche. Ensuite, dans le constructeur de TForm1, on initialise à NULL tous les pointeurs du tableau.

__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
for(int i=0;i<NbFenMax;i++) F[i]=NULL;
}

Cette petite astuce nous permettra de savoir pour un indice donné s'il est associé ou non à une fenêtre. Une des options de notre menu sera "Nouveau" pour créer une fenêtre. On cherchera dans le tableau le premier indice libre entre 0 et NbFenMax-1. Voici cette petite fonction :

int __fastcall Prem()
{
for(int i=0;i<NbFenMax;i++) if(F[i]==NULL) return i;
return -1;
}

Vous voyez qu'on boucle dans notre petit tableau de pointeurs, si l'indice lu est à NULL, cela signifie qu'il est libre et alors on sort tout de suite avec cet indice sinon on s'en retourne avec -1 qui indiquera au programme appelant qu'il est impossible d'ouvrir une nouvelle fenêtre parce que le maximum autorisé a été atteint. Dans nos tests, NbFenMax est à trois pour tester précisément ce cas mais si NbFenMax est assez grand, il est peut probable que cela se produise. Pour les puristes, j'accorde que l'on peut écrire if(!F[i]) au lieu de if(F[i]==NULL), les deux syntaxes sont équivalentes.

Double-cliquons dans l'option "Nouveau" de notre menu principal pour créer le gestionnaire d'événement correspondant et programmons l'ouverture d'une fenêtre-enfant.

void __fastcall TForm1::NouveauClick(TObject *Sender)
{
int i=Prem();
if (i==-1) Application->MessageBox("Refusé", NULL, MB_OK);
else
{
F[i]=new TForm(Form1);
F[i]->FormStyle=fsMDIChild;
F[i]->Color=clWhite;
F[i]->Caption="Fenêtre "+IntToStr(i+1);
F[i]->Tag=i;

F[i]->OnClose=FormCloseE;
}
}

Nous appelons d'abord notre fonction Prem qui nous renvoie le premier indice disponible du tableau de pointeurs de fiches. Si cet indice i est -1, la demande est refusée car nous n'avons plus de pointeurs disponibles, tous pointent une fenêtre-enfant. Dans ce cas, on affiche un message dans un MessageBox et on quitte la méthode. Sinon, l'indice i est valide, on crée alors la fenêtre via l'opérateur new puis on indique immédiatement qu'elle est du type fsMDIChild. Dans ces conditions, la fenêtre viendra se dessiner dans la zone client de son parent. Ensuite on donne la couleur de la fenêtre (ici clWhite pour la différencier de son parent en gris par défaut), puis le titre de la fenêtre via son numéro, on ajoute simplement 1, on aura donc "Fenêtre 1" pour l'indice 0, "Fenêtre 2" pour l'indice 1 et ainsi de suite. Ensuite on initialise son Tag avec l'indice du tableau, cela nous permet à tout moment de savoir quel est l'indice concerné si besoin est. Ensuite on indique que la méthode FormCloseE est associée à l'événement OnClose.

Créons cette méthode OnCloseE. Comme on ne peut pas connaître toutes les syntaxes, le mieux est d'en faire créer le prototype par C++Builder. Pour ce faire, sélectionnez Form1 puis double-cliquez l'événement "OnClose" de Form1. C++Builder vous en a créé la syntaxe. Revenez au source et faites maintenant ctrl-F6 (contrôle F6) pour afficher l'en-tête. Vous voyez que C++Builder a déclaré cette méthode dans la classe Form1, copiez-collez cette méthode dans la section "public" et rajouter un E pour Enfant à son nom, à partir de OnClose, on obtient OnCloseE. Revenez au source C++ et copiez-collez la méthode OnClose à la suite du source et rajoutez également le E à son nom. Par ces deux opérations, vous venez de créer la méthode OnCloseE associée à l'événement OnClose de la fenêtre-enfant. Voici comment nous allons programmer cette méthode.

void __fastcall TForm1::FormCloseE(TObject *Sender, TCloseAction &Action)
{
int i=MDIChildren[0]->Tag;
delete F[i];
F[i]=NULL;
}

Remarquez qu'on utilise le tableau MDIChildren indicé créé automatiquement par C++Builder. Or, l'indice 0 représente toujours la fenêtre active au moment de l'exécution de la méthode, on lit le Tag du pointeur de la fiche active ce qui nous donne l'indice réel par rapport à notre tableau. Ensuite on supprime la fiche et on réinitialise à NULL l'indice de ce tableau. Cette programmation est nécessaire sinon, en cliquant le petit x du menu système d'une fenêtre-enfant, cette fenêtre se réduit mais ne disparaît pas.

Dans cette méthode TForm1::FormCloseE, on dispose de l'argument Sender de type TObject. Cet objet, c'est évidemment la fenêtre dont on demande la fermeture et c'est aussi la fenêtre active pour l'instant. Il en résulte que pour obtenir l'indice correct on pouvait aussi écrire int i=((TComponent*)Sender)->Tag; au lieu de int i=MDIChildren[0]->Tag; et ce, parce que la fenêtre active correspond à l'objet envoyé. Il n'en est pas toujours ainsi. Imaginons que chaque fenêtre-enfant soit doté, comme ce sera pratiquement obligatoire, d'une méthode OnPaint, l'objet envoyé ne sera pas toujours la fenêtre active car on repeint aussi les fenêtres inactives suite à des déplacements de fenêtres. Imaginons qu'à chaque fenêtre F[i] soit associée deux bitmaps BM1[i] et BM2[i]. Au plan algorithmique, on dessine dans BM1[i], puis on recopie le dessin dans BM2[i] au moyen d'un Draw (ceci nous permet de toujours dessiner hors écran, on n'affiche que le résultat final), la méthode OnPaint d'une fenêtre se contentera de recopier BM2[i] dans le canvas de la fenêtre envoyée. La syntaxe d'écriture serait donc du type :

void __fastcall TForm1::FormPaintE(TObject *Sender)
{
int i=((TComponent*)Sender)->Tag;
Fen[i]->Canvas->Draw(0,0,BM2[i]);
}

Grâce à l'écriture int i=((TComponent*)Sender)->Tag; on récupère l'indice non pas de la fenêtre active (ce serait insuffisant pour la méthode OnPaint qui ne repeindrait pas les autres fenêtres) mais la fenêtre envoyée par le système (le système a repéré qu'un paint était nécessaire pour cette fenêtre-enfant). Le bitmap BM2[i] étant censé être prêt, on se contente de le recopier dans la fenêtre visée.

Si votre menu a une option "Fermer", il suffit de dupliquer ces lignes au gestionnaire correspondant mais il faut s'assurer qu'il existe au moins une fenêtre i.e. que MDIChildCount n'est pas nul sinon MDIChildren[0] ne pointerait rien et il y aurait une erreur d'exécution si aucune fenêtre n'était active à ce moment-là. Cette précaution n'a pas été nécessaire dans la méthode précédente parce qu'elle est exécutée suite au click du menu système de la fenêtre-enfant, il existe donc bien une fenêtre active au moment de l'exécution de la méthode. En revanche nous n'avons plus cette certitude dans une simple option du menu principal.

void __fastcall TForm1::FermerClick(TObject *Sender)
{
if(MDIChildCount)
{
int i=MDIChildren[0]->Tag;
delete F[i];
F[i]=NULL;
}
}

Les puristes remarqueront qu'on a dupliqué du code alors qu'une méthode peut en appeler une autre. C'est vrai donc on améliore, FormCloseE va simplement appeler FermerClick comme suit :

void __fastcall TForm1::FormCloseE(TObject *Sender,TCloseAction &Action)
{
FermerClick(Sender);
}

Si on choisit l'inverse c'est-à-dire FermerClick qui appelle FormCloseE, il faudrait envoyez à FormCloseE une variable de type TCloseAction, une variable et non une constante (e.g. caMinimize qui est d'ailleurs la valeur par défaut pour une fenêtre-enfant MDI, voir l'aide en ligne pour les différentes valeurs possibles de type TCloseAction) puisque sa déclaration dans FormCloseE se fait avec l'opérateur &. Si dans FermerClick on écrivait pas exemple FormCloseE(Sender, caMinimize), on aurait alors un warning du compilateur disant "un temporaire a été utilisé pour le paramètre Action". Pour éviter ce warning même s'il n'est pas fatal, il faudrait déclarer pour rien une variable par exemple A du type TCloseAction et écrire alors FormCloseE(Sender, A); c'est pour éviter ces difficultés d'ailleurs sans importance que nous avons opté pour l'appel inverse.

Il est bon de détruire par delete toutes les fenêtres-enfants, cela doit se faire à l'événement OnCloseQuery de Form1 (car Form1 doit encore exister pendant la destruction des fenêtres-enfants). Double-cliquez sur cet événement pour créer le gestionnaire d'événement et procédez comme suit :

void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose)
{
for(int i=0 ; i<MDIChildCount; i++) delete MDIChildren[i];
}

On utilise la variable gérée par C++Builder MDIChildCount qui représente le nombre de fenêtres-enfants ouvertes à ce moment-là. On aurait pu utiliser aussi notre tableau, par exemple :

void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose)
{
for(int i=0;i<NbFenMax;i++) delete F[i];
}

Faites F9 pour compiler-exécuter, vous constatez que vous pouvez ouvrir des fenêtres-enfants et qu'en cas de maximalisation, le menu système de la fenêtre vient bien se dessiner à droite du menu principal, ce qui est le comportement MDI type, et le nom de la fenêtre active vient s'inscrire sur la bordure de la fenêtre principale. Si vous préférez que les fenêtres-enfants soit maximalisées dès la création, il suffit de rajouter (par exemple juste après F[i]->Tag=i;) dans la méthode TForm1::NouveauClick (voir ci-dessus) l'instruction :

F[i]->WindowState=wsMaximized;

La fenêtre-enfant sera maximalisée dès sa création. Une propriété intéressante de la fenêtre principale est WindowMenu. Puisque nous avons un menu principal (quasi obligatoire rappelons-le pour les applications MDI), on aura comme dans Word une option "Fenêtre", en général placée tout à droite du menu, juste avant le point d'interrogation donnant accès à la rubrique d'aide. Cette option "Fenêtre" sera censée offrir diverses fonctions relatives à l'agencement des fenêtres. Si vous donnez à WindowMenu cette option "Fenêtre" (il suffit pour cela de la choisir dans le menu déroulant proposé) du menu principal, la gestion des fenêtres ouvertes se fera automatiquement c'est-à-dire qu'à l'exécution vous aurez la liste tenue à jour des fenêtres disponibles avec un petit signe indiquant la fenêtre active avec la possibilité d'en activer une autre puisque ces occurrences font partie du menu. C'est assez pratique puisque vous n'avez qu'à initialiser la propriété WindowMenu avec une option du menu principal pour que ça marche. C++Builder propose dans la liste déroulante associée à WindowMenu toutes les options du menu principal mais en réalité seule une fonction visible à l'écran (c'est-à-dire première d'une colonne dans le constructeur de menu) fait fonctionner ce mécanisme.

Remarquons que dans notre exemple, aucune fenêtre-enfant n'est créée à l'entrée de notre application. Si vous désirez en créer une au tout début de l'exécution, le mieux est de disposer d'une "Liste d'actions" (Composant TActionList, voir alinéa suivant), de créer l'action Ouverture (sans catégorie précise) et d'initialiser la propriété Action de la fenêtre principale Form1 avec cette méthode. D'ailleurs, dès que vous avez une liste d'actions (c'est-à-dire dès que le composant TActionList fait partie de votre projet étant déposé dans la fenêtre principale), un menu déroulant automatique en regard de la propriété Action vous invite à sélectionner une action parmi la liste. Ainsi, une fenêtre-enfant sera ouverte dès l'entrée dans l'application. Voyons donc comment fonctionne ce composant très utile et très pratique dont la vocation est de centraliser les actions importantes d'une application.

58. Le composant TActionList

Ce composant se situe sous l'onglet "standard" de la palette de composants, il se trouve normalement en dernière position, on y voit un petit rectangle à l'intérieur duquel on lit ok et une petite flèche blanche déroulant un menu. Ce petit logo est très explicite car si un bouton fait la même chose qu'une option du menu, on a tout intérêt à centraliser l'action dans une "liste d'actions". L'idée est la suivante. Pour un objet donné par exemple un bouton ou une option d'un menu, il existe une action particulière plus importante que les autres associée à cet objet, en général l'action en relation au click de l'objet. C'est pourquoi pour chaque objet il existe la propriété "Action" qui est d'ailleurs la toute première étant donné que les propriétés sont données par ordre alphabétique dans l'inspecteur d'objets. Si vous disposez d'un composant TListAction et que vous y déclariez des actions, vous aurez alors à disposition un menu déroulant à cette propriété Action et il vous suffit de choisir une action pour l'associer à cet objet. Cela vous dispense d'avoir à écrire un gestionnaire d'événement notamment OnClick. Cela dit C++Builder vous confirme dans les événements que l'action est bien liée à OnClick. Bien entendu, si vous n'utilisez pas ce composant, vous pouvez aussi appeler une méthode, comme nous l'avons fait dans l'alinéa précédent, pour éviter de dupliquer du code mais l'idée est plus de centraliser les actions. Si plusieurs objets font une certaine action, vous saurez que cette action se trouve dans la liste d'actions, vous n'aurez pas à chercher quelle méthode centralise ce code.

Ce composant est très simple à utiliser. On le dépose sur la fiche principale. Pour créer une action, on double-clique sur le composant ou bien on clique à droite en le pointant et on choisit "éditeur de liste d'actions". On tombe sur une petite fenêtre divisée en deux parties, une partie pour les catégories d'actions et l'autre pour le nom de ces actions. En effet, C++Builder a préparé pour vous des actions standard qu'on peut regrouper sous quatre catégories. Pour observer ces catégories, cliquez la petite flèche à côté de l'icône jaune (la bulle d'aide affiche "Nouvelle action") et choisissez "nouvelle action standard". Là, on vous affiche toutes les actions standard disponibles préfixées respectivement par TData, TEdit, THelp et TWindow donc des actions concernant respectivement les données, l'édition, l'aide et les fenêtres (à l'exception de la première qui est TAction et qui représente le type d'action neutre, sans catégorie). Annuler pour sortir de ce tableau puis cliquez sur l'icône jaune pour insérer une nouvelle action sans catégorie (ou bien appuyez sur la touche ins du clavier). On vous affiche "Action1" en regard de la catégorie "vide" (i.e. pas de catégorie particulière). Nous allons créer une action pour créer une fenêtre-enfant MDI, nous reprenons donc les éléments de l'alinéa précédent. Dans Name, nous donnons un nom à cette action par exemple "Ouvre" (nous ne choisissons pas "Nouveau" car on suppose que ce nom est déjà utilisé dans notre menu, voir alinéa précédent). Revenez à l'éditeur de liste d'actions et cliquez ce nouveau nom "Ouvre", C++Builder vous crée la méthode correspondant à cette action.

void __fastcall TForm1::OuvreExecute(TObject *Sender)
{

}

Là, vous écrivez le code correspondant à l'ouverture d'une fenêtre (voir méthode NouveauClick de l'alinéa précédent). Cette action est donc maintenant créée. Sélectionnons maintenant l'option "Nouveau" de notre menu, on initialise la propriété Action avec le nom de cette méthode à savoir OuvreExecute. Dans ce cas, vous n'avez pas de gestionnaire OnClick, cette déclaration en tient lieu. Si maintenant, un bouton quelconque exécute la même action, il suffira d'initialiser sa propriété Action avec la même valeur à savoir OuvreExecute, vous n'aurez rien d'autre à faire pour que ça marche immédiatement. Pour créer une fenêtre nous sommes partis d'une action sans catégorie parce que dans la catégorie Window il n'y a pas cette possibilité. En revanche, pour sa fermeture, on choisira une action standard du type TWindowClose. Le procédé est le même que pour les actions sans catégorie. Ainsi dans l'alinéa précédent, la fenêtre-enfant était liée à la méthode FormCloseE associée à l'événement OnClose. Nous avions écrit le code de cette action dans la méthode FormCloseE car nous ne disposions pas de liste d'actions. En disposant d'une liste d'actions, nous aurions créé une action de type TWindowClose que nous aurions appelée par exemple "Fermeture", en double-cliquant ce nom, C++Builder nous aurait créé la méthode et nous aurions écrit le code de fermeture d'une fenêtre-enfant comme précédemment :

void __fastcall TForm1::FermetureExecute(TObject *Sender)
{
if(MDIChildCount)
{
int i=MDIChildren[0]->Tag;
delete F[i];
F[i]=NULL;
EtatFen[i]=0;
}
}

Dans ces conditions, il nous suffit d'appeler cette action dans la méthode FormCloseE.

void __fastcall TForm1::FormCloseE(TObject *Sender,TCloseAction &Action)
{
FermetureExecute(Sender);
}

Notons au passage que nous n'aurions pas pu associer directement l'événement OnClose à FermetureExecute (c'est-à-dire écrire au moment de la création de la fenêtre-enfant F[i]->OnClose=FermetureExecute;) à cause de l'argument Action supplémentaire dans FormCloseE, les deux prototypes sont incompatibles. Un moindre mal consiste à appeler l'action de la liste comme proposé ci-dessus. En revanche, pour l'option "Fermer" de notre menu, on associe sa propriété Action avec FermetureExecute.

En résumé, quand vous développez avec une TActionList, vous créez d'abord votre action dans la liste d'actions et ce n'est qu'ensuite que vous associez cette action à la propriété Action d'un objet, en général plusieurs, car une même action s'effectue souvent par des chemins différents. Ainsi, toutes les actions de votre application sont centralisées et donc faciles à retrouver. Un autre avantage de passer par des listes d'actions est de bénéficier de comportements automatiques des composants sans aucune programmation supplémentaire. Par exemple, si un bouton est lié à une opération de fermeture de fenêtre-enfant dans une application MDI, il sera automatiquement désactivé s'il s'y a plus de fenêtre-enfant présente dans la fenêtre-parent (i.e. si MDIChildCount est nul), l'option de menu qui fermerait une fenêtre-enfant serait, elle aussi, désactivée.

Remarquons pour terminer qu'on ne peut pas insérer ni ajouter d'actions logiciellement. Le mieux, pour simuler un tel effet, est d'insérer un certain nombre d'actions à la main sans rien faire d'autre, cela réserve l'espace de déclaration à ces actions. Par exemple, on initialise normalement la section commençante de la liste d'actions puis on en insère un certain nombre en plus à la suite sans rien faire d'autre. On repère l'indice à partir duquel commence la section de ces actions non encore initialisées sachant que les indices commencent à partir de zéro (il y a ActionList1->ActionCount actions dans la liste indicées de 0 à ActionList1->ActionCount-1). Maintenant, pour affecter l'action d'indice n à une méthode du programme, on écrira :

ActionList1->Actions[n]->OnExecute=Nom_de_la_fontion_a_executer;

Si l'on veut associer le click d'un bouton à l'action d'indice n de la liste, on écrira :

Button->OnClick=ActionList1->Actions[n]->OnExecute;

59. La fonction GetCursorPos

Cette fonction renvoie les coordonnées réelles par rapport à l'écran, cela peut être utile dans certains cas, par exemple quand on veut déterminer la position d'un menu surgissant lequel est fonction de la position d'une ToolBar déplacée. Il faut déclarer une variable P de type POINT, donner &P en argument (ce qui est logique puisque la fonction doit retourner le résultat) et interroger P.x et P.y (x et y en minuscule). Ci-après l'appel de GetCursorPos suite à un click dans la fiche principale. Les coordonnées sont les coordonnées réelles par rapport à l'écran et non plus par rapport à la fiche.

void __fastcall TForm1::FormClick(TObject *Sender)
{
POINT P;
if(GetCursorPos(&P))
{
AnsiString A=IntToStr(P.x)+" "+IntToStr(P.y);
Application->MessageBox(A.c_str(),"ok",MB_OK);
}
}

60. Initialisation d'une application

En principe, ainsi que nous l'avons dit dans notre vue d'ensemble au début de ces "remarques", on initialise une application au moment du constructeur de la fiche principale TForm1::TForm1.

__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
/* initialisation ici de l'application */
}

Si, pour une raison ou une autre, vous devez prendre la main avant la construction de la classe TForm1, il faut alors utiliser Application->Initialize(). Entrez dans C++Builder et sauvez le projet vide unitxx et projetxx. Ensuite faites "Voir|Unités" (contrôle F12) et choisissez projetxx. C'est le programme maître créé par C++Builder lui-même. Vous constatez que la première instruction est Application->Initialize().

#include <vcl.h>
#pragma hdrstop
USERES("Project1.res");
USEFORM("Unit1.cpp", Form1);
//--------------------------------------------------------------------
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}

En réalité, Application->Initialize() ne fait rien (car InitProc est initialisé à NULL par défaut), on peut même supprimer cette instruction pour le cas où l'on n'en a pas besoin. Si toutefois vous voulez prendre la main avant la construction de TForm1, il faut initialiser la variable InitProc avec le nom d'une fonction d'initialisation, c'est cette fonction qui sera exécutée. Vérifions. Nous allons initialiser InitProc avec la fonction InitAppli laquelle ne fait que nous afficher un message, ce qui nous prouvera que la fonction est effectivement exécutée.

#include <vcl.h>
#pragma hdrstop
USERES("Project10.res");
USEFORM("Unit10.cpp", Form1);
void InitAppli(void);
//------------------------------------------------------------------
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
InitProc=InitAppli;
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
//-----------------------------------------------------------
void InitAppli()
{
Application->MessageBox("Initialisation de l'application","ok",MB_OK);
}

61. Gestion des exceptions

Quand une instruction risque de se retrouver en erreur, on la protège par l'instruction try et on gère l'erreur par catch. Le programme maître type proposé par C++Builder (voir alinéa précédent) en est un exemple. Il faut juste remarquer que par défaut cette gestion ne fonctionne pas sous C++Builder c'est-à-dire finalement en mode debug mais seulement en mode réel i.e. quand vous cliquez directement l'exe et ce, parce C++Builder détourne les exceptions en premier. Si vous voulez quand même tester sous C++Builder le comportement de telles exceptions, il faut faire "Outils|Options du débogueur" et décocher la case "Arrêter sur exceptions C++" en bas du formulaire puis valider. Dans ces conditions, vous pouvez tester les exceptions.

Imaginons que l'on veuille tester la création d'une zone mémoire par new, on programmera ceci :

try
{
PtrFic=new char[Longueur];
}
catch (...)
{
Application->MessageBox("Problème d'allocation dynamique",NULL,MB_ICONERROR);
}

où Longueur est une variable ou constante arbitraire. Si l'allocation réussit, le programme continue sinon il passe par le gestionnaire inauguré par catch car il y a une exception du type bad_alloc. Pour vérifier ce point, vous donnez à Longueur un nombre très grand, l'allocation sera alors refusée et vous constaterez qu'un message vous est affiché. Une fois ce test réalisé, vous n'avez plus qu'à recocher la case "Arrêter sur exceptions C++" pour continuer votre développement car il est préférable de travailler en mode protégé.

62. La méthode StretchDraw

Il s'agit d'un draw dans un rectangle. La méthode Draw ne fait que recopier tel quel un dessin (e.g. un bitmap dans un canvas) alors que StretchDraw recopie dans un rectangle. Il en résulte que si le rectangle est de dimension identique au bitmap à recopier, StretchDraw équivaut alors à un draw simple. Mais si le rectangle est de dimension différente, le bitmap est alors plus grand ou plus petit, on programme facilement des zooms de cette façon.

Voici un petit exemple. Nous déclarons un bitmap Bmp à l'intérieur duquel nous écrivons le mot Essai. Nous déclarons un rectangle R que nous initialisons en faisant coïncider le point en haut à gauche avec celui en bas à droite, autant dire que ce rectangle est néant pour le moment. On met un bouton sur Form1. À chaque click du bouton, nous allons d'une part, copier le bitmap par draw de manière à le faire apparaître tel quel et d'autre part, recopier ce bitmap par StretchDraw en augmentant ses coordonnées, on ajoute chaque fois la variable aug initialisée à 20. Vous constaterez que le bitmap grandit à chaque click.

Pour réaliser ce test, il suffit donc de poser un bouton sur Form1 (de préférence vers le haut sur la droite car l'affichage à lieu vers la gauche dans ce petit exemple) et de copier-coller le code suivant dans l'unité cpp associée.

#include <vcl.h>
#pragma hdrstop

#include "Unit10.h"
//------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
Graphics::TBitmap *Bmp;
int aug=20;
TRect R;
//---------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
Bmp=new Graphics::TBitmap();
Bmp->Height=50;
Bmp->Width=50;
Bmp->Monochrome=true;
Bmp->Canvas->TextOutA(10,10,"Essai");

R.left=120;
R.top=20;
R.right=R.left;
R.bottom=R.top;
}
//---------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
delete Bmp;
}
//---------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
R.right+=aug;
R.bottom+=aug;
Form1->FormPaint(Sender);
}
//----------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
Form1->Canvas->Draw(20,20,Bmp);
Form1->Canvas->StretchDraw(R,Bmp);
}

Remarquez que la méthode Button1Click appelle FormPaint. Ce petit programme ne fonctionnera pas immédiatement suite à ce copier-coller car les méthodes Button1Click et FormPaint ne font encore pas partie de la classe TForm1 puisqu'on est supposé partir d'une application vierge. Pour que le programme fonctionne, il faut double-cliquer sur l'événement OnClick de Bouton1 (pour que C++Builder crée le gestionnaire) et idem sur l'événement OnPaint de Form1 puis vous effacez immédiatement ces deux gestionnaires dans le cpp (ou même à la limite vous ne faites rien, ces deux gestionnaires disparaîtront d'eux-mêmes à la prochaine compilation puisqu'ils sont vides, par exemple faites alt-F9 pour compiler uniquement l'unité, les deux gestionnaires créés disparaissent). En effet, le but n'est pas d'utiliser ces gestionnaires (car ils existent déjà dans le source copié-collé) mais de déclarer ces deux méthodes dans la classe TForm1 situées dans unitxx.h. D'ailleurs, si vous faites Ctrl-F6 (contrôle F6) pour consulter le source en-tête, vous constatez que les deux méthodes Button1Click et FormPaint ont bien été insérées dans la classe TForm1.

void __fastcall Button1Click(TObject *Sender);
void __fastcall FormPaint(TObject *Sender);

Cette petite astuce de manipulation ayant été réalisée, vous pouvez tester ce petit programme.

63. Les propriétés ClientWidth et ClientHeight

La zone client de la fenêtre principale (Form1 par défaut) est donnée par les propriétés ClientWidth et ClientHeight, donc Form1->ClientWidth sera la largeur de la fenêtre et Form1->ClientHeight sera sa hauteur en nombre de pixels. Mais on constate une petite déperdition en X d'environ 10 pixels et en Y de 30 pixels. Il faut donc en tenir compte notamment si vous voulez centrer une image ou encore adresser une zone centrale avec bordure. Si vous utilisez la fenêtre sans avoir à gérer de bordure, utilisez Form1->ClientWidth et Form1->ClientHeight sans vous préoccuper de cette petite zone non visible. Nous allons tester ce point en dessinant un cadre dans la forme et en adaptant la dimension de ce cadre en cas de redimensionnement de la fenêtre de manière à ce qu'il y ait toujours une bordure à gauche, à droite, en haut et en bas de 20 pixels. Par ce petit test, nous constatons qu'effectivement les coordonnées sont correctes à gauche et en haut mais qu'il y a une déperdition d'environ 10 pixels à droite et de 30 en bas. Il s'agit de chiffres approximatifs pour obtenir un cadre toujours centré quelle que soit la dimension de la fenêtre. Entrez dans C++Builder et sauvez le projet (unitxx et projetxx). Comme nous allons travailler sur le canvas de Form1, il n'y a aucun composant à ajouter, il suffit de copier-coller le source de l'unité ci-dessous. Nous avons créé ici la méthode Dessine, il faut donc la rajouter dans la classe TForm1. Pour ce faire, allez sous éditeur de code où se trouve le source C++, faites ctrl-F6 (contrôle F6), cela provoque l'édition de l'en-tête de l'unité (si par exemple l'unité s'appelle unit17.cpp, vous aurez après ctrl-F6 le fichier unit17.h), dans la section "public", rajouter la déclaration void __fastcall Dessine(void); c'est une méthode de dessin, elle calcule les coordonnées du cadre à dessiner et à centrer.

#include <vcl.h>
#pragma hdrstop

#include "Unit17.h"
//-----------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
int Bord,FenW,FenH;
Graphics::TBitmap *BM;
const PerteX=10,PerteY=30;
//-------------------------------------------------------------
void __fastcall TForm1::Dessine()
{
TRect R;
R.left=0;
R.top=0;
R.right=FenW-1;
R.bottom=FenH-1;

BM->Width=FenW;
BM->Height=FenH;

BM->Canvas->FillRect(R);

BM->Canvas->MoveTo(Bord,Bord);
BM->Canvas->LineTo(FenW-Bord-PerteX,Bord);
BM->Canvas->LineTo(FenW-Bord-PerteX,FenH-Bord-PerteY);
BM->Canvas->LineTo(Bord,FenH-Bord-PerteY);
BM->Canvas->LineTo(Bord,Bord);

}
//-----------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
Bord=20;

BM=new Graphics::TBitmap();
BM->Monochrome=true;

FenW=Form1->ClientWidth;
FenH=Form1->ClientHeight;
}
//---------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
delete BM;
}
//--------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
Form1->Canvas->Draw(0,0,BM);
}
//---------------------------------------------------------
void __fastcall TForm1::FormCanResize(TObject *Sender, int &NewWidth,
int &NewHeight, bool &Resize)
{
FenW=NewWidth;
FenH=NewHeight;
}
//----------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
Dessine();
FormPaint(Sender);
}

La variable Bord est initialisée dans la constructeur de TForm1 à 20. Vous pouvez modifier cette valeur, c'est en nombre de pixels la bordure à gauche, à droite, en haut et en bas. On utilise deux variables FenW et FenH qui sont les dimensions de la fenêtre principale. Dans le constructeur de TForm1, on initialise ces deux variables avec Form1->ClientWidth et Form1->ClientHeight, donc la zone client de la fenêtre telle que renseignée par C++Builder. Dans la méthode FormCanResize qui reçoit les nouvelles dimensions de la fenêtre, on se contente de renseigner les deux variables générales FenW et FenH qui contiennent donc toujours les dimensions de la fenêtre. Dans le constructeur de TForm1, on déclare par new un bitmap monochrome que pointe la variable générale BM. La méthode FormPaint ne fait que recopier le bitmap BM à partir du coin haut gauche i.e. à partir de la coordonnée (0,0). C'est la méthode FormResize qui seule appelle la méthode de dessin. Comme cette méthode est appelée au début de l'exécution du programme (ce qui est logique puisqu'on dimensionne la fenêtre pour la première fois), le dessin va être créé dans le bitmap BM puis on se contente d'appeler FormPaint(Sender); ce qui revient à forcer un événement OnPaint. La méthode de dessin commence par dimensionner dans une variable R les coordonnées d'un rectangle correspondant aux dimensions de la fenêtre via les variables FenW et FenH. Le bitmap prend ces valeurs, on redimensionne donc à chaque fois le bitmap aux dimensions de la zone client de la fenêtre. On efface le bitmap par l'instruction BM->Canvas->FillRect(R); où R est notre rectangle correspondant à la zone client de la fenêtre. Puis on dessine dans le bitmap le cadre centré :

BM->Canvas->MoveTo(Bord,Bord);
BM->Canvas->LineTo(FenW-Bord-PerteX,Bord);
BM->Canvas->LineTo(FenW-Bord-PerteX,FenH-Bord-PerteY);
BM->Canvas->LineTo(Bord,FenH-Bord-PerteY);
BM->Canvas->LineTo(Bord,Bord);
Vous remarquez qu'on utilise deux constantes PerteX et PerteY qu'on a initialisé respectivement à 10 et 30 et qui représente ce qu'on perd respectivement à droite et en bas. Ce réajustement est nécessaire pour que notre cadre soit centré. Ainsi la coordonnée maximale en X est FenW-Bord-PerteX et en Y FenH-Bord-PerteY. Quant à la méthode FormDestroy, elle restitue la mémoire allouée pour le bitmap BM.

64. Création d'un exécutable indépendant

Si vous essayez de faire tourner un exécutable créé par C++Builder sur un autre ordinateur qui n'a pas C++Builder, il risque de manquer un certain nombre d'éléments notamment des DLL. Tant qu'on n'utilise le programme ou qu'on le développe sur son propre ordinateur, la question ne se pose pas car tous les fichiers nécessaires à l'exécution sont présents mais dès qu'un programme est susceptible d'être exécuté sur un ordinateur distant (par exemple suite à un téléchargement sur Internet), il faut alors rendre l'exécutable totalement autonome, il faut que l'EXE soit autosuffisant. Pour cela, deux précautions sont à prendre, d'une part ne pas utiliser la RTL dynamique et d'autre part ne pas construire l'exécutable avec les paquets d'exécution. Donc, entrez dans C++Builder et chargez votre application que vous voulez rendre indépendante, faites "Projets|options" (c'est tout en bas du menu déroulant). Puis sélectionnez l'onglet "lieur" et décochez la case "utilisez la RTL dynamique". Le mieux serait aussi de décocher les cases "informations de debogage" et "bibliothèque de débogage" puisque le programme est censé maintenant être au point. Ensuite sélectionnez l'onglet "paquets" et décochez la case "construire avec les paquets d'exécution". Le mieux serait aussi de sélectionner l'option "compilateur" et de cliquer sur la case "Finale" indiquant par là que c'est une version finale de votre programme. Mais les deux choses les plus importantes concernent la RTL et les paquets.



par Gilles Louise

Septembre 2000





Hit-Parade