VBA | Excel作業をVBAで効率化 https://vbabeginner.net いつものExcel作業はVBAを使えば数秒で終わるかもしれませんよ Sat, 24 May 2025 12:50:25 +0000 ja hourly 1 https://wordpress.org/?v=6.6.2 https://vbabeginner.net/wp-content/uploads/2019/02/favicon-150x150.png VBA | Excel作業をVBAで効率化 https://vbabeginner.net 32 32 指定列の10行移動平均を計算して隣の列に出力するVBA https://vbabeginner.net/vba-moving-average-excel/ Sat, 24 May 2025 12:50:25 +0000 https://vbabeginner.net/?p=7571 VBAで10行ごとの移動平均を計算する(数式は面倒・・)

Excelでデータ分析や数値の扱う際に、「過去10行の平均を毎行ごとに出したい」というケースがあります。

FXや株式取引でのテクニカル分析で扱う「移動平均線」は有名です。他にも売上やアクセス数、温度や計測値など、一定期間の推移をなめらかに見るためには移動平均がとても有効です。

ただ、Excel関数で1行ずつ手動で数式を入れるのは手間がかかりますし、対象データが増えると面倒です。

そこでこの記事では、VBAを使って、指定した列のデータに対して10行ごとの平均を自動計算して隣の列に出力する方法をご紹介します。コードを編集すれば10日間を20日間などに変更することが可能です。

マクロ概要

対象列のデータを1行ずつ読みながら、その行を含めた直近10行の平均を計算します。

計算結果を隣の列に自動で書き出します。

シートのデータが空になるまで繰り返し実行します。

コード

Sub SetMovingAverage()
    Dim r               As Range    '// 処理対象セル
    Dim averageRange    As Range    '// 移動平均セル範囲
    Dim sum             As Double   '// セル範囲の合計値
    Dim cell            As Range    '// セル範囲のループ中セル
    Dim numRows         As Long     '// 移動平均を計算する範囲の行数
    Dim targetCol       As String   '// 計算対象列
    Dim outputColOffset As Long     '// 計算結果出力列(計算対象列からの何列右か)

    '// 設定
    numRows = 10            '// 行数(10であれば10行分での移動平均を求める)
    targetCol = "G"         '// 列
    outputColOffset = 2     '// targetColからどれだけ右の列に出力するか(G列で2列右であればI列に出力する)

    '// 基準セルを指定
    Set r = Range(targetCol & "11")
    
    '//
    Do
        '// セルに値がなければループを抜ける
        If r.Value <> "" Then
            Exit Do
        End If
        
        '// 基準セルからn行前までのセル範囲をセット
        Set averageRange = Range(r, r.Offset(-(numRows - 1), 0))
        
        '// 合計値を初期化
        sum = 0

        '// n行分のセル値の合計を計算
        For Each cell In averageRange
            sum = sum + cell.Value
        Next
        
        '// 計算結果出力列に移動平均(セル値合計÷行数)を出力
        r.Offset(0, outputColOffset).Value = sum / numRows
        
        '// 次のセルに対象セルを変更
        Set r = r.Offset(1, 0)
    Loop
End Sub

実行方法

株価のデータを使って実行方法を説明します。

ここではA列の「日付」昇順でE列の「終値」の10日間の移動平均をG列に出したい場合とします。

上のようにシートにデータを入力します。例では罫線を付けていますが罫線は無くても構いません。

あとは、入力したシートを表示(アクティブ)状態で上のコードのSetMovingAverage関数を実行するだけです。

実行後は以下のようにE列の各10日分の平均がG列に出力されます。

注意点

平均の対象となる10行分のデータが不足している場合もそのまま計算されます。

空白セルや非数値が混ざっている場合、エラーになることがあります。

]]>
VBAで全シートの指定列の値を集約して出力する https://vbabeginner.net/output-specified-column-of-all-sheets/ Sat, 29 Jul 2023 14:50:25 +0000 https://vbabeginner.net/?p=6858 同じフォーマットのシートが複数ある場合のデータ確認

Excelで1つのブックの中に表形式の同じフォーマットのシートが複数あることがあります。

よくあるのは個数や金額のフォーマットや、データベースなどの項目定義のフォーマットなどが挙げられます。他にもいろんな業種で表形式のフォーマットは利用されています。

これらのフォーマットを複数のシートで利用する場合に、全シートで何が設定されているのかを確認しなければならないことがあります

よくあるのが、「入力内容がそもそも合ってるのか間違ってるのか、何が記入されているのかもよく分からない」という場合です。

こういう場合は片っ端から確認していくしかないのですが、量が多い場合や目視での確認に不安がある場合や、修正後の再確認が発生したりするとまた同じことのやり直しになってしまいます。

そこで以下では、全シートの指定列の内容を集約してイミディエイトウィンドウに出力するマクロを紹介します。

事前準備

以下のコードではDictionaryオブジェクトを利用しています。

VBA画面のツールメニュー→参照設定を選び、参照設定ダイアログで「Microsoft Scripting Runtime」にチェックを付けます。これでDictionaryが使えるようになります。

CreateObject関数を使う方法もありますが、詳細は「VBAのDictionaryの使い方(全メソッドとプロパティ網羅)」をご参照ください。

全シートの指定列の全行の値を取得して集約して出力する

以下のマクロでは、全シートの指定された列の全行の内容を取得して、同じ値であれば重複を除去してイミディエイトウィンドウに出力します。

例えば「北海道」から「沖縄」までの全都道府県の47シートがあるブックの場合、「福岡」シートのC2セルを選択している状態で以下のマクロを実行すると、47シートそれぞれのC列の全行の値を取得して重複を除去して出力します。

「福岡」シートのC2セルは「どの列を取得対象の列にするか?」という「C列」が欲しいだけで、「福岡」シートでなくても構いません。

Sub 全シートの指定列の値を集約して取得()
    Dim sht     As Worksheet        '// シート(全シートループ用)
    Dim col     As Integer          '// 基準となる列
    Dim dic     As New Dictionary   '// 同一値のみを取得するための辞書
    Dim iMaxRow As Long             '// 各シートの最終行
    Dim i       As Long             '// 行ループ用の行位置指定用ループカウンタ
    Dim s       As String           '// セルの文字列
    Dim key     As Variant          '// 辞書のキー
    
    '// 現在のアクティブシートのアクティブセルの列を基準列として取得する
    col = ActiveCell.Column
    
    '// 全シートループ
    For Each sht In Worksheets
        '// このシートの最終行を取得
        iMaxRow = sht.UsedRange.Rows.Count
        
        '// 行ループカウンタを先頭行位置の1に初期化
        i = 1
        
        '// シートの全行ループ
        Do
            '// 最終行に達した場合
            If i > iMaxRow Then
                '// 全行ループを抜ける
                Exit Do
            End If
            
            '// 現在行のセルの値を取得。列は先に取得した位置。
            s = sht.Cells(i, col).Value
            
            '// セルの値が未取得の場合
            If dic.Exists(s) = False Then
                '// 辞書に追加する
                Call dic.Add(s, s)
            End If
            
            '// 次行に設定
            i = i + 1
        Loop
    Next
    
    '// 辞書の全キーをループ
    For Each key In dic.keys
        '// イミディエイトウィンドウに出力
        Debug.Print key
    Next
