【ExcelVBA】Dictionaryを使って図形を配列管理する

前回、コネクタのタイプ変更をListBoxからできるようにしました。

範囲選択でシェイプを選択して、コネクタだけをListBoxに取得したのですが、
フロー図形を省いているって事は、フロー図形のみ取得する事もできる訳で…

コネクタ作成もListBoxが活用できるやん!って事で処理を作る事にしました。

新しくbookを作成して、今まで作った処理を盛り込んで、
実際に使える物に仕上げていきたいと思います。


まずコネクタ追加なんですが、ListBoxが必要になるので、
その辺りから準備します。
Dictionary⑰.jpg
UserFoam上部に、選択中図形取得ボタンを設置。
コネクタ作成・コネクタ変更のListBoxを、2つを用意します。

どちらのListBoxも、ColumnCountを2、ColumnWidthsを50pt,0ptに設定します。
MultiSelectを、Single→Multiに変更して複数選択できるようにします。

選択中図形取得については、前回記事を流用して作ります。

標準モジュールを2つ追加して、
シェイプ管理用のShapeManagerモジュール
UserFoam管理用のPanelManagerモジュール
を作ります。

まずは、選択中図形取得から
ShapeManager(標準モジュール)

Option Explicit

Sub SelectionShapeSearch()

Dim sh As Shape '選択図形取得'
Dim connectorCnt As Long 'コネクタカウンター'
Dim shapeCount As Long '図形カウンター'

'コネクタの判別子'
Const lineName As String = "*Connector*"

On Error GoTo NotSelect

For Each sh In Selection.ShapeRange

If sh.Name Like lineName Then
Call EntlyConnector(connectorCnt, sh)
connectorCnt = connectorCnt + 1

Else
Call EntlyShapes(shapeCnt, sh)
shapeCount = shapeCount + 1

End If

Next


'図形の選択を解除する為A1セルを選択する'
'シートがアクティブだと表示画面がA1まで移動するので停止しておく'
Application.ScreenUpdating = False

ActiveSheet.Range("A1").Select

Application.ScreenUpdating = True


Exit Sub

NotSelect:
MsgBox "選択されてないよ!"

End Sub

Connectorの文字を含む物は、コネクタ変更リスト(ListBox2)に
それ以外は、コネクタ作成リスト(ListBox1)に登録する処理になります。

リストに取り込む処理は、
PanelManager(標準モジュール)

Option Explicit

Dim workPanel As UserForm1


Sub PanelOpen()

If workPanel Is Nothing Then
Set workPanel = New UserForm1
workPanel.Show vbModeless

End If

End Sub


Sub ReleasePanel()

Set workPanel = Nothing

End Sub


Sub EntlyShapes(line As Long, sh As Shape)

With workPanel.ListBox1
.AddItem ""
.List(line, 0) = sh.TextFrame.Characters.Text
.List(line, 1) = sh.Name

End With

End Sub


Sub EntlyConnector(line As Long, sh As Shape)

Const straight As String = "直線"
Const elbow As String = "カギ線"
Dim conType As String

Select Case sh.ConnectorFormat.Type

Case msoConnectorElbow
conType = elbow

Case msoConnectorStraight
conType = straight

End Select

With workPanel.ListBox2
.AddItem ""
.List(line, 0) = conType
.List(line, 1) = sh.Name

End With

End Sub

UserFoamをインスタンス化するのですが、
モードレスで呼び出すと何度も呼び出す事ができてしまいます。

なので、変数にセットされてるか判断して一枚しか呼び出せないようにします。
UserFoamを閉じる時にReleasePanelプロシージャを呼び出せば、
UserFoamがリセットできます。

リストのColumnに取り込む内容は、
・コネクタ作成リスト
 0列目、図形上のテキスト(キャラクターテキスト)
 1列目、図形の名前

・コネクタ変更リスト
 0列目、コネクタの省略名
 1列目、コネクタの名前

ColumnWidthsを設定してるので、表示は0列目だけが見えてるようになります。

実際にテストしてみます。
Dictionary③.jpg
ボタンをぽち
Dictionary④.jpg
上手く分類できました。ヽ(´▽`)/~♪

これでListBoxのリストをクリックすると、図形が選択できるように
処理を作るのですが、前回の選択処理を見てみます。
ListBox1モジュール

Private Sub ListBox1_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)

Dim listCnt As Long

Application.ScreenUpdating = False
Range("A1").Select

For listCnt = 0 To ListBox1.ListCount - 1

With ListBox1

If .Selected(listCnt) Then
ActiveSheet.Shapes(.List(listCnt, 1)).Select False

End If

End With

Next

Application.ScreenUpdating = True

End Sub

ListBoxのリストを上から順に見ていって、
選択されてるリスト項目から、同じ名前のシェイプを選択状態にする処理になります。

図形間にコネクタを作成する際、このままでは問題ありなんですよね~( ´ω` ).。oO

なぜかと言うと図形の選択順にコネクタが作成できないんですよ。
リスト順に図形を選択してるので、矢印の向きが固定されてしまうんですよね…

