関数と条件付き書式だけで、開始日と終了日を入力すればバーが自動で出るところまで作れます。マクロは要りません。
同じ工程表を毎回作り直すなら、テーブル+VBAでボタン1つにできます。
ただし「何%進んだか」「実際にいつ終わったか」は、どちらの方法でも自動にはなりません。ここが最後まで残ります。
私は工場で、設備をいったん止めて行う定期メンテナンス工事の工程表を8年作ってきました。関わる会社も人も多いので、日程が1日ずれるだけで連絡が何本も飛びます。それでも道具はずっとExcelでした。専用ソフトを入れる話にはならず、関係者全員がその場で開けるものが、Excelしかなかったからです。
その工程表を、毎回次のように作っていました。前の年のファイルをコピーして、日付を今年に直して、土日の位置がずれた分だけセルの色を塗り直す。作業を1本足すたびに、3行セットをコピーしてサイズを合わせ直す。私はこれに、毎回半日から丸1日かけていました。
やっていることは工程管理のはずなのに、実際の手の動きはイラストを描く作業でした。バーを細く見せようとすると今度はマウスカーソルが乗らないので、Ctrlとマウスホイールで拡大して、色を塗って、倍率を戻して、文字を書いて、またセルの高さを合わせる。
いまはこの作り直しがありません。独学でExcelの関数とVBAを覚えたからですが、入口は特別なものではなく、この記事に書く数式を1本入れただけでした。そのとおりに組めば、開始日と終了日を打つとバーが出ます。私はここに来るまでに3年かかりました。当時はAIに聞くこともできなかったので、遠回りした部分も含めて全部書きます。

検証環境: Microsoft 365(ビジネス)/ Windows 11 / 確認日 2026年8月9日
※本記事の情報は 2026年8月時点 のものです。最新情報は公式ドキュメントをご確認ください。
工程表の作り直しを、手元のPCだけで終わらせる
Excelの工程管理や会議の整理を、手元のPCだけで完結できる時短ツールを作っています。ガントチャートや議事録整理などがあります。クラウドが使えない職場を前提に作りました。
時短ツールを見る無料で試せる版があります。この記事を読むだけでも工程表は作れます。
マクロなしで、バーが自動で出るところまで作る

先に結論を書くと、「工程表の自動作成」と呼ばれるものの大半は、マクロなしで作れます。
必要なのは3つだけです。日付の行、土日の色分け、そしてバーの色分け。順番に作ります。
このあと出てくる数式は、次のレイアウトを前提にしています。自分の表に合わせて列番号だけ読み替えてください。
| 場所 | 入れるもの |
|---|---|
| B列 | 作業内容 |
| C列 | 開始日 |
| D列 | 終了日 |
| 2行目(E列以降) | 月。数式で出す |
| 3行目(E列以降) | 日付そのもの。あとの数式はすべてこの行を見る |
| 4行目(E列以降) | 曜日。数式で3行目に追従させる |
| 5行目以降 | 作業。1作業につき3行を使う |
見出しを3段にしているのには理由があります。列幅を3文字ぶんまで細くしたいので、1つのセルに「9/1(火)」は入りません。月・日・曜を縦に分ければ、1列が細いままでも読めます。現場の工程表がたいていこの形をしているのも同じ理由です。
作業のほうも1行では作りません。1作業につき3行を使います。1行目に作業名と開始日・終了日、2行目にバー、3行目はすきまです。行の高さは上から17・13・5あたりが目安です。

3行に分ける理由は3つあります。バーの行を細くすると線が引き締まって、本数が増えても潰れません。作業名とバーが同じ行にないので、名前が長くてもバーに重なりません。そしてすきまの行があると、作業と作業の切れ目が目で追えます。ここを詰めると、10本を超えたあたりから急に読めなくなります。次の章のVBA版も、同じ3行1セットで描いています。
罫線も先に入れておいてください。カレンダーが右へ長くなると、線の無い表は目で行を追えなくなります。マス目に細い線を敷いたうえで、作業のまとまりごとに1本、少し濃い横線を引くと一気に読めるようになります。月曜の左に薄い縦線を足すと、週の区切りも分かります。なお次の章のVBA版では、この線もボタンを押すたびにマクロが引き直します。
日付の行は「開始日+1」で作る

セルB3に工事の開始日を入れます。そしてC3に、こう書きます。
=B3+1あとは右へオートフィルするだけです。日付は連番なので、足し算1つで並びます。
ここで大事なのは、日付を手で打たないことです。手で打つと、開始日が1日ずれただけで全部打ち直しになります。E3だけ直せば右が全部ついてくる形にしておくと、あとがまったく違います。
3行目の表示形式は、ユーザー定義で d にします。日にちだけが出るので、列幅3文字でも収まります。

曜日の行(4行目)は、真上のセルを見るだけです。B4にこう入れて、書式を aaa にします。
=B3

月の行(2行目)は、月が変わる列にだけ「2026年9月」と文字で入れます。3か月の工事でも3回だけなので、手で入れて十分です。
ここは数式にしたくなるところですが、実際にやってみるとうまくいきません。数式が空文字を返すと、Excelはそのセルを「中身がある」と判断して、隣のセルからの文字のはみ出しを止めます。列幅は3文字しかないので、はみ出せないと「202」で切れて読めなくなります。ラベルを置かない列は、数式も入れずに本当に空のままにしてください。
曜日の行は数式のままで問題ありません。3行目の日付を書き換えれば、曜日はついてきます。
土日は条件付き書式とWEEKDAY関数で自動的に塗る

