2010-03-07 36 views
5

Làm cách nào để ngăn chặn xử lý sự kiện mới bắt đầu khi xử lý sự kiện đang chạy?Delphi và ngăn sự kiện xử lý

Tôi nhấn nút button1 và trình xử lý sự kiện, ví dụ: công việc in chậm. Có một số điều khiển trong các nút biểu mẫu, chỉnh sửa, combo và tôi muốn rằng một sự kiện mới chỉ được phép sau khi chạy trình xử lý được kết thúc.

Tôi đã sử dụng biến fRunning để khóa trình xử lý trong trình xử lý sự kiện được chia sẻ. Có cách nào thông minh hơn để xử lý việc này không?

procedure TFormFoo.Button_Click(Sender: TObject);  
begin 
    if not fRunning then 
    try 
    fRunning := true; 
    if (Sender = Button1) then // Call something slow ... 
    if (Sender = Button2) then // Call something ... 
    if (Sender = Button3) then // Call something ... 
    finally 
    fRunning := false; 
    end; 
end; 

Trả lời

6

Một tùy chọn khác (mà không đòi hỏi một lĩnh vực cờ) sẽ tạm thời chuyển NIL sự kiện:

procedure TForm1.Button1Click(Sender: TObject); 
var 
    OldHandler: TNotifyEvent; 
begin 
    OldHandler := (Sender as TButton).OnClick; 
    (Sender as TButton).OnClick := nil; 
    try 
    ... 
    finally 
    (Sender as TButton).OnClick := OldHandler; 
    end; 
end; 

Đối với mục đích thuận tiện này có thể được gói gọn trong một giao diện:

interface 

function TempUnassignOnClick(_Btn: TButton): IInterface; 

implementation 

type 
    TTempUnassignOnClick = class(TInterfacedObject, IInterface) 
    private 
    FOldEvent: TNotifyEvent; 
    FBtn: TButton; 
    public 
    constructor Create(_Btn: TButton); 
    destructor Destroy; override; 
    end; 

constructor TTempUnassignOnClick.Create(_Btn: TButton); 
begin 
    Assert(Assigned(_Btn), 'Btn must be assigned'); 

    inherited Create; 
    FBtn := _Btn; 
    FOldEvent := FBtn.OnClick; 
    FBtn.OnClick := NIL; 
end; 

destructor TTempUnassignOnClick.Destroy; 
begin 
    FBtn.OnClick := FOldEvent; 
    inherited; 
end; 

function TempUnassignOnClick(_Btn: TButton): IInterface; 
begin 
    Result := TTempUnassignOnClick(_Btn); 
end; 

sẽ được sử dụng như thế này:

procedure TForm1.Button1Click(Sender: TObject); 
begin 
    TempUnassignOnClick(Sender as TButton); 
    ... 
end; 
+1

Giải pháp tuyệt vời này nếu chỉ có một Button1 trong biểu mẫu. OnClick của Button1 bị tắt, nhưng Button2 có thể kích hoạt sự kiện OnClick mới nếu Application.ProcessMessages được gọi trong khi xử lý sự kiện Button1. – pKarelian

+0

Cảm ơn dummzeuch. Trình bao bọc giao diện là cách rất tiện dụng để tiêu diệt đối tượng sự kiện tạm thời. Bạn không phải gọi miễn phí(). – pKarelian

+1

+1 nhưng trong * lý thuyết *, có khả năng sự kiện không được đính kèm lại một thời gian. Trong thực tế, tôi nghĩ rằng nó là an toàn để giả định rằng trường hợp giao diện bị phá hủy khi cuộc gọi được thực hiện (và eventhandler được đính kèm lại). –

2

