ナッキーの「Turbo Delphiはじめて奮戦記」 - 第11回 お絵かきソフトでペンの太さと色を設定

By: Hitoshi Fujii

Abstract: 前回はオブジェクトを使って、ダブルバッファリングを実現しました。今回はPenプロパティの活用とダイアログボックスについて紹介します。

Hide image
nacky75

ナッキー

オブジェクトが登場して、ちょっと冷や汗かいたけど、なんとか画面を再描画できました。メモリリークもチェックできちゃったし!今回は「ペンの色なんかを変えたい」って提案したけど、簡単にできるのかな?

 

Hide image
takahashi75

高橋先生

CanvasのPenプロパティを活用して、いろいろな線が描けるといいね。今回は、さらにダイアログボックスを使ってみよう。


    ペンの太さを変える

新しい機能を追加するなんて、簡単にできるのかなぁ?まずは、前回保存したファイルを開きます。Turbo Delphiを起動して、画面中央の「ホームページ」で「Drawing.bdsproj」を選択。もし一覧に表示されていなければ、ツールバーの[プロジェクトを開く(Ctrl+F11)]ボタンをクリックします。「プロジェクトを開く」ダイアログボックスから「Drawing.bdsproj」を探します。今回追加したい機能は、ペンの太さと、色を変えること。ペンの太さは、ボタンで細書きと太書きを切り替えられるようにして、ペンの色は、ボタンか何かで細かく色を変更できると分かりやすくていいね。

高橋先生:今はスクロールボックスが画面いっぱいに広がっているから、画面にボタンが並ぶように場所を作ろう。

ナッキー:場所を作るって、もう1つフォームをつくるとか?

高橋先生:ちょっと空間を作ればいいよ。

ナッキー:でも「Align」プロパティを「alClient」にしたから、ドラッグでは場所が空けられません。

高橋先生:「TPanel」コンポーネントを置いてみよう。そして、TPanelの「Align」プロパティを「alLeft」に設定すれば左に配置できるね。

では、やってみますね。画面中央を「FormDrawing」のフォームデザインの画面に変更します。画面右下のツールパレット「Standard」カテゴリから「TPanel」コンポーネントを探します。見つかったら、クリックしてフォーム上をクリックします。

プロパティを設定します。TPanelコンポーネントを選択していることを確認して、画面左下のオブジェクトインスペクタ「プロパティ」ページで「レイアウト」カテゴリの「Align」プロパティを探します。見つかったらAlignプロパティを「alLeft」に変更します。

なんか変な感じがするけど、なんでだろう?

Hide image
01TPanelの配置

図01 TPanelの配置

高橋先生:TPanelをTScrollBoxの上に配置したから、TPanelとの間に親子関係ができたんだ。TPanelはTScrollBoxの子コンポーネントになったので、スクロールバーがTPanelの下にまで続いているんだよ。

ナッキー:えっー、間違えちゃった!

高橋先生:大丈夫。何とかなるよ。

ナッキー:いいんですか?何とかなるって言ったってドラッグじゃあ移動できませんよ。

高橋先生:画面左上の構造ペインをみて。「Panel1」が「sbxDraw」スクロールボックスの下に並んでいるね。この「Panel1」を「frmMain」フォームの上へドラッグすると、スクロールボックスとの親子関係は解消されるよ。

Hide image
02構造ペインでドラッグ

図02 構造ペインでドラッグ

ナッキー:あーよかった。簡単に直るんですね。

高橋先生:親子関係を変更できたら、TPanelの幅を少し狭めて、Captionプロパティを空欄にしておこう。

では、指示に従って親子関係を変更します。構造ペインで「Panel1」を「frmMain」の上にドラッグします。うまく変更できたら、プロパティを変更します。幅の調整はフォームデザインで青いサイズ変更ハンドルをドラッグして調整するか、画面左下のオブジェクトインスペクタ、プロパティページで「レイアウト」カテゴリの「Width」プロパティに「70」と入力します。次に「ローカライズ対象」カテゴリの「Caption」プロパティに入力されている文字を削除します。

    TSpeedButtonのグループ化とコンポーネントの複数選択

ボタンを置く場所ができたから、ボタンコンポーネントを置けばいいのね。

