ウィザード形式のダイアログを作る


Delphi2になって、PageControlというコントロールが使えるようになりました。これを使ってウィザード形式のダイアログを作り、オブジェクトリポジトリに登録するまでを書いてみます。PageControlの使い方なんかも分かると思います。
ウィザード形式ダイアログのフレームワークを作る大まかな手順を書いてみると以下のような感じになります。
  1. フォームを新規作成して、PageControlと、Buttonを貼り付ける
  2. PageControlにTabSheetを追加する
  3. ページ制御ルーチンを実装する
実行時にはTabを表示しない事により、順にパネルの内容が変わっていくよう[な|に見える]ウィザードダイアログを作る事が出来ます。 ここではオブジェクトリポジトリに追加することを考えて、プロパティをフォーム作成時(OnCreate)に決定するような実装のしかたを考えてみます。

フォームのデザイン

  1. IDEのファイルメニューから「フォームの新規作成」を選びます。そして、Win95タブからPageControlを選んで貼り付けます。次にボタン(BitButton。理由はコラムを参照)を3つ右下のほうに横にならべて貼り付けます。この時、PageControlの上ではなく、フォームの上に直接張りつけるようにします。こんな感じになっているはずです。
    <−大きな画像が見たければクリック

    すべてのページでボタンが表示されるように...
    ここで、貼り付けた順番がPageControl、 ボタンと言うようになっていることに注意してください。要するに
    ・PageControlを先に貼り付ける
    ・Buttonは後から、直接フォーム上に配置する
    という点に注意してください。コントロール同士が重なった場合、後から貼り付けたコントロールの方が手前に表示される(Zオーダーが小さい)のです。ところが貼りつけを行った場所がコンテナコンポーネント(PanelやPageControlなど)の場合には貼り付けたコンポーネント(今の場合Button)のParentプロパティがそのコンテナ(TabSheet)に設定されてしまいます。通常はこれでなんの問題ないのですが、今の場合はボタン類はすべてのページで表示したいので、これではまずいわけです。ButtonのParentがTabSheetであるという事は、別のTabSheetに切り替われば当然表示されなくなるわけですね。というわけで、ページ(TabSheet)毎にボタンを貼り付けなくてはならなくなってしまいます
    もっとも、PageControlのAlignをAlTopなんかにしちゃって、このような感じのデザイン(コンテナの外にボタンがある)にすればそんな事考えなくてもいいのですが、私はこれから作ろうとするデザインの方が好きなので...(^^)。細かい事ですけど。
    あと、以上の事はWindowHandleを持っているコントロールにのみ当てはまる事です。ラベルや、ベベルなど、ウィンドウHandleを持っていないコントロールに関しては、一つ一つ貼りつけるか、継承してウィンドウHandleを持たせるようにすれば同様に出来ると思います。


  2. ボタンのCaptionをBitBtn1〜3の順番に"< 戻る(&R)"、"進む(&N) >"、"キャンセル" と設定します。また、BitBtn3のKindにはbkCancelを設定します。するとビットマップが勝手に設定されますので、これを削除しておきます。そして、PageControlのAlignプロパティをalClientに設定します。最後に、適当にTabSheetを追加していってくだい。PageControlを右クリックして、「ページ新規作成」メニューを選べば追加されます。

これで準備は出来ました。フォームの左側の部分に、グラフィック表示用にImageコンポを貼り付けておいてもいいかもしれません。次に最低限必要なイベントハンドラを記述していきます。


