覚書
Option Explicit
Public Sub 飛行作業線表生成()
Const INPUT_SHEET_NAME As String = "入力データ"
Const OUTPUT_SHEET_NAME As String = "出力データ"
Const DATA_FIRST_ROW As Long = 61
Const OUT_FIRST_ROW As Long = 26
Const OUT_ROW_STEP As Long = 4
Const DEFAULT_TEMPLATE_BLOCKS As Long = 6
Const DAY_START_MIN As Long = 420 ' 07:00
Const DAY_END_MIN As Long = 1320 ' 22:00
Dim wsIn As Worksheet
Dim wsOut As Worksheet
Dim dicAircraft As Object
Dim dicOutRow As Object
Dim keys As Variant
Dim i As Long
Dim r As Long
Dim ac As String
Dim dataLastRow As Long
Dim originLeft As Double
Dim nextHourLeft As Double
Dim minuteWidth As Double
Dim requiredBlocks As Long
Dim existingBlocks As Long
On Error GoTo ErrHandler
Application.ScreenUpdating = False
Application.EnableEvents = False
Set wsIn = ResolveInputSheet(ThisWorkbook, INPUT_SHEET_NAME)
Set wsOut = ResolveOutputSheet(ThisWorkbook, OUTPUT_SHEET_NAME)
If wsIn Is Nothing Then
Err.Raise vbObjectError + 1000, , "入力データ用のシートを特定できませんでした。"
End If
If wsOut Is Nothing Then
Err.Raise vbObjectError + 1001, , "出力データ用のシートを特定できませんでした。"
End If
dataLastRow = GetLastInputRow(wsIn, DATA_FIRST_ROW)
Set dicAircraft = CreateObject("Scripting.Dictionary")
Set dicOutRow = CreateObject("Scripting.Dictionary")
For r = DATA_FIRST_ROW To dataLastRow
If IsValidRecord(wsIn, r) Then
ac = Trim$(CStr(wsIn.Cells(r, "D").Value))
If ac <> "" Then
If Not dicAircraft.Exists(ac) Then
dicAircraft.Add ac, ac
End If
End If
End If
Next r
If dicAircraft.Count = 0 Then
MsgBox "入力データが見つかりませんでした。", vbExclamation
GoTo ExitProc
End If
requiredBlocks = dicAircraft.Count
existingBlocks = GetStoredBlockCount(ThisWorkbook, DEFAULT_TEMPLATE_BLOCKS)
ClearGeneratedShapes wsOut
If requiredBlocks > existingBlocks Then
EnsureOutputCapacity wsOut, OUT_FIRST_ROW, OUT_ROW_STEP, DEFAULT_TEMPLATE_BLOCKS, existingBlocks, requiredBlocks
existingBlocks = requiredBlocks
SetStoredBlockCount ThisWorkbook, existingBlocks
Else
If existingBlocks < DEFAULT_TEMPLATE_BLOCKS Then
existingBlocks = DEFAULT_TEMPLATE_BLOCKS
End If
SetStoredBlockCount ThisWorkbook, existingBlocks
End If
ClearAircraftLabels wsOut, OUT_FIRST_ROW, OUT_ROW_STEP, existingBlocks
keys = dicAircraft.Keys
SortVariantArray keys
For i = LBound(keys) To UBound(keys)
ac = CStr(keys(i))
dicOutRow.Add ac, OUT_FIRST_ROW + (i * OUT_ROW_STEP)
wsOut.Range("C" & (OUT_FIRST_ROW + (i * OUT_ROW_STEP))).Value = ac
Next i
originLeft = wsOut.Range("J1").Left
nextHourLeft = wsOut.Range("P1").Left
minuteWidth = (nextHourLeft - originLeft) / 60#
For r = DATA_FIRST_ROW To dataLastRow
If IsValidRecord(wsIn, r) Then
ac = Trim$(CStr(wsIn.Cells(r, "D").Value))
If dicOutRow.Exists(ac) Then
Dim sMin As Long
Dim eMin As Long
Dim eteHour As Double
Dim leftPts As Double
Dim widthPts As Double
Dim outRow As Long
Dim topY As Double, topH As Double
Dim mainY As Double, mainH As Double
Dim noteY As Double, noteH As Double
Dim crewText As String
Dim missText As String
Dim eteText As String
Dim noteText As String
Dim kindText As String
Dim fillColor As Long
sMin = HHMMToMinutes(wsIn.Cells(r, "H").Value)
eMin = HHMMToMinutes(wsIn.Cells(r, "L").Value)
If sMin >= 0 Then
If eMin <= sMin Then
eteHour = GetNumericValue(wsIn.Cells(r, "P").Value)
If eteHour > 0 Then
eMin = sMin + CLng(eteHour * 60#)
End If
End If
If eMin > sMin Then
If sMin < DAY_END_MIN And eMin > DAY_START_MIN Then
If sMin < DAY_START_MIN Then sMin = DAY_START_MIN
If eMin > DAY_END_MIN Then eMin = DAY_END_MIN
outRow = CLng(dicOutRow(ac))
leftPts = originLeft + ((sMin - DAY_START_MIN) * minuteWidth) + 1
widthPts = ((eMin - sMin) * minuteWidth) - 2
If widthPts < 36 Then widthPts = 36
topY = wsOut.Rows(outRow).Top + 1
topH = wsOut.Rows(outRow).Height - 2
mainY = wsOut.Rows(outRow + 1).Top + 1
mainH = wsOut.Rows(outRow + 1).Height - 2
noteY = wsOut.Rows(outRow + 2).Top + 1
noteH = wsOut.Rows(outRow + 2).Height + wsOut.Rows(outRow + 3).Height - 2
crewText = BuildCrewText(wsIn, r)
missText = Trim$(CStr(wsIn.Cells(r, "BQ").Text))
eteText = GetEteText(wsIn.Cells(r, "P").Value)
noteText = BuildRemarksText(wsIn, r, dataLastRow)
kindText = GetKindText(wsIn, r)
fillColor = GetLineColor(kindText)
AddFreeTextBox wsOut, _
"senpyo_top_" & CStr(r), _
leftPts, topY, widthPts, topH, _
crewText, 6.5, False, _
msoAlignLeft, msoAnchorMiddle
AddMainBox wsOut, _
"senpyo_main_" & CStr(r), _
leftPts, mainY, widthPts, mainH, _
missText & " " & eteText, fillColor
AddFreeTextBox wsOut, _
"senpyo_note_" & CStr(r), _
leftPts, noteY, widthPts, noteH, _
noteText, 6, True, _
msoAlignLeft, msoAnchorTop
End If
End If
End If
End If
End If
Next r
ExitProc:
Application.ScreenUpdating = True
Application.EnableEvents = True
Exit Sub
ErrHandler:
Application.ScreenUpdating = True
Application.EnableEvents = True
MsgBox "エラー: " & Err.Description, vbCritical
End Sub
Private Function ResolveInputSheet(ByVal wb As Workbook, ByVal preferredName As String) As Worksheet
Dim ws As Worksheet
On Error Resume Next
Set ResolveInputSheet = wb.Worksheets(preferredName)
On Error GoTo 0
If Not ResolveInputSheet Is Nothing Then Exit Function
For Each ws In wb.Worksheets
If HasInputSignature(ws) Then
Set ResolveInputSheet = ws
Exit Function
End If
Next ws
End Function
Private Function ResolveOutputSheet(ByVal wb As Workbook, ByVal preferredName As String) As Worksheet
Dim ws As Worksheet
Dim nm As String
On Error Resume Next
Set ResolveOutputSheet = wb.Worksheets(preferredName)
On Error GoTo 0
If Not ResolveOutputSheet Is Nothing Then Exit Function
For Each ws In wb.Worksheets
nm = Replace(Trim$(ws.Name), " ", "")
If UCase$(nm) = "SHEET2" Then
Set ResolveOutputSheet = ws
Exit Function
End If
Next ws
For Each ws In wb.Worksheets
If HasOutputSignature(ws) Then
Set ResolveOutputSheet = ws
Exit Function
End If
Next ws
End Function
Private Function HasInputSignature(ByVal ws As Worksheet) As Boolean
Dim score As Long
score = 0
If SheetContainsText(ws, "NO") Then score = score + 1
If SheetContainsText(ws, "A/C") Then score = score + 1
If SheetContainsText(ws, "ETD") Then score = score + 1
If SheetContainsText(ws, "ETA") Then score = score + 1
If SheetContainsText(ws, "ETE") Then score = score + 1
HasInputSignature = (score >= 4)
End Function
Private Function HasOutputSignature(ByVal ws As Worksheet) As Boolean
Dim score As Long
score = 0
If SheetContainsText(ws, "機番") Then score = score + 1
If SheetContainsText(ws, "0700") Or SheetContainsText(ws, "0700") Then score = score + 1
If SheetContainsText(ws, "0800") Or SheetContainsText(ws, "0800") Then score = score + 1
HasOutputSignature = (score >= 2)
End Function
Private Function SheetContainsText(ByVal ws As Worksheet, ByVal searchText As String) As Boolean
Dim f As Range
On Error Resume Next
Set f = ws.Cells.Find(What:=searchText, _
After:=ws.Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
On Error GoTo 0
SheetContainsText = Not f Is Nothing
End Function
Private Function GetLastInputRow(ByVal ws As Worksheet, ByVal minimumRow As Long) As Long
Dim cols As Variant
Dim i As Long
Dim r As Long
Dim lastRow As Long
cols = Array("B", "D", "H", "L", "P", "BQ", "BY")
lastRow = minimumRow
For i = LBound(cols) To UBound(cols)
r = ws.Cells(ws.Rows.Count, cols(i)).End(xlUp).Row
If r > lastRow Then lastRow = r
Next i
If lastRow < minimumRow Then lastRow = minimumRow
GetLastInputRow = lastRow
End Function
Private Function IsValidRecord(ByVal ws As Worksheet, ByVal r As Long) As Boolean
Dim ac As String
Dim etd As Long
ac = Trim$(CStr(ws.Cells(r, "D").Value))
etd = HHMMToMinutes(ws.Cells(r, "H").Value)
If ac = "" Then Exit Function
If etd < 0 Then Exit Function
IsValidRecord = True
End Function
Private Function HHMMToMinutes(ByVal v As Variant) As Long
Dim s As String
Dim hh As Long
Dim mm As Long
On Error GoTo ErrHandler
If IsDate(v) Then
HHMMToMinutes = Hour(CDate(v)) * 60 + Minute(CDate(v))
Exit Function
End If
s = Trim$(CStr(v))
s = Replace(s, ":", "")
s = Replace(s, ":", "")
If s = "" Then
HHMMToMinutes = -1
Exit Function
End If
If Not IsNumeric(s) Then
HHMMToMinutes = -1
Exit Function
End If
s = Right$("0000" & s, 4)
hh = CLng(Left$(s, 2))
mm = CLng(Right$(s, 2))
If hh < 0 Or hh > 23 Or mm < 0 Or mm > 59 Then
HHMMToMinutes = -1
Else
HHMMToMinutes = hh * 60 + mm
End If
Exit Function
ErrHandler:
HHMMToMinutes = -1
End Function
Private Function BuildCrewText(ByVal ws As Worksheet, ByVal r As Long) As String
Dim cols As Variant
Dim i As Long
Dim txt As String
Dim buf As String
cols = Array("T", "AA", "AH", "AO", "AV", "BC")
For i = LBound(cols) To UBound(cols)
txt = Trim$(CStr(ws.Cells(r, cols(i)).Text))
If txt <> "" Then
If buf <> "" Then buf = buf & " "
buf = buf & txt
End If
Next i
BuildCrewText = buf
End Function
Private Function BuildRemarksText(ByVal ws As Worksheet, ByVal r As Long, ByVal lastRow As Long) As String
Dim rr As Long
Dim txt As String
Dim buf As String
Dim endRow As Long
endRow = r + 3
If endRow > lastRow Then endRow = lastRow
For rr = r To endRow
txt = Trim$(CStr(ws.Cells(rr, "BY").Text))
If txt <> "" Then
If buf <> "" Then buf = buf & vbLf
buf = buf & txt
End If
Next rr
BuildRemarksText = buf
End Function
Private Function GetKindText(ByVal ws As Worksheet, ByVal r As Long) As String
GetKindText = Trim$(CStr(ws.Cells(r, "B").Text))
End Function
Private Function GetLineColor(ByVal kindText As String) As Long
kindText = Trim$(kindText)
Select Case True
Case kindText Like "訓*"
GetLineColor = RGB(0, 112, 192)
Case kindText Like "試*"
GetLineColor = RGB(255, 0, 0)
Case kindText Like "要*"
GetLineColor = RGB(255, 255, 0)
Case UCase$(kindText) = "GND"
GetLineColor = RGB(255, 255, 255)
Case Else
GetLineColor = RGB(255, 255, 255)
End Select
End Function
Private Function GetEteText(ByVal v As Variant) As String
If Trim$(CStr(v)) = "" Then
GetEteText = ""
ElseIf IsNumeric(v) Then
GetEteText = Format$(CDbl(v), "0.0")
Else
GetEteText = Trim$(CStr(v))
End If
End Function
Private Function GetNumericValue(ByVal v As Variant) As Double
If IsNumeric(v) Then
GetNumericValue = CDbl(v)
Else
GetNumericValue = 0
End If
End Function
Private Sub ClearAircraftLabels(ByVal ws As Worksheet, ByVal firstRow As Long, ByVal rowStep As Long, ByVal blockCount As Long)
Dim i As Long
For i = 0 To blockCount - 1
ws.Range("C" & (firstRow + i * rowStep)).Value = ""
Next i
End Sub
Private Sub ClearGeneratedShapes(ByVal ws As Worksheet)
Dim i As Long
For i = ws.Shapes.Count To 1 Step -1
If Left$(ws.Shapes(i).Name, 7) = "senpyo_" Then
ws.Shapes(i).Delete
End If
Next i
End Sub
Private Sub EnsureOutputCapacity(ByVal ws As Worksheet, _
ByVal firstRow As Long, _
ByVal rowStep As Long, _
ByVal templateBlocks As Long, _
ByVal currentBlocks As Long, _
ByVal requiredBlocks As Long)
Dim sourceStartRow As Long
Dim destStartRow As Long
Dim blockIndex As Long
If requiredBlocks <= currentBlocks Then Exit Sub
sourceStartRow = firstRow + (templateBlocks - 1) * rowStep
For blockIndex = currentBlocks To requiredBlocks - 1
destStartRow = firstRow + blockIndex * rowStep
ws.Rows(sourceStartRow & ":" & sourceStartRow + rowStep - 1).Copy
ws.Rows(destStartRow & ":" & destStartRow + rowStep - 1).Insert Shift:=xlDown
Application.CutCopyMode = False
ws.Range("C" & destStartRow).Value = ""
Next blockIndex
End Sub
Private Function GetStoredBlockCount(ByVal wb As Workbook, ByVal defaultCount As Long) As Long
Dim nm As Name
Dim v As Variant
On Error Resume Next
Set nm = wb.Names("_SenpyoBlockCount")
On Error GoTo 0
If nm Is Nothing Then
GetStoredBlockCount = defaultCount
Exit Function
End If
On Error Resume Next
v = Evaluate(nm.RefersTo)
On Error GoTo 0
If IsNumeric(v) Then
If CLng(v) >= defaultCount Then
GetStoredBlockCount = CLng(v)
Else
GetStoredBlockCount = defaultCount
End If
Else
GetStoredBlockCount = defaultCount
End If
End Function
Private Sub SetStoredBlockCount(ByVal wb As Workbook, ByVal blockCount As Long)
On Error Resume Next
wb.Names("_SenpyoBlockCount").Delete
On Error GoTo 0
wb.Names.Add Name:="_SenpyoBlockCount", RefersTo:="=" & CStr(blockCount)
End Sub
Private Sub AddMainBox(ByVal ws As Worksheet, _
ByVal shpName As String, _
ByVal leftPts As Double, _
ByVal topPts As Double, _
ByVal widthPts As Double, _
ByVal heightPts As Double, _
ByVal textValue As String, _
ByVal fillColor As Long)
Dim shp As Shape
Set shp = ws.Shapes.AddShape(msoShapeRectangle, leftPts, topPts, widthPts, heightPts)
shp.Name = shpName
shp.Placement = xlMoveAndSize
With shp
.Line.Visible = msoTrue
.Line.ForeColor.RGB = RGB(0, 0, 0)
.Line.Weight = 0.75
.Fill.Visible = msoTrue
.Fill.ForeColor.RGB = fillColor
.Fill.Solid
With .TextFrame2
.TextRange.Text = textValue
.TextRange.ParagraphFormat.Alignment = msoAlignLeft
.VerticalAnchor = msoAnchorMiddle
.MarginLeft = 3
.MarginRight = 1
.MarginTop = 1
.MarginBottom = 1
.WordWrap = msoTrue
.AutoSize = msoAutoSizeTextToFitShape
With .TextRange.Font
.Size = 11
.Bold = msoTrue
.Fill.ForeColor.RGB = RGB(0, 0, 0)
End With
End With
End With
End Sub
Private Sub AddFreeTextBox(ByVal ws As Worksheet, _
ByVal shpName As String, _
ByVal leftPts As Double, _
ByVal topPts As Double, _
ByVal widthPts As Double, _
ByVal heightPts As Double, _
ByVal textValue As String, _
ByVal fontSize As Double, _
ByVal shrinkToFit As Boolean, _
ByVal horizontalAlign As MsoParagraphAlignment, _
ByVal verticalAnchor As MsoVerticalAnchor)
Dim shp As Shape
Set shp = ws.Shapes.AddTextbox(msoTextOrientationHorizontal, leftPts, topPts, widthPts, heightPts)
shp.Name = shpName
shp.Placement = xlMoveAndSize
With shp
.Line.Visible = msoFalse
.Fill.Visible = msoFalse
With .TextFrame2
.TextRange.Text = textValue
.TextRange.ParagraphFormat.Alignment = horizontalAlign
.VerticalAnchor = verticalAnchor
.MarginLeft = 2
.MarginRight = 1
.MarginTop = 1
.MarginBottom = 1
.WordWrap = msoTrue
If shrinkToFit Then
.AutoSize = msoAutoSizeTextToFitShape
Else
.AutoSize = msoAutoSizeNone
End If
With .TextRange.Font
.Size = fontSize
.Bold = msoFalse
.Fill.ForeColor.RGB = RGB(0, 0, 0)
End With
End With
End With
End Sub
Private Sub SortVariantArray(ByRef arr As Variant)
Dim i As Long
Dim j As Long
Dim tmp As Variant
If IsEmpty(arr) Then Exit Sub
For i = LBound(arr) To UBound(arr) - 1
For j = i + 1 To UBound(arr)
If CStr(arr(i)) > CStr(arr(j)) Then
tmp = arr(i)
arr(i) = arr(j)
arr(j) = tmp
End If
Next j
Next i
End Sub