図形の変更により途切れたコネクタをVBAで再接続する: Excel VBA

この記事は,オートシェイプをたくさん選択して「図形の変更」をした結果,コネクタ全部切れてる…となってしまった状態から,VBAを使ってコネクタをつなぎ直すコードの紹介になります。

' 図形のコネクタ接続点の座標を取得する
Sub GetConnectionSitePosition(shape As shape, index As Long, ByRef posX As Double, ByRef posY As Double)
    Dim s_conn As shape
    Dim center_x As Double
    Dim center_y As Double
    center_x = shape.LEFT + shape.Width / 2#
    center_y = shape.TOP + shape.Height / 2#
 
    ' 試しに接続点に直線コネクタを接続してみる
    Set s_conn = shape.TopLeftCell.Worksheet.Shapes.AddConnector(msoConnectorStraight, center_x, center_y, shape.LEFT, shape.TOP)
    Call s_conn.ConnectorFormat.EndConnect(shape, index)
 
    ' 接続したコネクタの終点を取得する
    Call GetConnectorPointPosition(s_conn, center_x, center_y, posX, posY)
 
    ' コネクタを始末する
    s_conn.Delete
End Sub
 
 
' コネクタの始点・終点の座標を取得する
Sub GetConnectorPointPosition(shape As shape, ByRef startX As Double, ByRef startY As Double, ByRef endX As Double, ByRef endY As Double)
    Dim RIGHT As Integer, _
        LEFT As Integer, _
        TOP As Integer, _
        BOTTOM As Integer, _
        P_START As Integer, _
        P_END As Integer
    Dim NPmap(2, 2) As Integer
    Dim i As Integer, _
        temp As Integer
 
    LEFT = 1
    RIGHT = 2
    TOP = 1
    BOTTOM = 2
    P_START = 1
    P_END = 2
 
    ' DEFAULT
    NPmap(TOP, LEFT) = P_START
    NPmap(TOP, RIGHT) = 0
    NPmap(BOTTOM, LEFT) = 0
    NPmap(BOTTOM, RIGHT) = P_END
 
    ' V-FLIP
    If shape.VerticalFlip = msoTrue Then
        For i = LEFT To RIGHT
            temp = NPmap(TOP, i)
            NPmap(TOP, i) = NPmap(BOTTOM, i)
            NPmap(BOTTOM, i) = temp
        Next
    End If
 
    ' H-FLIP
    If shape.HorizontalFlip = msoTrue Then
        For i = TOP To BOTTOM
            temp = NPmap(i, LEFT)
            NPmap(i, LEFT) = NPmap(i, RIGHT)
            NPmap(i, RIGHT) = temp
        Next
    End If
 
    ' ROT
    Dim rot As Integer
    rot = shape.Rotation
    Do While rot > 0
        rot = rot - 90
        temp = NPmap(TOP, LEFT)
        NPmap(TOP, LEFT) = NPmap(BOTTOM, LEFT)
        NPmap(BOTTOM, LEFT) = NPmap(BOTTOM, RIGHT)
        NPmap(BOTTOM, RIGHT) = NPmap(TOP, RIGHT)
        NPmap(TOP, RIGHT) = temp
    Loop
 
    ' Set Position
    Dim x As Integer, y As Integer, z As Integer
    Dim res(2, 2) As Integer
    For z = P_START To P_END
        For y = TOP To BOTTOM
            For x = LEFT To RIGHT
                If NPmap(y, x) = z Then
                    If x = LEFT Then
                        res(z, 1) = shape.LEFT
                    Else
                        res(z, 1) = shape.LEFT + shape.Width
                    End If
                    If y = TOP Then
                        res(z, 2) = shape.TOP
                    Else
                        res(z, 2) = shape.TOP + shape.Height
                    End If
                End If
            Next
        Next
    Next
    startX = res(P_START, 1)
    startY = res(P_START, 2)
    endX = res(P_END, 1)
    endY = res(P_END, 2)
End Sub
' 図形の変更によって途切れてしまったコネクタを再接続する。
Sub ConnectorReconnect()
    Dim sh As Worksheet
    Set sh = ActiveSheet
    Dim v As Variant
    Dim v2 As Variant
    Dim s As shape
    Dim s2 As shape
 
    Dim conn_pos_x As New Dictionary
    Dim conn_pos_y As New Dictionary
    Dim spcs_pos_x As New Dictionary
    Dim spcs_pos_y As New Dictionary
 
    Dim hex_id As String
    Dim x1 As Double
    Dim x2 As Double
    Dim y1 As Double
    Dim y2 As Double
    Dim xx As Double
    Dim yy As Double
    Dim dist As Double
    Dim site As Long
    Dim count As Long
 
    Dim idx1 As Long
    Dim idx2 As Long
 
    ' コネクタの位置情報を収集する
    For count = 1 To sh.Shapes.count
        Set s = sh.Shapes.Item(count)
        hex_id = Format(count, "000000000")
        If s.Connector Then
            Call GetConnectorPointPosition(s, x1, y1, x2, y2)
            Call conn_pos_x.Add(hex_id & " " & "BEGIN", x1)
            Call conn_pos_x.Add(hex_id & " " & "END", x2)
            Call conn_pos_y.Add(hex_id & " " & "BEGIN", y1)
            Call conn_pos_y.Add(hex_id & " " & "END", y2)
        Else
            For site = 1 To s.ConnectionSiteCount
                Call GetConnectionSitePosition(s, site, x1, y1)
                Call spcs_pos_x.Add(hex_id & " " & site, x1)
                Call spcs_pos_y.Add(hex_id & " " & site, y1)
            Next
        End If
    Next
 
    ' 距離が近いものを接続する
    count = 0
    For Each v In spcs_pos_x.Keys
        For Each v2 In conn_pos_x.Keys
            x1 = spcs_pos_x(v)
            y1 = spcs_pos_y(v)
            x2 = conn_pos_x(v2)
            y2 = conn_pos_y(v2)
            xx = x2 - x1
            yy = y2 - y1
            xx = xx * xx
            yy = yy * yy
            dist = Sqr(xx + yy)
            If dist < 3 Then
                count = count + 1
                Debug.Print Format(count, "000:") &amp; "[" &amp; v &amp; "]-[" &amp; v2 &amp; "] = (" &amp; x1 &amp; "," &amp; y1 &amp; ")-(" &amp; x2 &amp; "," &amp; y2 &amp; ")"
                idx1 = CLng(LEFT(v, 9))
                idx2 = CLng(LEFT(v2, 9))
                Set s = sh.Shapes.Item(idx1)
                Set s2 = sh.Shapes.Item(idx2)
                site = CLng(Mid(v, 11))
                If Mid(v2, 11) = "BEGIN" Then
                    Call s2.ConnectorFormat.BeginConnect(s, site)
                Else
                    Call s2.ConnectorFormat.EndConnect(s, site)
                End If
            End If
        Next
    Next
