第19章 アイコン、ビットマップ、リソース

これまでに本稿で作ってきたすべてのアプリケーションでは、タイトルバーの左端やタスクバーに表示されるアイコンは、LoadIcon( ) APIの第二パラメータに IDI_APPLICATION を指定してシステムアイコンのハンドルを取得し、これをメインウィンドウのクラスアイコンに指定していた。 多くのアプリケーションでは、アプリケーションアイコンのほかにスプラッシュウィンドウやダイアログにアイコンやビットマップを表示したい場合があるだろう。 この章では、 まず最初に、外部ファイルから任意のアイコンやビットマップをロードして表示する方法を学ぶ。 それから、リソースファイルにアイコンやビットマップを取り込み、実行可能ファイルにリンクする方法を示す。 これはリソースファイルのための入門編の役割をする。 実行可能ファイルにリンクしてコンパイルすると、アイコンやビットマップが *.exe ファイルに取り込まれるため、アプリケーションとしての独立性が高まる。

19−0 この章の内容

第19章 アイコン、ビットマップ、リソース
19−1 アイコンの使用法
19−2 最初のリソースファイル
19−3 リソースコンパイラ
19−4 ビットマップの表示
19−5 ビットマップリソース
19−6 まとめ


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

19−1 アイコンの使用法

本稿のようにVCLを使わないコードベースのプログラムでも、IDE環境からコンパイルするとプロジェクトファイルと同じ名前で拡張子が res であるファイルが自動的に作られることに気づいた方も多いだろう。 これがデフォルトのリソースファイルである。(D5ではこのデフォルトアイコンのリソースファイルはVCLアプリでない場合は、自動的には作られないようである。)ファイルの形式はバイナリになっているのでテキストエディタでは開けないが、 Delphiに付属する ImageEdit で開くことができる。 デフォルトでは、 このリソースファイルには MAINICON という名前のアイコンが入っているだけである。 例の「松明」のアイコンである。 ImageEdit を使えば、アイコンやカーソルやビットマップを含むリソースファイルを作ることができる。 本稿では、これまでこのリソースを無視してきたが、この節ではIDEからコンパイルして自動的に生成されるデフォルトリソースの中のアイコンの使い方を調べよう。 このアイコンを ImageEdit で編集すると、自分の好きなアイコンを使用できる。 ImageEdit では単独のアイコンファイルを作ることもできる。 また、Delphi がインストールされている \Images\Icons フォルダには幾つかのアイコンファイルがある。 これを使って、クラスアイコンを設定し、クライアント領域にも表示することができる。

デフォルトアイコンと任意のアイコンファイルを使うプログラムをつくる。

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

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

program DefaultIcon;

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

  {$R *.RES}
  
//------------------------------------------------------------
//             Global Types, Constants and Variables
//------------------------------------------------------------
var
  MainWnd: TSDIMainWindow;

  DD1: TAPIDragDrop;
  Label1: TAPILabel;

//------------------------------------------------------------
//             Helper Handlers
//------------------------------------------------------------

//------------------------------------------------------------
//             Object Event Handlers
//------------------------------------------------------------
procedure DD1Drop(var Msg:TMessage);
var
  hIc,hIcprev: HICON;
begin
  hIc := LoadImage(0,PChar(DD1.Files[0]),
                   IMAGE_ICON,0,0,LR_LOADFROMFILE);
  if hIc <> 0 then begin
    hIcprev := MainWnd.Icon;
    MainWnd.Icon := hIc;
    Label1.Text := DD1.Files[0];
    InvalidateRect(MainWnd.Handle,nil,true);
    DestroyIcon(hIcprev);
  end else
    Label1.Text := 'アイコンファイルではありません';
end;

procedure MainPaint(var Msg: TMessage);
var
  ps:TPaintStruct;
begin
  BeginPaint(MainWnd.Handle,ps);
    DrawIcon(ps.hdc,20,50,MainWnd.Icon);
    DrawIconEx(ps.hdc, 90, 50, MainWnd.Icon, 128, 128, 0, 0, DI_NORMAL);
  EndPaint(MainWnd.Handle,ps);
  Msg.Result := 1;
end;

//------------------------------------------------------------
//             Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
  hWindow: HWND;
begin
  hWindow := MainWnd.Handle;
  MakeInitMenu(hWindow);

  Label1 := CreateLabel(hWindow,10,10,400,30,'');
  Label1.DrawFrame := true;

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

  with MainWnd do begin
    ClientWidth := 420;
    ClientHeight := 200;
    Center;
    Trap(WM_PAINT,MainPaint);
  end;
end;

procedure OnTest(hWindow: HWND);
var
  hIc: HICON;
begin
  hIc := MainWnd.Icon;
  Label1.Text := 'デフォルトアイコン';
  MainWnd.Icon := LoadIcon(hInstance,'MAINICON');
  InvalidateRect(MainWnd.Handle,nil,true);
  DestroyIcon(hIc);
end;

procedure MainCommand(var Msg: TMessage);
begin
  Msg.Msg := MainWnd.Handle;
  InitMenuCommand(Msg,OnTest);
end;

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

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

  MainWnd := TSDIMainWindow.Create(0,GetExeName);
  with MainWnd do begin
    OnCreate := MainCreate;
    OnDestroy := MainDestroy;
    OnCommand := MainCommand;
  end;
  MainWnd.DoCreate;

  Halt(MessageLoopNormal);

end.

DownloadExe.gifDefaultIcon.zip(17.725kB)
DefaultIcon.gif

