3流プログラマのメモ書き

元開発職→社内SE→派遣で営業支援の三流プログラマのIT技術メモ書き。 このメモが忘れっぽい自分とググってきた技術者の役に立ってくれれば幸いです。(jehupc.exblog.jpから移転中)

(.Net)Form.CancelButtonプロパティとButton.DialogResultプロパティにやられた

Form クラスに AcceptButton プロパティと CancelButton プロパティというのがあって、これに Button クラスのオブジェクトを設定すると、キーで Enter を押下したときは AcceptButton プロパティに設定したボタンが、ESC キーを押下したときは CancelButton プロパティに設定したボタンのクリックイベントが動きます。

これを使うと、設定等のダイアログウィンドウのキー操作が簡単にできると思ってやってみたところ、はまってしまったのでそのメモ。

Form.AcceptButton プロパティと Form.CancelButton プロパティにボタンオブジェクトをセットしたら、キャンセルボタン( Form.CancelButton にセットしたボタン)押下時に勝手にフォームを閉じるようになってしまったのです。

ということで、いろいろ調べてみると、@IT:モーダル・ダイアログやモードレス・ダイアログを表示するには?に答えが。。(「モーダル・ダイアログを表示するには?」の見出しの最後のほうにあります。)

ちょいと引用して見ました。

ボタンのDialogResultプロパティに、「DialogResult.Cancel」や「DialogResult.OK」などのDialogResult列挙体の値が設定されている場合、そのボタンがクリックされるとDialogオブジェクトのDialogResultプロパティに対してそのDialogResult列挙体の値が設定される。モーダル・ダイアログでは、DialogオブジェクトのDialogResultプロパティに「DialogResult.None」以外の値が設定されると、自動的にダイアログが終了する仕組みになっている。このため、ボタンの DialogResultプロパティを設定すれば、ボタンのClickイベント・ハンドラでダイアログの終了処理を実装する必要はない(ただし次に紹介するモードレス・ダイアログの場合は、Closeメソッドなどによるダイアログの終了処理が必要となる)。

 なお、DialogオブジェクトのCancelButtonプロパティを設定すると、設定されたボタンのDialogResultプロパティに「DialogResult.Cancel」が自動的に設定される(ただし、この自動設定が行われるのは、ボタンのDialogResultプロパティの値が「DialogResult.None」の場合のときだけだ)。しかし、AcceptButtonプロパティを設定した場合では、設定されたボタンの DialogResultプロパティの値は変更されないので注意してほしい。

 ちなみに、モーダル・ダイアログで、フォーム上部のタイトル・バーにある[×]ボタン(=[閉じる]ボタン)をクリックしてダイアログを閉じた場合、 DialogオブジェクトのDialogResultプロパティには「DialogResult.Cancel」が固定的に設定される。

ということで、流れを追ってみました。

まず、Form.CancelButton に Button オブジェクトを割り当てると、割り当てた Button.DialogResult プロパティに勝手に Windows.Forms.DialogResult.Cancel 値がセットされます。

(なぜかこのことは MSDN:Form.CancelButton には書いてませんでした。なお、Form.AcceptButton にボタン割り当てても Button.DialogResult は変わりません)

そして、Button.DialogResult プロパティが None 以外の値に設定されており、フォームが ShowDialog メソッドを使用して表示(モーダルウィンドウ)されている場合はフォームが閉じられるようです。実際には閉じるというより、Form.DialogResult プロパティに値がセットされ自動的にフォームを非表示にしてるだけっぽいです。(このことは、 MSDN:Button.DialogResult に書いてます。)

なので、流れ的には下記のような感じになってるっぽいです。

Form.CancelButton プロパティに Button オブジェクトセット。

 ↓

Button.DialogResult プロパティに Cancel 値が入る。

 ↓

キャンセルボタン(Form.CancelButton プロパティにセットしたボタン)をユーザがクリック(もしくはEsc)

 ↓

Button.DialogResult プロパティが設定さたたボタンのクリックイベントが走る。

 ↓

Form.DialogResult プロパティに Button.DialogResult プロパティの値がセット。

(Form.DialogResult プロパティがセットされるとフォームは非表示になり、呼び出し元に制御を戻す)

キャンセルボタン押したら、勝手にフォームが閉じたのはこういうカラクリのせいみたいです。

ちなみに、Form.DialogResult プロパティに None 以外がセットされた時フォームが閉じてますが、実際は Form.Close() メソッドを使っているわけでなく、ただフォームを非表示にしてるだけっぽいです。なので、フォームの呼び出し元で Form.Dispose() を呼んでインスタンスを破棄しないといけないようです。

