Dictionaryオブジェクト

Dictionaryオブジェクトの解説と記事一覧

'実行時バインディング
Dim zz辞書 As Object
Set zz辞書 = CreateObject("Scripting.Dictionary")
'事前バインディング
Dim zz辞書 As New Scripting.Dictionary

「Dictionary」オブジェクトは、リスト化されたデータを扱う際に便利なオブジェクトです
このオブジェクトは、使用時にはインスタンスの作成を行う必要があります
インスタンスの作成には、2通りの方法があります
それが記事コードの2つになります

基本的には1つ目の実行時バインディングを行ってください

2つ目は事前に設定する必要があるため、自分の環境下以外で使用する可能性がある場合はエラーが発生します

なお、この事前バインディングはFileSystemObjectと同じものを参照設定すると有効になります
以下の記事で設定方法を画像付きで解説しています

使用の流れ

Dictionaryオブジェクトは、インスタンスを作成した段階ではなんのデータも入っていない状態です
まずは、データを登録していく作業が必要になります

データの登録にはAddメソッドを使用します
Dictionaryオブジェクトは、keyとitemの2つがペアとなってリストとなります

必要なデータを登録して、リストが完成したらそのリストのデータから条件に一致するデータがあるかを検索します
検索にはExistsメソッドを使用します
これはkeyを検索するメソッドになり、itemを検索するメソッドはありません
なぜなら、keyは一意で重複不可のデータであるためです

検索して見つかったら、そのデータに対応するデータを取得します
データを取得するkeyを指定することで、ペアのitemのデータを取得することが出来ます

Set zz辞書 = Nothing

使用が終わったら、上記のコードで変数の解放を行います

また、同じような役割でVBA標準のCollectionオブジェクトがありますが
こちらは検索が行えないなど使い勝手が少し微妙なのでこちらのDictionaryオブジェクトの利用に慣れていたほうが良いと思います

セル範囲を指定することでDictionaryオブジェクトを作成する関数を作成しました
以下の記事で紹介しています

Dictionaryオブジェクトのメンバー

  • Addメソッド
    オブジェクトにデータの新規追加を行います
  • Existsメソッド
    オブジェクト内のkeyの内から指定データの有無の判定を行います
  • Itemプロパティ
    オブジェクト内の指定keyのItemデータを取得します
  • Removeメソッド
    オブジェクト内の指定keyとペアitemを削除します
  • RemoveAllメソッド
    オブジェクト内のkeyとitemの全てを一括削除します
  • Countプロパティ
    オブジェクト内のデータの個数を取得できます
  • Keys・Itemsメソッド
    それぞれの全てのデータを配列として返されます
  • Key・Itemプロパティ
    登録済みのデータをオブジェクト上で変更します

通貨型の変数宣言

通貨型の変数です。お金などの正確な数字の計算に使用します

'通貨型の変数宣言
Dim 通貨型 As Currency

Currency型は通貨型と呼ばれ、Long型よりも大きな数字を扱うことができる上にマイナスの負の数値と、4桁の小数点数値も扱えます

この変数の型は、お金など正確な数字を計算する場合に特に使用するように推奨されている型になります

小数点数値の範囲が限られてはいますので、基本的に小数点数値はDouble型を使用しますが、このCurrency型の範囲での計算であればこちらの型を使用するほうが正確に計算できます

代入可能な数字の範囲は、負を含む922兆程です、小数点数値は0.0001までを扱えます

金額計算で考えると日本の国家予算が100兆円ぐらいなので、日本9個分ということで、まったく困ってしまう範囲では無さそうですね

VBAの部品の使い方

このカテゴリにある記事を使う上での解説と注意点です

このカテゴリには、プロシージャとして作成されたコードが記載されています

これらは、いわゆるサブプロシージャと呼ばれるもので、本来の処理をサポート、または可読性向上に繋げる役割があります

ここにあるプロシージャは、広域関数なので同じプロジェクト内であればどこにあっても呼び出し可能です
ですが、どこに何があるか分からなくなると困るので1つ専用のモジュールを作成して、その中にコピペしてもらうようにした方がいいです

モジュールの作成は、VBAのメニューから簡単に行うことが出来ます
挿入メニューから標準のモジュールを追加してください
モジュールの名前は何でも構いません、参照することもありません

これで、部品を入れるモジュールが完成します
以下の記事で画像付きで解説していますので、参考にしてください

モジュールが作成出来たら、この中に記事トップにあるコードをそのまま貼り付けしてください

それで、あとは別のプロシージャで必要に応じて呼び出しを行えば利用可能です

また、記事にあるコードは一切解説コメントを付けていません
コードの意味を理解したい方は記事で解説を行っていますので、頑張ってスクロールして読んでください

おおよそはそんなんどうでもええわ、って場合が多いと思うのでそういった方々には邪魔な行になってしまうし、なによりそっちで解説しすぎるとこのサイトのこの記事はなんのために読んだらええのん?という素朴な疑問が払しょくできないからです