End Sub

コード説明

コードの内容はコメントに大体書いてますので、何をやろうとしているのかを説明します。

変数colはColumnの略で、どの列の全行を取りたいのか、という基準となる列を設定しています。マクロを実行した際のアクティブシートのアクティブセルを対象としているため、A1セルを選択してればA列、D100セルを選択していればD列を基準としています。

ActiveCell.Columnは実際には英字のAなどではなく列位置を示す数値が変数Colには格納されます。

あとは、For Eachで全シートを表すWorksheesオブジェクトのループで1シートずつ回して、その各シートの最終行をUsedRange.Rows.Countで取得し、1行目から最終行までの基準列のセルを1つずつ取得して、辞書に格納されていなければ格納しています

UsedRangeを使った最終行の取得の詳細については「VBAで編集セル範囲の選択と最終行と最終列の取得」をご参照ください。

Dictionaryを使っているのは重複を除去するのにExistsメソッドでの判定がラクなためです。

Dictionaryの詳細については「VBAのDictionaryの使い方(全メソッドとプロパティ網羅)」をご参照ください。

実行例

以下のように都道府県のシートがあるとします。ここでは北海道と沖縄の一部の市町村を書いてます。

<北海道>

<沖縄>

ここで北海道シートのB2セルを選択して、上のマクロを実行すると、各シートのB列の内容をイミディエイトウィンドウに出力します。

]]>
VBAで指定フォルダをエクスプローラーで開く https://vbabeginner.net/open-folder-in-explorer/ Sun, 16 Jul 2023 16:29:47 +0000 https://vbabeginner.net/?p=6855 エクスプローラーで指定フォルダを開くには

VBAを実行中に、実行結果が格納されているフォルダをエクスプローラーで開きたい場合があります。VBAで新規ブックやテキストファイルを作成して、それを確認したい場合などです。

ここでは指定したフォルダをエクスプローラで開く方法を紹介します。

エクスプローラーは「C:\Windows\explorer.exe」がアプリケーション本体です。VBAのコードでもこの「explorer.exe」を利用します。

WindowsにはXP、Vista、7、8、8.1、10、11といくつかのOSバージョンがありますが、explorer.exeはいずれも基本的には同じところにあります。ただ、業務用で使うPCなどではWindowsのインストールドライブがDドライブだったりすることもあります。

もしexplorer.exeの場所が分からなければ、エクスプローラーを普通に起動してアドレス欄に「%SystemRoot%」と入力+Enterを押せばexplorer.exeの場所が分かります。「%SystemRoot%」とはWindowsを操作するのに重要なファイルやアプリケーションがいろいろ格納されているフォルダの「C:\Windows」を表す環境変数です。

explorer.exeを実行するとエクスプローラーが起動しますが、その起動はShell関数を使います。詳しくは以下のコードをご参照ください。

VBAのExcelマクロブックがあるフォルダをエクスプローラーで開くコード

以下のコードはVBAを実行しているExcelマクロブックと同じフォルダをエクスプローラーで開きます。

