Minitutoriel pour C++ Builder 6

Gilles Louïse

À propos de la version 6

Au premier coup d'œil la présentation générale de ce très puissant environnement de développement C++ est globalement la même que celle de la version 5. Cela dit, un « Object TreeView » montrant la structure de l'application et synchronisé à l'inspecteur d'objets remplace avantageusement l'explorateur de classes. La fenêtre de code C++ permet maintenant l'édition immédiate du header associé par un onglet ainsi que la possibilité de créer un diagramme qui a pour double vocation d'une part, de mettre en évidence les relations logiques entre les composants visuels et non visuels (il suffit de faire glisser le composant de l'Object TreeView vers le diagramme) et d'autre part, de documenter le projet par l'ajout de commentaires à l'intérieur des schémas, au pluriel, car on peut créer autant de diagrammes différents que l'on veut mettant en scène tel ou tel type de relations. La palette de composants a été très sérieusement enrichie, des éléments plus complexes (ayant notamment trait à Internet, aux applications client-serveur ou aux bases de données) ou plus utilitaires et esthétiques en font maintenant partie, la documentation aussi très complète a beaucoup gagné en volume allant jusqu'à inclure un cours complet sur la STL (Standard Template Library). D'ailleurs un seul CD ne suffit plus pour installer C++ Builder 6, il en faut deux, on conseille également à partir de cette version 256 Mo de mémoire vive alors que 128 seulement était requis en versions antérieures.

Pour les anciennes applications en version 5, la migration est immédiate. Le logiciel prévient l'utilisateur au chargement d'un programme en version 5 qu'il va adapter l'application en version 6, il faudra simplement reconstruire le tout pour que ça marche, la compatibilité ascendante est donc parfaitement respectée, raison pour laquelle, soit dit en passant, la totalité de mes travaux sur C++Builder 5 s'applique tels quels à la version 6. ♪

- Titre : Minitutoriel pour C++ Builder 6

- Auteur : Gilles Louïse

- Parution : 1 mai 2002

- Licence : Copyright ® 2013 Gilles Louïse. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- 1. Le composant TLabeledEdit

- 2. La classe TImageList

- 3. Création de curseurs personnalisés

1. Le composant TLabeledEdit

Ce nouveau composant de la version 6 est le combiné d'un TLabel et d'un TEdit. Nous allons créer dynamiquement suite au clic d'un bouton une fenêtre modale qui contiendra deux TLabeledEdit pour accueillir des nombres entiers non nuls. Il faudra donc n'accepter que des chiffres et empêcher un zéro non significatif en première position. Dans ces conditions, nous serons certains pour chacun d'eux d'une part, d'avoir un nombre et d'autre part, que ce nombre sera non nul. Comme le traitement est identique pour ces deux composants, nous allons utiliser la même méthode de vérification pour éviter de dupliquer inutilement du code. Mais pour ce faire, il faudra « caster le sender » c'est-à-dire en meilleur français convertir le pointeur du type TObject en pointeur du type TLabeledEdit, ce qui se programme avec la directive dynamic_cast. Nous avons déjà donné un exemple de cette technique dans nos Remarques, alinéa 50, à propos des menus variables.

Nous créons donc une classe qui va contenir tous les éléments nécessaires à une telle fenêtre modale. On appelle fenêtre modale une fenêtre qui attend une réponse, on ne pourra rien faire tant que le bouton OK ou Annuler ne sera pas cliqué.

class Nombres
{
public:
TForm *f;

/* deux nombres */
TLabeledEdit *N1, *N2;

/* Boutons OK et Annuler */
TButton *pBQuit, *pBOK;

/* Méthodes */
Nombres();
~Nombres();
void __fastcall Touche(TObject*, char&);
void __fastcall Modif(TObject*);
};

