Quantcast
Channel: t-hom’s diary
Viewing all articles
Browse latest Browse all 493

VBA クラスモジュールでRangeの仕組みを説明する

$
0
0

昨日公開した以下の記事について、比喩ではちょっと難しいという声をいただいたので、今回はクラスモジュールを使って私が想像するRangeの仕組みを具体的なコードで解説しようと思う。
thom.hateblo.jp

まずはSheetの模擬クラスとRangeの模擬クラスを用意する。
本物と同じ名前にしてしまうと、メインコードで使う際にちゃんと自作の方を使ってるの?という疑惑につながるので、オブジェクトの名前はオリジナルにしておこう。

Sheetの代わりにSheeeet、Rangeの代わりにoRange(オレンジ)。
f:id:t-hom:20170417214655p:plain

まずSheeeetのコードはこちら

Private table(1To9,1To9)AsStringPropertyGet oRange(adddresss AsString)As oRange
    Dim ret As oRange: Set ret =New oRange
    Dim x AsLong: x = Ato1(Left(adddresss,1))Dim y AsLong: y =CLng(Right(adddresss,1))
    ret.Init Me, x, y
    Set oRange = ret
EndProperty'列のアルファベット(A~I)を列番号1~9に変換する関数Function Ato1(char AsString)AsInteger
    Ato1 =InStr(1,"ABCDEFGHI", char)EndFunctionFriendPropertyLetValue(r AsLong, c AsLong, v AsString)
    table(r, c)= v
EndPropertyFriendPropertyGetValue(r AsLong, c AsLong)AsStringValue= table(r, c)EndProperty

次にoRangeのコード

Private parent_ As Sheeeet
Private row_ AsLongPrivate column_ AsLongSub Init(p As Sheeeet, r AsLong, c AsLong)If parent_ IsNothingThenSet parent_ = p
        row_ = r
        column_ = c
    Else
        Err.RaisevbObject,"oRange","このoRangeはすでに初期化されています。"EndIfEndSubPropertyLetValue(v AsString)
    parent_.Value(row_, column_)= v
EndPropertyPropertyGetValue()AsStringValue= parent_.Value(row_, column_)EndProperty

そして標準モジュールに書くメインコードがこちら

Sub Rangeモドキ実験()Dim sh As Sheeeet: Set sh =New Sheeeet
    
   'まずは普通に値を設定して表示
    sh.oRange("A1").Value="Hello!"
    sh.oRange("B3").Value="GoodBye!"MsgBox sh.oRange("A1").ValueMsgBox sh.oRange("B3").Value'オブジェクト比較でFalseになる実験MsgBox sh.oRange("A2")Is sh.oRange("A2")'oRange型変数に入れて使用Dim r1 As oRange
    Set r1 = sh.oRange("C4")Dim r2 As oRange
    Set r2 = sh.oRange("C5")
    r1.Value="Konnichiwa"
    r2.Value="Sayonara"MsgBox r1.ValueMsgBox r2.ValueEndSub

これでメインコードを実行すると、本物のRangeオブジェクトと同じようにValueの設定、取得ができる。
まあ偽物なので定義した以上の機能はないけど。本物のシートに書き込まれるわけではなく、シートを模したSheeeetオブジェクト内の配列に値が設定されるだけ。

さて、解説だけど全部は面倒なのでポイントだけ。
まずSheeeetクラスの以下の1行。

Private table(1To9,1To9)AsString

これが9×9のセルのを模したもの。実際のシート上の値はここに保存される。
しょぼいけどまぁ説明用サンプルなので。

次にSheeeetクラスのoRangeプロパティ。

PropertyGet oRange(adddresss AsString)As oRange
    Dim ret As oRange: Set ret =New oRange
    Dim x AsLong: x = Ato1(Left(adddresss,1))Dim y AsLong: y =CLng(Right(adddresss,1))
    ret.Init Me, x, y
    Set oRange = ret
EndProperty

プロパティ名のoRangeと型名のoRangeはたまたま同じ名前になってるだけなので注意。これがRangeの理解がややこしくなる原因の一つ。名前が一緒なのでプロパティとオブジェクトを混同しがち。

このプロパティプロシージャでは内部でoRangeオブジェクトを生成して、それを返している。
ret変数に新規のoRangeオブジェクトを格納し、そのoRangeオブジェクトのInitプロシージャにMe(つまりSheeeetオブジェクト自身)を渡している。

そしてoRangeオブジェクトのInitプロシージャではこれを仮引数pで受け取り、自身のparent_変数にセットしている。このときに行と列の情報も受け取って自分のrow_とcolumn_に代入する。

Sub Init(p As Sheeeet, r AsLong, c AsLong)If parent_ IsNothingThenSet parent_ = p
        row_ = r
        column_ = c
    Else
        Err.RaisevbObject,"oRange","このoRangeはすでに初期化されています。"EndIfEndSub

これでoRangeが準備できたので、Valueプロパティで値をいじるとparent_変数にセットしたSheeeetオブジェクトのValueプロパティが操作される。

PropertyLetValue(v AsString)
    parent_.Value(row_, column_)= v
EndPropertyPropertyGetValue()AsStringValue= parent_.Value(row_, column_)EndProperty

つまりデータはあくまでSheeeetオブジェクト内にあり、oRangeは自身が参照を保持しているSheeeetオブジェクトのプロパティを通じて操作しているということ。

ちなみにSheeeet側のValueプロパティはFriendスコープにしているけれど、今回SheeeetのValueはoRangeによってのみ操作されるのであって、メインコードから直接操作はしませんよという意味づけのためにFriendスコープにした。
Friendは同じプロジェクト内でのみ参照できるというスコープである。今回のメインコードでは同じプロジェクト内に書いたので特に操作を制限するといった効果はない。

Excelブック1つごとに1つのプロジェクトしか作れないので、Friendスコープを活かそうと思ったらアドイン化して参照設定するしかないけれど。。
【参考】
thom.hateblo.jp

4/18追記

複数セルは?と突っ込まれそうなので予め言い訳しておくと、複数セルに対応させるとコードの複雑度が増すので今回はあえて単一セルのみサポートするRange型もどきとした。
Range型はエージェントのように機能するという点を解説するにはこれで十分なので。

ちなみに複数セルに対応するには、oRange型の内部で座標をコレクションか配列で持たせ、Valueにアクセスされたら座標をForかFor Eachでまわしながら連続でSheeeetにアクセスすれば良い。広い範囲を扱う場合は開始セルと終了セルを持たせておけばOK。ただし飛び地を扱うにはもう一工夫必要。などなど考えていくと結構面倒なコードになる。


Viewing all articles
Browse latest Browse all 493

Trending Articles