2.1 ファイル処理について

Microsoft ExcelのVBAを使ってファイルを処理するプログラムを書いてみましょう。ここで扱うファイルは原則としてテキスト形式にします。最終結果の出力や、乱アクセスファイルが必要なときには、Excel形式のファイル(拡張子が.xlsのファイル)を使うことにしましょう。

テキスト形式ファイルは、項目がカンマで区切られたCSV形式を中心に話を進めます。また、テキスト形式ファイルは、先頭から書かれた順番に読みだされる順アクセスファイルです。


2.1.1 ファイル処理のパターン

よく使われるファイル処理のパターンには以下のようなものがあります。

1つのファイルからデータを読み出し、順次処理して、1つのファイルに出力する処理です。レコード形式を変更したり、必要なレコードだけ取出したりすることがあります。
図2.1.1-1   図2.1.1-1 図2.1.1-2   図2.1.1-2 図2.1.1-3   図2.1.1-3

1つのファイルからレコードをすべて入力し、メモリや一時的ファイルを作業場所にして、キーの順番にレコードを整列して、1つのファイルに出力する処理です。作業ファイルには乱アクセスを用いたりすることがありますが、入出力ファイルは順アクセスです。
図2.1.1-4   図2.1.1-4

マージ、アップデート、マッチングなどと呼ばれる処理で、複数のファイルを順次読み込み、1つのファイルに出力する処理です。時には複数のファイルに分割して出力することもあります。
図2.1.1-5   図2.1.1-5 図2.1.1-6   図2.1.1-6 図2.1.1-7   図2.1.1-7

2.1.2 ファイル処理の流れ図

「抽出処理」を考えてみましょう。1つのファイルの先頭から順に1レコードずつ読み込み、必要なレコードだけを選び出して、1つの出力ファイルに順に書き出して行く処理です。時にはレコードの形式を変えて出力することもあります。
図2.1.2-1 図2.1.2-1

1つのファイルから順にレコードを読んで、1つのファイルに順に書いて行く処理に、入力処理や印刷処理、抽出処理といったものがありますが、入力処理は、入力にキーボードとディスプレイを使い、印刷処理は出力にプリンタを使うので、少し特殊です。Excelを使えば、データの入力や印刷はプログラムを作らなくても簡単にできてしまいます。

流れ図を書いてみると、おおよそ図2.1.2-2のようになるでしょう。
図2.1.2-2 図2.1.2-2
準備処理では、入力ファイルと出力ファイルを使えるように開きます。後処理では、2つのファイルを閉じます。
編集処理やレコード1件出力処理は、読み込んだレコードの内容によっては処理されないこともあるでしょう。この付近の処理は仕様によって変わってきますが、他のところは大体決まった処理になってしまいます。

入力ファイルのレコードは、1レコードごとに「改行文字」で区切られているものとします。メモ帳などのテキストエディタで編集できるファイルの1行に相当します。
以下のレコードは、CSV形式で書かれています。各項目を分ける「,(カンマ)」の個数は一定の方が好都合ですが、異なる場合は読み捨てるというような処理も可能ですし、別のファイルに書きだしたり、形式を変換したりすることも可能です。「#」で始まる行もイレギュラーなレコードなので同様です。
例えば、以下のようなデータです。

# 入退室ログ
所属,氏名,ID,年月日,出社時刻,退社時刻
営業,河田 次郎,10037,2014/4/1,8:19,18:31
営業,河田 次郎,10037,2014/4/3,8:16,17:31
営業,河田 次郎,10037,2014/4/2,8:39,18:02
技術,山川 太郎,10118,2014/4/2,9:30,18:00
技術,山川 太郎,10118,2014/4/3,8:35,19:42
技術,山中 司朗,10122,2014/4/1,8:46,17:33
技術,山中 司朗,10122,2014/4/2,8:06,18:01
企画,海山 三郎,10123,2014/4/1,8:46,19:30
企画,海山 三郎,10123,2014/4/2,9:03,17:35
総務,野口 五郎,10124,2014/4/1,8:30,18:24
総務,野口 五郎,10124,2014/4/2,8:39,19:03
……

2.1.3 ファイルを準備しよう

