VB6開発者向け:C#で始める.NETプログラミング

第2回VB6で作ったサンプルソフト

はじめに

本連載では、実際にVB6で開発したソフトウェアがあると仮定して、そのソフトウェアをC#に置き換えながら基本的な技術を学ぶという趣旨なので、当然サンプルとなるソフトウェアが必要です。

そこで、今回はVB6で開発した電話帳サンプルソフトというものを用意してみました。 また、VB6開発者の皆さんが、普段どのような設計をしているのか、どういう設計がよいのかについても、少しだけ触れさせていただきました。

電話帳サンプルソフトとは?

まずは、以下の画面をご覧ください。

電話帳サンプルソフト
電話帳サンプルソフト

電話番号を管理するためのシンプルなソフトウェアです。

番号を入力した後に[追加]ボタンをクリックすると新しい番号が登録され、⁠読込み]ボタンをクリックすると対応するデータが画面に表示されます。名前・電話番号の値は、自由に変更することができます。変更後に[更新]ボタンを押すと表示中の値が保存され、⁠削除]ボタンを押すと表示中の番号のデータが削除されます。そして[閉じる]ボタンで、ソフトウェアは終了します。

データベースはMicrosoft Accessを使用してDAO(Data Access Object)によって操作しています。

各コントロールの名前

電話帳サンプルソフトで使用されているコントロールは次の通りです。

  • ラベル(Label)
  • 番号:(label1)
  • 名前:(label2)
  • 電話番号:(label3)
  • コマンドボタン(CommandButton)
  • 読込み(ReadButton)
  • 追加(AddButton)
  • 更新(UpdateButton)
  • 削除(DeleteButton)
  • 閉じる(CloseButton)
  • テキストボックス(TextBox)
  • 番号(CodeTextBox)
  • 名前(NameTextBox)
  • 電話番号(TelephoneNumberTextBox)

上記の名前をご覧いただけばお分かりかと思いますが VB6 開発ではお馴染みのシステムハンガリアン記法は、サンプルコードでは一切使っておりません。これもオブジェクト指向プログラミングに向けた準備だと思ってください。

詳細は、今後の連載の中で触れていきますので、現時点では、オブジェクト指向プログラミングはハンガリアン記法は向いていないのだとご理解ください。

VB6ソースコード公開

VB6開発者の方に向けた内容なので、実際にVB6のソースコードを確認していただくのが分かりやすいでしょう。

コーディング規約の違いなどにより若干読み難い部分もあるかもしれません。この辺はご容赦ください。

Option Explicit
'********************************************************
' 電話帳サンプルソフト
'********************************************************
Private DB As Database
Private Const FILENAME = "c:\Database.mdb"

'********************************************************
' イベントハンドラ
'********************************************************
Private Sub AddButton_Click()
    If (Not AddLogic()) Then Exit Sub
    Call ClearData
End Sub

Private Sub CloseButton_Click()
    Call Unload(Me)
End Sub

Private Sub Form_Load()
    Call ConnectDatabase
    CodeTextBox.Text = ""
    Call ClearData
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Call CloseDatabase
End Sub

Private Sub ReadButton_Click()
    Call ReadLogic
End Sub

Private Sub UpdateButton_Click()
    Call UpdateLogic
End Sub

Private Sub DeleteButton_Click()
    If (Not DeleteLogic()) Then Exit Sub
    Call ClearData
End Sub

'********************************************************
' 名前と電話番号の表示をクリアします
'********************************************************
Private Sub ClearData()
    NameTextBox.Text = ""
    TelephoneNumberTextBox.Text = ""
End Sub

'********************************************************
' エラーメッセージ表示
' ErrorNumber   エラー番号
'********************************************************
Private Sub ShowErrorMessage(ByVal ErrorNumber As Long)
    Call MsgBox( _
        CStr(ErrorNumber) & vbCr & Error$(ErrorNumber))
End Sub

'********************************************************
' Code プロパティ
' Returns:  番号
' Remarks:  CodeTextBox.Text に入力されている値です
'********************************************************
Private Property Get Code() As Long
    Code = Val(CodeTextBox.Text)
End Property

Private Property Let Code(ByVal Value As Long)
    CodeTextBox.Text = CStr(Value)
End Property

'********************************************************
' 追加処理を実行します
' Returns:  結果 [True:成功 / False:失敗]
'********************************************************
Private Function AddLogic() As Boolean
    If (InvalidCode(Code)) Then
        Call MsgBox("追加できませんでした")
        AddLogic = False
        Exit Function
    End If
    
    Call AddRecord(Code)

    AddLogic = True
End Function

