2010-03-26 26 views
6

tôi cần phải hiển thị một cửa sổ popup trước khi thực hiện truy vấn, hiển thị thời gian trôi qua trong khi truy vấn sql đang được thực hiện, và đóng cửa sổ đó khi truy vấn kết thúc.Làm thế nào để hiển thị thời gian đã trôi qua trong khi một truy vấn SQL dài được thực hiện?

Thật sự tôi làm điều gì đó như thế này

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; 

Làm thế nào các nhãn có thể được cập nhật để hiển thị thời gian trôi qua trong khi truy vấn thực hiện? Sử dụng bộ hẹn giờ? Hoặc một sợi?

+0

Cơ sở dữ liệu nào? Ít nhất một cái mà tôi biết (DBISAM/ElevateDB) cung cấp một sự kiện OnProgress rất tiện dụng trên thành phần truy vấn của họ. Bạn có thể viết mã để báo cáo thời gian trôi qua, phần trăm hoàn thành, v.v. –

+2

gợi ý nhỏ: Tôi sẽ đặt 'try' trước' frm.Init' – mjn

+2

Lưu ý rằng việc gọi 'frm.Close' sẽ dẫn đến rò rỉ bộ nhớ trừ khi biểu mẫu có một trình xử lý 'OnClose' đặt' caFree'. Sử dụng 'frm.Release' sẽ an toàn hơn nhiều. – mghie

Trả lời

4

Bạn cần chạy truy vấn không đồng bộ, cho phép bạn cập nhật Biểu mẫu trong thời gian chờ đợi.
Cách đơn giản nhất để làm điều đó mà không nhận được bàn tay của bạn bẩn với đề là sử dụng Andreas Hausladen của AsynCalls library.
Bạn cũng có thể cung cấp cho một cái nhìn tại các OmniThread Library bởi Primož Gabrijelcic.

+0

+1 để đánh tôi gần như cùng một câu trả lời. (Tôi sẽ đề nghị Omnithread trước, và asyncalls thứ hai) :) – skamradt

+0

Hoặc trong số này có lẽ là tốt hơn so với sử dụng một con cháu 'TThread'. Nhưng thực hiện một truy vấn trong một chủ đề khác nhau có thể có vấn đề. Tất nhiên là cần thiết để chặn bất kỳ công việc nào với các đối tượng kết nối song song trong luồng chính, trừ khi các đối tượng kết nối cho phép truy cập song song một cách rõ ràng từ các luồng khác nhau (có thể không phải là trường hợp với các lớp VCL). Có thể có các hạn chế bổ sung, như phải thực hiện tất cả các truy vấn trong cùng một luồng mà kết nối được thiết lập. Xem http://17slon.com/blogs/gabr/2009/02/building-connection-pool.html ví dụ. – mghie

+0

+1 Cảm ơn Rất nhiều lời khuyên của bạn, thư viện AsynCalls hoạt động hoàn hảo. – Salvador

0

Câu hỏi này phức tạp hơn nhiều so với dự kiến ​​ban đầu; mà làm cho nó một câu hỏi hay.

Ban đầu tôi nghĩ Application.processmessages là đúng cách, tuy nhiên nó là một mỏ tiềm năng trừ khi bạn cẩn thận (nhờ @skamradt để chỉ ra điều này). Nó cũng không giúp đỡ với một cuộc gọi chặn duy nhất.

Chủ đề nền là cần thiết như sau: (cảm ơn @mghie vì đã chỉ ra các lỗi mà hiện đã được giải quyết). Vẫn có thể có vấn đề với đối tượng cơ sở dữ liệu được gọi trong các luồng khác nhau - do đó, luồng nền có thể cần phải có kết nối cơ sở dữ liệu riêng cho thao tác này (nếu có thể thực hiện được).

Trong ví dụ dưới đây tôi đã không được hiển thị cụ thể là mã cho việc tạo ra và phá hủy các cửa sổ tiến bộ vì nó sẽ làm cho đoạn code thậm chí lâu hơn, và dễ dàng để làm.

Vì vậy, chúng tôi cần hai đối tượng để thực hiện việc này:

Đầu tiên là chuỗi nền để xử lý truy vấn.

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.   

Các hình thức mà gọi truy vấn:

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

Hãy rất cẩn thận khi sử dụng Application.ProcessMessages vì ​​nó có thể dễ dàng gây ra các vấn đề khác nếu bạn không cẩn thận, và thường được coi là điều cuối cùng bạn muốn làm, không phải là thứ nhất. Hãy tìm một giải pháp khác, chẳng hạn như chuỗi nền, trước tiên. – skamradt

+1

Bạn nói đúng - Tôi nhớ rằng bạn có thể gặp vấn đề về reentrancy khủng khiếp trừ khi bạn cẩn thận. Vì vậy, phương pháp tiếp cận chính xác chỉ là một chủ đề và một ttimer - giữ luồng nền cho bản cập nhật. –

+0

-1 để gọi mã VCL từ một chuỗi nền và để gọi các phương thức của các thành phần có thể được giải phóng. – mghie

1

Bạn sẽ cần phải chạy truy vấn trong một thread nền nếu bạn muốn giao diện người dùng được đáp ứng trong thời gian thực hiện truy vấn. Nếu truy vấn hỗ trợ hủy, bạn cũng có thể thêm nút hủy. Tuy nhiên, tôi tin rằng chỉ các truy vấn ADO mới hỗ trợ điều này.

Các vấn đề liên quan