Here are two macro procedures – LagFilter and LeadFilter – for creating and applying a filter to show only tasks with leads or lags above a certain threshold value.
Using Lags and/or Leads (i.e. negative lags) in Project Scheduling is discouraged for good reasons. In most project scheduling software it is easy to identify violations by creating a filter to show only tasks with Lags (or Leads) in their predecessor relationships.
In Microsoft Project, lags are indicated by the presence of a “+” character in the task’s predecessors field. Here is the corresponding filter specification.
You can augment the filter to show tasks on both sides of the lags:
Leads are indicated (in MSP) by the presence of a “-” character in the task’s predecessors field. Here is the corresponding filter specification (showing both sides of the leads).
Unfortunately, these simple filters don’t help to differentiate high-lag/lead relationships from low-lag/lead relationships. All of them are lumped together in the same filter. It is possible to create filters for only the highest lead/lag values using a number of custom fields with complex formulas. It is far simpler, however, to create the necessary filters using vba/macros.
Here are two macro procedures – LagFilter and LeadFilter – for creating and applying a filter to show only tasks with leads or lags above a certain threshold value. Choosing a zero-value threshold leads to the same results as the simple filters above. These procedures work by examining the lag of each predecessor relationship of every task in the active project, comparing it to the specified threshold value. If the lag is high enough, then the Flag6 field of the task will be set to “yes”. At the end, a new filter is made and applied. Note that these macros will overwrite any values in the Flag6 field of your project, unless Flag6 is already controlled by a formula. (In that case, the macros will crash with an error.) You may need to edit the macros to select a different Flag field.
[I’ve edited these macros… a) to allow the user to select whether to show both sides or only one side (the successor) of each lead/lag; b) to avoid null filters (i.e. blank screens) by applying the filter only when leads or lags matching the criterion are found; and c) to allow work-time, elapsed-time, or percentage-based lead/lag criteria.]
To apply these, simply copy and paste them into a new module in your Project Visual Basic editor (VBE). (I typically keep these modules in the global.mpt file, though that practice is not always recommended.) You can then run them directly from the VBE or from custom buttons that you link to the macros through the “Customize the Ribbon” dialog.
Sub LagFilter() 'Copyright 15August2018 by T.Boyle PE, PSP 'This macro collects user input and filters the active project to display only tasks 'with dependency lags that are less than the user-specified threshold. The threshold 'may be specified in units of working time, elapsed time, or percentage. The filter 'is applied using the Flag6 custom field. 'FLAG6 WILL BE OVER-WRITTEN, IF POSSIBLE,OR THIS MACRO WILL CRASH. Dim t As Task Dim d As TaskDependency Dim LagUnits As String Dim ElapsedUnits As Boolean Dim LagThreshold As Double Dim LagLimit As String Dim SuccsOnly As Boolean Dim Filtername As String Dim Found As Boolean Found = False 'Get lag units from user LagUnits = (InputBox("Enter lag units (m,h,d,w,mo,em,eh,ed,ew,emo,%):")) 'Validate units Select Case LagUnits Case "m", "h", "d", "w", "mo", "em", "eh", "ed", "ew", "emo", "%" 'Get the filter limit from user LagThreshold = (InputBox("Enter lag threshold (" & LagUnits & "):")) LagLimit = LagThreshold & " " & LagUnits If Left(LagUnits, 1) = "e" Then ElapsedUnits = True Case Else MsgBox ("Invalid lag units entered (case-sensitive). Aborting.") Exit Sub End Select 'Convert units Select Case LagUnits Case "m" 'proceed Case "h" LagThreshold = LagThreshold * 60 Case "d" LagThreshold = LagThreshold * 60 * ActiveProject.HoursPerDay Case "w" LagThreshold = LagThreshold * 60 * ActiveProject.HoursPerWeek Case "mo" LagThreshold = LagThreshold * 60 * ActiveProject.HoursPerDay * ActiveProject.DaysPerMonth Case "em" 'proceed Case "eh" LagThreshold = LagThreshold * 60 Case "ed" LagThreshold = LagThreshold * 60 * 24 Case "ew" LagThreshold = LagThreshold * 60 * 24 * 7 Case "emo" LagThreshold = LagThreshold * 60 * 24 * 30 Case "%" 'proceed End Select If MsgBox("Display both Predecessors and Successors?" & vbCrLf & "(""Yes"" shows each lag twice. Default " _ & "shows Successors Only)", vbQuestion + vbYesNo + vbDefaultButton2, "???") = vbYes Then SuccsOnly = False Filtername = "HasLagsAboveThreshold" Else SuccsOnly = True Filtername = "HasPredecessorLagsAboveThreshold" End If For Each t In ActiveProject.Tasks If Not t Is Nothing Then Call ClearT(t) For Each d In t.TaskDependencies If (d.To = t) Or (SuccsOnly = False) Then If (d.Lag > 0 And LagThreshold = 0) Or (d.Lag >= LagThreshold And LagThreshold > 0) Then If (d.LagType = 19 And LagUnits = "%") Then Call MarkT(t, Found) ElseIf (d.LagType Mod 2 = 1 And (Not ElapsedUnits) And (LagUnits <> "%")) Then Call MarkT(t, Found) ElseIf (d.LagType Mod 2 = 0 And ElapsedUnits) Then Call MarkT(t, Found) End If End If End If Next d End If Next t If Found Then FilterEdit Name:=Filtername, TaskFilter:=True, Create:=True, OverwriteExisting:=True, FieldName:="Flag6", _ Test:="equals", Value:="Yes", ShowInMenu:=True, ShowSummaryTasks:=True FilterApply Name:=Filtername MsgBox ("Filter applied: " & Filtername & vbCrLf & "Filter Threshold: " & LagLimit) Else MsgBox ("No lags found above threshold (" & LagLimit & "). No filter applied") End If End Sub Sub LeadFilter() 'Copyright 15August2018 by T.Boyle PE, PSP 'This macro collects user input and filters the active project to display only tasks 'with dependency leads (i.e. negative lags) that are less than the user-specified threshold. 'The threshold may be specified in units of working time, elapsed time, or percentage. 'The filter is applied using the Flag6 custom field. 'FLAG6 WILL BE OVER-WRITTEN, IF POSSIBLE,OR THIS MACRO WILL CRASH. Dim t As Task Dim d As TaskDependency Dim LeadUnits As String Dim ElapsedUnits As Boolean Dim LeadThreshold As Double Dim LeadLimit As String Dim SuccsOnly As Boolean Dim Filtername As String Dim Found As Boolean Found = False 'Get Lead units from user LeadUnits = (InputBox("Enter Lead units (m,h,d,w,mo,em,eh,ed,ew,emo,%):")) 'Validate units Select Case LeadUnits Case "m", "h", "d", "w", "mo", "em", "eh", "ed", "ew", "emo", "%" 'Get the filter limit from user LeadThreshold = (InputBox("Enter Lead threshold (" & LeadUnits & "):")) LeadLimit = LeadThreshold & " " & LeadUnits If Left(LeadUnits, 1) = "e" Then ElapsedUnits = True Case Else MsgBox ("Invalid Lead units entered (case-sensitive). Aborting.") Exit Sub End Select 'Convert units Select Case LeadUnits Case "m" 'proceed Case "h" LeadThreshold = LeadThreshold * 60 Case "d" LeadThreshold = LeadThreshold * 60 * ActiveProject.HoursPerDay Case "w" LeadThreshold = LeadThreshold * 60 * ActiveProject.HoursPerWeek Case "mo" LeadThreshold = LeadThreshold * 60 * ActiveProject.HoursPerDay * ActiveProject.DaysPerMonth Case "em" 'proceed Case "eh" LeadThreshold = LeadThreshold * 60 Case "ed" LeadThreshold = LeadThreshold * 60 * 24 Case "ew" LeadThreshold = LeadThreshold * 60 * 24 * 7 Case "emo" LeadThreshold = LeadThreshold * 60 * 24 * 30 Case "%" 'proceed End Select If MsgBox("Display both Predecessors and Successors?" & vbCrLf & "(""Yes"" shows each Lead twice. Default " _ & "shows Successors Only)", vbQuestion + vbYesNo + vbDefaultButton2, "???") = vbYes Then SuccsOnly = False Filtername = "HasLeadsAboveThreshold" Else SuccsOnly = True Filtername = "HasPredecessorLeadsAboveThreshold" End If For Each t In ActiveProject.Tasks If Not t Is Nothing Then Call ClearT(t) For Each d In t.TaskDependencies If (d.To = t) Or (SuccsOnly = False) Then If (d.Lag < 0 And LeadThreshold = 0) Or (d.Lag <= -1 * LeadThreshold And LeadThreshold > 0) Then If (d.LagType = 19 And LeadUnits = "%") Then Call MarkT(t, Found) ElseIf (d.LagType Mod 2 = 1 And (Not ElapsedUnits) And (LeadUnits <> "%")) Then Call MarkT(t, Found) ElseIf (d.LagType Mod 2 = 0 And ElapsedUnits) Then Call MarkT(t, Found) End If End If End If Next d End If Next t If Found Then FilterEdit Name:=Filtername, TaskFilter:=True, Create:=True, OverwriteExisting:=True, FieldName:="Flag6", _ Test:="equals", Value:="Yes", ShowInMenu:=True, ShowSummaryTasks:=True FilterApply Name:=Filtername MsgBox ("Filter applied: " & Filtername & vbCrLf & "Filter Threshold: " & LeadLimit) Else MsgBox ("No Leads found above threshold (" & LeadLimit & "). No filter applied") End If End Sub Sub ClearT(t As Task) t.Flag6 = "No" End Sub Sub MarkT(ByRef t As Task, ByRef Found As Boolean) t.Flag6 = "Yes" Found = True End Sub
Neither the simple filter nor the macro provided here implements the algorithm used by the Project Logic Checker in our BPC Logic Filter Add-In, which incorporates a slightly different premise. That is: a relationship (or combination of relationships) with (positive or negative) lag may be the most effective and efficient method for modeling the true sequential restraints of the work, but only when the lag represents a relatively small proportion of the durations of the related tasks. Thus, the Project Logic Checker flags tasks where the relationship lead/lag exceeds a certain percentage of the associated task durations.
I’ve modified and expanded these macros to accommodate specific requests from some readers, but I don’t plan any further changes other than debugging of mistakes. Please don’t ask.
Thank-you for publishing these and other macros. I have pulled a variety down for use in my Global and advised other staff in my organization to take a look at this blog for advice and macros.