Visio VBAでコネクタ(線)をまとめて移動するサンプルコード

VBA

Visioで図を描いていると、

『この線の曲がり角だけまとめて動かしたい!』って時ありませんか?

でも、何本も手で直すのがつらい😇

例えばこんな図面・・・この曲がり角をあと2cm左にしたいけど

並んでいるコネクタ、1本ずつ動かすのが面倒なんだよね・・・

じゃあまとめて動かすコードを作ろう!

そこで今回は選択した図形に接続されているコネクタをまとめて移動するVBAを紹介します。
「ドラッグっぽい動き」をVBAで再現しているので直感的に使えると思います。

このマクロの使い方

このマクロの使い方

  1. コネクタを複数選択して実行
    ※最初に選ばれた線が「基準線」になります
  2. 基準線が赤くなります
  3. 動かしたい曲がり角にマウスを合わせてドラッグ
  4. ドラッグした分だけ、他の線も一斉に移動します

※ 同じ形状のコネクタのみ対応
※ 向きが逆・頂点数が違う場合は動かないことがあります。

処理の考え方(ざっくり)

やっていることはシンプル👇

まず、最初に選んだ線を「基準線」にします。
この線の動きを、他の線にも反映させます。

ドラッグ中は、マウスの位置そのものではなく
前回からどれだけ動いたか(差分) を取得しています。

そして、その動いた分を他のコネクタにもそのまま足しています。

つまり、

基準線の動きを、そのまま他の線にコピーしている

だけです。

難しい計算はしていません。
“動いた量”を再利用しているだけなので、自然な動きになります。

マウスイベントが便利だった!

「ドラッグっぽい動き」をVBAで再現するためにWithEventを使ったよ。

WithEvent???

そう、マウスの動きを拾えるようにマウスイベントを作って、そこから“差分”を計算しているんだ!

今回のマクロはマウスの動きから差分を取るのがPoint!ということでマウスの動きを認識するためにWithEventを使って、マウスイベントを図面上で使えるようにしました。

WithEventsの記事はここにあるので、読んでいただけると嬉しいです😊

サンプルコード

以下のVBAコードを使えば、線の束を同時に移動できます。
標準モジュールとThisDocumentにコードを貼り付けて下さい。

よし!貼り付けだっ!

コード

標準モジュール

' --- 標準モジュールにコピペ ---
Public gSelection As Visio.Selection ' 選択されたコネクタたち
Public gBaseShape As Visio.Shape      ' 判定基準にする1本
Public gHighlightLine As Visio.Shape  ' オレンジのハイライト線
Public gIsDragging As Boolean          ' ドラッグ中フラグ
Public gSegIndex As Long               ' 何番目の線分か
Public gStartX As Double, gStartY As Double ' ドラッグ開始位置
Public gOriginalColor As String
Public gOriginalWeight As String

' --- 起動マクロ ---
Public Sub StartDragSegmentMode()
    Set ThisDocument.app = Visio.Application
    
    ' 選択されている図形をセット
    Set gSelection = ActiveWindow.Selection
    
    If gSelection.Count = 0 Then
        MsgBox "コネクタをいくつか選んでから実行してね!"
        Exit Sub
    End If
    
    ' 判定の基準は、便宜上1番目の図形にする
    Set gBaseShape = gSelection(1)
    
  ' 元の状態を保存
    gOriginalColor = gBaseShape.CellsU("LineColor").FormulaU
    gOriginalWeight = gBaseShape.CellsU("LineWeight").FormulaU

  ' 1. 基準線の色を変える
    gBaseShape.CellsU("LineColor").FormulaU = "RGB(255,0,0)"

    ' 2. ここで選択を解除!ハンドルを消してハイライトを見やすくする
    ActiveWindow.DeselectAll
    
    gIsDragging = False
    gSegIndex = 0
    Debug.Print "Multi-Mode Start: " & gSelection.Count & " shapes selected."