これで完成である。 実行して、Test! メニューを選択するとデフォルトアイコンがクラスアイコンに指定され、クライアント領域にも実寸(32x32)と拡大されて表示される。(D5では、デフォルトアイコンが自動的に作られないので MainWindow と同じアイコンが表示される。また、Test! メニューからの実行では LoadIcon() が失敗するため何も表示されない) また、エクスプローラからアイコンファイルをドラグドロップすると同様に表示される。 この様子を右図に示す。

コードを調べよう。デフォルトのリソースファイルはプロジェクトファイルと同じ名前なので、コンパイラにリンクするように命令するには、 {$R *.RES} のコンパイラ指令を使う。RAD環境では、プロジェクトファイルに自動的にこのコンパイラ指令が挿入されている。 この指令により、コンパイルされて出来た実行可能ファイル( *.exe )にリソースが取り込まれる。 OnTest( )ハンドラでは、LoadIncon( ) APIでリソースからアイコンを取り出して、そのハンドルを取得している。

  
procedure OnTest(hWindow: HWND);
var
  hIc: HICON;
begin
  hIc := MainWnd.Icon;
  Label1.Text := 'デフォルトアイコン';
  MainWnd.Icon := LoadIcon(hInstance,'MAINICON');
  InvalidateRect(MainWnd.Handle,nil,true);
  DestroyIcon(hIc);
end;

このAPIは、

  function LoadIcon(hInstance: HINST; 
                   lpIconName: PChar): HICON; stdcall;

と宣言されている。 第一パラメータには、インスタンスハンドルを指定する。 ここでは、暗黙のグローバル変数 hInstance を指定する。 システムアイコンを取得するときにはゼロを指定する。 第二パラメータは、アイコンリソースの名前を示す文字列を指定する。 デフォルトアイコンの名前は、ImageEdit で見たとおり 'MAINICON' をである。 取り出したアイコンハンドルをメインウィンドウの Icon プロパティーに設定している。 このプロパティーの設定メソッドを以下に示す。 ( TSDIMainWindow クラスは、APIWindow.pas に実装されている。)

procedure TSDIMainWindow.SetIcon(value: HICON);
begin
  SetClassLong(FHandle,GCL_HICON,value);
end;

ここでは、SetClassLong( ) を使って、メインウィンドウクラスのアイコンを設定している。その前に、以前に設定されていたアイコンのハンドルをローカル変数に記憶させ、アイコン更新後に DestroyIcon( ) APIで削除する。 このAPIで削除する必要があるのは、CreateIcon( )CreateIconIndirect ( ) APIで作られたアイコンだけである。そのほかのアイコンは削除する必要はないが、以前設定されていたアイコンの由来を確かめることが出来ないときは、この例のように一応削除しておくと良い。 MainWndIcon プロパティーの取得メソッドを以下に示す。

function TSDIMainWindow.GetIcon: HICON;
begin
  result := GetClassLong(FHandle,GCL_HICON);
end;

ここでは GetClassLong( ) APIでクラスアイコンのハンドルを取得しているだけだ。OnTest( ) ハンドラではメインウィンドウのクラスアイコンを変更してから、InvalidateRect( ) を実行して、MainPaint( ) ハンドラを実行させる。

procedure MainPaint(var Msg: TMessage);
var
  ps:TPaintStruct;
begin
  BeginPaint(MainWnd.Handle,ps);
    DrawIcon(ps.hdc,20,50,MainWnd.Icon);
    DrawIconEx(ps.hdc, 90, 50, MainWnd.Icon, 128, 128, 0, 0, DI_NORMAL);
  EndPaint(MainWnd.Handle,ps);
  Msg.Result := 1;
end;

ここでクライアント領域にアイコンを描画している。 実寸で描画するには DrawIcon( ) を使う。

  function DrawIcon(hDC: HDC; 
                   X, Y: Integer; 
                  hIcon: HICON): BOOL; stdcall;

第一パラメータにはデバイスコンテキストハンドルを、 には描画するアイコンの左上の論理座標を指定する。 最後のパラメータに描画するアイコンハンドルを指定する。 縮小・拡大して描画するには DrawIconEx( ) を使う。

  function DrawIconEx(hdc: HDC; 
              xLeft, yTop: Integer; 
                    hIcon: HICON;
         cxWidth, cyWidth: Integer; 
            istepIfAniCur: UINT;  
       hbrFlickerFreeDraw: HBRUSH; 
                  diFlags: UINT): BOOL; stdcall;

cxWidth, cyWidth パラメータで幅と高さを指定する。 istepIfAniCur にはアイコンの描画の場合はゼロを指定する。 hbrFlickerFreeDraw には背景ブラシのハンドルを指定できる。 その場合は、 システムは小さなビットマップにアイコン(またはカーソル)を描いてから、クライアント領域にコピーする。 通常はゼロを指定して、直接描画するようにする。 diFlags に指定できる定数を以下に示す。

const
  DI_MASK = 1;       // ropMask ラスタ演算を行う
  DI_IMAGE = 2;      // ropImage ラスタ演算を行う
  DI_NORMAL = 3;     // DI_MASK or DI_IMAGE
  DI_COMPAT = 4;     // システムイメージで描画する
  DI_DEFAULTSIZE = 8;// 幅・高さ指定を無視して、オリジナルのサイズで描画する

通常は DI_NORMAL を指定すればよい。 ここでは、幅・高さを 128 に指定して四倍角のアイコンを描画している。 MainPaint( ) ハンドラの最後の実行行では、 Result フィールドにゼロ以外を設定して、DefWindowProc( ) の実行を迂回している。

エクスプローラからアイコンファイルがドロップされると DD1Drop( ) ハンドラが呼び出される。

