グラフィックの描画について

フォームに何かグラフィックを描画する場合には、フォームのプロパティである Canvas を使う必要があります。いままでにも、MoveTo や LineTo を使って簡単な例を見てみましたが、これらは全て Canvas 上に描画しています。あまり難しくとらえずに Canvas は単にグラフィックを描画する描画面と考えると分かりやすいかもしれません。そして、MoveTo や LineTo は Canvas のメソッドです。他にも Rectangle や Ellipse など様々なメソッドがあります。

例えば、Canvas 上に円を描いたとします。そして、その円が描かれたフォームを最小化したりサイズを小さくした場合、描かれていたはずの円が消えてしまいます。例えば以下のようにプログラムを書いてみてください。

procedure TForm1.Button1Click(Sender: TObject);
begin
  Canvas.Ellipse(10, 10, 100, 100);
end;

ボタンを押すと円が描かれます。そこで、その描かれた円よりもフォームのサイズを小さくしてみます。

このように円の一部が隠れるようにしてください。

次に、フォームの大きさを元に戻します。すると隠れていた部分は描画されません。

隠れていた部分は描画されなくなってしまいます。

フォームを最小化して、また元の大きさに戻しても同じ事が言えます。つまり描画されるグラフィックは保存されないということになります。

これは Windows 自体が描画されるグラフィックを保存しないという仕様だからです。では、なぜそれ以外のもの( 例えば、フォームに貼り付けたボタン等 ) は、最小化したりフォームのサイズを小さくしても、ちゃんと描画されているのでしょうか? それは保存をしているわけではなく再描画をしているからなんです。例えば、今回の場合を例にとってみましょう。実行してボタンを押すと「円」が描かれました。ここで、フォームを最小化したとします。そして再び元の大きさに戻そうとすると Windows は、フォームを表示する為に「あるメッセージ」をフォームに送ります。そして、フォームはメッセージを受け取って、自分を再び再描画する仕組みになっているのです。同じようにフォームに貼り付けたボタンもメッセージを受け取り再描画されます。しかし、ボタンを押して描画した「円」はどうでしょうか? この「円」はボタンを押す事によって単に描かれたに過ぎません。ボタン等には Windows からのメッセージを受けて再び描画する「仕組み」を持っていますが、今回の「円」には、そのような仕組みはありません( 上に書いたプログラムしか「円」を描く方法を持っていません )。ですから、最小化したりサイズを小さくして「円」の一部が隠れたら、その部分は再描画されないのです。

では、今回のようにボタン等を押した事によって描画するグラフィックには「再描画」という機能を持たせる事が出来ないのかと言うと簡単に出来てしまいます。Windows がフォームに再描画のメッセージを送った時には( Delphi の ) OnPaint イベントが発生するようになっています。オブジェクトインスペクタで Form を選択し、「イベント」ページに OnPaint があるのが分かると思います。そのイベントハンドラに「円」の描画を書いてやれば、最小化したりしても「円」が消える事はなくなります。言い換えると、最小化した後で再び元の大きさにもどしたり、或いはフォームのサイズを小さくし、円の一部が隠れた後で、再びその隠れた部分を表示させようとすると OnPaint イベントが発生して円が描画されるようになります。

procedure TForm1.FormPaint(Sender: TObject);
begin
  Canvas.Ellipse(10, 10, 100, 100);
end;

このように OnPaint イベントハンドラに書いてやりますと、最小化から復帰した場合などに、この OnPaint イベントが呼ばれて「円」が再び描画されます。

Invalidate

Invalidate メソッドは簡単に言うと OnPaint イベントを強制的に発生させます。簡単な例を見てみましょう。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  MaxCount: Integer;

procedure TForm1.Button1Click(Sender: TObject);
var
  s: string;
  i: Integer;
begin
  s := InputBox('OnPaint の実験', '1 から 5 の数字を入力してください。', '1');
  i := StrToIntDef(s, 100);

  case i of
    1: MaxCount := 1;
    2: MaxCount := 2;
    3: MaxCount := 3;
    4: MaxCount := 4;
    5: MaxCount := 5;
    else
    begin
      ShowMessage('正しく入力してください。');
      Exit;
    end;
  end;

  Invalidate; // 今回のキーポイント!
end;

procedure TForm1.FormPaint(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to MaxCount-1 do
    Canvas.Ellipse(10, 10+50*i, 60, 60+50*i);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  MaxCount := 2;
end;

end.

Form1.FormCreate, Form1.FormPaint は、問題ないと思います。続いて Button1Click イベントハンドラですが、

s := InputBox('OnPaint の実験', '1 から、5 の数字を入力してください。', '0');
i := StrToIntDef(s, 100);

InputBox で、ユーザーから任意の数値を入力してもらいます。この InputBox の戻り値は文字列ですので、次の StrToIntDef で数値に型変換しています。例えばもし、数値に変換できないようなものを InputBox に入力された場合は第二パラメータで設定した 100 がデフォルトで i に代入されるようにします。

  case i of
    1: MaxCount := 1;
    2: MaxCount := 2;
    3: MaxCount := 3;
    4: MaxCount := 4;
    5: MaxCount := 5;
    else
    begin
      ShowMessage('正しく入力してください。');
      Exit;
    end;
  end;

case 文で場合分けを行います。今回は、ユーザーには、1 から 5 までを選択してもらうようにしたので、それ以外の数値の場合は無効としています。

そして最後に Invalidate メソッドで OnPaint イベントを発生させてやります。このように強制的にOnPaint イベントを発生させることも可能となります。この Invalidate をコメントアウトしてみて、その違いもみてみると分かりやすいかもしれません。


up next
Last update: 2002/8/11