Nous avons donc déclaré deux nombres N1 et N2, ce sont pour l'instant des pointeurs de TLabeledEdit, c'est la programmation qui fera que ce seront des nombres puisque nous n'allons autoriser que les chiffres. En plus des constructeur et destructeur, il y a deux méthodes, Touche qui traitera l'appui d'une touche et Modif qui traitera la modification du composant. Comme ces méthodes sont en relation avec la classe TLabeledEdit, il faut respecter un certain prototype, vous n'avez pas le choix du prototype. Touche va correspondre à l'événement OnKeyPress, il faut donc un prototype qui corresponde à cet événement. Vous trouverez ce prototype dans la documentation du composant TLabeledEdit. Ou alors, si vous préférez, vous déposez dans une fenêtre de test (par exemple dans une autre session de C++Builder) le composant TLabeledEdit, puis, dans l'inspecteur d'objets vous double-cliquez sur l'événement OnKeyPress. Là, C++Builder crée la déclaration correspondant à cet événement, il vous suffit alors de copier-coller ce prototype et de modifier son nom. Il faut simplement savoir que pour toute méthode liée à un événement, vous devez respecter le prototype de cet événement sinon vous aurez une erreur de compilation. Nous avons aussi créé deux pointeurs de TButton, un pour OK et un pour Annuler. Vous remarquez qu'il n'y a pas de méthode liée à l'événement OnClick pour ces boutons. En effet, comme la fenêtre sera modale, nous n'avons pas besoin de ces événements, il suffira au moment de la déclaration de ces boutons de renseigner la propriété ModalResult avec mrOk pour le bouton OK et mrCancel pour le bouton Annuler. Dans ces conditions, le fait d'appeler la méthode ShowModal nous renvoie cette valeur, donc soit mrOk soit mrCancel. Il suffit donc de tester cette valeur. Remarquez qu'avec C++Builder5 il faut inclure le fichier controls.hpp (#include <controls.hpp>) pour que les constantes mrOk et mrCancel soient reconnues, cette petite contrainte n'est plus nécessaire avec la version 6.

Voici maintenant le constructeur de notre classe Nombres.

Nombres::Nombres()
{
const int DepX=20, DepY=20,AugY=40;
TBorderIcons BI;

/* fenêtre */
f=new TForm(Form1);
f->BorderStyle=bsDialog;
f-> Position = poScreenCenter;
f->Width=300;
f->Height=200;
f->Color=clAqua;
f->Caption="Entrée de deux nombres";
/* suppression des boutons système */
BI=f->BorderIcons;
BI>>biSystemMenu;
f->BorderIcons=BI;

/* Nombre 1 */
N1=new TLabeledEdit(f);
N1->Parent=f;
N1->Width=50;
N1->Left =DepX;
N1->Top=DepY;
N1->MaxLength=4;
N1->EditLabel->Caption="Nombre 1";
N1->OnKeyPress=Touche;
N1->OnChange=Modif;

/* Nombre 2 */
N2=new TLabeledEdit(f);
N2->Parent=f;
N2->Width=50;
N2->Left =DepX;
N2->Top=N1->Top+AugY;
N2->MaxLength=2;
N2->EditLabel->Caption="Nombre 2";
N2->OnKeyPress=Touche;
N2->OnChange=Modif;

pBQuit=new TButton(f);
pBQuit->Parent=f;
pBQuit->Left=f->ClientWidth-80;
pBQuit->Top=f->ClientHeight-40;
pBQuit->Caption="Annuler";
pBQuit->ShowHint=true;
pBQuit->Hint="Annuler";
pBQuit->ModalResult=mrCancel;

pBOK=new TButton(f);
pBOK->Parent=f;
pBOK->Left=pBQuit->Left-80;
pBOK->Top=pBQuit->Top;
pBOK->Caption="OK";
pBOK->ShowHint=true;
pBOK->Hint="OK";
pBOK->ModalResult=mrOk;
}

La fenêtre modale a pour propriétaire Form1 c'est-à-dire la fenêtre principale (f=new TForm(Form1);). Si vous voulez éviter de donner ainsi le nom de votre fenêtre principale, vous pouvez aussi écrire (c'est peut-être même mieux) f=new TForm(Application) ; ainsi, quel que soit le nom de votre forme principale, cette syntaxe sera toujours correcte. Remarquez que f n'a pas de parent, c'est une fenêtre libre, elle a un propriétaire, mais pas de parent (le programme planterait si vous indiquiez Form1 comme parent de f). En revanche tous les objets de f auront et un propriétaire et un parent. Le propriétaire est déclaré dans l'instruction new, il est responsable de la destruction de l'objet si le programme ne l'a pas détruit. Quant au parent, il permet de savoir dessiner le composant, notamment les propriétés Left et Top sont par rapport au parent. La fenêtre apparaîtra au milieu de l'écran (f-> Position = poScreenCenter;). Nous avons supprimé les boutons système en haut à droite de la fenêtre, ainsi l'utilisateur sera obligé de cliquer soit OK soit Annuler pour sortir, il ne pourra pas sortir autrement. Pour ce faire, on passe par une variable intermédiaire BI (TBorderIcons BI;), on lit l'état du BorderIcons de f dans cette variable (BI=f->BorderIcons;), on supprime les boutons système dans la variable (BI>>biSystemMenu;) et on recopie le résultat dans la propriété de f (f->BorderIcons=BI;). La fenêtre est du type dialogue (f->BorderStyle=bsDialog;), on ne peut pas modifier ses dimensions. Remarquez que nous n'avons pas donné à la fenêtre la propriété fsStayOnTop par l'instruction f->FormStyle=fsStayOnTop; car dans ce genre de fenêtre modale, il peut y avoir une aide contextuelle c'est-à-dire que vous pouvez lier la fenêtre à une page d'un fichier d'aide. Sur la création de fichiers d'aide, voir mes Remarques, alinéa 31. Imaginons par exemple que notre fenêtre modale soit liée à la page 8 du fichier d'aide, nous écririons f->HelpContext=8; ce qui aurait pour effet d'afficher cette page en appuyant sur la touche F1 qui est la touche standard de demande d'aide. Donc, la fenêtre ne sera pas en principe du type fsStayOnTop sinon la page d'aide apparaîtrait sous la fenêtre. Par défaut, la fenêtre est normale, nous n'avons pas initialisé la propriété FormStyle. L'événement OnKeyPress de N1 renvoie à Touche (N1->OnKeyPress=Touche;) et idem pour N2, cela signifie que l'événement sera traité par la méthode Touche de la classe Nombres ayant le prototype de OnKeyPress. L'événement OnChange de N1 envoie à Modif (N1->OnChange=Modif;) et idem pour N2 donc cet événement sera traité par la méthode Modif ayant le prototype de OnChange. On a renseigné la propriété ModalResult du bouton Annuler (pBQuit->ModalResult=mrCancel;) et idem pour le bouton OK (pBOK->ModalResult=mrOk;).

Voyons maintenant le destructeur de la fenêtre modale, ce sera toujours très simple, on se contente de détruire la fenêtre f.

Nombres::~Nombres()
{
delete f;
}

C'est un des points forts de la VCL (Visual Component Library) à savoir le fait qu'on déclare un propriétaire aux objets au moment de leur instanciation (création en mémoire) et ce propriétaire est chargé de la destruction des objets. On est ainsi très proche d'une conception JAVA du C++ puisque JAVA n'a pas de destructeur, JAVA se charge lui-même de la libération mémoire. Comme tous les composants de la fenêtre f ont f pour propriétaire, il en résulte qu'en détruisant f, on détruit aussi tous les composants de f. L'intérêt est que nous n'avons pas besoin de nous poser la question, à chaque fenêtre modale dynamique, de savoir quels composants y sont instanciés, il suffit qu'ils aient f pour propriétaire, c'est tout. Ainsi, même en programmant nous-mêmes les fenêtres (car on pourrait aussi bien le faire à la main à la conception, dans ce cas on déclarerait simplement une autre fiche et on y déposerait les composants), on se maintient dans l'esprit RAD (Rapid Application Development).

Voyons maintenant la méthode Touche qui traite la touche appuyée.

void _fastcall Nombres::Touche(TObject*S, char&K)
{
if((K<48||K>57)&&K!='\b') K=NULL;
}

Vous voyez que dans le prototype, K (pour Key, touche appuyée) est envoyé comme référence grâce à l'opérateur &. Donc, K est renvoyé à l'appelant, donc on peut modifier K. Ici, nous n'acceptons que les chiffres (codes ASCII compris entre 48 et 57) ainsi que la touche BackSpace (touche d'effacement) pour pouvoir effacer un caractère. Si vous voulez en plus autoriser la sortie en appuyant sur la touche Entrée (Return) dont le code ASCII est 13, il suffit de rajouter l'instruction if(K==13) pBOK->Click(); qui simule l'appui de la touche OK. Cette méthode ne tient pas compte du composant, on ne sait pas s'il s'agit de N1 ou de N2, mais ça n'a pas d'importance, car on traite la touche appuyée en tant que telle. En revanche, pour la méthode Modif, ça a de l'importance et c'est là qu'on utilise dynamic_cast.

void _fastcall Nombres::Modif(TObject*S)
{
TLabeledEdit *l = dynamic_cast<TLabeledEdit *>(S);
int n;
if((n=l->Text.Length())!=0)
if(l->Text[1]==48)
l->Text=l->Text.SubString(2,n-1);
}

On déclare un pointeur l du type TLabeledEdit en disant qu'il représente l'adresse de l'objet envoyé (TLabeledEdit *l = dynamic_cast<TLabeledEdit *>(S);). L'adresse de l'objet S est envoyée à la méthode, mais S est du type général TObject. Tant que S est de ce type, il est impossible d'accéder aux propriétés du composant, il faut donc « caster le sender » c'est-à-dire déclarer clairement le type d'objet que cette adresse S pointe. Ainsi l est maintenant l'adresse du composant TLabeledEdit, nous ne savons pas si c'est N1 ou N2, mais quel qu'il soit nous pouvons accéder à ses propriétés. Ainsi, si la longueur du texte du composant n'est pas nulle (if((n=l->Text.Length())!=0)) puis si le premier caractère de ce texte est un zéro soit le code ASCII 48 (if(l->Text[1]==48)), dans ce cas on donne à ce texte lui-même à partir du deuxième caractère (l->Text=l->Text.SubString(2,n-1);) ce qui a pour double effet d'une part, de supprimer un zéro en première position et d'autre part, d'interdire une valeur nulle. N'oubliez pas que l'indice des AnsiString commence à 1 (la propriété Text du composant est un AnsiString).

