我正在运行一个长时间的操作,我认为向用户展示它的一个好方法是使用IProgressDialog
对象使用系统进度对话框。
我只找到了几个用法示例,这是我的实现。我遇到的问题是应用程序仍然没有反应(我知道我可能需要使用一个线程)但是取消按钮根本不起作用(这可能是第一个问题的后果。)
我在Windows 8.1下使用Delphi XE。
修改:我在评估Application.ProcessMessages
之前添加了HasUserCancelled
来电,但它似乎没有多大帮助(对话仍未提供帮助) ; t点击取消按钮。)
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;
答案 0 :(得分:3)
您需要使用您希望应用程序响应的线程。您的取消按钮无效的原因是您的循环中没有处理消息。在循环中放入类似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组件中。它会创建一个监视取消单击的线程,并在单击该按钮时触发事件。
答案 1 :(得分:0)
Windows通过在Windows之间传递消息来工作。与某些操作系统不同,这些消息不会被注入,而是必须从消息队列中轮询它们。这就是Application.ProcessMessages调用的内容。
有点像合作多任务处理。因此,在您的线程中创建和操作的任何窗口控件(如此对话框)在处理循环运行时永远不会处理事件。
有两种方法。第一种是为您的处理创建一个线程并等待它返回。这可能有点复杂,可能需要检查交叉线程问题。
第二种方法是在处理循环中调用Application.ProcessMessages - 这会强制处理消息,您可以获得点击事件等。
HOWEVER 您正在使用的对象可能会在循环内被销毁,因为事件可能发生在循环中。例如,如果您的处理循环由于按钮单击表单而运行,并且用户关闭该表单并将其销毁(例如自动释放表单) - 则表单对象及其所有控件不再有效对象 - 引用它们将导致内存访问违规和异常。最简单的解决方案是使用标志,在处理循环期间永远不要让表单关闭。
无论哪种方式,您都有责任管理所有事物的生命周期,每种方法都有必须由您处理的问题。