t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA Cellsの正体は、全セルを包むRangeオブジェクトである

セルを行・列の数値で指定するCellsはRangeと並んで基礎中の基礎であるが、その正体を知る者は少ない。

まあ別に知ったところで使い方が変わるわけではないのだが、知っておいて損になるものではないので紹介しよう。

今回も評価という用語を用いるので、意味が分からない方はこちらを参照。
thom.hateblo.jp

さて、次のコードを例に評価プロセスを追ってみよう。

ThisWorkbook.Sheets(1).Cells(1,1).Value = "a"

上記はいろいろと省略されているので、まずフルバージョンを記述する。

ThisWorkbook.Sheets.Item(1).Cells.Item(1, 1).Value = "a"

※実際にはItemプロパティではなく[_Default]プロパティであるが、話がややこしくなるのでここではItemとしておく。

  1. ThisWorkbookが評価され、Workbookオブジェクトになる。
  2. Workbookオブジェクト.Sheetsが評価され、Sheetsオブジェクトになる。
  3. Sheetsオブジェクト.Itemプロパティ(1)が評価され、Sheetオブジェクトになる。
  4. Sheetオブジェクト.Cellsプロパティが評価され、Rangeオブジェクトになる。
  5. Rangeオブジェクト.Item(1, 1)が評価され、Rangeオブジェクトになる。
  6. Rangeオブジェクト.Valueに"a"が代入される。

評価のプロセスは以上である。

上記の4と5のステップについて、混乱された方がいるかもしれないのでもう少し補足する。

実は、Cellsに渡す行・列の値というのはCellsプロパティの引数ではない。
Cellsはあくまでシートの全セルを含むRangeオブジェクトを返すプロパティで、Cellsそのものに引数は存在しない。
Cellsプロパティが評価された結果、全セルを含むRangeオブジェクトが戻り値として返り、そのRangeオブジェクトのデフォルトプロパティが引数(1, 1)をとる。

これが、Cells(1, 1)の内部動作である。

その証拠にクイックヒントを見ると、Rangeプロパティは直接引数を求められるのに対し、Cellsプロパティでは_Defaultとなっている。
f:id:t-hom:20160530204510p:plain

Rangeオブジェクトのデフォルトプロパティに(1, 1)を渡すというのもややこしい話である。
安直にRange(1, 1)と書くと失敗する。なぜだろうか。
f:id:t-hom:20160530204912p:plain

実は、シートオブジェクト.RangeというのはあくまでRangeプロパティであり、評価されて初めてRangeオブジェクトになる。
具体的に以下のコードでRangeプロパティの場合の評価プロセスを見ていこう。

ThisWorkbook.Sheets(1).Range("A1").Value = 1
  1. ThisWorkbookが評価され、Workbookオブジェクトになる。
  2. Workbookオブジェクト.Sheetsが評価され、Sheetsオブジェクトになる。
  3. Sheetsオブジェクト.Itemプロパティ(1)が評価され、Sheetオブジェクトになる。
  4. Sheetオブジェクト.Rangeプロパティ("A1")が評価され、Rangeオブジェクトになる。
  5. Rangeオブジェクト.Valueに"a"が代入される。

以上がRangeプロパティを使った場合の評価プロセスである。

Cellsプロパティを使った場合、Cellsプロパティが単独で評価され、全セルを含むRangeオブジェクトになった。
Rangeプロパティを使った場合、引数"A1"と合わせて評価され、A1セルを指すRangeオブジェクトになった。

つまりRangeプロパティは、引数をとってRangeオブジェクトを返すプロパティなのである。

では次に、Rangeオブジェクトのデフォルトプロパティに(1, 1)を渡す処理を、Rangeプロパティを使ってやってみよう。

奇妙な書き方ではあるが、次のコードは正常に動作する。

ThisWorkbook.Sheets(1).Range("A1")(1, 1).Value = "a"

これはRangeオブジェクトのItemプロパティに(1, 1)を渡す、つまりCellsの時と同じ処理である。

Range("A1")の後に続けてカッコを書くと、Cellsのときと同じくクイックヒントに_Defaultが表示される。
f:id:t-hom:20160530210230p:plain

Cellsが全セルを指すRangeオブジェクトを返すなら、たとえばCells(2, 3)と書くのはつまり、Range("A1:XFD1048576")(2, 3)と書くのと同じ意味になる。実際にその通りなのだ。

以下に、いろんな方法で全セルを指定してみた。

Sub AllSame()
    Debug.Print Cells.Address
    Debug.Print Range("A1:XFD1048576").Address
    Debug.Print Range("1:1048576").Address
    Debug.Print Range("A:XFD").Address
End Sub

アドレスはどれも同じ、$1:$1048576が表示される。

では、Rangeオブジェクトの左上がA1じゃない場合にデフォルトプロパティに行・列を与えたらはどうなるんだろうか。

これは、Rangeオブジェクト範囲の一番左上が1, 1となる仕組みのようだ。
たとえば以下のように書いた場合はしたがって、C3に"a"が代入される。

Range("C3:K11")(1, 1) = "a"

また、左上の開始セルだけあれば他は省略しても動作するようで、以下のような記述でも、まるでOffsetプロパティのように機能する。

Range("C3")(5, 5) = "a"

※Offsetは自セルの開始点が0, 0なのに対し、Itemプロパティは自セルが1, 1である。

ということで、今回はCellsプロパティが全セルを含むRangeオブジェクトであることがお分かりいただけたと思う。

もう少しきれいに結論を纏めたかったけど、〆の文章を考えてるうちに眠くなってきたので今日はこのへんで失礼つかまつる。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。