さて、この抽出処理では2つのファイルを扱います。どちらもCSV形式のテキストファイルです。ファイルに対して入出力処理を行うときには、その対象となるファイルを開く必要があります。ファイルオブジェクトに用意された OpenAsTextStream メソッドを利用することにします。テキストストリームとは、ファイルを1行単位で順序通りに読み書きするためのオブジェクトです。詳しいことは、ヘルプを「TextStream」で検索して調べてください。

ファイルを利用するためには、

  1. ファイルシステムオブジェクトを取得しておき
  2. ファイルシステムから利用するファイルのファイルオブジェクトをファイル名で指定して取得し
  3. そのファイルオブジェクトをテキストストリームオブジェクトとして開きます
ファイルシステムオブジェクトは、
ファイル システムへのアクセスを提供します。即ち、ディレクトリやファイルを扱うためのメソッドやプロパティの集まりです。
ファイルオブジェクトは、
1つのファイルを読んだり書いたりするためのプロパティやメソッドの集まりです。

特定のファイルをテキストストリームとして開くと、シーケンシャルファイルとして扱えます。即ち、1行単位で読み書きできるようになります。処理がすべて終わったら、テキストストリームを閉じます。

次の例は、新しいファイルを書込み専用として開き、1行書込む例です。標準モジュールに作ります。 図2.1.3-1   図2.1.3-1

Sub TestWrite()

    Const ForWriting = 2    '書込み専用モード

    Dim fs As Object    ' FileSystemオブジェクト
    Dim f As Object     ' ファイルオブジェクト
    Dim fobj As Object  ' テキストストリームとしてのファイルオブジェクト
    Dim fnm As String   ' ファイルのパス名+ファイル名
    Dim Path As String  ' 一時フォルダのパス名

    Path = Environ("TEMP")                   '一時フォルダのパス
    fnm = Path & "\TestFile.TXT"    'ファイル名

    Set fs = CreateObject("Scripting.FileSystemObject")    'ファイルシステムオブジェクトへの参照を取得
    fs.CreateTextFile (fnm)                                'ファイルオブジェクトを新規作成し
    Set f = fs.GetFile(fnm)                                '参照を取得
    Set fobj = f.OpenAsTextStream(ForWriting)              'ファイルを書込み専用のテキストストリームとして開く
    
    
    fobj.WriteLine ("This is a test.")                     '1行書き出す
    fobj.Close                                             'テキストストリームを閉じる

End Sub

次の例は、読み取り専用としてファイルを開き、1行表示する例です。同じく標準モジュールに作ります。

Sub TestRead()
    Const ForReading = 1    ' 読み取り専用モード

    Dim fs As Object    ' FileSystemオブジェクト
    Dim f As Object     ' ファイルオブジェクト
    Dim fobj As Object  ' テキストストリームとしてのファイルオブジェクト
    Dim fnm As String   ' ファイルのパス名+ファイル名
    Dim Path As String  ' 一時フォルダのパス名

    Path = Environ("TEMP")                   '一時フォルダのパス
    fnm = Path & "\TestFile_20120331.TXT"    'ファイル名

    Set fs = CreateObject("Scripting.FileSystemObject")    'ファイルシステムオブジェクトへの参照を取得
    Set f = fs.GetFile(fnm)                                'ファイルオブジェクトへの参照を取得
    Set fobj = f.OpenAsTextStream(ForReading)              'ファイルをテキストストリームとして開く
    MsgBox fobj.ReadLine                                   '1行読み込む
    fobj.Close                                             'テキストストリームを閉じる
End Sub

上の2つの例のコードが理解できたら、実行してみましょう。「マクロの実行(F5)」ボタンを使います。

ファイル名のパスを求める際に使われている Environ("TEMP") は、一時フォルダを求めています。利用している Windows の環境によって異なりますが、一般的には、"C:\Documents and Settings\........\Local Settings\Temp" フォルダになります。エクスプローラのアドレス欄に「%temp%」と入力すると表示できます。環境変数 temp で定義されているフォルダを表します。

作成したファイルは、テキスト形式なのでメモ帳などのテキストエディタで開けます。確認してみましょう。

