時間のかかる処理を行う際、進捗を表示させているModalダイアログの右上の×ボタンを押したときに処理を中断する方法


risa  2022-04-17 03:39:49  No: 150180  IP: [192.*.*.*]

 いつもお世話になっております。現在、ある長い処理をする際、2つ目のフォーム(Form2)に進捗状況を表示させています。
 このフォームでは、Escapeキーを押したとき、処理を中断する仕組みを設けており、実際に以下のとおりで動いています。ここで、2つめのフォームの右上の×ボタンをおしたとき、処理を中断できるよう、以下のようにFormCloseQueryを追加しました。
 しかし、実際に右上の×ボタンを押しても、確認用のShowMessageは発生せず、フォームも閉じませんでした。
 ここで、×ボタンを押したとき、処理を中断してフォームを閉じたい場合、どのようにすればよいか、ご教示をお願いできますでしょうか。
(FormKeyPressのイベントは問題なく発生しているのに、FormCloseQueryが発生しない理由もよくわかっておりません。)

※環境はWindows10 + Delphi6 Personalです。

procedure TForm2.FormActivate(Sender: TObject);
begin
  (時間のかかる処理⇒ループ内でApplication.ProcessMessageを回しており、CannotProgressがTrueになったときはすぐに処理を抜けられるようになっている。)
  PostMessage(Handle, WM_CLOSE, 0, 0);
end;

procedure TForm2.FormKeyPress(Sender: TObject; var Key: Char);
begin
  if Key = #27 then CannotProgress := True;
end;

procedure TForm2.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
begin
  ShowMessage('処理をキャンセルしました。'); // 確認用
  if not CannotProgress then CanClose := False; // FormActivateの中断処理が無事終わってからWM_Closeを送って閉じる想定のため、ここでは閉じない。
  CannotProgress := True;
end;

編集 削除
Mr.XRAY  2022-04-17 22:40:41  No: 150181  IP: [192.*.*.*]

> 実際に以下のとおりで動いています。

はてな ? ( イスカンダルのユリーシャ風に )
この後の文章では「以下のとおり」では動かないとも解釈できるような ?

もしかして Form2 をモーダル表示しています ?
でしたら [X] ボタン押下の検出には WM_NCLBUTTONDOWN メッセージを使用してみてください..

//-----------------------------------------------------------------------------
//  WM_NCLBUTTONDOWN メッセージ処理
//  FCancel は終了判定用のフラグ
//  時間のかかる処理のループ内あるいはールバック関数内でこの値をチェックする
//-----------------------------------------------------------------------------
procedure TForm2.WMNCLButtonDown(var Message: TWMNCLButtonDown);
begin
  inherited;
  if Message.HitTest = HTCLOSE then begin
    FCancel := True;
  end;
end;

//=============================================================================
//  Form2 の OnKeyPress イベント処理
//  FCancel は終了判定用のフラグ
//  時間のかかる処理のループ内あるいはールバック関数内でこの値をチェックする
//=============================================================================
procedure TForm2.FormKeyPress(Sender: TObject; var Key: Char);
begin
  if Key = Char(VK_ESCAPE) then begin
    FCancel := True;
   end;
end; 

//=============================================================================
//  フォームが閉じる時の処理
//  ModalResult の値は処理をキャンセルしたかどうかの判定に使用する
//=============================================================================
procedure TForm2.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if FCancel then begin
    ModalResult := mrCancel;
  end else begin
    ModalResult := mrOk;
  end;
end; 

  
    参考
[ 07_メッセージ処理メソッドの作成 ]
http://mrxray.on.coocan.jp/Delphi/Others/A_IDETechnique.htm#07
  

編集 削除
Mr.XRAY  2022-04-18 00:47:47  No: 150182  IP: [192.*.*.*]

> //  時間のかかる処理のループ内あるいはールバック関数内でこの値をチェックする 

こんな感じですね.
このコードのテストには uses に MMSystem が必要です.

procedure TForm2.FormActivate(Sender: TObject);
var
  TimeStart : Cardinal;
begin
  TimeStart := timeGetTime;
  while True do begin
    if FCancel or ((timeGetTime - TimeStart) >= 20000) then break;
    Application.ProcessMessages;
    Sleep(1);
  end;
  PostMessage(Handle, WM_CLOSE, 0, 0);
end;
  

編集 削除
risa  2022-04-19 10:54:47  No: 150184  IP: [192.*.*.*]

ご連絡が遅れ、申し訳ありません。(トラブル対応に追われ、対応に難儀しておりました)

Mr.XRAY様、ご丁寧にコメントまでつけて例をご提示くださり、誠にありがとうございます。現状、まだ出先で検証ができない状況ですが、右上の×ボタンをクリックしたときのイベントを捉えれば良いという発想ですね。
全く使ったことのない事例のため、動作を確認し、どのような動きになるのか見て見たいと思います。

取り急ぎ、このようなご回答をお書きくださいましたMr.XRAY様に深くお礼申し上げたいと思います。誠にありがとございます。後ほど改めてご報告させていただきます。

編集 削除
Mr.XRAY  2022-04-24 02:52:29  No: 150185  IP: [192.*.*.*]

時間がかかる処理中にフォームの OnClose 等のイベント等を捕捉するには,
時間のかかる処理を別スレッドで処理します.
Delphi 6 であれば,TThread, BeginThread が利用できます.

編集 削除
risa  2022-04-24 10:41:40  No: 150186  IP: [192.*.*.*]

 先日は貴重なご助言をいただき、ありがとうございました。
 先日より、いただいたコードの内容を確認し、意味をインターネットで調べていたのですが、
その際、「いただいたコードはマウスで×ボタンをクリックしたときの動作を拾うものだが、
マウス動作だけでなく、Alt+F4のキー操作が行われたときも同様に動作するのが自然ではない
か?」という考えに至りました。

 いただいたコードですと、OnActiveイベントの実行中であってもイベントとして拾えるよう
でしたので、マウスクリック以外にも「閉じる」操作が行われたときの動作を拾えるメッセージ
がないか、改めて探してみました。
 その結果、以下のようなコードに行きつきました。

  private
    procedure WMSysCommand(var Msg: TWMSysCommand);
      message WM_SYSCOMMAND;

procedure TForm2.WMSysCommand(var Msg: TWMSysCommand);
begin
  if Msg.CmdType = SC_CLOSE then begin
    CannotProgress  := True;
    Msg.Result := 0; // OnActivateの動作が無事終わった後、WM_Closeを送って閉じるので、ここでは閉じない
  end else begin
    inherited;
  end;
end;

 現在のところ、これで良好に動いているようです。当面、様子を見て、これで処理を
する方向で考えてみたいと思います。
 もともとはイベントの順番などを単純に変えれば行けるのではないかと思い、四苦八苦
しておりましたが、Mr.XRay様のコードを見て、飛んでくるメッセージの利用という別の
切り口から解決のめどを立てることができました。ご助言、誠にありがとうございました。
大変助かりました。

編集 削除