2010-03-26 5 views
6

Je dois afficher une fenêtre contextuelle avant l'exécution d'une requête, afficher le temps écoulé pendant l'exécution de la requête sql et fermer cette fenêtre à la fin de la requête.Comment afficher le temps écoulé pendant l'exécution d'une longue requête SQL?

En fait, je fais quelque chose comme ça

var 
    frm : tFrmPopupElapsed; 
    // this form has a TTimer and a TLabel to show the elapsed time 
    // but the label is not updated, I tried using update and refresh 
    // but nothing happens 
begin 
    frm := tFrmPopupElapsed.Create(nil); 
    try 
    frm.Init; // this procedure enables the timer 
    frm.Show(); 
    ExecuteMyVeryLongQuery(); 
    finally 
    frm.Close; 
    end; 
end; 

Comment l'étiquette peut être mis à jour pour afficher le temps écoulé pendant que la requête exécute? En utilisant une minuterie? Ou un fil?

+0

Quelle base de données? Au moins un que je connais (DBISAM/ElevateDB) offre un événement OnProgress très pratique sur leur composant de requête. Vous pouvez coder dans leur rapport pour le temps écoulé, le pourcentage terminé, etc. –

+2

petite suggestion: Je mettrais le 'try' avant le' frm.Init' – mjn

+2

Notez que l'appel 'frm.Close' entraînera des fuites de mémoire à moins que le formulaire a un gestionnaire 'OnClose' qui définit' caFree'. Utiliser 'frm.Release' serait beaucoup plus sûr. – mghie

Répondre

4

Vous devez exécuter la requête de façon asynchrone, ce qui vous permet de mettre à jour le formulaire entre-temps.
La manière la plus simple de le faire sans se salir les mains avec les fils est d'utiliser AsynCalls library de Andreas Hausladen.
Vous pouvez également jeter un oeil à la OmniThread Library par Primoz Gabrijelcic.

+0

+1 pour me battre à peu près la même réponse. (J'allais suggérer Omnithread en premier, et asyncalls second) :) – skamradt

+0

L'un ou l'autre de ces deux est probablement préférable à l'utilisation d'un descendant 'TThread'. Mais l'exécution d'une requête dans un thread différent peut être problématique. Il est bien sûr nécessaire de bloquer tout travail avec les objets de connexion en parallèle dans le thread principal, à moins que les objets de connexion n'autorisent explicitement l'accès parallèle à partir de threads différents (ce qui n'est probablement pas le cas avec les classes VCL). Il peut y avoir des restrictions supplémentaires, comme avoir à exécuter toutes les requêtes dans le même thread dans lequel la connexion a été établie. Voir http://17slon.com/blogs/gabr/2009/02/building-connection-pool.html par exemple. – mghie

+0

+1 Merci Beaucoup pour vous conseiller, la bibliothèque AsynCalls fonctionne parfaitement. – Salvador

0

Cette question est beaucoup plus complexe que prévu initialement; ce qui en fait une bonne question. Initialement, je pensais que Application.processmessages était la bonne façon, mais c'est un champ de mines potentiel à moins que vous soyez prudent (merci @skamradt pour le signaler). Cela n'aide pas non plus avec un seul appel de blocage. Un thread d'arrière-plan est nécessaire comme suit: (merci @mghie pour signaler les erreurs qui sont maintenant résolues ). Il peut toujours y avoir des problèmes avec l'objet de base de données étant appelé dans différents threads - de sorte que le thread d'arrière-plan peut avoir besoin de sa propre connexion de base de données pour cette opération (si possible).

Dans l'exemple ci-dessous, je n'ai pas spécifiquement montré le code pour créer et détruire la fenêtre de progression car cela rendra le code encore plus long, et c'est facile à faire.

Nous avons donc besoin de deux objets à faire:

Tout d'abord le fil d'arrière-plan pour traiter la requête.

unit BackgroundProcess; 

{$mode objfpc}{$H+} 

interface 

uses 
    Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, windows; 

const 
    WM_MY_BACKGROUNDNOTIFY = WM_USER + 555; { change this } 
    NOTIFY_BEGIN = 22; 
    NOTIFY_END = 33; 

type 
    TBackgroundQueryThread = class(TThread) 
    private 
    hwndnotify : HWND; 
    protected 
    procedure Execute; override; 
    public 
    constructor Create(owner: TForm); 
    end; 


implementation 

constructor TBackgroundQueryThread.Create(owner: TForm) ; 
begin 
    inherited Create(False); 
    hwndnotify := owner.Handle; 
    FreeOnTerminate := true; 
    resume; 
end; 

procedure TBackgroundQueryThread.Execute; 
begin 
    PostMessage(hwndnotify, WM_MY_BACKGROUNDNOTIFY, NOTIFY_BEGIN, 0); 
    Sleep(2000); (* Query goes here. *) 
    PostMessage(hwndnotify, WM_MY_BACKGROUNDNOTIFY, NOTIFY_END, 0); 
end; 

end.   

La forme qui invoque la requête:

unit mainform; 

interface 

uses 
    Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, 
    StdCtrls, ExtCtrls, windows, BackgroundProcess; 

type 
    TForm1 = class(TForm) 
    private 
    frm : tFrmPopupElapsed; 
    { private declarations } 
    procedure OnMyBackgrounNotify(var Msg: TMessage); message WM_MY_BACKGROUNDNOTIFY; 
    public 
    { public declarations } 
    end; 

var 
    Form1: TForm1; 

implementation 


procedure TForm1.OnMyBackgrounNotify(var Msg: TMessage); 
begin 
    if (msg.WParam = NOTIFY_BEGIN) THEN 
    BEGIN 
    if (frm = nil) THEN 
    BEGIN 
     frm := tFrmPopupElapsed.Create(nil); 
     frm.Init; // this procedure enables the timer 
     frm.Show(); 
    END; 
    END; 

    if (msg.WParam = NOTIFY_END) THEN 
    BEGIN 
    if (frm <> nil) THEN 
    BEGIN 
     frm.Close; 
    END; 
    END; 

end; 

end. 
+2

Faites très attention à utiliser Application.ProcessMessages car il peut facilement causer d'autres problèmes si vous n'êtes pas prudent, et devrait généralement être traité comme la dernière chose que vous voulez faire, pas le premier. Recherchez une autre solution, telle qu'un thread d'arrière-plan. – skamradt

+1

Vous avez raison - je me souviens maintenant que vous pouvez avoir de terribles problèmes de réentrance à moins d'être prudent. Donc, l'approche correcte est juste un fil et un tert-teur - en gardant le fil d'arrière-plan pour la mise à jour. –

+0

-1 pour appeler du code VCL à partir d'un thread d'arrière-plan et pour appeler des méthodes de composants qui peuvent déjà être libérées. – mghie

1

Vous devrez exécuter la requête dans un thread d'arrière-plan si vous voulez que l'interface utilisateur d'être réactif lors de l'exécution de la requête. Si la requête prend en charge l'annulation, vous pouvez également ajouter un bouton d'annulation. Je crois que seules les requêtes ADO supportent ceci, cependant.