End Sub
' --- セグメント判定(XYToPageでズレ防止) ---
Function GetSegmentIndex(shp As Visio.Shape, mx As Double, my As Double, tol As Double) As Long
    Dim i As Integer, rowCount As Integer
    Dim px1 As Double, py1 As Double, px2 As Double, py2 As Double
    Dim minDist As Double: minDist = tol / 25.4
    
    rowCount = shp.rowCount(visSectionFirstComponent)
    
    ' 2行目から (最後-1) 行目までをループ対象にする
    ' これで端点のセグメントを無視
    For i = 2 To rowCount - 2
        shp.XYToPage shp.CellsU("Geometry1.X" & i).ResultIU, shp.CellsU("Geometry1.Y" & i).ResultIU, px1, py1
        shp.XYToPage shp.CellsU("Geometry1.X" & i + 1).ResultIU, shp.CellsU("Geometry1.Y" & i + 1).ResultIU, px2, py2
        
        If DistancePointToLine(mx, my, px1, py1, px2, py2) < minDist Then
            GetSegmentIndex = i
            Exit Function
        End If
    Next i
    GetSegmentIndex = 0
End Function

' --- オレンジ線の描画更新 ---
Sub UpdateHighlightSegment(shp As Visio.Shape, segIdx As Long)
    Dim px1 As Double, py1 As Double, px2 As Double, py2 As Double
    
    shp.XYToPage shp.CellsU("Geometry1.X" & segIdx).ResultIU, shp.CellsU("Geometry1.Y" & segIdx).ResultIU, px1, py1
    shp.XYToPage shp.CellsU("Geometry1.X" & segIdx + 1).ResultIU, shp.CellsU("Geometry1.Y" & segIdx + 1).ResultIU, px2, py2

    If gHighlightLine Is Nothing Then
        Set gHighlightLine = ActivePage.DrawLine(px1, py1, px2, py2)
        gHighlightLine.CellsU("LineColor").FormulaU = "RGB(255,165,0)"
        gHighlightLine.CellsU("LineWeight").FormulaU = "3pt"
        ' --- マウスに邪魔させない設定 ---
        ' 1: 選択不可、0: ヒットテスト無効
        gHighlightLine.CellsU("SelectMode").ResultIU = 1
        
    Else
        gHighlightLine.CellsU("BeginX").ResultIU = px1
        gHighlightLine.CellsU("BeginY").ResultIU = py1
        gHighlightLine.CellsU("EndX").ResultIU = px2
        gHighlightLine.CellsU("EndY").ResultIU = py2
    End If
End Sub

' --- ドラッグ変形(PageToXYで座標変換) ---
Sub DragSegment(mx As Double, my As Double)
    If gSegIndex = 0 Or gBaseShape Is Nothing Then Exit Sub

    Dim dx As Double, dy As Double
    Dim shp As Visio.Shape
    Dim x1 As Double, y1 As Double, x2 As Double, y2 As Double
    
    ' セグメントの向きを判定するために座標を取得
    x1 = gBaseShape.CellsU("Geometry1.X" & gSegIndex).ResultIU
    y1 = gBaseShape.CellsU("Geometry1.Y" & gSegIndex).ResultIU
    x2 = gBaseShape.CellsU("Geometry1.X" & gSegIndex + 1).ResultIU
    y2 = gBaseShape.CellsU("Geometry1.Y" & gSegIndex + 1).ResultIU

    ' 移動量を計算
    dx = mx - gStartX
    dy = my - gStartY
    
    For Each shp In gSelection
        ' 横線(Yがほぼ同じ)なら垂直移動、それ以外(縦線など)なら水平移動
        If Abs(y2 - y1) < 0.001 Then
            ' --- 垂直ドラッグ(Y座標を更新) ---
            shp.CellsU("Geometry1.Y" & gSegIndex).ResultIU = shp.CellsU("Geometry1.Y" & gSegIndex).ResultIU + dy
            shp.CellsU("Geometry1.Y" & gSegIndex + 1).ResultIU = shp.CellsU("Geometry1.Y" & gSegIndex + 1).ResultIU + dy
        Else
            ' --- 水平ドラッグ(X座標を更新) ---
            shp.CellsU("Geometry1.X" & gSegIndex).ResultIU = shp.CellsU("Geometry1.X" & gSegIndex).ResultIU + dx
            shp.CellsU("Geometry1.X" & gSegIndex + 1).ResultIU = shp.CellsU("Geometry1.X" & gSegIndex + 1).ResultIU + dx
        End If
    Next shp
    
    ' 次の移動量計算のために座標を更新
    gStartX = mx
    gStartY = my
    
    UpdateHighlightSegment gBaseShape, gSegIndex
