ファビコン。井の家紋。エクセルマクロの基本(初級)その2 | エクセルマクロ(VBA)実践蔵(じっせんぐら)

前の項目 - ウィジェット風カレンダーフォームの表示
次の項目 - エクセルマクロの基本(初級)その3

エクセルマクロの基本(初級)その2最終更新日:2023-06-16

上記記事のつづきからになります。

分岐処理

If文動作お試しマクロ

 
Sub ifTest1()

Dim Rng As Range '変数宣言

Set Rng = Range("A1") '変数にセルA1情報を設定

If Rng.Value = "" Then 'セルA1は空白か判定
'上の条件文が正しいなら実行される
MsgBox "セルA1は空です。"
End If

Set Rng = Range("B1") '変数にセルB1情報を設定
If Rng.Value = "" Then 'セルB1は空白か判定
'上の条件文が正しいなら実行される
MsgBox "セルB1は空です。"
End If

'メモリ解放
Set Rng = Nothing
End Sub

If文動作お試しマクロの解説

一番上は変数宣言。この変数をこれから使いますという宣言です。

Range型の変数は、セルの情報を扱います。Range型はオブジェクト型になります。

Sub ifTest1() ~ End Sub の間で使用ができます。オブジェクト型の変数は最後にNothingを設定してメモリを解放するのがお約束です。

オブジェクト型の変数に値を代入する場合は、「Set Rng = Range("A1")」のように前に「Set」が必要になります。その他の変数Long型やString型には「Set」がいりません。

セルA1に何も書かれていなければ、メッセージボックスが表示されます。

続けて、セルB1に何も書かれていなければ、また、メッセージボックスが表示されます。

セルA1やセルB1に何か記入しながら、動作確認をしてみてください。

実行するには、ifTest1の中ならどこの行でもいいので、カーソルを持っていき、F5で実行できます。

If - ElseIf - Else文動作お試しマクロ

下記のコードは、同じくシートオブジェクトに記述します。

 
Sub ifTest2()
Dim Rng As Range '変数宣言
Set Rng = Range("A1") '変数にセルA1情報を設定

If Rng.Value = "" Then 'セルA1は空白か判定
'上の条件文が正しいなら実行される
MsgBox "セルA1は空です。"

ElseIf IsNumeric(Rng.Value) Then
'上の条件文には一致しない、かつ、
'新しい条件文には一致するか判定
MsgBox MsgBox "セルA1には" & Rng.Value & "という数値があるよ"

Else
'上記の条件以外の場合に実行される
MsgBox "セルA1には" & Rng.Value & "と書かれているよ"

End If

'メモリ解放
Set Rng = Nothing
End Sub

If - ElseIf - Else文動作お試しマクロの解説

セルA1の内容によって分岐し、表示されるメッセージボックスが変わります。

セルA1が空欄なら、はじめのIf文に入り、「セルA1は空です。」というメッセージボックスが表示されます。

セルA1に数値のみが書かれている場合は、「セルA1には○○という数値があるよ」というメッセージボックスが表示されます。○○はセルA1の内容になります。小数も数値として判断されます。

セルA1に文字を入力すると、「セルA1には○○という文字列があるよ」というメッセージボックスが表示されます。○○はセルA1の内容になります。

コード内の&は連結の意味で、変数やオブジェクトからの情報と連結できます。Range("A1").Value & Range("B1").Value と変数やオブジェクトからの戻り値同士の連結もできます。

ElseIf文は、If文の中に複数記述することもできます。

試しに、Elseの前の一行に、下記を挿入してみましょう。

 
ElseIf IsDate(Rng.Value) Then
'上の条件文には一致しない、かつ、
'新しい条件文には一致するか判定
MsgBox "セルA1は" & Rng.Value & "という日付があるよ

セルA1に日付を入力してみましょう。上記のコードが無いときは、日付はElse文と一致して、文字列として判断されていました。Elseif文の追加で、日付の判定を追加すると、メッセージボックスで日付があるよと判断が変わることが確認できると思います。