procedure DD1Drop(var Msg:TMessage);
var
  hIc,hIcprev: HICON;
begin
  hIc := LoadImage(0,PChar(DD1.Files[0]),
                   IMAGE_ICON,0,0,LR_LOADFROMFILE);
  if hIc <> 0 then begin
    hIcprev := MainWnd.Icon;
    MainWnd.Icon := hIc;
    Label1.Text := DD1.Files[0];
    InvalidateRect(MainWnd.Handle,nil,true);
    DestroyIcon(hIcprev);
  end else
    Label1.Text := 'アイコンファイルではありません';
end;

外部ファイルからアイコン・ビットマップ・カーソルをロードするには、LoadImage( ) APIを使う。

  function LoadImage(hInst: HINST; 
                 ImageName: PChar; 
                 ImageType: UINT; 
                      X, Y: Integer; 
                     Flags: UINT): THandle; stdcall;

最初のパラメータ hInst には、リソースからロードするときはインスタンスハンドルを指定する。 外部ファイルからのときはゼロを指定する。 第二パラメータ ImageName には、リソース名、または外部ファイル名を指定する。 ImageType パラメータには、 以下の定数のうちの一つを指定する。

const
  IMAGE_BITMAP = 0;
  IMAGE_ICON = 1;
  IMAGE_CURSOR = 2;

パラメータには幅と高さを指定する。 ともにゼロを指定するとオリジナルサイズでロードされると期待できる。 最後のパラメータ Flags に指定できる定数を以下に示す。

const 
  LR_DEFAULTCOLOR = $0000;    // デフォルト。LR_MONOCHROME ではないという意味
  LR_MONOCHROME = $0001;      // 白黒イメージとしてロード
  LR_LOADFROMFILE = $0010;    // 外部ファイルからロードする。これがないときはリソースから
  LR_LOADTRANSPARENT = $0020; // 最初のピクセルデータを背景色と見なし、システム値に置き換える 
  LR_DEFAULTSIZE = $0040;     // 幅・高さの指定がゼロであれば、システムメトリック値のサイズを採用する
  LR_LOADMAP3DCOLORS = $1000; // カラーテーブルを走査して、3Dカラーをシステム値に置き換える 
  LR_CREATEDIBSECTION = $2000;// デバイス独立ビットマップとしてロードする 
  LR_SHARED = $8000;          // イメージハンドルを固定する(同一イメージには一つのハンドルだけ)

DD1Drop( ) では LR_LOADFROMFILE or LR_DEFAULTSIZE を指定している。 LoadImage( ) は成功すると対応するイメージのハンドルを返す。 失敗するとゼロを返す。 DD1Drop( ) ハンドラでは、ハンドルがゼロでないことを確認してから OnTest( ) と同様の設定を行っている。

この節では、デフォルトリソースや外部アイコンファイルからアイコンを取り出して利用する方法を調べた。

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

19−2 最初のリソースファイル

この章の最初で見てきたように、DelphiIDEからプログラムをコンパイルすると、自動的にプロジェクトファイルと同じファイル名で拡張子が RES であるリソースファイルが作られる。 ImageEdit でこのファイルを開いて、自分で作ったアイコンやビットマップやカーソルを追加することができるし、それらを編集することもできる。 VCLを使ったRAD環境では、これらのイメージ以外のリソースをあらわに扱うことはないので ImageEdit があれば十分である。 しかし、Win32 システムにおけるリソースファイルの重要性はイメージデータを実行可能ファイルにリンクできることにあるのではなく、むしろ、メニューやダイアログボックスやアクセレレータキーの定義をリソースとして取り込むことができることにある。

筆者が初めてリソースファイルというものを知ったとき、非常に奇妙な感触を持ったことを憶えている。 本稿でこれまで見てきたように、ウィンドウやコントロールは基本的に、CreateWindow( ) APIを使って作られる。 このことはこれらのラッパークラスを作成しても、そのコンストラクタから実行されているので、やはり成り立つ。 メニューやポップアップウィンドウにすぎないダイアログボックスも同じようにしてAPIを使ったコードで作ることができる。 ところが、リソースファイルを使うと、AppendMenu( )CreateWindow( ) APIを一度も使うことなく、メニューやプッシュボタン、リストボックスなどのコントロールをもつダイアログボックスを作ることができるのである。 また、Win32 にはこれらのリソースを扱うために多くのAPIが用意されている。 プログラムコードと切り離して、プログラムの重要な部分をリソースファイルとして記述し、コンパイル時にリンクするという方式には、コードのメンテナンスや他言語への対応を容易にするという、大きなメリットがあるにしても、リソースファイルの存在は少々奇異な感じを受けることは確かである。

DelphiVCLを使ったRAD環境では、 TMenu を使ったメニューエディタでメニュー項目を編集し、別のフォームを設計してダイアログボックスとして使用するという仕様になっているため、リソースファイルを扱う必要がない。別の見方をすると、RAD環境自体が精巧なリソースエディタであると言える。 *.dfm ファイルは、まさしくコンパイル時にリンクされるリソースファイルの役割をしている。 したがって、残念ながら Delphi から、本来のリソースファイルを使用することはできるが、使い勝手は良くない。( Delphi5 では、IDE環境からでもリソースファイルを扱えるようになったらしいが、筆者はまだ体験していない。)この節では、 本稿で作る最初のリソースファイルとして、既存の任意のアイコンファイルをリソースに取り込んで、実行可能ファイルにリンクすることを試してみよう。