私は最初、土日を手で塗っていました。前年の工程表をコピーすると土日の位置が変わるので、そのたびに塗り直しです。行を1本足すたびにまた塗り直しでした。
これは条件付き書式に置き換えられます。Excelで最初にやめるべき手作業がこれです。
E5から表の右下までを選択して、ホームタブの「条件付き書式」→「新しいルール」→「数式を使用して、書式設定するセルを決定」を選びます。数式欄にこう入れます。
=WEEKDAY(C$3,2)>=6
WEEKDAY は日付が何曜日かを数字で返す関数です。第2引数に 2 を指定すると、月曜が1、日曜が7になります。つまり 6以上なら土日です。この対応はMicrosoftサポートの WEEKDAY 関数のページに一覧があります。
E$3 の $ は行だけを固定する意味です。これを付けないと、下の行に行くほど参照する日付がずれていきます。条件付き書式でいちばん多いつまずきがここです。参照を意味する$ については、絶対参照と相対参照について解説した記事があります。こちらをご覧ください。
同じルールを曜日の行(4行目)にも入れておくと、土日の帯が見出しまで通って読みやすくなります。ただし月の行(2行目)には入れないでください。月のラベルは月初の列にだけ文字を置いて、右隣の空セルへはみ出させて表示しています。その途中に色が入ると、文字の下だけ帯が割り込んだように見えます。日付の行(3行目)も、背景を濃い色にしているなら入れません。土日だけ地の色が変わって、見出しが途切れて見えます。
なお条件付き書式の数式は、等号で始めてTRUEかFALSEを返す形でなければ動きません。またMicrosoftサポートの条件式の解説によると、別のブックへの外部参照は条件付き書式には使えません。祝日リストを別ファイルに置きたくなりますが、同じブックの中に置いてください。
祝日も塗りたい場合は、どこかのシートに祝日の日付を並べて名前を付け(ここでは 祝日 とします)、2つ目のルールとして追加します。
=COUNTIF(祝日,C$3)>0祝日を除いた営業日で日数を数えたい場合は、WORKDAY関数の使い方やNETWORKDAYS関数の解説のほうが向いています。この記事の工程表は「暦日で並べる」前提なので、色分けだけ入れてあります。
バーは「開始日と終了日のあいだなら塗る」だけ

ここが自動作成の本体です。やることは1つで、その列の日付が、その行の開始日と終了日のあいだに入っていたら色を塗る。それだけです。
同じくE5から表の右下を選択して、条件付き書式に次の数式を追加します。
=AND($C5<>"",E$3>=$C5,E$3<=$D5)
$C5<>""… 開始日が入っている行だけを対象にする(空の行に色が出るのを防ぐ)E$3>=$C5… その列の日付が、開始日以降であるE$3<=$D5… その列の日付が、終了日以前である
$C5 は列だけを固定、E$3 は行だけを固定です。この $ の付け方さえ合っていれば、あとは表をどれだけ広げても崩れません。
ここまで作ると、C列とD列に日付を打つだけでバーが出ます。開始日を1日ずらせば、バーも1日ずれます。塗り直しはもう発生しません。

ルールの適用順にだけ注意してください。土日のルールが上にあると、バーが土日で途切れて見えます。「条件付き書式ルールの管理」で、バーのルールを土日より上に置いてください。土日も含めて連続で見せたいならこの順、土日は動かない日として抜いて見せたいなら逆の順です。どちらが正しいということはなく、現場の見方に合わせます。

ここまでの数式は、1作業1行で作った場合のものです。上の3枚の画面も、話を単純にするために1行で撮っています。冒頭の完成形のように1作業3行にするなら、「バーの行だけ塗る」という条件を1つ足します。E5から右下までを選んで、次の数式に差し替えてください。
=AND(MOD(ROW()-5,3)=1,$C4<>"",E$3>=$C4,E$3<=$D4)足したのは先頭の MOD(ROW()-5,3)=1 だけです。5 は作業が始まる行、3 は1セットの行数。この計算が1になる行が、3行のうちの2行目、つまりバーの行です。参照が $C5 から $C4 に1つ上がっているのは、日付が1行上の作業名の行に入っているからです。範囲はE5から右下までのまま、ルールも2つのままで済みます。作業を足すときは3行ずつ足してください。
なお、条件付き書式を大量に設定したブックは動作が重くなることがあります。Microsoftのサポート記事でも、条件付き書式が多いブックで編集が遅くなる場合があることが案内されています。ただしルール数の上限は公式に数値が示されていませんので、「何個までなら安全」という言い方はできません。私の感覚では、範囲を表の実サイズぴったりに絞っておけば実用上は困りませんでした。
VBAでボタン1つにする(テーブルから工程表を描く)

ここまでの形で、私は最初の3年をやっていました。次の1年は手塗りとVBAを行ったり来たりして、最後の4年はVBAでした。
正直に書くと、ハイブリッドの1年がいちばんしんどかったです。頭の中に「こうなってほしい」という形はあるのに、当時はAIに聞くこともできなかったので、こうやればいいのかな、いや違うな、を延々とやっていました。いま同じことをやるなら、たぶん数日で組めると思います。
では、なぜ条件付き書式で足りているのにVBAにしたのか。表示する期間を切り替えたかったからです。
条件付き書式のやり方は、カレンダーの列をあらかじめ全部並べておく必要があります。3か月の工事なら90列。ここに「今週だけ見たい」「来月だけ見たい」を足そうとすると、列を隠したりスクロールしたりで結局手が動きます。そこをボタン1つにしたかったというのが動機です。
作業リストは「テーブル」にしておく

