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

検証環境: Microsoft 365(ビジネス)/ Windows 11 / 確認日 2026年8月9日
※本記事の情報は 2026年8月時点 のものです。最新情報は公式ドキュメントをご確認ください。
数式もVBAも書かずに、工程表を引き直す
工程表を引き直すたびに、行をコピーしてバーの長さを合わせ直す。あの作業をなくすために Schedika を作りました。手元のPCだけで動きます。クラウドが使えない職場を前提にしています。
Schedika のページを見る →無料で試せる版があります。この記事を読むだけでも工程表は作れます。
マクロなしで、バーが自動で出るところまで作る
先に結論を書くと、検索して出てくる「工程表の自動作成」の多くは、開始日と終了日からバーを出すところまでです。そこまでならマクロは要りません。
必要なのは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版では、この線もボタンを押すたびにマクロが引き直します。

ここから3つの手順に分けて作ります。このあとの3枚の画面は、仕組みが見えるように1作業1行の簡単な形で撮っています。3行1セットに広げるのは最後に数式を1か所足すだけなので、先に仕組みのほうを押さえてください。
日付の行は「開始日+1」で作る

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

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

私は最初、土日を手で塗っていました。前年の工程表をコピーすると土日の位置が変わるので、そのたびに塗り直しです。行を1本足すたびにまた塗り直しでした。
これは条件付き書式に置き換えられます。Excelで最初にやめるべき手作業がこれです。
E5から表の右下までを選択して、ホームタブの「条件付き書式」→「新しいルール」→「数式を使用して、書式設定するセルを決定」を選びます。数式欄にこう入れます。
=WEEKDAY(E$3,2)>=6WEEKDAY は日付が何曜日かを数字で返す関数です。第2引数に 2 を指定すると、月曜が1、日曜が7になります。つまり 6以上なら土日です。この対応はMicrosoftサポートの WEEKDAY 関数のページに一覧があります。
E$3 の $ は行だけを固定する意味です。これを付けないと、下の行に行くほど参照する日付がずれていきます。条件付き書式でいちばん多いつまずきがここです。
同じルールを曜日の行(4行目)にも入れておくと、土日の帯が見出しまで通って読みやすくなります。ただし月の行(2行目)には入れないでください。月のラベルは月初の列にだけ文字を置いて、右隣の空セルへはみ出させて表示しています。その途中に色が入ると、文字の下だけ帯が割り込んだように見えます。日付の行(3行目)も、背景を濃い色にしているなら入れません。土日だけ地の色が変わって、見出しが途切れて見えます。
なお条件付き書式の数式は、等号で始めてTRUEかFALSEを返す形でなければ動きません。またMicrosoftサポートの条件式の解説によると、別のブックへの外部参照は条件付き書式には使えません。祝日リストを別ファイルに置きたくなりますが、同じブックの中に置いてください。
祝日も塗りたい場合は、どこかのシートに祝日の日付を並べて名前を付け(ここでは 祝日 とします)、2つ目のルールとして追加します。
=COUNTIF(祝日,E$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作業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つだけ気をつけてください。入力規則で指定する範囲は、実際に日付が入っている行数ぴったりにします。空のセルまで範囲に入れると、ドロップダウンに空行が並びます。候補を増やすときは、数式を下へコピーしてから範囲も広げてください。なお作業リストのテーブルは下へ伸びるので、候補は右か、別のシートに置いておくとぶつかりません。
コードはこうなります。標準モジュールを2つ作って貼ってください。VBAをまだ触ったことがない場合は、先にVBAの始め方でエディタの開き方と標準モジュールの作り方を確認しておくと迷いません。
この先のコードは、説明のために区切った抜粋です。そのまま動かしたい場合は、記事の最後にある貼り付け用の完成版を2つ貼れば終わります。モジュールは2つに分けます。分ける線は「最初に一度だけ触るもの」と「ずっと動き続けるもの」です。工程表設定には設定の値とシートを作る初期設定が入り、触るのはこちらだけ。工程表マクロは描くところと書き出すところで、開かなくて構いません。
こう分けておくと、渡したあとが楽になります。設定を頼まれた人は工程表設定を開けば済みますし、こちらがコードを直して配り直すときも、工程表マクロだけ入れ替えてもらえば相手の設定はそのまま残ります。
この記事のコードは6つに分けて出てきます。貼り先は、工程表設定が①設定の値と⑥土台づくり(記事のいちばん最後)、工程表マクロが②全体工程・③詳細工程・④書き出し・⑤フォルダを選ぶ、です。
モジュールの名前は、左のプロパティウィンドウの (Name) で変えられます。名前が違っても動きます。
まず1つ目、工程表設定です。
クリックして全てのコードを見る
Option Explicit
'============================================================
' 工程表設定
'
' 最初に一度だけ触るところです。
'
' ① 下の設定を、自分の表に合わせる(変えなくても動きます)
' ② SetupBook を1回実行すると、シートとボタンができあがります
'
' もう1つの「工程表マクロ」は、描くところと書き出すところだけです。
' 開かなくて構いません。
'============================================================
'---- ① シートとテーブルの名前 ----
Public Const SHEET_NAME As String = "全体工程" ' ガントチャートを描くシート
Public Const DAILY_SHEET As String = "詳細工程" ' その日の時間割を描くシート
Public Const LIST_SHEET As String = "作業リスト" ' 作業リストのテーブルがあるシート
Public Const TABLE_NAME As String = "作業リスト" ' テーブルの名前
'---- ② 表のどこに何があるか ----
Public Const START_CELL As String = "B2" ' 全体工程で、表示開始日を選ぶセル
Public Const DAILY_CELL As String = "B2" ' 詳細工程で、表示する日を選ぶセル
Public Const MONTH_ROW As Long = 2 ' 月のラベルを出す行
Public Const HEAD_ROW As Long = 3 ' 日付を並べる行
Public Const WEEK_ROW As Long = 4 ' 曜日の行(=E3 の数式が入っている)
Public Const FIRST_ROW As Long = 5 ' 1件目のタイトル行
Public Const FIRST_COL As Long = 5 ' E列からカレンダーを描く
'---- ③ どれだけ出すか ----
Public Const DAY_COUNT As Long = 60 ' 全体工程を横に何日ぶん出すか
Public Const HOUR_COUNT As Long = 24 ' 詳細工程を0時から何時間ぶん出すか
'---- ④ 見た目(1作業ぶんの高さ)----
Public Const ROWS_PER_JOB As Long = 3 ' タイトル行+バー行+隙間行
Public Const H_TITLE As Double = 17 ' タイトル行の高さ
Public Const H_BAR As Double = 13 ' バーの行の高さ(細いほど締まって見える)
Public Const H_GAP As Double = 5 ' すきまの行の高さ
'---- ⑤ 勤務時間(詳細工程で、人がいない時間を塗るため)----
Public Const WORK_START As Long = 8 ' 日勤の始まり
Public Const WORK_END As Long = 17 ' 日勤の終わり
Public Const LUNCH_HOUR As Long = 12 ' 昼休み
'---- ⑥ 書き出し先。無ければ作ります ----
Public Const EXPORT_DIR As String = "C:\Users\Public\工程表\"
'---- ⑦ 最初に入れておくサンプルの分量(土台づくりだけが使う)----
Public Const JOB_COUNT As Long = 9 ' サンプルの作業の数
Public Const PICK_WEEKS As Long = 9 ' 全体工程のドロップダウンに出す週の数
Public Const PICK_DAYS As Long = 33 ' 詳細工程のドロップダウンに出す日数
'---- 土台づくりが「何をしたか」を覚えておく箱。あとでまとめて報告する ----
Private mMade As String ' 作ったもの
Private mKept As String ' もとからあったので触らなかったもの
Private mWarn As String ' こちらでは判断できなかったもの①と②だけ自分の表に合わせれば、あとはそのままで動きます。まず動かしてみるだけなら、どこも変えなくて大丈夫です。なおこのモジュールには、記事の最後にもう1つコードを足します。
2つ目が工程表マクロです。ここから⑤までのコードは、すべてこちらのモジュールに入ります。
クリックして全てのコードを見る
Option Explicit
'============================================================
' 工程表マクロ
'
' 描くところと、書き出すところだけが入っています。
' 触らなくて構いません。
' 設定と初期設定は「工程表設定」にまとめてあります。
'============================================================
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
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月" を
' 日付だと解釈してしまい、列幅が狭いと ### になるため先に表示形式を文字列にする。
' 書式もここで毎回そろえる。最初の1つだけ手で色を付けたブックだと、
' 月が変わって出てくる2つ目以降が既定の黒のまま残ってしまうため
With ws.Range(ws.Cells(MONTH_ROW, FIRST_COL), _
ws.Cells(MONTH_ROW, FIRST_COL + DAY_COUNT - 1))
.NumberFormat = "@"
.Font.Bold = True
.Font.Size = 9
.Font.Color = RGB(22, 50, 79)
.HorizontalAlignment = xlLeft
End With
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 = MonthLabel(baseDate, i)
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
'==== 月のラベル。次の月初までに何列空いているかで、入る長さを選ぶ ====
' 列幅は3文字ぶんしかないので、月初のすぐ手前から表を始めると
' "2026年9月" が右へはみ出しきれず "2026年" で切れてしまう
Private Function MonthLabel(baseDate As Date, i As Long) As String
Dim gap As Long
' 次にラベルが出る列(次の月の1日)まで、何列あるかを数える
gap = 1
Do While i + gap <= DAY_COUNT - 1
If Day(baseDate + i + gap) = 1 Then Exit Do
gap = gap + 1
Loop
If gap >= 4 Then
MonthLabel = Format$(baseDate + i, "yyyy年m月")
Else
MonthLabel = Format$(baseDate + i, "m月")
End If
End Function
'==== 前回描いた内容を消す(全体工程・詳細工程で共通)====
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 Functionつまずきやすいところを分解します。
ListObjects(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 = "@zangyo-free月のラベルを入れる行を、先に「文字列」にしています。これが無いと、日本語のExcelは2026年9月を日付として取り込んでしまい、列幅が狭いセルが#だけの表示になります- 月のラベルは、色と太さも毎回ここで指定し直しています
- 指定しないと、そのセルに前から付いていた書式がそのまま出ます。1つ目だけ手で色を付けたブックだと、月が変わって2つ目が出てきたときだけ既定の黒になり、揃いません
MonthLabel- ラベルの長さを決めるだけの関数です。列幅は3文字ぶんしかないので、
2026年9月は右の空きセルへはみ出して表示しています。月初のすぐ手前から表を始めると、はみ出す先が足りずに2026年で切れます。次の月初まで4列に満たないときは9月の形にして、切れないようにしています
- ラベルの長さを決めるだけの関数です。列幅は3文字ぶんしかないので、
Borders(xlEdgeBottom)- 罫線です。セルの内側に細いマス目を敷いたあと、作業のまとまりごとに1本だけ濃い横線を重ねています(Border オブジェクトの公式リファレンス)
Cells(行, 列) の書き方でつまずいた場合は、RangeとCellsの使い分けを先に読んでおくと、このコードが全部読めるようになります。
そしてここが実務で効くポイントです。ClearArea が消しているのは、VBAで塗った色と、書き込んだ値と、引いた罫線だけです。前の章で設定した条件付き書式の土日の色は消えません。
罫線を毎回引き直しているのには理由があります。作業が1件増えれば、区切りの横線を引く行も3行ずれます。手で引いた線は、作業を足した瞬間に表とずれたまま残ります。色と同じで、線も描く側に任せてしまったほうが楽でした。
ここで、前の章で作った土日の条件付き書式をどうするかという話になります。
私はVBAに移行したとき、条件付き書式を全部消しました。
理由は、条件付き書式はVBAで塗った色より優先されるからです。土日の条件付き書式を残したままバーを描くと、せっかく引いたバーが土日のところだけ消えて見えます。どちらが上に出るのかを毎回考えるのが面倒でした。
だから表示を決める場所を1つにしました。先にVBAで土日を一気に塗って、その上からバーを重ねる。コードの3)と4)がその順番です。
見え方はこうなります。