ここまできたら、コピープログラムは簡単に作れそうです。実際には、コピー元のファイルがなかった場合にはどうするか、コピー元のファイルの読み終わりをどうやって知るか、などの問題に対応しなければなりません。

2.1.4 終わり(End Of Stream)を判断しよう

ファイル処理は、先頭から1レコード(行)ずつ読み込んで、最後のレコードを処理したら終わりです。終わりのタイミングを間違えると1件も処理せずにプログラムが終了したり、無限ループに陥ったりします。

テキストストリームオブジェクトには、終わりを判断する AtEndOfStream メソッドが用意されています。ファイルの終わりまできたら真(True)を返します。ファイルの終わりまで達していないなら偽(false)を返します。以下のようにコーディングするとファイルの最後まで繰り返すことができます。もちろん、Do Until ~ Loop の形を使ってもかまいませんし、Do ~ Loop While や、Do ~ Loop Until の形を使ってもかまいません。

Do While fobj.AtEndOfStream <> True
    MsgBox fobj.ReadLine
    ...
Loop

上の例を参考にして、ファイルのコピー処理を書いてみましょう。

Sub TestCopy()
    Const ForReading = 1    '読み取り専用モード
    Const ForWriting = 2    '書込み専用モード

    Dim fs As Object    'FileSystemオブジェクト
    Dim f As Object     'ファイルオブジェクト
    Dim fsrc As Object  'コピー元ファイルのオブジェクト
    Dim fdst As Object  'コピー先ファイルのオブジェクト
    Dim Path As String  '一時フォルダのパス名
    Dim fnm1 As String  'ファイルのパス名+ファイル名
    Dim fnm2 As String  'ファイルのパス名+ファイル名

    Path = Environ("TEMP")               '一時フォルダのパス

    fnm1 = Path & "\TestFile.TXT"        'コピー元ファイル名
    fnm2 = Path & "\TestFileCopy.TXT"    'コピー先ファイル名

    Set fs = ___[1]___("Scripting.FileSystemObject")    'コピー元ファイルを開く
    Set f = fs.___[2]___(fnm1)
    Set fsrc = f.___[3]___(__[4]__)

    fs.___[5]___ (fnm2)                                 'コピー先ファイルを新規作成
    Set f = fs.___[2]___(fnm2)
    Set fdst = f.___[3]___(___[6]___)

    Do While fsrc.__[7]__ <> True                       'ファイルの終わりでない間
        fdst.__[8]__ fsrc.__[9]__                       '1行ずつコピー
    Loop

    fsrc.Close                                          'コピー元を閉じる
    fdst.Close                                          'コピー先を閉じる
__[1]__から__[9]__までの空欄を埋めて、プログラムを完成してください。

2.1.5 エラー処理を組み込もう

プログラムを走らせる前に、コピー元のファイルを準備し忘れていた場合にはどうなるでしょうか。ファイル名を間違えたり、保存するフォルダを間違えることも考えられます。エラーが発生したときの常套手段があります。

Function ExamFunc()

On Error GoTo ErrGyo
    ……

    ExamFunc = True
Exit Function

ErrGyo:
    ExamFunc = False
    MsgBox Err.Description & ":" & Err.Number, , "ExamFuncでエラーが発生しました。"
End Function

上の例では、Function プロシージャで書いています。On Error GoTo 文はどこにでも書けますが、それ以降の文でエラーが発生するとパラメータで指定した行に処理が移ります。ここでは ErrGyo: という名前をつけた場所から実行されるようになります。ExamFunc に False を代入しているので、戻り値が False になります。その後、MsgBox 関数でエラーメッセージを表示しています。End Function 文で呼び出し元に帰ります。

Err オブジェクトは、実行時エラーに関する情報を保有しています。プログラムの実行中に実行できないコードがあったりすると、そのエラーに関する情報がErr オブジェクトのプロパティにセットされます。上の例では、.Description プロパティ(エラーに関連する説明) と .Numberプロパティ (エラー番号) を表示しています。

エラー処理の MsgBox 関数では、第1パラメータに、エラーに関する説明とエラー番号を、第2パラメータは省略、第3パラメータにエラーの発生したプロシージャ名を渡しています。それぞれ、どこに表示されるか調べてみましょう。