VBAにする前に、作業リストをExcelのテーブルにします。範囲を選んで、ホームタブの「テーブルとして書式設定」を選ぶだけです。
テーブルにしておく理由は、行が増えても範囲を書き直さなくていいからです。ふつうのセル範囲だと A2:E50 のように書くことになり、51行目を足した瞬間にコードが拾わなくなります。テーブルなら行を足した分だけ自動で広がります。
Microsoftサポートのテーブルの概要では、フィルタが自動で有効になること、集計列は1つのセルに数式を入れれば列全体に適用されること、テーブル名[列名] という書き方で数式が読みやすくなることが挙げられています。
私が持たせていた列はこれだけです。
| 列名 | 中身 |
|---|---|
| 作業内容 | 工程の名前 |
| 開始日 / 開始時間 | いつから |
| 終了日 / 終了時間 | いつまで |
| 備考 | 何か書きたいことがあれば |
進捗率の列はありません。これは後で書きますが、最後まで作りませんでした。
日付と時間は別の列にしてください。1つのセルにまとめると、日だけを見たい全体工程のほうで、毎回時刻を切り落とすことになります。分けておけば、全体工程は日の列だけを見て、あとで作る詳細工程が時間の列も見る、という形にできます。
備考は、その作業のバーの真上に出ます。1作業3行にしてあるので、タイトル行のカレンダー側は空いたままです。そこにメモを置くと、右隣の空セルへはみ出して、バーに重ならずに読めます。長い文章を入れると右へ流れていくので、10文字前後までにしておくと収まりがいいです。
あとで出てくるコードは、列を名前で探しています(ListColumns("開始日") という書き方です)。左から何番目かで数えていないので、列を足しても、順番を入れ替えても、マクロは壊れません。使わない列を置いておいても大丈夫です。
テーブルには分かりやすい名前を付けておきます。テーブル内をクリックして「テーブルデザイン」タブの左端で変更できます。名前にはスペースが使えず、ブックの中で重複できない決まりです(構造化参照の公式解説)。
1つだけ決めておくことがあります。このテーブルは、ガントチャートとは別のシートに置いてください。
同じシートに置くと、バーを描く領域とテーブルが場所を取り合います。カレンダーは右へどんどん伸びるので、テーブルをどこに逃がしても、いつか当たります。私は「作業リスト」というシートを作って、そこにはテーブルだけを置いていました。
ボタンを押したら、いったん全部消してから描き直す
ここが設計の分かれ目です。私は上書きではなく、全部消してから描き直す形にしました。
理由は単純で、上書きだと消し忘れが出るからです。作業の期間を短くしたとき、前に塗ったバーの右端が残ります。それを1つずつ判定して消すコードを書くくらいなら、毎回まっさらにしたほうが確実でした。
そして表の左上にドロップダウンを置いて、そこで選んだ日付が表のいちばん左になるようにしました。日付を変えてボタンを押すと、その日を起点に描き直されます。
ドロップダウンの中身は、作業リストのシートの空いているところに日付を並べておき、データタブの「データの入力規則」で種類を「リスト」にして、その範囲を指定するだけです。
候補の日付も手で打つ必要はありません。先頭に1つだけ日付を入れて、その下に =(1つ上のセル)+7 を入れて下へコピーすれば、1週間おきに並びます。日付の行とまったく同じ考え方です。週単位で並べておくと、表示する範囲を1週間ずつずらせます。
1つだけ気をつけてください。入力規則で指定する範囲は、実際に日付が入っている行数ぴったりにします。空のセルまで範囲に入れると、ドロップダウンに空行が並びます。候補を増やすときは、数式を下へコピーしてから範囲も広げてください。なお作業リストのテーブルは下へ伸びるので、候補は右か、別のシートに置いておくとぶつかりません。
コードはこうなります。標準モジュールに貼ってください。VBAをまだ触ったことがない場合は、先にVBAの始め方でエディタの開き方と標準モジュールの作り方を確認しておくと迷いません。
この記事のコードは4つに分かれて出てきますが、中身は1つの標準モジュールです。標準モジュールを1つだけ作って、出てくる順に上から貼り足していけば、最後にそのまま完成します。①この下のコード(設定と全体工程)②詳細工程のコード ③書き出しのコード ④フォルダを選ぶコード、の順です。設定の Private Const は必ずモジュールのいちばん上に置いてください。Subより下に書くとコンパイルエラーになります。
クリックしてすべてのコードを見る
Option Explicit
'==== 設定(自分の表に合わせて、ここだけ変える) ====
Private Const SHEET_NAME As String = "全体工程" ' ガントチャートを描くシート
Private Const LIST_SHEET As String = "作業リスト" ' 作業リストのテーブルがあるシート
Private Const TABLE_NAME As String = "作業リスト" ' テーブルの名前
Private Const START_CELL As String = "B2" ' 表の左端にしたい日付(ドロップダウン)
Private Const MONTH_ROW As Long = 2 ' 月のラベルを出す行
Private Const HEAD_ROW As Long = 3 ' 日付を並べる行
Private Const WEEK_ROW As Long = 4 ' 曜日の行(=E3 の数式が入っている)
Private Const FIRST_ROW As Long = 5 ' 1件目のタイトル行
Private Const FIRST_COL As Long = 5 ' E列からカレンダーを描く
Private Const DAY_COUNT As Long = 60 ' 横に何日ぶん出すか
Private Const ROWS_PER_JOB As Long = 3 ' タイトル行+バー行+隙間行
Private Const H_TITLE As Double = 17 ' タイトル行の高さ
Private Const H_BAR As Double = 13 ' バーの行の高さ(細いほど締まって見える)
Private Const H_GAP As Double = 5 ' すきまの行の高さ
'---- ここから下は「その日の詳細工程」用 ----
Private Const DAILY_SHEET As String = "詳細工程" ' 時間単位で描くシート
Private Const DAILY_CELL As String = "B2" ' 表示する日付(ドロップダウン)
Private Const HOUR_COUNT As Long = 24 ' 0時から24時まで並べる
Private Const WORK_START As Long = 8 ' 日勤の始まり
Private Const WORK_END As Long = 17 ' 日勤の終わり
Private Const LUNCH_HOUR As Long = 12 ' 昼休み
Public Sub DrawGantt()
Dim ws As Worksheet
Dim lo As ListObject
Dim baseDate As Date
Dim i As Long, n As Long
Dim d As Date
Dim lastRow As Long
Dim titleRow As Long, barRow As Long
Dim sDate As Date, eDate As Date
Dim sCol As Long, eCol As Long
Dim memoCol As Long, k As Long
Dim memo As String
Set ws = ThisWorkbook.Worksheets(SHEET_NAME)
Set lo = ThisWorkbook.Worksheets(LIST_SHEET).ListObjects(TABLE_NAME)
baseDate = ws.Range(START_CELL).Value
lastRow = FIRST_ROW + lo.ListRows.Count * ROWS_PER_JOB - 1
' 備考の列があるかどうかを先に調べておく。無いブックでもエラーにしないため
memoCol = ColIndex(lo, "備考")
Application.ScreenUpdating = False
' 1) 前回描いた内容を全部消す
ClearArea ws, DAY_COUNT
' 2) 見出しを引き直す(2行目=月・3行目=日。4行目の曜日は数式が追従する)
' 月のラベルは必ず「文字」として入れる。日本語のExcelは "2026年9月" を
' 日付だと解釈してしまい、列幅が狭いと ### になるため先に表示形式を文字列にする
ws.Range(ws.Cells(MONTH_ROW, FIRST_COL), _
ws.Cells(MONTH_ROW, FIRST_COL + DAY_COUNT - 1)).NumberFormat = "@"
For i = 0 To DAY_COUNT - 1
d = baseDate + i
ws.Cells(HEAD_ROW, FIRST_COL + i).Value = d
' 月のラベルは左端と月初だけ。隣は空のままにしないと文字がはみ出せない
If i = 0 Or Day(d) = 1 Then
ws.Cells(MONTH_ROW, FIRST_COL + i).Value = Format$(d, "yyyy年m月")
End If
Next i
' 3) 先に土日を塗る(ここから下は塗る順番がそのまま重なる順番になる)
' 2行目(月のラベル)は塗らない。月名は右隣の空セルへはみ出して表示するので、
' 途中に色が入ると文字の下だけ帯が入ったように見える
For i = 0 To DAY_COUNT - 1
d = baseDate + i
If Weekday(d, vbMonday) >= 6 Then
ws.Range(ws.Cells(WEEK_ROW, FIRST_COL + i), _
ws.Cells(lastRow, FIRST_COL + i)).Interior.Color = RGB(231, 226, 216)
End If
Next i
' 4) その上にバーを重ねる
For n = 1 To lo.ListRows.Count
titleRow = FIRST_ROW + (n - 1) * ROWS_PER_JOB
barRow = titleRow + 1
' 行の高さもここで揃える。揃えないと3行とも同じ高さのままで、
' せっかく3行1セットにしてもバーが太く見えて締まらない
ws.Rows(titleRow).RowHeight = H_TITLE
ws.Rows(barRow).RowHeight = H_BAR
ws.Rows(barRow + 1).RowHeight = H_GAP
ws.Cells(titleRow, 2).Value = _
lo.ListColumns("作業内容").DataBodyRange.Cells(n).Value
sDate = lo.ListColumns("開始日").DataBodyRange.Cells(n).Value
eDate = lo.ListColumns("終了日").DataBodyRange.Cells(n).Value
' 表示している期間から外れている作業は描かない
If eDate >= baseDate And sDate <= baseDate + DAY_COUNT - 1 Then
If sDate < baseDate Then sDate = baseDate
If eDate > baseDate + DAY_COUNT - 1 Then eDate = baseDate + DAY_COUNT - 1
sCol = FIRST_COL + DateDiff("d", baseDate, sDate)
eCol = FIRST_COL + DateDiff("d", baseDate, eDate)
ws.Range(ws.Cells(barRow, sCol), ws.Cells(barRow, eCol)) _
.Interior.Color = RGB(15, 118, 110)
' 備考はバーの真上(タイトル行)に置く。タイトル行のカレンダー側は
' 空いているので、右隣の空セルへはみ出してそのまま読める
If memoCol > 0 Then
memo = CStr(lo.ListColumns(memoCol).DataBodyRange.Cells(n).Value)
If Len(memo) > 0 Then
With ws.Cells(titleRow, sCol)
.Value = memo
.HorizontalAlignment = xlLeft
End With
End If
End If
End If
Next n
' 5) 最後に罫線を引き直す
DrawBorders ws, lo.ListRows.Count, baseDate
Application.ScreenUpdating = True
End Sub
'==== 前回描いた内容を消す(全体工程・詳細工程で共通)====
Private Sub ClearArea(ws As Worksheet, colCount As Long)
Dim lastRow As Long, lastCol As Long
' 消す範囲は「そのシートで何かが入っている最終行」まで。作業名の位置から
' 逆算すると、名前を手で消したときに範囲が縮んで、前のバーが残ってしまう
lastRow = ws.UsedRange.Row + ws.UsedRange.Rows.Count - 1
If lastRow < FIRST_ROW Then lastRow = FIRST_ROW
lastCol = FIRST_COL + colCount - 1
' 2行目のラベルは値も色も消す
With ws.Range(ws.Cells(MONTH_ROW, FIRST_COL), ws.Cells(MONTH_ROW, lastCol))
.ClearContents
.Interior.ColorIndex = xlNone
End With
' 3行目は「値だけ」消す(見出しの背景色は残したいため)
ws.Range(ws.Cells(HEAD_ROW, FIRST_COL), ws.Cells(HEAD_ROW, lastCol)).ClearContents
ws.Range(ws.Cells(WEEK_ROW, FIRST_COL), _
ws.Cells(WEEK_ROW, lastCol)).Interior.ColorIndex = xlNone
With ws.Range(ws.Cells(FIRST_ROW, FIRST_COL), ws.Cells(lastRow, lastCol))
.ClearContents
.Interior.ColorIndex = xlNone
End With
' 罫線も消す。作業が減ったとき、前回の横線が下に残ってしまうため
ws.Range(ws.Cells(MONTH_ROW, FIRST_COL), _
ws.Cells(lastRow, lastCol)).Borders.LineStyle = xlNone
ws.Range(ws.Cells(WEEK_ROW, 2), ws.Cells(lastRow, 4)).Borders.LineStyle = xlNone
ws.Range(ws.Cells(FIRST_ROW, 2), ws.Cells(lastRow, 4)).ClearContents
' 行の高さも標準に戻す。作業が減ったとき、前回の高さがそのまま残るため
ws.Rows(FIRST_ROW & ":" & lastRow).UseStandardHeight = True
End Sub
'==== 縦線を1本引く ====
Private Sub VLine(ws As Worksheet, col As Long, lastRow As Long, lineColor As Long)
With ws.Range(ws.Cells(MONTH_ROW, col), ws.Cells(lastRow, col)).Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.Color = lineColor
.Weight = xlThin
End With
End Sub
'==== マス目・見出しの下線・作業の区切り線(全体工程・詳細工程で共通)====
Private Sub GridBorders(ws As Worksheet, jobCount As Long, lastCol As Long)
Dim lastRow As Long, k As Long, n As Long, sepRow As Long
Dim edges As Variant
lastRow = FIRST_ROW + jobCount * ROWS_PER_JOB - 1
' 1) まず全体に細いマス目を敷く
' 消す範囲と引く範囲を必ず同じにする。ここを FIRST_ROW から始めると、
' 見出しの行の罫線を消したまま引き直さないことになる
edges = Array(xlEdgeLeft, xlEdgeTop, xlEdgeBottom, xlEdgeRight, _
xlInsideVertical, xlInsideHorizontal)
For k = 0 To UBound(edges)
With ws.Range(ws.Cells(MONTH_ROW, FIRST_COL), _
ws.Cells(lastRow, lastCol)).Borders(edges(k))
.LineStyle = xlContinuous
.Color = RGB(216, 222, 230)
.Weight = xlHairline
End With
Next k
' 2) 見出しの下に1本(B列から右端まで)
With ws.Range(ws.Cells(WEEK_ROW, 2), _
ws.Cells(WEEK_ROW, lastCol)).Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.Color = RGB(22, 50, 79)
.Weight = xlMedium
End With
' 3) 作業のまとまりごとに1本、少し濃い横線
For n = 1 To jobCount
sepRow = FIRST_ROW + n * ROWS_PER_JOB - 1
With ws.Range(ws.Cells(sepRow, 2), _
ws.Cells(sepRow, lastCol)).Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.Color = RGB(100, 116, 139)
.Weight = xlThin
End With
Next n
End Sub
'==== 全体工程の罫線を引く ====
Private Sub DrawBorders(ws As Worksheet, jobCount As Long, baseDate As Date)
Dim lastRow As Long, i As Long
lastRow = FIRST_ROW + jobCount * ROWS_PER_JOB - 1
GridBorders ws, jobCount, FIRST_COL + DAY_COUNT - 1
' 月曜の左に縦線を入れて、週の区切りを見せる
For i = 0 To DAY_COUNT - 1
If Weekday(baseDate + i, vbMonday) = 1 Then
VLine ws, FIRST_COL + i, lastRow, RGB(148, 163, 184)
End If
Next i
VLine ws, FIRST_COL, lastRow, RGB(100, 116, 139)
End Sub
'==== 列の名前から位置を探す。無ければ 0 を返す ====
Private Function ColIndex(lo As ListObject, colName As String) As Long
Dim k As Long
ColIndex = 0
For k = 1 To lo.ListColumns.Count
If lo.ListColumns(k).Name = colName Then ColIndex = k
Next k
End FunctionListObjects(TABLE_NAME)- シート上のテーブルを掴みます。VBAからテーブルを扱うオブジェクトが
ListObjectです(Microsoft Learnのリファレンス)
- シート上のテーブルを掴みます。VBAからテーブルを扱うオブジェクトが
ListColumns("開始日").DataBodyRange.Cells(n)- 「開始日」列の n番目のデータを取る書き方です。見出し行を除いた本体だけを指すのが
DataBodyRangeです
- 「開始日」列の n番目のデータを取る書き方です。見出し行を除いた本体だけを指すのが
Interior.Color = RGB(15,118,110)- セルの塗りつぶしです。色は赤・緑・青を0〜255で指定します(Interior.Color の公式リファレンス)
DateDiff("d", baseDate, sDate)- 起点の日から何日ぶん右にずらすかを出しています。日付の引き算でも動きますが、こう書いたほうが意図が読めます
ScreenUpdating = False- 描画中の画面のちらつきを止めます。これを入れるかどうかで体感速度がまったく違います
NumberFormat = "@"- 月のラベルを入れる行を、先に「文字列」にしています。これが無いと、日本語のExcelは
2026年9月を日付として取り込んでしまい、列幅が狭いセルが#だけの表示になります
- 月のラベルを入れる行を、先に「文字列」にしています。これが無いと、日本語のExcelは
Borders(xlEdgeBottom)- 罫線です。セルの内側に細いマス目を敷いたあと、作業のまとまりごとに1本だけ濃い横線を重ねています(Border オブジェクトの公式リファレンス)
Cells(行, 列) の書き方でつまずいた場合は、RangeとCellsの使い分けを先に読んでおくと、このコードが全部読めるようになります。
そしてここが実務で効くポイントです。ClearGantt が消しているのは、VBAで塗った色と、書き込んだ値と、引いた罫線だけです。前の章で設定した条件付き書式の土日の色は消えません。
罫線を毎回引き直しているのには理由があります。作業が1件増えれば、区切りの横線を引く行も3行ずれます。手で引いた線は、作業を足した瞬間に表とずれたまま残ります。色と同じで、線も描く側に任せてしまったほうが楽でした。
ここで、前の章で作った土日の条件付き書式をどうするかという話になります。
私はVBAに移行したとき、条件付き書式を全部消しました。
理由は、条件付き書式はVBAで塗った色より優先されるからです。土日の条件付き書式を残したままバーを描くと、せっかく引いたバーが土日のところだけ消えて見えます。どちらが上に出るのかを毎回考えるのが面倒でした。
だから表示を決める場所を1つにしました。先にVBAで土日を一気に塗って、その上からバーを重ねる。コードの3)と4)がその順番です。
見え方はこうなります。
- 土日に作業が入っていなければ、グレーのまま
- 土日に作業がかかっていれば、バーの色がそのまま横に抜ける