例えば
Dictionary⑲.jpg
右の分岐から下のフローに向けてカギ線を追加する場合、
リストでは、フローが5番目で分岐が6番目に登録されています。

なので、コネクタを作成しても、フローから分岐に向けて矢印が向く事になります。

範囲選択した場合、左の縦列から順にリスト登録される特性があるので、
順番が逆転します。

リスト登録の順番を操作しても、選択順に矢印を向ける事はできないので、
図形の選択順を取得する必要があります。

ここから本題のDictionaryを使って、選択順の図形管理ができるようにしていきます。

まず、Dictionaryは連想配列と言って、通常配列とは少し機能が違います。

通常の配列と違う点は、
・配列の要素数を設定しなくていい
・動的配列として使える
・登録にキーの設定が必要

Dictionary⑨.jpg
配列要素数を設定して宣言する必要があります。
代入は、番号を指定して代入する形になります。

データの削除時は、
Dictionary⑩.jpg
データは削除されても入れ物は残るので、連続読み取りすると
0や空白、またはnothingとなってしまいます。

要素数を最初に設定するので、後から増やしたい場合、
ReDimなどが必要で、その際、変数内のデータがクリアされてしまいます。


連想配列と言うのは、
Dictionary⑪.jpg
Dictionaryオブジェクトを作成して、その中にデータを登録する形になります。
データを登録する際、Keyが必要になります。

利点は、
Dictionary⑫.jpg
配列要素数の宣言がいらない、中間のデータを削除すると無かったように振る舞う点です。

これを使えば、図形の選択順をコントロールできそうです。
早速、導入していきます。

Dictionaryは、オブジェクトとして宣言する必要があります。
標準モジュール

Option Explicit

Sub DictionaryTest()

Dim testDic As Object
Set testDic = CreateObject("Scripting.Dictionary")

End Sub

これで使う事ができます。

続いて、Dictionaryにデータを登録する方法ですが、
配列の様に番号指定ではなく、Keyを設定する必要があります。

Keyは、同じものが使えないようになっています。
同じKeyを設定するとエラーが出るので注意でっす。

配列の管理は、番号ではなくてKeyで行われるって事になります。

Key:A
データ:10
だった場合、

testDic.Add "A", "10"

登録には、Addコマンドを使って、Key、データと入力します。

データの取り出し方は、

testDic.Item("A")

ItemコマンドでKeyを指定すると取り出す事ができます。

早速、テストです。
標準モジュール

Option Explicit

Sub DictionaryTest()

Dim testDic As Object
Set testDic = CreateObject("Scripting.Dictionary")

Dim num As Long
Dim str As String

testDic.Add "A", "10"

num = testDic.Item("A")
str = testDic.Item("A")

MsgBox num & "/" & str

End Sub

これを実行すると…

Dictionary⑬.jpg
半角数字の場合、数値としても文字としても取り出す事ができるようです。

Keyの設定は、数値でもできるので、通しナンバーをKeyにすれば、
通常の配列と同じように番号管理もできるかと思います。

登録したデータを消去する場合は、

'特定のKeyを削除する'
testDic.Remove "A"

'全データを削除する'
testDic.RemoveAll

データの削除は、Keyを指定して個別に削除するか、
全データを一括削除するかで設定できます。

最後にKeyの検索コマンドを紹介します。

Dictionaryに登録されてないKeyを呼び出すとエラーが発生します。
なので、呼び出す前にKeyが登録されてるか確認するとエラー回避できます。

testDic.Exists("A") 'Existsコマンドで返ってくる値は、TrueかFalse'

キーが存在すればTrueが、なければFalseが返ってきます。
if文に設定すれば存在判定が可能になります。

これでDictionaryが使えるようになったので、
処理を作って…

その前に小ネタを書いておきます。


Dictionaryを使う為にオブジェクト宣言が必要って事を書いたのですが、
実は、もう少し簡単に宣言する方法もあります。

参照設定で、MicrosoftScriptingRuntimeをONにすると少し楽に宣言できます。
まず、
Dictionary⑭.jpg
ツールから参照設定を開きます。
Dictionary⑮.jpg
項目からMicrosoftScriptingRuntimeを探して、チェックを入れます。
後は、OKボタンを押せば終了です。

これで、変数にDictionaryを宣言できるようになるので、

Dim testDic As New Dictionary

オブジェクト宣言からDictionaryの代入の部分が一行でできるようになります。

普通にDictionaryを宣言する場合、
オブジェクト変数を設定して、Dictionaryを代入するので、
プロシージャを作る必要があります。

モジュール内でDictionaryを共有したい場合、
代入用のプロシージャを用意するか、使うプロシージャ内でインスタンス化と、
二度手間が発生します。

今回は、Dictionaryを共有できるようにしたいので、
参照設定ONで進めていきます。

さて、Dictionaryの使い方が分かった所で、
実際に図形の選択順を登録できるように処理を考えて行きます。

リストが選択されていたら、Dictionaryにリスト名を登録していくのですが、
一度、登録するとDictionary内に残り続けます。