End Sub
' --- お掃除 ---
' A: セグメントの「印」だけを消す(MouseMove用)
Public Sub ClearSegmentHighlight()
    If Not gHighlightLine Is Nothing Then
        On Error Resume Next
        gHighlightLine.Delete
        On Error GoTo 0
        Set gHighlightLine = Nothing
    End If
End Sub

' B: 基準線の「色」を元に戻す(MouseUp用)
Public Sub ClearBaseHighlight()
    If Not gBaseShape Is Nothing And gOriginalColor <> "" Then
        gBaseShape.CellsU("LineColor").FormulaU = gOriginalColor
        gBaseShape.CellsU("LineWeight").FormulaU = gOriginalWeight
        gOriginalColor = ""
    End If
End Sub

' --- 距離計算 ---
Function DistancePointToLine(px As Double, py As Double, x1 As Double, y1 As Double, x2 As Double, y2 As Double) As Double
    Dim L2 As Double
    L2 = (x2 - x1) ^ 2 + (y2 - y1) ^ 2
    If L2 = 0 Then DistancePointToLine = Sqr((px - x1) ^ 2 + (py - y1) ^ 2): Exit Function
    DistancePointToLine = Abs((y2 - y1) * px - (x2 - x1) * py + x2 * y1 - y2 * x1) / Sqr(L2)
End Function

ThisDocument

' --- ThisDocumentにコピペ ---
Public WithEvents app As Visio.Application

' マウスを押した:ドラッグ開始
Private Sub app_MouseDown(ByVal Button As Long, ByVal KeyButtonState As Long, ByVal x As Double, ByVal y As Double, CancelDefault As Boolean)
    If gSegIndex > 0 Then
        gIsDragging = True
        gStartX = x ' 開始位置を記録
        gStartY = y
        
        ' Visioの標準動作(図形の移動など)をキャンセルして、VBAに専念させる
        CancelDefault = True
        
        Debug.Print "Drag Start & Standard Action Cancelled"
    End If
    
    
End Sub

' マウスを動かした:ハイライト or 変形
Private Sub app_MouseMove(ByVal Button As Long, ByVal KeyButtonState As Long, ByVal x As Double, ByVal y As Double, CancelDefault As Boolean)
    If gBaseShape Is Nothing Then Exit Sub

    ' --- 1. ドラッグ中なら移動だけして即終了! ---
    If gIsDragging Then
        DragSegment x, y
        Exit Sub
    End If

    ' --- 2. ホバー中(ドラッグしてない時)だけ判定する ---
    Dim currentSeg As Long
    currentSeg = GetSegmentIndex(gBaseShape, x, y, 2)

    If currentSeg > 0 Then
        If currentSeg <> gSegIndex Then
            gSegIndex = currentSeg
            UpdateHighlightSegment gBaseShape, gSegIndex
        End If
    Else
        ' 線から離れた時だけリセット
        gSegIndex = 0
        ClearSegmentHighlight
    End If
End Sub

' マウスを離した:ドラッグ終了
Private Sub app_MouseUp(ByVal Button As Long, ByVal KeyButtonState As Long, ByVal x As Double, ByVal y As Double, CancelDefault As Boolean)
    If gIsDragging Then
        ' 1. ドラッグフラグを下ろす
        gIsDragging = False
    
        ' 2. オレンジの線を消し、基準線の色もどす
        ClearBaseHighlight
        ClearSegmentHighlight
    
        ' 3. 参照をクリアして実行モードを完全に終了する
        Set gBaseShape = Nothing
        Set gSelection = Nothing
        gSegIndex = 0
    
        Debug.Print "MouseUp: Operation Finished."
    
        ' 4. イベント監視も終了したい場合はこれ(必要に応じて)
        Set app = Nothing
    End If
    
End Sub

まとめ:イベント × 実用コードは強い

いかがでしたか?

このマクロ、地味だけどめちゃくちゃ実用的です。
何本も並んだコネクタを1本ずつ直すストレスから解放されます。

しかもやっていることは「動いた量(差分)をコピーしているだけ」。
仕組みはシンプルなのに、体感はかなり快適です。

そして今回のポイントは、
WithEventsでマウスの動きを拾ったこと。

イベントを使うと、VBAでも“操作している感覚”を作れます。
ぜひコピペして、あなたの図面でも試してみてください😊

タイトルとURLをコピーしました