'********************************************************
' 更新処理を実行します
' Returns:  結果 [True:成功 / False:失敗]
'********************************************************
Private Function UpdateLogic() As Boolean
    If (InvalidCode(Code)) Then
        Call MsgBox("更新に失敗しました")
        UpdateLogic = False
        Exit Function
    End If
    
    Call UpdateRecord( _
        Code, _
        NameTextBox.Text, _
        TelephoneNumberTextBox.Text)
    
    UpdateLogic = True
End Function

'********************************************************
' 削除処理を実行します
' Returns:  結果 [True:成功 / False:失敗]
'********************************************************
Private Function DeleteLogic() As Boolean
    If (InvalidCode(Code)) Then
        Call MsgBox("削除に失敗しました")
        DeleteLogic = False
        Exit Function
    End If
    
    Call DeleteRecord(Code)
    
    DeleteLogic = True
End Function

'********************************************************
' 読込み処理を実行します
' Returns:  結果 [True:成功 / False:失敗]
'********************************************************
Private Function ReadLogic() As Boolean
    Dim RS As Recordset
    Set RS = ReadRecord(Code)
    If (RS Is Nothing) Then Exit Function
    If (RS.BOF And RS.EOF) Then
        Call MsgBox("登録されていませんでした")
        ReadLogic = False
    Else
        NameTextBox.Text = RS("名前")
        TelephoneNumberTextBox.Text = RS("電話番号")
        ReadLogic = True
    End If
    Call RS.Close
End Function

'********************************************************
' 番号値が有効かどうかを検証した結果を取得します
' Value:    番号の値
' Returns:  結果 [True:無効 / False:有効]
'********************************************************
Private Function InvalidCode( _
    ByVal Value As Long) As Boolean

    If (Value <= 0 Or Value >= 100000000) Then
        Call MsgBox("番号の値が不正です")
        InvalidCode = True
    Else
        InvalidCode = False
    End If
End Function

'********************************************************
' レコードを追加します
' Code:  番号
' Returns:  結果 [True:成功 / False:失敗]
'********************************************************
Private Function AddRecord(ByVal Code As Long) As Boolean
    Dim SqlText As String
    SqlText = GetAddSqlText(Code)
    AddRecord = ExecuteQuery(SqlText)
End Function

'********************************************************
' レコードを更新します
' Code:          番号
' NameValue      名前
' TelephoneNumber   電話番号
' Returns:  結果 [True:成功 / False:失敗]
'********************************************************
Private Function UpdateRecord( _
    ByVal Code As Long, _
    ByVal NameValue As String, _
    ByVal TelephoneNumber As String _
) As Boolean
    Dim SqlText As String
    SqlText = GetUpdateSqlText( _
        Code, NameValue, TelephoneNumber)
    UpdateRecord = ExecuteQuery(SqlText)
End Function

'********************************************************
' レコードを削除します
' Code:  番号
' Returns:  結果 [True:成功 / False:失敗]
'********************************************************
Private Function DeleteRecord(ByVal Code As Long) As Boolean
    Dim SqlText As String
    SqlText = GetDeleteSqlText(Code)
    DeleteRecord = ExecuteQuery(SqlText)
End Function

'********************************************************
' レコードを読み込みます
' Code:  番号
' Returns:  RecordSet オブジェクト
'********************************************************
Private Function ReadRecord( _
    ByVal Code As Long) As Recordset

    Dim Sql As String
    Sql = GetSelectSqlText(Code)

    On Error Resume Next
    Err.Number = 0

    Dim RS As Recordset
    Set RS = DB.OpenRecordset(Sql, dbOpenSnapshot)
    If (Err.Number <> 0) Then
        Call ShowErrorMessage(Err.Number)
    End If
    On Error GoTo 0

    Set ReadRecord = RS
End Function

'********************************************************
' 各 SQL 文字列を取得します
' Code:  番号
' Returns:  SQL 文字列
'********************************************************
Private Function GetSelectSqlText( _
    ByVal Code As Long) As String

    GetSelectSqlText = _
        "SELECT * FROM MyTable " & _
        "WHERE 番号 = " & CStr(Code)
End Function

Private Function GetAddSqlText( _
    ByVal Code As Long) As String

    GetAddSqlText = _
        "INSERT INTO MyTable(番号, 名前, 電話番号) " & _
        "VALUES(" & CStr(Code) & ", '' , '')"
End Function

Private Function GetUpdateSqlText( _
    ByVal Code As Long, _
    ByVal NameValue As String, _
    ByVal TelephoneNumber As String _
) As String
    GetUpdateSqlText = _
        "UPDATE MyTable " & _
        "Set " & _
            "名前 = '" & NameValue & "', " & _
            "電話番号 = '" & TelephoneNumber & "' " & _
        "WHERE 番号 = " & CStr(Code)
