ねらい:Excelで「タスク管理」を安全に運用し、誰が使っても同じ結果にする
Excelでのタスク管理は、入力シートの項目、状態遷移、担当者、期限、優先度、ログ、通知などが絡みます。VBAで仕組み化すれば、重複や記入漏れ、更新ミスを防ぎ、チーム運用でも安定します。ここでは初心者でも貼って動かせるテンプレートを、表構造とクラス、配列I/O、状態管理、ログ、フィルタ、スケジューラの順に解説します。
重要ポイントの深掘り
「表の前提を固定してから処理する」が肝心です。ヘッダー検証で列崩れを止め、Range→配列→一括書き戻しで速度と安定を確保します。状態は定義済みの列挙で扱い、遷移を一箇所に閉じ込めると、運用ルールが守られます。更新は必ずログに残し、後から辿れる安心を用意します。
シート構成とヘッダー前提を固定する
タスクシートのヘッダーと意味を決める
タスク表の最小ヘッダー例として、TaskId、Title、Assignee、Priority、DueDate、Status、UpdatedAt、UpdatedBy を用意します。TaskIdは重複禁止、Statusは定義済みのみ、日時はシリアル日付で扱います。最初にヘッダーを検証し、前提が崩れたら即停止します。
' ModValidate.bas
Option Explicit
Public Sub RequireHeaders(ByVal data As Variant, ByVal expected As Variant)
Dim i As Long: For i = LBound(expected) To UBound(expected)
Call IndexByHeader(data, CStr(expected(i)))
Next
End Sub
Public Function IndexByHeader(ByVal data As Variant, ByVal headerName As String) As Long
Dim j As Long
For j = 1 To UBound(data, 2)
If StrComp(CStr(data(1, j)), headerName, vbTextCompare) = 0 Then
IndexByHeader = j: Exit Function
End If
Next
Err.Raise 9100, , "ヘッダーがありません: " & headerName
End Function
VB重要ポイントの深掘り
「必要ヘッダーが揃っているか」を入口で固定するだけで、列追加・並べ替えによる事故をほぼ防げます。Fail Fast(早期停止)で原因が明確になり、現場の復旧が速くなります。
共通基盤とデータ入出力の用意
開始・終了の共通枠で必ず復帰できるようにする
' ModApp.bas
Option Explicit
Public Sub AppEnter(Optional ByVal status As String = "")
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
If Len(status) > 0 Then Application.StatusBar = status
End Sub
Public Sub AppLeave()
Application.StatusBar = False
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
VB配列で一括読み書きし、セル往復を撤廃する
' ModIO.bas
Option Explicit
Public Function ReadRegion(ByVal ws As Worksheet, ByVal topLeft As String) As Variant
ReadRegion = ws.Range(topLeft).CurrentRegion.Value
End Function
Public Sub WriteArray(ByVal ws As Worksheet, ByVal topLeft As String, ByVal arr As Variant)
ws.Range(topLeft).Resize(UBound(arr, 1), UBound(arr, 2)).Value = arr
End Sub
VB重要ポイントの深掘り
長いループでセルを1件ずつ読む・書くのはExcelを固める主因です。Range→配列→一括書き戻しに変えると、数千〜数万件でも実用速度になります。開始・終了枠で描画・イベント・再計算を止めるのも効果的です。
状態管理の型とルールを定義する
Statusを列挙型で固定し、文字列との往復を部品化する
' ModStatus.bas
Option Explicit
Public Enum TaskStatus
tsTodo = 1
tsDoing = 2
tsBlocked = 3
tsDone = 4
End Enum
Public Function StatusToText(ByVal st As TaskStatus) As String
Select Case st
Case tsTodo: StatusToText = "TODO"
Case tsDoing: StatusToText = "DOING"
Case tsBlocked: StatusToText = "BLOCKED"
Case tsDone: StatusToText = "DONE"
Case Else: StatusToText = "UNKNOWN"
End Select
End Function
Public Function TextToStatus(ByVal s As String) As TaskStatus
Select Case UCase$(Trim$(s))
Case "TODO": TextToStatus = tsTodo
Case "DOING": TextToStatus = tsDoing
Case "BLOCKED": TextToStatus = tsBlocked
Case "DONE": TextToStatus = tsDone
Case Else: Err.Raise 9200, , "不明なStatus: " & s
End Select
End Function
VB重要ポイントの深掘り
状態を自由記入にしないことが安定運用の第一歩です。列挙に寄せると、遷移の妥当性チェックや統計が楽になり、フィルタ・色分け・集計の一貫性が保てます。
タスククラスで1件の妥当性と更新を閉じ込める
1件を表すクラスで、更新時に監査情報を自動付与する
' CTask.cls
Option Explicit
Private pTaskId As String
Private pTitle As String
Private pAssignee As String
Private pPriority As Long
Private pDueDate As Date
Private pStatus As TaskStatus
Private pUpdatedAt As Date
Private pUpdatedBy As String
Public Property Get TaskId() As String: TaskId = pTaskId: End Property
Public Property Let TaskId(ByVal v As String)
If Len(Trim$(v)) = 0 Then Err.Raise 9301, , "TaskIdは必須"
pTaskId = Trim$(v)
End Property
Public Property Get Title() As String: Title = pTitle: End Property
Public Property Let Title(ByVal v As String): pTitle = Trim$(v): End Property
Public Property Get Assignee() As String: Assignee = pAssignee: End Property
Public Property Let Assignee(ByVal v As String): pAssignee = Trim$(v): End Property
Public Property Get Priority() As Long: Priority = pPriority: End Property
Public Property Let Priority(ByVal v As Long)
If v < 1 Or v > 5 Then Err.Raise 9302, , "Priorityは1~5"
pPriority = v
End Property
Public Property Get DueDate() As Date: DueDate = pDueDate: End Property
Public Property Let DueDate(ByVal v As Date): pDueDate = v: End Property
Public Property Get Status() As TaskStatus: Status = pStatus: End Property
Public Property Let Status(ByVal v As TaskStatus): pStatus = v: End Property
Public Property Get UpdatedAt() As Date: UpdatedAt = pUpdatedAt: End Property
Public Property Get UpdatedBy() As String: UpdatedBy = pUpdatedBy: End Property
Public Sub Touch(ByVal user As String)
pUpdatedAt = Now
pUpdatedBy = Trim$(user)
End Sub
VB重要ポイントの深掘り
妥当性(必須・範囲)をプロパティで受け止めると、不正値が下流に流れません。更新時に UpdatedAt/UpdatedBy を自動付与する「Touch」は、誰がいつ直したかの見える化に効きます。
タスク一覧を配列で扱い、CRUDやフィルタを実装する
シートから読み取り、クラス集合へ変換する
' CTaskRepository.cls
Option Explicit
Public Function Load(ByVal ws As Worksheet) As Collection
Dim data As Variant: data = ReadRegion(ws, "A1")
RequireHeaders data, Array("TaskId","Title","Assignee","Priority","DueDate","Status","UpdatedAt","UpdatedBy")
Dim tasks As New Collection
Dim r As Long
For r = 2 To UBound(data, 1)
If Len(Trim$(data(r, 1))) = 0 Then GoTo NextRow
Dim t As New CTask
t.TaskId = CStr(data(r, 1))
t.Title = CStr(data(r, 2))
t.Assignee = CStr(data(r, 3))
t.Priority = CLng(Val(data(r, 4)))
If IsDate(data(r, 5)) Then t.DueDate = CDate(data(r, 5))
t.Status = TextToStatus(CStr(data(r, 6)))
tasks.Add t, t.TaskId
NextRow:
Next
Set Load = tasks
End Function
VB一覧を配列に戻し、シートへ一括書き戻す
Public Sub Save(ByVal ws As Worksheet, ByVal tasks As Collection)
Dim rows As Long: rows = tasks.Count + 1
Dim cols As Long: cols = 8
Dim a() As Variant: ReDim a(1 To rows, 1 To cols)
a(1, 1) = "TaskId": a(1, 2) = "Title": a(1, 3) = "Assignee": a(1, 4) = "Priority"
a(1, 5) = "DueDate": a(1, 6) = "Status": a(1, 7) = "UpdatedAt": a(1, 8) = "UpdatedBy"
Dim i As Long
For i = 1 To tasks.Count
Dim t As CTask: Set t = tasks(i)
a(i + 1, 1) = t.TaskId
a(i + 1, 2) = t.Title
a(i + 1, 3) = t.Assignee
a(i + 1, 4) = t.Priority
a(i + 1, 5) = IIf(t.DueDate = 0, "", t.DueDate)
a(i + 1, 6) = StatusToText(t.Status)
a(i + 1, 7) = t.UpdatedAt
a(i + 1, 8) = t.UpdatedBy
Next
WriteArray ws, "A1", a
End Sub
VB重要ポイントの深掘り
配列への往復を標準化すると、フィルタや集計、ソートの実装が簡単になります。読みは妥当性チェック付き、書き戻しはヘッダー込みで統一すると、表が乱れてもすぐ復旧できます。
操作コマンド(追加・更新・完了・ブロック化)を用意する
追加(新規タスク)と更新の入口を整える
' ModCommands.bas
Option Explicit
Public Sub Cmd_AddTask(ByVal id As String, ByVal title As String, ByVal assignee As String, _
ByVal prio As Long, ByVal due As Date)
On Error GoTo EH
AppEnter "AddTask"
Dim repo As New CTaskRepository
Dim ws As Worksheet: Set ws = Worksheets("Tasks")
Dim list As Collection: Set list = repo.Load(ws)
If Exists(list, id) Then Err.Raise 9400, , "重複TaskId: " & id
Dim t As New CTask
t.TaskId = id: t.Title = title: t.Assignee = assignee
t.Priority = prio: t.DueDate = due: t.Status = tsTodo
t.Touch Environ$("UserName")
list.Add t, t.TaskId
repo.Save ws, list
AppLeave: MsgBox "追加しました: " & id: Exit Sub
EH:
AppLeave: MsgBox "失敗: " & Err.Description, vbExclamation
End Sub
Private Function Exists(ByVal list As Collection, ByVal id As String) As Boolean
On Error Resume Next: Dim x As Object: Set x = list(id): Exists = Not x Is Nothing: On Error GoTo 0
End Function
VB状態遷移(DOING、BLOCKED、DONE)を一箇所で管理する
Public Sub Cmd_ChangeStatus(ByVal id As String, ByVal newSt As TaskStatus, Optional ByVal note As String = "")
On Error GoTo EH
AppEnter "ChangeStatus"
Dim repo As New CTaskRepository, ws As Worksheet: Set ws = Worksheets("Tasks")
Dim list As Collection: Set list = repo.Load(ws)
Dim t As CTask: Set t = list(id)
If t Is Nothing Then Err.Raise 9401, , "Taskがありません: " & id
' 簡易遷移ルール(必要に応じて厳密化)
If t.Status = tsDone And newSt <> tsDone Then Err.Raise 9402, , "Doneから戻せません"
t.Status = newSt
t.Touch Environ$("UserName")
If Len(note) > 0 Then LogInfo "TaskNote", id & ": " & note
repo.Save ws, list
AppLeave: MsgBox "更新しました: " & id: Exit Sub
EH:
AppLeave: MsgBox "失敗: " & Err.Description, vbExclamation
End Sub
VB重要ポイントの深掘り
遷移ルールを入口で統一すると、勝手な状態変更が防げます。必要なら「DOING→DONEのみ」「BLOCKEDはDOINGからのみ」などの厳密ルールに拡張できます。更新時のログ記録を併用すると、監査に耐えます。
ログ・進捗・サマリを自動化する
変更ログをシートに残し、後から辿れるようにする
' ModLog.bas
Option Explicit
Public Sub LogInfo(ByVal action As String, ByVal detail As String)
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Worksheets("Log")
If ws Is Nothing Then Set ws = ThisWorkbook.Worksheets.Add: ws.Name = "Log"
On Error GoTo 0
Dim r As Long: r = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row + 1
ws.Cells(r, 1).Value = Format(Now, "yyyy-mm-dd HH:NN:SS")
ws.Cells(r, 2).Value = action
ws.Cells(r, 3).Value = detail
End Sub
VB状態別件数や期限超過の集計を配列で高速に出す
' ModSummary.bas
Option Explicit
Public Sub Run_TaskSummary()
On Error GoTo EH
AppEnter "TaskSummary"
Dim data As Variant: data = ReadRegion(Worksheets("Tasks"), "A1")
RequireHeaders data, Array("TaskId","Status","DueDate")
Dim idxSt As Long: idxSt = IndexByHeader(data, "Status")
Dim idxDue As Long: idxDue = IndexByHeader(data, "DueDate")
Dim todo As Long, doing As Long, blocked As Long, done As Long, overdue As Long
Dim r As Long
For r = 2 To UBound(data, 1)
Select Case UCase$(CStr(data(r, idxSt)))
Case "TODO": todo = todo + 1
Case "DOING": doing = doing + 1
Case "BLOCKED": blocked = blocked + 1
Case "DONE": done = done + 1
End Select
If IsDate(data(r, idxDue)) Then
If CDate(data(r, idxDue)) < Date And UCase$(CStr(data(r, idxSt))) <> "DONE" Then overdue = overdue + 1
End If
Next
With Worksheets("Summary")
.Range("A1").Value = "TODO": .Range("B1").Value = todo
.Range("A2").Value = "DOING": .Range("B2").Value = doing
.Range("A3").Value = "BLOCKED": .Range("B3").Value = blocked
.Range("A4").Value = "DONE": .Range("B4").Value = done
.Range("A6").Value = "Overdue": .Range("B6").Value = overdue
End With
AppLeave: Exit Sub
EH:
AppLeave: MsgBox "失敗: " & Err.Description, vbExclamation
End Sub
VB重要ポイントの深掘り
サマリは「配列で一気に回して出す」のが最短です。状態別の数や期限超過は運用上のボトルネック可視化に直結します。進捗のUI更新は1〜5%刻みで間引き、体感と速度のバランスを保ちます。
フィルタ・検索・ビュー生成で運用を楽にする
担当者や期限でフィルタしたビューを別シートへ作る
' ModViews.bas
Option Explicit
Public Sub Run_View_Assignee(ByVal name As String)
On Error GoTo EH
AppEnter "AssigneeView"
Dim data As Variant: data = ReadRegion(Worksheets("Tasks"), "A1")
RequireHeaders data, Array("TaskId","Title","Assignee","Priority","DueDate","Status")
Dim idxA As Long: idxA = IndexByHeader(data, "Assignee")
Dim out() As Variant: ReDim out(1 To UBound(data, 1), 1 To UBound(data, 2))
Dim w As Long: w = 1
Dim r As Long, c As Long
For c = 1 To UBound(data, 2): out(1, c) = data(1, c): Next
For r = 2 To UBound(data, 1)
If StrComp(CStr(data(r, idxA)), name, vbTextCompare) = 0 Then
w = w + 1
For c = 1 To UBound(data, 2): out(w, c) = data(r, c): Next
End If
Next
If w = 1 Then MsgBox "該当なし": AppLeave: Exit Sub
WriteArray Worksheets("View_Assignee"), "A1", out
AppLeave: Exit Sub
EH:
AppLeave: MsgBox "失敗: " & Err.Description, vbExclamation
End Sub
VB重要ポイントの深掘り
フィルタは配列で抽出してビューシートへ書くだけで十分実用的です。AdvancedFilterやListObjectでも可能ですが、配列は壊れにくく、伝票数が多くても速く終わります。
タイマー実行で「定期サマリ・期限チェック」を自動化する
OnTimeで一定間隔・決まった時刻に走らせる
' ModScheduler.bas
Option Explicit
Private gNext As Date, gStop As Boolean
Public Sub StartHourly()
gStop = False
ScheduleNext Now + TimeValue("1:00:00")
End Sub
Public Sub StopSchedule(): gStop = True: End Sub
Private Sub ScheduleNext(ByVal when As Date)
gNext = when
Application.OnTime gNext, "TickSummary", , True
End Sub
Public Sub TickSummary()
If gStop Then Exit Sub
Run_TaskSummary
ScheduleNext Now + TimeValue("1:00:00")
End Sub
VB重要ポイントの深掘り
OnTimeはExcelが開いている間だけ有効です。業務時間に合わせて予約し直す設計にすると、日次・時間単位のサマリ生成や期限警告を自動化できます。入口は必ず開始・終了枠で包み、失敗時も復帰させます。
例題の通し方と確認のポイント
最初にヘッダー検証と共通枠を導入してから動かす
Tasksシートにヘッダーを置き、Cmd_AddTaskで1件追加、Cmd_ChangeStatusでDOINGとDONEへ遷移、Run_TaskSummaryで件数と期限超過が反映されることを確認します。View_Assigneeで担当者ビューが出るかも見ます。
更新ログに誰がいつ何をしたかが残るかを確認する
Cmd_ChangeStatusでnoteを渡し、Logシートに時刻・Action・内容が記録されることを確認します。これで監査対応の安心が得られます。
まとめ:前提固定→配列I/O→状態列挙→ログの四点セットが安定運用の核
ヘッダー検証で前提を固定し、配列I/Oで速度と安定性を確保し、状態列挙で運用ルールを守り、ログで後から辿れるようにする。この四点が揃えば、Excelでのタスク管理は長期運用に耐えます。