プロシージャについて

このカテゴリの記事には、大きく分けて2種類のプロシージャがあります

それが、SubプロシージャFunctionプロシージャです

これらの違いは戻り値を持つか持たないかと、Callステートメントを使うか使わないかの点になります

戻り値に関しては、正直引数を参照渡しにする方式であれば、処理中での計算値を返すことは出来るのでどうとでもなってしまいます

ですが、呼び出し方法に関しては対応していない方法ではエラーが出ますので注意が必要です

Subプロシージャの呼び出しについて

これには、Callステートメントを使用する必要があります
このステートメントは、他のプロシージャを実行する際に使用するものです

例えば、以下のようなSubプロシージャがあった場合で解説します

Sub zzzMsg(Optional zzString As String = "TestMsg")

MsgBox zzString

End Sub

このプロシージャは、引数を持っているSubプロシージャです
この引数は文字列型の引数になるので、処理の呼び出し時の引数に文字列を指定することで、この引数名に代入されます

処理内容としては、その引数に代入された文字列をメッセージボックスに表示するだけです

なお、引数名の前についている「Optional」キーワードは引数を省略することが出来ることを意味します
そして、省略された場合の初期値を型の後に指定することが出来ます
ここでは「”TestMsg”」が指定されていることを確認してください

引数があり、かつ省略可能なSubプロシージャとなりますのでCallステートメントの書き方は2通りあります

Call zzzMsg

この様に引数を省略して呼び出します、引数を全てを省略する場合や引数の存在しない場合はSubプロシージャの名前だけを記載するだけでいいです

引数を省略して実行した時のメッセージボックス
引数を省略したメッセージ

この場合に表示されるメッセージは画像の様に省略時の文字列になります

次に、引数を指定して実行する場合の解説です

入力時にクイックヒントが表示される

