Bonjour à tous.
Je travaillais jusqu'à présent sous Delphi 5 notre société a décidé de passer à Delphi7 pour certaines raisons (le .NET ne nous sert pas).
Nous travaillons sur un projet qui consiste en une suite d'applications qui travaillent avec une SGBD. Par moment nous sommes ammenés à effectuer un traitement assez important qui peut durer plusieurs secondes voir minutes, dans ce cas et afin que l'utilisateur ait un retour visuel du traitement nous affichons un "splashscreen" qui indique qu'un traitement est en cours. Ce splashscreen affiche une animation censée indiquer grosso modo que l'application n'est pas planté mais fait du traitement, de plus son affichage est mise à jour automatiquement de telle sorte que si une fenêtre passe par dessus à un moment donnée le splashscreen se redessine et reset ainsi toujours visible durant le temps du traitement.
Le problème est le suivant, depuis que j'ai passé le projet sous Delphi7 le splashscreen reste figé (plus d'animation et celui-ci ne se redessine plus).
Pour illustrer mes propos par du code voici un exemple d'utilisation du splashscreen :
procedure DoWork;
var
MySplashScreen: TMySplashScreen;
begin
MySplashScreen:= TMySplashScreen.Create;
try
// Affichage SplashScreen (met visible à 'true')
// 'TMySplashScreen' hérite de 'TForm'...
MySplashScreen.Show;
try
// Gros Traitement qui dure plusieurs secondes
DoSomethingLong;
finally
// On oubli pas de retirer le SplashScreen
MySplashScreen.Hide;
end;
finally
// On oubli pas de libérer le SplashScreen
MeSplashScreen.Free;
end;
end;
// Voici maintenant une partie de l'implémentation du SplashScreen, celui contient une instance
// de 'TThreadSplashScreen' qui est créé à la création du splashscreen, cette instance démarre
// une thread à sa création qui se charge de mettre à jour l'affichage du splashscreen :
// Appelé en interne par notre thread pour MAJ l'affichage,
// je l'ai décomposé dans une autre fonction afin de la synchroniser car
// des appels à des méthodes et propriétés d'objets VCL non 'threadsafe' sont effectuées
procedure TThreadSplashScreen.MyUpdate;
begin
if ( MySplashScreen = nil ) then exit;
if ( MySplashScreen.Visible ) then begin
// MAJ Animation
MySplashScreen.DoAnimate;
// MAJ splashscreen
MySplashScreen.Update;
// MAJ fiche pere
if ( Assigned(MySplashScreen.Owner) ) and ( MySplashScreen.Owner is TForm ) and
( TForm(MySplashScreen.Owner).Visible ) then TForm(MySplashScreen.Owner).Update;
end;
// s'il s'agit d'un affichage temporisé (type messagedialog), on vérifie si le temps d'affichage est écoulé
if ( MySplashScreen.AfficheTime > 0 ) then begin
// si la temporisation a été écoulée on cache le splashscreen et on réinitialise AfficheTime à 0
if ( TimeStampToMSecs( DateTimeToTimeStamp( Time ) ) > ( MySplashScreen.BeginAfficheTime + MySplashScreen.AfficheTime ) ) then begin
MySplashScreen.AfficheTime:= 0;
MySplashScreen.Hide;
end;
end;
end;
procedure TThreadSplashScreen.Execute;
begin
{ Placez le code du thread ici}
while true do begin
// on fait une temporisation correpondant au taux de rafraîchissement de l'affichage
// cela permet aussi de laisser du temps CPU aux autres threads pour s'exécuter
// (encore heureux pour un affichage de splashscreen censé indiquer un traitement en cours...)
Sleep( 1000 div (MySplashScreen.RefreshRate) );
// On teste si on doit quitter la thread
if Terminated then EndThread( 0 );
// On synchronise la MAJ de l'affichage : au final j'ai du mal à comprendre
// en quoi cela est différent de faire un timer dans le splashscreen qui
// appelerait 'MyUpdate' et pourtant à l'éxécution cela se comporte tout à
// fait différement (et heureusement sinon le splashscreen resterait figé).
// D'après l'aide de Delphi la méthode Synchronize attend que la thread
// principale entre dans la boucle de traitement des messages mais je n'ai
// pas l'impression que ce soit réellement le cas, je pense que la thread
// principale est stoppée dans son traitement, éxécute la fonction, et
// retourne à son traitement.
Synchronize( MyUpdate );
end;
end;
Vous noterez que j'accède à 'MySplashScreen.RefreshRate' hors du synchronize, cela n'est pas
"threadsafe" mais le risque encouru est tout à fait mesuré, j'ai décidé de laisser ainsi.
Le problème est que la fonction se bloque à l'appel à la méthode "Synchronize", ce qui explique que
mon splashscreen n'est pas mise à jour.
Vous remarquerez également mes commentaires d'époque que j'avais fait et qui explique en partie le problème
que je rencontre actuellement, en effet ce résultat me semble après tout logique :
comme ma thread principale est bloqué dans une fonction qui réalise du traitement, elle ne peut pas pendant ce temps
gérer et dispatcher les messages, c'est pourquoi mon splashscreen ne s'affiche pas, la méthode 'Synchronize' attends
que la thread principale rentre dans la boucle de traitement de message (en fait c'est un appel à 'CheckSynchronize'
sous Delphi7 qui "débloque" le Synchronize), or cet appel a lieu dans la boucle de traitement des messages.
CE que je ne comprend pas c'est comment mon splashscreen pouvait-il s'afficher correctement sous Delphi5, apparement
le mecanisme de synchronisation était différent bien que l'aide spécifiait que la fonction attendait que la thread
principale entre dans la boucle de traitement des messages ce n'était vraisemblablement pas tout à fait le cas.
L'affichage se figeait parfois légèrement mais d'une manière générale celui-ci se mettait bien à jour avec l'animation
"maison", ce qui n'est plus du tout le cas avec Delphi7.
Avec le recul je me rends compte que l'implémentation était mauvaise et qu'il aurait été plus logique de faire les
longs traitements dans une thread dédiée et de laisser l'affichage dans la thread principale mais le problème est
que tout le projet a été implmenté sur ce modèle et qu'il me faudrait faire bien trop de modifications complexes
pour revenir à une implémentation plus logique 'Le splashscreen doit être appelé à plus de 200 endroits différents
dans l'application...). Comment puis-je procéder pour laisser le traitement visuel dans la thread dédié sachant que
j'ai essayé pas mal de solutions en vain (Création du splashscreen dans la thread dédiée, appel de
'PeekMessage'/'DispatchMessage' dans la thread dédiée, etC....). Etant donné l'implémentation de "Form.pas"
(en gros toutes les fiches sont gérées dans la thread principale) il est normal que toutes ses solutions aient
échouées, et je ne vois pas quelle solution je pourrai choisir...
En attendant j'ai fait un simple splashscreen contenant un 'TAnimate' mais d'une part celui-ci ne met plus à jour son
affichage, ce qui signifie que si une fenêtre passe par dessus alors le splashscreen ne se redessine plus. D'autre
part le 'TAnimate' ne fonctionne pas très bien, l'animation se déclenche de manière aléatoire en apparence et j'ai un
peu de mal à cerner pourquoi...
Une dernière chose, depuis ce matin lorsque je compile en passant par l'IDE je me tape l'erreur
"[Fatal Error] Variants.pas(1024): Program or unit 'Variants' recursively uses itself" alors que bien évidemment
ce n'est pas le cas mais je n'arrive pas à comprendre ce qui pose ce problème. J'ai touché aux sources de Delphi pour faire
des tests mais depuis je les ai tous remis comme dans leur état d'origine .
Merci d'avance de votre aide.