リソースファイル( *.res )を作るためには、まずリソース記述ファイル( *.rc )を作成し、それをリソースコンパイラでコンパイルして *.res に変換する。 リソース記述ファイルは、テキストファイルなので、メモ帳などのテキストエディタで作成できる。 Delphi に付属するリソースコンパイラは、BIN ディレクトリにある Brcc32.exe である。 これは、コンソールアプリケーションなので使い方に少々注意が必要だ。 エクスプローラから Delphi がインストールされている BIN ディレクトリの Brcc32.exe をダブルクリックすると、DOS窓が一瞬開いてまた閉じてしまう。 これでは何も分からないので、DOS窓のコマンドラインから実行してみよう。 空白を含んだり長い名前を持つディレクトリをコマンドラインから移動するのは面倒なので、以下のようにする。

  1. \Windows ディレクトリの Command.com をエクスプローラからデスクトップにドロップして、DOS窓のショートカットをデスクトップにつくる。
  2. つくったショートカットアイコンを右クリックして表示されるポップアップメニューから「プロパティー」を選ぶ。
  3. 表示されるダイアログボックスのプログラムページを開いて、作業ディレクトリを"B:\delphi31\Delphi 3\BIN\"のように Delphi がインストールされている BIN ディレクトリに設定する。
  4. 更新ボタンを押してから、OKボタンを押してダイアログを閉じる。
  5. 編集したDOS窓ショートカットをダブルクリックする。
Dosmado.gif

以上で、 Brcc32.exe があるディレクトリをカレントとしてDOS窓が開くはずである。 コマンドラインから「 brcc32 -?」と入力して改行キーを押すと、上図のようになる。DOS窓からコピーして貼り付けると、以下のような文が表示されているはずだ。

B:\delphi31\Delphi 3\BIN>brcc32 -?
Borland Resource Compiler  Version 5.02
Copyright (c) 1990, 1997 Borland International.  All rights reserved.

Syntax: BRCC32 [options ...] filename
  options marked with a '*' are on by default

  @   Take instructions from command file
  -r                    (ignored for compatibility)
  -32                 * Build 32-bit Windows compatible .res file
  -fofilename           Set output filename
  -v                    Verbose
  -ipath                Set include path
  -dname[=string]       Define #define
  -x                    Ignore INCLUDE environment variable

  -m                    Enable multi-byte character support
  -? or -h              Display this message

これから、オプションの詳細は分からないにしても、「 -r 」や「 -32 」などのオプションを指定しなくても、32ビットのアプリケーション用のリソースファイルがコンパイルされて生成する事が分かる。これで、リソースコンパイラの準備ができたので、いよいよリソース記述ファイルを作ることにしよう。

浅いディレクトリを一つ作る。 例えば、ここでは C:\TestResource ということにする。ここにアイコンファイルを一つ作る。 既存のアイコンをコピーして "API.ICO" という名前に変更してもいいし、ImageEdit を使って新しくアイコンを作って、やはり上の名前で保存しても良い。 メモ帳やエディタで以下のような5行のテキストファイルを作って、このディレクトリに 「 MyRes.rc 」という名前をつけて保存する。

//
//  MyRes.rc
//

MyIcon ICON "API.ICO"

DownloadExe.gifFirstResource.zip(17.618kB)

これでリソース記述ファイルができたので、先程つくったDOS窓のアイコンをダブルクリックしてから、コマンドラインに 「 brcc32 c:\TestResource\MyRes.rc 」と入力して改行キーを押す。 うまくいくと C:\TestResource ディレクトリに MyRes.res という名前のリソースファイルが出来ている。 ImageEditでこのファイルを開くと、MYICON という名前のアイコンがあることが分かるだろう。このファイルをプログラムファイルのあるディレクトリにコピーすると、先に示したデフォルトアイコンの使用法と同じように使用することができる。ただし、コンパイラ指令を {$R MyRes.res} にかえ、OnTest( ) ハンドラの LoadIcon(hInstance,'MAINICON')の文を LoadIcon(hInstance,'MYICON') にする必要がある。 FirstResource.gif 実行したときの様子を右図に示す。 このプログラムファイルを 1902FirstResource ディレクトリにつくる。是非試してほしい。

上のアイコン用のリソース記述ファイルの構成は、最初の三行はコメント(必須!!)であり、最後の一行が、外部アイコンファイルとリソースファイルの中のアイコンリソースとを関連付ける文である。 MyIcon はこのリソースの名前であり、空白の次の ICON はリソースの種類がアイコンであることを示している。 最後の "API.ICO" は関連づける外部ファイルの名前である。 コンパイルされてリソースファイル( *.res )ができると、外部アイコンファイル自体は必要なくなる。 バイナリデータとしてリソースファイルに取り込まれているからだ。

この節では、 簡単なリソース記述ファイルをつくってコンパイルし、リソースファイルを作る手順を調べた。ビットマップやメニュー、ダイアログも同様の手順でリソースファイルに取り込まれる。 これらのリソースはアイコンより使用法がやや複雑であるが、リソースファイルの作り方としては基本的な手順は共通している。

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

19−3 リソースコンパイラ

前節では、Delphi に付属するリソースコンパイラ Brcc32.exe をコマンドラインから起動して、リソース記述ファイルをコンパイルした。 コマンドラインからの起動は使い勝手が良くないので、この節では任意のリソース記述ファイルをドロップしてボタンを押すだけで Brcc32.exe を起動してコンパイルできるようにする小さなアプリケーションを作ろう。 アプリケーションから他のアプリケーションを起動するには ShellExecute( )を使用する。 このAPIの宣言は ShellAPI.pas にあるので、ShellAPIuses する必要がある。

  function ShellExecute(hWnd: HWND; 
                   Operation, 
                    FileName, 
                  Parameters,
                   Directory: PChar; 
                     ShowCmd: Integer): HINST; stdcall;

