t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA 業務フローチャートをマクロで簡単に作成する

フローチャートはプログラミングでよく用いられていたが、最近は専ら業務の流れを説明する図として利用されている。
特に部門をまたがる業務の全体像を把握するには便利である。

しかし、図なのでとにかく作成が面倒くさい。
どうしてもボックスのサイズをそろえたり、位置を揃えたり、コネクターをつなげたりといった細かい作業に時間を取られてしまう。

今回はマクロを使ってフローチャートを簡単に作成する方法について紹介する。

目次

作成するマクロの概要

まずはGIFアニメで楽々とフローを作成している様子を紹介。

f:id:t-hom:20160731010231g:plain

プロセスボタンをクリックするとプロセス入力モードになり、あらかじめ用意された枠をクリックすることでプロセス名が入力できる。
ネクターボタンをクリックするとコネクター接続モードになり、プロセスを次々クリックしていくだけで矢印で接続される。
最後にチャートの完成ボタンをクリックすると、不要な枠が消え、まっすぐな鉤型コネクターは直線コネクターに変換されることでコネクターのズレも綺麗になる。

作り方

新しいブックに標準モジュール「Module1」を挿入し、以下のコードを張り付ける。

Option Explicit
Public Enum Mode
    デフォルト = 0
    消去 = 2
    プロセス入力 = 3
    コネクタ接続 = 5
    判断入力 = 4
End Enum

Public Enum Direction
    North = 1
    West = 2
    South = 3
    East = 4
End Enum
Public 前にクリックしたシェイプ As Shape

Public Function 反対方向(D As Direction) As Direction
    Dim ret As Direction
    If D < 3 Then
        ret = D + 2
    Else
        ret = D - 2
    End If
    反対方向 = ret
End Function

Sub Click()
    Dim 今クリックしたシェイプ As Shape: Set 今クリックしたシェイプ _
        = Sheet1.Shapes(Application.Caller)

    If Sheet1.現在のモード <> コネクタ接続 Then Set 前にクリックしたシェイプ = Nothing
    
    Dim シェイプ文字列 As String
    
    Select Case Sheet1.現在のモード
    Case Mode.消去
        Call シェイプ無効化(今クリックしたシェイプ)
    Case Mode.プロセス入力
        シェイプ文字列 = InputBox("入力してください")
        If シェイプ文字列 = "" Then Exit Sub
        Call シェイプ有効化(今クリックしたシェイプ)
        Call シェイプ変更(今クリックしたシェイプ, msoShapeFlowchartProcess)
        今クリックしたシェイプ.TextFrame2.TextRange.Text = シェイプ文字列
    Case Mode.判断入力
        シェイプ文字列 = InputBox("入力してください")
        If シェイプ文字列 = "" Then Exit Sub
        Call シェイプ有効化(今クリックしたシェイプ)
        Call シェイプ変更(今クリックしたシェイプ, msoShapeFlowchartDecision)
        今クリックしたシェイプ.TextFrame2.TextRange.Text = シェイプ文字列
    Case Mode.コネクタ接続
        If Not 前にクリックしたシェイプ Is Nothing Then
            Dim コネクタ As Shape
            Set コネクタ = Sheet1.Shapes.AddConnector(msoConnectorElbow, 624, 154, 816, 272)
            コネクタ.Line.EndArrowheadStyle = msoArrowheadOpen
            コネクタ.Line.Weight = 1.5
            コネクタ.Line.ForeColor.RGB = vbBlack
            
            Dim 接続方向 As Direction
            接続方向 = 方位判定(前にクリックしたシェイプ, 今クリックしたシェイプ)
            
            コネクタ.ConnectorFormat.BeginConnect _
                ConnectedShape:=前にクリックしたシェイプ, _
                ConnectionSite:=接続方向
                
            コネクタ.ConnectorFormat.EndConnect _
                ConnectedShape:=今クリックしたシェイプ, _
                ConnectionSite:=反対方向(接続方向)
        End If
        Set 前にクリックしたシェイプ = 今クリックしたシェイプ
    Case Else
        MsgBox "モードを選択してください。"
    End Select
End Sub

Sub シェイプ有効化(sh As Shape)
    With sh
        .TextFrame2.TextRange.Font.Fill.ForeColor.RGB = vbBlack
        .Line.ForeColor.RGB = vbBlack
        .Line.Weight = 2
        .Fill.Transparency = 0
        .Line.DashStyle = msoLineSolid
        .Fill.ForeColor.RGB = vbWhite
    End With
End Sub

