ナッキーの「Turbo Delphiはじめて奮戦記」 - 第13回 お絵かきソフトでファイル操作

By: Hitoshi Fujii

Abstract: 前回はステータスバーとメニューを作成しました。今回はメニューをクリックすると動くように、機能を追加してします。ファイルを開いたり、保存したりできるようになります。

Hide image
nacky75

ナッキー

ステータスバーとメニューが増えただけで、見た目はずいぶん本格的になりましたよね。なんだか、これだけで満足な気分です。

 

Hide image
takahashi75

高橋先生

おいおい、見た目だけじゃ動かないんだ。メニューをクリックして、ちゃんと動くものを作ろう。実際に動くようにすることを「実装」っていうんだよ。今回は、[開く]メニューと[名前をつけて保存]メニュー、[クリア]メニューをつくろう。


    [名前をつけて保存(S)...]メニューを作る

じゃあ、[名前をつけて保存(S)...]メニューを「実装」するってことになるのね。[名前をつけて保存(S)...]メニューっていうとダイアログボックスを表示して、ファイル名を入力するイメージがあるけど、あんな画面作るのは大変そうだな。教えて、高橋先生!

高橋先生:以前ペンの色を変えるときに使った、「Dialogs」カテゴリのコンポーネントを使えば簡単だよ。色の設定のほかにファイルを保存したり、開いたりするダイアログボックスもちゃんとあるんだ。今回もコンポーネントを配置して、「Execute」メソッドを使えばダイアログボックスを表示できる。

ナッキー:じゃあ、ダイアログボックスのコンポーネントを配置するだけでいいんですね。

高橋先生:今回は、画像を扱うので「TSavePictureDialog」コンポーネントと「TOpenPictureDialog」コンポーネントを使うよ。


まずは、前回保存したファイルを開きます。Turbo Delphiを起動して、画面中央の「ホームページ」で「Drawing.bdsproj」を選択。もし一覧に表示されていなければ、ツールバーの[プロジェクトを開く(Ctrl+F11)]ボタンをクリックします。「プロジェクトを開く」ダイアログボックスから「Drawing.bdsproj」を探します。

「FormDrawing」のフォームデザイナ画面を表示します。画面右下のツールパレットの「Dialogs」カテゴリで「TSavePictureDialog」コンポーネントと「TOpenPictureDialog」コンポーネントを探して、フォームの適当な位置に配置します。非ビジュアルコンポーネントなので、フォーム上のどこに配置してもかまいません。似たコポーネントに「TSaveDialog」と「TOpenDialog」がありますから、間違えないように気をつけてね、って高橋先生が言っていました。

01アイコン

図01 TSavePictureDialogとTOpenPictureDialogのアイコン

プロパティも設定します。SavePictureDialog1コンポーネントとOpenPictureDialog1コンポーネント、どちらにも同じプロパティ設定をします。まず、デフォルトのファイル拡張子を指定します。SavePictureDialog1を選択して、画面左下のオブジェクトインスペクタ、プロパティページで「その他」カテゴリの「DefaultExt」プロパティに「bmp」と半角英字で入力します。次に表示するファイルの種類を限定します。SavePictureDialog1を選択していることを確認して、プロパティページ「ローカライズ対象」カテゴリの「Filter」プロパティの[…](ダイアログ)ボタンをクリックします。表示されるダイアログボックスには画像ファイルの種類が記述されています。そこで「ビットマップ(*.bmp)」以外の項目の「フィルタ名」欄と「フィルタ」欄の両方をを削除して[OK]ボタンをクリックします。同様にしてOpenPictureDialog1コンポーネントのプロパティも設定します。

SavePictureDialog1、OpenPictureDialog1

カテゴリ名

プロパティ名

設定値

その他

DefaultExt

bmp

ローカライズ対象

Filter

「ビットマップ(*.bmp)」以外を削除


Hide image
02フィルタの設定

図02 「フィルタの設定」ダイアログボックス

さて、これでファイルを開いたり閉じたりできるんだわ~。テストしてみよう。

高橋先生:ダイアログボックスはコンポーネントを置いただけじゃダメだったよね。「色の設定」ダイアログボックスのときと同じだ。「Execute」メソッドで、やっとダイアログボックスを開くことができるんだよ。それに、ダイアログボックスを開いただけでは、ファイルを開くことができない。ダイアログボックスは表示した後、ユーザーが入力した項目をプロパティとして持っているだけなんだ。今回のダイアログボックスでは保存する場合も、開く場合も画像のファイル名を取得するだけだ。だからそのファイル名を使って、保存したり開いたりするコードも書かないと何もできないよ。