End Function

Private Function GetDeleteSqlText( _
    ByVal Code As Long) As String

    GetDeleteSqlText = _
        "DELETE * FROM MyTable " & _
        "WHERE 番号 = " & CStr(Code)
End Function

'********************************************************
' クエリーを実行します
' SqlText:  実行したい SQL 文字列
' Returns:  結果 [True:成功 / False:失敗]
'********************************************************
Private Function ExecuteQuery( _
    ByVal SqlText As String) As Boolean
    
    ExecuteQuery = True
    
    On Error Resume Next
    Err.Number = 0
    Call DB.Execute(SqlText)
    If (Err.Number <> 0) Then
        Call ShowErrorMessage(Err.Number)
        ExecuteQuery = False
    End If
    On Error GoTo 0
End Function

'********************************************************
' データベースへの接続を開始します
'********************************************************
Private Sub ConnectDatabase()
    Set DB = _
        DBEngine.Workspaces(0).OpenDatabase(FILENAME)
End Sub

'********************************************************
' データベースの接続を終了します
'********************************************************
Private Sub CloseDatabase()
    Call DB.Close
End Sub

ソースコード解説

全体に関して

いかがでしょうか? まず、エラー処理の煩雑さに、お気づきになられたかと思います。実際、上記ではエラー処理が不完全な部分もあります。これは、アプリケーションは異常終了する原因となるため、本来はきちんと対応する必要があります。

次に、このアプリケーションの一連の処理が、フォームファイルにまとまって記述されている点に違和感を感じる方もいらっしゃると思います。この書き方は、VB6開発者の方に多く見られる書き方です。

しかし、注意して見ると、少なくとも以下にあげる独立性の高い3つの処理が含まれていることに気づくでしょう。

  • ユーザーインターフェイス関連
  • 処理ロジック関連
  • データアクセス関連

これらは、本来各目的に応じて分離して記述することが望ましいとされています。

例えば、ユーザーインターフェイスに関する部分はフォームファイルに記述して、処理ロジックに関する部分や、データアクセスに関する部分は、それぞれ別のクラスモジュールに記述します。

これによって、データアクセス関連の処理は、このアプリケーション以外でも再利用できますし、ユーザーインターフェイスと実際の処理を分けて記述しておくことで、複数のユーザーインターフェイスから共通の処理を共有することなどが簡単にできるようになります。

これは、設計に絡む話で.NETプログラミングには関係ないと思われるかもしれません。しかし、このように再利用性を考慮して、目的や機能ごとに処理を分離するという考え方は.NETプログラミングにおいて、とても重要な考え方になります。

この考え方にシフトすることについては、恐らく大きな問題ではないでしょう。 ソフトウェアの開発者であれば上記の説明で十分納得していただける筈です。

このような書き方を日頃から実践していない方は、再度、上記のソースコードを眺めてみてください。そして、どのように分離すべきなのか、また各クラスモジュールの独立性や利便性を検討して、どのようなプロパティやメソッドを実装すればよいのかを上記の説明に基づいて是非検討してみてください。

ユーザーインターフェイス関連

サンプルコードの上部にまとめて記述しています。

読込み・追加・更新・削除・閉じるボタンコントロールが、それぞれ何という名前になっているのかについては、あえて説明する必要もないでしょう。

典型的なイベントドリブン型のアプリケーションです。

処理ロジック部分

サンプルコードの中央部分にまとめて記述しています。

各処理ロジックは、イベントによって呼び出されています。

データアクセス部分

サンプルコードの下部にまとめて記述しています。

DAOでは、読込み・追加・更新・削除用にあらかじめメソッドが用意されていますが、今回はあえてクエリー式を使用する方法をとりました。

この大きな理由として、C#ではDBMS(Data Base Management System)の種類に応じて、コードに手を加えるような記述をすべきではないという考え方があるからです。

また、実際には、RDO(Remote Data Objects)やADO(ActiveX Data Objects)を使用している方もいらっしゃるでしょう。このような場合、一部のメソッドを差し替えていただくだけで対応できてしまうことが、上記のコードを見れば簡単にわかるのではないでしょうか。

次回の予定

Visual C# 2008 Express Editionをご存知でしょうか?  評価、学習、ホビーとして最適なプログラミングツールで、現時点では無償で提供されています。

次回は、今回VB6で開発したソフトウェアの移行準備として、上記ソフトウェアによる開発環境を用意していただき、その後に基本的な操作を学んでいただく予定です。

おすすめ記事

記事・ニュース一覧