最終更新日 :
VB Tips

結び目型コレクション(その1)

プログラムライブラリで公開している「結び目型コレクション」について、その作成の過程・働きについてこの場を借りて書いていきたいと思います。
新しくオブジェクトを構築する時などの参考にして頂ければ幸いです。

プロパティ, メソッド(KnotItemオブジェクト)
プロパティ :
Index, [Manager], Item, Value, PrevItem(Get), NextItem(Get), [PrevItem(Let)], [NextItem(Let)]
メソッド :
InsertPrev, InsertNext, GetAt, Remove, ReleaseItem, [SetNumbers]

[]の中のものはFriendプロシージャです。プロジェクト内で使用する場合、Publicに書き換えるのは自由です。

KnotItemオブジェクトの作成
1.きっかけ
このオブジェクトは、プロパティリストボックスを作成する際、プロパティを"ソート"して表示する必要があったので、何か効率がいい方法はないかと思って考えた末に出てきました。
初めは、隠しリストボックスを貼り付けておく、APIでリストボックスを作る、なんて言うことを考えていたんですけど、リストボックスに項目を追加してから項目を頭からもう一度調べるなら、 最初から挿入すべき場所を調べながら走査すればいいし、数珠つなぎになっていれば、プロパティを表示する時にForを使わずにDo...Loopで済ませられる(表示位置を決定する時に変数が節約できる)ということもありました。
2.イメージ
私はそのオブジェクトを"KnotItem"(結び目)としました。主な操作は右の図の3つです。
KnotItemオブジェクトには、それ自身に次のKnotItemオブジェクトと前のKnotItemオブジェクト(それぞれNextItem, PrevItemプロパティ)を変数として含んでいます。
この方法は、C言語の構造体でよく利用されている方法なのですが、VBのユーザー定義型(構造体)では不可能なことなのですが、クラスではできます。

それではイメージすべき動作を考えましょう。
まず、アイテムの挿入です。アイテムの挿入方法は、2つあります。1つは対象アイテムの前に挿入する方法(Object.InsertPrev)、もうひとつはあとに挿入する方法(Object.InsertNext)です。 InsertPrevメソッドでは、挿入されるアイテム(引数で設定)が、対象アイテム(Object)の前に来なければなりません。当然のことですが。 ですから、挿入されるアイテム()のPrevItemが対象アイテムのPrevItem()(図では赤線の部分)になり、挿入される最後のアイテム(図では2つ並んだ独立アイテムの右側)のNextItemが対象アイテム()になり、 対象アイテム()のPrevItemが挿入される最後のアイテム()(図では赤線の部分)を指すように、また対象アイテムのPrevItem()のNextItemが挿入される最初のアイテム()を指すように改変しなければなりません。 つまり、BとEを結び、FとC(対象アイテム)を結べばいいのです。
逆に、InsertNextメソッドでは挿入されるアイテム()のPrevItemが対象アイテム自身()を指し(図では赤線の部分)、対象アイテム()のNextItemが挿入されるアイテム()を、 挿入される最後のアイテム()のNextItemが対象アイテムのNextItem()を指し(図では赤線の部分)、また対象アイテムのNextItem()のPrevItemは挿入される最後のアイテム()を示すようにならなければなりません。 同じく、B(対象アイテム)とEを結び、FとCを結べばいいのです。
次にアイテムの削除です。先の挿入の話と重なりますが、対象アイテム(Object)の両脇のPrevItemとNextItemを操作してやって、くっつけてしまえばいいのです。対象アイテムがCなら、BのNextItemにDを、DのPrevItemにBを設定してやればいいのです。
最後に切り離し(Release)です。これはもっと簡単な話で、対象アイテム(Object)のPrevItem(図ではB)のNextItemをNothingにしてやるだけです。
追加(挿入)した場合や、マネージャにアタッチ(後述)した場合、インデックスを整理するためにSetNumbersメソッドを実行しなければなりません。
3.イメージをコード化する
それでは、上に示した操作の部分を中心に実際にコードにしてみます。