マクロなしでいくなら全部条件付き書式、VBAでいくなら全部VBA。混ぜないほうが、あとで悩みません。
その日の詳細工程を、時間で出す

全体工程は日の単位です。ところが現場で朝いちばんに見たいのは、その日1日をどう回すかでした。何時から何が動くのか、どこが重なるのか。これは日の単位の表では出せません。
そこで、日付を選ぶとその日だけを時間で並べるシートを持たせていました。全体工程とは別のシートにしてください。同じシートに混ぜると、列の意味が「日」と「時間」で入れ替わってしまい、どちらも読みにくくなります。
ここで1つ、実際に作ってみないと気づかなかったことがあります。時間の範囲は0時から24時まで必要でした。
日勤の時間帯は8時から17時なので、最初はその範囲だけで作りました。ところが定時より早い時間から動いている作業があるので、それが表に載りません。結局0時から24時まで全部並べて、8時より前と17時より後、それと12時の昼休みをグレーで薄く塗る形に落ち着きました。「ここは人がいない時間ですよ」という意味です。
作業リストに開始時間と終了時間の列を足しておく理由もここです。日をまたぐ作業を、その日の中でどこからどこまでにするかを決めるのに要ります。
| その日が | 開始 | 終了 |
|---|---|---|
| 作業の初日 | 開始時間から | 24時まで |
| 途中の日 | 0時から | 24時まで |
| 最終日 | 0時から | 終了時間まで |
| 当日で終わる | 開始時間から | 終了時間まで |
4パターンありますが、コードにすると2行です。
If Int(sVal) = theDate Then sh = Hour(sVal) Else sh = 0
If Int(eVal) = theDate Then eh = Hour(eVal) Else eh = HOUR_COUNT「その日が初日なら開始時間から、そうでなければ0時から」「その日が最終日なら終了時間まで、そうでなければ24時まで」。これだけで上の表が全部そろいます。Int は日付から時刻を切り落として、日だけにする関数です。
もう1つ、順番に気をつけてください。その日にかかる作業を、塗る前に数えます。本数が決まらないと、グレーの帯をどこまで下ろすかも、罫線をどこまで引くかも決められません。
コードはこうです。ここまでのコードの下に、そのまま貼り足してください。
クリックしてすべてのコードを見る
'==== 作業の開始・終了を「日付+時間」の1つの値にして返す ====
Private Function JobStart(lo As ListObject, n As Long) As Date
JobStart = Int(lo.ListColumns("開始日").DataBodyRange.Cells(n).Value) + _
CDbl(lo.ListColumns("開始時間").DataBodyRange.Cells(n).Value)
End Function
Private Function JobEnd(lo As ListObject, n As Long) As Date
JobEnd = Int(lo.ListColumns("終了日").DataBodyRange.Cells(n).Value) + _
CDbl(lo.ListColumns("終了時間").DataBodyRange.Cells(n).Value)
End Function
'==== その日の詳細工程を描く ====
Public Sub DrawDaily()
Dim ws As Worksheet
Dim lo As ListObject
Dim theDate As Date
Dim i As Long, n As Long, k As Long, cnt As Long
Dim lastRow As Long
Dim titleRow As Long, barRow As Long
Dim sVal As Date, eVal As Date
Dim sh As Long, eh As Long
Dim idx() As Long
Dim memoCol As Long
Dim memo As String
Set ws = ThisWorkbook.Worksheets(DAILY_SHEET)
Set lo = ThisWorkbook.Worksheets(LIST_SHEET).ListObjects(TABLE_NAME)
' 時間の列が無いと日をまたぐ判定ができないので、ここで止める
If ColIndex(lo, "開始時間") = 0 Or ColIndex(lo, "終了時間") = 0 Then
MsgBox "作業リストに「開始時間」「終了時間」の列がありません。", vbExclamation
Exit Sub
End If
theDate = Int(ws.Range(DAILY_CELL).Value)
memoCol = ColIndex(lo, "備考")
Application.ScreenUpdating = False
ClearArea ws, HOUR_COUNT
' 1) その日にかかっている作業を先に数える。
' 行の数が決まらないと、塗る範囲も罫線の範囲も決められない
ReDim idx(1 To lo.ListRows.Count)
cnt = 0
For n = 1 To lo.ListRows.Count
If Int(JobStart(lo, n)) <= theDate And Int(JobEnd(lo, n)) >= theDate Then
cnt = cnt + 1
idx(cnt) = n
End If
Next n
lastRow = FIRST_ROW + cnt * ROWS_PER_JOB - 1
If lastRow < FIRST_ROW Then lastRow = FIRST_ROW
' 2) 時刻の目盛りを並べる。日付は B2 に出ているので、ここには書かない
' (同じ日付が2か所に出ると、どちらを直せばいいのか分からなくなる)
ws.Range(DAILY_CELL).NumberFormatLocal = "yyyy/m/d(aaa)"
For i = 0 To HOUR_COUNT - 1
ws.Cells(HEAD_ROW, FIRST_COL + i).Value = i
Next i
' 3) 人がいない時間を先に塗る。8時より前・17時以降・昼休み
For i = 0 To HOUR_COUNT - 1
If i < WORK_START Or i >= WORK_END Or i = LUNCH_HOUR Then
ws.Range(ws.Cells(WEEK_ROW, FIRST_COL + i), _
ws.Cells(lastRow, FIRST_COL + i)).Interior.Color = RGB(231, 226, 216)
End If
Next i
' 4) その上にバーを重ねる
For k = 1 To cnt
n = idx(k)
titleRow = FIRST_ROW + (k - 1) * ROWS_PER_JOB
barRow = titleRow + 1
ws.Rows(titleRow).RowHeight = H_TITLE
ws.Rows(barRow).RowHeight = H_BAR
ws.Rows(barRow + 1).RowHeight = H_GAP
sVal = JobStart(lo, n)
eVal = JobEnd(lo, n)
ws.Cells(titleRow, 2).Value = _
lo.ListColumns("作業内容").DataBodyRange.Cells(n).Value
' その日の中での開始と終了。前の日から続いていれば0時から、
' 次の日へ続くなら24時まで。ここが日をまたぐ作業の扱いになる
If Int(sVal) = theDate Then sh = Hour(sVal) Else sh = 0
If Int(eVal) = theDate Then eh = Hour(eVal) Else eh = HOUR_COUNT
' 24:00 は時刻として解釈されると日付に化けて #### になる。文字で入れる
ws.Range(ws.Cells(titleRow, 3), ws.Cells(titleRow, 4)).NumberFormat = "@"
ws.Cells(titleRow, 3).Value = sh & ":00"
ws.Cells(titleRow, 4).Value = eh & ":00"
If eh > sh Then
ws.Range(ws.Cells(barRow, FIRST_COL + sh), _
ws.Cells(barRow, FIRST_COL + eh - 1)).Interior.Color = RGB(15, 118, 110)
If memoCol > 0 Then
memo = CStr(lo.ListColumns(memoCol).DataBodyRange.Cells(n).Value)
If Len(memo) > 0 Then
With ws.Cells(titleRow, FIRST_COL + sh)
.Value = memo
.HorizontalAlignment = xlLeft
End With
End If
End If
End If
Next k
DrawDailyBorders ws, cnt
Application.ScreenUpdating = True
If cnt = 0 Then
MsgBox Format$(theDate, "m月d日") & " にかかる作業はありません。", vbInformation
End If
End Sub
'==== 詳細工程の罫線を引く ====
Private Sub DrawDailyBorders(ws As Worksheet, jobCount As Long)
Dim lastRow As Long
If jobCount = 0 Then Exit Sub
lastRow = FIRST_ROW + jobCount * ROWS_PER_JOB - 1
GridBorders ws, jobCount, FIRST_COL + HOUR_COUNT - 1
' 日勤の始まりと終わりに縦線。全体工程の月曜の線と同じ役目
VLine ws, FIRST_COL + WORK_START, lastRow, RGB(148, 163, 184)
VLine ws, FIRST_COL + WORK_END, lastRow, RGB(148, 163, 184)
VLine ws, FIRST_COL, lastRow, RGB(100, 116, 139)
End Sub日付を選ぶドロップダウンは、全体工程と同じ作り方です。空いているところに日付を並べて、入力規則の「リスト」でその範囲を指定します。並べ終わったら、その列は非表示にしてください。ドロップダウンは元の列を隠したままでも動きます。表の横に日付が延々と並んでいると、それだけで読みにくくなります。
日付そのものは、選ぶセルに1回出れば十分です。同じ日付を見出しにもう一度書かないでください。2か所に出ると、どちらを直せばいいのか分からなくなります。曜日も見たいなら、選ぶセルの表示形式を yyyy/m/d(aaa) にすれば1か所で済みます。
できた工程表をExcelとPDFで書き出す