Sub シェイプ無効化(sh As Shape)
    With sh
        .AutoShapeType = msoShapeFlowchartProcess
        .Line.Weight = 0.25
        .TextFrame2.TextRange.Delete
        .Line.ForeColor.RGB = RGB(150, 150, 150)
        .Fill.Transparency = 1
        .Line.DashStyle = msoLineDash
    End With
End Sub

Sub ひな形作成()
    Set 前にクリックしたシェイプ = Nothing
    Call 全シェイプ削除
    DimAs Double: 幅 = 100
    Dim 高さ As Double: 高さ = 40
    Dim 開始X As Double: 開始X = 100
    Dim 開始Y As Double: 開始Y = 120
    Dim 横間隔: 横間隔 = 50
    Dim 縦間隔: 縦間隔 = 30
    Dim 横数: 横数 = 5
    Dim 縦数: 縦数 = 10
    
    Dim x, y
    For x = 0 To 横数 - 1
        For y = 0 To 縦数 - 1
            Dim sh As Shape
            Set sh = Sheet1.Shapes.AddShape( _
                Type:=msoShapeFlowchartProcess, _
                Left:=開始X + (x * (+ 横間隔)), _
                Top:=開始Y + (y * (高さ + 縦間隔)), _
                Width:=, _
                Height:=高さ)
            Call シェイプ無効化(sh)
            sh.OnAction = "Click"
        Next
    Next
    Call Sheet1.ボタン状態クリア
    Sheet1.現在のモード = デフォルト
End Sub

Sub 全シェイプ削除()
    Dim s As Shape
    For Each s In ActiveSheet.Shapes
        If s.Type <> msoFormControl Then    '←ボタンを削除しないため
            s.Delete
        End If
    Next
End Sub

Function 方位判定(s1 As Shape, s2 As Shape) As Direction
    Dim s1横中央: s1横中央 = s1.Left + (s1.Width / 2)
    Dim s2横中央: s2横中央 = s2.Left + (s2.Width / 2)
    Dim 横の距離: 横の距離 = s1横中央 - s2横中央
    
    Dim s1縦中央: s1縦中央 = s1.Top + (s1.Height / 2)
    Dim s2縦中央: s2縦中央 = s2.Top + (s2.Height / 2)
    Dim 縦の距離: 縦の距離 = s1縦中央 - s2縦中央

    If Abs(横の距離) - Abs(縦の距離) > 0 Then
        If 横の距離 > 0 Then
            方位判定 = West
        Else
            方位判定 = East
        End If
    Else
        If 縦の距離 > 0 Then
            方位判定 = North
        Else
            方位判定 = South
        End If
    End If
End Function

Sub シェイプ変更(TargetShape As Shape, T As MsoAutoShapeType)
    Dim 接続されたコネクタ一覧 As New Collection
    Dim s As Shape
    
    '現在TargetShapeに接続されたコネクタを一覧化しておく
    For Each s In TargetShape.Parent.Shapes
        If s.Connector Then
            If s.ConnectorFormat.BeginConnected Then
                If s.ConnectorFormat.BeginConnectedShape Is TargetShape Then
                    接続されたコネクタ一覧.Add _
                        Array(s, s.ConnectorFormat.BeginConnectionSite, True) 'True=Begin
                End If
            End If
            If s.ConnectorFormat.EndConnected Then
                If s.ConnectorFormat.EndConnectedShape Is TargetShape Then
                    接続されたコネクタ一覧.Add _
                        Array(s, s.ConnectorFormat.EndConnectionSite, False) 'False=End
                End If
            End If
        End If
    Next
    
    'シェイプタイプを切り替えたタイミングでコネクタの接続が外れる
    TargetShape.AutoShapeType = T
    
    '一覧に登録されたコネクタをTargetShapeに再接続する
    Dim c As Variant
    For Each c In 接続されたコネクタ一覧
        Dim コネクタ As Shape: Set コネクタ = c(0)
        If c(2) Then 'True=Begin, False=End
            コネクタ.ConnectorFormat.BeginConnect TargetShape, c(1)
        Else
            コネクタ.ConnectorFormat.EndConnect TargetShape, c(1)
        End If
    Next
End Sub

Sub チャート完成()
    Dim s As Shape
    Dim Arr() As String
    ReDim Arr(0)
    For Each s In Sheet1.Shapes
        If s.Type <> msoFormControl Then
            s.OnAction = ""
            If s.Connector Then
                If s.Height < 2 Or s.Width < 2 Then
                    s.ConnectorFormat.Type = msoConnectorStraight
                End If
            End If
            If s.Fill.Transparency = 1 And s.AutoShapeType = msoShapeFlowchartProcess Then
                s.Delete
            End If
        End If
    Next
    Call Sheet1.ボタン状態クリア
End Sub

次に、Sheet1モジュールに以下のコードを張り付ける。

Private cm As Mode

