[英]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.