和風スパゲティのレシピ

日本語でコーディングするExcelVBA

複数領域を持つRangeのプロパティ挙動について

Rangeオブジェクトは特殊なオブジェクトで、
単独セル、セル範囲どちらもRangeオブジェクトであり、
さらにはそれらを複数領域指定してもRangeオブジェクトです。

複数のセル領域を格納したRangeオブジェクトについて、
各プロパティがどのような挙動を示すかを検証します。

Areasプロパティ

まずは基本知識として、複数領域をもつRangeオブジェクトは、
Areasプロパティを使って領域ごとに取得することができます。

' 複数領域を持つRangeを領域ごとにループする
Dim 親Range As Range
Set 親Range = Range("A1,B2:C2,D4:E6")

Dim 各セル範囲 As Range
For Each 各セル範囲 In 親Range.Areas
    Debug.Print 各セル範囲.Address
Next

 
Areasプロパティを使用すれば、今回説明する挙動のほとんどを、
変に気にすることなく正確なコードを書くことができます。

まずはこのプロパティをしっかり押さえてください。

詳細はこちらをどうぞ

Valueプロパティ,Rows/Columnsプロパティ

よく使うValue、Rows、Columnsなどのプロパティの挙動ですが、
これらは「第1エリアだけを見て取得する」という挙動になります。

Dim 親Range As Range
Set 親Range = Range("A1,B2:C2,D4:E6")

親Range.Value ' ← A1セルの値が入ります。
親Range.Rows.Count ' ← A1セルなので1行
親Range.Columns.Count ' ← A1セルなので1列

 
つまりこれらのプロパティは、

親Range.Areas(1).Value ' ← A1セルの値
親Range.Areas(1).Rows.Count ' ← 1
親Range.Areas(1).Columns.Count ' ← 1

このコードと同じ挙動になるということになります。


逆に「総行数」をカウントするようなプロパティはありませんので、
これを実装する場合は愚直にカウントしていく必要があります。

Dim 親Range As Range
Set 親Range = Range("A1,B2:C2,D4:E6")

Dim 総行数 As Long: 総行数 = 0
Dim 各セル範囲 As Range
For Each 各セル範囲 In 親Range.Areas
    総行数 = 総行数 + 各セル範囲.Rows.Count
Next

 
特に↓のような形で複数行をためていくような処理において、

Set 対象行 = Union(Rows(R), 対象行)

この対象行の総行数を「対象行.Row」ではカウントできません。


この場合は上記のように各Areaを愚直に数えて回るか、
Intersectメソッドでカウントするのも便利なので覚えておきましょう。

対象行数 = Intersect(対象行, Columns("A")).Cells.Count

Interiror/Font/Mergeプロパティなど

続いて書式系列のプロパティですが、
こちらはAreaの数に関係なく通常のセル範囲と同様の動きになります。

通常のセル範囲と同様とは、

  • 内部のセルがすべて同じ書式に統一されているならそれを取得
  • 内部のセル書式が統一されていないなら0やNull

という取得形式になります。


例えば「A1セル赤色、B1セル赤色、C1セル白色」だったとき、

Range("A1").Interior.Color ' = 255
Range("A1,B1").Interior.Color ' = 255(同色なのでOK)
Range("A1,C1").Interior.Color ' = 0(別色なので0)

こんな挙動になります。


このように複数領域を持つRangeオブジェクトにおいては、

  • Valueをはじめとした「第1エリアだけ見る」プロパティ
  • Interiorをはじめとした「全エリアを見る」プロパティ

の主に2種類がありますので、
意図して使う場合はどちらの仕様のプロパティかを注意して使用してください。

Cellsプロパティ

続いて仕様の理解が難しいのがCellsです。

まずは以下のコードのように「引数の無いCells」は、
そっくり全エリアのCellsを見てくれます。

Dim 親Range As Range
Set 親Range = Range("A1,B2:C2,D4:E6")

Debug.Print 親Range.Cells.Count ' = 9
Debug.Print 親Range.Cells.Address ' = $A$1,$B$2:$C$2,$D$4:$E$6

 
ただし、各セルが重複しているかを判定はしてくれませんので、
例えば「A3:E3,C1:C5」といった交差した範囲を扱うときは、

' ↓9セルしかないけど10が返る
Debug.Print 親Range.Cells.Count
    
' ↓C3が2回でてくる
Dim 各セル As Range
For Each 各セル In 親Range.Cells
    Debug.Print 各セル.Address
Next

これらの仕様に注意してください。


さて問題はCellsに引数を渡したときです。

例えば以下のコードを実行してみてください。