Sub OpenVBABookFolderInExplorer()
    Dim sPath       As String       '// エクスプローラーで開くフォルダパス
    Dim sCommand    As String       '// 実行するコマンド文字列
    
    '// エクスプローラーで開くフォルダを指定する
    sPath = ThisWorkbook.Path
    
    '// Shell関数に渡すコマンド文字列を指定する
    sCommand = "explorer.exe """ & sPath & """"
    
    '// エクスプローラーを起動して指定フォルダを開く
    Call Shell(PathName:=sCommand, WindowStyle:=vbNormalFocus)
End Sub

「ThisWorkbook.Path」がVBAが保存されているExcelブックのフォルダパスを表します。この部分に開きたいフォルダパスを指定すれば、任意のフォルダをエクスプローラーで開くことが出来ます。

「ThisWorkbook」は実行しているVBAが保存されているExcelブックを示すWorkbookオブジェクトです。

「ThisWorkbook.Path」は実行しているVBAの関数が「C:\test\a.xlsm」に保存されているのであれば「C:\test\a.xlsm」を指し、「PERSONAL.XLSB」のVBAの関数を実行しているのであればPERSONAL.XLSBを指します。

ThisWorkbookの詳細については「ThisWorkBookとActiveWorkBookの違い」をご参照ください。

あとはShell関数でエクスプローラーのアプリケーション本体である「explorer.exe」と半角スペースで区切って「explorer.exe」への引数として開く先のフォルダパスであるExcelマクロブックがあるフォルダを指定しています。

「explorer.exe」はフォルダパスを指定していませんが、環境変数「SystemRoot」にフォルダパスの「C:\WINDOWS」として定義されているため、「explorer.exe」にフォルダパスが付いてなくても環境変数のどれかに定義されているフォルダパスに「explorer.exe」が無いかを探しにいきます。

Shell関数の第二引数の「WindowStyle」は起動したアプリケーション(ここではエクスプローラー)のウィンドウの状態を指定します。ここで指定している「vbNormalFocus」はエクスプローラーを前回起動したときと同じウィンドウサイズで開くための定数です。

Shell関数の詳細については「VBAで他のアプリケーションで起動する(Shell)」をご参照ください。

現在利用しているブックのフォルダをエクスプローラーで開くコード

上のコードではVBA実行中のExcelマクロブックのフォルダをエクスプローラーで開く内容でしたが、現在利用中のアクティブブックのフォルダを開く場合は以下になります。

Sub OpenActiveWorkbookFolderInExplorer()
    Dim sPath       As String       '// エクスプローラーで開くフォルダパス
    Dim sCommand    As String       '// 実行するコマンド文字列
    
    '// エクスプローラーで開くフォルダを指定する
    sPath = ActiveWorkbook.Path
    
    '// Shell関数に渡すコマンド文字列を指定する
    sCommand = "explorer.exe """ & sPath & """"
    
    '// エクスプローラーを起動して指定フォルダを開く
    Call Shell(PathName:=sCommand, WindowStyle:=vbNormalFocus)
End Sub

上のコードと異なるのは6行目の「ActiveWorkbook.Path」の部分だけです。あとは同じです。

]]>
batファイルからExcelブックのマクロを呼び出す方法 https://vbabeginner.net/call-macro-from-bat/ Sat, 15 Jul 2023 07:08:07 +0000 https://vbabeginner.net/?p=6850 batファイルからExcelブックのマクロを呼び出すには

Excelブックに書いてあるマクロは、通常は同じExcelブックか別のExcelブックから呼び出します。

例えば、aaa.xlsmに書いてある「Sub GetData()」という関数があった場合、シートにボタンを置いたりして実行できるようにするのが一般的な使い方です。

しかし、ExcelブックにあるマクロをExcelではない別のアプリケーションから呼び出したい場合があります。

ここではそれをbatファイルを使って実現する方法を紹介します。

batファイルからExcelマクロを呼び出す用途

batファイルからExcelマクロを呼び出す用途ですが、手動でマクロを実行できない場合などが考えられます。

通常マクロを実行するにはボタンやクイックアクセスツールバーなどをクリックしたり、セルの値が変わった場合などのイベント発生時になりますが、そのような手動でマクロを実行できない時間帯に自動で動かしたい、とかの場合はどうしたらよいか、という場面があります。

それを解決する方法の1つに、VBAには指定時刻に実行するOnTimeメソッドが用意されています。これはテレビレコーダーの録画予約みたいなものですが、Excelが起動していることを前提としているため、Excelの起動忘れや異常終了が発生しているとOnTimeでの実行が出来ません。

これを回避するために一番使い勝手がよいのがWindowsのタスクスケジューラ機能を利用する方法です。

タスクスケジューラは事前に設定した日時にアプリケーションを実行する機能です。exeファイルだけでなくbatファイルなども実行できます。WindowsOS自体が管理するため、OnTimeメソッドと異なりExcelが起動していなくても構いません。

なお、OnTimeについての詳細は「VBAで指定時刻にマクロを実行する(OnTime)」をご参照ください。

batファイルからExcelブックを呼び出す手順

具体的には以下のような実行手順になります。

  1. batファイルを実行してVBScriptを呼び出す。
  2. VBScriptからExcelマクロを呼び出す。
  3. Excelマクロが実行される。

batファイル(*.bat) → VBScriptファイル(*.vbs) → Excelマクロ(*.xlsm、PERSONAL.XLSBなど) という呼び出し順になります。

Excelマクロをbatファイルから呼び出すことは出来ないため、Excelを実行することが出来るVBScriptを途中に挟みます。

VBScriptは拡張子がvbsのファイルで、メモ帳などのテキストエディタで作成できます。

VBScriptファイルではなく他のプログラミング言語で作成したアプリケーション(*.exe)などを呼び出してもいいのですが、難易度が上がるのでここではテキストファイルとして作成できるVBScriptを利用します。

batファイルのコード

batファイルには以下のように書きます。

cscript "V:\test\tasksc\vbacall.vbs" "123" "abc"

ここではbatファイル名をVBScriptCall.batとして保存しています。

保存の際の注意点として、文字コードはShift-JIS(SJIS)で保存してください。UTF-8などで保存すると日本語表記の部分が文字化けして動作しないことがあります。特にファイルパスなど日本語が使われやすい部分で問題になります。

スペース区切りで4つ書いています。

“cscript”はVBScriptを実行することを示すコマンドです。

その次に実行するVBScriptファイル名を指定します。batファイルとVBScriptファイルが同じフォルダにある場合はファイル名だけでいいですが、別フォルダにある場合はフォルダパスを指定してください。3番目以降はカンマ区切りで引数を指定します。引数が無い場合は省略可能です。

VBScriptファイルのコード

VBScriptファイルには以下のように書きます。

Dim sArg1           '// 第一引数
Dim sArg2           '// 第二引数

'// 引数受け取り
sArg1 = WScript.Arguments(0)
sArg2 = WScript.Arguments(1)

WScript.Echo sArg1
WScript.Echo sArg2

'// Excelを起動する(既にExcelが起動済みの場合は、さらにもう一つ起動する)
Dim excelApp
Set excelApp = CreateObject("Excel.Application")

'// Excelを表示する
excelApp.Application.Visible = True

'// Excelマクロブックを開く
Dim wb              '// Excelブック
Set wb = excelApp.Workbooks.Open("V:\test\tasksc\a.xlsm")

'// Excelマクロブックの関数を実行する
excelApp.Application.Run "a.xlsm!Module1.SubSample", sArg1, sArg2

'// Excelを終了する
excelApp.Quit

ここではbatファイル名をvbacall.vbsとして保存しています。

文字コードはbatファイルと同様で、Shift-JISで保存してください。

このコードでやっているのは、新しくExcelを起動して、対象のExcelマクロブックを開き、マクロを実行する、という流れです。マクロを実行後は新しく起動していたExcelを終了しています。

CreateObject関数でExcelを起動しているため、既に起動しているExcelがあったとしても新規プロセスのExcelとして起動します。

通常、Excelブックは同時に同じ名前のブックを開くことができませんが、仮に対象のExcelマクロブックが開いていたとしても、別プロセスのExcelで起動しているため、開くことが可能です。

VBScriptはVBAとほとんど同じプログラミング構文になっています。ただ、変数のデータ型を付けられず全てVariantで扱う、などの違いはあります。

VBScriptでハマるポイントがRunメソッドの書き方です。大事なのは3つあります。

ハマりやすい1つ目は、「Excelブック名!モジュール名.関数名」の形で書くという点です。どのブックの、どのモジュールの、どの関数か、ということをきちんと明示する必要があります。ブックを1つしか開いていないなど、状況によってはExcelブック名を書かなくても動作することがありますが、きちんと書いた方が確実です。

ハマりやすい2つ目は、引数の渡し方です。引数がない場合は以下のように書きます。

excelApp.Application.Run "a.xlsm!Module1.SubSample"

引数がある場合は渡す引数の数だけカンマで区切って渡します。

excelApp.Application.Run "a.xlsm!Module1.SubSample", sArg1, sArg2

ハマりやすい3つ目は、データ型です。VBScriptではデータ型の概念が無いため変数は全てVariant型になります。そのため、呼び出す先のVBAの関数の引数がStringやIntegerなどになっていると「型が一致しません」のエラーになります。

なので、よほどのことがない限り、VBAの関数の引数のデータ型もVariant型にしておき、VBAの関数内部でVariant型から別のデータ型に変換するようにした方がよいでしょう。ここで紹介しているコードもそのようにしています。

VBScriptでデータ型変換関数を使って渡すことも出来ますが、データ型の運用をVBScriptとVBAの両方で行わないといけなくなります。

どうしてもVBAの引数のデータ型に合わせないといけない場合は以下のようにVBScript側のRunメソッドでの呼び出し時にデータ型をVBAの引数の型に合わせてCInt関数やCStr関数などで変換する必要があります。

excelApp.Application.Run "a.xlsm!Module1.SubSample", CInt(sArg1), CStr(sArg2)

データ型の変換関数については「VBAの型変換関数(キャスト)」をご参照ください。

呼び出すVBAの関数コード

上記のbatファイル→VBScriptファイルから呼び出されるVBAのコードは以下のようになります。

普通のVBAの関数の書き方でOKです。ちなみにSubの左にPrivateを付けてても呼び出せます。非公開なのになんで動くの?と思いますが動いてしまうのでそういうもんと考えましょう。

Sub SubSample(arg1 As Variant, arg2 As Variant)
    Dim iCount  As Integer
    Dim sData   As String
    
    iCount = Val(arg1)
    sData = CStr(arg2)
    
    MsgBox "データ件数は[" & CStr(iCount) & "]件です。データ名は[" & sData & "]です。"
End Sub

実行するとbatファイルからVBScriptに引数で渡された”123″と”abc”が、メッセージボックスで表示されます。

VBScriptの説明のところで書きましたが、VBScriptから受け取る変数はVariantで受け取り、VBA側でデータ型を変換しています。

使い方

コマンドプロンプトを起動して、batファイルを実行すると、VBScriptファイルを介してExcelマクロブックの関数が呼び出されます。

ここではV:\test\taskscフォルダの中にbatファイル(VBScriptCall.bat)、VBScriptファイル(vbacall.vbs)、Excelマクロブック(a.xlsm)を格納している状態でコマンドプロンプトでVBScriptCall.batを実行しています。

実行するとa.xlsmが開き、そのブックの中に書いてあるSubSample関数が呼び出されています。SubSample関数の中でMsgBox関数でメッセージ出力しています。

タスクスケジューラで指定日時に実行したい場合は、VBScriptCall.batを設定すればよいです。タスクスケジューラの使い方は詳しいサイトがあると思いますのでそちらを探してみてください。

]]>
VBAでExcelを終了する(Application.Quit) https://vbabeginner.net/application-quit/ Mon, 10 Jul 2023 15:52:46 +0000 https://vbabeginner.net/?p=6846 Excelを終了する方法

Excelを終了するときは「Application.Quit」メソッドを呼び出します。

Quitメソッドには引数も戻り値もありません。

以下の関数を実行すると、開いているExcelブックも含めてExcelを終了します。

Sub ApplicationQuit()
    '// Excelを終了する
    Application.Quit
End Sub

ただ、この場合、未保存のブックがあると、保存するかどうかのダイアログが表示されます。

5つのブックを開いていて、2つのブックが未保存だった場合は2回ダイアログが表示されます。

未保存のブックを全て保存せずにExcelを終了する方法

Excelを終了する際に、未保存のブックがあっても保存せずに終了したい場合は以下のように書きます。

Sub ApplicationQuitWithoutSaving()
    '// 保存確認ダイアログを表示しない
    Application.DisplayAlerts = False
    
    '// Excelを終了する
    Application.Quit
End Sub

Application.DisplayAlertsにFalseを設定すると、保存するかどうかのダイアログが表示されません。

そのため、未保存のブックがあっても保存せずにExcelが終了します。Book1のような新規ブックが未保存の場合も保存されません。

未保存のブックを全て保存してExcelを終了する方法

Excelを終了する際に、保存していないブックがある場合に自動的に保存したい場合は以下のように書きます。

Sub ApplicationQuitWithSaving()
    Dim wb  As Workbook     '// ブック
    
    '// 保存確認ダイアログを表示しない
    Application.DisplayAlerts = False
    
    '// 開いているブックを1つずつループ
    For Each wb In Workbooks
        '// ブックが未保存の場合
        If wb.Saved = False Then
            '// 保存する
            wb.Save
        End If
    Next wb
    
    '// Excelを終了する
    Application.Quit
End Sub

保存確認ダイアログは表示されませんが、未保存のブックは保存して閉じられます。

新規ブックのBook1などで未保存のままになっているブックはドキュメントフォルダの直下にBook1.xlsxのように保存されます。

Book1にマクロが記載されていて、それを保存するかどうか、という話はありますが、コードを書いておいて保存せずにApplication.Quitメソッドで強制的にExcelを終了する、ということはまず考えられないため、ここでは紹介しません。

もしそういう用途があるのであれば、「Application.DisplayAlerts = False」の行を削除して保存ダイアログを出すようにした方がよいでしょう。

今動いているExcelとは別のExcelを起動して終了する方法

VBAを実行しているExcelとは別に、VBAの中で別のExcelを起動することが出来ます。その場合の別Excelアプリケーションを終了する場合について説明します。

ここでは別Excelアプリでの新規ブックを作成してA1セルに”abc”と入力して、ドキュメントフォルダにabc.xlsxで保存してから別Excelを終了しています。

変数exが別Excelアプリケーションを指しています。

別Excelは「New Excel.Application」として変数を作成することで別Excelが起動します。

Sub OtherApplicationQuit()
    Dim ex  As New Excel.Application    '// 別のExcelアプリケーション
    Dim wb  As Workbook                 '// ブック
    
    '// 新規ブック作成(この時点では未保存)
    Set wb = ex.Workbooks.Add
    
    '// A1セルに入力
    wb.Worksheets(1).Range("A1").Value = "abc"
    
    '// ブックを保存
    Call wb.SaveAs(Filename:="C:\Users\username\Documents\abc.xlsx")
    
    '// ブックを閉じる
    Call wb.Close
    
    '// 別Excelを終了する
    ex.Quit
End Sub

別Excelの変数宣言の部分を「Dim ex As New Excel.Application」ではなくCreateObject関数を使って書く場合は以下のようになります。

Sub OtherExcelCreateObject()
    Dim ex  As Object
    
    Set ex = CreateObject("Excel.Application")
    
    '// 以下省略
End Sub

]]>
VBAでブックをセルの値で名前を付けて保存する https://vbabeginner.net/save-by-cell-value/ Tue, 30 May 2023 17:30:34 +0000 https://vbabeginner.net/?p=6751 セルの値を使ってブック名にするには

ブックを保存するとき「名前を付けて保存」で別名を付けることが出来ます。VBAではWorkbookオブジェクトのSaveAsメソッドを使うことで名前を付けて保存をすることが出来ます。

SaveAsメソッドの詳細については「VBAでブックに名前を付けて保存する(SaveAs)」をご参照ください。

通常のExcelでの「名前を付けて保存」を行う場合は、保存時のダイアログが表示され、そこでブック名を入力して保存する操作になりますが、事前にセルに保存したいブック名を書いておき、それを使ってブックの保存を行うことも可能です。

このような方法を採用する場合、以下のようないくつかのルールを決めておく必要があります。

  1. ブック名を書くセルはA1セルとする。
  2. ブック名はフルパスで指定する。
  3. 同名のブックが存在する場合は上書きダイアログを表示せずに強制的に上書きする。
  4. マクロを実行する際には名前を付けて保存するブックをアクティブにしておく。

などです。後述するマクロはこのルールに従っています。

なお、ブック名をフルパスで書く理由ですが、ブック名だけの場合はカレントフォルダに保存されてしまうため、それを避けるのが目的です。

セルの値を使ってブック名を保存するコード

以下のコードは、マクロブック(ここではmacro.xlsmとします)に保存しておきます。

そして、名前を付けて保存したいブックは別に用意します。そのため、ブックが2つ開いている状態でマクロを実行することになります。

名前を付けて保存したいブックは、新規ブックでも既存ブックでもどちらでも構いません。

Sub SaveByCellValue()
    Dim sBookPath   As String       '// ブックパス
    Dim wbMacro     As Workbook     '// 当関数が書いてあるマクロブック
    Dim wb          As Workbook     '// 保存するブック
    Dim iFormat     As XlFileFormat '// 保存するブックのファイル形式(.xlsxか.xlsmか)
    
    '// 既に同名ファイルが存在する場合の上書き確認ダイアログを出さない(強制上書き保存する)ようにする
    Application.DisplayAlerts = False
    
    '// A1セルを指定する当マクロブックを指定
    Set wbMacro = ThisWorkbook
    
    '// 保存するブックとしてアクティブブック(最前面で表示しているブック)を指定
    Set wb = ActiveWorkbook
    
    '// A1セルから保存するブックのフルパスを取得
    sBookPath = wbMacro.Worksheets(1).Range("A1").Value
    
    '// A1セルのブック名の拡張子がxlsm(マクロブック)の場合
    If (Right(sBookPath, 4) = "xlsm") Then
        '// .xlsmで保存
        iFormat = xlOpenXMLWorkbookMacroEnabled
    Else
        '// .xlsxで保存
        iFormat = xlOpenXMLWorkbook
    End If
    
    '// A1セルのパス+ブック名で名前を付けて保存
    Call wb.SaveAs(Filename:=sBookPath, FileFormat:=iFormat)
    
    '// 確認ダイアログを元の設定に戻す
    Application.DisplayAlerts = True
End Sub

コードの説明

コメントに大体書いていますが、考え方を補足として説明します。

Excelで扱うブックには、通常のブック(.xlsx)とマクロブック(.xlsm)の2つがあります。

WorkbookオブジェクトのSaveAsメソッドは、ファイル形式を指定する引数としてFileFormat引数があります。このFileFormat引数は省略することが可能ですが、省略した場合は通常のブック(.xlsx)として保存するように扱われます。

そのため、FileFormat引数を省略している状態で、マクロブックの.xlsmのファイル名で保存しようとするとエラーになります。

このエラーを避けるために、A1セルに書いてある拡張子がxlsmであればマクロブックとみなし、そうでなければ通常ブックとみなすようにしてSaveAsメソッドのFileFormat引数を指定するようにしています。

使い方

  1. まず、macro.xlsmの標準モジュールに上記コードを書きます。
  2. 次に、macro.xlsmの一番左のシートのA1セルに、名前を付けて保存したいブックのフルパスを書きます。
  3. 名前を付けて保存したいブックをアクティブにします。まだこの時点で新規ブックの「Book1」とか既に存在しているブックを開いた状態のどちらでも構いません。
  4. あとは、macro.xlsmの標準モジュールに書いてあるSaveByCellValue関数を実行します。

実行後に、A1セルに指定したブック名で保存されます。

もし、macro.xlsmがアクティブの状態で実行すると、macro.xlsmがA1セルに指定したブック名で保存されます。そうならないようにガードを掛けてもいいのですが、コードが長くなるのでやっていません。

]]>
VBAで翌月(次月)を取得する https://vbabeginner.net/get-next-month/ Sun, 28 May 2023 17:10:02 +0000 https://vbabeginner.net/?p=6735 VBAで翌月(次月)を取得するには

VBAで翌月を取得するには、いくつかの方法がありますが、簡単なのはDateSerial関数を利用する方法です。

DateSerial関数の詳細については「年月日の数値をDate型に変換する(DateSerial)」をご参照ください。

一般的に日付を扱う場合はDate型を使う場合と、文字列の日付をCDate関数でDate型にする場合の2通りがあるため、どちらの書き方についても後述のサンプルで記載しています。

DateSerial関数で翌月(次月)を取得する方法

DateSerial関数は年、月、日の3つの数値(数字ではなく数”値”)からDate型の値を返します。

DateSerial(2000, 12, 31)

であれば、2000/12/31 をDate型で返します。

また、日がその月の最終日を超えている場合は月に繰り越され、月が12を超えている場合は年に繰り越されます。

そのため、以下のように月と日に通常より大きな値を設定すると、

DateSerial(2000, 13, 32)

であれば、日と月が繰り越されて 2001/02/01 が返されます。

この特性を利用して、DateSerial関数で翌月を取得するには以下のような関数を用意します。

現在日付の翌月の月を取得する場合

Function GetNextMonth()
    Dim d   As Date     '// Date型変数
    
    '// システム日時を保持
    d = Now
    
    '// システム日時の翌月を取得(第二引数の月に翌月を意味する1を加算。日は1日固定。)
    d = DateSerial(Year(d), Month(d) + 1, 1)
    
    '// 翌月の月部分を返却
    GetNextMonth = Month(d)
End Function

 

現在日付の翌月の日付を取得する場合

Function GetNextMonthDate()
    Dim d   As Date     '// Date型変数
    
    '// システム日時を保持
    d = Now
    
    '// システム日時の翌月を取得(第二引数の月に翌月を意味する1を加算。日は1日固定。)
    d = DateSerial(Year(d), Month(d) + 1, 1)
    
    '// 翌月の月部分を返却
    GetNextMonthDate = d
End Function

Year関数やMonth関数の詳細については「VBAで年、月、日を取得する(Year、Month、Day)」をご参照ください。

使い方

上の2つの関数を呼び出すサンプルです。

Sub GetNextMonthTest()
    Dim dMonth  '// 月
    Dim dDate   '// 日付
    
    dMonth = GetNextMonth
    Debug.Print dMonth      '// 翌月の月を返す(1から12のいずれか)
    
    dDate = GetNextMonthDate
    Debug.Print dDate       '// 翌月1日を返す(2023/06/01 など)
End Sub

現在が2023/5/29であれば、GetNextMonthは「6」を返し、GetNextMonthDateは「2023/06/01」を返します。

任意の日付の翌月を取得する方法

任意の日付の翌月の月を取得する場合は、上記の関数の現在日付の部分を任意の日付を受け取る引数に変更する形にすればOKです。返却値はDateSerial関数の結果にMonth関数で月の数値を返します。

Function GetMonthAfterSpecifiedMDate(dBase As Date)
    Dim d   As Date '// 日付
    
    '// 引数日付の翌月を取得(第二引数の月に翌月を意味する1を加算。日は1日固定。)
    d = DateSerial(Year(dBase), Month(dBase) + 1, 1)
    
    '// 翌月の月を返却
    GetMonthAfterSpecifiedMDate = Month(d)
End Function

 

翌月1日を取得する場合は、DateSerial関数の結果をそのまま返します。

Function GetDateAfterSpecifiedDate(dBase As Date)
    Dim d   As Date '// 日付
    
    '// 引数日付の翌月を取得(第二引数の月に翌月を意味する1を加算。日は1日固定。)
    d = DateSerial(Year(dBase), Month(dBase) + 1, 1)
    
    '// 翌月1日の日付を返却
    GetDateAfterSpecifiedDate = d
End Function

使い方は以下のようになります。日付文字列からDate型への変換にはCDate関数を利用しています。

CDateについての詳細は「VBAの型変換関数(キャスト)」をご参照ください。

あとは先に紹介した関数と同じですが、呼び出す関数の引数にDate型の任意の日付を渡します。

Sub GetMonthAfterSpecifiedDateTest()
    Dim s       As String   '// 日付文字列
    Dim d       As Date     '// 日付文字列のDate型
    Dim iMonth  As Integer  '// 翌月の月
    Dim dNext   As Date     '// 翌月日付
    
    '// 日付文字列
    s = "2023/5/29"
    
    '// 日付文字列をDate型に変換
    d = CDate(s)
    
    '// 翌月を取得
    iMonth = GetMonthAfterSpecifiedMDate(d)
    Debug.Print iMonth
    
    '// 翌月1日の日付を取得
    dNext = GetDateAfterSpecifiedDate(d)
    Debug.Print dNext
End Sub

実行結果:
6 ・・・ 2023/5/29の翌月の6
2023/06/01 ・・・ 2023/5/29の翌月1日の日付

]]>
VBAで図形の前面や背面位置を操作する(Zオーダー) https://vbabeginner.net/z-order/ Sun, 21 May 2023 03:01:30 +0000 https://vbabeginner.net/?p=6703 Zオーダーとは?

Excelでオートシェイプや画像などの図形を複数扱う場合、重なりあう部分が出てきます。このときに、あとから配置した方が最前面(全て見える状態)で表示されます。

このような状態のときに、図形を選択して「図形の書式」タブ → 配置 → 「前面へ移動」や「背面へ移動」を操作することで重なりの順序を入れ替えることが出来ます。

Zオーダーとは、このような図形の重なり順序を指します。Excelのシートの列(X軸)と行(Y軸)に対して、深さを表すため、Z軸の順序=Z order(オーダー)と命名されています。

Photoshopなどの画像ソフトでの「レイヤー」の重なり順序、というとイメージしやすい方もいると思います。

「Zオーダー」という言葉は普段Excelを使っているときに使うことはまずありません。VBAで深さの操作を行う際に出てきます。

ZOderメソッド

図形はShapeRangeオブジェクトを使って操作します。

そして、これらの図形の重なり順序を変更するには、ShapeRangeオブジェクトのZOderメソッドを利用します。

Sub ZOrder(ZOrderCmd As MsoZOrderCmd)

ZOrderCmd 指定した図形の配置をMsoZOrderCmd列挙型で設定します。

MsoZOrderCmd列挙型

定数 内容
msoBringForward 2 図形を今の位置から1つ前面に移動させる。
msoBringInFrontOfText 4 Wordでのみ利用可能。図形をテキストの前面に移動させる。Excelの場合は指定しても無視される。
msoBringToFront 0 図形を最前面に移動させる。
msoSendBackward 3 図形を今の位置から1つ背面に移動させる。
msoSendBehindText 5 Wordでのみ利用可能。図形をテキストの背面に移動させる。Excelの場合は指定しても無視される。
msoSendToBack 1 図形を最背面に移動させる。

ZOder用の関数

Excelで指定できるのは、前面、背面、最前面、最背面の4つです。選択図形のZオーダーを頻繁に変える場合はそれぞれの関数を用意しておいた方がいいでしょう。

というのも、VBAで図形の処理を行う場合、図形が選択されていない場合や引数で指定されていない場合にShapeRangeオブジェクト変数として扱おうとしても対象図形を参照できない場合があります。その場合はエラーになってしまいます

ZOrderメソッドを使う箇所ごとにエラー処理を入れるのは面倒なので、関数にしておいて関数側でエラー処理を入れておいた方が使い勝手がよくなります。以下はエラー処理を入れた場合の参考コードです。

'// 図形を今の位置から1つ前面に移動させる
Sub ZOrder_msoBringForward()
    On Error GoTo ERROR_EXIT
    
    Dim shp As ShapeRange
    
    Set shp = Selection.ShapeRange
    
    '// 図形を今の位置から1つ前面に移動させる
    Call shp.ZOrder(msoBringForward)
    
ERROR_EXIT:
End Sub

'// 図形を最前面に移動させる
Sub ZOrder_msoBringToFront()
    On Error GoTo ERROR_EXIT
    
    Dim shp As ShapeRange
    
    Set shp = Selection.ShapeRange
    
    '// 図形を最前面に移動させる
    Call shp.ZOrder(msoBringToFront)
    
ERROR_EXIT:
End Sub

'// 図形を今の位置から1つ背面に移動させる
Sub ZOrder_msoSendBackward()
    On Error GoTo ERROR_EXIT
    
    Dim shp As ShapeRange
    
    Set shp = Selection.ShapeRange
    
    '// 図形を今の位置から1つ背面に移動させる
    Call shp.ZOrder(msoSendBackward)
    
ERROR_EXIT:
End Sub

'// 図形を最背面に移動させる
Sub ZOrder_msoSendToBack()
    On Error GoTo ERROR_EXIT
    
    Dim shp As ShapeRange
    
    Set shp = Selection.ShapeRange
    
    '// 図形を最背面に移動させる
    Call shp.ZOrder(msoSendToBack)
    
ERROR_EXIT:
End Sub

ZOrderPositionプロパティ

図形の重なり順序がどの位置にあるかを知りたい場合には、ShapeRangeオブジェクトのZOrderPositionプロパティを利用します。

ZOrderPositionプロパティは読み取り専用のため、調べたい対象の画像などの重なり順序を知ることはできますが、重なり順序を変更することはできません。

Property ZOrderPosition As Long

戻り値 図形の重なり順序を返します。最背面は1になります。

例えば、図形が10個あれば最前面の図形のZOrderPositionプロパティは10を返します。図形が重なっていなくてもシートにあるそれぞれの図形にはZオーダーが設定されているため、図形それぞれでZOrderPositionプロパティが同じなることはありません。

また、グループ化している図形の場合は、グループ化したあとの図形と、グループ化する前の個別の図形にはそれぞれZOrderPositionプロパティが設定されます。

以下はZOrderPositionプロパティの値を出力する関数です。1つ目の関数が選択図形のZOrderPositionプロパティ値を取得し、2つ目の関数が1つ目の関数を呼び出しているZOrderPositionプロパティ結果を出力しているテスト関数です。

Function GetZOrderPosition()
    On Error GoTo ERROR_EXIT
    
    Dim shp As ShapeRange
    
    Set shp = Selection.ShapeRange
    
    GetZOrderPosition = shp.ZOrderPosition
    Exit Function
    
ERROR_EXIT:
    GetZOrderPosition = 0
End Function

Sub GetZOrderPositionTest()
    Dim i
    i = GetZOrderPosition
    Debug.Print i
End Sub

]]>
VBAで英文を区切り文字.?で配列に分割する https://vbabeginner.net/english-split/ Sat, 15 Apr 2023 07:01:01 +0000 https://vbabeginner.net/?p=6669 英文を配列に分割するには

英文には終端文字として.と?が使われます。1行の長い英文の場合、1行の中に.や?が複数使われることがありますが、英文を区切り文字ごとに分けたい場合もあります。

VBAでは文字列の分割にSplit関数を使うことがあります。ただ、Split関数は英文を分割するのにはあまり向いていません。なぜかというと、Split関数は分割する文字自体を残さないことと、複数の文字の種類を分割できないためです。

.や?のように2種類の文字で分割したい場合にSplitを使うと、.や?自体が英文から消えてしまいますし、そもそも2種類の分割が対応していません。

そこでここでは、英文の中にある.と?があれば配列に分割するマクロを紹介します。

.と?以外の文字で分割したい場合は、後述するマクロに文字を追加するだけで対応できるようにしています。

なお、Split関数については「VBA関数:指定文字で分割して配列にする(Split)」、Split関数で区切り文字を複数使う方法については「VBAのSplit関数で区切り文字を複数使う方法」をご参照ください。

英文を分割するマクロ

以下のSplitEnglish関数は、引数で渡された英文の文字列を.と?の部分で配列に分割して返します。

例えば、「aaa? bbb. ccc. ddd?」という英文の場合は、
[aaa?]
[ bbb.]
[ ccc.]
[ ddd?]
の4つの要素を持つ配列を返します。

各行の先頭の空白文字は元の英文を分解する機能に特化するためあえて残しています。先頭の空白を除去したい場合は、このSplitEnglish関数を呼び出す側でTrim関数で除去してください。後述する使い方でTrim関数を使っていますので参考にしてください。

処理の内容ですが、英文を1文字ずつループして、それが区切り文字の.か?であるかを判定して、そうであれば配列の要素を追加しています。

Function SplitEnglish(eng As String) As Variant
    Dim ar()        As String       '// 引数文字列を分割後の配列
    Dim delimiter   As String       '// 区切り文字
    Dim iArg        As Integer      '// 引数文字列のループカウンタ
    Dim jDelim      As Integer      '// 区切り文字数のループカウンタ
    Dim s           As String       '// 引数文字列の現ループの1文字
    Dim bMatch      As Boolean      '// 引数文字列の1文字が区切り文字か判定(True:引数文字が区切り文字と一致、False:不一致)
    
    '// 区切り文字を設定
    delimiter = ".?"
    ReDim ar(0)
    
    '// 引数文字列を1文字ずつループ
    For iArg = 1 To Len(eng)
        '// 引数文字列の現ループ文字を取得
        s = Mid(eng, iArg, 1)
        
        '// 一致フラグを不一致として初期化
        bMatch = False
        
        '// 区切り文字の数だけループ
        For jDelim = 1 To Len(delimiter)
            '// 引数文字列の現ループ文字と区切り文字が一致する場合
            If s = Mid(delimiter, jDelim, 1) Then
                '// 一致フラグを一致として設定
                bMatch = True
                
                '// 一致があれば以降のループは不要のためループを抜ける
                Exit For
            End If
        Next
        
        '// 引数文字と区切り文字が一致している場合
        If bMatch = True Then
            '// 配列の終端に区切り文字を連結
            ar(UBound(ar)) = ar(UBound(ar)) & s
            
            '// 次の配列要素を追加
            ReDim Preserve ar(UBound(ar) + 1)
        Else
            '// 配列の終端に引数文字を連結
            ar(UBound(ar)) = ar(UBound(ar)) & s
        End If
    Next
    
    '// 配列要素の終端の空要素を削除
    If ar(0) <> "" And UBound(ar) > 1 Then
        ReDim Preserve ar(UBound(ar) - 1)
    End If
    
    SplitEnglish = ar
End Function

使い方

SplitEnglish関数に英文文字列を渡すと、.と?で配列に分解されて返されます。

SplitEnglish関数では区切り文字で分解しているだけのため、.や?の後ろに空白文字があるとそれが行の先頭文字として設定されています。

そのため、Debug.Printの出力ではTrim関数を使って分割後の英文の前後の空白文字を除去しています。

Sub SplitEnglishTest()
    Dim s As String
    Dim ar As Variant
    
    s = "Are you having internet speed issues? If you have problems, test your internet line speed. Performs speed measurement and speed diagnosis, and displays the results."
    
    ar = SplitEnglish(s)
    
    Dim v
    For Each v In ar
        Debug.Print Trim(v)
    Next
End Sub

実行結果(3行で出力)
Are you having internet speed issues?
If you have problems, test your internet line speed.
Performs speed measurement and speed diagnosis, and displays the results.

Debug.PrintでTrimしなかった場合の出力(.の次の空白が次の行の先頭に設定される)
[Are you having internet speed issues?]
[ If you have problems, test your internet line speed.]
[ Performs speed measurement and speed diagnosis, and displays the results.]

]]>
VBAのEnum(列挙型):列指定、しきい値に便利 https://vbabeginner.net/enum/ Sun, 05 Mar 2023 16:47:31 +0000 https://vbabeginner.net/?p=6653 Enum(列挙型)とは

Enum(列挙型)は、自動で1ずつ増える連番を振られた定数の集まりのことです。

連番の開始値は0になりますが、任意の値を開始値にすることも可能です。マイナス値もOKです。

構文

[ Public | Private ] Enum 列挙型名
    定数名 [= 値または式]
    定数名 [= 値または式]
    ・
    ・
End Enum

Public、Private(省略可) Enum宣言をVBAProject全体で利用したい場合はPublicを指定します。Enumを宣言しているモジュール内でのみ利用したい場合はPrivateを指定します。PublicとPrivateを同時に設定することはできません。省略時はPublic扱いになります。
列挙型名 Enum型の名前を指定します。
定数名 定数名を指定します。複数指定する場合はより上から下に向かって連番が設定されます。
値または式(省略可) 定数に設定する値を指定します。省略時は直前の定数値+1が設定されます。先頭の定数を省略した場合は0が設定されます。

Enum(列挙型)の用途

Enumの定数の値は自由に設定できます。ただ、設定しすぎるとEnumの特性である「連番の自動付与」を捨てることになり、そうなってくるとEnumを使う意味が薄れてきます。

Enum(列挙型)の用途は、なにかしら連続する値の集まりであることがはっきりわかっている場合や、定数のグループとして扱える場合などがあります。そのため、連番や意味ある数値である必要性がない場合の利用には不向きです。そのような場合は通常の定数(Const)を利用した方がよいでしょう。

以降ではコードと一緒にExcelでEnumを使う用途として考えられるものを2つ紹介します。

1つはシートの列の位置を示す使い方で、もう1つはデータ値の性格を示す使い方です。

シートの列の位置を示す列挙型

以下のようにシートに表形式のデータがある場合に、A~D列を示す列挙型を用意します。

Private Enum 表の列位置
    No = 1
    日付    '// 連番で2が自動で設定される
    個数    '// 連番で3が自動で設定される
    メモ    '// 連番で4が自動で設定される
End Enum

先頭の「No」にだけ1を設定しています。これは、RangeオブジェクトではA列の位置を1として扱うためです。「Range(“A1”)」や「Cells(1, 1)」と書いた場合はA1セルを意味します。

「No」が1のため、以降の「日付」には2、「個数」には3、「メモ」には4が定数値として設定されます。

以下のコードは表の内容をイミディエイトウィンドウに出力しています。1行ずつループして、各列の内容を出力しています。

Private Enum 表の列位置
    No = 1
    日付    '// 連番で2が自動で設定される
    個数    '// 連番で3が自動で設定される
    メモ    '// 連番で4が自動で設定される
End Enum

Type 表
    No      As String
    日付    As String
    個数    As String
    メモ    As String
End Type

Sub 表データ取得()
    Dim r   As Range
    Dim h   As 表
    
    '// No列の先頭行を基点セルに設定
    Set r = Cells(1, 表の列位置.No)
    
    Do
        If r.Value = "" Then
            Exit Do
        End If
        
        '// 構造体に各列の値を設定
        h.No = r.Value
        h.日付 = r.Offset(0, 表の列位置.日付 - 1)
        h.個数 = r.Offset(0, 表の列位置.個数 - 1)
        h.メモ = r.Offset(0, 表の列位置.メモ - 1)
        
        Debug.Print r.Row & "行目:" & 表の列位置.No & "列目:No=" & h.No
        Debug.Print r.Row & "行目:" & 表の列位置.日付 & "列目:日付=" & h.日付
        Debug.Print r.Row & "行目:" & 表の列位置.個数 & "列目:個数=" & h.個数
        Debug.Print r.Row & "行目:" & 表の列位置.メモ & "列目:メモ=" & h.メモ
        
        '// 次の行を基点セルに設定
        Set r = r.Offset(1, 0)
    Loop
End Sub

実行結果
1行目:1列目:No=No
1行目:2列目:日付=日付
1行目:3列目:個数=個数
1行目:4列目:メモ=メモ
2行目:1列目:No=1
2行目:2列目:日付=2023/03/01
2行目:3列目:個数=1
2行目:4列目:メモ=あああああああ
3行目:1列目:No=2
3行目:2列目:日付=2023/03/02
3行目:3列目:個数=22
3行目:4列目:メモ=いいいいいい
4行目:1列目:No=3
4行目:2列目:日付=2023/03/03
4行目:3列目:個数=333
4行目:4列目:メモ=うううう

列の追加があっても定数値やコードを変更しなくてよい

もし、表の構成が変わって「日付」と「個数」の間に新しい列が追加された場合はEnumにも追加すると、以降の項目の定数値も変わります。この利点は、Enumの定数自体も定数を利用しているソースコードも書き換える必要がなくなる点です。列の位置がどこなのかはEnumの連番が指定してくれているので、気にしなくてよくなります。これがEnumを列位置に使う利点です。

上のコードに列が追加された場合、コードの中で変更が必要になるのは下の★マークを付けている箇所だけです。★マークの部分は追加列の処理のため、元々のコードには一切変更が入っていませんが、列が追加されて1ずつずれていてもEnumが連番を再振りしているので影響はありません。

Private Enum 表の列位置
    No = 1
    日付    '// 連番で2が自動で設定される
    追加列  '// ★列追加★連番で3が自動で設定される
    個数    '// 連番で4が自動で設定される
    メモ    '// 連番で5が自動で設定される
End Enum

Type 表
    No      As String
    日付    As String
    追加列  As String      '// ★処理追加
    個数    As String
    メモ    As String
End Type

Sub 表データ取得()
    Dim r   As Range
    Dim h   As 表
    
    '// No列の先頭行を基点セルに設定
    Set r = Cells(1, 表の列位置.No)
    
    Do
        If r.Value = "" Then
            Exit Do
        End If
        
        '// 構造体に各列の値を設定
        h.No = r.Value
        h.日付 = r.Offset(0, 表の列位置.日付 - 1)
        h.追加列 = r.Offset(0, 表の列位置.追加列 - 1) '// ★処理追加
        h.個数 = r.Offset(0, 表の列位置.個数 - 1)
        h.メモ = r.Offset(0, 表の列位置.メモ - 1)
        
        Debug.Print r.Row & "行目:" & 表の列位置.No & "列目:No=" & h.No
        Debug.Print r.Row & "行目:" & 表の列位置.日付 & "列目:日付=" & h.日付
        Debug.Print r.Row & "行目:" & 表の列位置.追加列 & "列目:追加列=" & h.追加列   '// ★処理追加
        Debug.Print r.Row & "行目:" & 表の列位置.個数 & "列目:個数=" & h.個数
        Debug.Print r.Row & "行目:" & 表の列位置.メモ & "列目:メモ=" & h.メモ
        
        '// 次の行を基点セルに設定
        Set r = r.Offset(1, 0)
    Loop
End Sub

実行結果
1行目:1列目:No=No
1行目:2列目:日付=日付
1行目:3列目:追加列=追加列
1行目:4列目:個数=個数
1行目:5列目:メモ=メモ
2行目:1列目:No=1
2行目:2列目:日付=2023/03/01
2行目:3列目:追加列=追加A
2行目:4列目:個数=1
2行目:5列目:メモ=あああああああ
3行目:1列目:No=2
3行目:2列目:日付=2023/03/02
3行目:3列目:追加列=追加B
3行目:4列目:個数=22
3行目:5列目:メモ=いいいいいい
4行目:1列目:No=3
4行目:2列目:日付=2023/03/03
4行目:3列目:追加列=追加C
4行目:4列目:個数=333
4行目:5列目:メモ=うううう

しきい値を示す

もう一つEnumの使い方を紹介します。それは、定数をしきい値として利用する方法です。

以下のEnumは成績の点数によってどのように扱うか、という場合に利用します。

Private Enum 成績値
    ランクE = 20
    ランクD = 40
    ランクC = 60
    ランクB = 80
    ランクA = 99
    ランクS = 100
End Enum

各定数値には連番ではなく「しきい値」を設定しておきます。

あとは、成績値によって処理を変えたい場合に、Enumで定義した「しきい値の定数」を使ってコーディングします。

Sub ランク出力()
    Dim i   As Integer  '// 成績値
    
    i = 50
    
    If i <= 成績値.ランクE Then
        Debug.Print "ランクE:" & i
    ElseIf i <= 成績値.ランクD Then
        Debug.Print "ランクD:" & i
    ElseIf i <= 成績値.ランクC Then
        Debug.Print "ランクC:" & i
    ElseIf i <= 成績値.ランクB Then
        Debug.Print "ランクB:" & i
    ElseIf i <= 成績値.ランクA Then
        Debug.Print "ランクA:" & i
    ElseIf i = 成績値.ランクS Then
        Debug.Print "ランクS:" & i
    End If
End Sub

実行結果
ランクC:50

ここでは「ランクC」は41点以上60点以下としていますが、55点以下にしたい場合はEnumの「ランクC」を60から55に書き換えるだけで、本処理は何も手を加える必要がありません。これがEnumでしきい値を使う場合の利点です。

]]>