La fenêtre est maintenant logiciellement construite, voyons comment l'appeler sur un clic de bouton.

void __fastcall TForm1::Button1Click(TObject *Sender)
{
Nombres *p= new Nombres();

if(p->f->ShowModal()==mrOk)
{
// ici l'utilisateur a cliqué OK, on traite p->N1->Text et p->N2->Text
}
delete p;
}

On instancie la classe Nombres (Nombres *p= new Nombres();) puis on teste la valeur de retour de l'affichage modal de la fenêtre f (if(p->f->ShowModal()==mrOk)). Ce test affiche donc la fenêtre et renvoie, quand l'utilisateur a appuyé soit sur OK soit sur Annuler, le code de retour. Ici il ne se passe rien, car nous nous proposons seulement de donner les syntaxes fondamentales, mais en principe le logiciel agira en cas de OK. Par exemple, il sauvegardera ces valeurs dans un fichier de type INI, voir mes Remarques, alinéa 69 et il tiendra compte de ces nouvelles valeurs saisies en appelant une fonction de traitement. Puis on détruit la classe instanciée (delete p;). Comme le destructeur de la classe détruit f et que f est propriétaire de tous ses composants, toute la mémoire est ainsi libérée.

2. La classe TImageList

Nous avons déjà parlé de cette classe dans nos Remarques, alinéa 56, mais nous proposons ici un autre type d'utilisation. Le composant TImageList gère une suite d'images indicée à partir de 0 (pour n images l'index ira donc de 0 à n-1) qui ont toutes le même format. Supposons que nous ayons chargé grâce à l'éditeur de liste d'images (revoir l'alinéa 56 sur cette question) un certain nombre de bitmaps. Nous allons montrer comment visualiser la suite de ces images et en sélectionner une. Chaque image sera affichée dans un composant TImage et on utilisera un ScrollBar pour passer à l'image suivante ou la précédente. Pensez par exemple à un logiciel de musique. L'utilisateur va choisir une clé, clé de sol, clé de fa, clé d'ut première ligne, clé d'ut deuxième ligne et ainsi de suite. On va d'abord créer tous les bitmaps correspondant à ces images avec l'éditeur d'images (alinéa 55), ces bitmaps auront par exemple pour dimensions 50x75 et seront monochromes. Une fois créés, on va les charger à la main dans un TImageList. Nous allons créer logiciellement une fenêtre qui contiendra un composant TImage, un scrollBar vertical et deux boutons OK et Annuler. Au retour, en cas de clic sur OK, nous serons en mesure de dire quelle clé a été sélectionnée par l'utilisateur par son indice. On commence par créer une classe ChoixCle.

class ChoixCle
{
public:
TForm *f;
TImage *im;
TScrollBar *s;
TButton *pBQuit, *pBOK;
ChoixCle();
~ChoixCle();
void __fastcall Modif(TObject*);
void __fastcall Montre(TObject*);
};

Il y aura donc une fenêtre f qu'on appellera modalement par l'instruction ShowModal(), une image im qui contiendra une des images de la liste d'images, un scrollbar s, deux boutons OK et Annuler. En plus des constructeur et destructeur, il y a la méthode Modif qui répondra à l'événement OnChange du scrollbar et Montre qui répondra à l'événement OnShow de la fenêtre f. Voici le constructeur de cette fenêtre.

ChoixCle::ChoixCle()
{
TBorderIcons BI;

f=new TForm(Form1);
f->BorderStyle=bsDialog;
f-> Position = poScreenCenter;
f->ClientWidth=300;
f->ClientHeight=150;
f->Color=clAqua;
f->Caption="Choix d'une clé";
BI=f->BorderIcons;
BI>>biSystemMenu;
f->BorderIcons=BI;
f->OnShow=Montre;

/* Image de la clé */
im=new TImage(f);
im->Parent=f;
im->Left=50;
im->Top=10;
im->Width=Form1->ClesBM->Width;
im->Height=Form1->ClesBM->Height;

/* ScroolBar pour le choix de la clé */
s=new TScrollBar(f);
s->Parent=f;
s->Kind=sbVertical;
s->Height=im->Height;
s->Left=im->Left+im->Width;
s->Top=im->Top;
s->Min=0;
s->Max=Form1->ClesBM->Count-1;
s->OnChange=Modif;

pBQuit=new TButton(f);
pBQuit->Parent=f;
pBQuit->Left=f->ClientWidth-90;
pBQuit->Top=f->ClientHeight-30;
pBQuit->Caption="Annuler" ;
pBQuit->ModalResult=mrCancel;

pBOK=new TButton(f);
pBOK->Parent=f;
pBOK->Left=pBQuit->Left-80;
pBOK->Top=pBQuit->Top;
pBOK->Caption="OK";
pBOK->ModalResult=mrOk;
}

L'événement OnShow de f a été associé à la méthode Montre (f->OnShow=Montre;). En effet, au moment où la fenêtre s'affichera, il faudra que l'image soit initialisée avec la première image de notre liste, ce TImageList a pour nom ClesBM (clés sous forme de bitmap). L'image aura pour dimension en X celle des images de la liste (im->Width=Form1->ClesBM->Width;) et idem pour Y (im->Height=Form1->ClesBM->Height;). Il est préférable de travailler avec des variables plutôt que de renseigner ces propriétés avec des nombres « en dur » c'est-à-dire dans le programme lui-même. La raison en est que si vous décidez de modifier les dimensions de ces images (l'éditeur d'images vous donne cette possibilité de tout recharger à partir de zéro), vous n'aurez pas à modifier le code puisqu'il lit ces dimensions. Le scrollbar sera vertical (s->Kind=sbVertical;). Sa hauteur sera celle de l'image (s->Height=im->Height;). Sa position en X sera à droite de l'image ( s->Left=im->Left+im->Width;), cela signifie qu'on ajoute au X de la position de l'image sa largeur. Sa position en Y sera la même que celle de l'image ( s->Top=im->Top;), donc le scrollbar vertical se trouvera collé à droite de l'image et sera de même hauteur que cette image. L'indice minimal du scrollbar sera 0 ( s->Min=0;) et le maximum sera le nombre d'images de la liste moins une unité, car pour n images l'indice va de 0 à n-1 ( s->Max=Form1->ClesBM->Count-1;). C'est grâce à ces propriétés que le composant saura combien il y a de positions du curseur du scrollbar. L'événement OnChange du scrollbar est relié à la méthode Modif (s->OnChange=Modif;).