エラーが発生せずに正常に実行されると、正常終了を呼び出し元に伝えるために ExamFunc に True を代入して、Exit Function 文で呼び出し元に帰ります。

以下のコードは、前節で作成したファイルの読み取りプロシージャ TestRead() にエラー処理を付け加えた例です。

Sub TestRead()
    Const ForReading = 1    ' 読み取り専用モード

    Dim fs As Object    ' FileSystemオブジェクト
    Dim f As Object     ' ファイルオブジェクト
    Dim fobj As Object  ' テキストストリームとしてのファイルオブジェクト
    Dim fnm As String   ' ファイルのパス名+ファイル名
    Dim Path As String  ' 一時フォルダのパス名

On Error GoTo errsyori
    Path = Environ("TEMP")           '一時フォルダのパス
    fnm = Path & "\TestFile2.TXT"    'ファイル名

    Set fs = CreateObject("Scripting.FileSystemObject")    'ファイルシステムオブジェクトへの参照を取得
    Set f = fs.GetFile(fnm)                                'ファイルオブジェクトへの参照を取得
    Set fobj = f.OpenAsTextStream(ForReading)              'ファイルをテキストストリームとして開く
    MsgBox fobj.ReadLine                                   '1行読み込む
    fobj.Close                                             'テキストストリームを閉じる

    Exit Sub

errsyori:
    MsgBox Prompt:=Err.Description & Err.Number, Title:="TestRead"
End Sub

ファイルのコピー処理にもエラー処理を追加してみましょう。エラーが発生したとき、読み込みファイルと書き出しファイルのどちらのエラーか区別して表示できるようしておく方が利用者には親切ですし、デバッグの際も役に立つでしょう。

Sub TestCopy()
    Const ForReading = 1    ' 読み取り専用モード
    Const ForWriting = 2    '書込み専用モード

    Dim fs As Object    ' FileSystemオブジェクト
    Dim f As Object     ' ファイルオブジェクト
    Dim fsrc As Object  ' コピー元ファイルのオブジェクト
    Dim fdst As Object  ' コピー先ファイルのオブジェクト
    Dim Path As String  ' 一時フォルダのパス名
    Dim fnm1 As String  ' ファイルのパス名+ファイル名
    Dim fnm2 As String  ' ファイルのパス名+ファイル名

On Error GoTo errsyori
    Path = Environ("TEMP")                   '一時フォルダのパス

    fnm1 = Path & "\TestFile.TXT"        'コピー元ファイル名
    fnm2 = Path & "\TestFileCopy.TXT"    'コピー先ファイル名

    Set fs = CreateObject("Scripting.FileSystemObject")    'コピー元ファイルを開く
    Set f = fs.GetFile(fnm1)
    Set fsrc = f.OpenAsTextStream(ForReading)

    fs.CreateTextFile (fnm2)                               'コピー先ファイルを新規作成
    Set f = fs.GetFile(fnm2)
    Set fdst = f.OpenAsTextStream(ForWriting)

    Do While fsrc.AtEndOfStream <> True
        fdst.WriteLine fsrc.ReadLine
    Loop

    fsrc.Close                                             'テキストストリームを閉じる
    fdst.Close                                             'テキストストリームを閉じる
    Exit Sub

errsyori:
    MsgBox Prompt:=Err.Description & ":" & Err.Number, Title:="TestCopy"
    ' プログラムの実行が終わると自動的に開いていたファイルは閉じられますが、
    '開いているファイルが分かる場合は閉じてから終わるようにします。
End Sub

2.1.6 クラスを作ろう

今まで見てきたように、ファイル処理は、ファイルを開いて、1件ずつ読んで処理し、最後に閉じるというのが基本です。扱うファイルは通常、入力ファイルと出力ファイルの2つです。1つクラスを書いておけば再利用も可能ですし、ファイルに関する処理をカプセル化できます。

まず、必要なプロパティについて考えてみましょう。