引数は「()」で囲んで入力します
「(」を入力した時点で、画像の様にクイックヒントが表示されます
ここでの表示は関数のものと同じです、「[]」で囲まれた引数は省略可能を意味しています
その際に、省略時の代入値も表示されているのが確認できます

Call zzzMsg("ProductionMsg")

そのまま引数に文字列を指定したのが、このコードになります
これを実行すると、引数に指定した文字列がメッセージボックスに表示されます

引数を指定して表示したメッセージボックス

画像の様に、引数に指定した文字列が表示されるようになります

Subプロシージャは、Callステートメントを使用して引数を利用する場合はその引数も含めてコードを記載することで処理を実行する事ができます

Functionプロシージャの呼び出しについて

こちらは関数と同じ使用方法になるので呼び出しはあまり難しくはありません
ですが、Functionプロシージャでの作り方がSubプロシージャと違う点があるので注意してください

Function zzzLoopString(zzLoopSt As String, Optional zzLoop As Long = 1) As String

zzzLoopString = String(zzLoop, zzLoopSt)

End Function

大きく違う点が2つあります
・引数の()の後に、プロシージャ自体の型宣言を行うこと
・プロシージャ名に代入することで戻り値となる

この2点に注目しながらFunctionプロシージャは読み取るようにしてください

このプロシージャでは、まず1行目後半部分で型宣言しています
String型になっていますので、この関数の戻り値は文字列であることが分かります

また、処理中でプロシージャ名に文字列を代入しています
つまりこの時点で戻り値が取得可能な状態である、ということです
このコード以降にも処理を作成することは可能です
その場合に、Exitステートメントなどでプロシージャを強制終了したとしても戻り値の取得は可能です

なおこのFunctionプロシージャは引数「zzLoopSt」に指定された文字列を引数「zzLoop」の回数繰り返して返す関数です
というか、そのためのString関数を実行しているだけです

MsgBox zzzLoopString("A", 4)

呼び出しを行う場合は、通常の関数同様、Functionプロシージャの名前を直接記載して実行します
引数に関しても関数同様で、「()」で囲んで指定を行います

ここでは、「A」を4回繰り返す文字列を取得してメッセージボックスに表示する処理になります

Functionプロシージャの返し値をメッセージボックスに表示
コードを実行した時に表示されるメッセージボックス

コードを実行すると、メッセージボックスが表示されます
その時に表示される内容は、Functionプロシージャで返された文字列です

ここで、少しわかりにくいかもしれませんが
この1行のステートメントで、Functionプロシージャの関数と、MsgBox関数の2つが実行されています
しかし、順番は先に引数のFunctionプロシージャが実行されてから、MsgBox関数が実行されていますので問題はありません

また、今回作成したFunctionプロシージャでは引数「zzLoopSt」は省略不可の設定になっています、Optionalキーワードを付けなければ省略不可となります

この設定で、引数を省略して記載した場合は実行時エラーが発生しますので注意してください

引数は都度合わせて行いますが、要はSubプロシージャはCallステートメントで呼び出す・Functionプロシージャはプロシージャ名だけで呼び出す、という形でOKです

重複しないファイル名を取得する関数

ファイルの名前を付けて保存する際に、重複しないファイル名を取得する関数です

Function zzz可能ファイル名(ByVal zz保存名 As String) As String

Dim zz保存名_前 As String, zz保存名_後 As String
Dim zz連番 As Long
Dim zz検証用文字列 As String
zz保存名_前 = Mid(zz保存名, 1, InStrRev(zz保存名, ".") - 1)
zz保存名_後 = Mid(zz保存名, InStrRev(zz保存名, "."))
zz連番 = 2

zz検証用文字列 = zz保存名
Do Until Dir(zz検証用文字列) = ""
    zz検証用文字列 = zz保存名_前 & "_(" & zz連番 & ")" & zz保存名_後
    zz連番 = zz連番 + 1
Loop
zzz可能ファイル名 = zz検証用文字列

End Function

ExcelVBAのコードで、ファイル名を付けて保存する処理があります
その際に、付けたいファイル名が使用済みであった場合上書き保存の確認が出てきたり、自動的に上書きされてしまったりします

エクスプローラー上での連番が振られて、ファイルが別ファイルとして保存されていくような処理はVBAで自動的に行うことは出来ません

実際に保存処理を行う前に、重複しない名前を取得しておく必要があります
その時に使用する関数です

この関数は、引数に指定したパス文字列のファイル名を重複する場合は連番を付けて返します
この関数で取得した文字列をファイル名の絶対パスとして指定すれば、重複することが無くなります

コードの使用方法

ThisWorkbook.SaveAs zzz可能ファイル名(ThisWorkbook.FullName)

このコードの様に、関数として引数を指定して使用します
ここでは、このコードのあるExcelブックを同じ場所に名前を付けて保存します

コードを数回実行した状態
コードを複数回実行

コードがある元々のExcelブックは「TestFile.xlsm」になります

そのファイルに上記の関数とコードを記載して、元ファイルを開きなおして数回実行したフォルダ内の状態です

元々のExcelブック名に連番を付けて保存します

コード解説

Function zzz可能ファイル名(ByVal zz保存名 As String) As String

・・・

End Function

使用可能な文字列を返すので、戻り値を利用するのでFunctionプロシージャで作成を行います
また、戻り値はパス文字列なのでString型で宣言しています

引数は1つ設定しています
zz保存名は、使用したいファイル名の絶対パス文字列を指定してもらいます
なお、ここでの処理ではその文字列があれば良いのでByValで引数を指定しています
これは引数を値渡しとするものです、処理中に変更したりはしませんので
参照渡しでもええっちゃええです

Dim zz保存名_前 As String, zz保存名_後 As String
zz保存名_前 = Mid(zz保存名, 1, InStrRev(zz保存名, ".") - 1)
zz保存名_後 = Mid(zz保存名, InStrRev(zz保存名, "."))

作成する保存名を作成するうえで必要なパス文字列として、引数として指定された文字列を一旦拡張子までで分割します
引数に指定する文字列は拡張子まで含めた絶対パス文字列になるので、文字列の最後にはファイルの拡張子が存在します

なので、この引数の文字列の後に単純に連番を追加しても有効なパス文字列にはなりません
その為に、分割した文字列を別々の変数にそれぞれ取得させます

その変数の宣言と、分割取得の箇所になります

Mid関数は、指定文字列の中から指定文字数を抜き出す関数です
これを文字数を指定することでパス文字列を分割取得することが出来ます

引数の中にある、InStrRev関数は指定文字列の中から特定の文字を検索して何文字目にあるかを返す関数です
InStrRev関数は、特定の文字を検索する場合に文字列の右側から検索を行います

今回分割したい文字列は拡張子の部分になります
なので「.」を検索する必要がありますが、この文字自体はファイル名やフォルダ名に使用することが可能です

その為、文字列の左側から検索した場合拡張子が取得できない可能性があります
また拡張子の文字数も3文字や4文字など固定されたものではありません
ですが、さすがにファイルの拡張子に「.」が使用されることはありませんので、文字列の右側から検索を行えば、確実に拡張子との分割箇所が特定できます

ちなみに、この関数は絶対パスから「\」を検索することでフォルダまでのパスに分割するような事にもよく使われます

Dim zz連番 As Long
zz連番 = 2

ファイルに結合する連番の整数値用の変数です
重複があれば数字を連結するので、この数値を使用するタイミングは前提として重複するものがあるという事なので、初期値は2から始めます

Dim zz検証用文字列 As String
zz検証用文字列 = zz保存名

検証用の文字列を一時的に代入するための文字列変数です

初期値として、引数の文字列をそのまま代入します
まず最初にこの代入した文字列で検証を行う形になります

Do Until Dir(zz検証用文字列) = ""
    zz検証用文字列 = zz保存名_前 & "_(" & zz連番 & ")" & zz保存名_後
    zz連番 = zz連番 + 1
Loop

ループ処理で、使用可能な連番の数値になるまで繰り返します

Dir関数でパス文字列が使用可能かどうかを検証しています
この関数は、引数のパスが有効であればそのファイル名のみを取得する関数です

パスのファイルが存在しない場合は、空白を返します
つまり、この関数で空白が取得されれば重複するファイル名が存在しないことが判定できます

ループ処理の中では、連番数字を連結した文字列を検証用文字列に代入しています
代入が終わってから、数値変数を加算しています

代入が終わったら、また上記の検証を行い
使用可能なパス文字列が生成されるまでループ処理を行います

zzz可能ファイル名 = zz検証用文字列

最後に、使用可能なパス文字列が代入された検証用文字列の文字列を関数名に代入します
これが、この関数の戻り値になります

関数の型は、String型なので文字列として、このパス文字列を返すことが出来ます

FileDialog.InitialFileNameプロパティ

ダイアログ表示をする際に、初期表示されるフォルダの指定を行うプロパティ

'このExcelブックのフォルダを初期表示する
Application.FileDialog(msoFileDialogFilePicker).InitialFileName = ThisWorkbook.Path

「FileDialog.InitialFileName」プロパティでは、ダイアログの表示時に初期表示されるフォルダを指定することが出来ます
このプロパティに指定フォルダまでの絶対パスを代入することで、設定を行うことが出来ます

このプロパティを設定しない場合は、カレントフォルダという作業中のフォルダが指定されます

ダイアログを使用する場合に、ある程度フォルダ位置が限定できている場合は使用するようにするとユーザーにやさしい設計になります

記事コードでは、このコードを記載したExcelブックのあるフォルダを指定しています

それがコード右辺の「ThisWorkbook.Path」という部分です

「ThisWorkbook」というオブジェクトは、このVBAが作成されているExcelブックのことになります
ActiveWorkbookは可変ですが、こちらは常に固定のExcelブックを取得できるので便利です

「Path」は、そのオブジェクトの絶対パス文字列のプロパティです
ファイルのあるフォルダ名までが取得されますので、こういった同じ場所を指定するには便利なプロパティです

フィルタ後の結果件数を取得

SUBTOTAL関数を使用してフィルタの抽出結果件数を取得するコード

'フィルタ結果件数取得
Debug.Print Application.WorksheetFunction.Subtotal(103, Range("A:A")) - 1
フィルタの結果件数をイミディエイトに出力する
フィルタ抽出後にコードを実行

ワークシート上でフィルタ機能を使用して、抽出されたデータの件数を取得します
このコードを実行すると、件数を整数値として取得出来ます

そもそもフィルタとは、Excelの標準機能の1つです
特定の列のデータの中から指定の条件に一致するデータだけを表示させて、一致しないものは行が非表示になります

そして上記にもあるように、これは標準機能です
なのに、結果の件数を取得するプロパティやメソッドはありません
自分で数える必要があります

フィルタをかけた結果の状態
フィルタをかけた状態

この画像はフィルタをかけた状態です
画像下部の赤く色の付いた箇所にフィルタの結果件数が表示されています
この数字が欲しい訳です

ここにあるんやから、プロパティとかで簡単に取得出来そうな感じがするのに何でかありません

件数の求め方は、2通りあります
1つは記事コードのワークシート関数を使用する方法
もう1つは表示・非表示のセル個数を数えて求める方法です

結果は当然同じになるので、どっちでもええと思います
ただ後者の方法は回りクドいコードになってしまうので、ワークシート関数を使用する方が簡単です

コード解説

Debug.Print Application.WorksheetFunction.Subtotal(103, Range("A:A")) - 1

コード解説というか、これは関数解説にあたるかもしれません

ただ単にワークシート関数のSUBTOTAL関数を使用しているだけです
なので、この関数について少し解説をします

この関数は、小計関数とも呼ばれている
小計計算に適した関数です

さらに特殊なのが、引数1つ目の設定で色々な計算方法を選択することが出来る点です

今回はその中で、COUNTA関数の計算方式を使用します
COUNTA関数は空白ではないセル個数を取得する関数です

その指定箇所が、引数1つ目の103という数値の部分です
この103は、COUNTA関数の計算方法で、かつ非表示行を計算に含まない指定になります

これで、表示されたデータのあるセルの個数が取得されます

~~ - 1

ここで取得したデータのあるセルというのは、当然見出し部分も含んでいます
なので、この取得したセルの行数から見出し行の数だけマイナスすればフィルタの結果件数を取得することが出来ます

見出しの行数が2行ある場合は、「-1」を「-2」に変更するようにしてください

ただこれだけで良いのですが、プロパティがあったらええのになぁ、と感じます

何故なら、このフィルタをかけて何らかの処理を行う場合にはほぼ必須のコードだからです
フィルタの結果件数が0になる可能性は自動処理では往々にしてあります

この状態で抽出されたデータをコピペする場合、なにも範囲指定されないだけならまだしも、使用するプロパティ次第では見出し方向に指定されて見出しがコピペされてしまったりします

そもそも条件に一致するものが無いということ自体をユーザーに知らせる必要がある場合もありますので、フィルタをVBAでかける際はきっちり判定を行うようにしましょう

引数の「103」について

引数1つ目は計算方法の指定と解説しました、103という数値が表示された行のCOUNTA関数になります
これを「3」と設定した場合は、表示・非表示は考慮しなくなりますので、非表示の行のデータの個数も数えられます

関数のリファレンスにも記載はあるのですが、このSUBTOTAL関数でどちらを設定しても、フィルタにより非表示になっている行は除外されます

なので、フィルタ件数のカウントに関してはどちらでも構わない訳です
ですが記事コードでは非表示を含まないように指定しています

フィルタを使用せずに、ユーザーが非表示にした行数を数えたいことも過去にあったのでどちらにも対応できるようにしたかったからです

フィルタがかかっていない状態で、単にユーザーが行の非表示をした場合でこの「3」か「103」かで差が発生します

フィルタをかけずに、ユーザーが2行非表示にした場合にこの設定次第でその2行の差がカウント数に出ます
当然と言えば、当然ですよね
非表示を含むか含まないかの設定なんだから

まあ、そのユーザー操作の方も非表示を除外したい場合は「103」にするわけです

ただ、ここで1つ仕様上の注意点があります
そしてこれこそが記事コードの弱点でもあります

手動非表示行に対応できていない動き
手動で非表示行を設定している場合

この画像を確認してください
特に右下のレコード数の表示の部分に注目してください

「5レコード中4個が見つかり!」と、途中で切れてまるでアニメの萌えキャラの独特な語尾のようになっていますが、それはさておき実際にシート上に表示されているデータ行数と一致していません

これは「フィルタをかけたうえで手動で1行非表示にする」というとんでもなく意地悪な操作を行っているためです
それは手動で行わず、フィルタで行ってくれよ!と言いたいところですが、こんな意地悪な状況でも関数はうまく答えています

データ個数「3」が出力されています
そして、今回は引数1つ目を「3」と「103」の両方を使用していますが、どちらも同じ数値を取得しています

と、ここで不思議な話です
フィルタをかけて非表示になった行に関しては、設定の違いに関係なく非表示行は無視される、と上記でも解説しました

ですが、今回はフィルタではなく手動による非表示行があります
実際、ステータスバーの文字列のカウントが4個となっていることからもフィルタとしての非表示と違うのが分かります

これで分かるのは、ステータスバーでの個数の数え方が根本的に表示だとか非表示だとかではなく、条件に一致するものを数えているのだということです

つまり、このステータスバーの数値が簡単にプロパティで取得できればええのに~と言いましたがこの数値がそのまま表示されたデータ個数にはならないことがあるため、プロパティが無いのかもしれません

開発者の方々のデバッグ力の凄まじさが良く分かるし
やっぱりVBAって、良く考えられてる言語やな~

見出し行以外の全範囲を選択する

見出し以外の全体を取得します
データ範囲のみの取得や初期化に使用します

'2行目以降を全て選択する
Cells. _
Resize(Cells.Rows.Count - (Range("A2").Row - 1), Cells.Columns.Count). _
Offset(Range("A2").Row - 1, 0). _
Select

表範囲の中で、見出し(見出しを1行目だけとした場合)以外の行を取得します
見出し以外を全て新たなデータに置き換える時にデータ範囲を全て初期化する時に使用します
なお、解説の為に改行しています

2行目以降の全ての範囲を選択する
コードを実行した時の動き

画像のような表範囲の中で、1行目が見出しで2行目以降がデータ範囲である場合にコードを使用することで見出し以外の範囲を全て選択することができます
少し見づらいですが、左下のウォッチ式の中の値がA1から行範囲にアドレスが変化している部分を確認してください

また、少し応用すると空白を除いた純粋なデータ範囲のみを取得する事も可能です

コード解説

Cells. _

何よりまずは、セル全体を指定しますのでCellsオブジェクトに対して処理を行います
Cellsオブジェクトは引数を省略した場合はセル全体が取得されます

Resize(Cells.Rows.Count - (Range("A2").Row - 1), Cells.Columns.Count). _

次に、Resizeプロパティを使用して範囲サイズの変更を行います
このプロパティは引数に指定した数値のセル範囲を取得するプロパティです

この際に指定する数値は、1つ目の引数が行数になります

(Cells.Rows.Count – (Range(“A2”).Row – 1),
この部分が1つ目の引数の部分になります、ここで行数を指定します
ここでは、A2セルを起点にするので2行目ということになります
2行目から取得を行うということは、除外するのが1行目の1行だけということになりますので、セルの全行数から1を引くことで指定することができます

とはいえ、その数値は固定では2行目以降しか選択できなくなってしまいますので可変にしておいたほうが便利です
そこでRangeオブジェクトのRowプロパティを使用して指定セルの行数を取得します
その行数から、-1します
これは、除外する行数の指定なので指定セルは除外対象になりません
なので除外する行数から-1してあげることで、指定セルの1行上が指定できます

Cells.Columns.Count)
2つ目の引数は列数の指定になりますが、今回は全ての範囲を指定するのでCellsオブジェクトに含まれている列数を指定します
これで全ての列の指定になります

これで、取得するべきセル範囲に変更することが出来ました

Offset(Range("A2").Row - 1, 0). _

Reiszeプロパティは、指定セル範囲のサイズを変更しますが、左上からのサイズ変更になります

つまり、上のコードで変更した行数では範囲の下から行数が削られていきます
なので、このままでは1行目が除外されるわけでは無く、最下行が除外された状態になります

そこでOffsetプロパティを使用して範囲を移動させます
Offsetプロパティは引数に指定した数値分、セル範囲を移動させます

今回は行方向への移動を行いますので、引数の1つ目に移動数を指定します
この移動値に関しては、Resizeプロパティで使用したものと同じになります

Select

最後に、この取得したセル範囲を選択状態にします
ここのメソッドを変更すれば、他の操作も可能になります

メソッドを使用せずに変数に取得させて、これらの範囲に対して色々な操作を行うことも出来ます

セル範囲を画像ファイルとして保存

セル範囲をPNG画像として保存する方法です

'選択範囲をクリップボードにコピー
Selection.CopyPicture

Dim グラフ範囲 As ChartObject, ファイルサイズ As Long
'空白のグラフ範囲を新規作成する、サイズは選択範囲と同じサイズにする
Set グラフ範囲 = ActiveSheet.ChartObjects.Add(0, 0, Selection.Width, Selection.Height)
'空白のグラフ範囲を画像ファイルとして保存
グラフ範囲.chart.Export ThisWorkbook.Path & "\セル範囲画像.png"
'そのファイルのサイズを整数変数に取得
ファイルサイズ = FileLen(ThisWorkbook.Path & "\セル範囲画像.png")
'空白のファイルサイズを超えるまでループする
Do Until FileLen(ThisWorkbook.Path & "\セル範囲画像.png") > ファイルサイズ
'クリップボードにコピーしたセル範囲画像を貼り付け
グラフ範囲.chart.Paste
'貼り付けしたグラフ範囲を画像ファイルとして保存
グラフ範囲.chart.Export ThisWorkbook.Path & "\セル範囲画像.png"
'CPUの解放
DoEvents
Loop
'作成が完了したらグラフ範囲は削除して解放
グラフ範囲.Delete
Set グラフ範囲 = Nothing

Excelのワークシート上で綺麗に仕上げたセル範囲が完成しました
整然と並んだデータ群と、目に優しい柔らかな配色
見やすさを考え抜いた文字種とサイズ
生データのままではなく、見やすく入力しやすくした表示形式

はい、画像として保存しておきたいですよね?
ただのコピペじゃ、貼り付けられない表現力を遺憾無く見せつけられるのが画像化です

画像保存したいExcelの表範囲
生成前のセル範囲

コードを使用してデータのある範囲を画像として保存してみます

実際に画像として保存されたセル範囲画像
コードで生成された画像

この画像はスクショではありません、コードを実行して保存した画像です
指定範囲を切り取り作業無しで保存できます
このピッタリのセル範囲画像の気持ちよさは、思わず職場のPCのデスクトップ背景に設定してしまうのではないでしょうか

そうですね、言いたいことは分かります
最初にあれだけ書式設定をがんばって作ったとか言ってたのに、例画像がまったく頑張ってへん!というのは心にしまっておいてください

コード解説

程よく脱線というか、そもそも入線すらしてなかったところで、コード解説です

先に大まかな処理の流れを解説します

まず、セル範囲を画像として保存するにはその範囲をスクショ的なことをします
ただ、それを画像ファイルにする事は直接出来ないため、それが可能なグラフ範囲を使用します
このグラフ範囲にスクショ的なやつを貼りつけて、それを画像として保存します

'選択範囲をクリップボードにコピー
Selection.CopyPicture

まずは、セル範囲をスクショ的なやつします
それが、このCopyPictureメソッドです

このメソッドは、セル範囲をクリップボードに画像として取得します
なので、このまま他の場所にペーストすれば画像を貼り付けられます

今回はファイル保存なので、後に続きます

Dim グラフ範囲 As ChartObject, ファイルサイズ As Long

ここで使用する変数の宣言です
この処理で使用する変数は2つです

1つ目はグラフ範囲を取得するObject型の変数です
固有の型指定としてChartObject型を指定します
これはグラフ範囲のObjectです、この中に実際のグラフ範囲やタイトル範囲などのObjectがあります
その一部にコピーした画像を貼り付けます

と、いうのもクリップボードにコピーした画像をファイルとして保存するメソッドはありません
これをどこかに貼り付けてファイル保存する必要があり、それが可能なのがグラフ範囲になります

なので、作成後もいろいろ操作をするので変数に取得させておくほうが便利です

2つ目の変数はファイルサイズの整数値を取得させる変数です
この利用理由は後述します

'空白のグラフ範囲を新規作成する、サイズは選択範囲と同じサイズにする
Set グラフ範囲 = ActiveSheet.ChartObjects.Add(0, 0, Selection.Width, Selection.Height)

グラフ範囲を作成します
ここで作成されるグラフは何もない真っ白なグラフ範囲になります

グラフの作成にはChartObjects.Addメソッドを使用します
引数は、作成する位置と大きさのサイズになります

最初の2つが位置設定になりますが、この処理では位置はどこになっていても関係ないので「0」を指定します

後半の2つの設定が、サイズ設定になります
ここでは選択範囲と同じサイズにする必要がありますので、Selectionに対する高さと横幅を指定するようにします

これで、選択範囲と同じサイズのグラフ範囲が完成します
完成と同時に、その作成されたグラフを変数に代入します
これ以降は、この変数名でグラフを操作します

'空白のグラフ範囲を画像ファイルとして保存
グラフ範囲.chart.Export ThisWorkbook.Path & "\セル範囲画像.png"

まずは、クリップボードの画像を貼り付けする前に空白のグラフを画像として保存します

chart.Exportメソッドを使用することで、グラフ範囲を画像として保存することができます
引数には保存パスを絶対パスで指定します、この時にファイルの拡張子まで指定しますが他の画像拡張子も利用可能ですが、その場合は2つ目に省略された引数FilterNameを対応するものに変更します
ここでは、省略していますが省略した場合はPNGが指定されます

これは、後で必要になる手順の準備です

'そのファイルのサイズを整数変数に取得
ファイルサイズ = FileLen(ThisWorkbook.Path & "\セル範囲画像.png")

上で作成した空白の画像ファイルのサイズを整数値で取得します
ファイルサイズの取得にはFileLen関数を使用します
引数に指定されたファイルのサイズが整数値で返されますので、それを変数に取得します

これは、この処理自体の問題点に起因します
この処理では選択範囲をクリップボードに貼り付ける際、クリップボードの処理がVBAでは行っていないため、VBAで後述の貼り付け操作をする際にクリップボードが更新されておらず、真っ白なまま画像として保存されてしまいます

時間での待ちでも構わないのですが、貼り付けたいセル範囲が大きな場合その時間が不足する場合があります
そこで、空白のファイルサイズを取得しておき画像を貼り付けたファイルサイズと比較することでしっかり貼り付けられたことを認識することができます

空白の画像ファイルは小さいし、画像が貼り付けられれば間違いなくこの空白の画像よりサイズが大きくなります
逆になることは確実にあり得ませんし、画像が貼り付けられても同じサイズということもあり得ません

この方法なら、小さい画像であれば無駄な待ち時間も発生しないのもいいね

'空白のファイルサイズを超えるまでループする
Do Until FileLen(ThisWorkbook.Path & "\セル範囲画像.png") > ファイルサイズ
・・・
Loop

上記でくどくど説明した、ファイルサイズを比較している部分です
このDoループは空白のファイルサイズと改めて保存された画像ファイルのサイズを比較して、新たに保存された画像ファイルの方がファイルサイズが大きければ処理を終了します

間違いなく最初に1回は実行されます

'クリップボードにコピーしたセル範囲画像を貼り付け
グラフ範囲.chart.Paste

ファイルサイズが超えていなければ、クリップボードのデータを貼り付けします
この時点でグラフ範囲にセル範囲画像が貼り付けられます

上記にもあるように、クリップボードの処理が追い付いていない場合はこの時点でも空白のグラフ範囲になる場合があります

'貼り付けしたグラフ範囲を画像ファイルとして保存
グラフ範囲.chart.Export ThisWorkbook.Path & "\セル範囲画像.png"

グラフ範囲を改めてここで保存しなおします
同名のファイルがあった場合は自動的に上書きされるので、メッセージの抑止は不要です

というか、その点は無条件上書きなので注意が必要です

'CPUの解放
DoEvents

貼り付けがうまくいかない時は、クリップボードの処理が追い付いてない時になりますので、それを処理してもらうためにここでDoEvents関数でCPUを解放しています

'作成が完了したらグラフ範囲は削除して解放
グラフ範囲.Delete
Set グラフ範囲 = Nothing

ループを抜けたということは、しっかり画像が貼り付けられた画像ファイルが生成されたことを意味します
なので、不要になったグラフ範囲を削除します
変数の参照も解放しておきましょう

では、記事コードで本気の書式設定セル範囲を画像保存してくださいね!

DoループでRangeオブジェクトを使用する

Doループを使用して、セルを移動させて検証していく際に、Range型の変数を利用する方法

'A2の下にある空白セルを選択する
Dim セル As Range
Set セル = Range("A2")

Do Until セル = ""
Set セル = セル.Offset(1, 0)
Loop
セル.Select

セル選択を減らすためには、RangeオブジェクトをForEachループで処理を行います
ただその処理は事前にセル範囲が分かっている場合に限定されます

今回の記事にある、空白のセルになるまで処理を繰り返す
というような、セル範囲ではなくセル単体を検証していくようなループではDoループで処理を行います

そして、その際にセルを選択することを無くすためには、コードにあるRange.OffsetプロパティやCellsオブジェクトなどで整数値を増減させて移動させます

この方法でセル選択は無くすことが出来るので、処理速度の向上は図れています

ただ、この方法での面倒な場合があります
それは別のシートやブックのセルを処理したい場合です
この場合には、シート名から指定する必要があり、コードが冗長になります
Withステートメントを使用する方法もいいのですが、あまり処理行が長い場合にはコードの可読性はあまり高くありません

そこで使用したいのが記事コードです

コードの解説

Dim セル As Range
Set セル = Range("A2")

Do Until セル = ""
Set セル = セル.Offset(1, 0)
Loop
セル.Select

このコードではセルを変数に代入して、それを更新していくことでセルの移動を行うループ方法です

DoループをRange型変数で実行した時の動き
コード実行によるセル選択

画像のコードの動きを確認してください
最初にA2セルが代入されて、ループ処理により検証取得されたセルを最後に選択しています、A2から下方向に空白のセルを検索しているので、A4が選択されて終了しています

Dim セル As Range
Set セル = Range("A2")

まずはセル変数の宣言と、初期位置の取得です
A2セルを取得させることで、この変数での初期位置とします

Do Until セル = ""

・・・

Loop

取得したセルが空白になるまでループを繰り返します
ただ、当然このままでは永遠にA2セルを参照し続けるので無限ループです

Set セル = セル.Offset(1, 0)

ここでセルを更新しています
セル変数に代入するセルは、その変数自体を指定することができます

このセル変数からOffsetを使用して1つ下のセルを代入させます

セル変数自体は、すでにブックやシートを含めたセルが取得されていますのでアクティブブックやシートである必要もありません

そして、ここでの代入によって次のセルが参照対象になるわけですが、セルへの参照が切り替わるだけでセル選択に関しては行っていませんので、メモリ上で処理が実行されています

なので整数型を使用した移動処理と差は感じられることは無いと思います

セル.Select

空白のセルが見つかったら、セルの更新が行われずにループを抜けるので
ループ後にそのセル変数を操作することが出来ます

別のシートのセル範囲を操作したいことは多いので、この方法を使用すればコードが非常にすっきりするので試してみてください

処理実行前の選択範囲に戻す

Application.Gotoメソッドを使用して、処理を実行する前のセル選択範囲に戻す方法

'処理前の選択範囲の取得
Application.Goto Selection

’取得したセル範囲を選択
Application.Goto

処理を実行する時にどうしてもセル選択を行う必要があり、しかも処理実行後にはもともと選択していたセル範囲に選択を戻しておきたい

そんな場面があります、そんな時に使用するのがこのコードです

コード解説

Application.Goto Selection

Range("A100:A103").Select
Range("B100:B103").Select
Range("C100:C103").Select

Application.Goto

記事コードを上記の様に変更しました
選択範囲を変更して処理実行前の選択範囲に戻します

処理実行前の選択範囲に戻る動き
処理実行後にもともとの選択範囲に戻す
Application.Goto Selection

Application.Gotoメソッドを使用して、最初にもともとのセル範囲を再指定します

ここは正直、引数にSelectionを指定する必要はありません
このSelectionはコードでいうRange(“A100:A103”)を指定しても構いません

コードの可読性の為にしているだけですね

SelectionでもRange指定であっても、このコードが実行された時点で、もともと選択されていたセル範囲がApplication.Gotoメソッドに保持されます

Range("A100:A103").Select
Range("B100:B103").Select
Range("C100:C103").Select

そのあとで、適当に処理を実行してセル選択を別の範囲にします
この時、選択範囲の変更は何度行っても問題ありません

Application.Goto

処理を実行して、またもともとの選択範囲に戻したい場合はApplication.Gotoメソッドの引数Referenceを省略して指定することで、もともとの選択範囲が指定されます

2つ目の引数ScrollをTrueに指定すれば、もともとの選択範囲を表示内の左上に表示させます

また、このメソッドは直前に保持した分のみしか保持できませんので
処理の中でApplication.Gotoメソッドを使用したい場合は利用できません

その場合はもともとの選択範囲をRange型のObject変数に代入して保持しておく方法で行います
あまりないとは思いますが・・・