Windows アプリケーションで画面遷移をしてみよう(その2)

掲示板などでよく方法が聞かれる、インストールウィザードの様な画面遷移を行います。
一言でいうと、紙芝居のような感じです。
紙芝居は、おじさんが持っている枠は物語の始まりから終わりまで変わりませんが、
途中途中で中の紙が変わりますよね。
枠 = Form、紙 = UserControl として、考えるとわかり易いと思います。

ここでは、詳細は説明は省きますが、コードを読んでいただくなり、
サンプルをダウンロードしていただくなりすれば、どのような事をやっているかお解かりになるかと思います。
※解説に用いた Visual Studio 2005 の Edition は、Team System Edition です。
※Windows Form デザイナで生成されたコードについては省略しています。
サンプルのダウンロード

このカテゴリーの先頭へ このページの先頭へ

■実行イメージ

プログラムが開始されると、最初に以下の画面が出現します。
winFormChange02_001
[お名前]を入力して、タブキーで Focus 移動すると、[次へ]ボタンが有効になります。

次に、居住地域の選択画面が現れます。
ComboBox より値を選択して、タブキーで Focus 移動すると、[次へ]ボタンが有効になります。
winFormChange02_002

次に、メールアドレスの入力画面が現れます。
[メールアドレス]を入力して、タブキーで Focus 移動すると、[次へ]ボタンが有効になります。
※メールアドレスっぽい文字列でないと、エラーメッセージが出力されるようになっています。
winFormChange02_003

最後に、完了画面が現れます。
おめでとうございます!えっちなサイトにユーザー登録されてしまいました!
※嘘です。画面に今まで入力してきた値を表示しているだけですので、ご安心下さい。
winFormChange02_004

このカテゴリーの先頭へ このページの先頭へ

■コード

紙芝居のおじさんが持っている枠である Wizard です。
いくつも画面が登場していますが、実際は Form オブジェクトはコレだけです。

Wizard.vb
Public Class Wizard

    ' この画面で管理している Panel
    Private m_panels As List(Of PanelUserControlBase) = New List(Of PanelUserControlBase)
    ' 現在表示している Panel の Index
    Private m_currentPos As Integer = -1
    ' 現在表示している Panel
    Private m_currentPanel As PanelUserControlBase
    ' 最後のパネルのインデックス
    Private Const LAST_PANEL_INDEX As Integer = 3
    ' 画面遷移(次へ)を許すか否か
    Private m_allowNext As Boolean = False
    ' 画面遷移(戻る)を許すか否か
    Private m_allowBack As Boolean = False

    ' 画面遷移(次へ)を許すか否か
    Public Property AllowNext() As Boolean
        Get
            Return Me.m_allowNext
        End Get
        Set(ByVal value As Boolean)
            If value AndAlso LAST_PANEL_INDEX = Me.m_currentPos Then
                Return
            End If
            Me.m_allowNext = value
            Me.btnNext.Enabled = value
        End Set
    End Property

    ' 画面遷移(戻る)を許すか否か
    Public Property AllowBack() As Boolean
        Get
            Return Me.m_allowBack
        End Get
        Set(ByVal value As Boolean)
            If value AndAlso 0 = Me.m_currentPos Then
                Return
            End If
            Me.m_allowBack = value
            Me.btnBack.Enabled = value
        End Set
    End Property

    ' 自画面がロードされた時のイベント
    Private Sub Wizard_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Dim pnlLocation As System.Drawing.Point = New System.Drawing.Point(12, 12)
        ' 1 枚目
        Dim pnl1 As Panel1 = New Panel1
        pnl1.UserInfo = New UserInformation
        pnl1.Location = pnlLocation
        Me.m_panels.Add(pnl1)

        ' 2 枚目
        Dim pnl2 As Panel2 = New Panel2
        pnl2.Location = pnlLocation
        Me.m_panels.Add(pnl2)

        ' 3 枚目
        Dim pnl3 As Panel3 = New Panel3
        pnl3.Location = pnlLocation
        Me.m_panels.Add(pnl3)

        ' 4 枚目
        Dim pnl4 As Panel4 = New Panel4
        pnl4.Location = pnlLocation
        Me.m_panels.Add(pnl4)

        ' 1 枚目を表示する
        Me.m_currentPos = 0
        Me.m_currentPanel = pnl1
        Me.Controls.Add(Me.m_currentPanel)
        Me.AllowNext = False
        Me.AllowBack = False
    End Sub

    ' 戻るボタンのイベント
    Private Sub btnBack_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnBack.Click
        If Me.m_currentPos <= 0 Then Return
        Me.m_currentPos -= 1
        Dim nextPanel As PanelUserControlBase = Me.m_panels(Me.m_currentPos)
        If Not Me.changePanel(nextPanel) Then Return

        Select Case Me.m_currentPos
            Case 0
                Me.AllowNext = True
                Me.AllowBack = False
            Case LAST_PANEL_INDEX
                Me.AllowNext = False
                Me.AllowBack = False
            Case Else
                Me.AllowNext = True
                Me.AllowBack = True
        End Select
    End Sub

    ' 次へボタンのイベント
    Private Sub btnNext_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnNext.Click
        If Not Me.m_allowNext Then Return
        If Me.m_currentPos = Me.m_panels.Count - 1 Then Return
        Me.m_currentPos += 1
        Dim nextPanel As PanelUserControlBase = Me.m_panels(Me.m_currentPos)
        If Not Me.changePanel(nextPanel) Then Return

        If nextPanel.HasValue Then
            Me.AllowNext = True
        Else
            Me.AllowNext = False
        End If
        Select Case Me.m_currentPos
            Case 0, LAST_PANEL_INDEX
                Me.AllowBack = False
            Case Else
                Me.AllowBack = True
        End Select
    End Sub

    ' パネルをとっかえるメソッド
    Private Function changePanel(ByVal nextPanel As PanelUserControlBase) As Boolean
        If Me.m_currentPanel Is nextPanel Then Return False
        Dim userInfo As UserInformation = Me.m_currentPanel.UserInfo    ' 現在の UserInformation オブジェクトの参照を取って...
        Me.Controls.Remove(Me.m_currentPanel)

        nextPanel.UserInfo = userInfo                                   ' 次の画面に渡す
        Me.m_currentPanel = nextPanel
        Me.Controls.Add(nextPanel)
        Return True
    End Function

    ' 終了ボタンのイベント
    Private Sub btnClose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClose.Click
        If Me.m_currentPos <> LAST_PANEL_INDEX Then
            Dim dresult As DialogResult = _
                            MessageBox.Show("登録の途中です。本当に終了しますか", _
                            Me.Text, _
                            MessageBoxButtons.OKCancel, _
                            MessageBoxIcon.Question)
            If dresult = Windows.Forms.DialogResult.Cancel Then
                Return
            End If
        End If
        Me.Close()
    End Sub

    ' 画面を閉じる時のイベント
    Private Sub Wizard_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        For i As Integer = 0 To Me.m_panels.Count - 1
            Me.m_panels(i).Dispose()
        Next
        Application.Exit()
    End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