Public Property Get 現在のモード() As Mode
    現在のモード = cm
End Property

Public Property Let 現在のモード(m As Mode)
    cm = m
End Property

Sub ボタン状態クリア()
    With Sheet1.Buttons(Array("Mode3", "Mode4", "Mode5", "Mode2")).Font
        .FontStyle = "標準"
        .ColorIndex = 0
    End With
End Sub

Sub ModeButton()
    Set Module1.前にクリックしたシェイプ = Nothing
    Dim 押されたボタン As Button
    Set 押されたボタン = Sheet1.Buttons(Application.Caller)

    Call ボタン状態クリア
    
    If 現在のモード = CInt(Right(押されたボタン.Name, 1)) Then
        現在のモード = Mode.デフォルト
    Else
        With 押されたボタン.Font
            .FontStyle = "太字"
            .ColorIndex = 5
        End With
        現在のモード = CInt(Right(押されたボタン.Name, 1))
    End If
End Sub

次にSheet1にボタンを配置する。
f:id:t-hom:20160731023354p:plain

このボタンはフォームコントロールのものを使用すること。
f:id:t-hom:20160731023455p:plain

割り当てるマクロは、次のとおり。
「ひな形作成」ボタン→「ひな型作成」マクロ
「プロセス入力」ボタン→「Sheet1.ModeButton」マクロ
「判断入力」ボタン→「Sheet1.ModeButton」マクロ
「コネクター」ボタン→「Sheet1.ModeButton」マクロ
「消去」ボタン→「Sheet1.ModeButton」マクロ
「チャートの完成」ボタン→「チャート完成」マクロ

ボタンを右クリックで選択すると、左上の名前ボックスで名前を付けることができる。
f:id:t-hom:20160731024059p:plain

今回は押されたボタンの判定にこの名前を使用するので、
以下のボタンにはそれぞれ名前をつけておく。
「プロセス入力」ボタン→Mode3
「判断入力」ボタン→Mode4
「コネクター」ボタン→Mode5
「消去」ボタン→Mode2

以上でマクロは完成である。

使ってみる

ひな形作成ボタンを押すと、このように薄い枠が表示される。
f:id:t-hom:20160731024442p:plain

私の環境では、シートのズームは70%くらいにしておくと収まりが良い。
(設計したときにシートが70%ズームになっているのに気づかず、その状態でフィットするようにコーディングしてしまった。将来的にはサイズ調整するUIも付けたいけど、今のところ作りっぱなしの手抜き)

プロセス入力ボタンを押すとボタンが青字になり、この状態で枠をクリックすることでプロセスが入力できる。
f:id:t-hom:20160731024858p:plain
判断入力では「ひし形」の図形が配置される。
f:id:t-hom:20160731024923p:plain

ネクターボタンをクリックすると、最初のプロセスクリックでは何も起きないが、その時にクリックしたシェイプを記憶し、次にクリックしたシェイプと矢印で接続される。連続でプロセスをクリックすることで次々つなげることができる。
一旦コネクターを切って新しいシェイプから始めたいときは、一度別のモードに切り替えるか、もう一度コネクターボタンを押して一度デフォルトモードにもどしてから、再度コネクターボタンでコネクタ接続モードに切り替える。

プロセスを修正するときは、プロセスモードで既存のプロセスをクリックすればよい。
プロセスを判断に切り替えたいときも、判断モードで既存プロセスをクリックする。
また、不要なプロセスを削除したい場合はシェイプごと消すのではなく、消去モードにしてクリックする。
ネクターの削除は今のところ手動だ。

最後にチャートの完成ボタンを押すと、不要な枠が消える。

また、作成途中はコネクターの種類がすべて鉤型コネクターなので、真横や真下につないでもズレて見える。
f:id:t-hom:20160731032553p:plain

チャートの完成ボタンを押すことでこれらの本来まっすぐになるべきコネクタは直線コネクタに自動変換され、綺麗になる。

完成ボタンを押した後はプロセス入力はできなくなるので注意。

なお、再度ひな形作成ボタンを押すと、ボタン以外の全シェイプが消去されて新しい枠が配置される。

課題

斜めに配置されたプロセスを接続した場合はこのようになる。
f:id:t-hom:20160731031614p:plain

これは、シェイプ同士の中心距離を縦、横ではかり、より短い方のルートを通るように設計したためだ。
f:id:t-hom:20160731032147p:plain

ただ、フローの流れを考えると、この設計は失敗だった。

今のところ、このように混線してしまうので、これは手で付け替える必要がある。
f:id:t-hom:20160731032256p:plain

デザイン