Public Sub InsertNext(ByVal ListItem As KnotItem)
'アイテムの次の位置に別のアイテムを挿入します。
Dim li As KnotItem
' マネージャの設定
ListItem.Manager = mManager
' 前項と次項の設定
' ListItem の最後の項を探す
Set li = ListItem
Do
If li.NextItem Is Nothing Then Exit Do
Set li = li.NextItem
Loop
If Not (mNext Is Nothing) Then mNext.PrevItem = li
li.NextItem = mNext
ListItem.PrevItem = Me
Set mNext = ListItem

SetNumbers mIndex, mManager ' Indexを整理させます。

End Sub

"Loop"のところまでは説明無しでもわかって頂けると思います。きっと。"mNext.PrevItem = li", "li.NextItem = mNext"は、上の説明で言いますと、"F"と"C"を結ぶということになり、 "ListItem.PrevItem = Me"は、"E"のPrevItemを"(現在の)B"に指定になります。また、"Set mNext = ListItem"というのが"B"のNextItemを"E"に指定したことになります。
Public Sub InsertPrev(ByVal ListItem As KnotItem)
'アイテムの前の位置に別のアイテムを挿入します。
' 前項と次項の設定
Dim i As KnotItem, li As KnotItem
' マネージャの設定
ListItem.Manager = mManager
Set i = mPrev
' ListItem の最後の項を探す
Set li = ListItem
Do
If li.NextItem Is Nothing Then Exit Do
Set li = li.NextItem
Loop
Set mPrev = li
li.NextItem = Me
ListItem.PrevItem = i
If Not (i Is Nothing) Then i.NextItem = ListItem

If i Is Nothing Then
ListItem.SetNumbers mIndex
ElseIf i.Index > 0 Then
i.SetNumbers i.Index
Else
mManager.FirstItem.SetNumbers 1
End If

End Sub

まず、変数iはこれからmPrevを変更するため、mPrevを別の変数に移しておくためのものです。 "Set mPrev = li"は、上の説明で言いますと、"C"のPrevItemを"F"に指定ということになり、 "li.NextItem = Me"は、"F"のNextItemを"C"に指定になります。 また、"ListItem.PrevItem = i"というのが"E"のPrevItemを"(現在の)C"のPrevItem(iにキャッシュ済み)に指定したことになります。 そして、"i.NextItem = ListItem"で、"(現在の)C"のPrevItem(iにキャッシュ済み)のNextItemを"E"に指定して作業は終了です。
Public Sub Remove()
'アイテムをリスト中から切り離します。
' 次のオブジェクトの"前項"参照先をこのオブジェクトの"前項"にする。
If Not (mNext Is Nothing) Then mNext.PrevItem = mPrev
' 前のオブジェクトの"次項"参照先をこのオブジェクトの"次項"にする。
If Not (mPrev Is Nothing) Then mPrev.NextItem = mNext
' インデックス番号の整理
If Not (mPrev Is Nothing) Then
mPrev.SetNumbers mPrev.Index ' 前の項目からの続き
ElseIf Not (mNext Is Nothing) Then
mNext.SetNumbers 1 ' 次の項目が新しいトップインデックス
Else
' このオブジェクトがリストに所属していなければマネージャは無し
mManager.AttachListItems Nothing
End If
Set mManager = Nothing

End Sub

図で見た通りのことをやっているので、お分かり頂けると思います。
Public Sub ReleaseItem()
'1つ前のアイテムをリストで最後のアイテムに変え、このアイテムを新しいトップアイテムにします。
If Not (mPrev Is Nothing) Then
mManager.LastItem = mPrev
' 切り離しによってできる新しいリストのインデックスの
' 整理はマネージャにアタッチされて初めて行われる
Set mPrev = Nothing
End If

End Sub

これは単純ですね。
Friend Sub SetNumbers(ByVal NewIndex As Long, Optional ByVal NewManager As KnotManager)
' この内容はマネージャの仕組みと共に説明します。

End Sub

今回のKnotItem, KnotManagerオブジェクトはPropertyListBoxの作成と共に更新されていく可能性が十分にあります。 現にこのページを作っている間もいくつか変更を加えています(ってわかるわけないか)。その2ではKnotManagerについて、その3では実際にKnotItemを使ったデータの格納法を紹介したいと思います。
また、このオブジェクトについての質問等は遠慮なくしてください。

Copyright (C) 1999 Satoshi Tadaフィードバックはこちら
Home