・hWnd        親ウィンドウのハンドルを指定する。

・Operation   'open' または 'print' を指定して、 Filename パラメータで指定されるファイル
               に対する動作を指定する。 nil を指定すると、'open' を指定したのと同じになる。

・FileName    オープンまたは印刷する実行ファイルかドキュメントファイルのパス付きの名前

・Parameters  FileName が実行ファイルなら、このパラメータで実行パラメータ指定する。 ドキュ
              メントファイルなら、 nil を指定する。

・Directory   デフォルトのディレクトリを指定する。 nil ならカレントディレクトリになる。

・ShowCmd     FileName が実行ファイルのときの表示方法を指定する。ShowWindow( ) APIの第二
              パラメータと同じパラメータを指定し、 以下のものの一つを指定する。

const
  SW_HIDE = 0;
  SW_SHOWNORMAL = 1;
  SW_SHOWMINIMIZED = 2;
  SW_SHOWMAXIMIZED = 3;
  SW_MAXIMIZE = 3;
  SW_SHOWNOACTIVATE = 4;
  SW_SHOW = 5;
  SW_MINIMIZE = 6;
  SW_SHOWNA = 8;
  SW_RESTORE = 9;

ShellExecute( )は成功したら、起動されたプロセスのインスタンスハンドル、失敗したら32以下の数値を戻す。

ShellExecute( ) を利用して、Brcc32.exe を起動するプログラムを作る。

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

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

program ResourceCompiler;

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

//------------------------------------------------------------
//             Global Types, Constants and Variables
//------------------------------------------------------------
const
  RC: string = 'b:\delphi31\delphi 3\bin\brcc32.exe';

var
  MainWnd: TSDIMainWindow;

  L1: TAPILabel;
  DD: TAPIDragDrop;
  Btn: TAPIButton;

//------------------------------------------------------------
//             Helper Handlers
//------------------------------------------------------------

//------------------------------------------------------------
//             Object Event Handlers
//------------------------------------------------------------
procedure DDDrop(var AMsg: TMessage);
var
  EXT: string;
begin
  L1.Text := DD.Files[0];
  EXT := AExtractExt(L1.Text);
  if (Ext = 'rc') or (EXT = 'RC') then Btn.Enable := true;
end;

procedure BtnClick(var AMsg: TMessage);
begin
  ShellExecute(MainWnd.Handle,'open',PChar(RC),
          PChar(L1.Text),PChar(AExtractDir(L1.Text)),SW_HIDE);
  Btn.Enable := false;
end;

//------------------------------------------------------------
//             Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
  hWindow: HWND;
begin
  hWindow := MainWnd.Handle;
  MakeInitMenu(hWindow);

  L1 := CreateLabel(hWindow,10,10,530,35,'');

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

  Btn := CreateButton(hWindow,10,55,530,35,'Do Compile!',BtnClick);
  Btn.Enable := false;

  with MainWnd do begin
    ClientWidth := 550;
    ClientHeight := 100;
  end;
end;

procedure OnTest(hWindow: HWND);
begin
  MessageBeep($FFFFFFFF);
end;

procedure MainCommand(var Msg: TMessage);
begin
  Msg.Msg := MainWnd.Handle;
  InitMenuCommand(Msg,OnTest);
end;

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

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

  MainWnd := TSDIMainWindow.Create(0,GetExeName);
  with MainWnd do begin
    OnCreate := MainCreate;
    OnDestroy := MainDestroy;
    OnCommand := MainCommand;
  end;
  MainWnd.DoCreate;

  Halt(MessageLoopNormal);

end.

DownloadExe.gifこのコードはシステム依存コードを含むので添付しない
ResourceCompiler.gif

ResouceCompiler.dpr をコンパイルして実行する前に、グローバル定数の文字列 RC の定義に注意してほしい。この文字列は、Brcc32.exe のフルパス名を保持している。リストには、 筆者の環境でのパスを示しているが、読者の Delphi がインストールされている BIN ディレクトリに書き直す必要がある。 実行する前に、前節で作ったリソース記述ファイルがあるフォルダから .res の拡張子のファイルを削除しておく。ResouceCompiler を実行して、MyRes.rc ファイルをドロップし、ボタンを押して .res ファイルが作成されたら成功である。 コードでは、ShellExecute( ) の最後のパラメータに、SW_HIDE を指定しているので、Brcc32.exe 実行時にもDOS窓が表示されない。 代わりに SW_SHOWNORMAL を指定すると一瞬DOS窓が表示されるのが分かるだろう。

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

19−4 ビットマップの表示

この節では、ビットマップを扱う手始めとして、外部ビットマップファイルを読み込んでウィンドウのクライアント領域に表示する簡単な方法を示す。 ディスプレイ装置が、いわゆる 'TrueColor''Hi-Color' と呼ばれる色解像度を持っているときは、この簡単な方法で十分正視に耐えるクオリティーで表示できる。 256色以下の低解像度の場合は、256色以下の色しか使っていないビットマップでもきれいに表示するにはパレットを明示的に操作しなけばならず、扱いが難しいのでここでは扱わない。 最近のパソコンはインターネットブラウズを前提としているので、ビデオボードの色解像度は通常のXGA(1024x768)程度の大きさでも 'TrueColor' で表示できるのはずなので、大きな問題は生じないだろう。 ビットマップファイルの詳しい解析は、後の章で示す事にする。それでは早速試してみよう。

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

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

