第9章 リストボックスの応用

リストボックスは多数の項目を表示したり、表示項目から選択する機能を持っている。Win32 APIには EnumXXXX という、コールバック関数をつかって様々な情報を列挙する関数が幾つかある。 ラジオボタンの実装のときには EnumChildWindows( ) 関数を使った。これらの関数をはじめ、この節では、 リストボックスコントロールをつかった小さなアプリケーションを作ってみよう。

9−0 この章の内容

第9章 リストボックスの応用
9−1 ドラグドロップによるファイル名の取得
9−2 TAPIDragDrop クラス
9−3 ファイルの列挙
9−4 壁紙チェンジャ
9−5 ウィンドウの列挙
9−6 まとめ


| arrw18c.gif 前の章へ | 総目次へ | 次の章へ arrw18d.gif

9−1 ドラグドロップによるファイル名の取得

エクスプローラやファイルマネージャからファイルをドラグドロップすることにより「開く」ファイル名を取得できるアプリケーションは多数ある。「ファイルを開く」ダイアログからファイルにアクセスするよりは直接的で非常に気持ちがよい。 DelphiVCLには標準ではこの機能はサポートされていないが、コンポーネントパレットからフォームにドロップするだけでこの機構をサポートするフリーの非可視コンポーネントがウェブからダウンロードできる。この節では、この機能をコードレベルでどのように実現するか調べよう。

エクスプローラからのドラグドロップによってファイル名を取得するためには、ShellAPI.pas で定義されている DragAcceptFiles( )DragQueryFile( )DragFinish( ) の三つのAPIをつかう。DragAcceptFiles( ) は、

  procedure DragAcceptFiles(Wnd: HWND; 
                         Accept: BOOL); stdcall;

と定義され、 Accepttrue にして実行すると、 Wnd パラメータで指定されたウィンドウのクライアント領域でファイルのドロップを受け付けるようになる。 このとき、ファイルのドロップが起こると、OSはそのウィンドウ関数に WM_DROPFILES メッセージを送る。 Win32.hlp では、

WM_DROPFILES 

hDrop = (HANDLE) wParam;  // handle of internal drop structure 

と定義され、wParam が「ドロップハンドル」を意味している。 このメッセージの処理ハンドラでは、DragQueryFile( ) APIをつかって、ドロップされたファイルの数とフルパス付きのファイル名を取得する。

  function DragQueryFile(Drop: HDROP; 
                    FileIndex: UINT; 
                     FileName: PChar; 
                           cb: UINT): UINT; stdcall;