また、セルB1に文字や数値、日付などを入力して、セルA1には「=B1」と入力してみてください。結果がどうなるか、想像しながら確認してみてください。注意事項としては、一度セルに日付を入力するとセルの書式設定が日付となってしまうため、数値を表示させたい場合は、書式設定を標準に変更してください。

次に下記も追加してみましょう。追加する位置は、If文のMsgBoxの下の行です。

 
ElseIf Rng.HasFormula = True Then
'上の条件文には一致しない、かつ、
'セルに計算式があるか判定する
MsgBox "セルA1には" & Rng.Formula & "という計算式があるよ

「Rng.HasFormula」はそれほど使用するものでもないので覚えなくていいです。こんな判定もできる程度でいいです。ここで大事なのは、条件を記述する位置です。If文の「空欄ではない」の次の判定で「計算式か?」の判定を行えば正しく判定されます。しかし、Else文の直前に追加した場合、セルA1が「=B1」とセルB1を参照していて、セルB1に1と数値を入力すると、「ElseIf IsNumeric(Rng.Text) Then」の条件で、先に一致する場合、そのうしろに「セルA1は計算式か?」の判定があったとしても無視させるので、ElseIfを記述する順番は考慮が必要なことを覚えておいてください。(補足:計算式とはセル内で=(イコール)から始まるセルです。

ループ制御

ループの書き方は、いくつかありますがすべては書きません。おすすめのものだけにしたいと思います。

ひきつづき、シートオブジェクトに記述します。

 
Sub LoopTest()

Dim Rng As Range '変数宣言

For Each Rng In Range("A1:A5")
'セルA1,A2,A3,A4,A5とループ

'イミディエイトウィンドウへセルの内容を出力
Debug.Print Rng.Value

Next

'メモリ解放
Set Rng = Nothing
End Sub

一番のおすすめは、For Each 変数 in ○○ ~ Next というループの書き方です。

○○には、複数のまとまった情報(オブジェクトや配列)を記入して、ひとつずつ取り出してループします。Nextの行まで進むと、変数の中身が次に進みます。「~」の部分にコードが入ります。一度しか通らない場合もあります。上記コードの「Range("A1:A5")」を「Range("A1")」と書き換えてもエラーにはなりません。

ループを検証する場合は、イミディエイトウィンドウが便利です。Visual Basicのメニューバーにある「表示」から「イミディエイトウィンドウ」を選んで、表示させてください。「Debug.Print」と記述すると内容をイミディエイトウィンドウへ出力できます。

エクセルのシートのセルA1からA5に、数値でも文字や文章でも日付でもよいので、何か入力してから実行してみてください。セルの内容がイミディエイトウィンドウに表示されるはずです。

この内容をB列に代入してみましょう。For Each文の動作範囲は、「Range("A1:A5")」に戻しておいてください。次に、「Debug.Print Rng.Value」を削除して、「Range("B" & Rng.Row).Value = Rng.Value」に変えてみてください。A列と同じ行数(Rng.Row)のB列にA列のセルの内容がコピーされます。

 
Sub LoopTest2()

Dim Rng As Range '変数宣言
Dim offset As Long

offset = 5 '書き出し位置をずらす
For Each Rng In Range("A1:A5")
'セルA1,A2,A3,A4,A5とループ

'B列へコピー
Range("B" & Rng.Row + offset).Value = Rng.Value

Next

'メモリ解放
Set Rng = Nothing
End Sub

B列の書き込む開始行数をずらしたい場合は、整数を扱うLong型の変数offsetを準備して、Rng.Rowに加算すると、B列のoffset分ずれた行数にA列の内容が書き写されます。

次は、ループとIf文の組み合わせになります。

 
Sub LoopTest3()

Dim Rng As Range '変数宣言

For Each Rng In Range("A1:A5")
'セルA1,A2,A3,A4,A5とループ

If Rng.Value = "" Then 'セルA1は空白か判定
'上の条件文が正しいなら実行される
MsgBox "セル" & Rng.Address & "は空です。"

ElseIf IsNumeric(Rng.Value) Then
'上の条件文には一致しない、かつ、
'新しい条件文には一致するか判定
MsgBox "セル" & Rng.Address & "は数値です。"

Else
'上記の条件以外の場合に実行される
MsgBox "セル" & Rng.Address & "と書かれているよ"

End If
Next
'メモリ解放
Set Rng = Nothing
End Sub

MsgBoxの内容をRng.Addressに変更しました。これで、セルの位置が取得できます。

ループの中でIf文やElseIf文の条件に従って、実行したいコードを書いていくことができます。

Exit Forについて

For Each 変数 in オブジェクト名で回るループは、途中でExit Forを記述すると、最後まで、ループを回らずに、処理を抜けることができます。

Exit Forを使用するケースは、お目当てのものを見つけるまでループして、見つけたらループを抜けます。

セルの位置を探すならFind関数で探す方が記述量は少なくて済みますが、今回はExit Forの説明のため、ループで条件の一致するセルを探しています。

下記のような表があったとして、「住所」と書いてあるセルを見つけることを考えます。

ループの書き方説明用テーブル

 
Sub LoopTest4()

'変数宣言
Dim Rng As Range

'イミディエイトウィンドウへの出力
Debug.Print "UsedRangeのアドレス" & UsedRange.Address
Debug.Print "UsedRangeの一行目のアドレス" & UsedRange.Rows(1).Address

'データが入っている一番上の行
For Each Rng In Range(UsedRange.Rows(1).Address)

If Rng.Value = "住所" Then
Exit For
End If

Next

'ループを途中で抜けたら、ループの外でもRngは使えます
'ループを回りきっても一致する場所がない場合、
'RngにはNothingが入ります。Nothingは = で検証せずに
'下記のように is を使用します。
'If Not は否定でNotを書かないときのElseと同じです。
If Not Rng Is Nothing Then

'探しているセルは見つかったよ
MsgBox Rng.Address & "に見つけたよ"
Else
MsgBox "見つからないよ"

End If

'メモリ解放
Set Rng = Nothing
End Sub

UsedRangeを使用してみました。使われているセルの範囲を取得できます。

コードの内にRows(1)の記述がありますが、エクセルシートの1行目ではありません。

イミディエイトウィンドウへアドレスを出力していますので、確認してみてください。

上の図のシートで実行すると、「UsedRangeのアドレス$B$5:$E$7」「UsedRangeの一行目のアドレス$B$5:$E$5」と表示されます。

Range("B5:E5")の中から一つずつセルの情報をRngに取り出して、ループしています。

そして、見つけたらExit Forでループを抜けます。抜けたら、「For Each 変数」で記述している変数をそのまま使用できます。For ループ内で条件に一致するセルが見つからない場合、ループが最後まで回った後、Nextの行まで進むと、変数にはNothingが入ります。

これを利用して、ループ内でほしい情報が見つかったのか、見つからなかったのかを判定するために、If Not 変数 is Nothing を使用して判定します。 If Not は否定の意味なので、Not を記述しないケースのElse文に該当します。

せっかくテスト用に表まで作っちゃったので、このまま、行も探索して、お目当てのセルを書き換えるテストコードも書いてしまおうと思います。上の表で「赤鬼」さんの「住所」をマクロで見つけて書き換えたいと思います。

 
Sub LoopTest5()

'変数宣言
Dim Rng As Range
Dim 住所列数Lg As Long

'変数を初期化
住所列数Lg = 0

'データが入っている一番上の行
For Each Rng In Range(UsedRange.Rows(1).Address)
If Rng.Value = "住所" Then
住所列数Lg = Rng.Column
Exit For
End If
Next

Set Rng = UsedRange.Find("赤鬼")

If Not Rng Is Nothing And 住所列数Lg > 0 Then

Cells(Rng.Row, 住所列数Lg).Value = "マクロから書き換えよう"
MsgBox "書き換え成功!"
Else

MsgBox "書き換え失敗!"

End If

'メモリ解放
Set Rng = Nothing
End Sub

Cells(行数,列数)は、引数に行数と列数の順で指定します。Range("A1")とは、書き方の順序が逆になるため注意してください。列数を数値で扱うには、Cellsを使用します。

書き方はいろいろあります。住所列数というLong型の変数を増やしましたが、Rangeオブジェクトをもうひとつ増やして書くことも可能です。ただ、簡単な名前の変数名の場合、使いまわすことが多いです。cntやi,j,row,colmn,等。Rngも、Rng1,Rng2などの名前をつけるとややこしくなります。

使いまわさない変数は、用途がはっきりした名前にしておくとどの変数に格納したかを忘れずに使えます。なので、日本語を含めて住所列数Lgとしてみました。日本語も変数名や関数名に使用できます。Lgをつけたのは、Long型からです。すべて日本語だと""で囲い忘れたか?と文字列と見間違える人もいるかもしれないので、文字列と混同しないように日本語と英語を合わせてみました。

今回、初めてIF文内にAndを使っていますが、「かつ」の意味です。Andの代わりにOrとすると「または」になります。左の条件「Not Rng Is Nothing」かつ右の条件「住所列数Lg > 0」の時にIf文の中に入ります。

今回、Find関数の他の引数をかなり省略して記述しています。部分一致や完全一致、大文字と小文字の区別など、細かく指定できます。特に指定しなければ、部分一致になりました。(UsedRange.Find("赤")でもヒットしたので。)検索範囲を広くすると、予定外の部分にもヒットする可能性があることは認識しておいてください。

上記を考慮して、コードを書き直すと下記になります。

 
Sub LoopTest5()

'変数宣言
Dim Rng As Range
Dim NameColRng As Range
Dim 住所列数Lg As Long
Dim 名前列数Lg As Long

'変数を初期化
住所列数Lg = 0
名前列数Lg = 0

'データが入っている一番上の行
For Each Rng In Range(UsedRange.Rows(1).Address)
If Rng.Value = "住所" Then
'住所と一致する場合
住所列数Lg = Rng.Column
ElseIf Rng.Value = "名前" Then
'名前と一致する場合
名前列数Lg = Rng.Column
End If
If 住所列数Lg > 0 And 名前列数Lg > 0 Then
'両方見つけたら、ループ終了
Exit For
End If
Next

'Set Rng = UsedRange.Find("赤鬼")

'名前の列と一致するRangeオブジェクトを探す(列単位にループ)
For Each NameColRng In UsedRange.Columns
If NameColRng.Column = 名前列数Lg Then
'名前列数Lgと列数が一致したら
Exit For
End If
Next

'一致した列があれば
If Not NameColRng Is Nothing Then
'Find関数で探す
Set Rng = NameColRng.Find("赤鬼")
Debug.Print "探す範囲は、" & NameColRng.Address
End If

'Find関数で見つかった、かつ、住所の列数を特定済みなら
If Not Rng Is Nothing And 住所列数Lg > 0 Then

Cells(Rng.Row, 住所列数Lg).Value = "もう一度挑戦!"
MsgBox "書き換え成功!"
Else

MsgBox "書き換え失敗!"

End If

'メモリ解放
Set Rng = Nothing
Set NameColRng = Nothing
End Sub

あとは、Excel特有のオブジェクトの使い方や、文字列操作(ReplaceやStrComp/InStr)の使い方、になってくるかと思います。

前の項目 - ウィジェット風カレンダーフォームの表示
次の項目 - エクセルマクロの基本(初級)その3