Chapitre 1 : la
boucle principale |
. |
a) On nettoie |
Quand vous ouvrez Delphi, un nouveau
projet vous attend, même qu'il s'appelle Project1. Devant
vous apparaît la bête fenêtre grise par défaut, qui se contente
de se fermer quand on le luit dit, ce qui d'ailleurs provoque
l'arrêt du programme. En fait de bête fenêtre, il s'agit déjà
de tout un travail fait par notre pote Delphi, car ouvrir
une fenêtre ne se fait pas tout seul (bon, ça n'est pas dur
non plus, ne me faites pas dire ce que je n'ai pas dit). |
Or donc Delphi nous offre un travail
déjà tout fait, et pour l'en remercier, la première chose
que nous faisons, à ce projet, c'est d'y supprimer Form1,
qui malgré sa haute technologie ne nous interesse pas du tout.
On ne va pas afficher dans une fenêtre, mais à l'écran...
|
Alors pour supprimer cette fenêtre
du projet, c'est facile, vous allez dans le menu (bon, ma
version est en Français, mais c'est pas dur à traduire savez-vous?)
Projet/Retirer du projet, vous y sélectionnez Unit1/Form1,
et PAF vous faites Ok. |
Patatra, ya plus rien! Ou presque
rien, remarquez tout de même que le nom Prject1 apparaît toujours
dans la barre de titre de Delphi. C'est que, malgré les apparences,
vous n'avez pas du tout fermé le projet en cours, et qu'il
reste donc "quelque chose". |
Bon, avant toutes choses, commençons
par lui donner un nom et sauvons-le quelque part sur notre
beau disque dur. Nous allons l'appeler Effect (je suis à court
d'imagination ce soir et puis j'ai mal dormi). On fait donc
"Tout enregister" dans le menu Fichier, ce qui provoque l'apparition
de la boite de dialogue de sauvegarde vous demandant où vous
désirez (et sous quel nom) enregistrer votre oeuvre. Bon,
donc vous l'appelez Effect.Dpr (dpr pour Delphi Project je
pense, et pis si c'est autre chose on s'en fiche un peu),
vous sauvez et vous constatez que la barre de titre de Delphi
vient de prendre le nom Effect. |
Nous progressons... |
Bien, je vous disais donc que malgré
les apparences, il restait quelque chose dans ce projet. Les
personnes curieuses auront déjà essayé de lancer le programme
"pour voir", et elles ne verront rien sauf que le programme
se terminera immédiatement. |
Forcément, que voulez-vous qu'il fasse?
|
Bien, donc je vais vous apprendre
un truc (ou bien pas du tout parce que vous le savez déjà)
: en Delphi, c'est comme en Pascal, tout programme commence
par le mot clef Program. Si vous êtes attentif, le code qui
accompagne une fenêtre commence toujours par Unit. Ca n'est
donc jamais le programme principal. Mais où est donc ce fichu
programme principal? Ben c'est facile, le fichier .dpr, c'est
un fichier texte, et c'est précisément lui, le programme.
|
Bon, forcément, Delphi vous propose
de l'éditer, il vous suffit d'aller dans Projet/Voir le source.
|
C'est pas grand... |
Mais on s'y retrouve. Cette fois notre
fichier commence par Program et se termine par End. (Admirez
Delphi qui a automatiquement changé le nom en notant program
Effect) |
Analysons un peu ce que l'on a sous
les yeux. Tout d'abord, une clause Uses, bien connue des habitués
de TurboPascal. Pour ceux qui débarquent du C ou du C++, c'est
une sorte de #include, mais en mieux. (La directive $include
existant en Delphi comme en TP7). |
Vous voyez qu'on utilise Forms, c'est
l'unité de gestion des fenêtres. Késako? Pourtant, yen a pas,
de fenêtres. Mais vous devez utiliser Forms si vous voulez
utiliser cette espèce de pseudo variable objet qu'est Application.
Application c'est donc une sorte de bidule bien pratique quand
il s'agit de l'interaction entre votre programme et Windows.
Vous savez peut-être que pour quitter votre programme depuis
une fenêtre, vous appelez Application.Terminate. L'utilité
précise de tout ceci sera précisé plus loin. |
Ensuite, on voit une directive de
compilation, qui demande à Delphi d'inclure tous les fichier
RES du répertoire dans le programme. Le fichier RES contient
les informations sur les fenêtres que vous créez avec la souris
dans l'éditeur de Delphi. Comme vous avez supprimé l'unique
exemplaire, il n'y a aucun .res dans le répertoire et vous
pouvez supprimer cette ligne. |
Suivent ensuite deux lignes qui sont
Application.Initialize et Application.Run. Initialize permet
de faire des trucs marrants quand on utilise des Units qui
le sont également, mais ici on ne va pas jouer avec les subtilités
(dont j'avoue ne pas toutes les maitriser, loin de là) de
notre ami Delphi, ce qui en fait également une ligne inutile
qu'on efface, et hop! |
Et puis on a Application.Run. A l'époque
je me demandais vraiment ce que ça faisait, ce drole de truc.
Dans la situation qui nous occupe, si vous supprimiez cette
ligne, vous ne verriez aucune différence, car de toutes façons
ce programme ne fait rien. En fait Run est une boucle dont
on ne sortira qu'à la fermeture de la fenêtre principale du
programme. Comme ici il n'y a aucune fenêtre... on sort tout
de suite et le programme se termine. |
. |
b) La boucle |
Ici, nous désirons en fait reprogrammer
cette boucle, car non seulement nous désirons faire quelque
chose d'interessant malgré que nous n'avons (pour le moment)
aucune fenêtre, mais de plus nous voulons pouvoir appeler
nos procédures à chaque tour de cette boucle, comme tout programme
fait sous le bon vieux DOS. |
Alors pas de remords, virons cette
dernière ligne. (Il ne reste vraiment plus grand chose, là...)
|
Bon, alors si vous avez déjà programmé
un effet graphique sous Dos, habituellement (une fois les
initialisations de circonstances faites), cela commence par
un Repeat et termine par un Until IsTerminated (ou un boolean
du même genre). |
Bien, alors faisons comme sous Dos,
créons une variable IsTerminated de type boolean, mettons
là à false, et tapons donc ce petit programme : |
. |
Var IsTerminated:Boolean; |
Begin |
IsTerminated:=False; |
Repeat |
??? |
Until IsTerminated; |
End. |
. |
La question est à présent la suivante
: que fait-on dans la boucle et quand décide-t-on qu'il est
temps de quitter? |
C'est maintenant que je vais me faire
taper dessus : après avoir bousillé notre fenêtre, je vais
vous dire de la recréer. Mais pas de la même manière. Nous
allons créer une fenêtre sans bords (sans barre de titre ni
rien), et entièrement maximisée de manière à recouvrir tout
l'écran. La raison en est bien simple : une fois que nous
aurons un contrôle absolu sur l'écran grâce au directx, il
faudra se souvenir que nous restons sous Windows, et que par
conséquent une application pourrait décider d'afficher sa
fenêtre. Même si on sera en droit d'immédiatement réécraser
ce qu'elle vient de faire, ça sera vite très très très moche.
C'est pourquoi nous ouvrons une fenêtre recouvrant tout l'écran,
et comme nous sommes maîtres chez nous, nous sommes sûr que
rien ni personne n'ira jouer avec notre image. |
Et puis c'est facile, car grâce à
cette fenêtre, on peut recevoir les événements clavier pour
savoir quand quitter. |
. |
c) La fenêtre |
Créer sois-même une fenêtre en Delphi
n'est pas compliqué. Une fenêtre, c'est après tout une variable
comme une autre. Nous allons donc déclarer une variable de
type TForm (c'est le type fenêtre). TForm n'est pas un Type
mais une classe, nous ne pourrons donc pas directement l'utiliser,
il faudra l'initialiser en appelant son constructeur (si vous
ne voyez pas de quoi je parle, ça n'est pas grave, il vous
suffira de me croire sur parole). Donc on commencera dans
le code par MyForm:=TForm.Create(Nil); (en supposant que MyForm
soit la variable de type TForm) |
Une fois cet objet créé et initialisé,
il faut lui expliquer ce qu'on attend de lui. Ici c'est simple,
il faut lui dire "pas de bordures et taille maximale", ce
qui se traduit par : |
|
MyForm.BorderStyle:=bsNone; |
MyForm.WindowState:=wsMaximized; |
. |
Il faut à présent configurer l'événement
OnKeyPress : créez la procédure suivante : |
. |
Procedure Key(Sender: TObject; Var
Key: Char); |
Begin |
IsTerminated:=True; |
End; |
. |
Et associez l'événement à cette procédure
: |
@MyForm.OnKeyPress:=@Key; |
. |
Ca n'est pas, loin s'en faut, la manière
la plus élégante de procéder mais cela suffira... |
Ensuite on peut afficher cette fenêtre
: |
MyForm.Show; |
. |
A la sortie de la boucle, il faudra
se souvenir de supprimer la fenêtre : |
Form.Hide; |
Form.Release; |
. |
Si vous lanciez le programme
sans rien mettre dans la boucle principale, vous auriez bien
la fenêtre qui s'afficherait sur tout l'écran, mais rien ne
se passerait quand vous presseriez une touche : vous seriez
bel et bien bloqués. Car si on a dit à Delphi ce qu'il devait
se passer quand il recevait un message de pression d'une touche,
on ne lui a jamais dit de LIRE ces messages! Il faut donc
mettre dans la boucle principale l'instruction Application.ProcessMessages.
|
Notre application est
à présent celle-ci : |
. |
program Effect; |
uses Forms; |
var |
IsTerminated:Boolean;
|
MyForm:TForm; |
.. |
Procedure Key(Sender:
TObject; Var Key: Char); |
Begin |
IsTerminated:=True; |
End; |
. |
begin |
IsTerminated:=False; |
MyForm:=TForm.Create(Nil); |
MyForm.BorderStyle:=bsNone; |
MyForm.WindowState:=wsMaximized;
|
@MyForm.OnKeyPress:=@Key; |
MyForm.Show; |
Repeat Application.ProcessMessages; |
Until IsTerminated; |
MyForm.Hide; |
MyForm.Release; |
end. |
. |
Il ne nous reste qu'à
savoir comment afficher quelque chose sur cet écran vierge,
ce que nous verrons dans le chapitre
2. |
. |
d) Approfondissements
|
Le ProcessMessages peut
parraître curieux pour ceux qui n'ont pas lu le tutoriel sur
le fonctionnement du multitâche Windows, et si cela est le
cas, je vous presse vivement à en prendre connaissance (tain
je fais des tournures de phrases intelligentes ce soir). On
ferra bien la différence entre ProcessMessages et HandleMessage,
qui malgré leurs noms proches ne font pas exactement la même
chose : |
ProcessMessages va lire
UN message et l'envoyer à qui de droit, puis retourne à l'appelant.
Si aucun message n'est en attente, il retournera de suite,
ce qui fait que la boucle s'excécute sans interruption. Autrement
dit, notre application ne se trouve jamais en état de Idle.
|
HandleMessage lui, va
excécuter tous les messages se trouvants dans la queue de
l'application, puis retournera à l'appelant. Si aucun message
n'était disponible, il ne REVIENDRAIT PAS a l'appelant et
partirait en idle jusqu'à l'arrivée d'un message. Autrement
dit, si l'utilisateur ne faisait rien (et c'est bien ce que
l'on attend de lui dans un effet graphique), notre application
se bloquerait tout de suite, et n'afficherai des nouvelles
images qu'au moment où, par exemple, la souris aurait été
déplacée sur la fenêtre. Sympa... |
Pour ceux qui arrivent
tout droit du VC++, ou pour ceux qui sont curieux, on peut
remplacer ProcessMessages par : |
uses (...),Windows; |
(...) |
var Msg:TMsg; |
(...) |
Repeat |
PeekMessage(Msg,0,0,0,PM_REMOVE); |
TranslateMessage(Msg); |
DispatchMessage(Msg); |
Until IsTerminated; |
... |
. |
Et HandleMessage par :
|
uses (...),Windows; |
(...) |
var Msg:TMsg; |
(...) |
Repeat |
GetMessage(Msg,0,0,0); |
TranslateMessage(Msg); |
DispatchMessage(Msg); |
Until IsTerminated; |
... |
. |
Cela peut vous servir
si vous décidez également d'afficher votre fenêtre par un
autre moyen que TForm (CreateWindowEx), de manière à éviter
l'utilisation de l'unit Forms car celle-ci (A la différence
de Windows) est toujours inclue en entier dans le .exe et
lui donne tout de suite une taille importante (plus de 300k
pour pas grand chose). |