このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
さて、これで通常のメソッドは全て実装した。
最後に ArrayList で最も重要な機能を実装する。
ArrayList が配列同様に振舞うためには、
配列アクセス(インデックス)表記が必要となる。
そのような機能を持つメソッドをインデクサと呼ぶ。
VBA にインデクサそのものは存在しないが、
COM 由来のプロシージャ ID と言う属性があり、
その中に「既定」という属性がある。
プロシージャの属性を「既定」に設定すれば、
プロシージャ名を省略して呼び出すことができる。
これを使えば同じような機能を実装することはできる。
通常、戻り値を持つ関数かプロパティを使う。
方針を固めよう。
これを ArrayList に実装するには、
まずはインデクサの機能を持つプロパティが必要となる
これは、Item という名前のプロパティにしよう。
引数として、整数値を一つ取るようにすれば、
以下のように呼び出せるはずだ。
Dim a As ArrayList
Dim v As Variant
Set a = New ArrayList
Call a.Add(12345)
v = a.Item(0)
a.Item(0) = 54321
Item プロパティの属性を既定の設定すると、
最後の 2 行は以下のように書くことができるようになる。
v = a(0)
a(0) = 54321
うむ。配列っぽい。
よし、それでは Item プロパティを作ろう。
最初は、プロパティ取得メソッド(getter)からだ。
Public Property Get Item(ByVal Index As Long) As Variant
If Index < 0 Or Index >= m_lCount Then
Call Err.Raise(9)
End If
If IsObject(m_vValues(Index)) Then
Set Item = m_vValues(Index)
Else
Item = m_vValues(Index)
End If
End Property
昨日やった、値型と参照型の問題と、
インデックス範囲の問題だけで OK なので簡単だ。
続いて、プロパティ設定メソッドだ。
プロパティの設定は、代入式によって行なわれるので、
昨日出てきた値型と参照型の問題がここでも表面化する。
何でも格納できる Variant を代入する場合、
値型と参照型で異なるプロシージャ必要なのだ。
VBA には、setter と letter があり、
setter は参照型用、letter が値型用となる。
では、letter を作る。
Public Property Let Item(ByVal Index As Long, ByVal Value As Variant)
If Index < 0 Or Index >= m_lCount Then
Call Err.Raise(9)' 範囲外
End If
m_vValues(Index) = Value
End Property
プロパティの値型代入プロシージャは、
Property Let という名前になる。
引数は、Property Get と同じものが並び、
一番最後に代入される値が追加される。
上では ByVal Value As Variant としているのがそれだ。
ByRef で受け取ることもできるが、
ここでは ByVal を使って受け取ることにしている。
Property Let は、値型の代入のみが処理されるため、
引数 Value を IsObject で調べる必要はない。
続いて setter だ。
Public Property Set Item(ByVal Index As Long, ByVal Value As Variant)
If Index < 0 Or Index >= m_lCount Then
Call Err.Raise(9)' 範囲外
End If
Set m_vValues(Index) = Value
End Property
殆ど letter と変わりない。
こちらは必ず参照型代入構文でのみ使用されるので、
m_vValues(Index) への代入は、Set を使う。
これでコードの実装は完了だ。
後は、Item プロパティを既定属性にするだけだ。
実は、Visual Basic 6 など、単体の製品においては、
メニューに、ツール⇒プロシージャ属性という項目がある。
しかし、Excel や Word などの VBA にはその項目がない。
VBA で既定属性を設定するには、
裏技的な方法を使う必要がある。
その方法は以下の通りだ。
まずは、ArrayList を右クリックし、
ファイルのエクスポートを選ぶ。
場所を聞かれるので、適当な所に保存する。
VB のクラスモジュールファイルとして保存される。
次に、今エクスポートした ArrayList.cls を、
メモ帳などのテキストエディタで開く。
そして、Public Property XXX Item ~ の行を探す。
Get/Let/Set の 3 つのプロシージャがあるはずなので、
一番上にあるものを探そう。
ここでは、Get が一番上にあったとする。
Public Property Get Item(ByVal Index As Long) As Variant
の行を探し出し、その直下に以下の行を手入力して保存する。
Attribute Item.VB_UserMemId = 0
修正後は以下のようになるはずだ。
Public Property Get Item(ByVal Index As Long) As Variant
Attribute Item.VB_UserMemId = 0
If Index < 0 Or Index >= m_lCount Then
Call Err.Raise(9)
End If
If IsObject(m_vValues(Index)) Then
Set Item = m_vValues(Index)
Else
Item = m_vValues(Index)
End If
End Property
VBA に戻り、一度 ArrayList を削除する。
削除は、右クリック⇒ArrayList の解放だ。
エクスポートしますかと聞いてくるのでいいえで OK。
そして改めてプロジェクトを右クリックし、
ファイルのインポートを選ぶ。
そして、先ほど改ざんした ArrayList.cls を読み込む。
見た目には何の変更もないが、
これで既定プロシージャの指定が行なわれている。
確認するためには、オブジェクトブラウザを利用する。
表示⇒オブジェクトブラウザ(もしくは F2)を選び、
クラス一覧から、ArrayList を探して選択する。
右側にメソッド一覧が表示される。
Item プロパティに小さい水色の丸がついていれば成功だ。
Item プロパティを選択すると、下の説明表示部分にも、
Nonogram.ArrayList の「既定メンバ」と
表示されているはずだ。
これで ArrayList は実装できた。
この状態では For Each などによる列挙はできないが、
可変長配列をオブジェクト的に扱えるようになった。
========== クラスモジュール: ArrayList ==========
Option Explicit
'##################################################
'# 可変配列のラッパクラス(列挙はできない)
'##################################################
Private Const DEFAULT_CAPACITY As Long = 16
Private m_vValues() As Variant
Private m_lCount As Long
' 初期化
Private Sub Class_Initialize()
m_lCount = 0
ReDim m_vValues(0 To DEFAULT_CAPACITY - 1) As Variant
End Sub
' 要素を取得(既定プロパティ)
' Index 要素番号
Public Property Get Item(ByVal Index As Long) As Variant
If Index < 0 Or Index >= m_lCount Then
Call Err.Raise(9)
End If
If IsObject(m_vValues(Index)) Then
Set Item = m_vValues(Index)
Else
Item = m_vValues(Index)
End If
End Property
' 要素を設定(既定プロパティ)
' Index 要素番号
' Value 追加する値(代入式の右辺)
Public Property Let Item(ByVal Index As Long, ByVal Value As Variant)
If Index < 0 Or Index >= m_lCount Then
Call Err.Raise(9)
End If
m_vValues(Index) = Value
End Property
Public Property Set Item(ByVal Index As Long, ByVal Value As Variant)
If Index < 0 Or Index >= m_lCount Then
Call Err.Raise(9)
End If
Set m_vValues(Index) = Value
End Property
' 要素数を取得
Public Property Get Count() As Long
Count = m_lCount
End Property
' 配列の再割り当てを行なわずに格納できる要素数を取得
Public Property Get Capacity() As Long
Capacity = UBound(m_vValues) + 1
End Property
' 配列の再割り当てを行なわずに指定した要素数を格納できるように配列を拡大
' MinimumCapacity 必要な最低容量
Public Sub EnsureCapacity(ByVal MinimumCapacity As Long)
Dim lNew As Long
If Capacity >= MinimumCapacity Then Exit Sub
lNew = Me.Capacity * 2
If lNew = 0 Then lNew = DEFAULT_CAPACITY
If lNew < MinimumCapacity Then lNew = MinimumCapacity
ReDim Preserve m_vValues(0 To lNew - 1) As Variant
End Sub
' 要素を最後に追加し、そのインデックスを返す
' Value 追加する値
Public Function Add(ByVal Value As Variant) As Long
Call EnsureCapacity(m_lCount + 1)
If IsObject(Value) Then
Set m_vValues(m_lCount) = Value
Else
m_vValues(m_lCount) = Value
End If
Add = m_lCount
m_lCount = m_lCount + 1
End Function
' 要素を任意の位置に追加
' Index 要素番号
' Value 追加する値
Public Sub Insert(ByVal Index As Long, ByVal Value As Variant)
Dim i As Long
If Index < 0 Or Index > m_lCount Then
Call Err.Raise(9)
End If
Call EnsureCapacity(m_lCount + 1)
If Index < m_lCount Then
Call ShiftElements(m_lCount - 1, Index, 1)
End If
If IsObject(Value) Then
Set m_vValues(Index) = Value
Else
m_vValues(Index) = Value
End If
m_lCount = m_lCount + 1
End Sub
' 特定位置の要素を削除
' Index 要素番号
Public Sub RemoveAt(ByVal Index As Long)
Dim i As Long
If Index < 0 Or Index >= m_lCount Then
Call Err.Raise(9)
End If
If Index < m_lCount - 1 Then
Call ShiftElements(Index + 1, m_lCount - 1, -1)
End If
m_lCount = m_lCount - 1
m_vValues(m_lCount) = Empty
End Sub
' 全ての要素を削除
Public Sub Clear()
Dim i As Long
For i = 0 To m_lCount - 1
m_vValues(i) = Empty
Next
m_lCount = 0
End Sub
'==================================================
' Private メンバ
'==================================================
' 特定範囲の要素を 1 つずらす
' First 最初の要素番号
' Last 最後の要素番号
' Direction 要素をずらす方向。1 か -1
Private Sub ShiftElements(ByVal First As Long, ByVal Last As Long, ByVal Direction As Long)
Dim i As Long
For i = First To Last Step -Direction
If IsObject(m_vValues(i)) Then
Set m_vValues(i + Direction) = m_vValues(i)
Else
m_vValues(i + Direction) = m_vValues(i)
End If
Next
End Sub
========== end of ArrayList ==========