2009-07-17 8 views
12

J'ai une application héritée ici qui a quelques boucles «chronophages» qui se déclenchent à la suite de diverses interactions de l'utilisateur. Le code qui prend du temps met régulièrement à jour quelque chose sur l'écran avec des informations de progression (généralement une étiquette), puis, apparemment pour persuader l'actualisation visuelle de se produire là-bas, le code appelle Application.ProcessMessages (argh!). Nous savons tous maintenant quel genre de problème cela peut introduire dans une application GUI (caritative, c'était un moment plus innocent à l'époque) et nous constatons que, comme les œufs, de temps en temps nous obtenons des utilisateurs l'impossible avec le programme parce qu'ils cliquent sur des contrôles pendant que le programme est 'occupé'.Obtenez la fenêtre pour actualiser (etc) sans appeler Application.ProcessMessages?

Quelle est la meilleure façon d'actualiser périodiquement les visuels du formulaire sans prendre en charge d'autres événements/messages, etc.?

Mes pensées étaient soit;
- désactiver tous les contrôles avant de faire quoi que ce soit du temps, et en laissant les appels » ... ProcessMessages de mises en place pour la « force » l'actualisation ou
- trouver un autre moyen de rafraîchir un contrôle périodique

Je peux faire le premier mais il m'a laissé me demander - y at-il une meilleure solution?

exemple de code de l'héritage;

 
i:=0; 
while FJobToBeDone do 
begin 
    DoStepOfLoop; 
    inc(i); 
    if i mod 100 = 0 then 
    begin 
    UpdateLabelsEtc; 
    Application.ProcessMessages; 
    end; 
end; 

Je peux déjà vous entendre tous s'évanouir, à l'arrière. :-)

Répondre

18

Si vous appelez Update() sur les contrôles après avoir modifié les propriétés, vous les forcerez à redessiner. Une autre façon est d'appeler Repaint() au lieu de Refresh(), ce qui implique un appel à Update().

Vous devrez peut-être appeler Update() sur le contrôle des parents ou des cadres aussi bien, mais cela pourrait vous permettre d'éliminer les ProcessMessages() completely.

+0

spot sur mghie , Merci! J'ai laissé tomber ceci dans quelques endroits clés d'échantillon et lui ai donné un essai rapide; les mises à jour de l'écran se passent bien, mais les contrôles ne génèrent pas de «nouveaux» événements lorsque le formulaire est occupé. Si je combine cela avec le verrouillage explicite du formulaire lorsque le travail démarre, j'ai bon espoir que cela empêchera les événements indésirables d'avoir lieu. Merci beaucoup! – robsoft

8

La solution que j'utilise pour les mises à jour longues consiste à effectuer les calculs dans un thread séparé. De cette façon, le fil principal reste très réactif. Une fois le thread terminé, il envoie un message Windows au thread principal, indiquant que le thread principal peut traiter les résultats.

Cela a cependant d'autres inconvénients sérieux. Tout d'abord, alors que l'autre thread est actif, vous devrez désactiver quelques contrôles car ils pourraient redémarrer le thread à nouveau. Un deuxième inconvénient est que votre code doit devenir thread-safe. Cela peut être un vrai défi parfois. Si vous travaillez avec du code existant, il est très probable que votre code ne soit pas compatible avec les threads. Enfin, le code multi-thread est plus difficile à déboguer et devrait être fait par des développeurs expérimentés.

Mais le grand avantage du multi-threading est que votre application reste réactive et que l'utilisateur peut continuer à faire d'autres choses jusqu'à ce que le thread soit terminé. Fondamentalement, vous traduisez une méthode synchrone dans une fonction asynchrone. Et le thread peut déclencher plusieurs messages indiquant certains contrôles pour actualiser leurs propres données, qui seraient mises à jour immédiatement, à la volée. (Et au moment où vous voulez qu'ils soient mis à jour.

J'ai déjà utilisé cette technique plusieurs fois, parce que je pense que c'est beaucoup mieux. (Mais aussi plus complexe.)

+0

Merci, Atelier Alex - Je pense qu'aller de l'avant c'est la «bonne» chose à faire, en particulier là où il pourrait y avoir une longue période d'inactivité. Dans ce cas particulier, la fonction 'Update' de mghie semble être ajoutée et remplacer entièrement l'appel ProcessMessages. – robsoft

+0