Dim 親Range As Range
Set 親Range = Range("A1,B2,C3,D4,E5")

Dim i As Long
For i = 1 To 親Range.Cells.Count
    親Range.Cells(i) = 1
Next

 
このコードの結果はなんと↓のようになります。
Cellsの挙動


Cellsプロパティは引数を渡すと別物になり、
「第1エリアからのOffset」のような動きになります。


そもそもCells(i)自体が元のRangeをはみ出すことができ、

Dim 親Range As Range
Set 親Range = Range("A1:C3,E4")

Dim i As Long
For i = 1 To 親Range.Cells.Count + 10
    親Range.Cells(i) = i
Next

このコードが以下のような実行結果になります。
Cells.CountをオーバーしたFor文


Cellsの元々の複雑さにAreas(2)以降が無視される仕様が加わりますので、
ないとは思いますが意図して使用する場合は十分注意しましょう。

Rangeオブジェクトの格納限界(255文字)

ここでRangeオブジェクトがどれくらいのセルを格納できるかですが、
Worksheets("○○").Cellsで1048576×16384セルを取得できるように、
セルの数には上限はありません。

ただし、Range"プロパティ"には「255文字以内」という制限があり、

Debug.Print Range("$A$2,$A$4,$A$6,$A$8,$A$10,$A$12,$A$14,$A$16,$A$18,$A$20,$A$22,$A$24,$A$26,$A$28,$A$30,$A$32,$A$34,$A$36,$A$38,$A$40,$A$42,$A$44,$A$46,$A$48,$A$50,$A$52,$A$54,$A$56,$A$58,$A$60,$A$62,$A$64,$A$66,$A$68,$A$70,$A$72,$A$74,$A$76,$A$78,$A$80,$A$82,$A$84,$A$86,$A$88").Address

こちらのコード(""内が259文字)は、

Rangeメソッドは失敗しました。

エラーとなります。


※ RangeオブジェクトとRangeプロパティの違いはちょっと難しいのですが、

Set Range変数 = Range("A1")

このコードを
「Rangeプロパティに"A1"を渡して取得したA1セルというRangeオブジェクト」
と読むとなんとなくわかるかもしれません。

詳しく読みたい方はこちらの記事をどうぞ


この255文字制限を突破したRangeオブジェクトを取得したい場合は、
UnionメソッドでRangeを結合していきます。

Sub Rangeの格納限界テスト()

    Dim rng As Range: Set rng = Range("A2")
    Dim R As Long
    For R = 4 To 1000 Step 2
    
        Set rng = Union(rng, Cells(R, 1))

    Next
    
    rng.Select

End Sub

このコードであれば、A2、A4、…と500個のセルを格納できます。


一応、仕様上の格納数に上限はないようなのですが、
1000を2000にすると4~5倍、3000にすると10倍と、
セル数が増えると加速度的に処理時間が増えていきます。

現実的には1000程度が限界と思っておいた方がよさそうですね。

Addressプロパティ(255文字制限)

先ほどのコードの最後でAddressプロパティを参照してみます。

Sub Rangeの格納限界テスト()

    Dim rng As Range: Set rng = Range("A2")
    Dim R As Long
    For R = 4 To 1000 Step 2
    
        Set rng = Union(rng, Cells(R, 1))

    Next
    
    rng.Select
    Debug.Print rng.Address
    Debug.Print rng.Address(0, 0)

End Sub

すると、

  • 第1コードは「…,$A$84,$A$86」までを表示
  • 第2コードは「…,A120,A122,A124」までを表示

と、A1000セルまでを表示してくれません。

しかしながらrng.Selectは正しく機能しており、
A1000セルをしっかり選択しています。


このように、AddressプロパティにもRangeプロパティと同様、
255文字の文字数制限がかかる
ことがわかります。

他に、

  • エラーにはならず表示できる部分までを表示する
  • きっかり255文字ではなく、オーバーしない最後のエリアまでを表示する
  • 絶対参照にすると$の分表示できるセル数が減る

などの特徴があることもわかりますね。


これが不具合を生むことは稀な気はしますが、
一応この仕様も覚えておきましょう。


以上で複数領域を持つRangeのプロパティ挙動についての検証を終わります。

ひとまず「Value」や「Rows/Columns」が第1エリアしか見ないことを覚えて、
あとはなるべくこれらの仕様に引っかからないよう

  • AreasプロパティをFor Eachで回してしっかり範囲ごとに処理をする
  • Selectionなど複数領域の懸念がある場合はAreas.Countが1かを調べる

これらを守ってコードを書いていきましょう。