Notre destructeur est universel, il se contente de détruire f et c'est tout.

ChoixCle::~ChoixCle()
{
delete f;
}

Voici ce qui se passe quand la fenêtre s'affiche.

void __fastcall ChoixCle::Montre(TObject *o)
{
Graphics::TBitmap *b=new Graphics::TBitmap();
Form1->ClesBM->GetBitmap(0,b);
im->Canvas->Draw(0,0,b);
delete b;
}

On instancie un bitmap b (Graphics::TBitmap *b=new Graphics::TBitmap();). On charge dans ce bitmap la première image de la liste ClesBM, on utilise pour ce faire la méthode GetBitmap (Form1->ClesBM->GetBitmap(0,b);). Remarquez au passage que ClesBM appartient à Form1. Or, comme on est dans une autre classe, il faut indiquer Form1 pour que le compilateur comprenne qu'il s'agit de ClesBM de Form1. Tant que vous êtes dans la classe de Form1, cette précision n'est pas nécessaire, c'est ce qui arrive souvent quand vous traitez des événements des composants de Form1, vous n'avez pas besoin de préciser Form1, mais en dehors de la classe Form1, vous êtes obligé de faire cette précision sinon le compilateur vous dira que ClesBM est inconnu. Donc b contient la première image de la liste, celle d'indice 0, on dessine donc b dans le composant image (im->Canvas->Draw(0,0,b);). Ensuite, on détruit b (delete b;). Pourquoi me direz-vous avons-nous eu besoin d'un TImage ? Pourquoi, puisque f a un canvas, n'avoir pas dessiné par draw directement dans le canvas de f ? C'est effectivement possible, mais il faut alors passer par l'événement OnPaint de f, car si on se contente d'écrire dans le canvas de f au moment du OnShow de f, le bitmap ne se redessinera pas s'il disparaît, par exemple s'il est momentanément caché par une autre fenêtre. Pour que ça marche sans TImage, il aurait fallu déclarer une méthode de paint et l'associer à l'événement OnPaint de f. En principe, un TImage est plus souple et plus logique d'autant que si on a besoin de l'événement OnClick, celui se programmera facilement alors que sans TImage, il va falloir faire des comparaisons pour savoir où se situe le curseur, ce ne serait pas très pratique. Voici ce qui se passe quand l'utilisateur modifie la position du scrollbar.

void __fastcall ChoixCle::Modif(TObject *o)
{
Graphics::TBitmap *b=new Graphics::TBitmap();
Form1->ClesBM->GetBitmap(s->Position,b);
im->Canvas->Draw(0,0,b);
delete b; }

On instancie un bitmap b (Graphics::TBitmap *b=new Graphics::TBitmap();) puis on lit dans b le bitmap indicé par s->Position, car s->Position représente le nombre compris entre Min et Max (Form1->ClesBM->GetBitmap(s->Position,b);). Puis cette image est dessinée dans TImage (im->Canvas->Draw(0,0,b);) ce qui aura pour effet qu'à chaque modification de la position du curseur du scrollbar, l'image correspondante de la liste ClesBM s'affichera. Ensuite, on libère la mémoire allouée pour le bitmap (delete b;).

Cette fenêtre va être appelée modalement au moment du clic d'un bouton (par exemple).

