[英]Cancel IProgressDialog in Delphi
我正在進行長時間的操作,並且想出了一種向用戶展示它的好方法是使用帶有IProgressDialog
對象的系統進度對話框。
我只找到了幾個用法示例,這是我的實現。 我遇到的問題是應用程序仍然沒有響應(我知道我可能需要使用線程),但是“取消”按鈕根本不起作用(這可能是第一個問題的結果。)
我正在Windows 8.1下使用Delphi XE。
編輯 :我已經在評估HasUserCancelled
之前添加了Application.ProcessMessages
調用,但是它似乎沒有多大幫助(對話框仍然無法處理單擊“ 取消”按鈕的操作。)
var
i, procesados: Integer;
IDs: TList<Integer>;
pd: IProgressDialog;
tmpPtr: Pointer;
begin
procesados := 0;
try
tmpPtr := nil;
CoCreateInstance(CLSID_ProgressDialog, nil, CLSCTX_INPROC_SERVER,
IProgressDialog, pd);
// also seen as pd := CreateComObject(CLSID_ProgressDialog) as IProgressDialog;
pd.SetTitle('Please wait');
pd.SetLine(1, PWideChar(WideString('Performing a long running operation')),
false, tmpPtr);
pd.SetAnimation(HInstance, 1001); // IDA_OPERATION_ANIMATION ?
pd.Timer(PDTIMER_RESET, tmpPtr);
pd.SetCancelMsg(PWideChar('Cancelled...'), tmpPtr);
pd.StartProgressDialog(Handle, nil, PROGDLG_MODAL or
PROGDLG_NOMINIMIZE, tmpPtr);
pd.SetProgress(0, 100);
IDs := GetIDs; // not relevant, returns List<Integer>
try
for i in IDs do
begin
try
Application.ProcessMessages;
if pd.HasUserCancelled then
Break; // this never happens
Inc(procesados);
pd.SetProgress(procesados, IDs.Count);
LongRunningOp(id);
except
// ?
end;
end;
finally
IDs.Free;
end;
finally
pd.StopProgressDialog;
// pd.Release; doesn't exist
end;
end;
end;
您將需要使用希望應用程序響應的線程。 取消按鈕不起作用的原因是循環中未處理消息。 將諸如Application.ProcessMessages之類的內容放入循環中將使其能夠響應單擊“取消”按鈕,但是線程仍然是更好的選擇。
您應該將帶有LongRunninOp(id)的循環放入線程中,然后使用Synchronize反饋到UI。 像這樣:
procedure TMyThread.Execute;
var
i: Integer;
begin
for i in IDs do
begin
try
// If pd.HasUserCancelled is thread safe then this will work
// if Terminated or pd.HasUserCancelled then
// Break;
// If pd.HasUserCancelled is not thread safe then you will need to do something like this
Synchronize(
procedure
begin
if pd.HasUserCancelled then
Terminate():
end);
if Terminated then
Break;
Synchronize(
procedure
begin
MainForm.pd.SetProgress(I, IDs.Count);
end);
LongRunningOp(id);
except
// ?
end;
end;
end;
使用線程時,您需要確保您不會從主線程和后台線程訪問諸如ID之類的東西。 我也不喜歡MainForm.pd.SetProgress類型的調用,但是我把它放在這里告訴您發生了什么。 在您調用的主窗體上最好有一個方法。
在上面的代碼中,當從主線程調用MyThread.Terminate()
時,對Terminated的檢查將返回true。 這是您應在事件處理程序中為“取消”按鈕添加的內容。 這表明線程應該關閉。 理想情況下,也應在LongRunningOp調用內進行此檢查,以防止在調用Terminate
時延遲響應。
正如Remy所指出的,您可以使用線程OnTerminate
事件告訴主表單線程何時完成,終止時間或線程結束時的結束時間。
我剛剛用以下代碼進行了測試,它可以按預期工作:
var
iiProgressDialog: IProgressDialog;
pNil: Pointer;
begin
pNil := nil;
iiProgressDialog := CreateComObject(CLASS_ProgressDialog) as IProgressDialog;
iiProgressDialog.SetTitle('test');
iiProgressDialog.StartProgressDialog( Handle, nil, PROGDLG_NOMINIMIZE, pNil);
repeat
Application.ProcessMessages;
until iiProgressDialog.HasUserCancelled > 0;
iiProgressDialog.StopProgressDialog;
end;
按取消將終止循環。 之所以看不到相同的效果,是因為LongRunningOp花費的時間太長。 在該例程中,沒有任何東西可以處理消息。 如果要在單個線程中運行,則也需要在該例程中定期調用Application.ProcessMessages。
以下組件也可以為您提供幫助:
http://www.bayden.com/delphi/iprogressdialog.htm
它將IProgressDialog包裝到Delphi組件中。 它創建一個監視取消單擊的線程,如果單擊該按鈕,則將觸發事件。
Windows通過在窗口之間傳遞消息來工作。 與某些操作系統不同,這些消息不是注入的,而是必須從消息隊列中輪詢的。 這是Application.ProcessMessages調用所做的。
這有點像合作多任務。 結果,在您的處理循環運行時,在該線程中創建並運行的任何窗口控件(如此對話框)都無法處理事件。
有兩種方法。 第一種是為您的處理創建一個線程,然后等待它返回。 安排起來可能有點復雜,並且可能需要檢查跨線程問題。
第二種方法是在處理循環中調用Application.ProcessMessages-這將強制對消息進行處理,並且您可以獲取click事件等。
但是 ,由於在循環內可能發生事件,因此可以在循環內銷毀您正在使用的對象。 例如,如果您的處理循環由於單擊按鈕而在表單上運行,並且用戶關閉該表單並銷毀了該表單(例如自動釋放表單),則該表單對象及其所有控件將不再有效對象-引用它們將導致內存訪問沖突和異常。 最簡單的解決方案是使用標志,並且永遠不要在處理循環中關閉表單。
無論哪種方式,您都有責任管理所有事物的生命周期,並且每種方法都有必須由您處理的問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.