Delphi Programming / Object Pascal
[掲載 2005年11月23日] [更新 2013年05月19日] Delphi サンプルプログラム集 |
700_一定時間だけ処理を中断 |
動作確認等 |
Windows XP(SP3) + Delphi XE Pro
Windows 7 U64(SP1) + Delphi XE Pro |
|
700_WaitSleep.zip [2,155 KB] 2013年05月19日版 (EXE 同梱) |
- 2011年05月19日
- サンプルを追加
- WaitTime 関数の修正.戻り値を動作時間のミリ秒にしてタイマー,ストップウォッチとしても利用可能とした
- WaitTime 関数の修正.DateTime 型の精度は低いので,MMSystem の timeGetTime を使用
- 動作確認環境の変更
- 2012年04月29日
- サンプルを 1 つだけにして,Delphi XE で動作確認.記事を修正
- 2013年05月19日
- ボタンの Enabled := False 関係のサンプルを追加
|
01_Sleep 関数とマルチメディアタイマーを使用したテスト |
アプリケーションの実行中に,一定時間処理を中断したいことがあるとします.Sleep という関数を使用すれば可能ですが,この関数は実行したプロセスそのものを停止します.したがって,実行中は,フォームの移動などができなくなります.そのような場合,タイマー関係の関数を使用して,時間を計測し,Appliction の ProcessMessage を利用すると,この現象を避けることができます.このサンプルプログラムは,この現象を確認するためのものです.
ただし,Application.ProcessMaessage は,Windows がイベントに応答できるようアプリケーションの実行を一時的に停止する命令です.したがって,処理終了時にフォーム移動等,他の処理をしていると処理が戻ってきません.
このページには,タイマーコンポーネントを使用したサンプルもありましたが掲載を中止しました.
簡単なテストで使用するには十分なのですが,実用的なアプリで使用するには,スレッドによる処理が不可欠です.[004_タイマー] を参考にしてください. |
|
図1
設計時画面 |
|
図2
Button1 クリック
- Sleep(5000) による待ち時間開始
- 途中,フォームの移動等はできない
|
設定した時間が経過しても,フォームを移動したりしていると,下図のように指定した時間になっても終了しません. |
|
図3
Button2 をクリックしてフォームを移動
|
リスト1
一定時間だけ処理を中断
Sleep 関数とマルチメディアタイマー使用 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Panel1: TPanel;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
fgWaitBreak : boolean;
function WaitTime(const t: Cardinal): Cardinal;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses MMSystem;
{$R *.DFM}
//=============================================================================
// Sleep関数を使用して指定時間処理を停止
// この間,フォームの移動などはできなくなる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
StartTime : Cardinal;
begin
Panel1.Caption := '';
Panel1.Update;
StartTime := timeGetTime;
Sleep(5000);
Panel1.Caption := FormatFloat('# ミリ秒', timeGetTime - StartTime);
end;
//=============================================================================
// Application.ProcessMessageを使用して指定時間処理を待つ
// この間,フォームの移動,他のイベント操作も可能
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
StopTime : Cardinal;
begin
Panel1.Caption := '';
Panel1.Update;
StopTime := WaitTime(5000);
Panel1.Caption := FormatFloat('# ミリ秒', StopTime);
end;
//=============================================================================
// 引数のt(ミリ秒単位)で指定する時間だけ処理を待つ関数
// (精度は1ミリ秒- 最大1ミリ秒のゆらぎがある)
// 戻り値は待った時間(ミリ秒).途中で処理を中断した場合は,中断までの時間
// つまりストップウォッチとしても利用可能
//
// 処理中,このプログラムのどこかでfgWaitBreakをTrueにすると待つ処理を中断
//
// [備考]
//、Application.ProcessMaessageは,Windowsがイベントに応答できるようアプリケー
// ションの実行を一時的に停止する命令
// したがって,処理終了時にフォーム移動等,他の処理をしているとこの関数の処理
// は終了しない.それを避けるためには,スレッド処理等を使用する
//
// usesにMMSystmeが必要
//=============================================================================
function TForm1.WaitTime(const t: Cardinal): Cardinal;
var
StartTime : Cardinal;
DisTime : Cardinal;
begin
fgWaitBreak := False;
StartTime := timeGetTime;
while True do begin
DisTime := timeGetTime - StartTime;
if DisTime >= t then break;
if fgWaitBreak then break;
Application.ProcessMessages;
Sleep(1);
end;
Result := DisTime;
end;
end.
02_TButton の Enabled := False の時の描画の問題 |
中断中に,ボタンの操作を禁止したい場合,TButton.Enabled := False にすることになります.そこで上のサンプルにこのコードを追加してテストしてみます.
その結果が下図です.
下の Button2 では期待通りに動作しています.問題は Button1 の場合です.クリックすると,初回起動時と,2 回目以降とは違います.2 回目以降の動作は次のようになります.
- Button1 は Disable な表示になる
- Button2 は指定時間経過後,一瞬無効 (Disable) な表示となり,すぐに元に戻る
実際には,Button2 は無効にはなっています.表示つまり,描画が更新されないために,このようになります.Windows Vista 以降では,ランタイムテーマを有効にしていると,UI インターフェイスのレンダリング (描画) が複数のフレームを使用したものとなります.この描画の遅延のために発生します.事実,上のサンプルでも,ボタンをクリックする動作を観察していると,ボタン表面の描画の変化が少し緩慢であることが分かります. |
|
図4
Button1 クリック
- Button2 の表示が Enabled := False の描画状態となってくれない
|
|
図5
Button2 クリック
|
当然ランタイムテーマを無効にすれば,下図のように期待通りに動作します.また,ランタイムテーマが有効であっても,Windows XP であれば期待通りに動作します. |
|
図6
ランタイムテーマが無効の場合
|
リスト2
ボタンの Enabled := False のテスト |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
uses MMSystem;
{$R *.DFM}
//=============================================================================
// Sleep関数をで一定時間処理を停止
// 単なるEnabled := False のテスト用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.Enabled := False;
Button2.Enabled := False;
Sleep(3000);
Button1.Enabled := True;
Button2.Enabled := True;
end;
//=============================================================================
// Application.ProcessMessageを使用して指定時間処理を待つ
// 実際のアプリに近いかもしれないコードでの Enabled := False の動作
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
StartTime : Cardinal;
begin
Button1.Enabled := False;
Button2.Enabled := False;
StartTime := TimeGetTime;
//何かの処理の代わりとしてのループ
while True do begin
if (timeGetTime - StartTime) > 3000 then break;
Application.ProcessMessages;
Sleep(1);
end;
Button1.Enabled := True;
Button2.Enabled := True;
end;
end.
03_TButton の Enabled := False の時に Repaint で再描画 |
上のサンプルの現象を回避するには,ボタンを Repaint 等で強制的に再描画することになります.
ただし,単に Repaint を実行しただけでは,描画の更新が間に合わないことがあります.そこでこのサンプルでは Sleep 関数でタイムラグを持たせ,その間に Windows にボタンの描画の内部処理をさせるようにしています.下図は,150 ms だけ Sleep 関数でタイムラグを設定した場合です. |
|
図7
Sleep と Repaint 関数を使用して再描画 |
短いタイムラグだと,再描画が完全に完了しないこともあります.下図は 100 ms の場合です. |
|
図8
Sleep(100) の場合 |
リスト3
Sleep 関数と Repaint 関数を使用して,ボタンの再描画を実行 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
//=============================================================================
// Sleep関数をで一定時間処理を停止
// 単なるEnabled := False のテスト用
// Sleepでタイムラグを持たせてからRepaintで再描画する
// Sleepの引数の値で状態が微妙に違ってくる
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.Enabled := False;
Button2.Enabled := False;
Sleep(150);
Button1.Repaint;
Button2.Repaint;
Sleep(3000);
Button1.Enabled := True;
Button2.Enabled := True;
end;
end.
上のサンプルのように,ボタンの再描画を実行すれば,ボタンの表示も更新されます.
ただし,Sleep 関数はプロセスを一時的に停止するだけで,その間のキーやマウスの操作はバッファに蓄えられます.したがって,Sleep で設定した時間が経過すると,そのバッファに記録された内容が実行されてしまいます.正確な表現ではありませんが,Application.ProcessMessages を実行するとこのバッファの一部をクリアすることができます.そのテストです.以下の手順で確認できます.
- (1) Button1 をクリック
- (2) ダイアログが表示されるので,{OK] をクリックして閉じる
- (3) 閉じたらすぐ Button1 をクリックする
- (4) 指定時間経過すると,またダイアログが現れる
- (5) 今度は,[二重クリック防止] にチェックを入れて同じテストを行う
この対策も,実は完全ではありません.このページの最初のサンプルの参考リンクにあるように,対処方法はケースバイケースとなるでしょう.
実際のアプリケーションでは,このサンプルのように,再描画が行われるようなコードが実行される状況であれば,再描画の処理コードは必要ないことになります. |
|
図9
実行開始画面 |
|
図10
テスト中の画面 |
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
CheckBox1: TCheckBox;
Button2: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
//=============================================================================
// Sleep関数をで一定時間処理を停止
// 単なるEnabled := False のテスト用
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
Button1.Enabled := False;
Button2.Enabled := False;
//ダイアログを表示すると再描画が行われるので以下の3行は不要
//Sleep(100);
//Button1.Repaint;
//Button2.Repaint;
MessageBox(Handle, '二重クリック防止テスト', 'テスト', MB_OK);
Sleep(3000);
if CheckBox1.Checked then Application.ProcessMessages;
Button1.Enabled := True;
Button2.Enabled := True;
end;
end.
|