イベントハンドラの記述

  1. フォームのOnCreateイベントハンドラ
    TabSheetのTabは隠します。最初に表示されるページを設定、そのページでは「戻る」ボタンは非表示。これらを実行時に設定するようにします。こうする事で、フォーム設計時にはタブを表示したまま、どんどんTabSheetを追加しながらデザインできます。後、ボタンのCaptionなどはフォーム内の定数として宣言してもいいかもしれませんね。
    procedure TForm1.FormCreate(Sender: TObject);
    var
      i:integer;
    begin
      with PageControl1 do begin
        for i := 0 to PageCount - 1 do
          Pages[i].TabVisible := false;
        ActivePage := Pages[0];
        BitBtn1.visible := false;
      end;
    end;
    
    PageCountの説明はどこ?
    TPageControlをヘルプで引いて、プロパティを見てみると上記のPageCountプロパティのところが何も書いてありません(^^)。しかもこれが主なメソッドって、鍵マークがついてる(・・)。また、Pagesプロパティを表示して、参照からPageCountを引いてみるとなんとFindNextPageメソッドの説明に飛んでしまいます。うーむ...。というわけで、直接キーワード検索でPageCountを引いてください。

  2. ページが変わったときのイベントハンドラを追加する
    これは独自のメソッドを追加します(コラム参照)。フォームのPrivateメソッドとして以下のようなイベントハンドラを記述します。最初のページには「戻る」ボタンは必要ありませんし、最後のページには「次へ」ボタンは必要ありません。その代わり、設定を確定するための「確定」ボタンがいりますね。という事で、「次へ」ボタンにその役目を担ってもらいましょう(^^)。こんなコーディングは嫌いって言うのであればBitBtn2と同じ位置に、もう一つボタンを貼り付けて最後のページ以外ではVisible :=falseにしておけばいいです。最後のページではBitBtn2とVisibleプロパティを入れ替えます。
    procedure TForm1.CheckPageNo;
    begin
      with PageControl1 do begin
        if ActivePage.PageIndex = 0 then BitBtn1.visible := false
        else BitBtn1.visible := true;
        with BitBtn2 do
          if ActivePage.PageIndex  = PageCount - 1 then begin
            Kind := bkOK;
            Glyph := NIL;
            Caption := '確定';
          end
          else begin
            Kind := bkCustom;
            ModalResult := mrNone;
            Caption := '次へ(&N) >';
          end;
      end;
    end;
    
    PageControlのOnChangeイベントハンドラではなぜだめなのか?
    PageControlのOnChangeイベントはマウスでタブをクリックすれば発生しますが、プログラムの中で、ActivePageプロパティを変更しても発生しません(T_T)。というわけで、全部のタブをVisible:=falseに設定しているこのようなプログラムの中では独自にイベントハンドラを定義してやればOKです。この場合は、ボタンが押されたときにページが変更になるので、次のようにボタンのOnClickイベントの最後(ActivePageを切り替えた後)に呼び出してやればいいですね。
    BitButtonを使った理由
    上記のようにKindプロパティを変えてやる事によって、フォームをクローズするときの余分なコードを省く事が出来ます。これがBitButtonを使った理由です。ビットマップを表示させる必要がなくても、こんな風に使えるBitButtonは重宝します(^^)。
    KindプロパティとModalResultプロパティ(97/4/23追加)
    キャンセルボタンをいじるとき、いったん Kind:=bkCancel にした後、Glyph を無し(NIL)にした場合、Kind プロパティは bkCustom に戻ってしまいます。しかし、ModalResult は mrCancel のままなので、このままキャンセルボタンとして機能します。つまり、いったん ModalResult が mrNone 以外に設定されると、その後 Kind:=bkCustom に設定されても直前のModalResultの値は変わりません。これはVCLのソースの中で、Kind=bkCustom の時には何もせずに kind プロパティのアクセスメソッドから抜けるように実装されてるからです。また、ModalResultプロパティは、ヘルプでは実行時のみのプロパティとなっていますが、Pulishedとして宣言されていますから、オブジェクトインスペクタでいじる事が出来てしまいます(^^)。これは単なる内部変数への代入になっています。

  3. ボタンのOnClickイベントハンドラ
    ボタンのクリックによって、アクティブページの制御をするためのルーチンです。BitBtn2の方は最後のページでは「確定」ボタンとして使う事にするので、処理ルーチンの雛形を作っておく。
    //戻るボタン
    procedure TForm1.BitBtn1Click(Sender: TObject);
    begin
      with PageControl1 do
      	ActivePage := FindNextPage(ActivePage,false,false);
      CheckPageNo;
    end;
    
    //次へボタン
    procedure TForm1.BitBtn2Click(Sender: TObject);
    begin
      with PageControl1 do
        ActivePage := FindNextPage(ActivePage,true,false);
        //確定したときの処理
        if BitBtn2.Caption = '確定' then begin
          
        end;
      CheckPageNo;
    end;
    
    SelectNextPageとFindNextPageメソッド
    PageControlのこの2つのメソッドはFindNextPageはTabが非表示のページへ移動できるのに対して、SelectNextPageの方はTabが表示されているものにしか移動できません。ヘルプには個々のメソッドについては正しく書いてありますが、ActivePageプロパティの説明にはウソが書いてあります。これを見て、私は最初コーディング量がちょっと少なくて済むので、SelectNextPageを使ってしまい悩んでしまいました(^^)。

オブジェクトリポジトリを使う

  1. リポジトリに追加する
    1. FormのNameプロパティにWizardFormとでもつけます。
    2. 適当な場所に保存します(コラム参照)。
    3. フォームを右クリックして出てくるメニューから、「リポジトリに追加」を選びます。
    4. 適当に名前や説明、どこに分類するか、作者名を決めて、OKボタンを押します。
    これでもう何度でもすぐに使えるテンプレートが出来てしまいました。あっという間ですね(^^)。
    リポジトリや自作のVCLソースの保存場所
    これって、皆さんはどうしておられるのでしょうね(Delphi-MLに流してみるかな?)。私は、デフォルトで出来ている'Delphi2.0'と言うフォルダの下にLib.gen、ObjRepos.genと言うフォルダを作って、VCLは前者、リポジトリの分は後者に全て入れています。あまりたくさんフォルダを作りすぎると、Delphiの検索パスが長くなりすぎて、新たにVCLをインストールしたいときなどにエラーが出ますので注意が必要ですね(これも結構FAQネタ)。

  2. 追加したWizardFormを使う
    プロジェクトにこのフォームを追加したくなったら、「ファイル」-「新規作成」で表示されるダイアログから先ほど登録したWizardFormを選びます。「コピー」となっているところをチェックしておいてOKボタンを押せばプロジェクトに追加されますので、後は各TabSheetをデザインしていって下さい。

今回作ったWizard.Pasをダウンロード
改変履歴
97/4/24 KindプロパティとModalResultプロパティに関するコラム増設
BitBtnを貼り付けているのに、nameがButtonになっていたのを修正しておきました。戸惑ってしまっていた方ごめんなさい。
その他ちょろちょろ(^^)と文面修正

このページの感想、改良提案、バグ報告などありましたら、げんまで。一応私のマシンで実際に走っているコードを載せていますが、このページにある内容を実行する時は各自の責任の下で行ってください。


Last Modified 97/04/24(木) 01:59:35
[Top Page] [Delphiり〜んくす !!] [Gen's Delphi FAQ & Tips]