ナッキー:「色の設定」ダイアログボックスのときは、そうだったわね。あのときも色だけがプロパティでわかったから、ペンのプロパティに代入したんだったわ。今度のファイルを扱うダイアログボックスでも、ファイル名がわかっただけなんだ。

高橋先生:そういうこと。まずは、ファイルを保存することから考えよう。[名前をつけて保存(S)...]メニューはデフォルトで「Enabled」プロパティをFalseにしてあるから、Trueにしてやらないと使えないね。ペイントボックスのpbxDrawMouseDownでマウスの左ボタンをクリックしたとき、画像を描いたとみなそう。このとき[名前をつけて保存(S)...]メニューの「Enabled」プロパティをTrueにしよう。

ナッキー:ペイントボックスの上でマウスのボタンが押されたら、画像に変化があったとみなして保存できるようにするんですね。


イベントハンドラにコードを追加します。画面をコードエディタに切り替えて、pbxDrawMouseDownイベントハンドラを探します。マウスの左ボタンが押されているかどうかのif文の中に[名前をつけて保存(S)...]メニューの「Enabled」プロパティをTrueに変更するコードを書きます。太字部分を追加します。

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);
    S1.Enabled := True;
  end;
end;

高橋先生:ファイルを保存するときは、ダブルバッファリングで使ったビットマップオブジェクトの「SaveToFile」メソッドで保存できる。パラメータはファイル名。このファイル名に、ダイアログボックスの「FileName」プロパティを使えばいいんだ。

ナッキー:え?ペイントボックスをファイルに保存するんじゃないんですか?

高橋先生:ペイントボックスは何かに隠れると、画像がなくなるんだったよね。保存のダイアログボックスに隠れるかもしれないし、それじゃあ保存しても意味がない。

ナッキー:そっか、だったら仕方ないですね。

高橋先生:タイトルバーにファイル名が表示されるともっといいね。タイトルバーは、フォームの「Caption」プロパティだ。プログラム名の横に「- ファイル名」としよう。プログラム名は「Application.Title」で取得しておくと、今後プログラム名が変わってもコードを書きなおす必要がない。それから[名前をつけて保存(S)...]メニューの「Enabled」プロパティをFalseに変更しておこう。


それでは、イベントハンドラを作成します。フォームデザイナでMainMenu1コンポーネントをダブルクリック、もしくはマウスの右ボタンクリックで[メニューデザイナ(Y)...]を選択します。メニューデザイナで、[名前をつけて保存(S)...]メニューを選択します。オブジェクトインスペクタ、イベントページで「入力」カテゴリの「OnClick」をダブルクリックします。画面にメニューデザイナが表示されていたら、[×](閉じる)ボタンで閉じておきます。このあともメニューにイベントハンドラを作成しますが、メニューデザイナが邪魔なときは閉じておいてもいいんですって。ダイアログボックスの場合は「if DialogBox.Execute then」ではじめるんだったわよね。if文の中に複数文はいるのでブロック化しておきます。太字部分を追加します。

procedure TfrmMain.S1Click(Sender: TObject);
begin
  if SavePictureDialog1.Execute then
  begin

  end;
end;

次に、ダイアログボックスの「FileName」プロパティを使ってファイルを保存します。それからタイトルバーにファイル名を表示して、[名前をつけて保存(S)...]メニューの表示を非表示にします。太字部分を追加します。

procedure TfrmMain.S1Click(Sender: TObject);
begin
  if SavePictureDialog1.Execute then
  begin
    bmpBuf.SaveToFile(SavePictureDialog1.FileName);
    Caption := Application.Title + ' - ' + SavePictureDialog1.FileName;
    S1.Enabled := False;
  end;
end;

これで、「お絵かきソフト」でファイルを保存できます。フォームを保存して、実行してみましょう。ツールバーの[すべて保存]ボタンで保存して、[実行]ボタンで実行テストします。ペイントボックスに、何か適当に絵を描きます。メニューバーの「ファイル(F)|名前をつけて保存(S)...」を選択します。適当な場所に名前をつけて保存します。

Hide image
03名前をつけて保存

図03 名前をつけて保存

ナッキー:タイトルバーに表示した文字が「お絵かきソフト」ではなくて、プロジェクト名の「Drawing」になっています。

高橋先生:「Application.Title」にしたからね。

