Using Microsoft Project To Manage Projects
Using Microsoft Project To Manage Projects
Project management is an important part of a planner's responsibilities. For the most part, planners manage projects in an informal way: they have a deadline to meet, and they monitor progress of the project intuitively. However, sophisticated tools are available for managing projects more formally. These instructions introduce you to Microsoft Project, software that helps to plan and track projects. You will walk through instructions for project planning with Project, and then for project tracking. Since time is limited, you will only work with a subset of the features available with Project. In managing projects, there are essentially two steps: project planning, and project tracking. Projects consist of several tasks, each of a certain duration, that are to be carried out in a certain sequence. Project planning happens at the start of a project; at this stage the planner identifies the tasks involved, estimates task duration, and guesses at task sequence. As the project progresses, some of this may change: task sequences may have to be altered, and tasks may take longer or shorter than originally planned. Project tracking involves recording these various changes, and making appropriate changes to the proposed schedule; tracking must take places throughout the life of the project.
Initial Steps
To launch Project, double-click on the Microsoft Project icon. (Macintosh users will find this on URBAN-SERVER1 in the MAC_APPS volume; PC users will find this in the .) When the Welcome! message box is displayed, click on the button next to Start a New Project. The various parts of the Project screen are as follows:
Below the menu bar is the tool bar. This contains a number of buttons that can be clicked to carry out certain common operations. Below the toolbar, to the left side of the screen is the Task Table. This looks like a spreadsheet, with information about each task in a row. To the right of the Task Table is the Gantt Chart. Using horizontal bars, the Gantt Chart graphically represents the task schedule.
To change the relative sizes of each of these parts of the screen, drag their boundaries to the new location. To increase the height of the Gantt Chart, drag the lower boundary of the Chart. To change the size of portions within each part (for instance, the width of the Name column in the Task Table), drag the bounding element of that portion. To change the width of the Name column in the Task Table, drag the vertical line on the right side of the column to a new location.
Several options for Project functioning and appearance can be controlled using the Tools| Options... menu item. For this exercise, I suggest you change the following settings in the way Project functions. In the Options dialog box, choose the Schedule tab, and set the different options like this:
Choose the File|Summary Info... dialog box, and in the resulting dialog box choose the Project tab. In the Start Date field, type 3/4/96 (the scheduled start of the imaginary project we will be scheduling in this tutorial). For the examples in this tutorial to work, you should also set "today's date" to fit with the rest of the dates used. In the Current Date field, type 3/1/96.
Project Planning
Project planning involves identifying the tasks involved, their durations, and the relationships among the tasks. Microsoft Project then automatically computes the total project duration and other related information. To enter information about a task, click on an empty cell in the Task Table, type in the name of the task, and press enter. The task name appears in the cell, and a default duration of one day is assigned. To change the default duration, click on the cell in question, type in the correct duration, and press enter.
Enter the list of tasks shown below together with their duration:
To insert a new task, select in the Task Table the row above which the new task is to be inserted. Choose the Insert|Insert Task menu item to insert a blank row in the Task Table. Type in the name of the new task and its duration.
Add two new tasks: Project start at the head of the list, and Project finish at the end of the list. Assign them both durations of 0 days. A special kind of task is the milestone. This is usually has a duration of one or zero days, and is used to indicate an event rather than a task. To create a milestone, double-click on its cell in the Task Table. In the resulting Task Information dialog box, choose the Advanced tab and click on the box next to Mark Task as Milestone.
Project start and Project finish are milestones in the project we are working on. Mark them as such. You can create hierarchies of tasks: main tasks and their subtasks. To create a hierarchy with a main task and its subtasks, list the subtasks immediately below the main task. Highlight the subtasks, and click on the demote button, , in the toolbar. The highlighted subtasks are indented, and the main task is shown in bold. This action can be reversed by highlighting a subtask and clicking on the promote button, . , in the toolbar. To hide the subtasks under a main task, click on the , in the toolbar; reverse this action using the expand button, collapse button,
Insert a new task labeled Study phase above Conduct survey and make it the main task containing the subtasks Conduct survey, Collect secondary data, and Analyze data. The Task Table should look as follows:
To identify the sequence of tasks, double-click on each task in turn, and designate predecessor tasks: those that logically precede the current task. In the Task Information dialog box, choose the Predecessors tab. As shown in the graphic below, first click on a blank cell under Task Name, then click on the drop button indicated, and from the drop list select the predecessor task. (You can also create links using a toolbar button, but I will not go into that.) As links are created between tasks, the Gantt Chart will change to reflect these links.
Set the predecessors for each task as shown in the graphic below. (The number refers to the ID number of the predecessor task; for instance, task ID 1 is Project Start.
To change the timescale of the Gantt Chart, choose Format|Timescale.... In the resulting Timescale dialog box, set the major and minor scale units as follows:
To change the appearance of the Gantt Chart, choose Format|Gantt Chart Wizard... To display critical tasks, for example: in Step 2, choose Critical Path, and click Next. In Step 9, choose Dates, and click Next. Keep clicking Next in the remaining steps, until you get to click on Format It. Click Exit to close the Wizard. The chart should appear as follows:
The red bars indicate that these tasks are critical: they must be completed on time if the project is not to be delayed beyond the target date. (For some reason, in some cases Project does not correctly identify critical tasks, at least those tasks that would intuitively appear to be critical. It pays to visually inspect your Gantt Chart.) The default relationship between a predecessor and a successor task is as follows: when the predecessor Finishes the successor Starts (FS). This may not be what is desired. Instead, for instance, five days after the predecessor Starts, you may wish to Start the successor (SS, with 5 days lag time). To change the relationship between two linked tasks, double-click on the successor task in the Task Table. In the Task Information dialog box, choose the Predecessors tab. Click on Type cell of the target predecessor. Click on the button with the downward arrow that appears under Predecessors. From the list that drops down, choose the appropriate relationship. In the Lag cell of the predecessor, type in the desired lag time, and press enter. Lag time is the number of days between the
finish or start of the predecessor and the finish or start of the successor task; it can be a positive number or a negative number.
Set Write proposal to start 5 days after Analyze results starts. Note how the duration of the entire project is reduced. The project has been fast-tracked.] Once the initial project schedule has been determined, it has to be recorded as the baseline plan; future adjustments to the schedule will be compared to this baseline. To do this, choose Tools|Tracking|Save Baseline... In the resulting dialog box, choose Baseline and Entire Project, and click the OK button. (In this dialog box, you can also save upto five different interim project schedules.) The current view (whatever is on the screen) can be either printed (choose File| Print Preview and then print) or copied and pasted into a Microsoft Word document: To copy the Chart, first highlight all the tasks that must be included in the picture to be copied. Then, click on the button in the toolbar. In the resulting dialog box, choose For Printer for high-resolution pictures. You can now switch to the other application and paste the picture. (Please note that if the application window is larger than 640x480 pixels--the size of a 14" monitor--the chart will be truncated. I suggest you reduce the window to 640x480 before copying the chart.) To save the project information, use File|Save As for the first time, and File|Save for later times. To exit Microsoft Project, use File|Exit.
Project Tracking
To track projects in Microsoft Project, a distinction is drawn between planned, actual, and scheduled information about tasks. Planned task dates (start, finish, duration) are those that are assigned in the initial schedule that is created. As the project progresses, actual dates are recorded; these may or may not be different from the planned dates. In response to changing circumstances, the project may have to be rescheduled; this new information is recorded as scheduled dates. The three different types of information make it possible to keep track of how a project is progressing vis--vis the initial plan. Since we will be mimicking a future tracking session, the first step is to set the current date. Choose the File|Summary Info... menu item. Next to the Current Date, type in the date when the tracking is taking place. (For this exercise, set the current date to 4/1/96.) Next, enter the actual completion data for tasks that are already complete or under way. To do this, click on each task that is complete, and select Tools|Tracking|Update Tasks... In the Update Task dialog box, type the percentage of work completed or the actual finish date.
For this exercise, Identify site finished on 3/13/96; Meet Residents and Collect secondary data are 100% complete; Conduct survey is only 60% complete. Note that there is a bar through each task showing how much is complete. If the project is on schedule, then all tasks finishing before today's date should have a bar extending their whole length. Tasks currently under way should have a bar extending till the current date. Clearly there is a problem with Conduct survey; it is behind schedule.
We must adjust our schedule to allow for the slippage (assuming we do not have additional people to work on this task). Here is how it can be done: Increase the time allotted to the task so that 60% of the task represents the number of days till today's date. Click on the task Conduct survey, and choose the Tools|Tracking| Update Tasks... menu item. In the Update Task dialog box, increase the Remaining Duration to 10, and click OK. The task is now going to take longer, but the completion bar is still short of today's date. Choose the Tools|Tracking| Update Tasks... menu item again, and in the Update Task dialog box, change the % Complete back to 60%. Now, the schedule is approximately as we want it:
Choose a different view to get a better sense of how the project has changed. Choose View|More Views... In the More Views dialog box, choose Tracking Gantt Chart (this is the last in the list), and click on Apply. (You will have to change the Timescale as described earlier.) The Gantt Chart now looks as follows:
The gray bars are the baseline schedule; the upper bars are the current schedule. The impact of the work slippage is now clearly visible. This ends the tutorial. Microsoft Project allows much more powerful planning and tracking of projects including the resources consumed in completing tasks. It also allows charts and reports to be very highly customized. I will let you discover all these features on your own.
Dim ts as Tasks Dim t as Task Set ts = ActiveProject.Tasks For Each t in ts If Not t is Nothing Then If Not t.Summary Then 'do something End If End If Next t
By putting your code in the middle of this structure (where it says "do something" you can be sure it will be applied to all the regular tasks in the project and won't generate an error when it hits a blank line.
Posted on April 12, 2005 12:38 PM | Comments (2)
Application.Projects
Projects
The Project object I use most often is the ActiveProject. ActiveProject is simply the project you are currently working on in project. If you have multiple projects open then it is the one which is in front and which has the cursor active in it. Most of the time you want your code to operate on the ActiveProject and not some other project so code typically looks like this:
Set ts as ActiveProject.Tasks
There are cases where you DO want to work on all the projects that are open. In this case you would forgo using ActiveProject and refer to them individually. You can use For..Next to go through all of the open projects:
Dim proj1 as Project Dim proj2 As Project Set proj1 = ActiveProject Set proj2 = FileOpen("c:\myfilename.mpp") If proj2.Tasks(5).Finish = proj1.Tasks(5).Finish Then msgbox "Task 5 is unchanged." End if End Sub
You can use an index to refer to a specific project, though the index of the project is dependent on the order in which the files were opened, so there is room for some surprises here:
Dim subproj As Subproject Dim myproj As Project 'go through all the subprojects in the file For Each subproj In ActiveProject.Subprojects 'open them all in turn FileOpen (subproj.Path) Set myproj = ActiveProject 'when open do something to the file FileClose Next subproj
The Projects collection has a small number of properties including count, parent and item. It also has a method to add a project. Project and SubProject have too many properties to describe here, but eventually I'll get around to covering some of the more interesting ones.
Posted on April 13, 2005 7:35 AM | Comments (1)
If xlApp Is Nothing Then 'Start new instance Set xlApp = CreateObject("Excel.Application") If xlApp Is Nothing Then MsgBox "Can't Find Excel, please try again.", vbCritical End 'Stop, can't proceed without Excel End If Else Set xlR = Nothing Set xlApp = Nothing Set xlBook = Nothing Set xlApp = CreateObject("Excel.Application") If xlApp Is Nothing Then MsgBox "Can't Find Excel, please try again.", vbCritical End 'Stop, can't proceed without Excel End If End If xlapp.Visible = False Set xlBook = xlapp.Workbooks.Add Set xlSheet = xlBook.Worksheets.Add xlSheet.Name = ActiveProject.Name
I use CreateObject here rather than GetObject based on Microsoft's recommendation in this article. If you use GetObject you may get this error:
This problem is caused by the fact that the project gives a numerical value of 4294967296 (2 to the 32nd power - 1) if the field is "NA" (blank). Why it does this rather than giving a value of 0 I do not know, however once you know that it uses this number you can write a formula which accounts for it. The solution is to use an iif statement. The syntax for an iif statement is as follows:
Iif([Baseline Finish] > 50000, "There is no baseline for this task", [Baseline Finish]-[Finish])
Another alternative is to use ProjDateValue to evaluate the data stored in the baseline. Since an empty baseline shows "NA" for dates such as Baseline Finish, you can test for it directly.
I want to subtract one date from another in Project. There are a number of ways to do date subtraction. The first is to simply subtract one from the other like this:
[Finish]-[Start]
On a one day task which starts and ends the same day this will return a value of .38 which is somewhat useful, but as in the section above it takes some conversion to make sense of it. .38 days = 8 hours. This approach also has some problems if you are subtracting across a non-working time such as a weekend or holiday. Or if the task ends on the next day. Then the value will be quite unexpected.
So there is another method that Project provides to do date math. It is to use the ProjDateDiff function. The syntax is as follows:
ProjDateDiff([Start],[Finish])
Note that the field order is different than the original equation. For a positive result you put the soonest date as the first parameter and the latest date as the second.
Posted on April 21, 2005 12:22 PM | Comments (2)
Dim mytask As Task Dim myoutlinelevel As Integer myoutlinelevel = 1 While myoutlinelevel < 10 For Each mytask In ActiveProject.Tasks If Not (mytask Is Nothing) Then If mytask.OutlineLevel = myoutlinelevel Then mytask.Text2 = mytask.OutlineParent.Text2 & " | " & mytask.Name End If End If Next mytask myoutlinelevel = myoutlinelevel + 1 Wend End Sub
The trouble with this approach is that it runs through the entire set of tasks one time for each level of heirarchy that you want to name. And, you have to define how many levels deep you want to go. Even if you have only one level of heirarchy this code will still read and check each task 10 times. And if you have more than 10 levels, the tasks beyond the 10th level will not get labeled correctly. The solution is to use recursion. With recursion we ask the program to name all the children of a task and then name all the children of that task all the way down until there are no more children. We do this by having a procedure which calls itself. Here we are using a procedure called "kids" which calls the same procedure for all of the child tasks - when it runs using those child tasks it will get all their child tasks etc. etc. etc.
Sub kids(ByRef t As Task) Dim kid As Task t.Text2 = t.OutlineParent.Text2 & " | " & t.Name For Each kid In t.OutlineChildren kids kid Next kid End Sub
Pretty simple. Now the only question is how to get it started off. We can't put the code to start it inside the procedure or it will keep restarting itself. So we write a procedure which sets the starting task and then calls the kids procedure:
Sub recursionExample() Dim t As Task Set t = ActiveSelection.Tasks(1) kids t End Sub Sub kids(ByRef t As Task) Dim kid As Task t.Text2 = t.OutlineParent.Text2 & "-" & t.Name For Each kid In t.OutlineChildren kids kid Next kid End Sub
That is all there is to it. I have an example of how recursive techniques can be used to trace dependencies on my website which adds some additional logic so it can trace forward or backward or only critical tasks, but the basic principle is the same. One thing to be aware of before you use recursion is that whatever you are recursing through does require some limit or stopping point. In this case it stops when there are no further children. In the Trace macro it stops at the end of the chain of dependencies. However, if you are not careful you can construct something that will continue indefinitely. To avoid this, try setting a breakpoint so you can step through the code the first few times to make sure it doesn't break. And always back up your files before you start.
Posted on May 13, 2005 10:57 PM | Comments (0)
Critical Resources
I keep losing track of these things so I'm going to just put all the links here:
Project 2003 Object Model Project 2003 XML Schema Building a Project Server PDS Extension with dot net. PDS Reference download I used to have a link to the code for the "Export Timescaled Data to Excel" add-in, but I can't find it right now. [Update: Here it is Download Timescaled Data Source Code] Any of these links lead you to the MSDN documentation. I suggest you browse around the other topics while you are there.
Posted on May 20, 2005 3:24 PM | Comments (0)
6 MOD 4 = 2 12 MOD 4 = 0
By putting them together you can break numbers into their component parts. Doing date math is an easy way to see how this works. Let's let "Days" be a number of days. We want to know how many weeks and how many days it is. The following formula would return how many weeks and how many days there are in that amount of time.
Days\7 & " Weeks, " & Days MOD 7 & " Days"
If Days is 23 days, then the result would be:
3 Weeks, 2 Days
Using References
Save both projects and ensure that both projects are open. In VBE editor open Project explorer which lists all the projects. By default all vba projects are named as "vbaproject". The global.MPT file is named "ProjectGlobal". If you want to reference ProjectGlobal you don't need to do anything else. If you want to reference another project you will need to change the name to make it unique. To do this select the vbaproject corresponding to project1.mpp, click on properties icon, change the vba project name to vbaproject1. Do the same to project2 but call it vbaproject2. Next select vbaproject2 and go to the Tools menu, select References, you can see vbaproject1 and ProjectGlobal listed in the references dialog box. Set the reference to vbaproject1. Now you can call any macro in vbaproject1 from project2.mpp as follows : This is Macro1 stored in Project1/Module1
References are automatically opened if available. You can call macros in any type of modules including standard modules The macro appears in auto list as a method You can pass arguments
The disadvantages of this method are: You can not use this method to call a project macro from some other application like vb6, Excel etc. You will get an error if Project1 is renamed, deleted or moved. This is my method of choice when the module is in the global.MPT file.
Application.Macro "Project1.mpp!Module1.Macro1"
The advantages of this method are: It is simple You can call macros in any type of modules including standard modules You can use this method to call project macro from some other application like vb6, Excel etc. The disadvantages of this method are: You can not pass arguments The macro does not appear in auto list of methods You need to ensure that source project is open
Projects("Project1.mpp").macro1
An alternative is to create a project object for Project1.mpp and use:
The disadvantages of this method are: You can not call macros in standard modules The macro does not appear in auto list of methods You need to ensure that source project is open To summarize. I prefer the first method, but the other two are valid alternatives, though the third is a bit fussy. Thanks to Venkata Krishna for pointing these three methods out to me many years ago.
Posted on June 16, 2005 7:41 AM | Comments (0)
use Win32::OLE; use Win32::OLE::Variant; use strict; my $app = Win32::OLE->GetObject("SomeProject.mpp") or die "Couldn't open project"; my $project = $app->{Projects}->Item(1);
Posted on July 25, 2005 7:09 PM | Comments (1) | TrackBacks (1)
Dim ts As Tasks Dim tempString As String Dim key As Long Set ts = ActiveProject.Tasks key = InputBox("Enter Key between 1 and 256") For Each t In ts If Not t Is Nothing Then tempString = t.Text1 eNcode tempString, key t.Text1 = tempString End If Next t MsgBox "Done" End Sub Private Sub eNcode(ByRef eText As String, ByRef eKey As Long) Dim bData() As Byte Dim lCount As Long bData = eText For lCount = LBound(bData) To UBound(bData) bData(lCount) = bData(lCount) Xor eKey Next lCount eText = bData End Sub
What this macro does is prompt the user for a key which is used with the XOR operator to encrypt the data. You can read more about how this works here. If you like, you can expand on this and use a more sophisticated algorithm, but this should stop most casual readers from decrypting your data unless they have read this article. The problem with this approach is that the algorithm used for encryption is exposed whenever anyone hits ALT+F11 and views the macro code. You can avoid this by keeping the code in your global.mpt file. However, that would prevent any others from being able to encrypt the data. So we need to take a second step and protect the macro code itself. 1. 2. 3. 4. 5. From the VBA editor, right-click on the module where the code is located. From the shortcut menu, select VBAProject Properties (If it is in your global.mpt file it will be ProjectGlobal Properties) On the Protection tab, check the Lock Project For Viewing check box. Enter a password and verify it in the boxes at the bottom of the tab. Click OK.
Now your code is protected. I should warn you that even this is not secure. It is possible to break the password that you have used to protect the macro and with knowledge of the algorithm you used it may be possible for someone to break the password which you have used to encrypt the data, so if something is really secret don't even bother to do this, just keep the file locked somewhere secure and don't share it. But for casual users this should be sufficient to keep them from snooping around.
Posted on July 29, 2005 12:16 PM | Comments (0)
AUGUST 2, 2005
One common problem people face with project is that there are three classes of custom fields; task fields, assignment fields and resource fields. If you are in a resource view and you are looking at the Text1 field it won't have the same information as if you are looking at the Text1 field in a task view. This is true with reports as well. The solution is to copy over the items from the one field to the other. This is painful unless you automate it. So, to reduce the pain here is VBA code which does it for you:
Sub CopyTaskFieldToAssignment() 'This macro copies information in the task text5 field 'into the assignment text5 field so that is can 'be displayed in a usage view or in a report. 'Modify the line noted below to fit your needs Dim t As Task Dim ts As Tasks Dim a As Assignment Set ts = ActiveProject.Tasks For Each t In ts If Not t Is Nothing Then For Each a In t.Assignments 'change the following line to use 'for a different custom field a.Text5 = t.Text5 Next a End If Next t End Sub
Pretty easy. This one should have no problems because each assignment only has a single task that it references. However, going the other way could be a problem as each task can have several assignments. To sidestep the issue we can simply concatenate all of the text from all of the assignments. The code would then look like this:
Sub CopyAssignmentFieldToTask() Dim t As Task Dim ts As Tasks Dim a As Assignment Set ts = ActiveProject.Tasks For Each t In ts If Not t Is Nothing Then t.Text5 = "" For Each a In t.Assignments 'change the following line to use 'for a different custom field t.Text5 = t.Text5 & ", " & a.Text5 Next a End If Next t End Sub
The line t.Text5 = t.Text5 & ", " & a.Text5 appends whatever is in the assignment field to whatever is already existing in the task field. Some simple modifications can make it work to copy from the resource fields.
Posted on August 2, 2005 4:39 PM | Comments (2)
Sub Macro1() ' Macro Macro1 ' Macro Recorded 8/29/05 by yourusername. SelectRow Row:=-6, Height:=2 ZoomTimescale Selection:=True End Sub
This code is OK, but it is not reusable because each time you run it, it will select two rows which are 6 rows above where ever your cursor is. Chances are you don't want that. So we edit it and remove that row.
Sub Macro1() If Not ActiveSelection = 0 Then ZoomTimescale Selection:=True End If End Sub
Now if we have a task or tasks selected this code will zoom the view to show the entire duration of the longest task. An obvious next step is to assign this to a toolbar button so you can zoom the selection with a single click. A more complicated example is exporting a file to excel. I can never remember the exact syntax off the top of my head, but turning on the macro recorder and exporting makes it easy. Here is the code I get while creating a map and saving a file. Note: for formatting reasons I've added several line continuation characters "_" so that the long lines will fit on the screen correctly.
TableName:="Task_Table1", _ FieldName:="Name", ExternalFieldName:="Name", _ ExportFilter:="Critical", _ ImportMethod:=0, _ HeaderRow:=True, _ AssignmentData:=False, _ TextDelimiter:=Chr$(9), _ TextFileOrigin:=0, _ UseHtmlTemplate:=False, _ TemplateFile:="C:\...\Centered Mist Dark.html", _ IncludeImage:=False MapEdit Name:="Map 1", _ DataCategory:=0, FieldName:="Finish", _ ExternalFieldName:="Finish_Date" MapEdit Name:="Map 1", DataCategory:=0, _ FieldName:="% Complete", _ ExternalFieldName:="Percent_Complete" MapEdit Name:="Map 1", DataCategory:=0, _ FieldName:="Resource Names", ExternalFieldName:="Resource_Names" FileSaveAs Name:="C:\foo.xls", FormatID:="MSProject.XLS5", _ map:="Map 1" End Sub
You can see that the macro recorder makes this a lot easier than typing this in from scratch.
Posted on August 29, 2005 12:34 PM | Comments (0)
SEPTEMBER 1, 2005
OCTOBER 6, 2005
Sometimes we want Project to calculate a schedule a little differently than it does naturally. At least a few times I've had people ask if it is possible to set the start of a specific task based on the date the first it's predecessors completes. With a little code it is easily possible. This code takes the predecessors of a selected tasks and figures out which is the one which will finish first. Then it applies negative lag to the dependencies between itself and the other predecessors. The comments in the code (lines starting with an apostrophe ') describe what is being done
Set t = ActiveSelection.Tasks(1) 'Set lag to 0 to remove any previous lags Set tdeps = t.TaskDependencies For Each tdep In tdeps If tdep.To = t.ID Then tdep.lag = 0 End If Next tdep CalculateProject 'Find earliest predecessor Set ps = t.PredecessorTasks Set earliest = ps(1) For Each p In ps If p.Finish <= earliest.Finish Then Set earliest = p End If Next p 'Set lag so it covers the greatest task variance l = -Application.DateDifference(earliest.Finish, t.Start) 'Apply that lag to all predecessors except for the earliest For Each tdep In tdeps If tdep.To = t.ID And tdep.From <> earliest.ID Then tdep.lag = l End If Next tdep CalculateProject End Sub
Pretty simple.
Posted on October 6, 2005 7:27 PM | Comments (0)
DateDiff(interval, date1, date2[, firstdayofweek[, firstweekofyear]]) interval is required and is the time unit you want the result returned in:
yyyy = Year q = Quarter m = Month y Day of year d = Day w = Weekday ww = Week h = Hour n = Minute s = Second date1 and date2 are required and are the two dates you are working with. firstdayofweek and firstdayofyear are optional and will change the defaults from Sunday and the week which contains January 1 to whatever else you might choose. Now this is a pretty powerful and useful function, but when you are calculating schedule dates it becomes a problem. The issue is easily illustrated with the example of a weekend. Suppose you want the working hours for a task between now and two working days from now. DateDiff would work fine if both days are in the same workweek, but once the interval spans a weekend then the calculation is wrong. To resolve this, Project VBA has the DateDifference function. DateDifference is considerably simpler. Here is the syntax:
Application.DateDifference(StartDate, FinishDate, Calendar) StartDate and FinishDate are required. They are the start and finish dates used.
Calendar is optional. It can be a resource or task base calendar object. The default value is the calendar
of the active project. The result of this function is the duration in minutes. You can convert the minutes into days by dividing by 480 (for an 8 hour day or more accurately dividing by 60 * HoursPerDay. HoursPerDay is a Project property which reflects the current definition of workdays in Project. You can also divide by the HoursPerWeek and DaysPerMonth functions if you want to use longer timescales. Application.DateDifference is what you would use in VBA code. In Project custom field formulas the situation is almost exactly the same. However instead of being called DateDifference, they named the function ProjDateDiff. The arguments are the same:
and the result is also returned in minutes. Converting this into usable time periods IS different though. Custom formulas offer the [Minutes Per Day] and <[Minutes Per Week] constants to do conversion to days and weeks. There is no month conversion available. Here are a couple of examples: Project VBA DateDifference example:
NOVEMBER 8, 2005
Project_Open (which acts like the On_Open event you may be familiar with), Activate, BeforeClose, BeforeSave, Calculate, Change, Deactivate
These events apply to the project and are fairly simple to implement. There are also a number of application level events which allow you to specify actions based on changes in individual fields. They are somewhat more difficult to implement, but if you can follow the example below you will have no problems. The list of application events includes:
ProjectAfterSave, ProjectAssignmentNew, ProjectBeforeAssignmentChange, ProjectBeforeAssignmentChange2, ProjectBeforeAssignmentDelete, ProjectBeforeAssignmentDelete2, ProjectBeforeAssignmentNew, ProjectBeforeAssignmentNew2, ProjectBeforeClearBaseline, ProjectBeforeClose, ProjectBeforeClose2, ProjectBeforePrint, ProjectBeforePrint2, ProjectBeforeResourceChange, ProjectBeforeResourceChange2, ProjectBeforeResourceDelete, ProjectBeforeResourceDelete2, ProjectBeforeResourceNew, ProjectBeforeResourceNew2, ProjectBeforeSave, ProjectBeforeSave2, ProjectBeforeSaveBaseline,
ProjectBeforeTaskChange, ProjectBeforeTaskChange2, ProjectBeforeTaskDelete, ProjectBeforeTaskDelete2, ProjectBeforeTaskNew, ProjectBeforeTaskNew2, ProjectCalculate, ProjectResourceNew, ProjectTaskNew, NewProject, LoadWebPage, ApplicationBeforeClose.
Private Sub Project_Open(ByVal pj As Project) MsgBox "Project Just Opened" End Sub
You can replace the MsgBox code with whatever you want to happen when you open the project. For example it could call a macro which you have already written. Similar macros can be written to take action before printing or saving. For example you may want to copy certain data into a custom field before saving the file so that you can restore it later if necessary.
Application Events:
The example above only requires pasting some code in a single place. However using application events requires a few more steps. The first step is to create a new class module and declare an object of type Application with events. Creating the class module is done by going to the insert menu and selecting "ClassModule" as shown here:
When you have done this, double click on the class module and declare the object by using the following code:
Writing the procedure is similar to writing any macro. The image below shows a simple example using the ProjectBeforeTaskChange event.
Note that NewVal holds the value that the user has input. The original value can still be referenced in the standard way (t.Name). The code to cut and paste is shown next.
Private Sub App_ProjectBeforeTaskChange(ByVal t As Task, _ ByVal Field As PjField, _ ByVal NewVal As Variant, _ Cancel As Boolean) If Field = pjTaskName And NewVal = "foo" Then MsgBox ("you can not change the name to foo") MsgBox ("The old name was " & t.Name) Cancel = True End If
Note that a space followed with an underscore is used to break a single line of code. This is called a line continuation and I use it to keep code readable when there is a long line. Now that you have written the code, there is one final step to undertake before using it. Before the procedures will run you must connect the declared object (in this case the one we called "App") in the class module with the Application object. It sounds complicated, but it is really rather simple. First we declare a new object based on the class module. In this case our class module is named TEvent. Code to do this would be something like:
After you run the InitializeApp procedure, the App object in the class module points to the Microsoft Project Application object, and the event procedures in the class module will run when the events occur. Most of the time you will want to do the initalization when your project opens so the events will work from the start, but you can put this information in any typical module which holds some of your macros. The screenshot below shows what it would look like if we want it to initialize when the project file opens.
This example shows how events can be in a specific project file. You can also have this code in the global.mpt file so that things occur whenever you open Project as an application. The Project_Open event is also useful to distribute macros or other standard formatting. For example, you could have a Project_Open macro which sets up an environment for you (copying views tables etc.) using the organizer. When a user opens the file, those items could be copied into their global.mpt file. (Note: You might notice this is
familiar. That is because I'm porting the material from my old Microsoft Project Macros website to this one bit by bit so that everything can be found in one place)
Posted on November 8, 2005 11:15 AM | Comments (1)
Sub writemyproperties2() Dim MyString as String Dim MyFile As String Dim fnum as Integer Dim myIndex As Integer Dim myProj As Project Dim skipme As Boolean skipme = False Set myProj = ActiveProject
Declaring the variables is optional in Project VBA, but it can help prevent some problems later. If a variable isn't defined then Project treats it as a "variant" which it needs to allocate more memory for. Project also makes some assumptions about how to treat a variant in different circumstances. In most cases it assumes correctly, but there is always the chance that it may make the wrong assumption so it is good practice to be explicit about your variables. Following this we set the initial values for some of the variables. The next step is to set up a file to write to. The next bit of code sets the file name and then uses the FreeFile() method to create a file. We open the file to write to it. We choose to open the file "for output". This allows us to write to it. The other possible modes are "for input" which would allow us to read data from the file and "for append" which appends data to the file. This last mode is useful for logs.
MyFile = "c:\" & ActiveProject.Name & "_properties.txt" fnum = FreeFile() Open MyFile For Output As fnum
Once we have created and opened the file we can write to it. In the "for output mode we do this with a "Write" statement. First we write a line as a header and then a blank line. Each "write" statement creates a new line. The comma is required.
Now we use a "For...Next" loop to go through all the properties. Because there are some gaps in the numbering of the properties, the macro would fail when it hits gaps in the sequence. We solve this by putting in an error handler. When the code errors then it goes to the code which we want to execute "On
Error"
For myIndex = 1 To myProj.BuiltinDocumentProperties.Count skipMe = False On Error GoTo ErrorHandler MyString = (myIndex & _ ": " & _ myProj.BuiltinDocumentProperties(myIndex).Name & _ ": " & _ myProj.BuiltinDocumentProperties(myIndex).Value) If skipMe = False Then Write #fnum, MyString End If Next myIndex
You can see that each loop through will write the property number, the name and the value of the property. You may be wondering why the if...then" statement is there. I include it because of the way the errors are handled. Our error code is very simple. It just resumes on the next statement. Since the next statement writes a line to the file it would result in re-writing the previous property again, so we add a line to the error handler which sets skipMe to true and then we do not write the property. From here the code for the custom document properties is the same.
Write #fnum, "-----------------------------------------------" Write #fnum, Write #fnum, "Custom Properties" Write #fnum, For myIndex = 1 To myProj.CustomDocumentProperties.Count skipMe = False On Error GoTo ErrorHandler MyString = (myIndex & _ ": " & _ myProj.CustomDocumentProperties(myIndex).Name & _ ": " & _ myProj.CustomDocumentProperties(myIndex).Value) If skipMe = False Then Write #fnum, MyString End If Next myIndex
We need to close the file after this.
Close #fnum
And finally we have the code to handle errors and end the procedure.
Sub writemyproperties2() 'This macro exports all the built-in and custom project properties 'to a text file. It lists the index of the property, the name and the value. 'It demonstrates the use of a simple error handler to skip the errors that 'occur when a property is not defined or used. 'Copyright Jack Dahlgren, Nov 2005 Dim Dim Dim Dim Dim Dim MyString as String MyFile As String fnum as Integer myIndex As Integer myProj As Project skipme As Boolean
Set myProj = ActiveProject skipMe = False 'set location and name of file to be written MyFile = "c:\" & ActiveProject.Name & "_2properties.txt" 'set and open file for output fnum = FreeFile() Open MyFile For Output As fnum 'write project info and then a blank line Write #fnum, "Built In Properties" Write #fnum, For myIndex = 1 To myProj.BuiltinDocumentProperties.Count skipMe = False On Error GoTo ErrorHandler MyString = (myIndex & _ ": " & _ myProj.BuiltinDocumentProperties(myIndex).Name & _ ": " & _ myProj.BuiltinDocumentProperties(myIndex).Value) If skipMe = False Then Write #fnum, MyString End If Next myIndex Write #fnum, "-----------------------------------------------" Write #fnum, Write #fnum, "Custom Properties" Write #fnum, For myIndex = 1 To myProj.CustomDocumentProperties.Count skipMe = False On Error GoTo ErrorHandler MyString = (myIndex & _ ": " & _ myProj.CustomDocumentProperties(myIndex).Name & _ ": " & _ myProj.CustomDocumentProperties(myIndex).Value) If skipMe = False Then Write #fnum, MyString End If
Next myIndex Close #fnum ErrorHandler: skipMe = True Resume Next End Sub
Posted on November 11, 2005 8:29 AM | Comments (0)
The first thing that you need to do is to set a reference to the Microsoft Project Object Library. To do this go to 'the "Tools Menu" in the Visual Basic Editor (hit ALT+F11 to get there from Excel).
This should bring up a dialog box showing all of the available libraries. You will probably have to scroll down a bit to find the project library. Here you can see that I'm using Project 2002 as it is version 10.
Once you have set the reference the code is pretty simple:
Sub openMSProjectFromExcel() Set pjApp = CreateObject("MSProject.application") 'this checks to see if a valid object has been created. If not it pops up 'a warning and then quits. Users without Project installed will see this message.
If pjApp Is Nothing Then MsgBox "Project is not installed" End End If 'now that we have an application we make it visible pjApp.Visible = True 'we add a new project Set newProj = pjApp.Projects.Add 'we set the title property (you can do whatever you want here. newProj.Title = "My New Project" 'we make the new project the active project Set ActiveProject = newProj 'and finally we add a new task to the project newProj.Tasks.Add ("My First Task") End Sub
Obviously you will want to do more where this leaves off, but it should be enough to get you started with using Project from Excel.
Posted on November 18, 2005 11:39 AM | Comments (0)
DECEMBER 7, 2005
Sub AllTasksLoop() For Each Task In ActiveProject.Tasks 'do something Next Task End Sub
You would simply add a test to see if the task really exists.
Sub AllNonBlankTasks() For Each Task In ActiveProject.Tasks If Not Task Is Nothing Then 'do something End If Next Task End Sub
Another thing you might want to do is to eliminate external tasks. These are a sort of "ghost" task and don't have all of the information or properties of a real task. They merely point to the project file which has the real task. We can filter them in a similar way.
If Not Task Is Nothing Then If Not Task.ExternalTask Then 'do something End If End If Next Task End Sub
Now, you might think, "Why do I need two if statements? Can't I combine them? , but you can't. If the task is a blank task, then it will cause an error when it is checking the second condition. Blank tasks do not have the .ExternalTask property so the check for blank tasks always must come first on its own line. You CAN combine checks for summary tasks on the same line as for external tasks. This is commonly done when you are summing values from tasks. Since the summary task often has the sum of the tasks below it, summing and including it will give you an incorrect answer. Combining the two conditions with a booleanOR will do the trick.
Sub AllNonBlankInternalIndividualTasks() For Each Task In ActiveProject.Tasks If Not Task Is Nothing Then If Not (Task.ExternalTask Or Task.Summary) Then MsgBox Task.Name End If End If Next Task End Sub
Posted on December 7, 2005 9:53 AM | Comments (3)
MARCH 1, 2006
MARCH 2, 2006
Free Monte Carlo Simulator For Microsoft Project - Want to help out?
I don't think I've mentioned it here, but if you are interested in Monte Carlo Simulation for MS Project (and who isn't?!) then you can download my quick and dirty simulator here: Microsoft Project Monte Carlo Simulator Since it is just a VBA macro, the source is there for all to see and modify. Have at it! And if you make any improvements, please consider sharing them with me and others. Any updates will be posted with acknowledgement to the contributor. I've had this version up for a couple of years now and will keep it up until this function comes built into Project.
Posted on March 2, 2006 4:51 PM | Comments (1)
Extremities
An interesting rebuttal of the Agile Manifesto here: Burningbird Technology is already Extreme The points about belief, group behavior and diversity are worth investing a few minutes to digest. Fortunately it is leavened with one-liners like this: "The tech equivalent of The Beach Boys" It dovetails nicely with Glen's latest post on evidence. When am I ever going to write anything thoughtful again?
Posted on April 13, 2006 3:30 PM | Comments (0)
MAY 2, 2006
Eric Landes has a brief code snippet on using C3 to automate project here:Corporate Coder : Project 2003 Adding Tasks via C# All I can say is that C# is slightly less than elegant in the way it handles optional parameters. For what an idea of what I'm talking about, here is how to invoke FileOpen:
m_ProjectProApp.FileOpen ( "MyProjectName", missingValue, missingValue, missingValue, missingValue, missingValue, missingValue, missingValue, missingValue, missingValue, missingValue, PjPoolOpen.pjDoNotOpenPool, missingValue, missingValue, missingValue, missingValue);
Just a bit awkward, wouldn't you say?
Posted on May 2, 2006 6:19 AM | Comments (2)
Cell, CellColor property, CellColor as PjColor New for Cell object. Background color of the cell. In Project 2003 as GroupCriterion property only. Cell, FontColor property, FontColor as PjColor New for Cell object. Foreground color of the cell font. In Project 2003 as GroupCriterion property only. Cell, Pattern property, Pattern as PjBackgroundPattern New for Cell object. Background pattern of the cell. In Project 2003 as GroupCriterion property only. Global, Application Font method Boolean Font(Optional Variant Name, Optional Variant Size, Optional Variant Bold, Optional Variant Italic, Optional Variant Underline, Optional Variant Color, Optional Variant Reset, Optional Variant CellColor, Optional Variant Pattern) Changed: added parameters CellColor and Pattern.
See that? Cells have colors and patterns. Welcome to 1995! Now to install the beta and see if we are still limited to 16 colors... PS: Don't take the 1995 comment too harshly. From the other new things in Project Server 2007 it is clear that the team has focused on solid improvements to functionality
Posted on May 24, 2006 10:03 AM | Comments (1)
Drag a combo box from the toolbox onto the form. It will be called "ComboBox1" if it is the first one. You can rename it using properties (and you should) but for this example we are not. Now to write some code for the form. Hit F7 or go to the view menu and select "Code". This should bring up a window which you can type code in. The first thing is some code which will initialize the values for the combo box. I've called this simply "InitializeME" but name it what you like. The code in this sub has one line for each value you want to add. Here the values are hardcoded, but you could substitute it with code which reads values from a file stored somewhere or an array of values you have gathered from the project file itself. You are limited only by your imagination. Here is the code:
ComboBox1.AddItem "Faa" ComboBox1.AddItem "Fay" ComboBox1.AddItem "Fat" ComboBox1.AddItem "Fun" 'Set the initial value. Without this it will be blank ComboBox1.Value = "Select a Value" End Sub
Next you need some code to start the form. To keep this example pathetically simple we will only do two things here, initialize the combo box values and then show the form. You could do it the other way around, but better to have the form ready to go when the user first sees the form. To do this insert a module (again from the insert menu). Then call the code to initialize the combo box, then show the form. The code is very simple:
With the combo box selected on the left and the "change" procedure selected on the right the shell of a procedure will open and we will type code into it. This code sets the text1 field of selected tasks to the value that is selected in the combo box. When you change the combo box value it will run. Selecting a value in the combo box counts as a change. Here is the code:
Private Sub ComboBox1_Change() If Not ComboBox1.Value = "Select a Value" Then ActiveSelection.Tasks.Text1 = ComboBox1.Value End If End Sub
The only trick in there is the if then statement so that the text1 value does not change when you are setting the value of the combo box when it first displays. The code that you can put in the procedure again is only limited by what you can imagine. It could be extensive and create and initialize a new project with default values etc. As much as you are capable of. Warning: I have not shown any error handling here. If you had no tasks selected you would have gotten an error or if there was no project open or ... so be careful and write procedures to handle that. And always test against whatever scenarios you can imagine.
Posted on January 19, 2007 1:22 PM | Comments (0)
FEBRUARY 2, 2007
trim(mystring)
to return "There is space in front and in back" The problem was that converting a Microsoft Project Unique ID to a string adds a leading space to the string (why? I don't know, but it does) so you can use trim to obtain just the characters you want. Trim is not just a VBA function, you can use it in Excel formulas, MS Project custom field formulas and just about any programming language I've seen. I've used it extensively with spreadsheets that contain data pasted in from other sources.
Posted on February 2, 2007 11:50 AM | Comments (1)
FEBRUARY 5, 2007
Sub WriteToATextFile 'first set a string which contains the path to the file you want to create. 'this example creates one and stores it in the root directory MyFile = "c:\" & "whateveryouwant.txt" 'set and open file for output fnum = FreeFile() Open MyFile For Output As fnum 'write project info and then a blank line. Note the comma is required Write #fnum, "I wrote this" Write #fnum, 'use Print when you want the string without quotation marks Print #fnum, "I printed this" Close #fnum End Sub
There are a few other pieces of the puzzle to clarify. Open for has a number of mode parameters. You can open for input (read only), output (write only), append (write at end - good for logs), binary and random (read/write default). You can also control this by an optional access keyword (read, write, read write) but why bother if you have the mode set. Freefile is used to get the filenumber of the next free file.
Posted on February 5, 2007 2:44 PM | Comments (0)