Article soumis par Marcus Junglas
Lors de la programmation d'un gestionnaire d'événements dans Delphi (comme le Sur clic en cas de TButton), il arrive un moment où votre application doit être occupée pendant un certain temps, par ex. le code doit écrire un gros fichier ou compresser certaines données.
Si vous faites cela, vous remarquerez que votre application semble bloquée. Votre formulaire ne peut plus être déplacé et les boutons ne montrent aucun signe de vie. Il semble être écrasé.
La raison en est qu'une application Delpi est monothread. Le code que vous écrivez ne représente qu'un tas de procédures qui sont appelées par le thread principal de Delphi chaque fois qu'un événement se produit. Le reste du temps, le thread principal gère les messages système et d'autres choses comme les fonctions de gestion des formulaires et des composants.
Par conséquent, si vous ne terminez pas la gestion de vos événements en effectuant un long travail, vous empêcherez l'application de gérer ces messages.
Une solution courante pour ce type de problème est d'appeler "Application". ProcessMessages ". "Application" est un objet global de la classe TApplication.
L'application. Processmessages gère tous les messages en attente tels que les mouvements de fenêtre, les clics sur les boutons, etc. Il est couramment utilisé comme une solution simple pour faire fonctionner votre application.
Malheureusement, le mécanisme derrière "ProcessMessages" a ses propres caractéristiques, ce qui pourrait provoquer une grande confusion!
Qu'est-ce que ProcessMessages?
PprocessMessages gère tous les messages système en attente dans la file d'attente de messages des applications. Windows utilise des messages pour "parler" à toutes les applications en cours d'exécution. L'interaction de l'utilisateur est apportée au formulaire via des messages et "ProcessMessages" les gère.
Si la souris descend sur un TButton, par exemple, ProgressMessages fait tout ce qui doit se passer sur cet événement comme le repeindre le bouton à un état "enfoncé" et, bien sûr, un appel à la procédure de gestion OnClick () si vous en avez affecté un.
C'est le problème: tout appel à ProcessMessages peut contenir à nouveau un appel récursif à n'importe quel gestionnaire d'événements. Voici un exemple:
Utilisez le code suivant pour le gestionnaire OnClick pair d'un bouton ("work"). L'instruction for simule un long travail de traitement avec quelques appels à ProcessMessages de temps en temps.
Ceci est simplifié pour une meilleure lisibilité:
{dans MyForm:}
WorkLevel: entier;
{OnCreate:}
WorkLevel: = 0;
procédure TForm1.WorkBtnClick (expéditeur: TObject);
var
cycle: entier;
commencer
inc (WorkLevel);
pour cycle: = 1 à 5 faire
commencer
Memo1.Lines. Ajouter ('- Work' + IntToStr (WorkLevel) + ', Cycle' + IntToStr (cycle);
Application. ProcessMessages;
sommeil (1000); // ou un autre travail
fin;
Memo1.Lines. Add ('Work' + IntToStr (WorkLevel) + 'terminé.');
dec (WorkLevel);
fin;
SANS "ProcessMessages", les lignes suivantes sont écrites dans le mémo, si le bouton a été enfoncé deux fois en peu de temps:
- Travail 1, Cycle 1
- Travail 1, cycle 2
- Travail 1, cycle 3
- Travail 1, cycle 4
- Travail 1, cycle 5
Le travail 1 s'est terminé.
- Travail 1, Cycle 1
- Travail 1, cycle 2
- Travail 1, cycle 3
- Travail 1, cycle 4
- Travail 1, cycle 5
Le travail 1 s'est terminé.
Pendant que la procédure est occupée, le formulaire n'affiche aucune réaction, mais le deuxième clic a été placé dans la file d'attente de messages par Windows. Juste après la fin du "OnClick", il sera rappelé.
Y COMPRIS "ProcessMessages", la sortie peut être très différente:
- Travail 1, Cycle 1
- Travail 1, cycle 2
- Travail 1, cycle 3
- Travail 2, Cycle 1
- Travail 2, Cycle 2
- Travail 2, Cycle 3
- Travail 2, Cycle 4
- Travail 2, cycle 5
Fin du travail 2.
- Travail 1, cycle 4
- Travail 1, cycle 5
Le travail 1 s'est terminé.
Cette fois, le formulaire semble fonctionner à nouveau et accepte toute interaction de l'utilisateur. Ainsi, le bouton est enfoncé à mi-chemin lors de votre première fonction "travailleur" ENCORE, qui sera traitée instantanément. Tous les événements entrants sont traités comme tout autre appel de fonction.
En théorie, lors de chaque appel à "ProgressMessages", N'IMPORTE QUEL nombre de clics et de messages utilisateur peuvent se produire "sur place".
Soyez donc prudent avec votre code!
Exemple différent (en pseudo-code simple!):
procédure OnClickFileWrite ();
var monfichier: = TFileStream;
commencer
monfichier: = TFileStream.create ('myOutput.txt');
essayer
tandis que BytesReady> 0 faire
commencer
mon fichier. Écrire (DataBlock);
dec (BytesReady, sizeof (DataBlock));
DataBlock [2]: = # 13; {test line 1}
Application. ProcessMessages;
DataBlock [2]: = # 13; {test line 2}
fin;
enfin
myfile.free;
fin;
fin;
Cette fonction écrit une grande quantité de données et essaie de "déverrouiller" l'application en utilisant "ProcessMessages" chaque fois qu'un bloc de données est écrit.
Si l'utilisateur clique à nouveau sur le bouton, le même code sera exécuté pendant que le fichier est en cours d'écriture. Le fichier ne peut donc pas être ouvert une deuxième fois et la procédure échoue.
Peut-être que votre application effectuera une récupération d'erreur comme la libération des tampons.
En conséquence, "Datablock" sera libéré et le premier code déclenchera "soudainement" une "Violation d'accès" lorsqu'il y accédera. Dans ce cas: la ligne de test 1 fonctionnera, la ligne de test 2 plantera.
La meilleure façon:
Pour le rendre facile, vous pouvez définir le formulaire entier "enabled: = false", qui bloque toutes les entrées utilisateur, mais ne le montre PAS à l'utilisateur (tous les boutons ne sont pas grisés).
Une meilleure façon serait de régler tous les boutons sur "désactivé", mais cela pourrait être complexe si vous souhaitez conserver un bouton "Annuler" par exemple. Vous devez également parcourir tous les composants pour les désactiver et lorsqu'ils sont à nouveau activés, vous devez vérifier s'il doit en rester certains à l'état désactivé.
Vous pourriez désactiver un contrôle enfant conteneur lorsque la propriété Enabled change.
Comme le nom de classe "TNotifyEvent" le suggère, il ne doit être utilisé que pour des réactions à court terme à l'événement. Pour le code qui prend du temps, la meilleure façon est à mon humble avis de mettre tout le code "lent" dans un propre thread.
Concernant les problèmes avec "PrecessMessages" et / ou l'activation et la désactivation des composants, l'utilisation d'un deuxième fil ne semble pas du tout trop compliqué.
N'oubliez pas que même des lignes de code simples et rapides peuvent se bloquer pendant des secondes, par exemple l'ouverture d'un fichier sur un lecteur de disque peut devoir attendre la fin de la rotation du lecteur. Cela ne semble pas très bon si votre application semble planter car le lecteur est trop lent.
C'est ça. La prochaine fois que vous ajouterez "Application. ProcessMessages ", réfléchissez bien;)