ということが、 MSDN:Form.DialogResult に書かれてました。(ただ、Form.Close() が呼ばれてないにしても、FormClosing や FormClosed イベントは発生しています)

うーむ、、モーダルウィンドウの Form.DialogResult プロパティに None 以外の値入れるとフォームが非表示なること知りませんでした。

そして、Form.CancelButton プロパティ → Button.DialogResult プロパティ → Form.DialogResult プロパティの連鎖も知りませんでした。

試しに簡単なテストコードで確認してみました。

Public Class Form2

 

'''

''' コンストラクタ

'''

Public Sub New()

' この呼び出しは、Windows フォーム デザイナで必要です。

InitializeComponent()

' InitializeComponent() 呼び出しの後で初期化を追加します。

 

Console.WriteLine(vbNewLine & "①btnCancel.DialogResultプロパティ: " & Me.btnCancel.DialogResult.ToString())

 

'Form.CancelButton にキャンセルボタンオブジェクトセット

Me.CancelButton = btnCancel

 

Console.WriteLine(vbNewLine & "②Form.CancelButtonプロパティ: " & Me.CancelButton.ToString())

Console.WriteLine("③btnCancel.DialogResultプロパティ: " & Me.btnCancel.DialogResult.ToString())

 

'Form.AcceptButton にOKボタンオブジェクトセット

Me.AcceptButton = btnOK

 

Console.WriteLine(vbNewLine & "④Form.AcceptButtonプロパティ: " & Me.AcceptButton.ToString())

Console.WriteLine("⑤btnOK.DialogResultプロパティ: " & Me.btnOK.DialogResult.ToString())

End Sub

 

'''

''' OKボタン押下

'''

Private Sub btnOK_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOK.Click

Console.WriteLine("⑥OKボタン押下")

End Sub

 

'''

''' キャンセルボタン押下

'''

Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click

Console.WriteLine("⑦キャンセルボタン押下")

End Sub

 

Private Sub Form2_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing

Console.WriteLine("⑧フォーム閉じてる")

Console.WriteLine("⑨Form.DialogResultプロパティ: " & Me.DialogResult)

End Sub

 

End Class

フォームが表示し終わった時は下記のような出力になりました。

①btnCancel.DialogResultプロパティ: None

②Form.CancelButtonプロパティ: System.Windows.Forms.Button, Text: キャンセル

③btnCancel.DialogResultプロパティ: Cancel

④Form.AcceptButtonプロパティ: System.Windows.Forms.Button, Text: OK

⑤btnOK.DialogResultプロパティ: None

Form.CancelButton プロパティにボタンオブジェクトをセットすると、btnCancel.DialogResult プロパティの値が勝手に変わってますよね。

btnOK.DialogResult プロパティは変化なしです。

この状態でOKボタンを押下してみます。

⑥OKボタン押下

となるだけで、フォームは表示し続けています。

キャンセルボタンを押下すると

⑦キャンセルボタン押下

⑧フォーム閉じてる

⑨Form.DialogResultプロパティ: 2

となり、フォームが閉じられてることが分かります。

さて、こうなると Form.CancelButton で設定したボタンのクリックイベント中で、ある条件の時はフォーム非表示にし、その他の条件のときは非表示にしないということはできないのかというと、どうやら対策があるようです。

それはクリックイベント中に Form.DialogResult に Windows.Forms.DialogResult.None をセットしてやることです。

これで、フォーム非表示処理がキャンセルされるようですね。

上記のサンプルコードだとキャンセルボタンのクリックイベントに下記のようにします。

'''

''' キャンセルボタン押下

'''

Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click

Console.WriteLine("⑦キャンセルボタン押下")

Me.DialogResult = Windows.Forms.DialogResult.None

End Sub

この状態でキャンセルボタンを押下すると、

⑦キャンセルボタン押下

となり、フォーム非表示処理は行われません。

あと、モーダルウィンドウの場合、Form.Close() してもフォームオブジェクトは Dispose されないようです。(モードレスウィンドウは Dispose されます)

呼び出しもとで Form.Dispose() を読んでやる必要があることも初めて知りました。

まあ今回はフォーム関連の無知ゆえにハマった内容ですが、ただ Form.AcceptButton の時はセットしたボタンの Button.DialogResult が変わらず、Form.CancelButton のときは Cacel が入るという仕様は解せないですね。