高橋先生:通常のボタンつまりTButtonでも、細書きか太書きか表現は可能だけれど、どっちの状態か分かりにくいと思うよ。それに、ボタンには色が付けられないんだ。太さ設定も、色設定も普通のTButtonじゃ不十分だね。

ナッキー:ペンの太さを選ぶラジオボタンみたいなのじゃだめなの?

高橋先生:グループボックスコンポーネントを使う方法だね。確かにできるけど、ある程度広い場所が必要になるよね。トグルボタンって知ってる?

ナッキー:グル?全然、わからないです。

高橋先生:スイッチみたいにクリックするとへこんだ状態を維持するボタンをトグルボタンっていうんだ。グループボックスではどれか1つしか黒丸が付かないようになっているけれど、同じように、複数のボタンのうち、どれか1つしかへこまないようにすることもできるよ。

ナッキー:じゃあ、トグルボタンコンポーネントがあるんですね?

高橋先生:専用のコンポーネントはないけれど、計算機の回で使った、スピードボタンコンポーネントで設定できるよ。TSpeedButtonの「GroupIndex」プロパティに、共通する「0」以外の数値を入力する。同じGroupIndexをもつボタンの中で、どれか1つがへこんだ状態になるんだ。

ナッキー:スピードボタンっていろいろできるんですね。プロパティ設定だけで実現できるなら簡単そう。

高橋先生:コンポーネントの複数選択ができると、さらに設定が簡単になるよ。複数選択の方法は2種類ある。1つ目を選択したあとはキーボードの「Shift」キーを押しながらクリックする方法。そして、キーボードの「Ctrl」キーを押しながらドラッグする方法。どちらでもやりやすい方法を選んで覚えてね。

ナッキー:複数選択ができるとどう簡単になるんですか?

高橋先生:複数のコンポーネントのプロパティが1度に設定できるんだ。異なる種類のコンポーネントの場合は、共通するプロパティだけが変更できるよ。サイズを揃えたいときには、「Width」プロパティと「Height」プロパティで、幅と高さを揃えればいいんだ。

揃えるといえば、位置を揃えることもできる。これもコンポーネントの複数選択を使ってね。揃えたいコンポーネントをすべて選択しておく。選択したコンポーネントの上で、マウスの右ボタンをクリックして「位置(Z)」のサブメニュー「位置合わせ(A)…」を選択する。画面は左右に分かれていて、左が「水平位置合わせ」右が「垂直位置合わせ」になっている。横方向の揃え方、縦方向の揃え方を、それぞれ選択して[OK]ボタンをクリックする。ただし、間違えても元に戻せないから気をつけてね。

Hide image
03位置合わせ

図03 位置合わせ

ナッキー:ちょっと間違えちゃいそうだな。

高橋先生:練習用のコンポーネントをいくつか置いて、練習してみるといいよ。

ナッキー:自信ないから練習してから、設定してみます。

コンポーネントを配置してみましょう。Panel1パネルコンポーネントの上に、画面右下のツールパレット「Additional」カテゴリの「TSpeedButton」を2つ配置します。色設定用の色をつけたボタンを作りたいんですけど、ボタンコンポーネントじゃ色が付けられないんですって。「Additional」カテゴリの「TShape」コンポーネントなら色をつけた四角形になるんだそうです。ボタンの代わりに「TShape」コンポーネントをPanel1の上に配置します。あとでサイズを大きくするので、それぞれ少し間隔を開けて配置します。

次にプロパティ設定です。2つのTSpeedButtonのGroupIndexプロパティを設定します。SpeedButton1を選択した後、キーボードの「Shift」キーを押しながらSpeedButton2をクリックして複数選択します。オブジェクトインスペクタ、プロパティページで「その他」の「GroupIndex」プロパティを「1」にします。

SpeedButton1、SpeedButton2

カテゴリ名

プロパティ名

設定値

その他

GroupIndex

1


次に大きさを揃えたいので、SpeedButton1、SpeedButton2、Shape1をすべて選択します。SpeedButton1、SpeedButton2を選択していることを確認して、キーボードの「Shift」キーを押したまま、Shape1をクリックします。選択できたらオブジェクトインスペクタ、プロパティページの「レイアウト」カテゴリで「Height」プロパティを「40」に、「Width」プロパティも「40」にします。

SpeedButton1、SpeedButton2、Shape1