ナッキー:だって、そうしろって先生が言ってたような気が…

高橋先生:まだ正式にプログラム名をつけていなかったんだよ。

ナッキー:正式なプログラム名って何ですか?

高橋先生: Windows画面の下のほうにタスクバーがあるよね。実行したときにタスクバーに表示される名前がプログラムの名前だ。デフォルトではプロジェクト名になっている。これを変更するにはプロジェクトのオプションを設定する。設定できれば、タスクバーにもタイトルバーと同じ全角の文字が使える。

ナッキー:設定すれば、保存したあともタイトルバーに「お絵かきソフト」と表示できるんですね。


では、メニューバーで[プロジェクト(P)]の[オプション(O)...]メニューをクリックして、「Drawing.exeのプロジェクトオプション」ダイアログボックスを表示します。左側の項目から「アプリケーション」を選択して「タイトル(T)」に「お絵かきソフト」と入力して、[OK]ボタンをクリックします。

Hide image
04プロジェクトオプション

図04 プロジェクトオプション

設定できたら、保存して実行してみましょう。ツールバーの[すべて保存]ボタンで保存して、[実行]ボタンで実行テストします。タスクバーのプログラム名を確認します。そして、ペイントボックスに、もう1度何か絵を描いて、メニューバーの「ファイル(F)|名前をつけて保存(S)...」をクリックします。タイトルバーに「お絵かきソフト - ファイル名.bmp」と表示されていることを確認します。

Hide image
05実行テスト

図05 実行テスト

    [開く]メニューを作る

保存できると、今度は開いてみたくなっちゃうわね。ファイルを開くときもダイアログボックスを使うんだろうな。気をつけることはないのかな?教えて、高橋先生!

高橋先生:次は、ファイルを開くことを考えよう。ファイルを読み込む先も、ダブルバッファリングで使った、ビットマップオブジェクトにしよう。

ナッキー:今度もペイントボックスじゃダメなの?

高橋先生:ダメじゃないけど、pbxDrawペイントボックスに読み込んだあと、ビットマップオブジェクトにも、読み込む必要があるんだ。だったら、ピットマップオブジェクトに読み込んで、再描画のpbxDrawPaintイベントハンドラを呼び出せば、ペイントボックスに描画するのだってすぐだよね。そんな時のために、ペイントボックスコンポーネントに「Repaint」メソッドが用意されているんだ。このメソッドを呼び出せば、自動的にpbxDrawPaintイベントハンドラが呼び出されるようになっているんだ。

ナッキー:ビットマップオブジェクトに画像を用意してから、ペイントボックスに画像をコピーするって感じなのね。

高橋先生:ビットマップオブジェクトにファイルを読み込む前に、ピットマップオブジェクトを空にしておいてね。それから「LoadFromFile」メソッドでファイルを読み込む。「PixelFormat」プロパティは「pf32bit」。今回は、画像のサイズを「SetSize」メソッドではなくて、「Width」プロパティと「Height」プロパティで設定してみよう。プロパティが設定できてから「Repaint」メソッドを呼び出してね。

ナッキー:そういえば、ビットマップオブジェクトに読み込むときに、いろいろ準備が必要でしたね。


いろいろ注文があったけど、ひとつひとつやっていきましょう。イベントハンドラを作成します。フォームデザイナでMainMenu1コンポーネントをダブルクリック、もしくはマウスの右ボタンクリックで[メニューデザイナ(Y)...]を選択します。メニューデザイナで、[開く(0)...]を選択します。オブジェクトインスペクタ、イベントページで「入力」カテゴリの「OnClick」をダブルクリックします。if文の中に複数文入るのでブロック化します。太字部分を追加します。

procedure TfrmMain.O1Click(Sender: TObject);
begin
  if OpenPictureDialog1.Execute then
  begin

  end;
end;

次に、bmpBufビットマップオブジェクトがあったら、メモリ解放するようにコードを書きます。太字部分を追加します。

procedure TfrmMain.O1Click(Sender: TObject);
begin
  if OpenPictureDialog1.Execute then
  begin
    if Assigned(bmpBuf) then
      FreeAndNil(bmpBuf);
  end;
end;

空にできたら、「Create」メソッドでメモリに準備して、「LoadFromFile」メソッドでファイルを読み込みます。ファイル名はダイアログボックスの「FileName」プロパティで取得できます。次に、ビットマップオブジェクトの「PixelFormat」プロパティを設定します。pbxDrawペイントボックスコンポーネントの「Width」「Height」プロパティも設定して、「Repaint」メソッドを記述します。太字部分を追加します。

