1.きっかけ |
このオブジェクトは、プロパティリストボックスを作成する際、プロパティを"ソート"して表示する必要があったので、何か効率がいい方法はないかと思って考えた末に出てきました。 初めは、隠しリストボックスを貼り付けておく、APIでリストボックスを作る、なんて言うことを考えていたんですけど、リストボックスに項目を追加してから項目を頭からもう一度調べるなら、 最初から挿入すべき場所を調べながら走査すればいいし、数珠つなぎになっていれば、プロパティを表示する時にForを使わずにDo...Loopで済ませられる(表示位置を決定する時に変数が節約できる)ということもありました。 |
2.イメージ |
私はそのオブジェクトを"KnotItem"(結び目)としました。主な操作は右の図の3つです。 KnotItemオブジェクトには、それ自身に次のKnotItemオブジェクトと前のKnotItemオブジェクト(それぞれNextItem, PrevItemプロパティ)を変数として含んでいます。 この方法は、C言語の構造体でよく利用されている方法なのですが、VBのユーザー定義型(構造体)では不可能なことなのですが、クラスではできます。 それではイメージすべき動作を考えましょう。 まず、アイテムの挿入です。アイテムの挿入方法は、2つあります。1つは対象アイテムの前に挿入する方法(Object.InsertPrev)、もうひとつはあとに挿入する方法(Object.InsertNext)です。 InsertPrevメソッドでは、挿入されるアイテム(引数で設定)が、対象アイテム(Object)の前に来なければなりません。当然のことですが。 ですから、挿入されるアイテム(E)のPrevItemが対象アイテムのPrevItem(B)(図では赤線の部分)になり、挿入される最後のアイテム(図では2つ並んだ独立アイテムの右側F)のNextItemが対象アイテム(C)になり、 対象アイテム(C)のPrevItemが挿入される最後のアイテム(F)(図では赤線の部分)を指すように、また対象アイテムのPrevItem(B)のNextItemが挿入される最初のアイテム(E)を指すように改変しなければなりません。 つまり、BとEを結び、FとC(対象アイテム)を結べばいいのです。 逆に、InsertNextメソッドでは挿入されるアイテム(E)のPrevItemが対象アイテム自身(B)を指し(図では赤線の部分)、対象アイテム(B)のNextItemが挿入されるアイテム(E)を、 挿入される最後のアイテム(F)のNextItemが対象アイテムのNextItem(C)を指し(図では赤線の部分)、また対象アイテムのNextItem(C)のPrevItemは挿入される最後のアイテム(F)を示すようにならなければなりません。 同じく、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 |