第一パラメータ Drop には、このメッセージの「ドロップハンドル」つまり wParam を指定する。 FileIndex には取得するファイルのインデックスを指定する。これに $FFFFFFFFを指定すると、この関数はドロップされたファイルの数を返す。 FileName にはファイル名を受け取る PChar 型のバッファ、cb にはそのサイズを指定する。 ファイル名がバッファにコピーされた場合は、この関数はコピーされた文字数(バイト数、#0は含まず)を返す。 全てのファイル名を取得したら、処理の最後に DragFinish( ) APIを呼び出して、メッセージの処理が終了したことをOSに通知する。

  procedure DragFinish(Drop: HDROP); stdcall;

この手続きのパラメータには「ドロップハンドル」を指定する。これで WM_DROPFILES メッセージの処理を終える。 プログラムが終了するときには、 DragAcceptFiles( ) の第二パラメータに false を設定して実行することにより、ドラグドロップ機構を使い終えたことをOSに追加する。 以上の機能を試してみよう。

  1. エクスプローラを使って、新しいフォルダを 0901DragDrop01 という名前でつくる。
  2. 作ったフォルダに、エクスプローラを使って 0804ListBox03 フォルダの Skeleton02.dprUtilFunc.pasUtilClass.pasGDIObject.pasAPIControl.pas をコピーする。
  3. コピーした Skeleton02.dpr をダブルクリックして Delphi を立ち上げる。
  4. コードエディタに Skeleton02.dpr のコードがあることを確認してから、[ファイル]メニューから[プロジェクトに名前を付けて保存]を選んで DragDrop01.dpr という名前で保存する。
  5. スピードメニューから[プロジェクトに追加]を選んで APIControl.pas を追加する。
  6. コードエディタで以下のように編集する。
  7. コンパイルして実行する。

最初に、ドラグドロップをサポートするAPIShellAPI.pas で定義されているので DragDrop01.dpruses 節に ShellAPI を加える。 以下に全リストを示す。

program DragDrop01;

uses
  windows,
  messages,
  UtilFunc in 'UtilFunc.pas',
  UtilClass in 'UtilClass.pas',
  GDIObject in 'GDIObject.pas',
  APIControl in 'APIControl.pas',
  ShellAPI;      // これを追加

//------------------------------------------------------------
//             Global Types, Constants and Variables
//------------------------------------------------------------
var
  Clr: COLORREF;

  LB1: TAPIListBox;
  Label1: TAPILabel;

//------------------------------------------------------------
//             Object Event Handlers
//------------------------------------------------------------
procedure LB1SelChange(var Msg: TMessage);
begin
  Label1.Text:= LB1.SelText;
end;

//------------------------------------------------------------
//             Event Handlers
//------------------------------------------------------------
procedure OnCreate(var Msg: TMessage);
var
  hWindow: HWND;
begin
  hWindow := Msg.Msg;
  Clr := clrBtnFace;
  MakeInitMenu(hWindow);
  ChangeWindowSize(hWindow,580,390);
  CenterWindow(hWindow);

  Label1 := CreateLabel(hWindow,10,10,550,30,'');
  with Label1 do begin
    DrawFrame := true;
    Margin := 5;
  end;

  LB1 := CreateListBox(hWindow,10,50,550,280,LB1SelChange);

  DragAcceptFiles(hWindow,true);
end;

procedure OnTest(hWindow: HWND);
begin
  LB1.DoSort;
end;

procedure OnCommand(var Msg: TMessage);
begin
  InitMenuCommand(Msg,OnTest);
end;

procedure OnDropFiles(var Msg:TMessage);
var
  hDP: HDROP;
  i,numFile,sizeCopied: integer;
  PC: PChar;
  s: string;
begin
  hDP := HDROP(Msg.WParam);
  numFile := DragQueryFile(hDP,$FFFFFFFF,nil,0);
  LB1.Clear;
  LB1.Redraw := false;
  GetMem(PC,MAX_PATH+1);
  for i := 0 to numFile-1 do begin
    sizeCopied := DragQueryFile(hDP,i,PC,MAX_PATH+1);
    SetString(s,PC,sizeCopied);
    LB1.AddString(s);
  end;
  FreeMem(PC);
  DragFinish(hDP);
  LB1.Redraw := true;
  LB1.TopIndex := LB1.Count-6;
  Label1.Text := AIntToStr(LB1.Count)+' Files Dropped.';
end;

procedure OnEraseBkgnd(var Msg: TMessage);
begin
  Msg.Result := EraseBkGnd(Msg.Msg,Clr,Msg.WParam);
end;

procedure OnDestroy(var Msg: TMessage);
begin
  DragAcceptFiles(Msg.Msg,false);
  PostQuitMessage(0);
end;

//------------------------------------------------------------
//             Main Window Procedure
//------------------------------------------------------------
function MainWndProc(hWindow: HWND; Msg: UINT; WParam: WPARAM;
                      LParam: LPARAM): LRESULT; stdcall;
var
  AMsg: TMessage;
begin
  Result := 0;

  AMsg.Msg := hWindow;
  AMsg.WParam := WParam;
  AMsg.LParam := LParam;
  AMsg.Result := 0;

  case Msg of

//-------------  WM_CREATE  -----------------------------------

     WM_CREATE: OnCreate(AMsg);

//-------------  WM_COMMAND  ----------------------------------

     WM_COMMAND: OnCommand(AMsg);

//-------------  WM_DROPFILES  --------------------------------

     WM_DROPFILES: OnDropFiles(AMsg);

//-------------  WM_ERASEBKGND  -------------------------------

     WM_ERASEBKGND: begin
       OnEraseBkgnd(AMsg);
       result := AMsg.Result;
     end;

//-------------  WM_DESTROY  ----------------------------------

     WM_DESTROY: OnDestroy(AMsg);

//-------- そのほかのメッセージの処理 ------
  else begin
    result := DefWindowProc( hWindow, Msg, wParam, lParam );
    end;

  end; // case
end;

//------------------------------------------------------------
//             Main Procedure
//------------------------------------------------------------
begin

  MakeMainWindow(@MainWndProc);

  Halt(MessageLoopNormal);

end.

DownloadExe.gifDragDrop01.zip(16.685kB)
DragDrop01.gif

以上で完成だ。実行してエクスプローラからファイルをドロップすると上図のようになる。 OnCreate( ) ハンドラでは、ラベルとリストボックスを一つずつ作った後で、 DragAcceptFiles( )true を設定して、ドラグドロップ機能を使うことをOSに通知する。 OSからメインウィンドウのウィンドウ関数に WM_DROPFILES が送られてきたら、 OnDropFiles( ) ハンドラが実行される。

procedure OnDropFiles(var Msg:TMessage);
var
  hDP: HDROP;
  i,numFile,sizeCopied: integer;
  PC: PChar;
  s: string;
begin
  hDP := HDROP(Msg.WParam);
  numFile := DragQueryFile(hDP,$FFFFFFFF,nil,0);
  LB1.Clear;
  LB1.Redraw := false;
  GetMem(PC,MAX_PATH+1);
  for i := 0 to numFile-1 do begin
    sizeCopied := DragQueryFile(hDP,i,PC,MAX_PATH+1);
    SetString(s,PC,sizeCopied);
    LB1.AddString(s);
  end;
  FreeMem(PC);
  DragFinish(hDP);
  LB1.Redraw := true;
  LB1.TopIndex := LB1.Count-6;
  Label1.Text := AIntToStr(LB1.Count)+' Files Dropped.';
end;

ここでは、ドロップハンドルを取得し、ドロップされたファイルの数を取得してから、リストボックスをクリアし、再描画を止めてから for 文でファイル名を取得してリストボックスに追加している。 全てのファイル名が取得できたら、DragFinish( )を実行してから、リストボックスを再描画する。ラベルには、ドロップされたファイル数が表示される。 ファイル名を受け取るために、ここでは PChar 型のローカル変数 PCMAX_PATH+1 バイトのメモリを割り当てている。 MAX_PATHWin32 の定義済みの定数であり、ファイル名が取りうる最大バイト数を示している。 Windows.pas には、

const
  MAX_PATH = 260;

と定義されている。 最後に OnDestroy( ) ハンドラで DragAcceptFiles(Msg.Msg,false) を実行している。

greenbal.gifこの章の目次へ戻る

9−2 TAPIDragDrop クラス

前項で示したドラグドロップの機能をカプセル化するクラス TAPIDragDrop を作ることを考える。 ファイルのドロップイベントは DragAcceptFiles( ) で指定したウィンドウ関数に WM_DROPFILES メッセージとして伝達される。 これは、タイマのときの WM_TIMER と同じ仕様なので TAPITimer と同じように TSubClass の派生クラスとして設計しよう。 そうすれば親ウィンドウのウィンドウ関数は自動的にサブクラス化されて WM_DROPFILES メッセージをトラップできるし、 パスカルオブジェクトは親ウィンドウが破棄されるとき自動的に破棄されることになる。 このクラスを作ってテストするプログラムを作ろう。

  1. エクスプローラを使って、新しいフォルダを 0902DragDrop02 という名前でつくる。
  2. 作ったフォルダに、エクスプローラを使って 0901DragDrop01 フォルダの Skeleton02.dprUtilFunc.pasUtilClass.pasGDIObject.pasAPIControl.pas をコピーする。
  3. コピーした Skeleton02.dpr をダブルクリックして Delphi を立ち上げる。
  4. コードエディタに Skeleton02.dpr のコードがあることを確認してから、[ファイル]メニューから[プロジェクトに名前を付けて保存]を選んで DragDrop02.dpr という名前で保存する。
  5. スピードメニューから[プロジェクトに追加]を選んで APIControl.pas を追加する。
  6. コードエディタにフォーカスがあるようにしてから、[ Ctrl ] + [ f.12 ] を押して表示されるダイアログから UtilClass.pas を選んで OK ボタンを押し、コードエディタにこのユニットファイルを表示させる。
  7. コードエディタで以下のように編集する。
  8. コンパイルして実行する。

最初に、UtilClass.pasuses 節に ShellAPI を追加する。TAPIDragDrop クラスの宣言部を以下に示す。

//------------- TAPIDragDrop ----------------------------------------------
type
TAPIDragDrop = class(TSubClass)
private
  FFiles: TStringArray;
  FFileCount : integer;
  FOnDrop: TNotifyMessage;
  function GetFiles(i: integer): string;
public
  constructor Create(hParent:HWND); override;
  destructor Destroy; override;
  procedure DefaultHandler(var Message);override;
  procedure SetFocus(hWnd: HWND);
  property FileCount: integer read FFileCount;
  property Files[i: integer]: string read GetFiles;
  property OnDrop: TNotifyMessage read FOnDrop write FOnDrop;
end;

ドロップされるファイルの数をあらかじめ知ることはできないので、ファイル名の配列を動的に保持するクラスが必要になる。 ここでは、8−2節で作った TStringArray クラスのインスタンス FFiles に保持しよう。 WM_DROPFILES メッセージが届いたら FileCount プロパティーからドロップされたファイルの数を取得し、Files プロパティーを通してファイル名のリストを受け取る。SetFocus( ) メソッドは、ドロップ後に任意のウィンドウにフォーカスを移動させるために使う。

実装部を以下のようにする。

//------------- TAPIDragDrop ----------------------------------------------
constructor TAPIDragDrop.Create(hParent:HWND);
begin
  inherited Create(hParent);

  Dispatcher.TrapMessage(WM_DROPFILES,self);
  FFiles := TStringArray.Create;
  DragAcceptFiles(hParent, true);
end;

destructor TAPIDragDrop.Destroy;
begin
  FFiles.Free;
  DragAcceptFiles(FParent, false);
  inherited Destroy;
end;

procedure TAPIDragDrop.DefaultHandler(var Message);
var
  AMsg: TMessage;
  hD: HDROP;
  i: integer;
  PC: PChar;
begin
  AMsg := TMessage(Message);
  case AMsg.Msg of
    WM_DROPFILES:begin
      FFiles.Clear;
      hD := AMsg.wParam;
      GetMem(PC,MAX_PATH+1);
      FFileCount := DragQueryFile(hD,$FFFFFFFF,nil,0);
      for i := 0 to FFileCount-1 do begin
        DragQueryFile(hD,i,PC,MAX_PATH+1);
        FFiles.Add(string(PC));
      end;
      FreeMem(PC);
      DragFinish(hD);
      if Assigned(FOnDrop) then begin
        AMsg.Msg := UINT(self); // sender
        OnDrop(AMsg);
      end;
    end;
  end; // case
end;

procedure TAPIDragDrop.SetFocus(hWnd: HWND);
begin
  SetForeGroundWindow(FParent);
  Windows.SetFocus(hWnd);
end;

function TAPIDragDrop.GetFiles(i: integer): string;
begin
  result := FFiles[i];
end;

コンストラクタでは、継承メソッドを実行した後 DispatcherWM_DROPFILES メッセージを配送するように依頼している。 そして、FFiles をつくり、DragAcceptFiles( ) により親ウィンドウがファイルのドロップを受け取れるようにしている。デストラクタでは DragAcceptFiles( ) によりドロップ動作の受け入れを禁止している。DefaultHandler では、WM_DROPFILES メッセージだけを処理し、 ファイル名のリストを TStringArray 型の FFiles に保持している。そのあと DragFinish( ) を実行し、イベントハンドラを呼び出している。 個々のファイル名へのアクセスメソッド GetFiles( ) では、FFiles の項目にアクセスしている。 SetForcus( ) メソッドでは、APIをつかってメイウィンドウを最も手前に表示し、パラメータのハンドルで示されるウィンドウにフォーカスを移動している。

  function SetForegroundWindow(hWnd: HWND): BOOL; stdcall;

このAPIは、hWnd で示されるウィンドウをアクティブにして最も手前に表示する。

  function SetFocus(hWnd: HWND): HWND; stdcall;

このAPIは、キーボードフォーカスを hWnd で示されるウィンドウに設定する。

それでは、TAPIDragDrop が出来たのでこれをテストするプログラムを DragDrop02.dpr につくろう。以下に全リストを示す。

program DragDrop02;

uses
  windows,
  messages,
  UtilFunc in 'UtilFunc.pas',
  UtilClass in 'UtilClass.pas',
  GDIObject in 'GDIObject.pas',
  APIControl in 'APIControl.pas';

//------------------------------------------------------------
//             Global Types, Constants and Variables
//------------------------------------------------------------
var
  Clr: COLORREF;

  LB1: TAPIListBox;
  Label1: TAPILabel;
  DD1: TAPIDragDrop;

//------------------------------------------------------------
//             Object Event Handlers
//------------------------------------------------------------
procedure DD1Drop(var Msg:TMessage);
var
  i: integer;
begin
  LB1.Clear;
  LB1.Redraw := false;
  for i := 0 to DD1.FileCount-1 do LB1.AddString(DD1.Files[i]);
  LB1.Redraw := true;
  Label1.Text := AIntToStr(DD1.FileCount)+ ' Files Dropped.';
  DD1.SetFocus(LB1.Handle);
end;

procedure LB1SelChange(var Msg: TMessage);
begin
  Label1.Text:= LB1.SelText;
end;

//------------------------------------------------------------
//             Event Handlers
//------------------------------------------------------------
procedure OnCreate(var Msg: TMessage);
var
  hWindow: HWND;
begin
  hWindow := Msg.Msg;
  Clr := clrBtnFace;
  MakeInitMenu(hWindow);
  ChangeWindowSize(hWindow,580,390);
  CenterWindow(hWindow);

  Label1 := CreateLabel(hWindow,10,10,550,30,'');
  with Label1 do begin
    DrawFrame := true;
    Margin := 5;
  end;

  LB1 := CreateListBox(hWindow,10,50,550,280,LB1SelChange);
  LB1.Color := RGB(255,255,230);

  DD1 := TAPIDragDrop.Create(hWindow);
  DD1.OnDrop := DD1Drop;

end;

procedure OnTest(hWindow: HWND);
begin
  LB1.DoSort;
end;

procedure OnCommand(var Msg: TMessage);
begin
  InitMenuCommand(Msg,OnTest);
end;

procedure OnEraseBkgnd(var Msg: TMessage);
begin
  Msg.Result := EraseBkGnd(Msg.Msg,Clr,Msg.WParam);
end;

procedure OnDestroy(var Msg: TMessage);
begin
  PostQuitMessage(0);
end;

//------------------------------------------------------------
//             Main Window Procedure
//------------------------------------------------------------
function MainWndProc(hWindow: HWND; Msg: UINT; WParam: WPARAM;
                      LParam: LPARAM): LRESULT; stdcall;
var
  AMsg: TMessage;
begin
  Result := 0;

  AMsg.Msg := hWindow;
  AMsg.WParam := WParam;
  AMsg.LParam := LParam;
  AMsg.Result := 0;

  case Msg of

//-------------  WM_CREATE  -----------------------------------

     WM_CREATE: OnCreate(AMsg);

//-------------  WM_COMMAND  ----------------------------------

     WM_COMMAND: OnCommand(AMsg);

//-------------  WM_ERASEBKGND  -------------------------------

     WM_ERASEBKGND: begin
       OnEraseBkgnd(AMsg);
       result := AMsg.Result;
     end;

//-------------  WM_DESTROY  ----------------------------------

     WM_DESTROY: OnDestroy(AMsg);

//-------- そのほかのメッセージの処理 ------
  else begin
    result := DefWindowProc( hWindow, Msg, wParam, lParam );
    end;

  end; // case
end;

//------------------------------------------------------------
//             Main Procedure
//------------------------------------------------------------
begin

  MakeMainWindow(@MainWndProc);

  Halt(MessageLoopNormal);

end.

DownloadExe.gifDragDrop02.zip(17.090kB)
DragDrop02.gif

以上で完成である。 実行してエクスプローラからファイルをドロップすると上図のようになる。 このように、TAPIDragDrop はインスタンスをつくってハンドラを書くだけで使える。 DD1Drop( ) ハンドラを見てみよう。

procedure DD1Drop(var Msg:TMessage);
var
  i: integer;
begin
  LB1.Clear;
  LB1.Redraw := false;
  for i := 0 to DD1.FileCount-1 do LB1.AddString(DD1.Files[i]);
  LB1.Redraw := true;
  Label1.Text := AIntToStr(DD1.FileCount)+ ' Files Dropped.';
  DD1.SetFocus(LB1.Handle);
end;

ここでは、リストボックスの内容をクリアしてから、再描画を禁止し、ファイル名を取得して追加している。 そして、再描画をしてから、ラベルにファイル数を表示し、リストボックスにフォーカスを移動している。 TAPIDragDrop クラスはRAD環境でも正常に動作する。このことの確認は読者にお任せする。

greenbal.gifこの章の目次へ戻る

9−3 ファイルの列挙

エクスプローラからファイルがドロップされると、そのファイルがあるフォルダの全てのファイルをリストボックスに表示するプログラムを作ろう。 フルパス付きのファイル名からファイル名だけを取り出す ExtractFileName 関数や、拡張子だけを取り出す ExtractFileExt 関数、ディレクトリ部分を取り出す ExtractFileDir 関数は SysUtils.pas に定義されている。 また、ディレクトリ全体を走査して、全てのファイルを列挙する必要がある。 DelphiObject Pascal にはやはり SysUtils.pas で定義された FindFirst 関数、FindNext 関数、FindClose 手続きがあるが、これは以下の三つのAPIをカプセル化したものだ。

  function FindFirstFile(lpFileName: PChar; 
                 var lpFindFileData: TWIN32FindData): THandle; stdcall;

  function FindNextFile(hFindFile: THandle; 
               var lpFindFileData: TWIN32FindData): BOOL; stdcall;

  function FindClose(hFindFile: THandle): BOOL; stdcall;

FindFirstFile( ) の第一パラメータ lpFileName には、フルパス付きのファイル名の文字列を指定する。 この文字列にはワイルドカード文字( ? と * ) が含まれていても良い。 例えば、'C:\Windows\System\*.dll' というふうに指定する。 変数渡しの lpFindFileData パラメータは、見つかったファイルやディレクトリについての情報が格納される TWin32FindData 型のレコードが返される。

  TWin32FindDataA = record
    dwFileAttributes: DWORD;      // ファイル属性
    ftCreationTime: TFileTime;    // 作成日時
    ftLastAccessTime: TFileTime;  // 最終アクセス日時
    ftLastWriteTime: TFileTime;   // 最終更新日時
    nFileSizeHigh: DWORD;         // ファイルサイズの上位32ビット値
    nFileSizeLow: DWORD;          // ファイルサイズの下位32ビット値 
    dwReserved0: DWORD;           // 予約
    dwReserved1: DWORD;           // 予約
    cFileName: array[0..MAX_PATH - 1] of AnsiChar; // ファイル名 
    cAlternateFileName: array[0..13] of AnsiChar;  // 8.3形式での名前
  end;
  TWin32FindData = TWin32FindDataA;

このレコードの dwFileAttributes フィールドにはファイルの属性が格納されている。 これは Windows.pas に定義されている以下の定数の幾つかが入っている。

const
  FILE_ATTRIBUTE_READONLY             = $00000001; 
  FILE_ATTRIBUTE_HIDDEN               = $00000002; 
  FILE_ATTRIBUTE_SYSTEM               = $00000004; 
  FILE_ATTRIBUTE_DIRECTORY            = $00000010; 
  FILE_ATTRIBUTE_ARCHIVE              = $00000020; 
  FILE_ATTRIBUTE_NORMAL               = $00000080; 
  FILE_ATTRIBUTE_TEMPORARY            = $00000100; 

これらの定数はビットフラグなので、dwFileAttributes フィールドと and を取ると含まれているかどうか判断できる。 ftCreationTime フィールドにはファイルが作成された時刻が TFileTime 型レコードとして格納されている。

  TFileTime = record
    dwLowDateTime: DWORD;
    dwHighDateTime: DWORD;
  end;

このレコード型は、西暦1601年1月1日から経過した時刻を100ナノ秒を単位とした64ビット値の下位32ビットと上位32ビットが格納されている。 TWin32FindData レコードの ftLastAccessTime フィールドには、ファイルの最終アクセス時刻、ftLastWriteTime には最終書き込み時刻が TFileTime 型のレコードとして格納されている。 nFileSizeHighnFileSizeLow フィールドにはファイルのバイトサイズのそれぞれ上位32ビットと下位32ビットが格納されている。 cFileName にはファイル名が PChar 型互換の文字列として、また cAlternateFileName にはそのファイル名を 8.3 形式の短いファイル名で表した文字列が格納されている。

FindFirstFile( ) 関数は成功すると、以後の FindNextFile( ) FindClose( ) 関数で使われる「検索ハンドル」を返す。 失敗すると定数 INVALID_HANDLE_VALUE を戻す。 FindNextFile( ) APIの第二パラメータには、やはり見つかったファイルの情報を格納する TWin32FindData 型のレコードを変数渡しで指定する。 この関数は、失敗すると、つまり最後のファイルを見つけた後に呼び出されると失敗して false を返す。 その後で FindClose( ) によって検索ハンドルを破棄する。

ここでは、上の三つのAPIをつかって、ファイルを列挙したり、ファイルの属性を取得したりする関数を UtilClass.pas に作成し、テストするプログラムをつくる。

  1. エクスプローラを使って、新しいフォルダを 0903FileList という名前でつくる。
  2. 作ったフォルダに、エクスプローラを使って 0902DragDrop02 フォルダの Skeleton02.dprUtilFunc.pasUtilClass.pasGDIObject.pasAPIControl.pas をコピーする。
  3. コピーした Skeleton02.dpr をダブルクリックして Delphi を立ち上げる。
  4. コードエディタに Skeleton02.dpr のコードがあることを確認してから、[ファイル]メニューから[プロジェクトに名前を付けて保存]を選んで FileList.dpr という名前で保存する。
  5. スピードメニューから[プロジェクトに追加]を選んで APIControl.pas を追加する。
  6. コードエディタにフォーカスがあるようにしてから、[ Ctrl ] + [ f.12 ] を押して表示されるダイアログから UtilClass.pas を選んで OK ボタンを押し、コードエディタにこのユニットファイルを表示させる。
  7. コードエディタで以下のように編集する。
  8. コンパイルして実行する。

最初に、UtilClass.pas にファイルを列挙したり、ファイルの情報を取得する関数を作成する。 列挙されたファイル名のリストを TStringArray 型の変数を介して受け渡しをしたいので、 循環参照を避けるために、TStringArray クラスが実装されている UtilClass.pas にこれらの関数を作成することにした。 前項でつくった TAPIDragDrop クラスの宣言の下に、以下の宣言を追加する。

//------------- File Function -----------------------------------------
function AExtractFileName(s: string):string;
function AExtractDir(s: string): string;
function AExtractExt(s: string): string;

procedure FindAllFilesWithExt(dir,ext:string;SL:TStringArray);
procedure FindAllFiles(dir:string;SL:TStringArray);

function GetFileSize(FullPath: string): integer;
function GetFileCreationTime(FullPath: string): TSystemTime;
function GetFileLastAccessTime(FullPath: string): TSystemTime;
function GetFileLastWriteTime(FullPath: string): TSystemTime;

function GetFileAttributes(FullPath: string): DWORD;

実装部の対応する場所に、以下のコードを追加して、これらの関数を実装する。

//------------- File Function -----------------------------------------
function GetLowDir(var Path: string): Boolean;
var
  p,Len: integer;
begin
  result := false;
  p := Pos('\',Path);
  if p <> 0 then begin
    result := true;
    Len := Length(Path)-p;
    Path := Copy(Path,p+1,Len);
    SetLength(Path,Len);
  end;
end;

function AExtractFileName(s: string):string;
var
  Path: string;
begin
  Path := s;
  while GetLowDir(Path) do result := Path;
end;

function AExtractDir(s: string): string;
var
  fn: string;
begin
  fn := AExtractFileName(s);
  result := Copy(s,0,Length(s)-Length(fn)-1);
end;

function AExtractExt(s: string): string;
var
  fn: string;
  i: integer;
begin
  fn := AExtractFileName(s);
  i := Pos('.',fn);
  if i <> 0 then
    result := Copy(fn,i+1,Length(fn))
  else
    result := '';
end;

procedure FindAllFilesWithExt(dir,ext:string;SL:TStringArray);
var
  s: string;
  hFind: THandle;
  fd: TWin32FindData;
  Ret: Boolean;
begin
  s := dir + '\*.'+ext;
  hFind := FindFirstFile(PChar(s),fd);

  Ret := true;
  while ( (hFind <> INVALID_HANDLE_VALUE) and Ret ) do begin
    if (fd.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
      SL.Add(string(fd.cFileName));
    Ret := FindNextFile(hFind,fd);
  end;
  Windows.FindClose(hFind);
end;

procedure FindAllFiles(dir:string;SL:TStringArray);
begin
  FindAllFilesWithExt(dir,'*',SL);
end;

function GetFileSize(FullPath: string): integer;
var
  fd: TWin32FindData;
begin
  FindClose(FindFirstFile(PChar(FullPath),fd));
  result := fd.nFileSizeLow;
end;

function GetFileCreationTime(FullPath: string): TSystemTime;
var
  fd: TWin32FindData;
  ft: TFileTime;
begin
  FindClose(FindFirstFile(PChar(FullPath),fd));
  FileTimeToLocalFileTime(fd.ftCreationTime,ft);
  FileTimeToSystemTime(ft,result);
end;

function GetFileLastAccessTime(FullPath: string): TSystemTime;
var
  fd: TWin32FindData;
  ft: TFileTime;
begin
  FindClose(FindFirstFile(PChar(FullPath),fd));
  FileTimeToLocalFileTime(fd.ftLastAccessTime,ft);
  FileTimeToSystemTime(ft,result);
end;

function GetFileLastWriteTime(FullPath: string): TSystemTime;
var
  fd: TWin32FindData;
  ft: TFileTime;
begin
  FindClose(FindFirstFile(PChar(FullPath),fd));
  FileTimeToLocalFileTime(fd.ftLastWriteTime,ft);
  FileTimeToSystemTime(ft,result);
end;

function GetFileAttributes(FullPath: string): DWORD;
var
  fd: TWin32FindData;
begin
  FindClose(FindFirstFile(PChar(FullPath),fd));
  result := fd.dwFileAttributes;
end;

AExtractFileName( )AExtractDir( )AExtractExt( ) 関数については、Win32 APIが使われていないので説明しない。 これで、ドロップされたフルパス付きのファイル名からディレクトリの名前を抽出出来るようになった。 特定の拡張子を持ったファイルを列挙する FindAllFilesWithExt( ) 関数を見てみよう。

procedure FindAllFilesWithExt(dir,ext:string;SL:TStringArray);
var
  s: string;
  hFind: THandle;
  fd: TWin32FindData;
  Ret: Boolean;
begin
  s := dir + '\*.'+ext;
  hFind := FindFirstFile(PChar(s),fd);

  Ret := true;
  while ( (hFind <> INVALID_HANDLE_VALUE) and Ret ) do begin
    if (fd.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
      SL.Add(string(fd.cFileName));
    Ret := FindNextFile(hFind,fd);
  end;
  Windows.FindClose(hFind);
end;

FindFirstFile( ) にファイル名を渡して検索ハンドルを取得し、while ループの中で FindNextFile( ) により列挙している。 このコードで分かるように、検索ハンドルが INVALID_HANDLE_VALUE でなく、かつ直前の FindNextFile( ) の戻り値が true のときに検索を続ける。見つかったファイルの属性がディレクトリではないときだけ、リストに追加する。そして最後に FindClose( ) を実行する。つぎに、エクスプローラが「詳細」表示のときに表示される「更新日時」に相当する情報を取得する、GetFileLastWriteTime( ) 関数を見てみよう。

function GetFileLastAccessTime(FullPath: string): TSystemTime;
var
  fd: TWin32FindData;
  ft: TFileTime;
begin
  FindClose(FindFirstFile(PChar(FullPath),fd));
  FileTimeToLocalFileTime(fd.ftLastAccessTime,ft);
  FileTimeToSystemTime(ft,result);
end;

ここでは、ファイルのフルパス名をつかって FindFirstFile( ) を一回だけ実行して TWin32FindData 型のレコードを取得している。 このレコードの ftLastAccessTime フィールドをつかって、FileTimeToLocalFileTime( ) APIにより、UTCという時間系からローカル時間に変更している。 エクスプローラが表示する日時に一致させるために、さらに FileTimeToSystemTime( ) APIにより、システムタイムに変換する。システム時刻は TSystemTime レコードとして返される。

  TSystemTime = record
    wYear: Word;        // 年
    wMonth: Word;       // 月
    wDayOfWeek: Word;   // 曜日(日曜=1、月曜=2など)
    wDay: Word;         // 日にち
    wHour: Word;        // 時
    wMinute: Word;      // 分
    wSecond: Word;      // 秒
    wMilliseconds: Word;// ミリ秒
  end;

リストボックスの応用として、これらの手続きをテストするプログラムを FileList.dpr に作ろう。 以下に全リストを示す。

program FileList;

uses
  windows,
  messages,
  UtilFunc in 'UtilFunc.pas',
  UtilClass in 'UtilClass.pas',
  GDIObject in 'GDIObject.pas',
  APIControl in 'APIControl.pas';

//------------------------------------------------------------
//             Global Types, Constants and Variables
//------------------------------------------------------------
var
  Clr: COLORREF;

  LB1, LB2: TAPIListBox;
  Label1: TAPILabel;
  DD: TAPIDragDrop;
  Dir: string;

//------------------------------------------------------------
//             Helper Handlers
//------------------------------------------------------------
procedure FileInfo(filename:string;LB:TAPIListBox);
var
  st: TSystemTime;
  FileAttributes: DWORD;
  c:array[0..30] of Char;
  s: string;
begin
  LB.Clear;
  LB.AddString('サイズ : '+#9+AIntToStr(GetFileSize(Filename))+
                ' bytes');

  st := GetFileCreationTime(filename);
  wsprintf3(c,'%d/%.2d/%.2d',st.wYear,st.wMonth,st.wDay);
  s := string(c);
  wsprintf3(c,'%.2d:%.2d:%.2d',st.wHour,st.wMinute,st.wSecond);
  LB.AddString('作成日時 : '+#9+ s + '  '+string(c));

  st := GetFileLastAccessTime(filename);
  wsprintf3(c,'%d/%.2d/%.2d',st.wYear,st.wMonth,st.wDay);
  LB.AddString('アクセス日 : '+#9+string(c));

  st := GetFileLastWriteTime(filename);
  wsprintf3(c,'%d/%.2d/%.2d',st.wYear,st.wMonth,st.wDay);
  s := string(c);
  wsprintf3(c,'%.2d:%.2d:%.2d',st.wHour,st.wMinute,st.wSecond);
  LB.AddString('更新日時 : '+#9+ s + '  '+string(c));

  LB.AddString(' ');
  FileAttributes := GetFileAttributes(filename);
  if (FileAttributes and FILE_ATTRIBUTE_READONLY)<> 0 then
    LB.AddString('READONLY : '+#9+'Yes')
  else
    LB.AddString('READONLY : '+#9+'No');
  if (FileAttributes and FILE_ATTRIBUTE_HIDDEN)<> 0 then
    LB.AddString('HIDDEN : '+#9+'Yes')
  else
    LB.AddString('HIDDEN : '+#9+'No');
  if (FileAttributes and FILE_ATTRIBUTE_SYSTEM)<> 0 then
    LB.AddString('SYSTEM : '+#9+'Yes')
  else
    LB.AddString('SYSTEM : '+#9+'No');
  if (FileAttributes and FILE_ATTRIBUTE_ARCHIVE)<> 0 then
    LB.AddString('ARCHIVE : '+#9+'Yes')
  else
    LB.AddString('ARCHIVE : '+#9+'No');
  if (FileAttributes and FILE_ATTRIBUTE_NORMAL)<> 0 then
    LB.AddString('NORMAL : '+#9+'Yes')
  else
    LB.AddString('NORMAL : '+#9+'No');
  if (FileAttributes and FILE_ATTRIBUTE_TEMPORARY)<> 0 then
    LB.AddString('TEMPORARY : '+#9+'Yes')
  else
    LB.AddString('TEMPORARY : '+#9+'No');
end;

//------------------------------------------------------------
//             Object Event Handlers
//------------------------------------------------------------
procedure DDDrop(var Msg:TMessage);
var
  ST: TStringArray;
  i: integer;
begin
  ST := TStringArray.Create;
  Dir := AExtractDir(DD.Files[0]);
  FindAllFiles(Dir,ST);
  LB1.Clear;
  LB2.Clear;
  LB1.Redraw := false;
  for i := 0 to ST.Count-1 do LB1.AddString(ST[i]);
  LB1.Redraw := true;
  Label1.Text := 'Directory : '+Dir+'    '
                  +AIntToStr(ST.Count)+' Files.';
  ST.Free;
  DD.SetFocus(LB1.Handle);
end;

procedure LB1SelChange(var Msg:TMessage);
begin
  FileInfo(Dir+'\'+LB1.SelText,LB2);
end;

//------------------------------------------------------------
//             Event Handlers
//------------------------------------------------------------
procedure OnCreate(var Msg: TMessage);
var
  hWindow: HWND;
begin
  hWindow := Msg.Msg;
  Clr := clrBtnFace;
  MakeInitMenu(hWindow);
  ChangeWindowSize(hWindow,575,350);
  CenterWindow(hWindow);

  Label1 := CreateLabel(hWindow,10,10,540,30,'');
  with Label1 do begin
    DrawFrame := true;
    Margin := 5;
  end;

  LB1 := CreateListBox(hWindow,10,50,200,240,LB1SelChange);

  LB2 := CreateListBox(hWindow,220,50,330,240,nil);

  DD := TAPIDragDrop.Create(hWindow);
  DD.OnDrop := DDDrop;
end;

procedure OnTest(hWindow: HWND);
begin
  LB1.DoSort;
end;

procedure OnCommand(var Msg: TMessage);
begin
  InitMenuCommand(Msg,OnTest);
end;

procedure OnEraseBkgnd(var Msg: TMessage);
begin
  Msg.Result := EraseBkGnd(Msg.Msg,Clr,Msg.WParam);
end;

procedure OnDestroy(var Msg: TMessage);
begin
  PostQuitMessage(0);
end;

//------------------------------------------------------------
//             Main Window Procedure
//------------------------------------------------------------
function MainWndProc(hWindow: HWND; Msg: UINT; WParam: WPARAM;
                      LParam: LPARAM): LRESULT; stdcall;
var
  AMsg: TMessage;
begin
  Result := 0;

  AMsg.Msg := hWindow;
  AMsg.WParam := WParam;
  AMsg.LParam := LParam;
  AMsg.Result := 0;

  case Msg of

//-------------  WM_CREATE  -----------------------------------

     WM_CREATE: OnCreate(AMsg);

//-------------  WM_COMMAND  ----------------------------------

     WM_COMMAND: OnCommand(AMsg);

//-------------  WM_ERASEBKGND  -------------------------------

     WM_ERASEBKGND: begin
       OnEraseBkgnd(AMsg);
       result := AMsg.Result;
     end;

//-------------  WM_DESTROY  ----------------------------------

     WM_DESTROY: OnDestroy(AMsg);

//-------- そのほかのメッセージの処理 ------
  else begin
    result := DefWindowProc( hWindow, Msg, wParam, lParam );
    end;

  end; // case
end;

//------------------------------------------------------------
//             Main Procedure
//------------------------------------------------------------
begin

  MakeMainWindow(@MainWndProc);

  Halt(MessageLoopNormal);

end.

DownloadExe.gifFileList.zip(18.698kB)
FileList.gif

実行して、エクスプローラからファイルをドロップすると、そのファイルがあるディレクトリの中にある全てのファイル名が LB1 に表示される。Test! メニューを選ぶと LB1 の内容がソートされる。LB1 をクリックして一つのファイルを選ぶと、その情報が LB2 に表示される。 実行時の様子を上図に示す。 ヘルパ関数 FileInfo( ) では、日時や時刻の表示形式を2−6節で説明した wsprintf3( ) APIを使って設定している。

greenbal.gifこの章の目次へ戻る

9−4 壁紙チェンジャ

リストボックスの応用として、壁紙チェンジャを作ろう。中心となるのは SystemParametersInfo( ) APIである。

  function SystemParametersInfo(uiAction, 
                                 uiParam: UINT;
                                 pvParam: Pointer; 
                                 fWinIni: UINT): BOOL; stdcall;

このAPIはシステム全体に関連するパラメータを取得したり設定したりするのに使う。 uiAction には取得・設定したいパラメータを指定する定義済み定数を指定する。 Windows.pas にはここに指定できる定数が100あまり定義されている。 最初と最後の部分だけ以下に示す。

const
  { Parameter for SystemParametersInfo() }
  SPI_GETBEEP = 1;
  SPI_SETBEEP = 2;
  SPI_GETMOUSE = 3;
  SPI_SETMOUSE = 4;
  .................
  SPI_GETMOUSEHOVERTIME = 102; 
  SPI_SETMOUSEHOVERTIME = 103; 
  SPI_GETWHEELSCROLLLINES = 104; 
  SPI_SETWHEELSCROLLLINES = 105;

このように、SPI_GETXXX で取得、SPI_SETXXX で設定する。 壁紙を設定するには SPI_SETDESKWALLPAPER を指定する。uiParam パラメータは、uiAction で指定したパラメータにより、意味が異なる。 SPI_SETDESKWALLPAPER のときはゼロに指定する。 pvParam パラメータも uiAction で指定したパラメータにより、意味が異なる。 SPI_SETDESKWALLPAPER のときは、壁紙のファイル名を PChar 型で指定する。最後の fWinIni パラメータはレジストリの内容を更新するかどうかを指定する。

  SPIF_UPDATEINIFILE = 1;
  SPIF_SENDWININICHANGE = 2;

レジストリを更新しないならゼロを、更新するなら SPIF_UPDATEINIFILE を、更新後に WM_WININICHANGE メッセージを実行中の全てのプロセスに送る場合は SPIF_SENDWININICHANGE を指定する。

それでは壁紙を変更するアプリケーションを作ろう。

  1. エクスプローラを使って、新しいフォルダを 0904ChangeWallPaper という名前でつくる。
  2. 作ったフォルダに、エクスプローラを使って 0903FileList フォルダの Skeleton02.dprUtilFunc.pasUtilClass.pasGDIObject.pasAPIControl.pas をコピーする。
  3. コピーした Skeleton02.dpr をダブルクリックして Delphi を立ち上げる。
  4. コードエディタに Skeleton02.dpr のコードがあることを確認してから、[ファイル]メニューから[プロジェクトに名前を付けて保存]を選んで ChangeWallPaper.dpr という名前で保存する。
  5. スピードメニューから[プロジェクトに追加]を選んで APIControl.pas を追加する。
  6. コードエディタで以下のように編集する。
  7. コンパイルして実行する。

以下に ChangeWallPaper.dpr の全リストを示す。

program ChangeWallPaper;

uses
  windows,
  messages,
  UtilFunc in 'UtilFunc.pas',
  UtilClass in 'UtilClass.pas',
  GDIObject in 'GDIObject.pas',
  APIControl in 'APIControl.pas';

//------------------------------------------------------------
//             Global Types, Constants and Variables
//------------------------------------------------------------
var
  Clr: COLORREF;

  LB1: TAPIListBox;
  Label1: TAPILabel;
  GB: TAPIGroupBox;
  RB1, RB2: TAPIRadioButton;
  DD: TAPIDragDrop;

//------------------------------------------------------------
//             Helper Handlers
//------------------------------------------------------------
procedure WallPaperChange;
var
   fWinIni: UINT;
begin
  if RB2.Checked then
     fWinIni := SPIF_UPDATEINIFILE
  else
     fWinIni := 0;
  SystemParametersInfo(SPI_SETDESKWALLPAPER,0,
  PChar(Label1.Text),fWinIni);
end;

//------------------------------------------------------------
//             Object Event Handlers
//------------------------------------------------------------
procedure  LB1SelChange(var Msg:TMessage);
begin
  Label1.Text := LB1.SelText;
  WallPaperChange;
end;

procedure DDDrop(var Msg:TMessage);
begin
  Label1.Text := DD.Files[0];
  WallPaperChange;
  DD.SetFocus(LB1.Handle);
end;

//------------------------------------------------------------
//             Event Handlers
//------------------------------------------------------------
procedure OnCreate(var Msg: TMessage);
var
  hWindow: HWND;
  WinDir: array[0..MAX_PATH] of Char;
  ST: TStringArray;
  i: integer;
begin
  hWindow := Msg.Msg;
  Clr := clrBtnFace;
  ChangeWindowSize(hWindow,400,300);
  CenterWindow(hWindow);

  Label1 := CreateLabel(hWindow,10,10,360,30,'');
  with Label1 do begin
    DrawFrame := true;
    Margin := 5;
  end;

  LB1 := CreateListBox(hWindow,10,50,200,200,LB1SelChange);

  GB := CreateGroupBox(hWindow,220,50,150,100,'Change Registry');

  RB1 := CreateRadioButton(GB.Handle,30,30,100,30,' No',1,nil);
  RB1.Checked := True;

  RB2 := CreateRadioButton(GB.Handle,30,60,100,30,' Yes',1,nil);

  DD := TAPIDragDrop.Create(hWindow);
  DD.OnDrop := DDDrop;

  GetWindowsDirectory(PChar(@WinDir),SizeOf(Windir));
  ST := TStringArray.Create;
  FindAllFilesWithExt(string(WinDir),'bmp',ST);
  for i := 0 to ST.Count-1 do LB1.AddString(ST[i]);
  ST.Free;

end;

procedure OnEraseBkgnd(var Msg: TMessage);
begin
  Msg.Result := EraseBkGnd(Msg.Msg,Clr,Msg.WParam);
end;

procedure OnDestroy(var Msg: TMessage);
begin
  PostQuitMessage(0);
end;

//------------------------------------------------------------
//             Main Window Procedure
//------------------------------------------------------------
function MainWndProc(hWindow: HWND; Msg: UINT; WParam: WPARAM;
                      LParam: LPARAM): LRESULT; stdcall;
var
  AMsg: TMessage;
begin
  Result := 0;

  AMsg.Msg := hWindow;
  AMsg.WParam := WParam;
  AMsg.LParam := LParam;
  AMsg.Result := 0;

  case Msg of

//-------------  WM_CREATE  -----------------------------------

     WM_CREATE: OnCreate(AMsg);

//-------------  WM_ERASEBKGND  -------------------------------

     WM_ERASEBKGND: begin
       OnEraseBkgnd(AMsg);
       result := AMsg.Result;
     end;

//-------------  WM_DESTROY  ----------------------------------

     WM_DESTROY: OnDestroy(AMsg);

//-------- そのほかのメッセージの処理 ------
  else begin
    result := DefWindowProc( hWindow, Msg, wParam, lParam );
    end;

  end; // case
end;

//------------------------------------------------------------
//             Main Procedure
//------------------------------------------------------------
begin

  MakeMainWindow(@MainWndProc);

  Halt(MessageLoopNormal);

end.

DownloadExe.gifChangeWallPaper.zip(17.156kB)
ChangeWallPaper.gif

以上で完成だ。実行すると右図のように、リストボックスには Windows ディレクトリにあるビットマップファイルが表示される。 クリックしてファイルを選ぶとそれが壁紙に設定される。エクスプローラから任意のディレクトリにあるビットマップファイルをドロップするとそれを壁紙に設定することもできる。 ここでは、メニューを装備していないので WM_COMMAND に関連するコードは除いている。[ アクティブデスクトップが有効なときはこの方法はつかえないようである 2003/04/08 ]
Windows ディレクトリは、GetWindowsDirectory( ) APIで取得している。

  function GetWindowsDirectory(lpBuffer: PChar; 
                                  uSize: UINT): UINT; stdcall;

lpBuffer には、Windows ディレクトリのパスを受け取る文字列バッファを、uSize にはそのバッファのサイズを設定する。

greenbal.gifこの章の目次へ戻る

9−5 ウィンドウの列挙

実行中のアプリケーションのすべてのトップレベルウィンドウを列挙するには EnumWindows( ) API を使う。

  function EnumWindows(lpEnumFunc: TFNWndEnumProc; 
                           lParam: LPARAM): BOOL; stdcall;

第一パラメータ lpEnumFunc にはウィンドウが見つかるたびにOSから呼び出されるコールバック関数のアドレスを指定する。 lParam パラメータはコールバック関数に渡される任意の32ビットのデータを指定する。 全てのウィンドウを列挙するか、コールバック関数が false を返すまで実行がつづく。コールバック関数の形は Win32.hlp で知ることが出来る。

  function EnumWinProc(hWindow: HWND; 
                         lData: LPARAM):BOOL; stdcall;

hWindow には見つかったウィンドウのハンドルが、 lData には EnumWindows( ) を呼び出したときに指定した lParam の値が渡される。

EnumWindows( ) APIをテストするプログラムをつくる。

  1. エクスプローラを使って、新しいフォルダを 0905TestEnumWindows という名前でつくる。
  2. 作ったフォルダに、エクスプローラを使って 0904ChangeWallPaper フォルダの Skeleton02.dprUtilFunc.pasUtilClass.pasGDIObject.pasAPIControl.pas をコピーする。
  3. コピーした Skeleton02.dpr をダブルクリックして Delphi を立ち上げる。
  4. コードエディタに Skeleton02.dpr のコードがあることを確認してから、[ファイル]メニューから[プロジェクトに名前を付けて保存]を選んで TestEnumWindows.dpr という名前で保存する。
  5. スピードメニューから[プロジェクトに追加]を選んで APIControl.pas を追加する。
  6. コードエディタで以下のように編集する。
  7. コンパイルして実行する。

以下に TestEnumWindows.dpr の全リストを示す。

program TestEnumWindows;

uses
  windows,
  messages,
  UtilFunc in 'UtilFunc.pas',
  UtilClass in 'UtilClass.pas',
  GDIObject in 'GDIObject.pas',
  APIControl in 'APIControl.pas';

//------------------------------------------------------------
//             Global Types, Constants and Variables
//------------------------------------------------------------
var
  Clr: COLORREF;

  LB1: TAPIListBox;

//------------------------------------------------------------
//             Helper Handlers
//------------------------------------------------------------
function EnumWndProc(hWindow: HWND; lData: LPARAM):BOOL; stdcall;
var
  c1,c2: array[0..100] of Char;
begin
  GetWindowText(hWindow,c1,100);
  GetClassName(hWindow,c2,100);
  TAPIListBox(lData).AddString(string(c1)+' - '+string(c2));
  result := true;
end;

//------------------------------------------------------------
//             Object Event Handlers
//------------------------------------------------------------

//------------------------------------------------------------
//             Event Handlers
//------------------------------------------------------------
procedure OnCreate(var Msg: TMessage);
var
  hWindow: HWND;
begin
  hWindow := Msg.Msg;
  Clr := clrBtnFace;
  MakeInitMenu(hWindow);
  ChangeWindowSize(hWindow,580,320);
  CenterWindow(hWindow);

  LB1 := CreateListBox(hWindow,10,10,550,250,nil);
end;

procedure OnTest(hWindow: HWND);
begin
  LB1.Clear;
  LB1.Redraw := false;
  EnumWindows(@EnumWndProc,LPARAM(LB1));
  LB1.Redraw := true;
end;

procedure OnCommand(var Msg: TMessage);
begin
  InitMenuCommand(Msg,OnTest);
end;

procedure OnEraseBkgnd(var Msg: TMessage);
begin
  Msg.Result := EraseBkGnd(Msg.Msg,Clr,Msg.WParam);
end;

procedure OnDestroy(var Msg: TMessage);
begin
  PostQuitMessage(0);
end;

//------------------------------------------------------------
//             Main Window Procedure
//------------------------------------------------------------
function MainWndProc(hWindow: HWND; Msg: UINT; WParam: WPARAM;
                      LParam: LPARAM): LRESULT; stdcall;
var
  AMsg: TMessage;
begin
  Result := 0;

  AMsg.Msg := hWindow;
  AMsg.WParam := WParam;
  AMsg.LParam := LParam;
  AMsg.Result := 0;

  case Msg of

//-------------  WM_CREATE  -----------------------------------

     WM_CREATE: OnCreate(AMsg);

//-------------  WM_COMMAND  ----------------------------------

     WM_COMMAND: OnCommand(AMsg);

//-------------  WM_ERASEBKGND  -------------------------------

     WM_ERASEBKGND: begin
       OnEraseBkgnd(AMsg);
       result := AMsg.Result;
     end;

//-------------  WM_DESTROY  ----------------------------------

     WM_DESTROY: OnDestroy(AMsg);

//-------- そのほかのメッセージの処理 ------
  else begin
    result := DefWindowProc( hWindow, Msg, wParam, lParam );
    end;

  end; // case
end;

//------------------------------------------------------------
//             Main Procedure
//------------------------------------------------------------
begin

  MakeMainWindow(@MainWndProc);

  Halt(MessageLoopNormal);

end.

DownloadExe.gifTestEnumWindows.zip(14.845kB)
TestEnumWindows.gif

実行して Test! メニューをクリックすると上図のようになる。コールバック関数では、ウィンドウハンドルからウィンドウのタイトルとクラス名を取得している。ここでは、コールバック関数に渡すデータとしてリストボックスのパスカルオブジェクトの参照を指定し、 コールバック関数では TAPIListBox 型にキャストしてから、そのメソッドを使って文字列を追加していることに注目してほしい。

greenbal.gifこの章の目次へ戻る

9−6 まとめ

リストボックスコントロールをつかった小さなアプリケーションをつくった。まず、エクスプローラからのドラグドロップでファイル名を取得する機構を調べこの機能をカプセル化した TAPIDragDrop クラスを TSubClass から派生して作成した。 ディレクトリの全ファイルを列挙したり、フルパス名からファイル名やパス、拡張子を抽出する関数を作成した。 また、ディスクファイルの属性を取得する方法を示した。 さらに、壁紙チェンジャや実行中のトップウィンドウを列挙する方法を示した。


ここまでのプログラムファイル

  1. Skeleton02.dpr (3 kB)
  2. UtilFunc.pas (8 kB)
  3. UtilClass.pas (21 kB)
  4. GDIObject.pas (16 kB)
  5. APIControl.pas (18 kB)



greenbal.gifこの章の目次へ戻る