procedure TfrmMain.O1Click(Sender: TObject);
begin
  if OpenPictureDialog1.Execute then
  begin
    if Assigned(bmpBuf) then
      FreeAndNil(bmpBuf);
    bmpBuf := TBitmap.Create;
    bmpBuf.LoadFromFile(OpenPictureDialog1.FileName);
    bmpBuf.PixelFormat := pf32bit;
    pbxDraw.Width  := bmpBuf.Width;
    pbxDraw.Height := bmpBuf.Height;
    pbxDraw.Repaint;
  end;
end;

高橋先生:これで問題なく動けど、今度もタイトルバーにファイル名を表示しよう。プログラム名は「Application.Title」で取得しておく。

ナッキー:今度はバッチリ「お絵かきソフト - ファイル名.bmp」と表示できますね。


コードエディタのO1Clickイベントハンドラに続きを記述します。太字部分を追加します。

procedure TfrmMain.O1Click(Sender: TObject);
begin
  if OpenPictureDialog1.Execute then
  begin
    if Assigned(bmpBuf) then
      FreeAndNil(bmpBuf);
    bmpBuf := TBitmap.Create;
    bmpBuf.LoadFromFile(OpenPictureDialog1.FileName);
    bmpBuf.PixelFormat := pf32bit;
    pbxDraw.Width  := bmpBuf.Width;
    pbxDraw.Height := bmpBuf.Height;
    pbxDraw.Repaint;
    Caption := Application.Title + ' - ' + OpenPictureDialog1.FileName;
  end;
end;

これで、ファイルを開くことができます。保存して、実行してみましょう。ツールバーの[すべて保存]ボタンで保存して、[実行]ボタンで実行テストします。実行できたら「お絵かきソフト」の[ファイル(F)|開く(O)...]メニューをクリックします。表示したダイアログボックスで、先ほど保存したファイルを開いてみます。

    [クリア]メニューを作る

もうひとつ、[クリア]メニューも作るんですよね。消すだけだから簡単かな。

高橋先生:残念ながら、そうでもないんだ。ビットマップオブジェクトもクリアしなくちゃならないからね。ビットマップオブジェクトを白で塗りつぶすイメージなんだ。

ナッキー:Clearメソッドとか簡単なのないんですかぁ?

高橋先生:がんばって、塗りつぶししてね。まず四角形を表す変数を「TRect」型で用意する。上下左右の位置をピクセル単位で設定すれば準備はOK。ビットマップオブジェクトの設定は、「Brush」プロパティに白色を設定して、「FillRect」メソッドで四角く塗りつぶす。ビットマップオブジェクトが描けたら、「Repaint」メソッドを呼び出せば、ペイントボックスが白く塗りつぶされてクリアされる。[名前をつけて保存]メニューも使えるようにしておこう。


簡単かなーと思ったんだけどな。フォームデザイナでMainMenu1コンポーネントをダブルクリック、もしくはマウスの右ボタンクリックで[メニューデザイナ(Y)...]を選択します。メニューデザイナで、[編集(E)|クリア(C)]を選択します。オブジェクトインスペクタ、イベントページで「入力」カテゴリの「OnClick」をダブルクリックします。「r」変数を「TRect」型で用意しておきます。「r」変数がペイントボックスと重なるように、「Top」と「Left」のプロパティは「0」にします。「Right」プロパティはペイントボックスの幅の「Width」プロパティ、「Bottom」プロパティはペイントボックスの高さ「Height」プロパティに合わせます。ペイントボックスのほうは「Brush」プロパティに白色を設定します。それから、四角く塗りつぶすので「FillRect」メソッドを使います。パラメータにはサイズを設定した「r」変数が必要です。太字部分を追加します。

procedure TfrmMain.C1Click(Sender: TObject);
var
  r: TRect;
begin
  r.Left   := 0;
  r.Top    := 0;
  r.Right  := bmpBuf.Width;
  r.Bottom := bmpBuf.Height;
  bmpBuf.Canvas.Brush.Color := clWhite;
  bmpBuf.Canvas.FillRect(r);
end;

ビットマップオブジェクトが完成したら、「Repaint」メソッドでビットマップオブジェクトからペイントボックスへ画像を写します。最後に、[名前をつけて保存]メニューの「Enabled」プロパティを「True」にします。太字部分を追加します。

unit FormDrawing;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, 
Forms, Dialogs, ExtCtrls, XPMan, Buttons, Menus, ComCtrls, ExtDlgs; type TfrmMain = class(TForm) sbxDraw: TScrollBox; XPManifest1: TXPManifest; Panel1: TPanel; pbxDraw: TPaintBox; sbtnHoso: TSpeedButton; sbtnFuto: TSpeedButton; shpColor: TShape; ColorDialog1: TColorDialog; stbMain: TStatusBar; MainMenu1: TMainMenu; F1: TMenuItem; O1: TMenuItem; S1: TMenuItem; X1: TMenuItem; E1: TMenuItem; C1: TMenuItem; S2: TMenuItem; H1: TMenuItem; A1: TMenuItem; OpenPictureDialog1: TOpenPictureDialog; SavePictureDialog1: TSavePictureDialog; 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); procedure X1Click(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); procedure pbxDrawMouseLeave(Sender: TObject); procedure O1Click(Sender: TObject); procedure S1Click(Sender: TObject); procedure C1Click(Sender: TObject); private { Private 宣言 } preX: Integer; preY: Integer; bmpBuf: TBitmap; public { Public 宣言 } end; var frmMain: TfrmMain; implementation {$R *.dfm} procedure TfrmMain.C1Click(Sender: TObject); var r: TRect; begin r.Left := 0; r.Top := 0; r.Right := bmpBuf.Width; r.Bottom := bmpBuf.Height; bmpBuf.Canvas.Brush.Color := clWhite; bmpBuf.Canvas.FillRect(r); pbxDraw.Repaint; S1.Enabled := True; end; procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin if MessageDlg('終了しますか?', mtConfirmation, [mbOK, mbCancel], 0) = mrOk then CanClose := True else CanClose := False; end; 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 FreeAndNil(bmpBuf); end; procedure TfrmMain.O1Click(Sender: TObject); begin if OpenPictureDialog1.Execute then begin if Assigned(bmpBuf) then FreeAndNil(bmpBuf); bmpBuf := TBitmap.Create; bmpBuf.LoadFromFile(OpenPictureDialog1.FileName); bmpBuf.PixelFormat := pf32bit; pbxDraw.Width := bmpBuf.Width; pbxDraw.Height := bmpBuf.Height; pbxDraw.Repaint; Caption := Application.Title + ' - ' + OpenPictureDialog1.FileName; 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); S1.Enabled := True; end; end; procedure TfrmMain.pbxDrawMouseLeave(Sender: TObject); begin stbMain.Panels[0].Text := ''; stbMain.Panels[1].Text := ''; end; procedure TfrmMain.pbxDrawMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); var w: Integer; c: TColor; begin stbMain.Panels[0].Text := 'X = ' + IntToStr(X); stbMain.Panels[1].Text := 'Y = ' + IntToStr(Y); 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.S1Click(Sender: TObject); begin if SavePictureDialog1.Execute then begin bmpBuf.SaveToFile(SavePictureDialog1.FileName); Caption := Application.Title + ' - ' + SavePictureDialog1.FileName; S1.Enabled := False; end; end; procedure TfrmMain.shpColorMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if ColorDialog1.Execute then shpColor.Brush.Color := ColorDialog1.Color; end; procedure TfrmMain.X1Click(Sender: TObject); begin Close; end; end.

[クリア(C)]メニューができたら、保存して実行します。ツールバーの[すべて保存]ボタンで保存して、[実行]ボタンで実行テストします。実行できたら「お絵かきソフト」の[ファイル(F)|開く(O)...]メニューをクリックして、練習で作った画像ファイルを開きます。そのまま[編集(E)|クリア(C)]をクリックして画面が白くなることを確認します。

ナッキー:ファイルを保存したり、開いたりできるようになったので、いろいろ描いてみたくなっちゃいますね。子供の描いた絵も取っておけるんだわ。

高橋先生:次は画像のサイズを変更しよう。そのとき新たなフォームを使おうと思っているんだ。

ナッキー:Dialogsのコンポーネントを使うんじゃないんですか?

高橋先生:決まりきったダイアログボックスを使うときには便利なんだけど、独自の画面を作るときには、フォームを作らなくちゃね。

ナッキー:イメージはわくんですけど、作り方まではよくわかりません。

高橋先生:テンプレートを使うからそれほど難しくないよ。

ナッキー:テンプラー??

高橋先生:…。次回で完成をめざそう!


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

Prev | Next | Index


Server Response from: ETNASC02