カテゴリ名

プロパティ名

設定値

レイアウト

Height

40

レイアウト

Width

40


配置も揃えておきます。ここで、複数選択にPanel1も加えます。SpeedButton1、SpeedButton2、Shape1の選択は解除しないで、そのまま「Shift」キーを押しながらPanel1もクリックします。選択したコンポーネントの上でマウスの右ボタンをクリックします。「位置(Z)」のサブメニュー「位置合わせ(A)…」を選択して、位置合わせダイアログボックスを表示します。パネルの中央に置きたいので「水平位置合わせ」の「ウィンドウ中央に置く(W)」を選択して[OK]ボタンをクリックします。

ここからは、それぞれのコンポーネントについて設定します。複数選択したままでは個々のコンポーネントは選択しにくいので、選択していないペイントボックスなどをクリックして選択を解除してから、個々のコンポーネントを選択するとやりやすいんですって。表のようにそれぞれのプロパティを設定します。

SpeedButton1

カテゴリ名

プロパティ名

設定値

その他

Name

sbtnHoso

その他

Down

True

ローカライズ対象

Caption


SpeedButton2

カテゴリ名

プロパティ名

設定値

その他

Name

sbtnFuto

ローカライズ対象

Caption


Shape1

カテゴリ名

プロパティ名

設定値

その他

Name

shpColor

表示

Brush - Color

clFuchsia


Hide image
04プロパティ設定

図04 プロパティ設定

    Penプロパティの設定

次はコードを書いて、ペンの細書きと太書きを変更しなくっちゃね。とは言っても、どこをどうすればいいのかしら?教えて、高橋先生!

高橋先生:[細]ボタンか[太]ボタンがへこんだ状態だね。だから、使う直前にどちらがへこんでいるかチェックして太さを決めればいいんじゃないかな。

ナッキー:へぇ、使う直前でいいんだ。

高橋先生:Canvas.Penの「Width」プロパティで太さを設定する。単位はピクセルだから細いのは「5」、太いのは「20」くらいでいいだろう。お好みで違う値にしてもいいよ。ペイントボックスでも、ビットマップオブジェクトでも、同じ値を使いたいから、ローカル変数を用意しよう。同じ値を複数箇所で使うときは、変数を用意したほうがいいね。

ナッキー:じゃあ、[細]ボタンがへこんでいたら5を変数に代入するって感じでいいのね。

さっそく、コードを記述します。すでに作っているイベントハンドラを加工します。画面をコードエディタに切り替えます。pbxDrawMouseMoveイベントハンドラに記述するので、探して変数を宣言します。beginの前にvar節を置いて「w」変数を宣言します。次に、マウスの左ボタンが押されているかどうかを調べているIf文(if GetKeyState(VK_LBUTTON) < 0 then...)の中に、sbtnHosoスピードボタンがへこんでいるかどうかを調べるIf文を記述します。sbtnHosoスピードボタンコンポーネントがへこんでいればw変数に「5」を、そうでなければ「20」を代入します。これで変数wに、太さを指定できました。次に、この変数wの値をペンの太さを指定するプロパティ「Pen.Width」に代入します。これまでは、「5」を代入していましたから、これを「w」を代入するよう変更するんですね。太字部分を追加変更します。

procedure TfrmMain.pbxDrawMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  w: Integer;
begin

  if GetKeyState(VK_LBUTTON) < 0 then
  begin
    if sbtnHoso.Down then
      w := 5
    else
      w := 20;

    pbxDraw.Canvas.Pen.Mode  := pmCopy;
    pbxDraw.Canvas.Pen.Width := w;
    pbxDraw.Canvas.Pen.Color := clBlack;
    pbxDraw.Canvas.MoveTo(preX, preY);
    pbxDraw.Canvas.LineTo(X, Y);

    bmpBuf.Canvas.Pen.Mode  := pmCopy;
    bmpBuf.Canvas.Pen.Width := w;
    bmpBuf.Canvas.Pen.Color := clBlack;
    bmpBuf.Canvas.MoveTo(preX, preY);
    bmpBuf.Canvas.LineTo(X, Y);
  end;

  preX := X;
  preY := Y;

end;

