初心者のためのExcelマクロ超入門(絶対できるVBA開発)

マクロがまったくわからない人のためにエクセルマクロやVBAについてできるだけわかりやすく書いています。Twitter:@shuhhohhey

初心者のためのExcel(エクセル)マクロVBA入門-成績表マクロの作成:エラーチェックその2

続きです。では後はIf文の中に日本語で書かれているところをちゃんとチェックできるようなものに変えていくだけです。いくつかチェックがありますが、順番にやっていきましょう。

入力されているかは""(空文字)で調べる


数字が入力されてない場合は、単純に入力されている場所の値が空文字かどうかを調べればよいのです。空文字は「""」で表現できます。VBAプログラミングのお約束として文字列は「"」ダブルクオートで囲む。というのがありましたね。何もない文字をダブルクオートで囲むので、結果的に「""」となるわけです。

    ' 出席番号が入力されてない場合
    If shtScore.txtSyussekiNumber.Value = "" Then
        '' 出席番号を入力してください。
        strErrMsg = mdlDefine.ERROR_MSG1
        
        InputCheck = False
        Exit Function
        
    End If

結果こうなります。上のコードは前回の出席番号が入力されてない場合の部分だけを抜き出しています。If文の中身が変わっているだけです。「shtScore.txtSyussekiNumber.Value = ""」ですね。If文の後は条件式なので、直訳すると

成績表シートにある出席番号入力フォームの値が空文字だったら・・・

となります。きちんとshtScoreとシートオブジェクトを指定しましょう。何度でも言いますが、正しくオブジェクトを指定することがとても大事だからです。shtScoreと自分でシートオブジェクトに名前を付けたのですから、指定するのは簡単ですよね。

数字かどうか調べてくれる便利な関数IsNumeric


次は出席番号が数字かどうか?ですね。これを調べるにはIsNumeric関数を使います。IsNumeric関数は引数の値が数字かどうかを判定して、数字ならTrue、数字じゃないならFalseを返してくれます。これを使って次のチェックを書きます。

    ' 出席番号入力に数字以外を入力した場合
    If Not IsNumeric(shtScore.txtSyussekiNumber.Value) Then
        '' 出席番号が存在しません。処理を終了します。
        strErrMsg = mdlDefine.ERROR_MSG2
        
        InputCheck = False
        Exit Function
        
    End If


注意してほしいのはIf文の中にNotが入ってることです。これで「数字じゃないなら」となります。条件を満たしたら処理を実行したいわけですから、ここで満たす条件は数字ではない場合です。ですのでIf Not ~となります。引数の中身は当然、フォームの内容を入れればいいわけですからshtScore.txtSyussekiNumber.Valueとなりますね。

複数の数を一気にチェックするには・・・


問題は次ですね。成績表の点数をすべて調べないといけません。今回は指定された出席番号だけなく、成績表全体を調べます。なぜなら全体での成績表の出力も考えているからです。
成績表全部の点数をチェックにするには・・・どうしたらいいのでしょうか?
さすがに、条件式1つぽんっと入れるだけではできそうにありません。

自分でチェックするメソッドを作る!


答えは自分でチェックするメソッドを作り、そのメソッドがTrueやFalseを返せば後はIf文でその結果で判定すればいいのです。つまり。

If文でチェックするメソッドを呼び出す。

メソッドで成績表の点数をチェックする

TrueまたはFalseを返す

If文で判定される


という流れです。では早速メソッドを考えます。成績表をチェックするメソッドなのでCheckScoreというメソッドにしましょうか?メソッドはこのチェックの中だけでしか呼ばれないのでPrivateで作ります。したがって・・・

    ' 成績表の点数に数字以外が入っていた場合
    If Not CheckScore Then
        '' 成績表の点数が不正です。処理を終了します。
        strErrMsg = mdlDefine.ERROR_MSG3
        
        InputCheck = False
        Exit Function
        
    End If

として、成績表チェックをIf文の中で呼び出し・・・

''''''''''''''''''''''''''''''''''''''''''''''
' 成績表チェックするメソッド
' 戻り値:Boolean(True/False)
' 成績表の点数が数字じゃないならFalseを返す。
''''''''''''''''''''''''''''''''''''''''''''''
Private Function CheckScore() As Boolean

    '成績表をチェックする
    

End Function

としました。これで後はこのメソッドの中で成績表の点数を1つずつ調べて、点数かどうかをチェックすればいいのです。使う構文はIf文とFor文です。さて、わかりますでしょうか・・・

成績表の図

f:id:drumer2sh:20131024100342p:plain

成績表はC列からG列まであり、何行あるか?は不明です。今は10人ですが、100人かもしれないし、10000人かもしれません。2次元の表の内容を順番に調べたいのですが・・・

多次元の時は繰り返しを入れ子で書く


と覚えておくとほぼ間違いないです。今回は全部の成績表を調べるので、こうなります。懸念点はあるのですが・・・とりあえずまずはやってみましょう。もういきなり答えを書いてしまいます。