ここまでで、全体工程と詳細工程の2枚ができました。出力はExcelとPDFの両方を出せるようにしていました。理由は渡す相手によって欲しい形が違うからです。
- Excelでほしいという人がいる。自分なりに加工したいからです
- 私が作った元の版は、変えられたくない。だから確定版はPDFで置きます
社内で共有していたのはPDFのほうでした。この2つを毎回手で作り分けるのが面倒だったので、ボタン1つで両方出す形にしました。
詳細工程のほうを書き出したいときは、SHEET_NAME を DAILY_SHEET に変えるだけです。
クリックしてすべてのコードを見る
Public Sub ExportGantt()
' 出力先(初期値。変えたいときはここを直す)
' 定数をSubの外に置くならモジュールの先頭にまとめること。
' Subより下に書くとコンパイルエラーになるので、ここではSubの中に置いている
Const EXPORT_DIR As String = "C:\Users\Public\工程表\"
Dim ws As Worksheet
Dim baseName As String
Dim outDir As String
Set ws = ThisWorkbook.Worksheets(SHEET_NAME)
outDir = EXPORT_DIR
' 末尾の \ を忘れると、フォルダ名がファイル名の頭にくっついて
' 1つ上のフォルダに出てしまう。忘れても動くようにここで補う
If Right$(outDir, 1) <> "\" Then outDir = outDir & "\"
If Dir(outDir, vbDirectory) = "" Then MkDir outDir
baseName = Format$(Date, "yyyymmdd") & "_全体工程"
' PDFで出す
ws.ExportAsFixedFormat _
Type:=xlTypePDF, _
FileName:=outDir & baseName & ".pdf", _
Quality:=xlQualityStandard, _
IgnorePrintAreas:=False, _
OpenAfterPublish:=False
' Excelで出す(このシートだけ別ブックにして保存)
ws.Copy
Application.DisplayAlerts = False
ActiveWorkbook.SaveAs _
FileName:=outDir & baseName & ".xlsx", _
FileFormat:=xlOpenXMLWorkbook
ActiveWorkbook.Close SaveChanges:=False
Application.DisplayAlerts = True
MsgBox "書き出しました。" & vbCrLf & outDir, vbInformation
End SubPDF出力は ExportAsFixedFormat で、Type:=xlTypePDF を指定します。IgnorePrintAreas:=False にしておくと、シートに設定した印刷範囲がそのまま効きます(Microsoft Learnの ExportAsFixedFormat)。
Excel側の保存で使っている SaveAs は、指定する形式を間違えるとマクロが消えたり、上書き確認のダイアログで処理が止まったりします。そのあたりはWorkbooks.SaveAsの使い方にまとめてあります。
出力先のフォルダは、コードの中に定数として書いていました。変えたくなったらコードを直す、という運用です。乱暴に見えますが、触るのが自分しかいなかったのでこれで足りていました。
出力先のフォルダは、コードの中に定数として書いていました。変えたくなったらコードを直す、という運用です。乱暴に見えますが、触るのが自分しかいなかったのでこれで足りていました。
ただ実際には「今回だけ別の場所に出したい」が起きるので、フォルダを選べる形も付けています。
'==== 保存先をその場で選びたいとき ====
Public Function AskFolder(defaultPath As String) As String
With Application.FileDialog(msoFileDialogFolderPicker)
.Title = "書き出し先のフォルダを選んでください"
.InitialFileName = defaultPath
If .Show = -1 Then
AskFolder = .SelectedItems(1) & "\"
Else
AskFolder = defaultPath ' キャンセルなら初期値のまま
End If
End With
End Function自分しか使わないなら定数で十分。誰かに渡すならダイアログを付ける。この線引きだけ先に決めておくと、あとで作り直さずに済みます。
FileDialog は初期フォルダの指定やファイル種類の絞り込みもできます。使い方はVBA FileDialogの使い方にまとめています。
8年使って分かった、自動化できなかったこと
ここからが、この記事でいちばん書きたかったところです。
ボタン1つで工程表が出るようになって、作る時間は確かに消えました。でも8年やって残ったものがあります。
自動化できたのは「描く」ところだけだった
私が作ったVBA版には、進捗率がありません。何パーセント終わったかを書く欄がないし、色も変わりません。
実績を記録する機能もありません。予定どおりに終わったのか、2日延びたのか、それを残す場所を作りませんでした。イナズマ線(進んでいる・遅れているを折れ線で見せるやり方)も入っていません。
できたのは、とりあえず線を引いて「こういう順番でやるんだね」が分かるところまでです。
これは技術的にできなかったわけではありません。作らなかったんです。工事が動いているあいだは翌日の打ち合わせと資料づくりで手一杯で、実績まで手が回らない。実績は未来のために使うものなので、目先は困らない。だから後回しになって、そのまま残らない。
工程に関わっていたのは4人でしたが、実績をきちんと残していたのは1人だけでした。私が残し始めたのは去年からです。8年やって、去年からです。
正直に書きますが、この部分は道具を変えても完全には消えません。自動化できるのは手の動きであって、「終わったあとに入力する」という行為そのものではないからです。ここを「ツールを入れれば解決します」と書いている記事があったら、私は少し疑います。
幅と紙の制約は、最後まで解決しなかった
もう1つ、Excelの構造的な問題です。
工程表を横1枚に収めようとすると、日数が多いほど1列が細くなります。細くなると、バーの中に書いた作業名が読めなくなります。読めるようにすると今度は1枚に収まりません。
対処として無理やりA3にしますが、それ自体が大変です。逆に日数が少なすぎると1列が大きすぎて、スカスカで見栄えが悪い。
期間の長さが、表の読みやすさを決めてしまう。作り手の腕ではどうにもならない部分です。
紙の話も残ります。共有はデータで回していても、紙で持っておきたいという人は必ずいます。そうすると「1枚に収まっていること」が要件になります。横を1枚に収めると縦がスカスカになるので、印刷プレビューを見ながら毎回合わせていました。私は結局、基本はA4に何とか収めて、無理ならA3に諦めて全部A3用に合わせるという運用にしていました。
ちなみにExcelのシートは1,048,576行 × 16,384列まで使えます。容量が足りないわけではありません。足りないのは紙のほうです。
バージョンは増える。増えても困らない置き方にする
これは私も予想が外れたところなので、正直に書きます。
工程は変わるので、直すたびに新しい版が出ます。バージョンはどんどん増えます。ボタン1つで書き出せるようになると、なおさら出しやすくなります。
「自動化したせいで管理が破綻したのでは」と思われそうですが、そうはなりませんでした。理由は単純で、置き場所を1か所に決めていたからです。共有のサーバーに置いて、「最新はここにあります。いちばん新しい番号のものを見てください」で回していました。
版が増えること自体は、実は問題ではありませんでした。問題になるのは手元にコピーが渡ったときです。紙に印刷した瞬間、その紙は更新されません。「これ最新じゃなくないですか」というやりとりは、データではなく紙で起きていました。
ついでに書くと、その日の時間割のほうは、あってもバージョン2か3程度でした。1日分しかないので、そもそも直す回数が少ないんです。版が荒れるのは全体工程のほうだけでした。
だから、対策は「バージョンを増やさない」ではありません。
- 置き場所を1つに決める(増えてもいいので、探す場所を分散させない)
- 番号は日付ではなく通し番号にする(同じ日に2回直すことがあるため)
- 紙に出したものは、その場限りと割り切る
これだけで、実務上は困らなくなりました。
ここまでが、Excelでできることの全部です。私の場合はこの形で4年やって、それから工程表アプリを自作しました。「Excelでこれ以上やっても、幅と紙の問題は消えないな」と思ったのが理由です。
もし同じところで詰まっているなら、まずこの記事の形まで作ってみてください。それで足りるなら、それがいちばん安上がりです。足りなかったときに初めて、Excelの外を考えれば十分だと思います。
Schedika(スケディカ)
私がその後に作ったのが、HTMLファイルをChromeやEdgeで開くだけで動くガントチャートアプリです。インストールも通信も要らないので、クラウドが使えない職場を前提にしています。
- 向いている人:毎週・毎月ダイヤを引き直す人/会議中にその場で工程を組み替えたい人/ソフトのインストールに申請が要る職場の人
- 向いていない人:工程表を作るのが年に数回の人(この記事の形で足ります)/前の作業がずれた分だけ後ろを自動でずらす機能が要る人(この機能はありません)
- 無料でできる代替:この記事の条件付き書式とVBAで、作る手間はかなり減らせます。まずそちらを試してからで大丈夫です
Schedika でできること・できないこと
解決できること
行のドラッグでの並べ替え、バーのドラッグでの移動と伸縮ができます。Excelでいちばん重かった「3行1セットをコピーしてサイズを合わせ直す」がなくなります。親・子・孫の3階層まで持てて、折りたたみもできます。
できないこと
前の作業がずれた分だけ後ろを自動でずらす機能はありません。進捗率の自動計算もありません。実績は、この記事に書いたとおり、どんな道具を使っても自動では残りません。
動作条件
HTMLファイルをChromeまたはEdgeで開くだけで動きます。インストール不要・通信不要。会社PCで管理者権限が要る場面はありません(2026年8月時点)。
無料版(Lite)の範囲
1案件・タスク100件まで。動かせない日を示すピンは20本まで。PNG出力と印刷には透かしが入ります。制限なしのStandardは買い切り2,980円です(2026年8月時点)。
無料・手動でできる代替
この記事の条件付き書式とVBAで、作る手間はかなり減らせます。年に数回しか作らないなら、そちらで十分です。
よくある質問とまとめ
- コピペで使える条件付き書式の数式 … 土日
=WEEKDAY(E$3,2)>=6/ バー=AND($C5<>"",E$3>=$C5,E$3<=$D5) - テーブルからバーを描くVBAコード … 全部消して描き直す形。設定は先頭の定数だけ変える
- 判断のものさし … 年数回ならマクロなし。毎週作り直すならVBA。幅と紙で詰まったらExcelの外
まとめ