できたら、保存して実行します。ツールバーの[すべて保存]ボタンをクリックして、[実行]ボタンをクリックします。デフォルトでは[細]ボタンがへこんでいます。確認してからペイントボックスに線を描きます。次に[太]ボタンをクリックした後、ペイントボックスに線を描きます。[細]ボタンより太く描けることを確認します。もしも、太さが気に入らない場合は、wに代入する数値を変更してもいいんですって。これでペンの太さが変えられたわ。

    ダイアログボックスの使い方

次はペンの色です。細かく色設定できるといいんだけどなぁ。教えて、高橋先生!

高橋先生:初めてプログラムを作ったときに、フォームに色設定をしたことを覚えてる?

ナッキー:それは、覚えてます。きれいな色に設定できて楽しかったな。

高橋先生:その時「色の設定」ダイアログボックスって使ったよね。それを使ってみよう。

ナッキー:うわぁ。大変そう。だって、あの画面全部作るんですよね。

高橋先生:あの画面は作るんじゃなくて、呼び出すだけだよ。だから簡単。

ナッキー:呼び出すって、「おーい」とか?

高橋先生:…。型どおりのダイアログボックスを、表示するコンポーネントがあるんだ。それを使えば、すぐに表示できるよ。ツールパレットの「Dialogs」カテゴリにたくさん入っている。この中の「TColorDialog」コンポーネントを使おう。

ナッキー:へぇ。便利になっているんですね。

高橋先生:Dialogsカテゴリのコンポーネントは、ダイアログボックスを表示して、ユーザーが選んだり、入力した値は取得できるけど、色設定まではやらないよ。どこかに設定するなどは、コード上で処理してね。

ナッキー:何でもできるのかと思っちゃいました。

コンポーネントを配置します。画面をフォームデザイナに切り替えます。画面右下のツールパレット「Dialogs」カテゴリの「TColorDialog」コンポーネントをフォームに配置します。どこに配置してもかいまいません。あれ?ドラッグでサイズ変更ができないみたい。壊れちゃってる?

高橋先生:大丈夫、TColorDialogコンポーネントは実行しても目に見えない「非ビジュアルコンポーネント」なんだ。これに対して、通常のコンポーネントは「ビジュアルコンポーネント」と呼ばれる。「XPManufest」コンポーネントも非ビジュアルコンポーネントだね。フォームの設計には影響を与えないから、どこに配置してもいいし、サイズ変更もできないんだ。

ナッキー:てっきり自分でダイアログボックスを作るのかと思った。配置するだけなのね。

高橋先生:ダイアログボックスを表示するのは「Execute」メソッド。表示したダイアログボックスで[OK]ボタンが押されたら、「True」、キャンセルや[×]ボタンが押されたら、「False」が返るようになっている。だから

if ダイアログボックス.Execute then

ではじめるのが一般的。[OK]ボタンが押されたら処理をするように続けられるよ。設定はプロパティなどで知ることができる。

じゃあ、このままプロパティ設定とコード記述だけすればいいんですね。

    色の設定をする

shpColorコンポーネントをクリックしたとき、ダイアログボックスを出すようにコードを記述したいんですけど、OnClickイベントがありませんね。

高橋先生:どのコンポーネントにもOnClickがあるとは限らないよ。ただ、似たイベントはあるね。OnMouseDownとOnMouseUpだ。今回はOnMouseUpにしよう。どのタイミングか分かるかな?

ナッキー:マウスのボタンを放したときに、おこるイベントですね。

高橋先生:そのとおり。それじゃあ、ここでTColorDialogコンポーネントのExecuteメソッドを呼ぶようにしよう。Colorプロパティに選んだ色を格納しているので、そのままTShapeコンポーネントの「Brush.Color」プロパティに代入すればいいね。

難しそうなことができるのに、コードは単純なのね。イベントハンドラを作成します。shpColorシェープコンポーネントを選択してオブジェクトインスペクタ、イベントページの「入力」カテゴリで「OnMouseUp」を探してダブルクリックします。以下のコードの太字部分を追加します。

procedure TfrmMain.shpColorMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if ColorDialog1.Execute then
    shpColor.Brush.Color := ColorDialog1.Color;
end;

次にペンに色指定します。今回もピクチャーボックスの「OnMouseMove」で設定します。ペンの太さと同じく変数を用意します。変数名は「c」、型名は「TColor」とします。型がそろわないと代入できないもんね。sbtnHosoボタンがへこんでいるかどうかのIf文(if sbtnHoso.Down then …)のすぐ下で、c変数にshpColorの「Brush.Color」プロパティの値を代入します。