Bien qu'Alex ait donné une bonne explication, il manque un pointeur sur la façon de commencer: Delphi a un modèle d'unité de thread dans la boîte de dialogue 'ajouter un nouveau', mais essentiellement tout se résume à créer une classe dérivée de TThread, remplaçant le Exécuter la méthode. –

+0

Stijn fournit un bon moyen d'utiliser des threads dans Delphi. Fondamentalement, la classe TThread enveloppe l'API Windows et fournit une classe de base dans laquelle vous pouvez partager des données. Personnellement, je préfère utiliser l'API raw à la place, en créant une procédure thread distincte qui appelle à nouveau une méthode spécifique de ma classe de logique métier. (Ou une méthode de votre formulaire principal/enfant.) Mais à cause de la complexité, je n'oserais même pas utiliser cette technique avec du code hérité, à moins que je ne le récrive à partir de zéro. (Le code hérité n'est souvent pas adapté aux threads.) –

1

La technique que vous cherchez est de filetage. C'est une technique diffucile en programmation. Le code doit être manipulé avec soin, car il est plus difficile à déboguer. Que vous alliez avec threading ou pas, vous devez certainement désactiver les contrôles que les utilisateurs devraient pasmess avec (Je veux dire les contrôles qui peuvent affecter le processus en cours). Vous devriez envisager d'utiliser actions pour activer/désactiver les boutons, etc ...

+0

Merci pour ce codervish. Je dois admettre que je n'ai pas beaucoup utilisé les listes d'actions par le passé, mais en y regardant de plus près, il semble que ce soit une façon très pratique de contrôler l'ensemble des événements activés/événements. À votre santé! – robsoft

1

Plutôt que de désactiver les contrôles, nous avons une var booléen sous la forme FBusy, vérifiez simplement quand l'utilisateur appuie sur un bouton, nous l'a introduit pour les raisons exactes que vous mentionnez, les utilisateurs cliquent sur des boutons en attendant l'exécution d'un code à exécution longue (il est effrayant de savoir à quel point votre code est familier).

Alors vous vous retrouvez avec quelque chose comme

procedure OnClick(Sender:TObejct); 
begin 
    if (FBusy) then 
    begin 
     ShowMessage('Wait for it!!'); 
     Exit; 
    end 
    else FBusy := True; 
    try 
     //long running code 
    finally 
     FBusy := False; 
    end; 
end; 

Son impuissance de se rappeler de rap code long cours d'exécution dans un essai-finally en cas de sorties ou d'une exception, comme vous finiriez avec une forme ça ne marchera pas. Comme suggéré nous utilisons des threads si c'est pour le code qui n'affectera pas les données, par exemple exécuter un rapport ou une analyse de données, mais certaines choses ne sont pas des options, disons si nous mettons à jour 20.000 enregistrements de produits, alors nous Je ne veux pas que quelqu'un essaye de vendre ou de changer de façon sage les dossiers au milieu du vol, donc nous devons bloquer l'application jusqu'à ce que ce soit fait. Ne pas appeler Application.Processmessages, Ceci est lent et peut générer une récursivité illimitée.

+0

Merci pour cela - cette approche est la façon dont j'ai eu tendance à le faire moi-même dans d'autres applications. Je pense que je vais essayer d'utiliser un thread pour le code à long terme dans la prochaine application que j'écris, car il semble que quelque chose que je devrais être à l'aise d'utiliser. :-) – robsoft

3

Par exemple, pour mettre à jour tout en Panel1 sans film, nous pouvons utiliser cette méthode:

procedure TForm1.ForceRepaint; 
var 
    Cache: TBitmap; 
    DC: HDC; 
begin 
    Cache := TBitmap.Create; 
    Cache.SetSize(Panel1.Width, Panel1.Height); 
    Cache.Canvas.Lock; 
    DC := GetDC(Panel1.Handle); 
    try 
    Panel1.PaintTo(Cache.Canvas.Handle, 0, 0); 
    BitBlt(DC, 0, 0, Panel1.Width, Panel1.Height, Cache.Canvas.Handle, 0, 0, SRCCOPY); 
    finally 
    ReleaseDC(Panel1.Handle, DC); 
    Cache.Canvas.Unlock; 
    Cache.Free; 
    end; 
end; 

Pour de meilleures performances, le bitmap cache doit être créé d'abord et libre lorsque le processus est terminé

Questions connexes