- 「工程表の自動作成」と呼ばれるものの大半は、マクロなしで作れる。
- 開始日と終了日を打てばバーが出るところまでは関数と条件付き書式で足りる
- 毎回作り直すならVBA。
- テーブルを作業リストにして、ボタンで全部消して描き直す形が扱いやすい
- 条件付き書式で足りるものは、コードにしない。
- 土日は条件付き書式のまま残していい
- 自動化できるのは「描く」ところまで。
- 進捗率と実績の記録は、どちらの方法でも自動にはならない
- 幅と紙の制約はExcelの構造の問題。
- 作り手の腕では消えない
- バージョンは増える。
- 増やさない努力より、置き場所を1つに決めるほうが効く
私はこの形にたどり着くまでに、手で塗る3年と、試行錯誤の1年を使いました。同じ遠回りをする必要はありません。まずは土日の条件付き書式だけでも入れてみてください。あれが消えるだけで、だいぶ違います。
| いまの状態 | 次に見るもの |
|---|---|
| コードが読めなかった | VBA(マクロ)入門ガイド。ここからで大丈夫です |
| コードを書かずに済ませたい | Schedika Lite を無料で試す(PR・自社製品) |
| 制限なしで使いたい | Schedika Standard の販売ページ(PR・自社製品/買い切り2,980円・2026年8月時点) |

