文例を見てもなかなかしっくりこないものです。
マクロを使う上でのファイル(Book)の関係や命令(メソッド)の詳細を解説します。
全てデータを一人で入力して加工する場合は全く必要のないことですが、他の人にデータを入力してもらったり、サーバからダウンロードしたデータをExelに呼び込んで処理する場合などは必要に応じてBookとモジュールの関係性を整理しておいた方が良いでしょう。
その前に呼び出す(実行する)プロシージャを選択できるメニューの作り方を簡単に説明します。
メニューの作り方
開発-マクロの画面からモジュールを呼び出すのが基本ですが、メニュー画面を作成し、そこにコマンドボタンを配置することで呼び出しやすくなります。
手順は以下の通りです ①開発リボンから 【挿入】【フォームコントロール】【ボタン】を選択する
②③登録したいモジュール(プロシージャ)を選択して【ok】を押す
④ ボタンの表示を変える(右クリックで)【テキストの編集】を選ぶとボタンの表示が変更できます。
マクロを登録するBookを決めてそこにモジュールを登録し、必要があれば他のBookからデータを貼り付けて使用するのが基本となります。
イメージとしては右の図のようになります。黄色はマクロのモジュールが登録されていることを意味しています。すべてのSheet名はこのBookにあるため、Sheet名が重複することはありません。
(2) - ① マクロを持っていないBookからマクロを持っているBookの中のマクロを呼び出す
マクロを持たないBookとマクロを持っているBookを両方開きます。次にマクロを持たないBookから開発⇒マクロを選ぶと別ファイルのマクロを呼び出せます。マクロ作成者がそのままデータ管理をする場合の安直な方法です。
ただし呼ばれる側のモジュールはSheetXX.selectを外すなどの配慮が必要です。(開いているSheet以外に書き込みをしないため)
Bookの実線は書き込み処理有り、破線は読み込みだけを意味します。
(2) - ② マクロを持っているBookからマクロを持っていないBookを操作する。
これはBookの開き方は以下の(3)と同じです。ただUpdateするのがマクロを持たないBookなのか、マクロを持っているBookなのかの違いです。配信が必要なBookなどはxlsmは不適なのでこの方法を用います。
(1)の処理と類似していますが、他のBookから貼り付けるデータがある時次のような効果があります。
データを呼び込む時はシートに対して新規上書き、追加などできますが、前者の時はエリアの事前クリアーが必要になります。後者の場合は追加ポイントをどこかに記憶させておくことをおすすめします。
マクロの登録されているBookからマクロを起動させたら後は他のBookを複数開いて、そのBook間でデータ移動・集計などの処理をします。次のような場合にこの方法をとります。
(5)具体的に複数のBookを開いて扱うモジュールの一例
下記のモジュールはそのマクロが入っているシートから別のworkbookの各Sheet1 Range("C3") を表示してBookをクローズするものです。
複数のBook、複数のSheetを扱う時にはWorkbookオブジェクトとWorksheetオブジェクトを使うことをお勧めします。下記の①と②、③と④は全く同じ処理をした結果となります。記述がすごく簡単になりますね。
特にファイルを開いて閉じる場合には
A.開く場合 ⇒ ディレクトリを含めたフルネーム
B.閉じる場合 ⇒ Excelのファイル名のみ 2つの名前を使い分けないといけませんが workbookオブジェクトを使うと
それが1つで済みます。
またWorksheetオブジェクトを使用すれば、Book名を記述せずに直接Sheetを参照できます。
Sub A020_データ表示()
Dim xlBook1, xlBook2, xlBook3 As Workbook
Dim Fname1, Fname2, Fnmae3 As String
Dim sh_1, sh_2 As Worksheet
Set xlBook1 = ActiveWorkbook
Worksheets("環境設定").Select
Fname2 = Range("C2")
' RANGE("C2") の中身 C:\Users\zuikak\OneDrive\ドキュメント\2022\VBATEST\VBATEST2.xlsx
Fname3 = Range("C3")
' RANGE("C2") の中身 C:\Users\zuikak\OneDrive\ドキュメント\2022\VBATEST\VBATEST3.xlsx
'ファイルが存在するかチェックして、存在しなければメッセージを出して終了する
If Dir(Fname2) <> "" Then
'Bookを開くと同時に Workbook オブジェクトにセット
Set xlBook2 = Workbooks.Open(Fname2)
Else
MsgBox Fname2 & " ファイルが存在しません。プログラムを終了します"
Exit Sub
End If
'worksheet オブジェクトにセット
Set sh_1 = xlBook2.Sheets("sheet1")
If Dir(Fname3) <> "" Then
Set xlBook3 = Workbooks.Open(Fname3)
Else
MsgBox Fname3 & " ファイルが存在しません。プログラムを終了します"
Exit Sub
End If
Set sh_2 = xlBook3.Sheets("sheet1")
MsgBox Workbooks("VBATEST2.xlsx").Sheets("Sheet1").Range("C3") '①
MsgBox sh_1.Range("C3") '②
MsgBox sh_2.Range("C3")
Workbooks("VBATEST2.xlsx").Close '③
'xlBook2.Close '④
xlBook3.Close
End Sub
コピーし・貼り付けする時、For文でループさせるにセルの位置関係を変数表記で正確に表現することが必要になります。VBAではセルの表現方法は一般的に3種類あります。
しかも単一セルと複数セルの表現が異なるのでなれるまで少したいへんです。
以下3つの例を示しますが前提となるセルは
単一・・・B3 範囲・・・B3:D8です
変数は i = 3 j = 8 の前提です
単一 Range(B & "i")
範囲 Range(B & "i" & ":D" & j)
単一 Cells(i,2)
範囲 Range(Cells(i,2),Cells(j,4)
単一 Cells(i,”B”)
範囲 Range(Cells(i,"B"),Cells(j,"D")
(2)の場合は列にも変数を用いることができます。普段私は単一セルでは(3)、範囲を指定する時には(1)を使う時が多いですが、特に理由はありません。習慣の問題です。
プログラミングではIF文が重要な役割を持ちます。
様々な属性を持ったデータをそれぞれに適した扱いをするにはIF文が必要になります。
ここでは少し踏み込んだ使用方法を紹介します。
単一同じIF文でも使い方で3つの形をとります
1.基本型 IF Cells(i,"C") > Cells(i,"B") Then 'もしi行C列の値がi行B列の値より大きければ
Cells(i,"D") = 1 'i行D列の値を 0 とする
else
Cells(i,"D") = 0 'そうでなければ1とする
End if
2.elseを持たない形
①IF Cells(i,"C") > Cells(i,"B") Then 'もしi行C列の値がi行B列の値より大きければ
Cells(i,"D") = 1 'i行D列の値を 1 とする
End if
同じ機能で短縮形
②IF Cells(i,"C") > Cells(i,"B") Then Cells(i,"D") = 1
2.のケースではあらかじめ Cells(i,"D")に0を代入しておくのが一般的です
上記は一つの判断条件だけでしたが複数条件の判定が必要な場合があります。
(1)の判定の他にたとえば年齢条件を加えることにします。
アンド条件・・・20歳以上であることが必要となる。20歳以上でなければCells(i,"D")は0となる
オア条件 ・・・20歳以上であれば(1)の判定に関係なくCells(i,"D") は 1 となる
1-1.アンド条件の例 基本型
IF Cells(i,"C") > Cells(i,"B") Then 'もしi行C列の値がi行B列の値より大きければ
IF 年齢 >= 20 Then
Cells(i,"D") = 1 'i行D列の値を 1 とする
else
Cells(i,"D") = 0 'そうでなければ0とする
End if
else
Cells(i,"D") = 0 'そうでなければ0とする(対象がわかりずらい)
End if
1-2.アンド条件の例 elseを極力使わない方法
Dim F1 as Long
Dim F2 as Long
・・・・・・・・・・・・・・・・・・・・ここからループ文(Forなど)
F1 = 0
F2 = 0
IF Cells(i,"C") > Cells(i,"B") Then 'もしi行C列の値がi行B列の値より大きければF1を1とする
F1= 1
End if
IF 年齢 >= 20 Then 'もし年齢の値が20以上ならばF2を1とする
F2 = 1
End if
Cells(i,"D") = F1 * F2
個々の条件が成立しているかどうかをで記憶しておき、あとで掛け合わせれば一つでも不成立の時に結果は0となり全て成立している時だけ結果が1となります。
2-1.オア条件の例 基本型
IF Cells(i,"C") > Cells(i,"B") Then 'もしi行C列の値がi行B列の値より大きければ
Cells(i,"D") = 1 'i行D列の値を 1 とする
else
IF 年齢 >= 20 Then
Cells(i,"D") = 1 'i行D列の値を 1 とする
End if
else
Cells(i,"D") = 0 'そうでなければ0とする
End if
2-2.オア条件の例 elseを極力使わない方法
Dim F1 as Long
Dim F2 as Long
・・・・・・・・・・・・・・・・・・・・・ここからループ文(Forなど)
F1 = 0
F2 = 0
IF Cells(i,"C") > Cells(i,"B") Then 'もしi行C列の値がi行B列の値より大きければ
F1 = 1
End if
IF 年齢 >= 20 Then 'もし年齢の値が20以上ならばF2を1とする
F2 = 1
End if
F1 = F1 + F2
IF F1 > 1 Then
Cells(i,"D") = 1
else
Cells(i,"D") = 0 'そうでなければ0とする
End if
個々の条件が成立しているかどうかをで記憶しておき、あとで足し合わせればどれか一つでも成立したときに判定フラグは1以上になります。
1-1や2-1を理解することは(人のプログラムを流用したりする時に)必要ですが、あとは自分でプログラムを見直した時に理解しやすい方法が一番です。特に3レベル以上の条件になると else はそこから上位の判定を背負うことになりますので、見直す時など大変になります。
Ifの判定がyes,noではなく複数の選択肢からなる場合はSelect Case文を使うのが一般的かと思います。
例として年齢別の料金で考えてみます
年齢が60歳以上なら3000円、30歳以上60才未満なら5000円、23歳以上30歳未満なら4000円、23歳未満なら2000円という料金があったとします。1-1の方法をとるとIfとelseをかなり深いレベルまでさげていかなければな
りません。
1-2に準じた方法で
①60歳以上の場合
②30歳以上60歳未満の場合
③23歳以上30才未満の場合
④23才未満の場合
と独立した条件判定を設ければIf文のレベルは最大2レベルで抑えることができます。しかし②③に関しては2つのIf判定が必要になり、また4つのケースが全ての条件を満たしているのか、①~④が全て同じレベルの並列の判断なのかデバッグする時少し悩む可能性があります。そこで使われるのがSelect Case文です
1.基本型
Select Case 年齢.value (あるいは Cells(i,"F").value など)
Case Is >= 60
料金 = 3000 (あるいは Cells(i,"H") = 3000など
Case Is >=30
料金 = 5000
Case Is >=23
料金 = 4000
Case else
料金 = 2000
End Select
この文を使うと一つの条件が成立すれば他のCase以下は判定せずにEnd Select までジャンプしてくれるので
結果のデバッグもしやすいし、If文の節約にもなります。但し、年齢の他に条件がある場合(男女別の料金差など)片方ではCase文の判定、以下はIf文の判定となり解りにくくなる可能性もあります
2.SelectCase文を使わない方法
SelectCase文を使わない場合、一つの判定がされたらそれ以下は判定しないという仕組みが必要となりますがIf文は『exit』することができません。そこでダミーでFor文を入れることを考えました。For文はダミーで1回しか実行されません。
料金 = 0
For i = 1 to 1
If 年齢 >= 60 Then
料金 = 3000
Exit For
End If
If 年齢 >= 30 Then
料金 = 5000
Exit For
End If
If 年齢 >= 23 Then
料金 = 4000
Exit For
End If
料金 = 2000
Next
文は少し長くなりますが、何と何を比較しているのかわかりやすくなるかもしれません。
お好みの方法をお使い下さい。