このシステムで、ユーザーに入力させる情報を保持するクラスです。
画面から画面へのこのオブジェクトの引渡しは、Wizard クラスの changePanel メソッドの中で行っています。

UserInformation.vb
Public Class UserInformation

    '名前
    Private m_name As String
    '居住地域
    Private m_address As String
    'メールアドレス
    Private m_mailAddress As String

    Public Sub New()
    End Sub

    Public Property Name() As String
        Get
            Return Me.m_name
        End Get
        Set(ByVal value As String)
            Me.m_name = value
        End Set
    End Property

    Public Property Address() As String
        Get
            Return Me.m_address
        End Get
        Set(ByVal value As String)
            Me.m_address = value
        End Set
    End Property

    Public Property MailAddress() As String
        Get
            Return Me.m_mailAddress
        End Get
        Set(ByVal value As String)
            Me.m_mailAddress = value
        End Set
    End Property

End Class
このカテゴリーの先頭へ このページの先頭へ

紙芝居の紙自体の基底の抽象クラスです。

PanelUserControlBase.vb
Public MustInherit Class PanelUserControlBase

    ' ユーザーの情報オブジェクト
    Protected m_userInfo As UserInformation

    ' 自身の親コントロール(Wizard クラス)
    Private ReadOnly Property myParent() As Wizard
        Get
            Return DirectCast(Me.Parent, Wizard)
        End Get
    End Property

    ' 親に画面遷移(次へ)を許すか否か
    Protected Property Parent_AllowNext() As Boolean
        Get
            Return Me.myParent.AllowNext
        End Get
        Set(ByVal value As Boolean)
            Me.myParent.AllowNext = value
        End Set
    End Property

    ' 親に画面遷移(戻る)を許すか否か
    Protected Property Parent_AllowBack() As Boolean
        Get
            Return Me.myParent.AllowBack
        End Get
        Set(ByVal value As Boolean)
            Me.myParent.AllowBack = value
        End Set
    End Property

    ' ユーザーの情報オブジェクト アクセサ・ミューテータ
    Public Property UserInfo() As UserInformation
        Get
            Return Me.m_userInfo
        End Get
        Set(ByVal value As UserInformation)
            Me.m_userInfo = value
        End Set
    End Property

    ' 自身が面倒を見るべき情報を保持しているか否か
    Public MustOverride ReadOnly Property HasValue() As Boolean

End Class
このカテゴリーの先頭へ このページの先頭へ

1 枚目の紙芝居です。