Bạn không cần phải làm điều này cả, vì tất cả những điều này đang xảy ra trong chính (VCL) chủ đề: Không nút khác (VCL) sự kiện có thể được nhập trước khi sự kiện trước đó (VCL) handler đã quay trở lại ... Việc thực thi đồng thời một trình xử lý sự kiện khác chỉ có thể xảy ra bất ngờ nếu một chuỗi khác được ưu tiên nhập vào một sự kiện nút thứ hai (trước khi sự kiện đầu tiên hoàn thành), nhưng điều đó không thể xảy ra, vì chỉ có một Chủ đề VCL.

Bây giờ nếu điều bạn đang làm dài được thực hiện trong một chuỗi khác vì bạn không muốn chặn GUI, thì bạn có thể chỉ cần đặt thuộc tính Button.Enabled thành false cho đến khi quá trình xử lý xong.
Và nếu bạn quyết định chỉ gắn vào sự kiện nút cho đến khi mọi thứ đã hoàn thành, hãy sử dụng application.processmessages đủ thường xuyên trong vòng lặp xử lý của bạn để ngăn không cho gui bị đóng băng. Trong trường hợp đó, có, bạn phải vô hiệu hóa nút gốc để ngăn chặn reentry.

+1

Xin lỗi, đó là không đúng sự thật. – pKarelian

+1

Sau đó, tôi sẽ được quan tâm để được giải thích ở đây như thế nào VCL có thể được thực hiện hai sự kiện nút cùng một lúc trong một bối cảnh thread duy nhất. – filofel

+6

Không đúng sự thật. Sự kiện có thể được lồng ghép và đệ quy có thể dễ dàng xảy ra đặc biệt là khi Application.ProcessMessages được gọi trong mã sự kiện (như chúng ta thường phải làm). –

0

Nếu ứng dụng của bạn là một chuỗi đơn, sau đó trong khi mã xử lý sự kiện của bạn đang chạy, ứng dụng của bạn không thể chạy các mã khác, vì vậy tất cả các cuộc gọi tới trình xử lý sự kiện đó sẽ được đăng theo thứ tự và bạn không cần lo lắng.

Nếu trình xử lý sự kiện của bạn đang chạy bất kỳ công việc không đồng bộ nào, thì bạn có thể sử dụng kỹ thuật bạn đã trình bày trong câu hỏi của mình.

+4

Trừ khi mã đó gọi Application.ProcessMessages. Trong trường hợp này, bộ xử lý thông hơi có thể được gọi hai lần. –

2

Giải pháp của bạn là OK. Bạn cũng có thể liên kết các lần nhấp nút đến các hành động và bật/tắt các hành động trong trình xử lý sự kiện TAction.OnUpdate, nhưng bạn vẫn cần cờ fRunning để thực hiện. Các "nếu không có fRunning" dòng có thể không nessesary ở đây, nhưng tôi không loại bỏ nó vì nó là an toàn hơn:

// Button1.Action = acButton1, Button2.Action = acButton2, etc 

procedure TForm1.acButtonExecute(Sender: TObject); 
begin 
    if not fRunning then 

    try 
    fRunning:= True; 
    if (Sender = acButton1) then // Call something slow ... 
    if (Sender = acButton2) then // Call something ... 
    if (Sender = acButton3) then // Call something ... 
    finally 
    fRunning:= False; 
    end; 

end; 

procedure TForm1.acButtonUpdate(Sender: TObject); 
begin 
    (Sender as TAction).Enabled:= not fRunning; 
end; 
+2

Một phương pháp khác chỉ đơn giản là đặt Enabled: = False ở mức biểu mẫu. Điều này rõ ràng là tốt nhất với một thử ... cuối cùng xử lý –

+0

Cảm ơn Serg. Tôi sẽ thử giải pháp TActionList. – pKarelian

2

Như Gerry đã được đề cập trong một trong những ý kiến, bạn có thể vô hiệu hóa toàn bộ hình thức:

procedure TFormFoo.Button_Click(Sender: TObject);  
begin 
    try 
    Enabled := False; 
    //... 
    finally 
    Enabled := True; 
    end; 
end; 
+0

Cảm ơn bạn Torbins. Giải pháp đơn giản và tốt đẹp. – pKarelian

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