[一覧]
[入門編1]
[入門編2]
[入門編3]
[実践編]
[トラブル編]
マクロ記録とヘルプ
Excel97の Visual Basic Editor のメニューからヘルプを起動した場合は、[目次]を選択し、[Microsoft Excel Visual Basic リファレンス]-[Microsoft Excel Visual Basic リファレンスへのショートカット]をダブルクリックしてから、もう一度検索してみてください。
また、モジュール上で調べたい単語の中にキーボードカーソルを置き、F1キーを押すことでも検索が可能です。
Excel5.0/95では記録開始は[ツール]-[マクロの記録]-[新規マクロの記録]、終了は[ツール]-[マクロの記録]-[記録終了]です。 Excel97では記録開始は[ツール]-[マクロ]-[新しいマクロの記録]、終了は[ツール]-[マクロ]-[記録終了]です。
モジュールが挿入され、マクロが記録されますので、そこで使われている単語の意味をヘルプでひとつひとつ確認します。
記録できない内容の場合は、やりたいことをヘルプのキーワード検索や、アンサーウィザード(Excel95)、Officeアシスタント(Excel97)で検索してみてはいかがでしょう。
具体的にどんな命令文を使うのかは、「マクロ記録してヘルプを見る」という方法で、たいてい調べることができます。
では、がんばってください。
Sheet1やThisWorkbookは特定のイベントプロシージャを記述するためのモジュールです。特定のイベントプロシージャというのは、ワークシートが表示されたときに自動実行されるプロシージャなどのことです。それ以外のプロシージャを記述しても動かないことがあります。
また、Sheet1やThisWorkbookのイベントプロシージャでは、実行しようとしても無視されてしまうメソッド等もありますので、実際のマクロは標準モジュールに記述し、イベントプロシージャにはそのマクロを実行するコードだけを記述する方が無難です。
「コントロールツールボックス」ツールバーで作成するコントロールの方が新しく、より高機能ですが、扱いにくい部分もあります。
実行制御
ほとんどの場合、マクロ終了時には自動的にTrueに戻りますが、Excel97ではツールバーボタンからマクロを実行した場合、自動的にTrueに戻りません。バグか仕様か知りません。
Sub DeleteActiveSheet() '警告メッセージを表示しないようにします Application.DisplayAlerts = False 'アクティブシートを削除します ActiveSheet.Delete '警告メッセージを表示するようにします Application.DisplayAlerts = True End Sub
ただし、大きなセル範囲がコピーされている場合は、クリップボードのデータに対する処理を選択するメッセージが表示されます。クリップボードをクリアして良い場合は、コピーモードを解除してから閉じるようにします。
'アクティブブックを閉じるマクロ1 Sub CloseActiveWorkbook1() 'コピーモードを解除します Application.CutCopyMode = False 'アクティブブックを保存せずに閉じます ActiveWorkbook.Close False End Sub 'アクティブブックを閉じるマクロ2 Sub CloseActiveWorkbook2() 'コピーモードを解除します Application.CutCopyMode = False 'アクティブブックを保存済みの状態にします(実際に保存はされません) ActiveWorkbook.Saved = True 'アクティブブックを閉じます ActiveWorkbook.Close End Sub
クリップボードをクリアしたくない場合は、Application.DisplayAlertsを使います。ただし、クリップボードへデータを再登録するため、コピーデータが大きい場合は時間がかかります。
'アクティブブックを閉じるマクロ3 Sub CloseActiveWorkbook3() '警告メッセージを表示しないようにします Application.DisplayAlerts = False 'アクティブブックを保存せずに閉じます ActiveWorkbook.Close False '警告メッセージを表示するようにします Application.DisplayAlerts = True End Sub
ほとんどの場合、マクロ終了時に自動的にTrueに戻りますが、戻らない場合もあるようですので、Trueに戻す方が無難でしょう。例えば、Excel97では、Workbook_BeforeCloseにApplication.ScreenUpdating = False と書き、マクロからApplication.Quitを実行すると、元に戻らずにExcelがハングアップしたように見えます。
画面更新をしないようにするとマクロの性能が向上します。
Application.ScreenUpdating = False '処理... Application.ScreenUpdating = True
ダイアログボックスが繰り返し表示される場合はなかなか止まりませんが、Escキーを一定時間押し続けるとたいてい止まります。
Excel97ではThisWorkbookにWorkbook_Openというイベントプロシージャがあり、これもファイルを開いた場合に実行されますが、状況によって無視されてしまう処理があったり、マクロを使ってファイルを開く場合にも不都合があるので、おすすめはしません。
'複数のボタンに以下のマクロを登録して、各ボタンをクリックしてみてください。 Sub MyButton_Click() Dim obj As Object Set obj = ActiveSheet.DrawingObjects(Application.Caller) MsgBox "押されたボタンは " & obj.Name & "(" & obj.Caption & ") です。" End Sub
セル範囲の操作
もしアクティブシートがSheet2であれば、このステートメントは「Sheet1の、Sheet2のA1からSheet2のC3までの範囲を選択する」という、おかしな意味になってしまいます。
取得するもの | 記 述 |
絶対参照($B$3) | ActiveCell.Address |
相対参照(B3) | ActiveCell.Address(False,False) |
行番号(3) | ActiveCell.Row |
列番号(2) | ActiveCell.Column |
'選択範囲の行番号と列番号を取得するサンプルマクロ '複数のセル範囲を選択した状態では、最初の範囲だけを対象にします。 Sub Sample_RowAndColumn() Dim iRow_First As Long Dim iRow_Last As Long Dim iColumn_First As Long Dim iColumn_Last As Long '選択範囲の最初の範囲を対象にします With Selection.Areas(1) '先頭行番号と先頭列番号の取得 With .Cells(1, 1) iRow_First = .Row iColumn_First = .Column End With '最終行番号の取得(先頭行番号 + 選択行数 - 1) iRow_Last = iRow_First + .Rows.Count - 1 '最終列番号の取得(先頭列番号 + 選択列数 - 1) iColumn_Last = iColumn_First + .Columns.Count - 1 End With '結果の表示 MsgBox CStr(iRow_First) & "行" & CStr(iColumn_First) & "列" _ & " - " & CStr(iRow_Last) & "行" & CStr(iColumn_Last) & "列" End Sub
プログラミング
基本的に、VBAでは実数は浮動小数点数型(Double、Single)で扱われます。浮動小数点数型では、ほとんどの10進小数を正確に表現することができません。4.1も0.1も浮動小数点数型には存在しません。近似値として扱われます。つまり、4.1の近似値-0.1の近似値=4の近似値 となり、実際には4よりもわずかに小さな値になります。そのためInt関数で切り捨てると3になってしまいます。
実数計算の誤差の対策は単純ではありません。無理をせず、データの格納と計算は整数で行ってはいかがでしょう。例えば以下のマクロでは、実数を10倍して整数化してから計算と切り捨てを行っています。
Sub Calc_Integer() Dim x As Integer, y As Integer, z As Integer x = Application.Round(4.1 * 10, 0) y = Application.Round(0.1 * 10, 0) z = (x - y) \ 10 MsgBox z End Sub
計算の前に時間や分の整数値に変換してはいかがでしょう。
例えば、日付型変数Date1を時間数に変換するには、Day(Date1) * 24 + Hour(Date1) 、分数に変換するには Day(Date1) * 24 * 60 + Hour(Date1) * 60 + Minute(Date1) などとします。
Sub Calc_Date1() Dim dateStart As Date Dim dateEnd As Date '今日の日付を取得します dateStart = Date '10日後の日付を計算します dateEnd = dateStart + 10 MsgBox "10日後は " & _ Format$(dateEnd, "m""月""d""日""") & " です。" End Sub
2週間後の日付は、2 x 7日後の日付です。
Sub Calc_Date2() Dim dateStart As Date Dim dateEnd As Date '今日の日付を取得します dateStart = Date '2週間後の日付を計算します dateEnd = dateStart + 2 * 7 MsgBox "2週間後は " & _ Format$(dateEnd, "m""月""d""日""") & " です。" End Sub
年や月の単位で計算する場合は、年、月、日の数値に分けてから、DateSerial関数を使って計算します。年、月、日はそれぞれ、Year関数、Month関数、Day関数を使って取得します。
ただし、DateSerial関数では、例えば5月31日の1月後を計算すると6月31日はないので7月1日になります。以下のサンプルコードでは、月を超えた場合は、月の最終日を得るようにしています。
Sub Calc_Date3() Dim dateStart As Date Dim dateEnd As Date Dim iDay_Start As Integer Dim iDay_End As Integer '開始日を設定します dateStart = DateValue("1998/5/31") '開始日の日を取得します iDay_Start = Day(dateStart) '1年1月後の日付を取得します dateEnd = DateSerial( _ Year(dateStart) + 1, Month(dateStart) + 1, iDay_Start) '1年1月後の日付の日を取得します iDay_End = Day(dateEnd) 'もし、月の最終日を超えて翌月の日付になっていたら '前月の最終日を計算します If iDay_Start <> iDay_End Then dateEnd = dateEnd - iDay_End End If MsgBox Format$(dateStart, "yyyy""年""m""月""d""日""") & " の1年1月後は " _ & Format$(dateEnd, "yyyy""年""m""月""d""日""") & " です。" End Sub
変数も中に入れるデータ型がそれぞれ決まっています。
代入や比較はデータ型の同じものの間で行うことが基本です。異なるデータ型の間では、特殊な変換や比較が行われる場合もあり、期待する結果を得られないことがあります。
例えば、小数を含む数値を整数型の変数に代入する場合、小数点以下は四捨五入されるわけではありません。切り捨てでも切り上げでもありません。
以下の2つのIfステートメントはどちらも "1" = 1 という比較をしていますが、変数のデータ型によって結果が異なります。
Sub Test_Type() Dim a As String Dim b As Variant Dim c As Variant a = "1" b = "1" c = 1 If a = c Then MsgBox "True" Else MsgBox "False" If b = c Then MsgBox "True" Else MsgBox "False" End Sub
なお、VBAにはCInt() CLng() CDbl() CStr() などのデータ型を変換する関数が用意されています。(ただし、CIntもCLngも小数点以下を四捨五入するわけではありません。切り捨てでも切り上げでもありません。)
例えば、MySubがSubプロシージャ、MyFunctionがFunctionプロシージャのとき、 y = MyFunction(x) という使い方はできますが、y = MySub(x) という使い方はできません。
Sub Sample_StrConv() Dim sText1 As String Dim sText2 As String sText1 = "ABC" '全角に変換します sText2 = StrConv(sText1, vbWide) '半角に変換します sText1 = StrConv(sText2, vbNarrow) '小文字に変換します sText2 = LCase(sText1) '大文字に変換します sText1 = UCase(sText2) sText1 = "あいうえお" 'カタカナに変換します sText2 = StrConv(sText1, vbKatakana) 'ひらがなに変換します sText1 = StrConv(sText2, vbHiragana) End Sub
変数の有効範囲とは、ある変数が使えるプログラム上の範囲のことです。例えば、プロシージャの中で定義した変数の有効範囲は、そのプロシージャの中だけです。他のプロシージャで同じ名前の変数を使っても、それはまったく別の変数として扱われます。
変数の有効範囲には、プロシージャ、プライベート、パブリックの3つのレベルがあります。
レベル | 名前 | 定義方法 | 有効範囲 |
プロシージャ | ローカル 変数 | プロシージャの中で Dim または Static ステートメントを使う | 定義したプロシージャの中 |
プライベート | プライベート 変数 | モジュールの先頭で Dim または Private ステートメントを使う | 定義したモジュールの中 |
パブリック | パブリック 変数 | モジュールの先頭で Public ステートメントを使う | 定義したプロジェクト(Excelブック) および プロジェクトを参照設定している他のプロジェクト |
レベルの異なる同名の変数がある場合は、有効範囲が狭い方の変数と見なされます。例えば、モジュールの先頭に Dim i As Integer、プロシージャの中でも Dim i As Integer と書かれている場合、そのプロシージャの中で変数iを使うと、それはプロシージャの中で定義した変数iと見なされます。
同名の変数を区別して扱いたい場合は、変数名の前にプロジェクト名やモジュール名を付けます。以下のコードでは、Book1.xlsのModule1に定義したプライベート変数(モジュール内で有効な変数)を同名のローカル変数と区別して扱っています。
Private i As Integer Sub Test_Name() Dim i As Integer i = 100 [Book1.xls].[Module1].i = 200 MsgBox i MsgBox [Book1.xls].[Module1].i End Sub
このような有効範囲のレベルはプロシージャにもあります。
レベル | 名前 | 定義方法 | 有効範囲 |
プライベート | プライベート プロシージャ | Private Sub プロシージャ名() または Private Function プロシージャ名() | 定義したモジュールの中 |
パブリック | パブリック プロシージャ | Public Sub プロシージャ名() または Public Function プロシージャ名() | 定義したプロジェクト(Excelブック)とそのプロジェクトを参照設定している他のプロジェクト |
PrivateやPublicを省略すると、Publicを指定したものと見なされ、パブリックプロシージャになります。
例えば、ローカル変数(プロシージャレベルの変数)の場合、変数はプロシージャの終了時に自動的に消去されるので、有効期間はDimステートメント等で定義されてから、プロシージャが終了するまでです。ただし、プロシージャレベルの変数でも、Staticステートメントで定義するとプロシージャ終了時に消去されず、次にプロシージャを実行したときに前の値が残っています。
レベル | 名前 | 有効期間 |
プロシージャ | Dimで定義したローカル変数 | 定義からプロシージャの終了まで |
プロシージャ | Staticで定義したローカル変数 (スタティック変数) | プロジェクトの開始から終了またはリセットまで |
プライベート | プライベート変数 | プロジェクトの開始から終了またはリセットまで |
パブリック | パブリック変数 | プロジェクトの開始から終了またはリセットまで |
プロジェクト(Excelブック)がリセットされるのは、ユーザーによるリセット操作(メニューコマンド等)や、Endステートメントの実行、プロジェクトの変更、が行われたときです。コードを編集したときはもちろん、ワークシートの挿入や削除でもリセットされることがあります。プロジェクト実行中に確実に保持していたいデータは、ワークシートのセルへ入れておく方が無難です。