Panel1.vb
Public Class Panel1

    ' このパネルがロードされた時のイベント
    Private Sub Panel1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Me.TextBox1.Focus()
    End Sub

    ' TextBox から Focus が離れた時のイベント
    Private Sub TextBox1_Leave(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.Leave
        If Me.TextBox1.Text <> "" Then
            MyBase.m_userInfo.Name = Me.TextBox1.Text
            MyBase.Parent_AllowNext = True
        Else
            MyBase.Parent_AllowNext = False
        End If
    End Sub

    ' 自身が面倒を見るべき情報(名前)を保持しているか否か
    Public Overrides ReadOnly Property HasValue() As Boolean
        Get
            Return Me.TextBox1.Text <> ""
        End Get
    End Property
End Class
このカテゴリーの先頭へ このページの先頭へ

2 枚目の紙芝居です。

Panel2.vb
Public Class Panel2

    ' このパネルがロードされた時のイベント
    Private Sub Panel2_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        With Me.ComboBox1.Items
            .Clear()
            .Add("北海道")
            .Add("東北")
            .Add("首都圏")
            .Add("中部")
            .Add("関西")
            .Add("中国・四国")
            .Add("九州・沖縄")
        End With
        Me.ComboBox1.SelectedIndex = -1
        Me.ComboBox1.Focus()
    End Sub

    ' TextBox から Focus が離れた時のイベント
    Private Sub ComboBox1_Leave(ByVal sender As Object, ByVal e As System.EventArgs) Handles ComboBox1.Leave
        If Me.ComboBox1.Text <> "" Then
            MyBase.m_userInfo.Address = Me.ComboBox1.Text
            MyBase.Parent_AllowNext = True
        Else
            MyBase.Parent_AllowNext = False
        End If
    End Sub

    ' 自身が面倒を見るべき情報(居住地域)を保持しているか否か
    Public Overrides ReadOnly Property HasValue() As Boolean
        Get
            Return Me.ComboBox1.Text <> ""
        End Get
    End Property
End Class
このカテゴリーの先頭へ このページの先頭へ

3 枚目の紙芝居です。

Panel3.vb
Public Class Panel3

    ' このパネルがロードされた時のイベント
    Private Sub Panel3_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Me.TextBox1.Focus()
    End Sub

    ' TextBox から Focus が離れた時のイベント
    Private Sub TextBox1_Leave(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.Leave
        If Me.TextBox1.Text <> "" Then
            Dim inputText As String = Me.TextBox1.Text
            ' 正規表現で、メールアドレスっぽい文字列かどうかのチェックを行う
            Dim result As Boolean = System.Text.RegularExpressions.Regex.IsMatch( _
                                    inputText, _
                                    "\b[-\w.]+@[-\w.]+\.[-\w]+\b")
            If Not result Then
                ' NG だったら、エラーメッセージを出して処理終了
                MessageBox.Show("入力された文字はメールアドレスじゃないみたいです", _
                                "error", _
                                MessageBoxButtons.OK, _
                                MessageBoxIcon.Exclamation)
                Me.TextBox1.Focus()
                MyBase.Parent_AllowNext = False
                Return
            End If
            MyBase.m_userInfo.MailAddress = Me.TextBox1.Text
            MyBase.Parent_AllowNext = True
        Else
            MyBase.Parent_AllowNext = False
        End If
    End Sub

    ' 自身が面倒を見るべき情報(メールアドレス)を保持しているか否か
    Public Overrides ReadOnly Property HasValue() As Boolean
        Get
            Return Me.TextBox1.Text <> ""
        End Get
    End Property
End Class
このカテゴリーの先頭へ このページの先頭へ

4 枚目の紙芝居です。

Panel4.vb
Public Class Panel4

    ' このパネルがロードされた時のイベント
    Private Sub Panel4_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Me.m_userInfo Is Nothing Then Return
        Dim sr As System.Text.StringBuilder = New System.Text.StringBuilder
        sr.AppendLine("お名前:" + Me.m_userInfo.Name)
        sr.AppendLine("居住地域:" + Me.m_userInfo.Address)
        sr.AppendLine("メールアドレス:" + Me.m_userInfo.MailAddress)
        sr.AppendLine()
        sr.AppendLine("えっちなサイトにユーザー登録しました!!")

        Me.Label1.Text = sr.ToString()
    End Sub

    ' 自身が面倒を見るべき情報を保持しているか否か→ここでは面倒をみるものがないので常に False
    Public Overrides ReadOnly Property HasValue() As Boolean
        Get
            Return False
        End Get
    End Property
End Class
このカテゴリーの先頭へ このページの先頭へ

■ひとこと

PanelUserControlBase が抽象クラスになっているので、デザイナで開けないと思います。
デザイナで開くためには、一時的に 以下の作業を行ってください。
PanelUserControlBase の MustOverride の Property 及び、派生クラス(Panel1...Panel4)Overrides の Property をコメントアウトして下さい。
Wizard で、該当 Property を使っている部分もコメントアウトして下さい。
PanelUserControlBase の MustInherit 属性もはずして下さい。
その後、リビルドすると、デザイナで表示できると思います。

このカテゴリーの先頭へ このページの先頭へ