End Sub
 
' どこにも接続されていないコネクタの端点を〇で囲む
Sub PutCircleAtNotConnectedConnector()
    Dim sh As Worksheet
    Set sh = ActiveSheet
    Dim s As shape
    Dim s2 As shape
    Dim hex_id As String
    Dim x1 As Double
    Dim x2 As Double
    Dim y1 As Double
    Dim y2 As Double
    Dim count As Long
    ' コネクタの位置情報を収集する
    For count = 1 To sh.Shapes.count
        Set s = sh.Shapes.Item(count)
        hex_id = Format(count, "000000000")
        If s.Connector Then
            Call GetConnectorPointPosition(s, x1, y1, x2, y2)
            If Not s.ConnectorFormat.BeginConnected Then
                Set s2 = sh.Shapes.AddShape(msoShapeOval, x1 - 3#, y1 - 3#, 6, 6)
                s2.Line.ForeColor.RGB = RGB(255, 0, 0)
                s2.Line.Weight = 2
            End If
            If Not s.ConnectorFormat.EndConnected Then
                Set s2 = sh.Shapes.AddShape(msoShapeOval, x2 - 3#, y2 - 3#, 6, 6)
                s2.Line.ForeColor.RGB = RGB(255, 0, 0)
                s2.Line.Weight = 2
            End If
 
        End If
    Next
End Sub

さて,このコードの少しヤヤコシイところは「コネクタの始点・終点の座標を取得する」サブルーチンである「Sub GetConnectorPointPosition」になります。

というのも,コネクタ形状オブジェクトからコネクタの端点の位置を取得するためのプロパティはどうやら無いみたいなんですね。

コネクタ形状オブジェクトから得られる位置情報は,通常の図形と同じく Top, Left, Width, Height のみ。ここからどうやって端点の位置を取得するかというのが腕の見せ所になります。

コネクタの形状は様々で,Top, Left, Width, Heightだけの情報じゃ端点の位置を特定できないのは言うまでもありません。しかし,コネクタの形状(というか向きなんですが)を得る方法があります。それが,以下のプロパティです。

  • shape.VerticalFlip
  • shape.HorizontalFlip
  • shape.Rotation

そして,そもそもコネクタのTop, Left, Width, Height がどこの寸法を指しているのかということも理解しておく必要がありますので,以下の図を見てください。

基本的なコネクタ(線)はこれです。

このときの Top, Left, Width, Height はこうなります。

普通ですね。

しかし,次の場合はどうでしょう。

おや?コネクタの線がWidthをはみだしました。

このことから,コネクタのTop, Left, Width, Heightは,始点・終点を含む最小の矩形を指していることがわかります。途中の線は関係ないのですね。(途中の線は Adjustments プロパティで設定されます。)

これなら,矩形の頂点のいずれかに必ずコネクタの端点がくることになります。

あとは,コネクタの向きを調べて,矩形の頂点のどれがコネクタの始点で,どれがコネクタの終点なのかを特定すればOKです。

そのために

  • shape.VerticalFlip
  • shape.HorizontalFlip
  • shape.Rotation

を使うのです。

まず,基本形は次の向きです。

このとき,BEGIN=(Left, Top),END=(Left+Width, Top+Height)にあります。

次は HorizontalFlip

はい。左右反転です。
このとき,BEGIN=(Left+Width, Top),END=(Left, Top+Height)にあります。

次はVerticalFlip

はい。上下反転です。
このとき,BEGIN=(Left, Top+Height),END=(Left+Width, Top)にあります。

次はRotation,回転ですね。時計回りに(右へ)90度ずつ回転します。

これらの反転と回転は,組み合わせでかかっている場合もあります。

ということで,これらの組み合わせを読み解けば,Top, Left, Width, Heightから始点および終点の位置を特定することが可能なわけです。

「Sub GetConnectorPointPosition」の中では,H-FLIP,V-FLIP,ROTとコメントした場所で,始点・終点がどこにあるのかを求めています。求めているというか,2行2列の配列を次のような配置に見立て,始点ならP_START(=1),終点ならP_END(=2)を入れて置き,配列を反転させたり回したりしているだけです。

LEFTRIGHT
TOP10
BOTTOM02
配列のイメージ

標準形では始点(1)はTOP,LEFTに,終点(2)はBOTTOM,RIGHTにあるので,このように初期化しておき,H-FLIP,V-FLIP,ROTで1,2を入れ替えているわけです。これで端点がどっち側にあるのかわかるので,あとはTop, Left, Width, Heightを使って位置を計算して返せばOKなのです。


0件のコメント

コメントする

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください