procedure TForm1.FileSave1Click(Sender: TObject); begin Memo1.Lines.SaveToFile(MyFilename); //編集中のファイル名で保存 end;
ファイルを保存するにはSaveToFileメソッドを使う。 SaveToFileメソッドは、Memoコンポーネントの各行を引数MyFilenameで指定したファイル名で保存する。 ここで、MyFilenameは、既にimplementation部で宣言されたグローバル変数で、現在編集中のファイル名、例えば、"example.txt"が格納されている。
この〔上書き保存(S)〕メニューは、編集中のメモ帳にファイル名が付けられている場合に編集途中に上書き保存することを想定している。
しかし、新規ファイルを編集中に、既定値のファイル名「新しいメモ1」のときにも呼び出されるかもしれない。これはimplementation部で定数NewFilenameに与えたものである。
このような場合は、一度ファイルに〔名前を付けて保存(A)〕を実行して、新規ファイルのファイル名をユーザに入力させる指示を出すのが親切であろう。
ファイルの〔上書き保存(S)〕メニューのイベントハンドラを次のように改良しよう。
procedure TForm1.FileSave1Click(Sender: TObject); begin if MyFilename = NewFilename then //新規ファイルなら FileSaveAs1Click(Sender) //手続き〔名前を付けて保存〕を呼び出す else //そうでなければ Memo1.Lines.SaveToFile(MyFilename); //編集中のファイル名で保存 end;
それではFileSaveAs1(...)を定義する。ファイルに名前を付けて保存するプログラムのコード は、最初にSaveダイアログボックスを呼び出して、ユーザにファイル名を入力してもらい、Executeの値を調べ、入力があればそれをファイル名として取得する。 そして、上書き保存を呼び出して、そのファイル名で保存すればよい。 したがって、次のような6行のコードを書く。
procedure TForm1.FileSaveAs1Click(Sender: TObject); begin if SaveDialog1.Execute then begin MyFilename := SaveDialog1.Filename; //ファイル名を取得 Caption := MyCaption + MyFilename; //タイトルバーに表示 FileSave1Click(Sender) // 手続き〔上書き保存(S)〕を呼び出し、そのファイル名で保存 end; end;
![]() Fig.1 確認 |
![]() Fig.2 Save Dialog |
procedure TForm1.FileSaveAs1Click(Sender: TObject); begin SaveDialog1.Filename := MyFilename; // 開いているファイル名を代入 SaveDialog1.Options := [ofOverWritePrompt]; // 上書き保存の確認の設定 if SaveDialog1.Execute then // 保存するファイル名が入力されたなら begin MyFilename := SaveDialog1.Filename; // 保存するファイル名を取得 Caption := MyCaption + MyFilename; FileSave1Click(Sender); // 上書き保存の手続きを呼び出す end; end;
ファイルの〔上書き保存(A)〕の手続き FileSave1Click (Sender: TObject) は,その中からファイルに〔名前を付けて保存(S)〕する手続き FileSaveAs1Click (Sender: TObject) を呼び出して書かれている。 一方,手続き FileSaveAs1Click (...) の中でも 手続き FileSave1Click (...) を呼び出している。
この2つのイベントハンドラは互いに相手を呼び出し合っている,このような呼び出しを「再帰的呼び出し」または「再帰的手続き (recurcive procedure)」という。
上の例は自分自身を間接的に呼び出しているが,自分自身を直接に呼び出すことも可能である。
ある対象に対して何らかの処理を行ないたいとき,より簡単な場合に同じ処理を行なった結果を再利用して全体に対する処理を構成できる場合には,再帰的手続きを用いてわかりやすい自然なプログラムを書くことができる。
例題: 自分自身を直接に呼び出す再帰的関数を使って、正数の総和「1+2+3+.....+n」を計算する。 |
まず、フォームには、編集箱Edit1、ボタンButton1, Button2を配置する。Button1を押すとEdit1の文字を消去し、Button2を押すとEdit1に入力された正数(Nとする)を取得して、1からNまでの正数の総和「1+2+3+.....+n」を計算して結果をEdit1に表示するプロジェクトを設計したい。このプログラムコードはその一部である。
function Tform1.SumIntX(var X: integer): integer; // 関数 SumIntXの定義 var Ans,Y: integer; begin if X > 0 then begin Y := X-1; Ans := SumIntX(Y) + X; // X-1 までの和 SumIntX( X-1 ) に X を加えて, 1+...+X を求めている end else Ans := 0; // X = 0 の場合の答え SumIntX := Ans; end; procedure TForm1.Button2Click(Sender: TObject); // Button2のイベントハンドラの定義 var X, Ans: integer; begin X := StrToInt(Edit1.Text); Ans := SumIntX(X); // 関数 SumIntX の呼び出し Edit1.Text := IntToStr(Ans) end;
参考のために、これまでの段階で作成された「簡易版メモ帳」のプログラムを公開する。
DelphiのPrinterオブジェクトには,プリンタの殆どの動作がカプセル化されているので,これを使うと印刷が間単にできる。
Delphiでは,プリンタをテキストファイルの標準出力デバイスとして割り当てることができる。これにより,テキストファイルに書き込むのと同じように,プリンタに文字列を書き込むことができる。
プリンタをテキストファイルデバイスとして使う手順は次の通り:
実際に、メモ帳のテキストを印刷するメニューのプログラムのコードを次の手順で書いてみよう。
procedure TForm1.FilePrint1Click(Sender: TObject); var Line: Integer; {行カウンタ変数用} PrintText: TextFile; {テキストファイル変数} begin if PrintDialog1.Execute then {印刷OKボタンが選択されたなら} begin AssignPrn(PrintText); Rewrite(PrintText); Printer.Canvas.Font := Memo1.Font; try {1行ずつ印刷する} for Line := 0 to Memo1.Lines.Count -1 do WriteLn(PrintText, Memo1.Lines[Line]); finally {プリンタの解放} CloseFile(PrintText); end; end; end;
ファイル書き込みのコードを包み込む形で try ... ... finally ブロック (p.269) を使っていることに注意して欲しい。 try ... ... finally ブロックは作成したオブジェクトを必ず解放するためのコードである。 このブロックの重要な点は,プロテクトブロックで例外が発生してもfinally部にあるすべての文は必ず実行されることである。例外が発生しない場合は通常の順序で,つまりtry部の後で実行される。 したがって,ここではtry ... ... finally ブロックを使っているために,プリンタへの書き込みで例外エラーが発生してもテキストファイル(の印刷)は必ず閉じるようにしてある。
【注意】 コード記述のエラーが原因で実行時に印刷処理に異常が発生した時には,メモ帳の実行を終了させること,もし〔終了(X)〕メニューで終了できない時は,メモ帳の実行を強制終了させて下さい。Windows95で実行中のアプリケーションを強制終了させるには,〔Ctrl〕,〔Alt〕,〔Delete〕,のキーボードを同時に押して,開いたメニューから実行中の簡易版「メモ帳」を選択すればよい。
多くの応用プログラムでは、終了時に特別な処理が必要となる。
例えば、いま作成している簡易版「メモ帳」では、編集中のテキストが変更を受けていれば「保存するかどうか?」の確認をしてから終了すべきである。
MainMenuコンポーネントの〔終了(X)〕メニューに対するイベントハンドラとして,既に次のようなプログラムコードを書いた:
procedure TForm1.Exit1Click(Sender: TObject); begin Close; end;次に,テキストの変更があれば必ずこの保存確認をしてから終了するように〔終了(X)〕処理を改良する。
DelphiのMemoコンポーネントには,テキストの変更を自動的に処理する機能としてModifiedプロパティが用意されている。 Modifiedプロパティは値として論理値を返す。Modifiedプロパティは最初にMemoコンポーネントが作成されると"False"に設定される。その後はいつでもテキストが変更されると"True"に設定される。
Windowsの応用プログラムを終了させるには、〔終了(X)〕メニューの選択、右上角の"×"印を選択、左上角のアイコン(システムメニュー)から〔閉じる(C)〕を選択、など複数の手段がある。これらに1つ1つ対応する終了処理を書くのは大変である。そこで上の〔終了(X)〕メニューに対するプログラムはそのままにしておき、汎用的な終了処理の方法を捜そう。
OnCloseQueryイベント:
Delphiでは、OnCloseQueryイベントを捕らえて終了処理を記述できる。
OnCloseQuery イベントはフォームを閉じるアクションが起きるとき (Close メソッドが呼び出されたとき,またはユーザーがフォームのシステムメニューで[閉じる(C)]を選択したとき) に発生する。
CanClose パラメータの値によって,フォームを閉じることが可能かどうかが決まる。
CanCloseの値 | 意味(OnCloseQueryの働き) |
---|---|
True (デフォルト値) | フォームを閉じる、フォームが1つしかなければ応用プログラムの実行を終了する |
False | フォームを閉じることを中止する |
rocedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin if Memo1.Modified then //テキストが変更されていれば case MessageDlg( Msg, mtWarning, mbYesNoCancel, 0) of mrYes: FileSave1Click(Sender); mrCancel: CanClose := False; end; end;この意味は、メモ帳のテキストが変更されていれば'MessageDgl'関数 (p.177) によるボックスを開く。そして、確認のため警告'mtWarning'とメッセージ'Msg'を表示し、〔はい(Y)〕ボタンが選択されたら'FileSave1Click手続き'を呼び出してファイルを〔上書き保存(A)〕する。〔Cancel〕ボタンが選ばれたら閉じる状態を表す変数引数'CanClose'の値をFalseにして中止し、〔いいえ(N)〕ボタンが選ばれたら何もせずにフォーム1を閉じる。
implementation {$R *.DFM} uses Printers; //プリンタを使う(IX節で説明した) const NewFilename = '新しいメモ1'; // 新規ファイルのファイル名 MyCaption = 'メモ帳:'; // タイトルバーに付けるアプリケーション名 Msg = 'ファイルが変更されています' + Chr($d) + Chr($a) + '保存しますか'; var MyFilename: String; // 編集中のファイル名