Visual Basic 初級講座 |
Visual Basic 中学校 > 初級講座 >
第29回 2つ目のフォーム
いよいよフォームを1つしか使わないプログラムから脱出です。複数のフォームをうまく制御して高度なアプリケーションを作りましょう。今回はその第一歩です。
この回の要約 ・Form2を表示させるには次のようにする。
・ShowDialogメソッドでForm2を表示すると、Form2が閉じるまでForm1を操作できない。 ・ShowDialogメソッドの戻り値はForm2のDialoResultプロパティの値。 ・呼び出し時にOwnerプロパティをセットしておけば、Form2側ではOwnerプロパティを使ってForm1にアクセスすることができる。 ・OwnedFormsプロパティを使うとすべての子フォームにアクセスできる。 |
今まで入門講座・初級講座等ではフォームを1つだけ使うプログラムを説明してきました。市販の本や、多くのWebサイトでもほとんどの場合フォームを1つしか使わない例が紹介されています。
これは説明を簡単にするための当然の措置なのですが、実際のアプリケーションのほとんどは2つ以上の画面からできていますからフォーム1つだけで完成するプログラムというのはそれほど多くありません。
今回はまさにこの複数のフォームの使い方を中心に説明します。
さて、フォームを追加するだけなら実はとても簡単です。通常するようにVBでプロジェクトを作成する際に「Windows アプリケーション」を選択すれば、はじめからフォームが1つある空のプログラムが作成されます。このとき、ソリューションエクスプローラは次のようになっています。この例ではプロジェクト名を「FormTest」をしています。
■画像1:ソリューションエクスプローラ
ソリューションエクスプローラが表示されていない場合は[表示]メニューから[ソリューションエクスプローラ]を選択することで表示させることができます。通常は画面の右上の方に表示されています。
標準的な設定のソリューションエクスプローラでは、現在プロジェクトにどのようなファイルが含まれているかが表示されています。この画像からはFormTestプロジェクトにはAssemblyInfo.vbとForm1.vbが含まれていることがわかります。他にもいくつかの「参照設定」と呼ばれる項目も含まれています。
それぞれの項目に役割があるのですが、今回のテーマである「フォーム」に関してはすぐわかるようにForm1.vbが本体です。
ここにもう1つフォームを追加してみます。
FormTestプロジェクトの上で右クリックして、[追加] - [Windows フォームの追加]をクリックします。
■画像2:新しいフォームを追加する
そうすると新しい項目の追加画面が表示されるので新しいフォームの名前を入力して[開く]をクリックします。ここではデフォルトのまま「Form2.vb」という名前を付けることにします。
■画像3:新しい項目の追加ダイアログ
そうすると、次の画像のようにプロジェクトに追加されたForm2.vbがソリューションエクスプローラに表示されます。
■画像4:Form2が追加されたソリューションエクスプローラ
このようにフォームを追加するのは実に簡単です。この調子でいくつもフォームを追加していくことができます。
では、Form1とForm2をデザインして簡単なサンプルアプリケーションを作ってみましょう。
まず、ソリューションエクスプローラでForm1を選択して、Form1のデザイナを表示させてください。
■画像5
デザイナを表示させるには、上の画像の右側のボタンをクリックするかソリューションエクスプローラ上でForm1をダブルクリックします。
念のために言葉の説明をしておきますが、フォームのデザイナとは普段からボタンとかテキストボックスをフォームにぺたぺた貼り付けていくあのおなじみの画面のことです。フォームが1つであればデザイナを特に区別する必要がないのですが、今回はフォームが2つなのでデザイナに注意してください。Form1をデザインしているつもりが、よく見たらForm2だったということがないようにしなければなりません。
さて、デザイナが開いたらForm1にラベルとボタンを1つずつ貼り付けてください。ボタンが押されたらForm2を呼び出すようにします。
ボタンのクリックイベントに次の通り書き込んでください。ここではVB2005以降で可能な書き方と、VB.NET2002, VB.NET2003でも有効な書き方を並べて書きますが、どちらかを書けばよいのであって両方書いてはいけません。
Form2.Owner =
Me Form2.Show() |
■リスト1:Form2を表示させる
Dim
f As New Form2
f.Owner =
Me |
■リスト2:Form2を表示させる
この状態でとりあえず実行してみるとボタンをクリックすると確かにForm2が表示されるのがわかります。
しかもリスト2のようにした場合はボタンを何回もクリックするとForm2がたくさん表示されます。これは1つの型からたくさんのタイヤキができるように、1つのForm2という型からたくさんのForm2のインスタンスが作成されるからです。このプログラムでインスタンスを作成するためのNewキーワードを使用されている点を思い出してください。実際にフォームを表示させているのはShowメソッド(読み方:Show = ショウ)です。
次にForm2の側もデザインします。ソリューションエクスプローラでForm2を選択して、Form2のデザイナを表示させてください。ここにはボタンを1つだけ配置してそのClickイベントに次の通りプログラムしてください。
Dim
f As Form1 f =
Me.Owner |
■リスト3:Form1に命令する
このプログラムを実行するとForm1のラベルの色が赤くなります。
Form2の側ではOwnerプロパティ(読み方:Owner = オーナー)を見るとForm1が取得できるようになっています。これはForm2のインスタンスを作成するときに、Form2のOwnerプロパティにForm1をセットしているからです。
このようにOwnerプロパティが設定されている場合Form2はForm1と運命をともにします。たとえば、Form1が閉じられればForm2も閉じられますし、Form1が最小化されればForm2も最小化されます。このように連動している状態が気に入らなければOwnerプロパティに何も設定しないでおくことになります。しかし、そうした場合はOwnerプロパティを使用して呼び出しもとのフォームを取得することができませんので2つのフォームを連携させるために別の仕組みが必要になります。
2つのフォームがそれぞれ無関係に動作するのであればフォームの連携について何も注意すべきことはありませんが、たいていの場合は何らかの形で2つのフォームを連携させて動作させることになります。
たとえば、Form2が設定画面であれば、そこで設定した内容をForm1に伝える必要がありますし、Form2がファイルを表示する画面であれば、Form1はどのファイルをどのように表示するのかForm2に伝えなければなりません。このようなケースは無数にあります。
2つのフォームを連携させるにはまず、お互いがお互いの存在を知っていなければなりません。
たとえば、Form1側で次のようにForm2を表示させた場合どうなるでしょうか?
Dim
f As New Form2 f.Show() |
■リスト4
この場合Form2は確かに表示されますが、Form2の側ではいったい自分がどこから表示されたのかわかりません。Form1の側では変数fを通じてForm2にアクセスすることができますが、変数fが適用範囲から離れたとたんにForm2にアクセスする通常の手段はなくなります。
そこで、最初に説明したようにOwnerプロパティを使うことになります。
Dim
f As New Form2
f.Owner =
Me |
■リスト5
Ownerプロパティをこのようにセットしておけば、Form2の側では自分のOwnerプロパティを使って呼び出しもとのフォーム、つまりForm1にアクセスすることができますし、Form1の側でも変数fが適用範囲から離れてもOwnedFormsプロパティ(読み方:OwnedForms = オウンドフォームス)を使って、Form1が所有しているすべてのフォームにアクセスすることができます。OwnedFormsプロパティはFormの配列です。
ただし、Ownerプロパティを設定すると先ほど説明したように2つのフォームは自動的に連動するようになります。これが困る場合は独自の仕組みでフォームを連携させることになります。次にその例を紹介します。
Form2の宣言部に次の1行を追加してください。
Public Opener As Form |
■リスト6:Form2に記述する。
念のためにこの1行をどこに追加するのかForm2のプログラムの全体を掲げておきます。ここでは、VB.NET2002、VB.NET2003の例を挙げますが、VB2005の場合も「Windows フォーム デザイナで生成されたコード」の部分がないだけで他は同じです。
Public
Class Form2 Inherits System.Windows.Forms.Form
Public Opener As Form |
|
Private Sub Button1_Click(ByVal
sender As System.Object,
ByVal e As System.EventArgs)
Handles Button1.Click Dim f As Form1 f =
Me.Opener End Sub End Class |
■リスト7
変数を宣言したらForm1の側でForm2を呼び出す部分を次のように変更することで、Form2の側では変数Openerを使ってForm1にアクセスすることができるようになります。
Dim
f As New Form2
f.Opener =
Me |
■リスト8:Form1に記述する
ただし、こうするとForm1の側からはForm2にアクセスする手段は変数fだけとなってしまいますので、変数fが適用範囲から離れないようにするなど対策を講じる必要があります。
また、変数OpenerがDimではなくPublic(読み方:Public = パブリック)で宣言されている点にも注意してください。Publicで宣言された変数はこの例のように他のフォームや他のクラスからも参照することができます。もし、Dimで宣言していたならば他のフォームからは参照することができないのでこの変数を使ってフォームを連携させることはできません。
2つのフォームがお互いにアクセスできるようになっていれば基本的な連携はそう難しくありません。前の例で書いたようにForm2のボタンをクリックしてForm1のラベルの色を変えることもできますし、お互いのプロパティやメソッド・変数を呼び出すこともできます。
さて、今度は別の観点から2つのフォームの関係を眺めて見ます。Form1からForm2を表示させたときにForm2を閉じるまではForm1を操作できないようにしたい場合があります。
たとえば、メモ帳やExcelでは保存するファイル名を選択する画面を表示している間は文書の内容を変更できません。このように画面を閉じるまで親フォームが操作できなくなるような子フォームのことをモーダルフォーム、モーダルウィンドウなどと呼びます。(モーダルでない状態をモードレスと呼びます。)
つまり、Form2がモーダルであればForm2を閉じるまでForm1を操作できないということです。Form2をモーダルで表示させるのは実に簡単で、ShowメソッドではなくShowDialogメソッド(読み方:ShowDialog = ショウダイアログ)を使うだけです。
Dim
f As New Form2 f.ShowDialog() |
■リスト9:Form1に記述する。
ShowDialogメソッドは引数を指定することもできて、引数に指定したフォームが自動的にOwnerプロパティにセットされます。
Dim
f As New Form2 f.ShowDialog(Me) |
■リスト10:Form1に記述する。
もちろんVB2005の場合は次のように簡略に書くこともできます。
Form2.ShowDialog(Me) |
■リスト11:Form1に記述する。
モーダルウィンドウはファイルの保存や設定の変更・確認などの作業に使われることが多いために呼び出し元フォームと連携するために特別な仕組みを持っています。
この仕組みとはShowDialogメソッドとDialogResultプロパティ(読み方:DialogResult = ダイアログリザルト)を組み合わせて使う方法のことです。
この仕組みを使うとForm2で、ユーザーは[OK]したのか、[キャンセル]したのかなどの情報を受け取ることができます。突然[OK]や[キャンセル]などと言われてもなんのことかと思うかもしれませんが、多くのソフトのダイアログ画面には[OK]とか[キャンセル]とか言うボタンがついていることを思い出してください。多くのソフトが使っている機能なので特別にそういう仕組みが用意されているのです。ですから、もしあなたのプログラムでは[OK]ボタンや[キャンセル]ボタンまたはそれに類する機能などないと言うのであればこの仕組みは必要ないかもしれません。
ともあれ、ここは1つ試しにこの機能を使ってみましょう。次のようにForm2をデザインしてください。
■画像6
[OK]ボタンにはbtnOK、[キャンセル]ボタンにはbtnCancelという名前をつけます。
さて、ここでForm2でテキストボックスに入力した文字がForm1のタイトルに表示されるようにします。ただし、Form2で[キャンセル]ボタンがクリックされたときはForm1のタイトルは変更されません。
Form1側のプログラムは次のようになります。
Dim
f As New Form2
If f.ShowDialog(Me)
= DialogResult.OK Then |
■リスト12:Form1に記述する。
Form2側のプログラムは次のようになります。
Private
Sub btnOK_Click(ByVal
sender As System.Object,
ByVal e As System.EventArgs)
Handles btnOK.Click
Me.DialogResult
= DialogResult.OK End Sub |
Private Sub
btnCancel_Click(ByVal sender
As System.Object, ByVal
e As System.EventArgs)
Handles btnCancel.Click
Me.DialogResult
= DialogResult.Cancel End Sub |
■リスト13:Form2に記述する。
Form2側で[OK]ボタンや[キャンセル]ボタンがクリックされたときにDialogResultプロパティにOKだとかCancelだとかの情報をセットしているのがポイントです。
このDialogResultプロパティにセットされた値をForm1側ではShowDialogメソッドの戻り値として受け取ることができます。
Form1側ではForm2が閉じられるまでShowDialogメソッドの行でプログラムの実行が停止し、次の行に進むことはありません。
このようにしてかなりシンプルに2つのフォームを連携させることができましたね。VB6ではこんなにシンプルにはできません。
ところが実のところ、上述のようなコードはそもそも書く必要すらないのです。上述のような書き方はプログラムを見れば何をやっているかわかるようになっているので私の好みなのですが、本当に[OK]ボタンと[キャンセル]ボタンを単純に使うだけならばすべてをプロパティの設定だけで済ますこともできます。
Form2側のプログラム(btnOK_ClickとbtnCancel_Click)を削除して、代わりにプロパティウィンドウでbtnOKのDialogResultプロパティをOKに、btnCancelのDialogResultプロパティをCancelにしてみてください。なんとこれだけで先ほどと同じ動作をさせることができるのです。ボタンをクリックしたらフォームを閉じるところまで自動でやってくれます。
ここまで自動化されたものをはじめから紹介してしまうと応用が利かなくなってしまうと思い後回しにしたわけですが、実際にどのような方法でプログラムするかは状況と好み次第です。
次に2つ目のフォームを呼び出すときのイベントの発生順序を確認しておきます。イベントの発生の仕方がわからないと正しいタイミングでプログラムを実行することができませんから、こういったことは常に重要です。
細かいイベントの発生順序は後で紹介することにして、まずはイベントの活用方法を先にまとめます。
■図1
Newした時点ではForm2のNewが実行されるのみでイベントは発生しません。そこでフォームを表示する前に変数をセットするなどの作業が必要な場合はNewからShowまたはShowDialogの間に処理を記述するか、Newプロシージャ(つまりコンストラクタ)に処理を記述することになります。
ShowメソッドまたはShowDialogメソッドを実行してから実際に表示されるまでの間に発生する最も代表的なイベントはLoadです。表示直前に処理が必要であればLoadイベントに記述します。Activateイベントも発生しますがこのイベントは2つのフォームが表示されているときに片方のフォームがアクティブになったときに発生するのでShowまたはShowDialogのとき以外でも発生します。Activateイベントにはアクティブになるときに必要な処理を記述します。
VB2005では表示直後にShownイベント(読み方:Shown = ショウン)も発生します。
閉じるときの処理は通常通りなのでフォームが2つあるからといって特に注意することはありません。Form1側でForm2が閉じられたことを直接知るイベントはありません。
Form1側でForm2が閉じられたことを知りたい場合は、Form2のClosedイベントからForm1へ何かの方法で通知することになります。
以下に詳細なイベントの発生順序をまとめておきます。青い字で書いてある部分はVB2005でのみ発生するイベントです。
ShowDialogメソッド使用時 | Showメソッド使用時 | 説明 |
Form1で Dim f As New Form2 を実行 | Form1で Dim f As New Form2 を実行 | |
Form2のNew | Form2のNew | Form2のコンストラクタ実行 |
Form1で f.ShowDialog を実行 | Form1で f.Show を実行 | |
Form2.HandleCreated | Form2.HandleCreated | Form2のハンドル生成 |
Form2.Load | Form2.Load | |
Form2.VisibleChanged | Form2.VisibleChanged | Form2を表示しようとする。 |
Form1.Deactivate | Form1.Deactivate | Form1がアクティブでなくなる。 |
Form2.Activated | Form2.Activated | Form2がアクティブになる。 |
Form2.Shown | Form2.Shown | Form2が表示される。 |
Form2で Me.Close を実行 | Form2で Me.Close を実行 | |
Form2.Closing | Form2.Closing | Form2を閉じようとする。 |
Form2.FormClosing | Form2.FormClosing | |
Form2.Closed | Form2.Closed | Form2を閉じ終わる。 |
Form2.FormClosed | Form2.FormClosed | |
Form2.Deactivate | Form2.Deactivate | Form2がアクティブでなくなる。 |
Form1.Activate | Form1.Activate | Form1がアクティブになる。 |
Form2.VisibleChanged | Form2が非表示になる。 | |
Form2.HandleDestroyed | Form2.HandleDestroyed | Form2のハンドルを破棄。 |
Form2.Disposed | Form2を破棄 |
■表1
参考 Visibleプロパティ 実はShowメソッドを使わなくてもForm2.Visible = TrueとするとShowメソッドと同等の効果があります。しかし、将来Form2の側がShowメソッドをオーバーライド(※1)するかもしれないのでこの方法はあまりお勧めできません。 また、ShowDialogメソッドでForm2を表示しているときに、Form2のVisibleプロパティをFalseにするとForm2を閉じた場合と似たような効果があります。しかしイベントの発生の仕方が微妙に変わってしまいますのでやはりお勧めできません。 ※1 オーバーライド:メソッドの効果を上書きしてしまう技術。初級講座ではまだ登場していません。 |
最後に2つ以上のフォームを使用する場合のその他の気になる事柄について少し触れておきます。
まず、プログラムが開始するフォームですが、既定では最初からあるフォーム(Form1)からプログラムが開始します。しかし、この設定はいくらでも変えることができます。はじめのフォームを変更するには[プロジェクト]メニューで[プロパティー]を選択し、「スタートアップの設定」でプロジェクトを開始するフォームを選択することができます。
この選択肢の中には「Sub Main」というフォームではない場所からプログラムを開始するというものもあります。Sub Mainについては別の機会に説明します。
スタートアップに指定されているフォームはただそこからプログラムが開始するというだけではなくプログラム全体を司る特別な役目があります。というのもこのスタートアップフォームが閉じられると連動してすべてのフォームが閉じられプログラム全体が終了するようになっているのです。
次に、たくさんのフォームが表示されている場合にFormクラスのActiveFormプロパティ(読み方:ActiveForm = アクティブフォーム)を使うと現在アクティブなフォームを取得できるという点も覚えておくと良いと思います。初級講座の現段階ではあまり使い道はありませんが将来複数のフォームやクラスを使いこなすようになると出番があります。次のコードはアクティブなフォームのタイトルバーに現在の時刻を表示します。
Private
Sub Timer1_Tick(ByVal
sender As System.Object,
ByVal e As System.EventArgs)
Handles Timer1.Tick Form.ActiveForm.Text = Now.ToString("hh:mm:ss") End Sub |
■リスト14
それから、すべての子フォームに対して命令する方法を紹介しておきます。前にも説明したようにOwnedFormsプロパティは子フォームの配列です。このプロパティとループを利用してすべての子フォームに対して命令を行うことができます。
ここでいう「子フォーム」とはそのOwnerプロパティの親フォームが設定されているものを指しています。
次のコードではすべての子フォームを閉じます。
Dim
f As Form
For Each f
In Me.OwnedForms |
■リスト15