program DisplayBitmap;

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

//------------------------------------------------------------
//             Global Types, Constants and Variables
//------------------------------------------------------------
var
  MainWnd: TSDIMainWindow;

  hBitM: HBITMAP;

  DD1: TAPIDragDrop;

//------------------------------------------------------------
//             Helper Handlers
//------------------------------------------------------------
procedure DrawBitmap(DC:HDC;hBit:HBITMAP;x,y:integer);
var
  memDC: HDC;
  bm: TBitmap;
begin
  GetObject(hBit,SizeOf(bm),@bm);
  memDC := CreateCompatibleDC(DC);
  SelectObject(memDC, hBit);
  BitBlt(DC,x,y,bm.bmWidth,bm.bmHeight,memDC,0,0,SRCCOPY);
  DeleteDC(memDC);
end;

//------------------------------------------------------------
//             Object Event Handlers
//------------------------------------------------------------
procedure DD1Drop(var Msg:TMessage);
var
  hBit: HBITMAP;
begin
  hBit := LoadImage(0,PChar(DD1.Files[0]),IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
  if hBit > 0  then begin
    DeleteObject(hBitM);
    hBitM := hBit;
    MainWnd.Title := GetExeName + ' - '+ AExtractFileName(DD1.Files[0]);
    SetForegroundWindow(MainWnd.Handle);
    InvalidateRect(MainWnd.Handle,nil,true);
  end;
end;


procedure MainPaint(var Msg: TMessage);
var
  ps:TPaintStruct;
begin
  if hBitM = 0 then exit;
  BeginPaint(MainWnd.Handle,ps);
    DrawBitmap(ps.hdc,hBitM,10,10);
  EndPaint(MainWnd.Handle,ps);
end;

//------------------------------------------------------------
//             Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
  hWindow: HWND;
begin
  hWindow := MainWnd.Handle;
  MakeInitMenu(hWindow);

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

  with MainWnd do begin
    ClientWidth := 360;
    ClientHeight := 260;
    Center;
    Trap(WM_PAINT,MainPaint);
  end;
end;

procedure OnTest(hWindow: HWND);
begin
  MessageBeep($FFFFFFFF);
end;

procedure MainCommand(var Msg: TMessage);
begin
  Msg.Msg := MainWnd.Handle;
  InitMenuCommand(Msg,OnTest);
end;

procedure MainDestroy(var Msg: TMessage);
begin
  DeleteObject(hBitM);
  PostQuitMessage(0);
end;

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

  MainWnd := TSDIMainWindow.Create(0,GetExeName);
  with MainWnd do begin
    OnCreate := MainCreate;
    OnDestroy := MainDestroy;
    OnCommand := MainCommand;
  end;
  MainWnd.DoCreate;

  Halt(MessageLoopNormal);

end.

DownloadExe.gifDisplayBitmap.zip(16.051kB)
DisplayBitmap.gif

実行して、任意のビットマップファイルをエクスプローラからドロップすると、右図のように表示される。 ファイルの名前は、ウィンドウのタイトルにも表示される。 この表示は、 WM_PAINT メッセージに同期して行われているので、最小化して復帰したり、他のウィンドウの下から出てきたときのように更新されても表示が残る(再表示される)。Delphi がインストールされている '\Images\Splash' のディレクトリには、幾つかのビットマップファイルがあるし、Windows ディレクトリには壁紙のビットマップがある。

コードを見てみよう。ファイルがドロップされると DD1Drop( ) ハンドラが呼び出される。

procedure DD1Drop(var Msg:TMessage);
var
  hBit: HBITMAP;
begin
  hBit := LoadImage(0,PChar(DD1.Files[0]),IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
  if hBit > 0  then begin
    DeleteObject(hBitM);
    hBitM := hBit;
    MainWnd.Title := GetExeName + ' - '+ AExtractFileName(DD1.Files[0]);
    SetForegroundWindow(MainWnd.Handle);
    InvalidateRect(MainWnd.Handle,nil,true);
  end;
end;

ここでは、この章の第1節で説明した LoadImage( ) APIを使って、ビットマップファイルをロードし、ビットマップハンドルを取得している。 ハンドルが取得できたら、今までグローバル変数 hBitM に保持してきたビットマップを削除してから、ハンドルの値を更新し、再描画するために InvaridateRec( ) を呼び出し WM_PAINT メッセージを生成させる。WM_PAINT メッセージを処理する MainPaint( ) ハンドラでは、グローバル変数のビットマップハンドル hBitM を使ってビットマップを表示している。

procedure MainPaint(var Msg: TMessage);
var
  ps:TPaintStruct;
begin
  if hBitM = 0 then exit;
  BeginPaint(MainWnd.Handle,ps);
    DrawBitmap(ps.hdc,hBitM,10,10);
  EndPaint(MainWnd.Handle,ps);
end;

ここから呼び出されるヘルパ関数 DrawBitmap( ) を見てみよう。

procedure DrawBitmap(DC:HDC;hBit:HBITMAP;x,y:integer);
var
  memDC: HDC;
  bm: TBitmap;
begin
  GetObject(hBit,SizeOf(bm),@bm);
  memDC := CreateCompatibleDC(DC);
  SelectObject(memDC, hBit);
  BitBlt(DC,x,y,bm.bmWidth,bm.bmHeight,memDC,0,0,SRCCOPY);
  DeleteDC(memDC);
end;

ここでは、ビットマップハンドルをもとにしてビットマップのサイズ情報を取得している。それには、GetObject( ) APIを使う。

  function GetObject(p1: HGDIOBJ; 
                     p2: Integer; 
                     p3: Pointer): Integer; stdcall;

最初の p1 パラメータには、取得するオブジェクトのハンドルを指定する。 ペン、ブラシ、ビットマップ、パレット、フォントのハンドルを指定できる。p3 には情報を取得するためのレコードのアドレスを指定する。 p2 には、p3 で指定したレコードのサイズをバイト単位で指定する。 p1 にビットマップハンドルを指定した場合は、 p3 には TBitmap 型のレコードを指定する。 TBitmapVCLにも同名のクラスがあるが、ここでのそれは Windows.pas に定義されているレコードである。

type
{ Bitmap Header Definition }
  PBitmap = ^TBitmap;
  TBitmap = packed record
    bmType: Longint;		//いまのところ常にゼロとなる
    bmWidth: Longint;		//ビットマップの幅
    bmHeight: Longint;		//ビットマップの高さ
    bmWidthBytes: Longint;		//走査ラインのビット数。bmWidth の2倍であることが多い。
    bmPlanes: Word;			//カラープレーンの数
    bmBitsPixel: Word;		//1ピクセルあたりのカラーを表現するビット数
    bmBits: Pointer;		//ピクセルデータの開始位置のポインタ
  end;

GetObject( ) は成功すると p3 で指定されたレコードにこのAPIが書き込んだバイト数を返す。 失敗するとゼロを返す。 ビットマップハンドルを使って GetObject( ) により TBitmap 型の変数に情報を取得する。 このレコードの bmWidthbmHeight フィールドからビットマップのサイズを取得する。 その後、この手続きは hBit のビットマップハンドルで識別されるビットマップを表示する。コードの意味を箇条書きにすると以下のようになる。

  1. デバイスコンテキストハンドルDCにコンパチブルなデバイスコンテキストをメモリ上に作成して、そのハンドル(memDC)を得る。
  2. memDCにビットマップを選択する。
  3. memDC から DC にビットイメージを転送する。
  4. memDCを削除する。

このシークエンスは、取得済みのビットマップハンドルを使ってビットマップを表示する標準的な手続きである。ここでは 3.の BitBlt( ) APIが最も重要な役割をしている。

  function BitBlt(DestDC: HDC; 
                    X, Y, 
           Width, Height: Integer; 
                   SrcDC: HDC;
              XSrc, YSrc: Integer; 
                     Rop: DWORD): BOOL; stdcall;

このAPIは、転送元デバイスコンテキスト SrcDC から、転送先デバイスコンテキスト DestDC に矩形領域のビットブロックを転送する。 X,Y は転送先の矩形の左上の論理座標を指定する。 Width, Height には、転送する矩形の幅と高さを指定する。 XSrc, YSrc には転送元の矩形の左上の論理座標を指定する。 Rop には転送に伴うラスター操作を指定する。 Rop に指定できる定義済み定数の数は多いが、単にイメージをコピーするには、例のように SRCCOPY を指定する。 Rop に指定できる Windows.pas に定義されている定数を以下に挙げておく。

  { Ternary raster operations }
  SRCCOPY     = $00CC0020;     { dest = source                    }
  SRCPAINT    = $00EE0086;     { dest = source OR dest            }
  SRCAND      = $008800C6;     { dest = source AND dest           }
  SRCINVERT   = $00660046;     { dest = source XOR dest           }
  SRCERASE    = $00440328;     { dest = source AND (NOT dest )    }
  NOTSRCCOPY  = $00330008;     { dest = (NOT source)              }
  NOTSRCERASE = $001100A6;     { dest = (NOT src) AND (NOT dest)  }
  MERGECOPY   = $00C000CA;     { dest = (source AND pattern)      }
  MERGEPAINT  = $00BB0226;     { dest = (NOT source) OR dest      }
  PATCOPY     = $00F00021;     { dest = pattern                   }
  PATPAINT    = $00FB0A09;     { dest = DPSnoo                    }
  PATINVERT   = $005A0049;     { dest = pattern XOR dest          }
  DSTINVERT   = $00550009;     { dest = (NOT dest)                }
  BLACKNESS   = $00000042;     { dest = BLACK                     }
  WHITENESS   = $00FF0062;     { dest = WHITE                     }

このように、Rop は転送元のイメージを転送先にあるイメージとどのような演算をして「重ねる」のかということを指示している。 SRCCOPY 以外も試してみると良い。

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

19−5 ビットマップリソース

ビットマップファイルをリソースにするのは、 アイコンと同じ方法を用いる。 ペイントや Delphi に付属する ImageEdit をつかってビットマップファイルを作成したり、既存のファイルを利用できる。 ここでは、ImageEdit を使って 100x100 のビットマップファイルを二つ、left.bmp right.bmp という名前でつくる。リソース記述ファイルを以下のようにする。

//
//  MyRes.rc
//

MyIcon ICON "API.ICO"

Left BITMAP "left.bmp"

Right BITMAP "right.bmp"

このリソース記述ファイルを、アイコンファイルと二つのビットマップファイルがあるフォルダに MyRes.rc という名前で保存する。 前節で作った ResourceCompiler.exe を使って、リソースファイルにコンパイルする。 このリソースファイルを利用するプログラムを作る。

  1. エクスプローラを使って、新しいフォルダを 1905BitmapResource という名前でつくる。
  2. 作ったフォルダに、エクスプローラを使って 1904DisplayBitmap フォルダの Skeleton03.dprUtilFunc.pasGDIObject.pasUtilClass.pasAPIControl.pasAPIWindow.pas をコピーする。
  3. コピーした Skeleton03.dpr をダブルクリックして Delphi を立ち上げる。
  4. コードエディタに Skeleton03.dpr のコードがあることを確認してから、[ファイル]メニューから[プロジェクトに名前を付けて保存]を選んで BitmapResource.dpr という名前で保存する。
  5. [表示]メニューから[ユニット]を選んで表示されるダイアログから、UtilFunc を選ぶ。
  6. コードエディタで以下のように編集する。
  7. [F.9]でコンパイルして実行する。

最初に、前節で作った DrawBitmap( ) 手続きを UtilFunc.pas に実装しよう。 DoChooseFont( ) の宣言の下に以下の宣言を付け加える。

procedure DrawBitmap(DC:HDC;hBit:HBITMAP;x,y:integer);

そして、対応する実装部に以下のコードを追加する。

procedure DrawBitmap(DC:HDC;hBit:HBITMAP;x,y:integer);
var
  memDC: HDC;
  bm: TBitmap;
begin
  GetObject(hBit,SizeOf(bm),@bm);
  memDC := CreateCompatibleDC(DC);
  SelectObject(memDC, hBit);
  BitBlt(DC,x,y,bm.bmWidth,bm.bmHeight,memDC,0,0,SRCCOPY);
  DeleteDC(memDC);
end;

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

program BitmapResource;

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

  {$R MyRes.res}

//------------------------------------------------------------
//             Global Types, Constants and Variables
//------------------------------------------------------------
var
  MainWnd: TSDIMainWindow;

  hBLeft, hBRight: HBITMAP;

//------------------------------------------------------------
//             Helper Handlers
//------------------------------------------------------------

//------------------------------------------------------------
//             Object Event Handlers
//------------------------------------------------------------
procedure MainLButtonDown(var Msg: TMessage);
var
  DC: HDC;
begin
  DC := GetDC(MainWnd.Handle);
    DrawBitmap(DC,hBLeft,Msg.LParamLo,Msg.LParamHi);
  ReleaseDC(MainWnd.Handle, DC);
end;

procedure MainRButtonDown(var Msg: TMessage);
var
  DC: HDC;
begin
  DC := GetDC(MainWnd.Handle);
    DrawBitmap(DC,hBRight,Msg.LParamLo,Msg.LParamHi);
  ReleaseDC(MainWnd.Handle, DC);
end;
//------------------------------------------------------------
//             Main Window Event Handlers
//------------------------------------------------------------
procedure MainCreate(var Msg: TMessage);
var
  hWindow: HWND;
begin
  hWindow := MainWnd.Handle;
  MakeInitMenu(hWindow);

  MainWnd.Icon := LoadIcon(hInstance,'MYICON');

  hBLeft := LoadBitmap(HInstance,'LEFT');
  hBRight := LoadBitmap(HInstance,'RIGHT');

  with MainWnd do begin
    ClientWidth := 360;
    ClientHeight := 260;
    Center;
    Trap(WM_LBUTTONDOWN,MainLButtonDown);
    Trap(WM_RBUTTONDOWN,MainRButtonDown);
  end;
end;

procedure OnTest(hWindow: HWND);
begin
  MessageBeep($FFFFFFFF);
end;

procedure MainCommand(var Msg: TMessage);
begin
  Msg.Msg := MainWnd.Handle;
  InitMenuCommand(Msg,OnTest);
end;

procedure MainDestroy(var Msg: TMessage);
begin
  DeleteObject(hBLeft);
  DeleteObject(hBRight);
  PostQuitMessage(0);
end;

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

  MainWnd := TSDIMainWindow.Create(0,GetExeName);
  with MainWnd do begin
    OnCreate := MainCreate;
    OnDestroy := MainDestroy;
    OnCommand := MainCommand;
  end;
  MainWnd.DoCreate;

  Halt(MessageLoopNormal);

end.

DownloadExe.gifBitmapResource.zip(15.380kB)
BitmapResource.gif

実行して、メインウィンドウのクライアント領域の中をマウスで左クリックすると left.bmp が、右クリックすると right.bmp がクリックされたマウス位置を左上にして表示される。 この様子を右図に示す。 これらのビットマップはリソースとして実行可能ファイルに埋め込まれているので、実行可能ファイル単独で実行してもこれらのビットマップが表示される。

コンパイラにリソースファイルをリンクするように依頼するため、{$R MyRes.res} のコンパイラ指令を使っている。 MainCreate( ) ハンドラで以下のAPIを使用してリソースからビットマップをロードしている。

  function LoadBitmap(hInstance: HINST; 
                   lpBitmapName: PAnsiChar): HBITMAP; stdcall;

第一パラメータには、呼び出しているアプリケーションのインスタンスハンドルを指定する。第二パラメータには、リソース名を指定する。ここでは、'Left' または 'Right' を指定する。 このAPIは成功するとビットマップハンドルを返してくれる。 アイコンと同じように、ビットマップもハンドルで識別されるシステム上のオブジェクトである。 使い終わったら、MainDestroy( ) ハンドラで実行しているように DeleteObject( ) でハンドルで識別しているオブジェクトをメモリから削除しなければならない。

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

19−6 まとめ

本章では、 外部ファイルから任意のアイコンやビットマップをロードして表示する方法を示した。 それから、リソースファイルにアイコンやビットマップを取り込み、実行可能ファイルにリンクする方法を示した。 これはリソースファイルのための入門編である。


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

  1. Skeleton03.dpr (2 kB)
  2. UtilFunc.pas (12 kB)
  3. UtilClass.pas (46 kB)
  4. GDIObject.pas (21 kB)
  5. APIControl.pas (61 kB)
  6. APIWindow.pas (19 kB)



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