''''''''''''''''''''''''''''''''''''''''''''''
' 成績表チェックするメソッド
' 戻り値:Boolean(True/False)
' 成績表の点数が数字じゃないならFalseを返す。
''''''''''''''''''''''''''''''''''''''''''''''
Private Function CheckScore() As Boolean

    Dim i As Long, j As Long
    Const COL_C = 3
    Const COL_G = 7
    Const SCOREDATA_START_ROW = 4
    
    '成績表をチェックする
    ' 4行目から成績表の最終行まで繰り返す
    For i = SCOREDATA_START_ROW To shtScore.Cells(Rows.Count, 1).End(xlUp).Row
        
        ' 算数(C列)から英語(G列)まで繰り返す
        For j = COL_C To COL_G
            
            ' 数字じゃなかったら・・・
            If Not IsNumeric(shtScore.Cells(i, j).Value) Then
                
                'Falseを返してメソッドを終わる
                CheckScore = False
                Exit Function
                
            End If
        
        Next
        
    Next
    
    '全部チェックして問題なければTrueを返す
    CheckScore = True

End Function

これが答えです。1個ずつ見ていきましょうね。

Dim i As Long, j As Long

変数を作っていますが、こんな書き方もできます。以前は2行に分けて書いていたと思いますが、こんな書き方もできちゃいます。
注意しないといけないのは、「Dim i, j As Long」とは書けない。ということです。このように書くと、jはLong型ですがiは汎用型(Variant)になってしまいます。

定数を定義

メソッドの中で定数を書いてます。これはできるだけ数字そのものを入れたくないからです。VBAが独自に用意しているvbYesとかと同じで、数字を言葉で置き換えることで文章を読みやすくしています。ここでは、C列の番号をCOL_C、G列の番号と成績表のデータが始まる行をSCOREDATA_START_ROWとしています。

For i = SCOREDATA_START_ROW To shtScore.Cells(Rows.Count, 1).End(xlUp).Row ~

ここから最後の2つ目のNextまででチェックしています。フロー順に書くと・・・

  1. 4行目のC列の点数を数字かどうかチェックする
  2. 4行目のD列の点数を数字かどうかチェックする
  3. 4行目のE列の点数を数字かどうかチェックする
  4. 4行目のF列の点数を数字かどうかチェックする
  5. 4行目のG列の点数を数字かどうかチェックする
  6. 5行目のC列の点数を数字かどうかチェックする

……

となっていて、数字ではない時点でFalseを返してメソッドを中断しています。

f:id:drumer2sh:20131029102532p:plain


この図のように順番に数字の値をチェックしているわけです。変数iと変数jに順番に(4, 3)、(4, 4)、(4, 5)・・・となっていくのです。
しかし、このプログラムは非常に難しいところがあります。それは例えば1000人の生徒がいたとすると、成績表チェックは全部5000回も実行されるということです。10人くらいなら問題ないですが、5000回は結構な数です。パフォーマンスに影響するので今はいいですが心にとめておくと良いと思います。

出席番号があるか調べるにはFind関数?For文?


長くなってきたので、あと1つだけ次の出席番号が存在しているか?を調べましょう。出席番号はA列にあるので、For文で回して・・・となるから出席番号のチェックメソッドを作ろう!こうだ!

For For i = SCOREDATA_START_ROW To shtScore.Cells(Rows.Count, 1).End(xlUp).Row

    If shtScore.Cells(i, 1).Value = shtScore.txtSyussekiNumber.Value then
        '見つかった
        CheckSyusseki = True
    End If
Next

' 見つからない
CheckSyusseki = False


はい、ダメ全然ダメ。先ほども書きましたが、これだと10人だったらいいですが、生徒が全国10万人いたらどうするのですか?10万回繰り返すことになりませんか?
このようにして、「遅いマクロ」が出来上がっていきます。確かにプログラミングとしては間違ってませんが、これではまだExcelの本気を出しきっていないことになります。なぜ?エクセルでVBAなんぞしてるんですか?

Excelの機能が全部使えるから

です。あなたは自分で出席番号があるか調べる時にどうしますか?1行ずつ見ていきますか?違いますよね?
さっさとCtrl+Fを押して「検索」しますよね?
手動で出来るエクセルの操作は全部VBAで書けるんです。だから、早い方で書きましょう。
検索はFindメソッドです。こいつの使い方はこのブログのこの回でやっています。

検索範囲.Find(What:=検索したい文字)

はい、終了。ヘルプを見ればわかりますが、見つからない場合にFindメソッドはNothingを返します。したがって、、、、

    ' 存在しない出席番号が入力された場合
    If shtScore.Range("A:A").Find( _
            What:=shtScore.txtSyussekiNumber.Value _
            ) Is Nothing Then Then
        
        '' 出席番号が見つかりません。処理を終了します。
        strErrMsg = mdlDefine.ERROR_MSG4
        
        InputCheck = False
        Exit Function
        
    End If

わかりやすく改行してメソッドを書いてます。1行で書くと

If shtScore.Range("A:A").Find(What:=shtScore.txtSyussekiNumber.Value) Is Nothing Then

となります。引数Whatに入力された出席番号を入れていますね。後はFindメソッドの結果がNothing。つまり検索できなかったら、エラーになります。

特にメソッドを自分で作るわけでもなく、Findメソッドなら生徒が何人いてもまったく関係なく1度だけでいいわけです。この差はものすごく大きいですよね?1回と10万回。どちらが早いかは明白です。

さて、あとはファイルとフォルダの存在チェックですが、これはまた次回で。

チェックは長いですが、やっていることは、1つ1つを判定して、エラーならFalse、問題ならTrueとなっているだけです。


今日はここまで。

かしこ