表2.1.6-1 必要なプロパティ
プロパティ意味
ファイル名オープンするファイルのフルパスを保存します。
入出力モード読込み用か書込み用かを指定します。
レコードバッファ入出力のためのバッファ(十分な長さを用意するか読込む長さを制限します。)
入出力ステータス入出力動作の状態を保存します。正常に入出力ができたときは、True とします。
ファイルの終わりに到達してレコードが読めなかったときなどは、False とします。
エラーステータス実行時のエラーが発生したときに、True とします。
正常に動作を完了できた時は、False とします。

次に、必要なメソッドについて考えてみましょう。

表2.1.6-2 必要なメソッド
メソッド機能
Class_Initialize()オブジェクトの初期値を設定するときに使用します。
Class_Terminate()オブジェクトがアンロードされたあとに実行する処理を記述します。通常は不要です。
オープンファイル名と入出力モードを引数で受取って、ファイルをテキストストリームとして開きます。
1レコード読む読込み用ファイルとしてオープンしたときだけ動作します。
次のレコードを1件、レコードバッファに読み込みます。
1レコード書く書込み用ファイルとしてオープンしたときだけ動作します。
レコードバッファのレコードを1件、ファイルに書込みます
クローズファイルを閉じます。

以上をもとにCSV形式のファイルを処理するクラスを作成します。クラスはクラスモジュールに作ります。→1.4.2.1

オブジェクト名は「textClass」としておきます。

2.1.6.1 内部変数を用意しておこう

まず、プロパティを保存するためのプライベートな変数や定数などを定義しておきましょう。クラスの外からはアクセスできない変数です。

'################
'# ローカル変数 #
'################
'# オブジェクト変数
Private fs As Object    ' FileSystemオブジェクト
Private f As Object     ' ファイルオブジェクト
Private fobj As Object  ' テキストストリームのオブジェクト

'# ファイル名
Private FName As String       '扱うファイル名をフルパスで保存しておきます。

'# 入出力モード
Private IOMode As Integer
    Const ForReading = 1      '読込みモード
    Const ForWriting = 2      '書込みモード
    Const ForAppending = 8    '追記モード

'# 入出力バッファ
Private recString As String
'
'# 入出力ステータス
Private IOStat As Boolean       '入出力が正常なとき True
'
'# エラーステータス
Private ErrStat As Boolean      'エラーが発生した時 True

2.1.6.2 プロパティを書こう

5つのプロパティにアクセスできるようにプロパティプロシージャを書いて行きましょう。

先ず、ファイル名です。ファイル名は、オープンメソッドで与えられます。Get だけ書いておけば良いでしょう。

'#########################
'#プロパティ ファイル名 #
'#########################
'オープンメソッドで与えられる
'
Public Property Get fileName() As Variant
    fileName = FName
End Property

次に、入出力モードです。ファイル名と同じく、オープンメソッドで与えられます。Get だけ書いておけば良いでしょう。

'###########################
'#プロパティ 入出力モード #
'###########################
'ファイル名と同じく、オープンメソッドで与えられる
'
Public Property Get mode() As Variant
    mode = IOMode
End Property

3つめは、入出力バッファです。読込みモードのときは、Get でレコードを取出し、書込みモードのときは、Let でレコードを保存します。正式なシステムでは、モードによって呼び出せないように制限をかけておく方が好ましいでしょうが、簡単のため省略します。

'#############################
'#プロパティ 入出力バッファ #
'#############################
'メソッドrdNext()で読み込んだレコードを記憶する
'
Public Property Get record() As String
    record = recString
End Property
'
'メソッドwrNext()で書きこむレコードを記憶する
'
Public Property Let record(ByVal record_string As String)
    recString = record_string
End Property

4つめは、入出力ステータスです。入出力の結果を呼出し元に与える情報なので、Get だけ書いておけば良いでしょう。

'###############################
'#プロパティ 入出力ステータス #
'###############################
'入出力バッファの状態を呼出し元に与える情報
'有効なレコードあり:True
'有効なレコードなし:False
'
Public Property Get status() As Boolean
    status = IOStat
End Property

最後は、エラーステータスです。入出力ステータスと同じく、実行結果を呼出し元に与える情報なので、Get だけ書いておけば良いでしょう。