void __fastcall TForm1::Button1Click(TObject *Sender)
{
ChoixCle *p=new ChoixCle();
if(p->f->ShowModal()==mrOk)
{
ShowMessage(IntToStr(p->s->Position));
}
delete p;

On instancie la classe ChoixCle (ChoixCle *p=new ChoixCle();). On affiche la fenêtre et on teste le code de retour (if(p->f->ShowModal()==mrOk)), si c'est mrOK on affiche ici la position du scrollbar (ShowMessage(IntToStr(p->s->Position));), ceci nous prouve que nous maîtrisons parfaitement ce qui se passe puisque nous sommes en mesure de dire quelle image a été choisie, c'est l'image d'indice p->s->Position. Bien entendu un vrai programme fera autre chose que de nous afficher ce nombre, mais il agira en fonction de ce nombre (nous nous proposions seulement de montrer le principe).

Cela dit, quand on a la possibilité de construire logiciellement les images d'une liste d'images, on a tout intérêt à le faire par programme. En effet, ces ressources s'incorporent à l'exécutable qui grossit donc inutilement pour cette raison alors que quelques lignes de code suffisent à créer dynamiquement ces images sans augmentation notable du programme. Dans notre exemple du choix d'une clé, il suffit d'écrire les caractères musicaux dans des bitmaps et de les stocker dans un TImageList. En effet, les caractères musicaux (portée, clés, notes, silences, etc.) ne sont jamais que des caractères d'une police particulière dédiée à la musique. Dans tout système, il y a en a au moins une même si vous n'avez pas de logiciel musical (Encore, Cubase, NoteWorthyComposer, MidiNotate ou autre). Voyons donc comment adapter le programme précédent pour que ces images soient créées par logiciel pour ne pas grossir inutilement notre exécutable. Dans la classe ChoixCle nous ajoutons un pointeur vers un TImageList, donc ChoixCle a maintenant cette apparence.

class ChoixCle
{
public:
TForm *f;
TImageList *L;
TImage *im;
TScrollBar *s;
TButton *pBQuit, *pBOK;
ChoixCle();
~ChoixCle();
void __fastcall Modif(TObject*);
void __fastcall Montre(TObject*);
};

On supprime donc le composant TImageList que nous avions appelé ClesBM dans le source précédent, il ne figure donc plus dans la fenêtre principale. En effet, la liste d'images va, elle aussi, être créée dynamiquement. On l'appelle ici L, c'est un des avantages des classes, puisque d'une classe à l'autre on peut utiliser le même nom, de donner des noms très courts aux composants (L pour liste, im pour image, s pour ScrollBar et ainsi de suite), mais bien sûr, cela n'est vrai que pour les petites classes donc dotées de peu d'éléments. Dans le constructeur de ChoixCle on va simplement ajouter, par exemple juste après la création de f (il faut que f existe déjà puisque tous les composants de f auront f pour propriétaire), la création logicielle du TImageList.

/* Création des images dans la liste L */
L=new TImageList(f);
L->Width=WBMCle;
L->Height=HBMCle;
CreatIm(L);

Vous voyez que la liste d'images n'a pas de parent ce qui est logique, car ce n'est pas un composant visuel, le parent servant en fait à afficher le composant puisque les propriétés Left et Top sont par rapport au parent. On donne ensuite les dimensions des images de la liste, ici WBMCle et HBMCle qui sont des constantes. On appelle ensuite la fonction CreatIm qui va créer les images et les stocker dans le composant TImageList, la fonction envoie L en paramètre à savoir la liste d'images qui vient d'être créée par new. Voyons comme va se structurer cette fonction.

void CreatIm(TImageList *L)
{
Graphics::TBitmap* b = new Graphics::TBitmap();
Graphics::TBitmap* c = new Graphics::TBitmap();

/* bitmap de lignes de portée et de clé */
b->Width=L->Width;
b->Height=L->Height;
b->Monochrome=true;
b->Canvas->Font->Name=FontGP;
b->Canvas->Font->Size=16;

/* chiffres additionnels dans le bitmap c */
c->Width=L->Width;
c->Height=L->Height;
c->Monochrome=true;
c->Canvas->Font->Name="Arial";
c->Canvas->Font->Size=8;
c->Canvas->Font->Style=c->Canvas->Font->Style<<fsBold;

/* Création des 13 clés */
for(int i=0;i<13;i++) CreatIm1(L,i,b,c);

delete c;
delete b;
}

On commence par déclarer deux bitmaps b et c. Pourquoi deux ? Parce qu'un canvas n'a qu'une seule police de caractères. Or, il se peut que nous ayons à utiliser plusieurs polices dans la construction logicielle de ces images. En effet, certaines clés sont dotées de chiffres additionnels, par exemple la clé de sol avec un petit 8 en dessous pour indiquer qu'on est une octave en dessous de la clé de sol normale, clé utilisée pour la guitare. Or, bien que les polices musicales disposent de chiffres (ce sont d'ailleurs les mêmes codes ASCII que pour les polices standard à savoir les codes compris en 48 et 57), il se peut que le graphique de ces chiffres ne vous convienne pas d'autant qu'ils vont devoir être de la même grandeur que les autres éléments graphiques. En créant deux bitmaps, on a deux polices différentes. Remarquez que les bitmaps ont pour dimension les dimensions de la liste soit L->Width et L->Height. Remarquez que les caractères du bitmap c seront en gras (c->Canvas->Font->Style=c->Canvas->Font->Style<<fsBold;). On appelle ici FontGP la police musicale utilisée (c'est une constante du type AnsiString, nous n'indiquons pas ici laquelle, ça n'a aucune importance) et on utilisera « Arial », soit une police tout à fait standard, pour les chiffres additionnels. Ensuite on appelle 13 fois la fonction CretaIm1 qui va créer une à une ces images (for(int i=0;i<13;i++) CreatIm1(L,i,b,c);). La fonction envoie en argument L la liste d'images, i l'indice de l'image à créer et les deux bitmaps b et c. Voyons maintenant la structure de cette fonction CretaIm1 dans ses grandes lignes.

void CreatIm1(TImageList *L,
int n,
Graphics::TBitmap* b,
Graphics::TBitmap* c
)
{
const x=10, y=20, d=7;
const AnsiString Por = AnsiString::StringOfChar(LigPor,2);

/* Il faut repréciser BrushColor avant chaque FillRect */
b->Canvas->Brush->Color=clWhite;
b->Canvas->FillRect(Rect(0,0,b->Width,b->Height));
b->Canvas->Brush->Style=bsClear;
b->Canvas->CopyMode=cmSrcAnd;
b->Canvas->TextOutA(x,y,Por);

c->Canvas->Brush->Color=clWhite;
c->Canvas->FillRect(Rect(0,0,c->Width,c->Height));
c->Canvas->Brush->Style=bsClear;

switch(n)
{
/* sol 16+ */
case 0:
b->Canvas->TextOutA(x+d,y,CleSol);
c->Canvas->TextOutA(x+8,y-14,"16");
b->Canvas->Draw(0,0,c);
break;

/* sol 8+ */
case 1:
b->Canvas->TextOutA(x+d,y,CleSol);
c->Canvas->TextOutA(x+12,y-14,"8");
b->Canvas->Draw(0,0,c);
break;

/* sol */
case 2:
b->Canvas->TextOutA(x+d,y,CleSol);
break;

/* sol 8- */
case 3:
b->Canvas->TextOutA(x+d,y,CleSol);
c->Canvas->TextOutA(x+11,y+36,"8");
b->Canvas->Draw(0,0,c);
break;

/*AUTRES CAS À INSÉRER ICI */
}

/* Ajout de l'image à la liste d'images */
L->Add(b,NULL);
}

On déclare trois constantes (const x=10, y=20, d=7;), x et y pour indiquer la coordonnée d'écriture du caractère « portée » et d'un déplacement d pour écrire la clé. Ici nous avons choisi 7, il y aura donc 7 pixels entre le début de la portée et la clé. Ensuite une constante du type AnsiString qui contient deux fois le caractère portée (const AnsiString Por = AnsiString::StringOfChar(LigPor,2);), LigPor étant le code ascii de ce caractère. Ensuite on précise la couleur de remplissage du bitmap b à savoir le blanc (b->Canvas->Brush->Color=clWhite;), cette couleur est à réindiquer avant chaque remplissage. On remplit donc le bitmap (b->Canvas->FillRect(Rect(0,0,b->Width,b->Height));), le bitmap est ainsi réinitialisé, c'est un rectangle blanc. Ensuite on indique le style de brush ici bsClear (b->Canvas->Brush->Style=bsClear;), cela signifie que quand on écrira dans le bitmap des caractères, on n'écrira que le caractère et non pas son fond. Ainsi, quand on écrira la clé de sol par exemple, le fond à savoir les lignes de portées ne sera pas effacé, la clé de sol viendra en quelque sorte s'ajouter. Ensuite on indique le mode de recopie, on utilise ici le ET logique (b->Canvas->CopyMode=cmSrcAnd;). Cela signifie que quand nous allons copier le bitmap c dans b, il faudra procéder par un Et logique au bit à bit ce qui équivaudra à un ajout puisque le fond est blanc. En effet, le blanc correspond à la valeur maximale en binaire avec des 1 partout. Au moment de copier dans b le bitmap c, si un pixel de c est blanc, il le restera, car 1 ET 1 donne 1 mais s'il est d'une autre couleur, alors le ET logique forcera cette couleur en imposant des zéros à b là ou il y a des zéros dans c. Notez que ces propriétés Brush->Style et CopyMode sont à réindiquer après chaque remplissage, c'est pourquoi ces instructions se situent ici en non pas dans le programme appelant qui a initialisé les bitmaps. Puis, on écrit dans b les lignes de portées (b->Canvas->TextOutA(x,y,Por);), le bitmap b ne contient donc pour l'instant que les lignes de portées sur fond blanc. On réinitialise ensuite le bitmap c de la même façon que b en indiquant la couleur de remplissage en blanc, en remplissant puis en indiquant la caractéristique bsClear. Tout est maintenant prêt pour la création de l'image, b contient déjà les lignes de portées, car toutes les images vont avoir ces lignes et c est un rectangle blanc. On entre alors dans une structure de type switch/case en fonction de l'indice i envoyé en argument. Ceci va nous permettre de bien détailler ce qui va se passer pour chaque clé. La première clé d'indice 0 va être la clé de sol avec un 16 au-dessus pour indiquer deux octaves au-dessus de l'écriture normale. On dessine donc la clé de sol dans b (b->Canvas->TextOutA(x+d,y,CleSol);), CleSol étant une constante AnsiString contenant cette clé. Puis on écrit dans c, donc dans une autre police et une autre taille, le nombre « 16 » (c->Canvas->TextOutA(x+8,y-14,« 16 »);) en opérant les décalages nécessaires en x et en y (+8 en x et -14 en y) puis on copie c dans b (b->Canvas->Draw(0,0,c);), ce qui va avoir pour effet de rajouter à b le « 16 » contenu dans c. À chaque indice correspondra donc un type de construction. Puis, au sortir du switch/case, on enregistre l'image b dans la liste L (L->Add(b,NULL);). C'est fini, nous avons notre liste créée logiciellement. Un autre avantage de la création par programme est aussi qu'il est très facile de procéder à des modifications. Si l'on veut par exemple des chiffres d'une autre taille ou d'une autre police, il suffit de modifier le source alors qu'il est assez fastidieux de faire tout cela à la main. En résumé, TImageListe contiendra plutôt de vraies images ou des bitmaps dessinés, mais pour des éléments systématiques, on préférera la création par programme.

3. Création de curseurs personnalisés

C++Builder6 offre un certain nombre de curseurs prédéfinis, vous pouvez voir cette liste dans l'inspecteur d'objets de la forme principale (Form1 par défaut) à la propriété Cursor. Cette liste est également disponible dans la documentation à la rubrique « TCursor, type » qui indique tous les curseurs et les noms que l'on peut utiliser dans les programmes. Le curseur par défaut est donné par la constante crArrow (flèche). Pour changer de curseur à un moment donné, il suffit d'affecter la variable Cursor (qui correspond à Screen->Cursor ou même à Form1->Cursor) avec le nom de la constante voulue. Par exemple pour avoir le sablier, on écrira simplement

Cursor=crHourGlass;

Notez que tous les indices sont négatifs (0 correspond au curseur courant quel qu'il soit), ces indices négatifs valent pour le tableau Screen->Cursors[i] qui contient tous les curseurs disponibles. C'est ainsi que la constante crHourGlass correspond à l'indice -11 donc au curseur Screen->Cursors[-11]. Cette méthode a pour but évident de laisser les indices positifs à l'utilisateur. Si donc vous créez des curseurs, ceux-ci devront être écrits dans le tableau prévu à cet effet à savoir Screen->Cursors[i] avec i>0. Nous pouvons immédiatement vérifier ce point comme suit : on recopie le curseur crHourGlass à l'indice 1 (constante MonCurseur) puis on dit que le curseur devient celui d'indice MonCurseur.

const MonCurseur=1;
HCURSOR cur;
cur=Screen->Cursors[crHourGlass];
Screen->Cursors[MonCurseur]=cur;
Cursor=(TCursor)MonCurseur;

On déclare une instance cur de curseur (HCURSOR cur;), on recopie le curseur correspondant à crHourGlass dans cette variable (cur=Screen->Cursors[crHourGlass];), on renseigne l'indice 1 du tableau (Screen->Cursors[MonCurseur]=cur;) puis on indique le nouveau curseur actuel qui sera celui d'indice 1 à savoir celui que l'on vient d'écrire dans le tableau (Cursor=(TCursor)MonCurseur;). Cette séquence n'a évidemment aucune utilité si ce n'est celle de constater que les indices positifs nous appartiennent et qu'ils sont prêts à accueillir nos curseurs personnels.

Pour créer de nouveaux curseurs, il y a deux possibilités : soit les dessiner à la main puis les utiliser par programme, soit les créer logiciellement. Quand vos curseurs sont créés manuellement ou logiciellement, vous avez encore deux possibilités pour changer de curseur : soit charger tous les curseurs disponibles à l'initialisation de l'application et jouer avec leurs indices en affectant à chaque changement de curseur le nouvel indice à la variable Cursor, soit ne charger que celui qui vous intéresse à un moment donné et n'utiliser que l'indice 1 du tableau. Dans ce dernier cas, bien que la variable Cursor ne change pas puisqu'elle sera toujours égale à 1 (constante MonCurseur), il faudra quand même affecter la variable Cursor à 1, car c'est à ce moment-là que le curseur est effectivement modifié (et non pas au moment où le tableau de curseurs est affecté).

Créons d'abord des curseurs en les dessinant. Pour ce faire, on édite les ressources du projet via Outils|Éditeur d'image. Vous entrez ainsi dans un programme annexe, l'éditeur d'images. Faites Ouvrir, l'éditeur d'images vous propose spontanément les ressources de votre projet. Cliquez ces ressources (par défaut Projet1.res). Pour l'instant, si vous n'y avez pas encore touché, ces ressources ne contiennent que le logo par défaut de l'application (icône|MAINICONE). Faites Ressource|Nouvelle puis choisissez Curseur dans la liste déroulante, l'éditeur crée un curseur appelé Curseur1. Vous pouvez aussi cliquer à droite en pointant la fenêtre-ressource puis choisir Nouvelle puis Curseur, le résultat sera le même. La toute première chose à faire est de renommer ce curseur, pour ce faire on clique à droite en pointant son nom puis on choisit Renommer et on donne un nouveau nom à ce curseur. La raison en est que son nom doit n'être constitué que de majuscules, à cet égard le nom donné par défaut est trompeur, car si vous gardiez ce nom de « Curseur1 » vous ne pourriez jamais y accéder. Donc renommez-le, le programme convertira d'ailleurs les minuscules en majuscules s'il y en a dans le nouveau nom entré. Double-cliquez maintenant sur ce nouveau nom, on accède alors au bitmap de 32x32, vous pouvez dessiner votre nouveau curseur. Notez qu'au départ la matrice n'est constituée que de pixels bleus, le bleu étant la couleur de transparence. Le curseur n'a que deux couleurs réelles, le noir et le blanc. On sélectionne le noir en cliquant sur le petit carré noir et le blanc en cliquant sur le petit carré blanc, la couleur sélectionnée étant visible sur le carré le plus à gauche. Vous disposez de deux autres pseudocouleurs, la couleur de transparence (logo représentant un S rouge à gauche du carré blanc) et le reverse vidéo juste au-dessus donné par la couleur orange. Vous pouvez ajouter autant de curseurs que vous voulez, choisissez des noms différents pour chacun d'eux. Le curseur étant dessiné, il est bon de définir le point actif de la matrice, pour ce faire faites Curseur|définir le point actif, il suffit d'entrer les coordonnées de ce point et vous pouvez tester ce résultat par une simulation par Curseur|Tester. Quittez l'éditeur d'images et sauvegardez ces ressources. Une fois sorti de l'éditeur d'images et donc revenu à C++ Builder, il sera bon de fermer tous les fichiers ou encore de sortir carrément de C++ Builder, car ces modifications de ressources ne seront prises en compte qu'au prochain chargement du projet. Ensuite, ayant rechargé le projet, il faut tout reconstruire avec ces nouvelles ressources, faites Projet|Construire <nom_du_projet>, cela force la reconstruction totale du projet. Voici maintenant comment on procède pour utiliser logiciellement ces curseurs, il suffit d'écrire ceci :

const MonCurseur = 1;
Screen->Cursors[MonCurseur] = LoadCursor(HInstance, "NOM_DU_CURSEUR");
Cursor = (TCursor)MonCurseur;

On n'utilise ici que l'indice 1 (constante MonCurseur), on charge le curseur voulu par LoadCursor en l'affectant à l'indice 1 du tableau Screen->Cursors[i], puis on affecte la variable Cursor avec ce nombre. Il faut donc se souvenir de quatre choses pour que ça marche :

1. Renommer immédiatement le curseur, il aura alors un nom en majuscules ;

2. Ne pas oublier de sauvegarder ces nouvelles ressources avant de quitter ou en quittant l'éditeur d'images, au besoin entrer à nouveau dans l'éditeur d'images pour constater que les curseurs ont bien été enregistrés. En effet, s'ils ne l'étaient pas, le LoadCursor n'aurait aucun effet, mais ne provoquerait pas d'erreur à l'exécution, mais aucun curseur ne serait visible ;

3. Après avoir créé vos curseurs, fermer tous les fichiers (Fichier|Tout fermer) ou quitter C++ Builder puis, après être entré de nouveau, reconstruire l'ensemble du projet (Projet|construire) de manière à bien compiler ces ressources nouvelles ;

4. Le nom en majuscules du curseur que vous invoquez dans le LoadCursor doit correspondre à un des noms de curseurs que vous avez créés.

Si ces quatre conditions sont réunies, ça marchera immédiatement.

Voyons maintenant comment construire des curseurs intégralement par logiciel. On passe par la fonction CreateCursor de l'API Windows. Voici l'exemple que donne la documentation.

HCURSOR cur;

// ET logique
BYTE ANDmaskCursor[] =
{
0xFF, 0xFC, 0x3F, 0xFF, // ligne 1
0xFF, 0xC0, 0x1F, 0xFF, // ligne 2
0xFF, 0x00, 0x3F, 0xFF, // ligne 3
0xFE, 0x00, 0xFF, 0xFF, // ligne 4
0xF7, 0x01, 0xFF, 0xFF, // ligne 5
0xF0, 0x03, 0xFF, 0xFF, // ligne 6
0xF0, 0x03, 0xFF, 0xFF, // ligne 7
0xE0, 0x07, 0xFF, 0xFF, // ligne 8
0xC0, 0x07, 0xFF, 0xFF, // ligne 9
0xC0, 0x0F, 0xFF, 0xFF, // ligne 10
0x80, 0x0F, 0xFF, 0xFF, // ligne 11
0x80, 0x0F, 0xFF, 0xFF, // ligne 12
0x80, 0x07, 0xFF, 0xFF, // ligne 13
0x00, 0x07, 0xFF, 0xFF, // ligne 14
0x00, 0x03, 0xFF, 0xFF, // ligne 15
0x00, 0x00, 0xFF, 0xFF, // ligne 16
0x00, 0x00, 0x7F, 0xFF, // ligne 17
0x00, 0x00, 0x1F, 0xFF, // ligne 18
0x00, 0x00, 0x0F, 0xFF, // ligne 19
0x80, 0x00, 0x0F, 0xFF, // ligne 20
0x80, 0x00, 0x07, 0xFF, // ligne 21
0x80, 0x00, 0x07, 0xFF, // ligne 22
0xC0, 0x00, 0x07, 0xFF, // ligne 23
0xC0, 0x00, 0x0F, 0xFF, // ligne 24
0xE0, 0x00, 0x0F, 0xFF, // ligne 25
0xF0, 0x00, 0x1F, 0xFF, // ligne 26
0xF0, 0x00, 0x1F, 0xFF, // ligne 27
0xF8, 0x00, 0x3F, 0xFF, // ligne 28
0xFE, 0x00, 0x7F, 0xFF, // ligne 29
0xFF, 0x00, 0xFF, 0xFF, // ligne 30
0xFF, 0xC3, 0xFF, 0xFF, // ligne 31
0xFF, 0xFF, 0xFF, 0xFF // ligne 32
};

// Reverse video
BYTE XORmaskCursor[] =
{
0x00, 0x00, 0x00, 0x00, // ligne 1
0x00, 0x03, 0xC0, 0x00, // ligne 2
0x00, 0x3F, 0x00, 0x00, // ligne 3
0x00, 0xFE, 0x00, 0x00, // ligne 4
0x0E, 0xFC, 0x00, 0x00, // ligne 5
0x07, 0xF8, 0x00, 0x00, // ligne 6
0x07, 0xF8, 0x00, 0x00, // ligne 7
0x0F, 0xF0, 0x00, 0x00, // ligne 8
0x1F, 0xF0, 0x00, 0x00, // ligne 9
0x1F, 0xE0, 0x00, 0x00, // ligne 10
0x3F, 0xE0, 0x00, 0x00, // ligne 11
0x3F, 0xE0, 0x00, 0x00, // ligne 12
0x3F, 0xF0, 0x00, 0x00, // ligne 13
0x7F, 0xF0, 0x00, 0x00, // ligne 14
0x7F, 0xF8, 0x00, 0x00, // ligne 15
0x7F, 0xFC, 0x00, 0x00, // ligne 16
0x7F, 0xFF, 0x00, 0x00, // ligne 17
0x7F, 0xFF, 0x80, 0x00, // ligne 18
0x7F, 0xFF, 0xE0, 0x00, // ligne 19
0x3F, 0xFF, 0xE0, 0x00, // ligne 20
0x3F, 0xC7, 0xF0, 0x00, // ligne 21
0x3F, 0x83, 0xF0, 0x00, // ligne 22
0x1F, 0x83, 0xF0, 0x00, // ligne 23
0x1F, 0x83, 0xE0, 0x00, // ligne 24
0x0F, 0xC7, 0xE0, 0x00, // ligne 25
0x07, 0xFF, 0xC0, 0x00, // ligne 26
0x07, 0xFF, 0xC0, 0x00, // ligne 27
0x01, 0xFF, 0x80, 0x00, // ligne 28
0x00, 0xFF, 0x00, 0x00, // ligne 29
0x00, 0x3C, 0x00, 0x00, // ligne 30
0x00, 0x00, 0x00, 0x00, // ligne 31
0x00, 0x00, 0x00, 0x00 // ligne 32
};

cur=CreateCursor(HInstance, 0,0,32,32,ANDmaskCursor,XORmaskCursor);
Screen->Cursors[MonCurseur] =cur;
Cursor = (TCursor)MonCurseur;

La fonction CreateCursor reçoit sept arguments, HInstance (handle d'instance courante), les coordonnées du spot c'est-à-dire du point actif (ici 0,0), les dimensions de la matrice qui accueille le curseur (ici 32,32), la table du ET logique (le curseur en lui-même) et une table de reverse vidéo (Ou exclusif). Pour plus d'informations sur cette technique, posez le curseur sur le mot CreateCursor, faites F1 puis OverView puis cliquez sur page suivante (icône >>) jusqu'à tomber sur la page intitulée « Creating a cursor », vous y verrez cet exemple, mais aussi la table de vérité symbolisant la signification des éléments. Bien entendu, il reste très difficile d'exploiter cette méthode, car il faut dessiner le curseur puis convertir en codes hexadécimaux. Il existe une autre méthode qui consiste à créer par programme la table du ET logique (qui correspond au curseur en tant que tel) à condition que le dessin du curseur soit un caractère d'une police quelconque. Ainsi, on écrit ce caractère dans un bitmap puis on parcourt ce bitmap pour créer la matrice binaire. Imaginez un logiciel musical, le curseur peut par exemple être une noire ou une croche que l'utilisateur pose sur une portée. Or, ces symboles sont accessibles par des polices spécifiques par exemple Maestro ou Anastasia et bien d'autres encore. Dans l'exemple ci-dessus, nous nous limiterons à une lettre de l'alphabet (la lettre A de la police Arial en taille 16), le principe sera le même, quel que soit le caractère choisi. Voici cette séquence que vous pourrez adapter à votre guise.

int i,j,k;
Graphics::TBitmap *b=new Graphics::TBitmap();
char a[128];
char x[128];
unsigned char m;

b->Width=32;
b->Height=32;
b->Monochrome=true;
b->Canvas->Font->Name="Arial";
b->Canvas->Font->Size=16;
b->Canvas->TextOutA(10,10,"A");

for(i=0;i<128;i++) a[i]=255;

m=127;
k=0;
for(i=0;i<b-> Height;i++)
for(j=0;j<b-> Width;j++)
{
if(b->Canvas->Pixels[j][i]==clBlack) a[k]&=m;
if(m==254)
{
m=127;
k++;
}
else
{
m>>=1;
m+=128;
}
}

for(i=0;i<128;i++) x[i]=0;

Screen->Cursors[MonCurseur]=CreateCursor(HInstance,0,0,32,32,a,x);
Cursor = (TCursor)MonCurseur;

delete b;

Il y a trois moments, la création du caractère dans un bitmap, la déduction de la matrice et l'appel de CreateCursor. On commence par déclarer un bitmap b (Graphics::TBitmap *b=new Graphics::TBitmap();). Ce bitmap est de 32 sur 32, on indique la largeur (b->Width=32;) puis sa hauteur (b->Height=32;). Le bitmap est monochrome (b->Monochrome=true;). La police d'écriture sera Arial (b->Canvas->Font->Name=« Arial »;), en taille 16 (b->Canvas->Font->Size=16;). On écrit dans ce bitmap en (10,10) la lettre A (b->Canvas->TextOutA(10,10,« A »);). Notez qu'à la création du bitmap, celui-ci est tout blanc, cela signifie que tous les bits sont au 1 logique. En écrivant un A, les bits touchés par ce A seront au 0 logique. Nous allons maintenant créer la matrice correspondant au dessin situé dans le bitmap b. C'est pourquoi nous avons déclaré une suite de 128 octets pour la matrice correspondant au ET logique (char a[128];) et idem pour la matrice correspondant au OU exclusif logique (char x[128];). Il y a bien 128 octets, car chaque ligne a 32 pixels soit 32 bits donc 4 octets. Puisqu'il y a 32 lignes, la matrice se constitue de 32x4 soit 128 octets. On met tous les bits de la matrice au 1 logique (for(i=0;i<128;i++) a[i]=255;). Maintenant, nous parcourons le bitmap pixel après pixel et nous écrirons un 0 logique au bon endroit si un pixel noir est rencontré. Pour ce faire, nous allons utiliser un masque m sur 8 bits (unsigned char m;). Ce masque est initialisé avec la valeur 127 (m=127;) soit en binaire 01111111, ce qui signifie simplement que le bit7 de m est au 0 logique. On initialise un pointeur k à 0 (k=0;), k est un pointeur d'écriture dans la matrice a, le couple (k,m) indique en réalité le bit pointé c'est-à-dire le bit de la matrice qui sera mis au 0 logique si la couleur noire est détectée. On boucle en hauteur par l'entier i (for(i=0;i<b-> Height;i++)), pour chaque ligne on boucle en largeur par la variable j (for(j=0;j<b-> Width;j++)), pour tous les couples (j,i) on interroge le pixel pour savoir s'il est noir (if(b->Canvas->Pixels[j][i]==clBlack)). Si c'est vrai, on met au 0 logique le bit pointé par le couple k et m (a[k]&=m;), cette instruction calcule un ET logique entre l'octet pointé par k soit a(k) et le masque m et stocke le résultat de ce ET logique dans a(k), cela équivaut à mettre au 0 logique le bit visé par m dans a(k). À ce stade, qu'il y ait eu ou non mise au 0 logique, il faut procéder à la rotation à droite de m. On regarde d'abord si m est égal à 254 ou non (if(m==254)), en effet 254 correspond à 11111110, cela signifie que le 0 de m a pris toutes les positions successives et qu'il faut le réinitialiser à 127 soit 01111111 de manière à pointer le bit 7 de l'octet suivant. Donc si m=254, on remet m à 127 (m=127;) et k est incrémenté (k++;), cela signifie que le couple (k,m) pointe le bit 7 de l'octet suivant. Si m n'est pas égal à 254, alors on décale m sur la droite (m>>=1;), mais comme ce décalage se fait avec un bit 7 entrant au 0 logique, on remet ce bit 7 au 1 logique en y ajoutant 128 (m+=128;). Ainsi, m=01111111 la première fois puis il sera égal à 10111111 pointant ainsi le bit 6 puis 11011111 pointant ainsi le bit 5, etc. jusqu'à 11111110 (254) auquel cas on le remet à sa position de départ soit 01111111 (127) avec incrémentation de k dans ce cas, car on pointe l'octet suivant. Par ce mécanisme, on accède à tous les bits si nécessaire de la matrice a. À ce stade, la matrice a est construite en fonction de ce que contient le bitmap. On remet à zéro la matrice correspondant au OU exclusif (for(i=0;i<128;i++) x[i]=0;), car nous n'utilisons pas le reverse vidéo, on ne crée qu'un curseur tout simple. Les deux matrices a et x étant initialisées, on affecte le curseur au tableau (Screen->Cursors[MonCurseur]=CreateCursor(HInstance,0,0,32,32,a,x);) puis on affecte la variable Cursor (Cursor = (TCursor)MonCurseur;), bien que la valeur de Cursor ne change pas puisque nous n'utilisons que l'indice 1, c'est à ce moment précis que le curseur change de forme, il devient la lettre A en taille 16 de la police Arial. On détruit maintenant le bitmap qui ne nous sert plus (delete b;). À partir de là, il est très facile de manipuler tous les curseurs possibles et imaginables, qu'ils soient dessinés à la main ou créés logiciellement.