|< Day Day Up >|
Working with Tasks
Tasks are at the heart of Project, and you often need to make changes across a large number of tasks. Using a macro to do this is far more efficient than making all the individual changes by hand. Because of how common and powerful such macros can be, understanding how to cycle through all the tasks in a project and perform or not perform an operation on them is an essential skill.
For example, imagine a project in which several components are being developed. The workflow for each component is the same, so you would like to create just one schedule and then cut and paste for the other components . This works well when you keep the original outline structure, but you might sort , filter, or otherwise manipulate the tasks so that the original context is lost. To solve this, you can create a macro that joins the component name (which is in the summary task) to the task name so that the task name is a complete description.
Before you start coding, you need to think about the steps involved. You want to concatenate the name of each task's parent task with the task's own name and then store it in a text field (for example, text20 ). However, you don't want to do this on tasks that are summary tasks or external tasks (that is, those that are linked to other projects). More importantly, you don't want to do it on blank lines because the macro would fail if it tried to operate on a task that isn't there.
When you finish, you want to display a message stating that the macro has finished and how many changes were made. Giving feedback like this helps you and other users figure out what is happening and whether things worked as expected.
To create the TaskSummaryName macro, follow these steps:
As you type this code, you will notice some of the features of the VBE. When you press Enter after typing the Sub statement (the first line), the VBE automatically adds an End Sub statement at the end of the code. All code between the Sub and End Sub is kept together. ( Sub is short for subroutine .)
After you type Dim T As , a drop-down list appears, displaying all the possible entries you can type next. Typing T for Task is enough to uniquely select Task. Press the Tab, Enter, or spacebar key to have the VBE fill in the rest of the command for you. This will then happen all through your code. Whenever there is a limited set of commands, the VBE displays that list. This feature is very helpful in assisting you to spell the command correctly or to choose the right option. If the choices are not what you expect, you are not specifying the right object or using the right method.
The example defines three variables:
Dim T As Task Dim Ts As Tasks Dim intTcounter As Integer
Variables are essential to using VBA because they allow you to store and manipulate data. They are temporary storage locations for whatever you are working on or for information you want to carry over to a later operation. Variables are reserved in memory when they are defined or first used. Because you might want to store different types of information, variables have what is called a type. Some common types are strings, which hold alphanumeric data; integers, which hold integer numerical data; and tasks, which hold the necessary information about tasks, including all the properties of a task.
VBA is very flexible in allowing you to create variables at any time and without being strict about determining what type of variables they are. Although this increases the chances that you will get your code to run the first time, it also increases the chances that the code won't run the way you want it to. For example, say you have spelled a variable one way in one place and then spelled it a different way in another place. Visual Basic does not know that you meant the same variable when you typed these different words, and you end up with two separate variables. When you refer to what you think is the correct variable, you might not get the result you expected. The following code will not work the way it should because of the misspelling in the third line:
Sub MixUp() MyTask = ActiveProject.Task(1) MyTaks.Name = "New Name" Msgbox (MyTask.Name) End sub
One way to avoid having VBA create extra variables is to have the VBE apply stricter rules. The way to do this is to start a module with the words Option Explicit . This tells the VBE to require you to explicitly define the variables before you use them. If this option is set, when you try to run the code for the first time, you get a warning that the variable has not been defined. It highlights the code where the problem has occurred so that you can fix it. Having the VBE check your work like this can save you extra work later, when you are debugging your code.
Because you are going to perform the same operation on all the tasks, you need a variable to be the temporary location for that task. Use T and define it as a task as shown here:
Dim T As Task
You also need a variable to hold the collection of tasks that you are working on. For this you can use Ts and define it as a collection of tasks as seen here:
Dim Ts As Tasks
It is not necessary to define either of these variables because you will be working on only one task and one collection of tasks at a time. However, if you were working on more than one open task or project, it would be necessary to use these variables.
The final variable should hold the count of the tasks. You can call this intTcounter and define it as an integer because you are sure that there will be no fractional values:
Dim intTcounter As Integer
If there were, you would use another numeric type, such as single, long, double, or currency.
Each statement begins with the word Dim , which is short for dimension . Following that is the variable name, which can be whatever you choose, as long as it is not a name used by another object and is not a reserved word. Finally, you specify the variable type. You can use Dim with many variables in a single statement by separating them with commas:
Dim myTask, yourTask, anotherTask as Task Dim strMessage, strName, strWords as String
After you define a variable, you can set its values. Setting values is done in different ways for different types of variables. For example, intTcounter is a simple number and it has no properties other than its own value, so you set its value using a simple equals sign:
intTcounter = 0
Ts is a collection of tasks, an object variable, so you need to set it equal to another object. You do this using the set keyword. You set Ts to be the collection of tasks in the active project (the one that is active in the application you are working on):
Set Ts = ActiveProject.Tasks
The VBE alerts you if you do this incorrectly by giving you an "object required" error if you try to set a numerical or string variable equal to an object. It gives an "invalid use of property" error if you forget to use the set keyword when defining an object.
Setting the variable T is done implicitly:
For Each T In Ts
Because you are using a For Next construction, Visual Basic uses T to represent the task it is currently working on and then reassigns it to the next task when it is through with the first. This structure is discussed in more detail in the next section.
Variables can be declared anywhere in code, as long as they are declared before they are used. However, it is easiest to work with them when they are grouped in blocks at the beginning of code.
The next part of the macro is the heart of it. You want to operate on each task. You could name each task individually and then operate on it, but then your code would have a line for each task. To do this efficiently , there are a number of control structures you can use. In this case, you can use a For Each Next structure:
'Step through each task For Each T In Ts 'Only perform the operation on non-blank tasks If Not T Is Nothing Then 'Only perform on non-external tasks with Parents If Not T.ExternalTask And T.OutlineLevel > 1 Then 'combine the parent and task names and store in Text20 T.Text20 = T.OutlineParent.Name & "_" & T.Name intTcounter = intTcounter + 1 Else T.Text20 = T.Name intTcounter = intTcounter + 1 End If End If Next T
This structure enables you to step through each task in a collection of tasks without having to name them individually. It begins with the For Each and ends with Next . Each time through the loop, the operations within the loop are performed on a task, and when the code reaches the Next statement, the next task is selected and the operations are performed. This continues until all the tasks have been operated on. A few different operations occur within the loop.
Testing and Nesting
Another control structure in this macro is the If Then Else structure. This structure enables you to perform operations only if certain criteria are met. For example, you don't want to do anything to tasks that are external tasks or to tasks that don't have parent tasks. You also don't want to try to do anything on blank lines because they do not have names, and if you try to set their properties, you will get an error:
'Only perform the operation on non-blank tasks If Not T Is Nothing Then 'Only perform on non-external tasks with Parents If Not T.ExternalTask And T.OutlineLevel > 1 Then 'combine the parent and task names and store in Text20 T.Text20 = T.OutlineParent.Name & "_" & T.Name intTcounter = intTcounter + 1 Else T.Text20 = T.Name intTcounter = intTcounter + 1 End If End If
This code shows that you can nest these statements. The inner statement is executed only if the outer statement is executed. If the outer If Then statement is not true, the statements with the loop are not executed.
Blank lines are one of the main causes of macro failure. Typical control structures treat a blank line as a task, but it is actually a task defined as "nothing," and thus it has no properties, such as name and number. Fortunately, you can test to see if a task is nothing before you attempt to perform an operation that would cause an error. Testing whether a task is nothing should be the first step whenever you are dealing with a collection of tasks that might contain blank lines.
Control structures appear within other control structures, so following For Each , you immediately use an If statement to test whether the task is nothing. If it is nothing, you skip to the end of that If statement. Typically, programmers indent each level of nesting so that it is easy to find the start and end of each statement. The End If should be at the same level of indentation as the For Each that begins that statement.
If statements are written to be true or false and can contain almost any form of Boolean logic (for example, combining true or false statements by using AND , OR , or NOT ). In this case, you want to work on a task only if it is not nothing.
Next, you have a more complicated If statement that checks two conditions, and then if both conditions are true, you perform an operation. If both conditions are not true, you specify an alternative operation, using the Else clause. You could have constructed another If statement by using different criteria, but you can keep your code simpler and in one place by using an Else clause. End If terminates this loop, and the next End If terminates the loop that checks whether the task is nothing.
Working with the Object Properties
You do the real work in your macro in the loop. You need to set one of the properties of the task you are working with to be a value that is the concatenation of the name of the parent task, an underscore , and the task's own name. Concatenation works with any string values. A string is a character string, a series of alphanumeric characters that will be treated as text. If a string is being referenced as a property of an object (for example, T.Name ), quotes are not required around it. However, if you have some text that needs to be recognized as a string, it must be surrounded by quotation marks. Without them, Visual Basic treats the text as an object or a variable and generates an error message. You join strings together by using an ampersand ( & ) between the items you want to join. Here are two examples of concatenation:
myString = "This" & " and That" myString = "Today is " & Date
In the first example myString will be "This and That" . In the second example myString will be "Today is 1/1/2004" or whatever the current date is.
The second thing you do in the loop is to increment the counter. Each time the loop iterates, it sets the value of intTcounter to intTcounter + 1 :
intTcounter = intTcounter + 1
This might seem odd at first, but as long as you remember that the new value is always to the left and the previous value is used on the right side of the equation, it will become second nature to you.
Reporting to the User
At the end of the macro, you want to tell the user that the macro has finished. This is more important with a long macro than with a short one, but the mechanism is the same, regardless of the length of the macro: You use the MsgBox function:
MsgBox ("Macro complete" & Chr(13) & intTcounter & " Tasks Changed")
A message box can display messages. To create a message for a message box, you need to concatenate some fixed strings and the value that has been stored in the intTcounter variable. A box pops up on the user's screen when the macro reaches this point.
|< Day Day Up >|