'###############################
'#プロパティ エラーステータス #
'###############################
'実行結果を呼出し元に与える情報
'正常終了:False
'異常終了:True
'
Public Property Get error() As Boolean
    error = ErrStat
End Property

2.1.6.3 コンストラクタとデストラクタを書こう

2.1.6.3.1 コンストラクタ

まずは、コンストラクタから書いてみましょう。残念ながらコンストラクタには引数を渡せません。オブジェクト内のローカル変数の初期化だけ行います。ファイル名は、環境変数から一時フォルダのパスを取出し、ファイルシステムオブジェクトから一時ファイル名に使えるランダムな文字列を貰って、結合しています。

'################
'#コンストラクタ#
'################
'オブジェクト内のローカル変数を初期化する。
'ファイル名は、デフォルトで一時フォルダのファイル名をランダムに生成する。
'
Private Sub Class_Initialize()    '
    Set fs = CreateObject("Scripting.FileSystemObject")
    FName = Environ("TEMP") & "\" & fs.GetTempName
                                  '%temp%フォルダの一時ファイル名を生成
    IOMode = ForWriting           '書込みモード
    recString = ""                'バッファ空
    IOStat = True                 '正常
    ErrStat = False               'エラー発生なし
End Sub

2.1.6.3.2 デストラクタ

デストラクタは、オブジェクトがアンロードされたあとに実行されます。デストラクタは書かなくても問題ありません。。

'##############
'#デストラクタ#
'##############
'デストラクタは、オブジェクトがアンロードされたあとに実行されます。
'
Private Sub Class_Terminate()
End Sub

2.1.6.4 メソッドを書こう

2.1.6.4.1 fOpen メソッド

ファイル名と入出力モードを引数で受取って、ファイルをテキストストリームとして開きます。ファイル名が空文字列""で、入出力モードが書込みモードのときは一時ファイルを作成することにします。
書込みモードの場合は、ファイルを新規作成してから開きます。

'#################
'#メソッド fOpen #
'#################
'ファイル名と入出力モードを引数で受取って、ファイルをテキストストリームとして開きます。
'ファイル名が空文字列""で、入出力モードが書込みモードのときは一時ファイルを作成することにします。
'書込みモードの場合は、ファイルを新規作成してから開きます。
'
Public Function fOpen(ByVal fnm As String, ByVal mode As Integer) As Boolean
On Error GoTo fOpenError
    If (fnm <> "" Or mode <> ForWriting) Then
        FName = fnm
        IOMode = mode
    End If

    If (IOMode = ForWriting) Then _
        fs.CreateTextFile (FName)   '書込みモードの場合はファイルを新規作成する

    Set f = fs.GetFile(FName)
    Set fobj = f.OpenAsTextStream(IOMode)

    IOStat = True               '正常
    fOpen = True
    ErrStat = False             'エラー発生なし
Exit Function

fOpenError:
    MsgBox Err.Description & ":" & Err.Number, , "fOpen:textClass:" & FName
    IOStat = False               '異常終了
    fOpen = False
    ErrStat = True               'エラー発生
End Function

2.1.6.4.2 rdNext メソッド

ファイルから次のレコードを1件、レコードバッファに読み込みます。

'###################
'#メソッド rdNext #
'###################
'ファイルから次のレコードを1件、レコードバッファに読み込みます。
'
Public Function rdNext() As Boolean
On Error GoTo rdNextError    ' エラーが発生したならばrdNextErrorへジャンプ

    'ストリームの最後に達したならば、Falseを返す
    If fobj.AtEndOfStream Then
        IOStat = False                 'レコードはない

    'ストリームの最後に達していなければ1レコード読む
    Else
        'TextStream ファイルから 1 行 (改行文字を除く) を読み込む
        recString = fobj.ReadLine
        
        '1レコードの処理が終わったので True を返す
        IOStat = True                 'レコードが読めた

    End If

    rdNext = IOStat
    ErrStat = False    '正常終了

    
Exit Function

rdNextError:
    IOStat = False                 'レコードは読めない
    rdNext = IOStat
    ErrStat = True                 '異常終了
    MsgBox Err.Description & ":" & Err.Number, , "rdNext:textClass:" & FName
End Function