冒頭のアニメーションで紹介したマクロはセル幅をフローが収まる幅に合わせてある。
また、背景色やヘッダや罫線もあらかじめ作成してある。
f:id:t-hom:20160731033417p:plain

フローの位置に合わせて手動でセル幅を変更する際は、ひな形の枠を全選択して書式設定からセルと連動して動かないように設定しておくとよい。
f:id:t-hom:20160731034023p:plain

完成したフローを別のシートへ移動

完成したフローがこちら。
f:id:t-hom:20160731032843p:plain

完成したフローを別のシートに移すには、オブジェクトの選択モードにして、
f:id:t-hom:20160731035133p:plain

全シェイプ選択し、
f:id:t-hom:20160731035211p:plain

この状態でコピーして別のシートに張り付ければ良い。
f:id:t-hom:20160731035250p:plain

背景をデザインしている場合はこのように背景のセルを必要範囲選択してコピーすると、その範囲に存在するシェイプも一緒に付いてくる。
f:id:t-hom:20160731035410p:plain

ただ、普通に張り付けるとセル幅に合わせてつぶれてしまう。
f:id:t-hom:20160731035743p:plain

この場合は張り付け直後に表示される「Ctrl」と書かれた小さなポップアップをクリックし、元の列幅を保持するように変更すると直る。
f:id:t-hom:20160731035937p:plain

マクロの解説

さて、マクロの動作についてざっくり簡単に概要を説明する。
まずひな形作成であるが、これは単に設定値に合わせてシェイプをAddしているだけ。
ポイントは「sh.OnAction = "Click"」でシェイプにマクロを登録している点である。
これは最終的にはチャート完成時に、「s.OnAction = ""」でマクロ登録を外している。

Clickマクロはシェイプで共通であるが、「Application.Caller」で呼び出し元のシェイプ名がわかるので、そのシェイプ名をもとに該当するシェイプオブジェクトを「今クリックしたシェイプ」変数に格納して操作している。

Clickマクロは現在のモードによって処理が分かれている。
モードはModule1の冒頭で列挙型「Mode」として定義しており、現在のモードはSheet1にプロパティとして保持させている。

モードは4つのモードボタンで切り替える。
これも共通のModeButtonマクロが呼び出されるが、呼び出したボタンはApplication.Callerで判定している。
「Dim 押されたボタン As Button」の部分で、Button型というのは見たことが無いかもしれない。
これはオブジェクトブラウザで非表示のメンバーを表示させると見ることができる。
f:id:t-hom:20160731040747p:plain

ボタンもシェイプの一種ではあるが、Shape型で処理すると、「押されたボタン.Font」でメンバーが見つからないというエラーになる。
Object型で対応しても良かったのだが、インテリセンスを使わないと扱い方がわからなかったので固有型で宣言した。

プロセスや判断の入力、消去などはシェイプの変更で対応している。

コネクタの接続部分は苦労したが、今クリックしたシェイプと前にクリックしたシェイプの位置関係を「方位判定」関数で処理して適切な位置でつないでいる。
この関数の戻り値はModule1で定義している列挙型「Direction」である。

東西南北あるいは英語でNEWSを考えると、並び順が以下のようになっているのは不自然に思われるかもしれない。

Public Enum Direction
    North = 1
    West = 2
    South = 3
    East = 4
End Enum

これはシェイプのConnectionSiteが次の順になっているので合わせた結果である。
f:id:t-hom:20160731042043p:plain

最後にシェイプの完成時には高さまたは幅が2未満のコネクタを直線コネクタに変換している。
(1未満ではうまくいかなかったので2未満とした)

マクロの解説は以上

おわりに

私はこのマクロを「BreadChart(ブレッドチャート)」と名付けた。
最近たまたま電子工作で利用するBreadboardを見て閃いたためだ。
ブレッドボード - Wikipedia


もともと5年ほど前からフローチャートを楽に作るマクロを考えていたのだが、当時は今一つ「これだっ」と思えるアイデアが出ずに悩んでいた。
また実装スキルも低かったので、「こんな風にやればできるかも」と思ってもなかなか作れなかった。
いろいろと試行錯誤しながらガラクタを作り続けているうちに徐々に実装スキルが付き、今では自分が「できるかも」と思ったものは大体作れるようになった。

今はできないことでも、トライ&エラーを繰り返して悩むというプロセスを踏んでおけばスキルがついていつかできるようになる。そういうもんだと実感した。
みなさんも作りたいけどなかなかうまくいかないマクロがあったら、すぐに諦めずにあーだこーだ試行錯誤を続けると良い。たとえ今はガラクタであっても、いつかそれが宝の山になる日が来るかもしれない。

追記

8/13 ブラッシュアップしてGitHubに公開した。詳細は以下参照。
thom.hateblo.jp

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。