- 土日に作業が入っていなければ、グレーのまま
- 土日に作業がかかっていれば、バーの色がそのまま横に抜ける
マクロなしでいくなら全部条件付き書式、VBAでいくなら全部VBA。混ぜないほうが、あとで悩みません。
最後に、これをボタンにします。開発タブ → 挿入 → フォームコントロールの「ボタン」を選び、シートの空いているところへドラッグすると、マクロを選ぶ画面が出るので DrawGantt を選びます。ボタンの文字は、右クリックの「テキストの編集」であとから変えられます。開発タブが見当たらない場合は、ファイル → オプション → リボンのユーザー設定で「開発」にチェックを入れると出てきます。

ボタンを作らずに、開発タブ → マクロ から実行しても結果は同じです。ただ日付を変えるたびに実行するものなので、押す場所が表のすぐ近くにあったほうが楽でした。
その日の詳細工程を、時間で出す

全体工程は日の単位です。ところが現場で朝いちばんに見たいのは、その日1日をどう回すかでした。何時から何が動くのか、どこが重なるのか。これは日の単位の表では出せません。
そこで、日付を選ぶとその日だけを時間で並べるシートを持たせていました。全体工程とは別のシートにしてください。同じシートに混ぜると、列の意味が「日」と「時間」で入れ替わってしまい、どちらも読みにくくなります。
ここで1つ、実際に作ってみないと気づかなかったことがあります。時間の範囲は0時から24時まで必要でした。
日勤の時間帯は8時から17時なので、最初はその範囲だけで作りました。ところが定時より早い時間から動いている作業があるので、それが表に載りません。結局0時から24時まで全部並べて、8時より前と17時より後、それと12時の昼休みをグレーで薄く塗る形に落ち着きました。「ここは人がいない時間ですよ」という意味です。
作業リストに開始時間と終了時間の列を足しておく理由もここです。日をまたぐ作業を、その日の中でどこからどこまでにするかを決めるのに要ります。
| その日が | 開始 | 終了 |
|---|---|---|
| 作業の初日 | 開始時間から | 24時まで |
| 途中の日 | 0時から | 24時まで |
| 最終日 | 0時から | 終了時間まで |
| 当日で終わる | 開始時間から | 終了時間まで |
4パターンありますが、コードにすると3行です。
If Int(sVal) = theDate Then sh = Hour(sVal) Else sh = 0
If Int(eVal) = theDate Then eh = Hour(eVal) Else eh = HOUR_COUNT
If eh < HOUR_COUNT And Minute(eVal) > 0 Then eh = eh + 1 ' 端数は切り上げる上の2行が「その日が初日なら開始時間から、そうでなければ0時から」「その日が最終日なら終了時間まで、そうでなければ24時まで」です。これで表の4パターンがそろいます。Int は日付から時刻を切り落として、日だけにする関数です。
3行目は端数の処理です。この表は1時間刻みなので、8時30分のような分は列がありません。Hour は分を切り捨てるので、そのままだと8時30分〜8時45分のように1時間の枠に収まる作業で sh と eh が同じ値になり、バーが1本も描かれません(あとで出てくる If eh > sh Then を通らないためです)。終了だけ切り上げておけば、短い作業でも必ず1マスは出ます。
そのかわり、この作例で表示される時刻は枠の時刻です。8時30分〜9時30分の作業は「8:00〜9:00」と出ます。分まで正確に見せたい表が要るなら、1時間ではなく30分刻みで列を作り直すことになります。私は1時間で足りていたので、そこまではやりませんでした。
もう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
If eh < HOUR_COUNT And Minute(eVal) > 0 Then eh = eh + 1 ' 端数は切り上げる
' 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つで両方出す形にしました。
全体工程と詳細工程は、どちらも同じフォルダに出します。ただしボタンは別々に要ります。
ここで1つ、最初に私がつまずいたところがあります。フォームコントロールのボタンは、引数を持つマクロを呼べません。「シート名を渡して書き出すマクロ」を1本だけ書いても、ボタンからは呼べないということです。
なので、中身は1本、入口だけ2つにします。
クリックして全てのコードを見る
'==== ボタンから呼ぶ入口。全体工程を書き出す ====
Public Sub ExportGantt()
ExportSheet SHEET_NAME
End Sub
'==== ボタンから呼ぶ入口。詳細工程(その日の時間割)を書き出す ====
Public Sub ExportDaily()
ExportSheet DAILY_SHEET
End Sub
'==== 中身。シート名を受け取って、同じフォルダにPDFとExcelで出す ====
Private Sub ExportSheet(sheetName As String)
' 出力先は「工程表設定」の EXPORT_DIR を使う
Dim ws As Worksheet
Dim outWs As Worksheet
Dim baseName As String
Dim outDir As String
Dim ans As VbMsgBoxResult
Dim n As Long
Set ws = ThisWorkbook.Worksheets(sheetName)
outDir = EXPORT_DIR
' 末尾の \ を忘れると、フォルダ名がファイル名の頭にくっついて
' 1つ上のフォルダに出てしまう。忘れても動くようにここで補う
If Right$(outDir, 1) <> "\" Then outDir = outDir & "\"
If Dir(outDir, vbDirectory) = "" Then MkDir outDir
' 受け取ったシート名をそのままファイル名に使う。
' 全体工程なら「20260812_全体工程」、詳細工程なら「20260812_詳細工程」
baseName = Format$(Date, "yyyymmdd") & "_" & sheetName
' 同じ名前がすでにあるかを先に見る。PDFとExcelのどちらか一方でもあれば聞く
If FileExists(outDir & baseName & ".pdf") _
Or FileExists(outDir & baseName & ".xlsx") Then
ans = MsgBox(baseName & " はすでにあります。上書きしますか?" & vbCrLf & vbCrLf & _
"はい … 上書きする" & vbCrLf & _
"いいえ … _2、_3 と番号を付けて別に保存する" & vbCrLf & _
"キャンセル … 書き出しをやめる", _
vbYesNoCancel + vbQuestion, "書き出し")
If ans = vbCancel Then Exit Sub
If ans = vbNo Then
' PDFとExcelの両方が空いている番号まで進める。片方だけで判定すると
' PDFが_2でExcelが_3のようにずれて、どれが同じ書き出しか分からなくなる
n = 2
Do While FileExists(outDir & baseName & "_" & n & ".pdf") _
Or FileExists(outDir & baseName & "_" & n & ".xlsx")
n = n + 1
Loop
baseName = baseName & "_" & n
End If
End If
' このシートだけ別ブックにコピーして、そちらから書き出す
ws.Copy
Set outWs = ActiveWorkbook.Worksheets(1)
' ボタンはコピーにも付いてくる。渡した先にはマクロが無いので押すとエラーになるし、
' 紙にもそのまま出てしまう。コピーのほうから消しておく
Do While outWs.Shapes.Count > 0
outWs.Shapes(1).Delete
Loop
' PDFで出す
outWs.ExportAsFixedFormat _
Type:=xlTypePDF, _
FileName:=outDir & baseName & ".pdf", _
Quality:=xlQualityStandard, _
IgnorePrintAreas:=False, _
OpenAfterPublish:=False
' Excelで出す
' 上書きしていいかは上で聞いたあとなので、Excelにもう一度聞かせない
Application.DisplayAlerts = False
ActiveWorkbook.SaveAs _
FileName:=outDir & baseName & ".xlsx", _
FileFormat:=xlOpenXMLWorkbook
ActiveWorkbook.Close SaveChanges:=False
Application.DisplayAlerts = True
MsgBox "書き出しました。" & vbCrLf & outDir & baseName & ".pdf / .xlsx", vbInformation
End Sub
'==== ファイルがあるかどうかを見るだけの関数 ====
Private Function FileExists(filePath As String) As Boolean
FileExists = (Dir(filePath) <> "")
End Functionボタンは2つ作ります。全体工程のシートに1つ置いて ExportGantt を割り当て、詳細工程のシートにもう1つ置いて ExportDaily を割り当てます。作り方はさきほどと同じです。開発タブ → 挿入 → フォームコントロールの「ボタン」で、割り当てるマクロだけを変えます。
中身の ExportSheet は Private にしてあるので、マクロを選ぶ画面には出てきません。押せる入口が2つだけ並ぶので、間違えにくくなります。
出力先はどちらも同じ EXPORT_DIR です。ファイル名の後ろにシート名が付くので、同じフォルダに 20260812_全体工程.pdf と 20260812_詳細工程.pdf が並びます。
PDFもExcelも、元のシートからではなくコピーから出しています。ここは実際に渡してみて分かったことです。
ws.Copy で作ったコピーには、シートに置いたボタンもそのまま付いてきます。渡した先のブックにはマクロが入っていないので、受け取った人がボタンを押すと「マクロが見つかりません」で止まります。PDFに出せば、ボタンの絵が紙にそのまま印刷されます。どちらも、渡したあとに気づくやつです。
なので、コピーを作った直後に図形を全部消してから書き出します。
Do While outWs.Shapes.Count > 0
outWs.Shapes(1).Delete
LoopFor Each で回さずに Do While にしているのは、消しながら回すと数がずれて消し残るからです。B2のドロップダウンは図形ではなく「データの入力規則」なので、これでは消えません。日付の選択は渡した先でも残ります。
PDF出力は ExportAsFixedFormat で、Type:=xlTypePDF を指定します。IgnorePrintAreas:=False にしておくと、シートに設定した印刷範囲がそのまま効きます(Microsoft Learnの ExportAsFixedFormat)。
Excel側の保存で使っている SaveAs は、指定する形式を間違えるとマクロが消えたり、上書き確認のダイアログで処理が止まったりします。そのあたりはWorkbooks.SaveAsの使い方にまとめてあります。
上書きの扱いは、聞いてから決める形にしました。同じ名前のファイルがすでにあるときだけ、3択が出ます。
| 押したボタン | どうなるか |
|---|---|
| はい | そのまま上書きする |
| いいえ | _2、_3 と番号を付けて別ファイルにする |
| キャンセル | 書き出しをやめる(何も出さずに終わる) |
MsgBox は vbYesNoCancel を渡すと、押されたボタンが戻り値で返ってきます。それを ans で受けて分岐しているだけです。
番号を探すループでPDFとExcelの両方を見ているのには理由があります。片方だけで判定すると、PDFが _2 でExcelが _3 のようにずれて、どれとどれが同じ書き出しなのか分からなくなるからです。
Application.DisplayAlerts = False はそのまま残しています。Microsoft Learn の DisplayAlerts の説明にあるとおり、これを False にすると確認を求められる場面で既定の答えが選ばれます。上書きしていいかは自分で聞いたあとなので、Excelにもう一度聞かせない、という意味です。
逆に言うと、この行だけ残して自分の確認を消すと、同じ日の2回目で1回目の出力が黙って消えます。工程表は同じ日に2回直すことがあるので、ここは省かないでください。
出力先のフォルダは、コードの中に定数として書いていました。変えたくなったらコードを直す、という運用です。乱暴に見えますが、触るのが自分しかいなかったのでこれで足りていました。
ただ実際には「今回だけ別の場所に出したい」が起きるので、フォルダを選べる形も付けていました。
'==== 保存先をその場で選びたいとき ====
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この関数は置いただけでは動きません。使うときは、さきほどの ExportGantt の中の outDir = EXPORT_DIR を、次の1行に差し替えてください。
outDir = AskFolder(EXPORT_DIR)これでボタンを押すたびにフォルダ選択のダイアログが出て、キャンセルすれば EXPORT_DIR に出ます。
自分しか使わないなら定数で十分。誰かに渡すならダイアログを付ける。この線引きだけ先に決めておくと、あとで作り直さずに済みます。
FileDialog は初期フォルダの指定やファイル種類の絞り込みもできます。使い方はVBA FileDialogの使い方にまとめています。
なお、ここまでの土台(シート・列幅・見出しの数式・ボタン)を作るところも含めてマクロにしてあります。新しいブックにゼロから作りたい場合はまっさらなブックに貼るだけで、ここまで作るを見てください。
8年使って分かった、自動化できなかったこと
ここからが、この記事でいちばん書きたかったところです。
ボタン1つで工程表が出るようになって、作る時間は確かに消えました。でも8年やって残ったものがあります。
自動化できたのは「描く」ところだけだった
私が作ったVBA版には、進捗率がありません。何パーセント終わったかを書く欄がないし、色も変わりません。
実績を記録する機能もありません。予定どおりに終わったのか、2日延びたのか、それを残す場所を作りませんでした。イナズマ線(進んでいる・遅れているを折れ線で見せるやり方)も入っていません。
できたのは、とりあえず線を引いて「こういう順番でやるんだね」が分かるところまでです。
これは技術的にできなかったわけではありません。作らなかったんです。工事が動いているあいだは翌日の打ち合わせと資料づくりで手一杯で、実績まで手が回らない。実績は未来のために使うものなので、目先は困らない。だから後回しになって、そのまま残らない。
工程に関わっていたのは4人でしたが、実績をきちんと残していたのは1人だけでした。私が残し始めたのは去年からです。8年やって、去年からです。
正直に書きますが、この部分は道具を変えても完全には消えません。自動化できるのは手の動きであって、「終わったあとに入力する」という行為そのものではないからです。ここを「ツールを入れれば解決します」と書いている記事があったら、私は少し疑います。
幅と紙の制約は、最後まで解決しなかった
もう1つ、紙に収めることから来る問題です。
工程表を横1枚に収めようとすると、日数が多いほど1列が細くなります。細くなると、バーの中に書いた作業名が読めなくなります。読めるようにすると今度は1枚に収まりません。
対処として無理やりA3にしますが、それ自体が大変です。逆に日数が少なすぎると1列が大きすぎて、スカスカで見栄えが悪い。
期間の長さが、表の読みやすさを決めてしまう。1枚に収める前提でいるかぎり、Excelが上手くなっても消えませんでした。
紙の話も残ります。共有はデータで回していても、紙で持っておきたいという人は必ずいます。そうすると「1枚に収まっていること」が要件になります。横を1枚に収めると縦がスカスカになるので、印刷プレビューを見ながら毎回合わせていました。私は結局、基本はA4に何とか収めて、無理ならA3に諦めて全部A3用に合わせるという運用にしていました。
ちなみにExcelのシートは1,048,576行 × 16,384列まで使えます。容量が足りないわけではありません。足りないのは紙のほうです。
バージョンは増える。増えても困らない置き方にする
これは私も予想が外れたところなので、正直に書きます。
工程は変わるので、直すたびに新しい版が出ます。バージョンはどんどん増えます。ボタン1つで書き出せるようになると、なおさら出しやすくなります。
「自動化したせいで管理が破綻したのでは」と思われそうですが、そうはなりませんでした。理由は単純で、置き場所を1か所に決めていたからです。共有のサーバーに置いて、「最新はここにあります。いちばん新しい番号のものを見てください」で回していました。
版が増えること自体は、実は問題ではありませんでした。問題になるのは手元にコピーが渡ったときです。紙に印刷した瞬間、その紙は更新されません。「これ最新じゃなくないですか」というやりとりは、データではなく紙で起きていました。
ついでに書くと、その日の時間割のほうは、あってもバージョン2か3程度でした。1日分しかないので、そもそも直す回数が少ないんです。版が荒れるのは全体工程のほうだけでした。
だから、対策は「バージョンを増やさない」ではありません。
- 置き場所を1つに決める(増えてもいいので、探す場所を分散させない)
- 番号は日付ではなく通し番号にする(同じ日に2回直すことがあるため。書き出しのコードで
_2、_3と付けていたのはこれです) - 紙に出したものは、その場限りと割り切る
これだけで、実務上は困らなくなりました。
ここまでが、私がExcelで実用化した範囲です。この形で4年やって、それから工程表アプリを自作しました。「Excelでこれ以上やっても、幅と紙の問題は消えないな」と思ったのが理由です。
もし同じところで詰まっているなら、まずこの記事の形まで作ってみてください。それで足りるなら、それがいちばん安上がりです。足りなかったときに初めて、Excelの外を考えれば十分だと思います。
Schedika(スケディカ)
私がその後に作ったのが、HTMLファイルをChromeやEdgeで開くだけで動くガントチャートアプリです。インストールも通信も要らないので、クラウドが使えない職場を前提にしています。
- 向いている人:毎週・毎月ダイヤを引き直す人/会議中にその場で工程を組み替えたい人/ソフトのインストールに申請が要る職場の人
- 向いていない人:工程表を作るのが年に数回の人(この記事の形で足ります)
案件数とタスク数の制限を外した版は Schedika Standard(買い切り2,980円・2026年8月時点)です。まずLiteで足りるかを確かめてからで大丈夫です。
| 解決できること | 行のドラッグでの並べ替え、バーのドラッグでの移動と伸縮ができます。Excelでいちばん重かった「3行1セットをコピーしてサイズを合わせ直す」がなくなります。親・子・孫の3階層まで持てて、折りたたみもできます。 |
|---|---|
| できないこと | 前の作業がずれた分だけ後ろを自動でずらす機能はありません。進捗率の自動計算もありません。実績は、この記事に書いたとおり、どんな道具を使っても自動では残りません。 |
| 動作条件 | HTMLファイルをChromeまたはEdgeで開くだけで動きます。インストール不要・通信不要。通常は管理者権限も要りませんが、社内のセキュリティポリシーによってはファイルの実行や保存が制限される場合があります(2026年8月時点)。 |
| 無料版(Lite)の範囲 | 1案件・タスク100件まで。動かせない日を示すピンは20本まで。PNG出力と印刷には透かしが入ります。 |
| 無料・手動でできる代替 | この記事の条件付き書式とVBAで、作る手間はかなり減らせます。年に数回しか作らないなら、そちらで十分です。 |
よくある質問とまとめ
まっさらなブックに貼るだけで、ここまで作る
ここまでのコードは、シートの並びと列の位置が合っていることを前提にしています。全体工程 という名前のシートがあって、E列からカレンダーが始まって、4行目に曜日の数式が入っていて、作業リスト という名前のテーブルがある。どれか1つでも違うと、DrawGantt は最初の行で止まります。
私自身、新しいブックで作り直すたびにこの土台作りをやり直していました。面倒なのは描く処理ではなく、描かせるまでの下ごしらえのほうです。
そこで、下ごしらえだけをするコードを最後に用意しました。新しいブックに、この記事のコードを2つの標準モジュールへ貼り、最後に SetupBook を1回実行すれば、それで使える状態になります。このコードは、1つ目の「工程表設定」モジュールの続きに貼ってください。設定の値と、最初の1回だけ動かす初期設定は、同じところにまとめたいからです。
やっているのは、足りないものを足すことだけです。
| 見るもの | 無いとき | もうあるとき |
|---|---|---|
| 作業リストのシート | サンプルの予定つきで作る | 何もしない |
| テーブル「作業リスト」 | シートはあってもテーブルが無ければ、テーブルだけ作る | 何もしない |
| 全体工程のシート | 見出し・列幅・曜日の数式・印刷設定まで作る | 何もしない |
| 詳細工程のシート | 同じ。ドロップダウンの元になる日付の候補(AD列)も作る | 何もしない |
| 4つのボタン | 足りないものだけ置く | 何もしない |
| B2の日付とドロップダウン | 入れる | 何もしない |
あるものは見るだけで飛ばします。だから2回目以降に実行しても中身は消えませんし、待たされることもありません。途中まで手で作ってあるブックに実行しても構いません。「テーブルだけ作り忘れていた」なら、テーブルだけができます。最後に、何を作って何をそのままにしたかを一覧で出します。
クリックして全てのコードを見る
'============================================================
' ここから下は「土台づくり」。
' まだ何も無いブックに、シート・テーブル・ボタンを作る。
' 足りないものだけを作るので、2回目以降に実行しても中身は消えない。
' SetupBook を実行するのは最初の1回だけでよい。
'============================================================
Public Sub SetupBook()
mMade = ""
mKept = ""
mWarn = ""
' 途中で止まっても画面の更新が止まったままにならないように、
' エラーの行き先を先に決めてから ScreenUpdating を切る
On Error GoTo TROUBLE
Application.ScreenUpdating = False
EnsureList
EnsureGantt
EnsureDaily
Application.ScreenUpdating = True
' 何も作らなかったなら、描き直す必要もない。ここで終われば一瞬で済む
If Len(mMade) = 0 Then
MsgBox "そろっていました。何も変更していません。" & vbCrLf & vbCrLf & _
"もとからあったもの" & vbCrLf & mKept & Warnings, _
vbInformation, "工程表のセットアップ"
Exit Sub
End If
' 土台ができたので、そのまま1回描いて完成形を見せる
DrawGantt
DrawDaily
ThisWorkbook.Worksheets(SHEET_NAME).Activate
MsgBox "できました。" & vbCrLf & vbCrLf & _
"作ったもの" & vbCrLf & mMade & _
IIf(Len(mKept) > 0, vbCrLf & "もとからあったもの" & vbCrLf & mKept, "") & _
Warnings & vbCrLf & _
"このあと、必ず .xlsm(Excel マクロ有効ブック)で保存してください。" & vbCrLf & _
".xlsx で保存すると、貼り付けたコードが消えます。", _
vbInformation, "工程表のセットアップ"
Exit Sub
TROUBLE:
Application.ScreenUpdating = True
MsgBox "途中で止まりました。" & vbCrLf & vbCrLf & _
"エラー " & Err.Number & ":" & Err.Description, _
vbCritical, "工程表のセットアップ"
End Sub
'============================================================
' 作業リスト。シートとテーブルを別々に見る
'============================================================
Private Sub EnsureList()
Dim ws As Worksheet
Dim isNew As Boolean
Set ws = EnsureSheet(LIST_SHEET, isNew)
If isNew Then
BuildList ws
NoteMade LIST_SHEET & " シート(サンプルの予定つき)"
Else
NoteKept LIST_SHEET & " シート"
End If
' シートがあってもテーブルが無ければ、テーブルだけ作る。
' 「工程表マクロ」は列を名前で探すので、テーブルでないと動かない
If TableExists(TABLE_NAME) Then
If Not isNew Then NoteKept "テーブル「" & TABLE_NAME & "」"
ElseIf ws.Range("A1").Value = "作業内容" Then
MakeTable ws
NoteMade "テーブル「" & TABLE_NAME & "」(もとの表をテーブルにしました)"
ElseIf IsEmptySheet(ws) Then
BuildList ws
MakeTable ws
NoteMade LIST_SHEET & " の中身とテーブル「" & TABLE_NAME & "」"
Else
' 人が作った別の表が入っている。上書きするとその人の予定が消えるので触らない
NoteWarn LIST_SHEET & " のA1が「作業内容」ではありません。" & vbCrLf & _
" 見出しを 作業内容/開始日/開始時間/終了日/終了時間/備考 に直してから、" & vbCrLf & _
" もう一度実行してください。"
End If
End Sub
'============================================================
' 全体工程
'============================================================
Private Sub EnsureGantt()
Dim ws As Worksheet
Dim isNew As Boolean
Dim lastCol As Long
Set ws = EnsureSheet(SHEET_NAME, isNew)
lastCol = FIRST_COL + DAY_COUNT - 1
If isNew Then
BuildGantt ws
NoteMade SHEET_NAME & " シート"
Else
NoteKept SHEET_NAME & " シート"
' シートはあるけれど、動くのに要るものが欠けている場合だけ足す。
' 見た目(色・列幅)は直さない。手で整えた人のものを壊さないため
If Not IsDate(ws.Range("B2").Value) Then
ws.Range("B2").Value = SampleStart
NoteMade SHEET_NAME & " の表示開始日(B2)"
End If
If Len(ws.Cells(WEEK_ROW, FIRST_COL).Formula) = 0 Then
WeekdayRow ws, lastCol
NoteMade SHEET_NAME & " の曜日の行(4行目の数式)"
End If
If Not HasValidation(ws.Range("B2")) Then
DateDropdown ws.Range("B2"), _
"=" & LIST_SHEET & "!$G$2:$G$" & (PICK_WEEKS + 1)
NoteMade SHEET_NAME & " のB2のドロップダウン"
End If
End If
EnsureButton ws, "btnDrawGantt", "工程表を描く", "DrawGantt", 290, 5
EnsureButton ws, "btnExportGantt", "PDFとExcelで出す", "ExportGantt", 422, 5
End Sub
'============================================================
' 詳細工程
'============================================================
Private Sub EnsureDaily()
Dim ws As Worksheet
Dim isNew As Boolean
Set ws = EnsureSheet(DAILY_SHEET, isNew)
If isNew Then
BuildDaily ws
NoteMade DAILY_SHEET & " シート"
Else
NoteKept DAILY_SHEET & " シート"
If Len(ws.Range("AD2").Formula) = 0 And Not IsDate(ws.Range("AD2").Value) Then
DateCandidates ws
NoteMade DAILY_SHEET & " の日付の候補(AD列)"
End If
If Not IsDate(ws.Range("B2").Value) Then
ws.Range("B2").Value = SampleStart + 7
NoteMade DAILY_SHEET & " の表示する日(B2)"
End If
If Not HasValidation(ws.Range("B2")) Then
DateDropdown ws.Range("B2"), "=$AD$2:$AD$" & (PICK_DAYS + 1)
NoteMade DAILY_SHEET & " のB2のドロップダウン"
End If
End If
EnsureButton ws, "btnDrawDaily", "その日の工程を描く", "DrawDaily", 290, 5
EnsureButton ws, "btnExportDaily", "PDFとExcelで出す", "ExportDaily", 422, 5
End Sub
'============================================================
' 中身を作る。ここはシートを新しく作ったときだけ通る
'============================================================
Private Sub BuildList(ws As Worksheet)
Dim jobs As Variant
Dim r As Variant
Dim n As Long
' 見出し。この6つの名前で「工程表マクロ」が列を探すので、変えるなら両方直す
ws.Range("A1").Value = "作業内容"
ws.Range("B1").Value = "開始日"
ws.Range("C1").Value = "開始時間"
ws.Range("D1").Value = "終了日"
ws.Range("E1").Value = "終了時間"
ws.Range("F1").Value = "備考"
' サンプルの作業。日付は「今月1日から何日目か」で入れているので、
' いつ実行しても今月の表になる。中身は自分の予定に書き換える前提
' 作業内容, 開始+日, 開始時, 終了+日, 終了時, 備考
jobs = Array( _
Array("事前準備", 0, 8, 3, 17, "手配と段取り"), _
Array("資材手配", 2, 9, 8, 15, "納期は要確認"), _
Array("足場組立", 6, 8, 7, 12, ""), _
Array("現地作業A", 7, 6, 15, 18, "定時前から開始"), _
Array("仮設電源の敷設", 7, 8, 7, 17, "当日で完了"), _
Array("立会い確認", 7, 13, 7, 15, "社外の方が同席"), _
Array("現地作業B", 13, 8, 23, 17, "Aと3日重なる"), _
Array("検査", 24, 9, 28, 12, "立会いあり"), _
Array("片付け・報告", 29, 8, 32, 15, ""))
For n = 0 To UBound(jobs)
r = jobs(n)
ws.Cells(n + 2, 1).Value = r(0)
ws.Cells(n + 2, 2).Value = SampleStart + r(1)
ws.Cells(n + 2, 3).Value = TimeSerial(r(2), 0, 0)
ws.Cells(n + 2, 4).Value = SampleStart + r(3)
ws.Cells(n + 2, 5).Value = TimeSerial(r(4), 0, 0)
ws.Cells(n + 2, 6).Value = r(5)
Next n
ws.Columns("A").ColumnWidth = 14.8
ws.Columns("B").ColumnWidth = 11.8
ws.Columns("C").ColumnWidth = 9.8
ws.Columns("D").ColumnWidth = 11.8
ws.Columns("E").ColumnWidth = 9.8
ws.Columns("F").ColumnWidth = 16.8
ws.Columns("G").ColumnWidth = 15
ws.Range("B2:B" & (JOB_COUNT + 1)).NumberFormatLocal = "yyyy/m/d"
ws.Range("D2:D" & (JOB_COUNT + 1)).NumberFormatLocal = "yyyy/m/d"
ws.Range("C2:C" & (JOB_COUNT + 1)).NumberFormatLocal = "h:mm"
ws.Range("E2:E" & (JOB_COUNT + 1)).NumberFormatLocal = "h:mm"
ws.Range("B2:E" & (JOB_COUNT + 1)).HorizontalAlignment = xlCenter
HeaderStyle ws.Range("A1:F1")
With ws.Range("A2:F" & (JOB_COUNT + 1)).Font
.Size = 9
.Color = RGB(31, 42, 55)
End With
ws.Range("F2:F" & (JOB_COUNT + 1)).Font.Color = RGB(100, 116, 139)
' 全体工程のドロップダウンに出す候補。1週間ずつずらした日付を並べておく
With ws.Range("G1")
.Value = "表示開始日の候補"
.Font.Bold = True
.Font.Size = 9
.Font.Color = RGB(100, 116, 139)
End With
ws.Range("G2").Value = SampleStart
ws.Range("G3:G" & (PICK_WEEKS + 1)).FormulaR1C1 = "=R[-1]C+7"
With ws.Range("G2:G" & (PICK_WEEKS + 1))
.NumberFormatLocal = "yyyy/m/d"
.HorizontalAlignment = xlCenter
.Font.Size = 9
End With
HideGridlines ws
End Sub
'==== 表をテーブルにする。
' G列(日付の候補)を書いたあとに呼ぶこと。先にテーブルを作ってから
' 隣のG列に書き込むと、テーブルがG列を巻き込んで7列に広がる ====
Private Sub MakeTable(ws As Worksheet)
Dim lo As ListObject
Dim lastRow As Long
' 最終行は作業内容の列から数える
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
If lastRow < 2 Then lastRow = 2
Set lo = ws.ListObjects.Add(xlSrcRange, ws.Range("A1:F" & lastRow), , xlYes)
lo.Name = TABLE_NAME
lo.TableStyle = "TableStyleLight1"
End Sub
Private Sub BuildGantt(ws As Worksheet)
Dim lastCol As Long
lastCol = FIRST_COL + DAY_COUNT - 1
' 列幅の数字は「標準フォントで何文字ぶんか」なので、
' Excelの標準フォントが違う人の画面では見た目の幅も変わる。
' 日付は2桁しか出さないので、3文字ぶんあれば足りる
ws.Columns("A").ColumnWidth = 5
ws.Columns("B").ColumnWidth = 20
ws.Columns("C:D").ColumnWidth = 2.44
ws.Range(ws.Columns(FIRST_COL), ws.Columns(lastCol)).ColumnWidth = 3.22
ws.Rows(1).RowHeight = 16.2
ws.Rows(MONTH_ROW).RowHeight = 14.4
ws.Rows(WEEK_ROW).RowHeight = 15
With ws.Range("B1")
.Value = "VBA版|表示開始日を選んで[工程表を描く]"
.Font.Bold = True
.Font.Size = 12
.Font.Color = RGB(22, 50, 79)
End With
With ws.Range("A2")
.Value = "開始"
.Font.Size = 8
.Font.Color = RGB(100, 116, 139)
.HorizontalAlignment = xlCenter
End With
' 表示開始日。ここを変えて[工程表を描く]を押すと、表が丸ごとずれる
With ws.Range("B2")
.Value = SampleStart
.NumberFormatLocal = "yyyy/m/d(aaa)"
.Font.Bold = True
.Font.Size = 10
.Font.Color = RGB(22, 50, 79)
.Interior.Color = RGB(233, 162, 59)
.HorizontalAlignment = xlCenter
End With
DateDropdown ws.Range("B2"), "=" & LIST_SHEET & "!$G$2:$G$" & (PICK_WEEKS + 1)
' 見出しの帯。色はここで付けておく。
' DrawGantt は3行目の「値だけ」を消すので、この色は描き直しても残る
ws.Range("B3").Value = "作業内容"
HeaderStyle ws.Range("B3:B4")
HeaderStyle ws.Range(ws.Cells(HEAD_ROW, FIRST_COL), ws.Cells(HEAD_ROW, lastCol))
ws.Range(ws.Cells(HEAD_ROW, FIRST_COL), _
ws.Cells(HEAD_ROW, lastCol)).NumberFormatLocal = "d"
' 月のラベルの行。左詰めにしておかないと、月名が右隣にはみ出して表示できない
With ws.Range(ws.Cells(MONTH_ROW, FIRST_COL), ws.Cells(MONTH_ROW, lastCol))
.NumberFormat = "@"
.Font.Bold = True
.Font.Size = 9
.Font.Color = RGB(22, 50, 79)
.HorizontalAlignment = xlLeft
End With
WeekdayRow ws, lastCol
' 印刷。横幅を1枚に収め、縦は何枚になってもよい設定。
' Zoom = False にしないと FitToPagesWide が効かない(倍率指定が優先される)。
' ただし60日ぶんを1枚に押し込むと字が小さくなる。実際には表示日数を減らすか、
' 期間を区切って刷ることになる
With ws.PageSetup
.Orientation = xlLandscape
.PaperSize = xlPaperA4
.Zoom = False
.FitToPagesWide = 1
.FitToPagesTall = False
.PrintTitleColumns = "$A:$D"
End With
FreezeAt ws, "E5"
HideGridlines ws
End Sub
Private Sub BuildDaily(ws As Worksheet)
Dim lastCol As Long
lastCol = FIRST_COL + HOUR_COUNT - 1
ws.Columns("A").ColumnWidth = 2.33
ws.Columns("B").ColumnWidth = 16.8
ws.Columns("C:D").ColumnWidth = 6.8
ws.Range(ws.Columns(FIRST_COL), ws.Columns(lastCol)).ColumnWidth = 4.22
ws.Rows(1).RowHeight = 25.95
ws.Rows(MONTH_ROW).RowHeight = 15
ws.Rows(HEAD_ROW).RowHeight = 15
ws.Rows(WEEK_ROW).RowHeight = 7.95
With ws.Range("B1")
.Value = "詳細工程|日付を選んで[その日の工程を描く]"
.Font.Bold = True
.Font.Size = 12
.Font.Color = RGB(22, 50, 79)
End With
DateCandidates ws
' 表示する日。最初は1週間後にしておく(サンプルの作業が重なっている日)
With ws.Range("B2")
.Value = SampleStart + 7
.NumberFormatLocal = "yyyy/m/d(aaa)"
.Font.Bold = True
.Font.Size = 9
.Interior.Color = RGB(245, 180, 76)
.HorizontalAlignment = xlCenter
End With
DateDropdown ws.Range("B2"), "=$AD$2:$AD$" & (PICK_DAYS + 1)
ws.Range("B3").Value = "作業内容"
ws.Range("C3").Value = "開始"
ws.Range("D3").Value = "終了"
HeaderStyle ws.Range(ws.Cells(HEAD_ROW, 2), ws.Cells(HEAD_ROW, lastCol))
' こちらは24時間ぶんしかないので、幅を1枚に合わせても字がつぶれない
With ws.PageSetup
.Orientation = xlLandscape
.PaperSize = xlPaperA4
.Zoom = False
.FitToPagesWide = 1
.FitToPagesTall = False
.PrintTitleColumns = "$A:$D"
End With
HideGridlines ws
End Sub
'==== ドロップダウンの中身。使う人には見せないのでAD列は隠す ====
Private Sub DateCandidates(ws As Worksheet)
With ws.Range("AD1")
.Value = "日付の候補"
.Font.Bold = True
.Font.Size = 9
.Font.Color = RGB(100, 116, 139)
End With
ws.Columns("AD").ColumnWidth = 12.8
ws.Range("AD2").Value = SampleStart
ws.Range("AD3:AD" & (PICK_DAYS + 1)).FormulaR1C1 = "=R[-1]C+1"
ws.Range("AD2:AD" & (PICK_DAYS + 1)).NumberFormatLocal = "yyyy/m/d"
ws.Columns("AD").Hidden = True
End Sub
'============================================================
' ここから下は道具
'============================================================
'==== サンプルの起点。今月の1日。
' DrawGantt の中の baseDate と紛らわしいので別の名前にしている ====
Private Function SampleStart() As Date
SampleStart = DateSerial(Year(Date), Month(Date), 1)
End Function
'==== シートを返す。無ければ足して、足したことを isNew で知らせる ====
Private Function EnsureSheet(sheetName As String, isNew As Boolean) As Worksheet
Dim ws As Worksheet
isNew = False
For Each ws In ThisWorkbook.Worksheets
If ws.Name = sheetName Then
Set EnsureSheet = ws
Exit Function
End If
Next ws
Set ws = ThisWorkbook.Worksheets.Add( _
After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
ws.Name = sheetName
isNew = True
Set EnsureSheet = ws
End Function
'==== その名前のテーブルがブックのどこかにあるか ====
Private Function TableExists(tblName As String) As Boolean
Dim ws As Worksheet
Dim lo As ListObject
For Each ws In ThisWorkbook.Worksheets
For Each lo In ws.ListObjects
If lo.Name = tblName Then
TableExists = True
Exit Function
End If
Next lo
Next ws
End Function
'==== その名前のボタンがそのシートにあるか ====
Private Function ShapeExists(ws As Worksheet, shapeName As String) As Boolean
Dim k As Long
For k = 1 To ws.Shapes.Count
If ws.Shapes(k).Name = shapeName Then
ShapeExists = True
Exit Function
End If
Next k
End Function
'==== 入力規則が入っているか。
' 入っていないセルの .Validation.Type は
' 値を返さずエラーになるので、エラーが出るかどうかで見る ====
Private Function HasValidation(c As Range) As Boolean
Dim t As Long
On Error Resume Next
t = c.Validation.Type
HasValidation = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
'==== 何も入っていないシートか ====
Private Function IsEmptySheet(ws As Worksheet) As Boolean
IsEmptySheet = (Application.WorksheetFunction.CountA(ws.Cells) = 0)
End Function
'==== 日付のドロップダウンを付ける ====
Private Sub DateDropdown(c As Range, listFormula As String)
c.Validation.Delete
c.Validation.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, _
Formula1:=listFormula
End Sub
'==== 曜日の行。真上の日付を見るだけの数式を並べる。
' これがあるので、VBA側で曜日を書かなくて済む ====
Private Sub WeekdayRow(ws As Worksheet, lastCol As Long)
With ws.Range(ws.Cells(WEEK_ROW, FIRST_COL), ws.Cells(WEEK_ROW, lastCol))
.FormulaR1C1 = "=R[-1]C"
.NumberFormatLocal = "aaa"
.Font.Size = 8
.Font.Color = RGB(100, 116, 139)
.HorizontalAlignment = xlCenter
End With
End Sub
'==== 見出しの帯(濃い紺に白抜き)====
Private Sub HeaderStyle(target As Range)
With target
.Font.Bold = True
.Font.Size = 9
.Font.Color = RGB(255, 255, 255)
.Interior.Color = RGB(22, 50, 79)
.HorizontalAlignment = xlCenter
End With
End Sub
'==== ボタンを1つ置く。もうあるなら何もしない ====
Private Sub EnsureButton(ws As Worksheet, btnName As String, btnCaption As String, _
macroName As String, L As Double, T As Double)
Dim b As Object
If ShapeExists(ws, btnName) Then
NoteKept ws.Name & " の[" & btnCaption & "]ボタン"
Exit Sub
End If
Set b = ws.Buttons.Add(L, T, 126, 24)
b.Name = btnName
b.Caption = btnCaption
b.OnAction = macroName
NoteMade ws.Name & " の[" & btnCaption & "]ボタン"
End Sub
'==== 見出しを固定する。画面の設定なので、ここだけシートを表に出す必要がある ====
Private Sub FreezeAt(ws As Worksheet, cellAddress As String)
ws.Activate
ActiveWindow.FreezePanes = False
ws.Range(cellAddress).Select
ActiveWindow.FreezePanes = True
End Sub
'==== 目盛線を消す。これも画面の設定 ====
Private Sub HideGridlines(ws As Worksheet)
ws.Activate
ActiveWindow.DisplayGridlines = False
End Sub
'==== 何をしたかを覚えておく ====
Private Sub NoteMade(msg As String)
mMade = mMade & "・" & msg & vbCrLf
End Sub
Private Sub NoteKept(msg As String)
mKept = mKept & "・" & msg & vbCrLf
End Sub
Private Sub NoteWarn(msg As String)
mWarn = mWarn & "・" & msg & vbCrLf
End Sub
Private Function Warnings() As String
If Len(mWarn) = 0 Then Exit Function
Warnings = vbCrLf & "こちらでは決められなかったもの" & vbCrLf & mWarn
End Function- 新しいブックを作る
Alt+F11でVBAの画面を開く- 挿入 → 標準モジュールを2つ作る

- 1つ目のモジュール名を「工程表設定」にして、下の完成版①を貼る
- 2つ目のモジュール名を「工程表マクロ」にして、下の完成版②を貼る
SetupBookの中のどこでもいいのでカーソルを置いてF5.xlsm(Excel マクロ有効ブック)で保存する
7番だけ間違えやすいので気を付けてください。.xlsx で保存すると、貼り付けたコードが消えます。
3か所だけ、そう書いた理由を残しておきます。
JOB_COUNT・PICK_WEEKS・PICK_DAYSはモジュールのいちばん上にある … この土台づくりだけが使う値ですが、VBAはモジュール全体で使う宣言を先頭に置く決まりなので、ここには書けません。mMadeなどの3つの変数も同じ理由で上にあります- 入力規則があるかどうかを、エラーが出るかどうかで見ている … 入力規則が入っていないセルの
Validation.Typeは、値を返さずにエラーになります。「入っていますか」と素直に聞ける書き方が無いので、On Error Resume Nextで受けて判定しています - テーブルを作るのは、G列を書いたあと … 先にテーブルを作ってから隣のG列に書き込むと、テーブルがG列を巻き込んで7列に広がります。順番を逆にするだけで防げます
なお列幅の数字は「標準フォントで何文字ぶんか」という意味なので、Excelの標準フォントが違う人の画面では見た目の幅も少し変わります。日付は2桁しか出さないので、3文字ぶんあれば足ります。
貼り付け用の完成版
ここまで①〜⑥に分けて出したコードを、モジュールごとに1つへまとめました。貼るのは、この2つだけで大丈夫です。上で分けて出したコードも一緒に貼ると、同じ処理が2つになって「名前が適切ではありません」というエラーになります。
完成版① 工程表設定(1つ目のモジュールに貼る)
Option Explicit
'============================================================
' 工程表設定
'
' 最初に一度だけ触るところです。
'
' ① 下の設定を、自分の表に合わせる(変えなくても動きます)
' ② SetupBook を1回実行すると、シートとボタンができあがります
'
' もう1つの「工程表マクロ」は、描くところと書き出すところだけです。
' 開かなくて構いません。
'============================================================
'---- ① シートとテーブルの名前 ----
Public Const SHEET_NAME As String = "全体工程" ' ガントチャートを描くシート
Public Const DAILY_SHEET As String = "詳細工程" ' その日の時間割を描くシート
Public Const LIST_SHEET As String = "作業リスト" ' 作業リストのテーブルがあるシート
Public Const TABLE_NAME As String = "作業リスト" ' テーブルの名前
'---- ② 表のどこに何があるか ----
Public Const START_CELL As String = "B2" ' 全体工程で、表示開始日を選ぶセル
Public Const DAILY_CELL As String = "B2" ' 詳細工程で、表示する日を選ぶセル
Public Const MONTH_ROW As Long = 2 ' 月のラベルを出す行
Public Const HEAD_ROW As Long = 3 ' 日付を並べる行
Public Const WEEK_ROW As Long = 4 ' 曜日の行(=E3 の数式が入っている)
Public Const FIRST_ROW As Long = 5 ' 1件目のタイトル行
Public Const FIRST_COL As Long = 5 ' E列からカレンダーを描く
'---- ③ どれだけ出すか ----
Public Const DAY_COUNT As Long = 60 ' 全体工程を横に何日ぶん出すか
Public Const HOUR_COUNT As Long = 24 ' 詳細工程を0時から何時間ぶん出すか
'---- ④ 見た目(1作業ぶんの高さ)----
Public Const ROWS_PER_JOB As Long = 3 ' タイトル行+バー行+隙間行
Public Const H_TITLE As Double = 17 ' タイトル行の高さ
Public Const H_BAR As Double = 13 ' バーの行の高さ(細いほど締まって見える)
Public Const H_GAP As Double = 5 ' すきまの行の高さ
'---- ⑤ 勤務時間(詳細工程で、人がいない時間を塗るため)----
Public Const WORK_START As Long = 8 ' 日勤の始まり
Public Const WORK_END As Long = 17 ' 日勤の終わり
Public Const LUNCH_HOUR As Long = 12 ' 昼休み
'---- ⑥ 書き出し先。無ければ作ります ----
Public Const EXPORT_DIR As String = "C:\Users\Public\工程表\"
'---- ⑦ 最初に入れておくサンプルの分量(土台づくりだけが使う)----
Public Const JOB_COUNT As Long = 9 ' サンプルの作業の数
Public Const PICK_WEEKS As Long = 9 ' 全体工程のドロップダウンに出す週の数
Public Const PICK_DAYS As Long = 33 ' 詳細工程のドロップダウンに出す日数
'---- 土台づくりが「何をしたか」を覚えておく箱。あとでまとめて報告する ----
Private mMade As String ' 作ったもの
Private mKept As String ' もとからあったので触らなかったもの
Private mWarn As String ' こちらでは判断できなかったもの
'============================================================
' ここから下は「土台づくり」。
' まだ何も無いブックに、シート・テーブル・ボタンを作る。
' 足りないものだけを作るので、2回目以降に実行しても中身は消えない。
' SetupBook を実行するのは最初の1回だけでよい。
'============================================================
Public Sub SetupBook()
mMade = ""
mKept = ""
mWarn = ""
' 途中で止まっても画面の更新が止まったままにならないように、
' エラーの行き先を先に決めてから ScreenUpdating を切る
On Error GoTo TROUBLE
Application.ScreenUpdating = False
EnsureList
EnsureGantt
EnsureDaily
Application.ScreenUpdating = True
' 何も作らなかったなら、描き直す必要もない。ここで終われば一瞬で済む
If Len(mMade) = 0 Then
MsgBox "そろっていました。何も変更していません。" & vbCrLf & vbCrLf & _
"もとからあったもの" & vbCrLf & mKept & Warnings, _
vbInformation, "工程表のセットアップ"
Exit Sub
End If
' 土台ができたので、そのまま1回描いて完成形を見せる
DrawGantt
DrawDaily
ThisWorkbook.Worksheets(SHEET_NAME).Activate
MsgBox "できました。" & vbCrLf & vbCrLf & _
"作ったもの" & vbCrLf & mMade & _
IIf(Len(mKept) > 0, vbCrLf & "もとからあったもの" & vbCrLf & mKept, "") & _
Warnings & vbCrLf & _
"このあと、必ず .xlsm(Excel マクロ有効ブック)で保存してください。" & vbCrLf & _
".xlsx で保存すると、貼り付けたコードが消えます。", _
vbInformation, "工程表のセットアップ"
Exit Sub
TROUBLE:
Application.ScreenUpdating = True
MsgBox "途中で止まりました。" & vbCrLf & vbCrLf & _
"エラー " & Err.Number & ":" & Err.Description, _
vbCritical, "工程表のセットアップ"
End Sub
'============================================================
' 作業リスト。シートとテーブルを別々に見る
'============================================================
Private Sub EnsureList()
Dim ws As Worksheet
Dim isNew As Boolean
Set ws = EnsureSheet(LIST_SHEET, isNew)
If isNew Then
BuildList ws
NoteMade LIST_SHEET & " シート(サンプルの予定つき)"
Else
NoteKept LIST_SHEET & " シート"
End If
' シートがあってもテーブルが無ければ、テーブルだけ作る。
' 「工程表マクロ」は列を名前で探すので、テーブルでないと動かない
If TableExists(TABLE_NAME) Then
If Not isNew Then NoteKept "テーブル「" & TABLE_NAME & "」"
ElseIf ws.Range("A1").Value = "作業内容" Then
MakeTable ws
NoteMade "テーブル「" & TABLE_NAME & "」(もとの表をテーブルにしました)"
ElseIf IsEmptySheet(ws) Then
BuildList ws
MakeTable ws
NoteMade LIST_SHEET & " の中身とテーブル「" & TABLE_NAME & "」"
Else
' 人が作った別の表が入っている。上書きするとその人の予定が消えるので触らない
NoteWarn LIST_SHEET & " のA1が「作業内容」ではありません。" & vbCrLf & _
" 見出しを 作業内容/開始日/開始時間/終了日/終了時間/備考 に直してから、" & vbCrLf & _
" もう一度実行してください。"
End If
End Sub
'============================================================
' 全体工程
'============================================================
Private Sub EnsureGantt()
Dim ws As Worksheet
Dim isNew As Boolean
Dim lastCol As Long
Set ws = EnsureSheet(SHEET_NAME, isNew)
lastCol = FIRST_COL + DAY_COUNT - 1
If isNew Then
BuildGantt ws
NoteMade SHEET_NAME & " シート"
Else
NoteKept SHEET_NAME & " シート"
' シートはあるけれど、動くのに要るものが欠けている場合だけ足す。
' 見た目(色・列幅)は直さない。手で整えた人のものを壊さないため
If Not IsDate(ws.Range("B2").Value) Then
ws.Range("B2").Value = SampleStart
NoteMade SHEET_NAME & " の表示開始日(B2)"
End If
If Len(ws.Cells(WEEK_ROW, FIRST_COL).Formula) = 0 Then
WeekdayRow ws, lastCol
NoteMade SHEET_NAME & " の曜日の行(4行目の数式)"
End If
If Not HasValidation(ws.Range("B2")) Then
DateDropdown ws.Range("B2"), _
"=" & LIST_SHEET & "!$G$2:$G$" & (PICK_WEEKS + 1)
NoteMade SHEET_NAME & " のB2のドロップダウン"
End If
End If
EnsureButton ws, "btnDrawGantt", "工程表を描く", "DrawGantt", 290, 5
EnsureButton ws, "btnExportGantt", "PDFとExcelで出す", "ExportGantt", 422, 5
End Sub
'============================================================
' 詳細工程
'============================================================
Private Sub EnsureDaily()
Dim ws As Worksheet
Dim isNew As Boolean
Set ws = EnsureSheet(DAILY_SHEET, isNew)
If isNew Then
BuildDaily ws
NoteMade DAILY_SHEET & " シート"
Else
NoteKept DAILY_SHEET & " シート"
If Len(ws.Range("AD2").Formula) = 0 And Not IsDate(ws.Range("AD2").Value) Then
DateCandidates ws
NoteMade DAILY_SHEET & " の日付の候補(AD列)"
End If
If Not IsDate(ws.Range("B2").Value) Then
ws.Range("B2").Value = SampleStart + 7
NoteMade DAILY_SHEET & " の表示する日(B2)"
End If
If Not HasValidation(ws.Range("B2")) Then
DateDropdown ws.Range("B2"), "=$AD$2:$AD$" & (PICK_DAYS + 1)
NoteMade DAILY_SHEET & " のB2のドロップダウン"
End If
End If
EnsureButton ws, "btnDrawDaily", "その日の工程を描く", "DrawDaily", 290, 5
EnsureButton ws, "btnExportDaily", "PDFとExcelで出す", "ExportDaily", 422, 5
End Sub
'============================================================
' 中身を作る。ここはシートを新しく作ったときだけ通る
'============================================================
Private Sub BuildList(ws As Worksheet)
Dim jobs As Variant
Dim r As Variant
Dim n As Long
' 見出し。この6つの名前で「工程表マクロ」が列を探すので、変えるなら両方直す
ws.Range("A1").Value = "作業内容"
ws.Range("B1").Value = "開始日"
ws.Range("C1").Value = "開始時間"
ws.Range("D1").Value = "終了日"
ws.Range("E1").Value = "終了時間"
ws.Range("F1").Value = "備考"
' サンプルの作業。日付は「今月1日から何日目か」で入れているので、
' いつ実行しても今月の表になる。中身は自分の予定に書き換える前提
' 作業内容, 開始+日, 開始時, 終了+日, 終了時, 備考
jobs = Array( _
Array("事前準備", 0, 8, 3, 17, "手配と段取り"), _
Array("資材手配", 2, 9, 8, 15, "納期は要確認"), _
Array("足場組立", 6, 8, 7, 12, ""), _
Array("現地作業A", 7, 6, 15, 18, "定時前から開始"), _
Array("仮設電源の敷設", 7, 8, 7, 17, "当日で完了"), _
Array("立会い確認", 7, 13, 7, 15, "社外の方が同席"), _
Array("現地作業B", 13, 8, 23, 17, "Aと3日重なる"), _
Array("検査", 24, 9, 28, 12, "立会いあり"), _
Array("片付け・報告", 29, 8, 32, 15, ""))
For n = 0 To UBound(jobs)
r = jobs(n)
ws.Cells(n + 2, 1).Value = r(0)
ws.Cells(n + 2, 2).Value = SampleStart + r(1)
ws.Cells(n + 2, 3).Value = TimeSerial(r(2), 0, 0)
ws.Cells(n + 2, 4).Value = SampleStart + r(3)
ws.Cells(n + 2, 5).Value = TimeSerial(r(4), 0, 0)
ws.Cells(n + 2, 6).Value = r(5)
Next n
ws.Columns("A").ColumnWidth = 14.8
ws.Columns("B").ColumnWidth = 11.8
ws.Columns("C").ColumnWidth = 9.8
ws.Columns("D").ColumnWidth = 11.8
ws.Columns("E").ColumnWidth = 9.8
ws.Columns("F").ColumnWidth = 16.8
ws.Columns("G").ColumnWidth = 15
ws.Range("B2:B" & (JOB_COUNT + 1)).NumberFormatLocal = "yyyy/m/d"
ws.Range("D2:D" & (JOB_COUNT + 1)).NumberFormatLocal = "yyyy/m/d"
ws.Range("C2:C" & (JOB_COUNT + 1)).NumberFormatLocal = "h:mm"
ws.Range("E2:E" & (JOB_COUNT + 1)).NumberFormatLocal = "h:mm"
ws.Range("B2:E" & (JOB_COUNT + 1)).HorizontalAlignment = xlCenter
HeaderStyle ws.Range("A1:F1")
With ws.Range("A2:F" & (JOB_COUNT + 1)).Font
.Size = 9
.Color = RGB(31, 42, 55)
End With
ws.Range("F2:F" & (JOB_COUNT + 1)).Font.Color = RGB(100, 116, 139)
' 全体工程のドロップダウンに出す候補。1週間ずつずらした日付を並べておく
With ws.Range("G1")
.Value = "表示開始日の候補"
.Font.Bold = True
.Font.Size = 9
.Font.Color = RGB(100, 116, 139)
End With
ws.Range("G2").Value = SampleStart
ws.Range("G3:G" & (PICK_WEEKS + 1)).FormulaR1C1 = "=R[-1]C+7"
With ws.Range("G2:G" & (PICK_WEEKS + 1))
.NumberFormatLocal = "yyyy/m/d"
.HorizontalAlignment = xlCenter
.Font.Size = 9
End With
HideGridlines ws
End Sub
'==== 表をテーブルにする。
' G列(日付の候補)を書いたあとに呼ぶこと。先にテーブルを作ってから
' 隣のG列に書き込むと、テーブルがG列を巻き込んで7列に広がる ====
Private Sub MakeTable(ws As Worksheet)
Dim lo As ListObject
Dim lastRow As Long
' 最終行は作業内容の列から数える
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
If lastRow < 2 Then lastRow = 2
Set lo = ws.ListObjects.Add(xlSrcRange, ws.Range("A1:F" & lastRow), , xlYes)
lo.Name = TABLE_NAME
lo.TableStyle = "TableStyleLight1"
End Sub
Private Sub BuildGantt(ws As Worksheet)
Dim lastCol As Long
lastCol = FIRST_COL + DAY_COUNT - 1
' 列幅の数字は「標準フォントで何文字ぶんか」なので、
' Excelの標準フォントが違う人の画面では見た目の幅も変わる。
' 日付は2桁しか出さないので、3文字ぶんあれば足りる
ws.Columns("A").ColumnWidth = 5
ws.Columns("B").ColumnWidth = 20
ws.Columns("C:D").ColumnWidth = 2.44
ws.Range(ws.Columns(FIRST_COL), ws.Columns(lastCol)).ColumnWidth = 3.22
ws.Rows(1).RowHeight = 16.2
ws.Rows(MONTH_ROW).RowHeight = 14.4
ws.Rows(WEEK_ROW).RowHeight = 15
With ws.Range("B1")
.Value = "VBA版|表示開始日を選んで[工程表を描く]"
.Font.Bold = True
.Font.Size = 12
.Font.Color = RGB(22, 50, 79)
End With
With ws.Range("A2")
.Value = "開始"
.Font.Size = 8
.Font.Color = RGB(100, 116, 139)
.HorizontalAlignment = xlCenter
End With
' 表示開始日。ここを変えて[工程表を描く]を押すと、表が丸ごとずれる
With ws.Range("B2")
.Value = SampleStart
.NumberFormatLocal = "yyyy/m/d(aaa)"
.Font.Bold = True
.Font.Size = 10
.Font.Color = RGB(22, 50, 79)
.Interior.Color = RGB(233, 162, 59)
.HorizontalAlignment = xlCenter
End With
DateDropdown ws.Range("B2"), "=" & LIST_SHEET & "!$G$2:$G$" & (PICK_WEEKS + 1)
' 見出しの帯。色はここで付けておく。
' DrawGantt は3行目の「値だけ」を消すので、この色は描き直しても残る
ws.Range("B3").Value = "作業内容"
HeaderStyle ws.Range("B3:B4")
HeaderStyle ws.Range(ws.Cells(HEAD_ROW, FIRST_COL), ws.Cells(HEAD_ROW, lastCol))
ws.Range(ws.Cells(HEAD_ROW, FIRST_COL), _
ws.Cells(HEAD_ROW, lastCol)).NumberFormatLocal = "d"
' 月のラベルの行。左詰めにしておかないと、月名が右隣にはみ出して表示できない
With ws.Range(ws.Cells(MONTH_ROW, FIRST_COL), ws.Cells(MONTH_ROW, lastCol))
.NumberFormat = "@"
.Font.Bold = True
.Font.Size = 9
.Font.Color = RGB(22, 50, 79)
.HorizontalAlignment = xlLeft
End With
WeekdayRow ws, lastCol
' 印刷。横幅を1枚に収め、縦は何枚になってもよい設定。
' Zoom = False にしないと FitToPagesWide が効かない(倍率指定が優先される)。
' ただし60日ぶんを1枚に押し込むと字が小さくなる。実際には表示日数を減らすか、
' 期間を区切って刷ることになる
With ws.PageSetup
.Orientation = xlLandscape
.PaperSize = xlPaperA4
.Zoom = False
.FitToPagesWide = 1
.FitToPagesTall = False
.PrintTitleColumns = "$A:$D"
End With
FreezeAt ws, "E5"
HideGridlines ws
End Sub
Private Sub BuildDaily(ws As Worksheet)
Dim lastCol As Long
lastCol = FIRST_COL + HOUR_COUNT - 1
ws.Columns("A").ColumnWidth = 2.33
ws.Columns("B").ColumnWidth = 16.8
ws.Columns("C:D").ColumnWidth = 6.8
ws.Range(ws.Columns(FIRST_COL), ws.Columns(lastCol)).ColumnWidth = 4.22
ws.Rows(1).RowHeight = 25.95
ws.Rows(MONTH_ROW).RowHeight = 15
ws.Rows(HEAD_ROW).RowHeight = 15
ws.Rows(WEEK_ROW).RowHeight = 7.95
With ws.Range("B1")
.Value = "詳細工程|日付を選んで[その日の工程を描く]"
.Font.Bold = True
.Font.Size = 12
.Font.Color = RGB(22, 50, 79)
End With
DateCandidates ws
' 表示する日。最初は1週間後にしておく(サンプルの作業が重なっている日)
With ws.Range("B2")
.Value = SampleStart + 7
.NumberFormatLocal = "yyyy/m/d(aaa)"
.Font.Bold = True
.Font.Size = 9
.Interior.Color = RGB(245, 180, 76)
.HorizontalAlignment = xlCenter
End With
DateDropdown ws.Range("B2"), "=$AD$2:$AD$" & (PICK_DAYS + 1)
ws.Range("B3").Value = "作業内容"
ws.Range("C3").Value = "開始"
ws.Range("D3").Value = "終了"
HeaderStyle ws.Range(ws.Cells(HEAD_ROW, 2), ws.Cells(HEAD_ROW, lastCol))
' こちらは24時間ぶんしかないので、幅を1枚に合わせても字がつぶれない
With ws.PageSetup
.Orientation = xlLandscape
.PaperSize = xlPaperA4
.Zoom = False
.FitToPagesWide = 1
.FitToPagesTall = False
.PrintTitleColumns = "$A:$D"
End With
HideGridlines ws
End Sub
'==== ドロップダウンの中身。使う人には見せないのでAD列は隠す ====
Private Sub DateCandidates(ws As Worksheet)
With ws.Range("AD1")
.Value = "日付の候補"
.Font.Bold = True
.Font.Size = 9
.Font.Color = RGB(100, 116, 139)
End With
ws.Columns("AD").ColumnWidth = 12.8
ws.Range("AD2").Value = SampleStart
ws.Range("AD3:AD" & (PICK_DAYS + 1)).FormulaR1C1 = "=R[-1]C+1"
ws.Range("AD2:AD" & (PICK_DAYS + 1)).NumberFormatLocal = "yyyy/m/d"
ws.Columns("AD").Hidden = True
End Sub
'============================================================
' ここから下は道具
'============================================================
'==== サンプルの起点。今月の1日。
' DrawGantt の中の baseDate と紛らわしいので別の名前にしている ====
Private Function SampleStart() As Date
SampleStart = DateSerial(Year(Date), Month(Date), 1)
End Function
'==== シートを返す。無ければ足して、足したことを isNew で知らせる ====
Private Function EnsureSheet(sheetName As String, isNew As Boolean) As Worksheet
Dim ws As Worksheet
isNew = False
For Each ws In ThisWorkbook.Worksheets
If ws.Name = sheetName Then
Set EnsureSheet = ws
Exit Function
End If
Next ws
Set ws = ThisWorkbook.Worksheets.Add( _
After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
ws.Name = sheetName
isNew = True
Set EnsureSheet = ws
End Function
'==== その名前のテーブルがブックのどこかにあるか ====
Private Function TableExists(tblName As String) As Boolean
Dim ws As Worksheet
Dim lo As ListObject
For Each ws In ThisWorkbook.Worksheets
For Each lo In ws.ListObjects
If lo.Name = tblName Then
TableExists = True
Exit Function
End If
Next lo
Next ws
End Function
'==== その名前のボタンがそのシートにあるか ====
Private Function ShapeExists(ws As Worksheet, shapeName As String) As Boolean
Dim k As Long
For k = 1 To ws.Shapes.Count
If ws.Shapes(k).Name = shapeName Then
ShapeExists = True
Exit Function
End If
Next k
End Function
'==== 入力規則が入っているか。
' 入っていないセルの .Validation.Type は
' 値を返さずエラーになるので、エラーが出るかどうかで見る ====
Private Function HasValidation(c As Range) As Boolean
Dim t As Long
On Error Resume Next
t = c.Validation.Type
HasValidation = (Err.Number = 0)
Err.Clear
On Error GoTo 0
End Function
'==== 何も入っていないシートか ====
Private Function IsEmptySheet(ws As Worksheet) As Boolean
IsEmptySheet = (Application.WorksheetFunction.CountA(ws.Cells) = 0)
End Function
'==== 日付のドロップダウンを付ける ====
Private Sub DateDropdown(c As Range, listFormula As String)
c.Validation.Delete
c.Validation.Add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, _
Formula1:=listFormula
End Sub
'==== 曜日の行。真上の日付を見るだけの数式を並べる。
' これがあるので、VBA側で曜日を書かなくて済む ====
Private Sub WeekdayRow(ws As Worksheet, lastCol As Long)
With ws.Range(ws.Cells(WEEK_ROW, FIRST_COL), ws.Cells(WEEK_ROW, lastCol))
.FormulaR1C1 = "=R[-1]C"
.NumberFormatLocal = "aaa"
.Font.Size = 8
.Font.Color = RGB(100, 116, 139)
.HorizontalAlignment = xlCenter
End With
End Sub
'==== 見出しの帯(濃い紺に白抜き)====
Private Sub HeaderStyle(target As Range)
With target
.Font.Bold = True
.Font.Size = 9
.Font.Color = RGB(255, 255, 255)
.Interior.Color = RGB(22, 50, 79)
.HorizontalAlignment = xlCenter
End With
End Sub
'==== ボタンを1つ置く。もうあるなら何もしない ====
Private Sub EnsureButton(ws As Worksheet, btnName As String, btnCaption As String, _
macroName As String, L As Double, T As Double)
Dim b As Object
If ShapeExists(ws, btnName) Then
NoteKept ws.Name & " の[" & btnCaption & "]ボタン"
Exit Sub
End If
Set b = ws.Buttons.Add(L, T, 126, 24)
b.Name = btnName
b.Caption = btnCaption
b.OnAction = macroName
NoteMade ws.Name & " の[" & btnCaption & "]ボタン"
End Sub
'==== 見出しを固定する。画面の設定なので、ここだけシートを表に出す必要がある ====
Private Sub FreezeAt(ws As Worksheet, cellAddress As String)
ws.Activate
ActiveWindow.FreezePanes = False
ws.Range(cellAddress).Select
ActiveWindow.FreezePanes = True
End Sub
'==== 目盛線を消す。これも画面の設定 ====
Private Sub HideGridlines(ws As Worksheet)
ws.Activate
ActiveWindow.DisplayGridlines = False
End Sub
'==== 何をしたかを覚えておく ====
Private Sub NoteMade(msg As String)
mMade = mMade & "・" & msg & vbCrLf
End Sub
Private Sub NoteKept(msg As String)
mKept = mKept & "・" & msg & vbCrLf
End Sub
Private Sub NoteWarn(msg As String)
mWarn = mWarn & "・" & msg & vbCrLf
End Sub
Private Function Warnings() As String
If Len(mWarn) = 0 Then Exit Function
Warnings = vbCrLf & "こちらでは決められなかったもの" & vbCrLf & mWarn
End Function完成版② 工程表マクロ(2つ目のモジュールに貼る)
Option Explicit
'============================================================
' 工程表マクロ
'
' 描くところと、書き出すところだけが入っています。
' 触らなくて構いません。
' 設定と初期設定は「工程表設定」にまとめてあります。
'============================================================
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
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月" を
' 日付だと解釈してしまい、列幅が狭いと ### になるため先に表示形式を文字列にする。
' 書式もここで毎回そろえる。最初の1つだけ手で色を付けたブックだと、
' 月が変わって出てくる2つ目以降が既定の黒のまま残ってしまうため
With ws.Range(ws.Cells(MONTH_ROW, FIRST_COL), _
ws.Cells(MONTH_ROW, FIRST_COL + DAY_COUNT - 1))
.NumberFormat = "@"
.Font.Bold = True
.Font.Size = 9
.Font.Color = RGB(22, 50, 79)
.HorizontalAlignment = xlLeft
End With
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 = MonthLabel(baseDate, i)
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
'==== 月のラベル。次の月初までに何列空いているかで、入る長さを選ぶ ====
' 列幅は3文字ぶんしかないので、月初のすぐ手前から表を始めると
' "2026年9月" が右へはみ出しきれず "2026年" で切れてしまう
Private Function MonthLabel(baseDate As Date, i As Long) As String
Dim gap As Long
' 次にラベルが出る列(次の月の1日)まで、何列あるかを数える
gap = 1
Do While i + gap <= DAY_COUNT - 1
If Day(baseDate + i + gap) = 1 Then Exit Do
gap = gap + 1
Loop
If gap >= 4 Then
MonthLabel = Format$(baseDate + i, "yyyy年m月")
Else
MonthLabel = Format$(baseDate + i, "m月")
End If
End Function
'==== 前回描いた内容を消す(全体工程・詳細工程で共通)====
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 Function
'==== 作業の開始・終了を「日付+時間」の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
If eh < HOUR_COUNT And Minute(eVal) > 0 Then eh = eh + 1 ' 端数は切り上げる
' 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
'==== ボタンから呼ぶ入口。全体工程を書き出す ====
Public Sub ExportGantt()
ExportSheet SHEET_NAME
End Sub
'==== ボタンから呼ぶ入口。詳細工程(その日の時間割)を書き出す ====
Public Sub ExportDaily()
ExportSheet DAILY_SHEET
End Sub
'==== 中身。シート名を受け取って、同じフォルダにPDFとExcelで出す ====
Private Sub ExportSheet(sheetName As String)
' 出力先は「工程表設定」の EXPORT_DIR を使う
Dim ws As Worksheet
Dim outWs As Worksheet
Dim baseName As String
Dim outDir As String
Dim ans As VbMsgBoxResult
Dim n As Long
Set ws = ThisWorkbook.Worksheets(sheetName)
outDir = EXPORT_DIR
' 末尾の \ を忘れると、フォルダ名がファイル名の頭にくっついて
' 1つ上のフォルダに出てしまう。忘れても動くようにここで補う
If Right$(outDir, 1) <> "\" Then outDir = outDir & "\"
If Dir(outDir, vbDirectory) = "" Then MkDir outDir
' 受け取ったシート名をそのままファイル名に使う。
' 全体工程なら「20260812_全体工程」、詳細工程なら「20260812_詳細工程」
baseName = Format$(Date, "yyyymmdd") & "_" & sheetName
' 同じ名前がすでにあるかを先に見る。PDFとExcelのどちらか一方でもあれば聞く
If FileExists(outDir & baseName & ".pdf") _
Or FileExists(outDir & baseName & ".xlsx") Then
ans = MsgBox(baseName & " はすでにあります。上書きしますか?" & vbCrLf & vbCrLf & _
"はい … 上書きする" & vbCrLf & _
"いいえ … _2、_3 と番号を付けて別に保存する" & vbCrLf & _
"キャンセル … 書き出しをやめる", _
vbYesNoCancel + vbQuestion, "書き出し")
If ans = vbCancel Then Exit Sub
If ans = vbNo Then
' PDFとExcelの両方が空いている番号まで進める。片方だけで判定すると
' PDFが_2でExcelが_3のようにずれて、どれが同じ書き出しか分からなくなる
n = 2
Do While FileExists(outDir & baseName & "_" & n & ".pdf") _
Or FileExists(outDir & baseName & "_" & n & ".xlsx")
n = n + 1
Loop
baseName = baseName & "_" & n
End If
End If
' このシートだけ別ブックにコピーして、そちらから書き出す
ws.Copy
Set outWs = ActiveWorkbook.Worksheets(1)
' ボタンはコピーにも付いてくる。渡した先にはマクロが無いので押すとエラーになるし、
' 紙にもそのまま出てしまう。コピーのほうから消しておく
Do While outWs.Shapes.Count > 0
outWs.Shapes(1).Delete
Loop
' PDFで出す
outWs.ExportAsFixedFormat _
Type:=xlTypePDF, _
FileName:=outDir & baseName & ".pdf", _
Quality:=xlQualityStandard, _
IgnorePrintAreas:=False, _
OpenAfterPublish:=False
' Excelで出す
' 上書きしていいかは上で聞いたあとなので、Excelにもう一度聞かせない
Application.DisplayAlerts = False
ActiveWorkbook.SaveAs _
FileName:=outDir & baseName & ".xlsx", _
FileFormat:=xlOpenXMLWorkbook
ActiveWorkbook.Close SaveChanges:=False
Application.DisplayAlerts = True
MsgBox "書き出しました。" & vbCrLf & outDir & baseName & ".pdf / .xlsx", vbInformation
End Sub
'==== ファイルがあるかどうかを見るだけの関数 ====
Private Function FileExists(filePath As String) As Boolean
FileExists = (Dir(filePath) <> "")
End Function
'==== 保存先をその場で選びたいとき ====
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- コピペで使える条件付き書式の数式 … 土日
=WEEKDAY(E$3,2)>=6/ バー=AND($C5<>"",E$3>=$C5,E$3<=$D5) - テーブルからバーを描くVBAコード … 全部消して描き直す形。設定は先頭の定数だけ変える
- 判断のものさし … 年数回ならマクロなし。毎週作り直すならVBA。幅と紙で詰まったらExcelの外
まとめ
- 検索して出てくる「工程表の自動作成」の多くは、開始日と終了日を打てばバーが出るところまで。そこは関数と条件付き書式で足りる
- 毎回作り直すならVBA。テーブルを作業リストにして、ボタンで全部消して描き直す形が扱いやすい
- 条件付き書式で足りるものは、コードにしない。土日は条件付き書式のまま残していい
- 自動化できるのは「描く」ところまで。進捗率と実績は、入力するか他のシステムから持ってこない限り残らない
- 1枚に収める前提だと、期間の長さと読みやすさは必ずぶつかる。Excelが上手くなっても消えない
- バージョンは増える。増やさない努力より、置き場所を1つに決めるほうが効く
私はこの形にたどり着くまでに、手で塗る3年と、試行錯誤の1年を使いました。同じ遠回りをする必要はありません。まずは土日の条件付き書式だけでも入れてみてください。あれが消えるだけで、だいぶ違います。
| いまの状態 | 次に見るもの |
|---|---|
| コードが読めなかった | VBA(マクロ)入門ガイド。ここからで大丈夫です |
| コードを書かずに済ませたい | Schedika Lite を無料で試す(PR・自社製品) |
| 制限なしで使いたい | Schedika Standard の販売ページ(PR・自社製品/買い切り2,980円・2026年8月時点) |