2.1.6.4.3 wtNext メソッド

ファイルに引数で渡されたレコードを1件書込みます。

'###################
'#メソッド wtNext #
'###################
'ファイルに引数で渡されたレコードを1件書込みます。
'
Public Function wtNext(wtString As String) As Boolean
On Error GoTo wtNextError    ' エラーが発生したならばwtNextErrorへジャンプ

    'TextStream ファイルに指定された文字列と改行文字を書き込みます。
    recString = wtString
    fobj.writeLine (recString)

    IOStat = True    '正常終了
    wtNext = IOStat
    ErrStat = False
    
Exit Function

wtNextError:
    IOStat = False    '異常終了
    wtNext = IOStat
    ErrStat = True
    MsgBox Err.Description & ":" & Err.Number, , "wtNext:textClass:" & FName
End Function

2.1.6.4.4 fClose メソッド

ファイルを閉じます。

'###################
'#メソッド fClose #
'###################
'ファイルを閉じます。
'
Public Sub fClose()
On Error GoTo fCloseError    ' エラーが発生したならばfCloseErrorへジャンプ

    fobj.Close

    IOStat = True       '正常終了
    ErrStat = False


Exit Sub

fCloseError:
    IOStat = False      '異常終了
    ErrStat = True
    MsgBox Err.Description & ":" & Err.Number, , "fClose:textClass:" & FName
End Sub

2.1.7 プロシージャを呼び出してみよう

コピー処理の大筋の流れは、オブジェクトを使わない場合と変わりません。前出の図2.1.2-2 をもう一度紹介します。
図2.1.2-2 図2.1.2-2

コードのレベルになると、レコードの読込みとファイルの終わり判定を1行で済ませており、少し印象が変わってきますが、やっていることは同じです。抽出処理やレコード形式の変更処理の場合は、繰り返しの中が複雑になります。

ファイルに関する処理をクラス化したことで、エラー処理などがすっきりとまとめられました。2.1.5で作成したTestCopy()プロシージャではファイルをオープンしたとき、読み書きしたときのエラー処理は書いてありません。実際に書くとなると本来のコピー処理よりもエラー処理の方にステップ数が必要になり、流れも複雑になるでしょう。試しに挑戦してみてください。エラー処理に、かなりの労力を割かなければならないことが理解できるでしょう。

Sub FileCopy()
    Const ForReading = 1    ' 読み取り専用モード
    Const ForWriting = 2    '書込み専用モード

    Dim fsrc As Object  ' コピー元ファイルのオブジェクト
    Dim fdst As Object  ' コピー先ファイルのオブジェクト
    Dim fnm1 As String  ' コピー元ファイルのパス名+ファイル名
    Dim fnm2 As String  ' コピー先ファイルのパス名+ファイル名

On Error GoTo FileCopyError
    Set fsrc = New TextStreamClass      'コピー元ファイルのオブジェクト
    Set fdst = New TextStreamClass      'コピー先ファイルのオブジェクト
    
    fnm1 = InputBox("ファイル名:")     'コピー元ファイル名を入力
    If Len(fnm1) < 1 Then Exit Sub
    
    Call fsrc.fOpen(fnm1, ForReading)   'コピー元ファイルを開く
    If fsrc.error Then Exit Sub         '失敗したら終了
    
    Call fdst.fOpen("", ForWriting)     'コピー先ファイルはデフォルトで作成
    If fdst.error Then Exit Sub         '失敗したら終了
    fnm2 = fdst.fileName                'コピー先ファイル名を記憶

    Do While fsrc.rdNext = True         'コピー元ファイルの終わりまで
        If fdst.wtNext(fsrc.record) = False Then _
            Exit Sub                    'コピーが失敗したら終了
    Loop

    MsgBox "コピー先:" & fnm2          'デフォルトで作成した出力ファイル名を表示

    fsrc.fClose     'コピー元ファイルを閉じる
    fdst.fClose     'コピー先ファイルを閉じる
Exit Sub

FileCopyError:
    MsgBox Prompt:=Err.Description & ":" & Err.Number, Title:="FileCopy"
End Sub

プログラムの流れが理解できたら、実行してみましょう。