なので、選択が解除された時にDictionaryから削除する処理も必要になります。

判定としては、
判定1:リスト選択・Dictionary登録確認・未登録なら登録
判定2:リスト未選択・Dictionary登録確認・登録済なら削除

少し、複雑な判定処理が必要になります。

分かりにくいので、処理の流れをフローにしてみました。
Dictionary㉔.jpg
かえって分かりにくいでしょうか(;^_^A

リスト項目の選択順に、Dictionaryにリスト名が登録できるので、
最後にDictionaryの配列順に図形を選択すれば上手く機能してくれそうです。

流れが分かったので、処理を作っていきます。
まず、Dictionaryを作るので、標準モジュールを追加して、
DictionaryModuleとリネームしておきます。
DictionaryModule(標準モジュール)

Option Explicit

Dim shDic As New Dictionary



'フロー図形Dictionary'
'-----------------------------------------'
Sub ShapeDictionary(shName As String, listNum As Long, listOn As Boolean)

'判定1'
If listOn And shDic.Exists(listNum) = False Then
shDic.Add listNum, shName

'判定2'
ElseIf listOn = False And shDic.Exists(listNum) Then
shDic.Remove listNum

End If

End Sub


Sub SelectShapes()

Dim dicKey As Variant

For Each dicKey In shDic
ActiveSheet.Shapes(shDic(dicKey)).Select Replace:=False

Next

End Sub
'フロー図形Dictionary'
'-----------------------------------------'


'Dictionaryのリセット'
Sub DictionaryReset()

shDic.RemoveAll

End Sub

ShapeDictionaryプロシージャが登録用になります。
SelectShapesプロシージャは、Dictionaryに登録された図形を選択するものです。

まず、ShapeDictionaryプロシージャですが、引数を3つ設定しました。
・shName
 図形の名前、Dictionaryの登録データにする
・listNum
 リスト番号、Dictionaryの登録・削除のKeyにする
・listOn
 リスト項目の選択状態

この引数を元に判定を行います。


続いて、SelectShapesプロシージャですが、
図形の選択は、Dictionaryに登録されてる順に選択していく必要があるので、
Dictionaryが参照できる、このモジュール内で行います。

Dictionaryの配列順にデータを取得する場合、For Eachを使うと楽にできます。

順番にKeyを取得して、ItemコマンドでKeyを設定すれば、
対応した図形名を取り出す事ができます。

ちなみにshDic(dicKey)は、shDic.Item(dicKey)と同じ意味で、
.Itemの部分は省略できます。

後は、ActiveSheet.Shapesで図形名を指定すれば選択状態になります。
図形は、Multiで選択する必要があるので、.Selectの後ろにFalseをお忘れなく。

作業後にDictionary内にデータが残っているとまずいので、
リセット用のプロシージャを作っておきます。

UserFoamの開閉時に呼び出したり、リセットボタンを設置するなど、
作業後にリセットできるようにしておきます。

これで、図形選択まで処理が作れたので、ListBox1から呼び出します。
ListBox1モジュール

Private Sub ListBox1_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)

Dim listCnt As Long

Application.ScreenUpdating = False
ActiveSheet.Range("A1").Select

With ListBox1

For listCnt = 0 To .ListCount - 1
Call ShapeDictionary(.List(listCnt, 1), listCnt, .Selected(listCnt))

Next

End With

Call SelectShapes

Application.ScreenUpdating = True

End Sub

MouseUpコマンドで呼び出して、リストの上から順に、
図形名・リスト番号・選択状態を引数にセットして、ShapeDictionaryをコールします。

Dictionaryの処理が終わったら、SelectShapesをコールして図形を選択します。

SelectShapesの呼び出しタイミングは、必ずFor~Nextが終わってからにして下さい。
ループ内で間違えて呼び出すと、リスト項目の数だけDictionaryが回ってしまうので、
最悪、エクセルがフリーズする事もあります( -᷄ω-᷅ )💭

これでリストの選択順が取得できるようになったので、
実際の図形にコネクタを作成してみます。
Dictionary㉕.jpg
分岐→フローと順に選択してカギ線を繋ぐと、
カギ線の矢印も選択順の方向になりました。

選択順を逆にしてみると
Dictionary㉖.jpg
フロー→分岐と選択して線を繋ぐと、
無事に選択順方向に矢印が向いてます。

上手く処理が作れました。ヽ(´▽`)/~♪

コネクタ作成と向きの処理は以上となります。

残すは、前回から保留にしている、
コネクタタイプ変更時のListBoxの表示名変更です。

これもDictionaryを使えば作れそうですが、
記事が少し長くなったので、次回にまとめようと思います。

Dictionaryは、少し使う時に悩む部分もあるんですが、
使いこなせるようになると配列より使い勝手が良さそうなので、
初心者の方は、覚えるとコードを組む際、少し幸せになれると思います。

この記事がDictionaryの参考にでもなれば幸いでっす。

それではこの辺で、
~~~ヽ(^◇^))) ほな、さいなら!






この記事へのコメント

この記事へのトラックバック