ペイントボックスコンポーネントとビットマップオブジェクトの「Pen.Color」プロパティに「clBlack」を代入している部分2箇所で、「c」変数を代入するよう書き換えます。太字の部分を追加変更します。

unit FormDrawing;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, XPMan, Buttons;

type
  TfrmMain = class(TForm)
    sbxDraw: TScrollBox;
    XPManifest1: TXPManifest;
    Panel1: TPanel;
    pbxDraw: TPaintBox;
    sbtnHoso: TSpeedButton;
    sbtnFuto: TSpeedButton;
    shpColor: TShape;
    ColorDialog1: TColorDialog;
    procedure pbxDrawMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure pbxDrawMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormCreate(Sender: TObject);
    procedure pbxDrawPaint(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure shpColorMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { Private 宣言 }
    preX: Integer;
    preY: Integer;
    bmpBuf: TBitmap;
  public
    { Public 宣言 }
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  preX := -1;
  preY := -1;
  bmpBuf := TBitmap.Create;
  bmpBuf.PixelFormat := pf32bit;
  bmpBuf.SetSize(640, 480);
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
//  ReportMemoryLeaksOnShutdown := True;

  if Assigned(bmpBuf) then
  begin
    bmpBuf.FreeImage;
    FreeAndNil(bmpBuf);
  end;
end;

procedure TfrmMain.pbxDrawMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if GetKeyState(VK_LBUTTON) < 0 then
  begin
    pbxDrawMouseMove(Sender, Shift, X, Y);
  end;
end;

procedure TfrmMain.pbxDrawMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  w: Integer;
  c: TColor;
begin

  if GetKeyState(VK_LBUTTON) < 0 then
  begin
    if sbtnHoso.Down then
      w := 5
    else
      w := 20;

    c := shpColor.Brush.Color;

    pbxDraw.Canvas.Pen.Mode  := pmCopy;
    pbxDraw.Canvas.Pen.Width := w;
    pbxDraw.Canvas.Pen.Color := c;
    pbxDraw.Canvas.MoveTo(preX, preY);
    pbxDraw.Canvas.LineTo(X, Y);

    bmpBuf.Canvas.Pen.Mode  := pmCopy;
    bmpBuf.Canvas.Pen.Width := w;
    bmpBuf.Canvas.Pen.Color := c;
    bmpBuf.Canvas.MoveTo(preX, preY);
    bmpBuf.Canvas.LineTo(X, Y);
  end;

  preX := X;
  preY := Y;

end;

procedure TfrmMain.pbxDrawPaint(Sender: TObject);
begin
  pbxDraw.Canvas.CopyMode := cmSrcCopy;
  pbxDraw.Canvas.Draw(0, 0, bmpBuf);
end;

procedure TfrmMain.shpColorMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if ColorDialog1.Execute then
    shpColor.Brush.Color := ColorDialog1.Color;
end;

end.

変更できたら、保存して実行してみます。shpColorシェープコンポーネントをクリックします。表示したダイアログボックスで、色を黒以外に設定して[OK]ボタンをクリックします。ペイントボックスで絵を描きます。

Hide image
05完成図

05完成図

色の追加ボタンで、独自に追加した色も使うことができます。

ナッキー:色やペンの太さを変更できて、お絵かきソフトとして使いやすくなりました。使ってみても楽しいですね。

高橋先生:お絵かきソフトとして最低限の機能が持てたね。

ナッキー:最低限って、また何か追加するんですか?

高橋先生:プログラムにはメニューバーがよくあるよね。「お絵かきソフト」にメニューバーをつけてさらに機能追加したらどうかな。

ナッキー:メニューバーがあると本格的ですね。ちょっと難しそうだけど、そう聞くと機能を追加したくなっちゃいます。

高橋先生:1度に作らなくても少しずつ完成に近づけばいいんだよ。いい機能を思いつかないときは、プログラムを寝かしておくこともあるんだ。あせらずにゆっくり進めよう。


ナッキーの「Turbo Delphiはじめて奮戦記」

Prev | Next | Index


Server Response from: ETNASC01