Working with Microsoft Visual Studio 2005
As you saw earlier, the programmable object behind the Task List window is the EnvDTE. TaskList object. Using the TaskList object, you can add new task items to provide information to the user about work that needs to be performed, as well as examine tasks added by a compiler or other tool.
The List Items
The EnvDTE.TaskList object lets you get to the items in the Task List window by calling the TaskItems property, which returns a TaskItems collection containing one item for each task item in the Task List window. You can view subsets of the items in the Task List window by filtering out items that don't belong to a particular grouping, or category, but items that are hidden because of this filtering will still have an item in the EnvDTE.TaskItems collection.
As with any other collection, you can index EnvDTE.TaskItems by its numerical position, which returns an EnvDTE.TaskItem object. You can use a number as an index to this collection, but it doesn't have a string format as an index.
Adding New Tasks
You can add new items to the Task List window to build a wide range of new tools. Examples of tools you can build that use the Task List window include:
-
Code analysis tools that find common programming errors, letting you find a bug before your customer does. You can place details about these errors in the Task List window alongside compiler errors. These tools are sometimes called Lint tools.
-
Scheduling tools that pull information from other software such as Microsoft Project and create task items to let programmers know when a specific portion of their work is due. When the check box next to a task item is selected, the corresponding item in Project is marked as completed.
-
An add-in that searches through compiler errors and fixes as many as it can. Remember the last time you compiled a C# project, only to have errors generated because you forgot a semicolon? Wouldn't it be great to have a tool to fix this automatically?
-
A macro that synchronizes your calendar in Microsoft Outlook® with the Visual Studio Task List window, reminding you, among other things, to pick up a gift on your way home from work for an anniversary or a birthday. (Such a tool can save you a lot of grief.)
You can build such tools because you can insert new task items into the Task List window by using the TaskItems.Add method. TaskItems.Add offers a great deal of flexibility in the elements that are displayed for new task items and how they're displayed. As a result, this method has one of the most complex argument signatures of all the methods in Visual Studio:
public EnvDTE.TaskItem Add(string Category, string SubCategory, string Description, EnvDTE.vsTaskPriority Priority = vsTaskPriorityMedium, object Icon, bool Checkable = false, string File = "", int Line = -1, bool CanUserDelete = true, bool FlushItem = true)
You can use the sample add-in AddTaskListItems to see the output generated by the many combinations of these parameters. We'll look at each parameter in turn over the next few sections.
Category and SubCategory
All tasks, whether they're created by the automation object model or by Visual Studio 2005 itself, belong to a category. Categories are used simply to group tasks and relate them to one another. Common category types are compile errors, user tasks, and shortcuts. You can create new category groups by using the Category parameter of the Add method. When you call the method, the list of currently known categories is searched for a category with a name that matches this argument. If one is not found, a new category is added and the new task item is added to this category. If a category with a matching name is found, the new task is added to that existing category group.
Visual Studio doesn't currently use the SubCategory argument of the Add method; your add-in or macro can leave it blank, or use it for internal bookkeeping.
Description
The description of a task appears in the Description column of the Task List window, and the Description argument of the Add method sets this column. This parameter of the Add method and the Category and SubCategory parameters are the only required parameters. Ignoring the optional parameters for now, we'll create our first Task List item by using the following macro code:
Sub TLAddItems() Dim taskList As EnvDTE.TaskList taskList = DTE.Windows.Item(Constants.vsWindowKindTaskList).Object taskList.TaskItems.Add("Category", "", "Description2") taskList.TaskItems.Add("Category", "", "Description1") End Sub
Priority
The next argument you can pass to the Add method is the Priority argument. This argument is optional when you use the Visual Basic programming language, but if it's supplied, an icon appears in the first column of the Task List—the priority column—to remind the user of the importance of completing that task. A high-priority task has a red exclamation point next to it, a low-priority task has a blue downward-pointing arrow, and a medium-priority task has no priority icon. The following macro adds new task items to the Task List, each with a different priority.
Sub TLAddItemsPriority() Dim taskList As EnvDTE.TaskList taskList = DTE.Windows.Item(Constants.vsWindowKindTaskList).Object taskList.TaskItems.Add("Category", "", _ Description1", vsTaskPriority.vsTaskPriorityHigh) taskList.TaskItems.Add("Category", "", _ "Description2", vsTaskPriority.vsTaskPriorityLow) taskList.TaskItems.Add("Category", "", _ "Description3", vsTaskPriority.vsTaskPriorityMedium) End Sub
Icon
The Icon parameter allows you to place an icon next to a newly added task item to identify that task item. The five predefined icons are described in Table 9-2.
Icon Image | Constant |
---|---|
| vsTaskIcon.vsTaskIconComment |
| vsTaskIcon.vsTaskIconUser |
| vsTaskIcon.vsTaskIconSquiggle |
| vsTaskIcon.vsTaskIconShortcut |
| vsTaskIcon.vsTaskIconCompile |
Left out of this table is the default icon, vsTaskIconNone–a blank icon–which appears (or, in this case, does not appear) if this parameter is not specified.
Note | If you call the TaskItems.Add method and supply the value vsTaskIconShortcut as the icon, a shortcut in a file isn't created. The icon is used for display purposes only. This applies to the other values that can be passed as the Icon parameter; using vsTaskIconCompile doesn't create a compiler error, vsTaskIconComment doesn't add a comment to a source file, and so forth. |
If these predefined images don't suit the task item you're creating, you can create your own image to display next to the task. You need a 16-by-16-pixel bitmap image with a color depth of either 16 colors or 16,777,215 (24-bit) colors. Any pixel in the image that has a background RGB color of 0,254,0 will bleed through the image, showing the color of the Task List window. The Icon paramater can be set to one of three formats in addition to the previously listed constants. You can pass the handle of a System.Drawing.Bitmap object, which is retrieved from the GetHbitmap method. You can also pass the path as a string to a bitmap file on disk. The final way is to load the bitmap into an IPictureDisp instance and then pass it as the Icon parameter. An IPictureDisp interface is the COM way of passing around a bitmap object. To create an IPictureDisp object in a .NET add-in, you must write a small amount of P/Invoke code to create this object type. (P/Invoke is the technology the .NET Framework uses to call unmanaged code from .NET programs.) The system DLL, oleaut32.dll, exports a method called OleLoadPictureFile that takes a path to a bitmap file, which can be the bitmap to show in the Task List, and returns the necessary IPictureDisp object. Before you call the OleLoadPictureFile method, you must add some code that might seem magical to the class that implements your add-in:
[DllImport("oleaut32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto, SetLastError=true)] internal extern static int OleLoadPictureFile(object fileName, [MarshalAsAttribute(UnmanagedType.IDispatch)] ref object ipictrueDisp);
This code defines the method signature for code that's exported from the COM DLL OleAut32.dll, and, with this P/Invoke method declared, you can call the OleLoadPictureFile method with the file name of the custom bitmap:
object objIPictureDisp = null; string filename = "C:\SomeImage.bmp"; int nret = OleLoadPictureFile(fileName, ref objIPictureDisp);
When this method call returns, the objIPictureDisp variable is set to an IPictureDisp object that can be passed as the Icon parameter of the TaskItems.Add method.
If you try to call the Add method from within a macro and pass as the Icon parameter an IPictureDisp object or the handle to a bitmap, an exception is generated. This happens because your macros run in a separate process. When a method or property is called on the Visual Studio object model, all data must be marshaled, or translated from the memory being used by the Macros editor program to the memory used by the Visual Studio program, across the process boundaries. However, objects such as IPicture, IPictureDisp, and HBITMAP can't be marshaled across processes, so if you try to create an IPicture, IPictureDisp, or HBITMAP and pass it to the TaskItems.Add method from a macro, an exception will be generated. This limits you to passing only a file path or one of the constants from a macro, but you can create and use a custom bitmap from within an add-in by using IPictureDisp or a handle because add-ins are loaded into the same process as Visual Studio.
Checkable
The Checkable parameter of TaskListItems.Add controls whether the check box appears next to a task item. If it's set to true, the check box is available; if it's set to false, the check box does not appear.
File and Line
File and Line are a string and an integer, respectively, that fill out the File and Line columns of the Task List. These can contain any values you want—they're not used in any way other than for information to display within the Task List. If the user later performs the default action on the task (either double-clicking or pressing the Enter key when the task item is selected), the file won't open and the caret won't be placed on the line specified in the Line argument unless you write a little extra code, which will be discussed shortly.
CanUserDelete
The CanUserDelete parameter controls whether the user can delete the task item by pressing the Delete key when the task is selected in the user interface. If this value is set to false, the user cannot delete the item, but you can still delete it through the object model by calling the TaskItem.Delete method.
FlushItem
The last parameter of TaskListItems.Add is a Boolean value called FlushItem. As each new item is inserted into the Task List, the Task List must be updated to show the new task. If you add a large number of tasks, redrawing the Task List each time an item is added will slow down your application's performance. If you pass a false value as the FlushItem argument, no updates are made to the Task List until either another task item is added that does an update or the method TaskItems.ForceItemsToTaskList is called.
The TaskItem Object
Once an item has been added to the Task List—whether it was created by using the TaskItems. Add object or created by Visual Studio itself and obtained by the TaskItems collection—you can use the TaskItem object's methods and properties to examine and modify the data displayed for that task item. The properties Category, SubCategory, Checked, Description, FileName, Line, and Priority each can be read programmatically to see what data is stored for those columns of the Task List. You can also set these properties as long as they're not read-only. Some task items that Visual Studio creates have their columns marked as read-only so they can't be modified. To test whether a particular column can be set, you can make a call to the IsSettable property, which accepts as a parameter the column within the Task List (a vsTaskListColumn enumeration value), and if the column can be modified, the IsSettable property returns true; otherwise, it returns false. For example, to change a task item's description value, you can write code such as this, which first verifies that the description can be changed:
Sub ModifyTaskDescription() Dim taskList As EnvDTE.TaskList Dim task As EnvDTE.TaskItem taskList = DTE.Windows.Item(Constants.vsWindowKindTaskList).Object task = taskList.TaskItems.Item(1) If (task.IsSettable(vsTaskListColumn.vsTaskListColumnDescription)) Then task.Description = "A new description" End If End Sub
Delete deletes the item, if deletion is possible, from the list of items. As mentioned earlier, all items added through the object model can be deleted whether the CanUserDelete parameter is true or false when you call the TaskItems.Add method. Other task items can be deleted depending on who created them. For example, if the task item was added by the user clicking on the Create User Task button on the command bar of the Task List when the User Tasks category is selected, it can be deleted by using the object model. If the item was created by IntelliSense®, an exception is generated when this method is called because the only way to remove the task item is to modify the source code that's causing the task item to appear.
AutoNavigate
The TaskItem.Navigate method simulates the user double-clicking or pressing the Enter key when the task has the focus. If the task was added by Visual Studio or by a compiler, or if the task is a shortcut task, this opens the target file and places the caret on the line specified by the task. By default, if the task was added through the automation model, no action is taken unless you write code to manually navigate to the proper file and line location by using the TaskNavigated event. However, you can also let Visual Studio handle opening the referenced document and line number through the AutoNavigate parameter of the TaskItems2.Add2 method. TaskItems2.Add2 looks very similar to the TaskItems.Add method, except it takes one additional parameter, AutoNavigate. If this parameter is set to true, when the user double-clicks the task item or calls Navigate on the TaskItem object, then the document path given in the File parameter is opened—or, if the document is already open, it is made active, and then the caret is placed at the beginning of the line indicated by the Line parameter. You should make sure that if you specify true to AutoNavigate, the file is a text document and not a binary file; otherwise, unpredictable results may occur.
Task List Events
As the user interacts with the Task List, events are fired to allow your add-in or macro to respond to those user interactions. Possibly the most important event of your add-in or macro that adds task list items is the TaskNavigate event. This event is fired when the user double-clicks on a Task List item, presses the Enter key when a task has the focus, or chooses Next Task or Previous Task from the Task List's shortcut menu. To capture this event, you can connect to the TaskListEvents.TaskNavigated event. This event is passed the TaskItem object of the item that the user wants to navigate to, plus a reference to a Boolean value called NavigateHandled that you can use to tell Visual Studio whether your code has handled the navigation of the task item. If the value false is passed back through the NavigateHandled argument and no one else handles the navigation of the task, Visual Studio plays a bell sound for the user.
Connecting to this event within a macro project is as simple as opening the EnvironmentEvents macro module, selecting the TaskListEvents item from the drop-down list at the top left of the editor window for this module, and then selecting the TaskNavigated event from the top-right drop-down list. Using this event and the arguments that are passed to it, you can write a macro event handler for the NavigateHandled event that opens the file (if specified) that the task item refers to and select the line in the source file that the task item points to. The code for this event handler would look like this:
Private Sub TaskListEvents_ TaskNavigated(ByVal TaskItem As EnvDTE.TaskItem, _ ByRef NavigateHandled As Boolean) Handles TaskListEvents.TaskNavigated 'If the file argument has been specified for this task... If (TaskItem.FileName <> "") Then Dim fileWindow As EnvDTE.Window Dim textWindow As EnvDTE.TextWindow Dim textPane As EnvDTE.TextPane 'Then open the file, find the TextWindow and TextPane objects... fileWindow = DTE.ItemOperations.OpenFile(TaskItem.FileName, _ EnvDTE.Constants.vsViewKindTextView) textWindow = CType(fileWindow.Object, EnvDTE.TextWindow) textPane = CType(textWindow.ActivePane, EnvDTE.TextPane) 'Then move the caret to the correct line: textPane.Selection.MoveTo(TaskItem.Line, 1, False) textPane.Selection.SelectLine() NavigateHandled = True End If End Sub
Connecting to this event within an add-in is almost as simple as connecting to it within a macro, but a little more code is needed. The first step is to declare a variable to connect the event handler to. In this case, we're connecting to Task List events, so we'll use the EnvDTE. TaskListEvents class:
private EnvDTE.TaskListEvents taskListEvents;
Next, you declare the event handler method, which must follow the method signature as declared in the Object Browser. You can also convert the macro code shown earlier into C# for an add-in:
public void TaskNavigated(EnvDTE.TaskItem taskItem, ref bool navigateHandled) { //If the file argument has been specified for this task... if(taskItem.FileName != "") { EnvDTE.Window fileWindow; EnvDTE.TextWindow textWindow; EnvDTE.TextPane textPane; // Then open the file, find the TextWindow and TextPane objects... fileWindow = applicationObject.ItemOperations.OpenFile( taskItem.FileName, EnvDTE.Constants.vsViewKindTextView); textWindow = (EnvDTE.TextWindow)fileWindow.Object; textPane = (EnvDTE.TextPane)textWindow.ActivePane; //Then move the caret to the correct line: textPane.Selection.MoveTo(taskItem.Line, 1, false); textPane.Selection.SelectLine(); navigateHandled = true; } }
Finally, you must set the taskListEvents variable to an instance of a TaskListEvents object, which you find by calling the Events.TaskListEvents property. This property takes one argument—a category that's used as a filter. If you pass the empty string as an argument, your event handler is called when any task item generates an event—whether the item was added by an add-in or macro or by Visual Studio itself. But if you specify a category for this argument—the same category string you can pass as the first argument to the TaskItems. Add method—only events for a task item that have this same category are sent to your event handler. This filtering mechanism can help cut down on the number of events that are fired, thereby increasing the performance of your code. Because we want our code to handle events for all task items, we'll pass the empty string to the Events.TaskListEvents property:
EnvDTE.Events events = applicationObject.Events; taskListEvents = (EnvDTE.TaskListEvents)events.get_TaskListEvents("");
The last step is to associate the event object with the event handler. You do this by creating a new EnvDTE._dispTaskListEvents_TaskNavigatedEventHandler object and adding it to the taskListEvents.TaskNavigated collection of event handlers:
taskListEvents.TaskNavigated += new _dispTaskListEvents_TaskNavigatedEventHandler(this.TaskNavigated);
TaskNavigated isn't the only Task List event your code can capture. TaskAdded and TaskRemoved events are fired when a new task item is added or just before it's removed, respectively. The last event, TaskModified, is fired when one of the columns of the Task List is modified. For instance, the user can check or uncheck an item or change the priority or descriptive text for a task item. To let your code know when these tasks are changed, the TaskModified event is fired, passing the task item and the column that was modified.
Comment Tokens
Developers commonly leave portions of their code incomplete as they work, with the intention of finishing it later. This omitted code might include error-checking, some parameter validation, or notes to themselves to handle a few additional code paths. Of course, unless you specifically search through the code for these tokens, either by visually inspecting it or by using the text editor search tools, you might never revisit these notes and make the corrections. However, if you use a special notation, the Task List can find and report these notes for you automatically. When you open a source file, the file is scanned for these special tokens, and, if any are found, an entry is made in the Task List. The tokens in the source file have the format of a language comment marker followed by the comment token, the colon (:) character, and finally the note that is to appear within the Task List. For example, the comment //TODO: Fix this later creates a Task List item for Microsoft Visual C++ and C# with the description Fix this later, and the comment ‘TODO: Fix this later’ does the same for a Visual Basic file.
The special tokens that the Task List searches for are defined in the Options dialog box, where you can add new tokens and remove or modify existing tokens. The default comment tokens are HACK, TODO, UNDONE, and UnresolvedMergeConflict.
You can add a new token by typing a token name in the Name box, selecting a priority, and then clicking the Add button. You can also add, remove, and modify these tokens by using the object model. To program these tokens, use the Properties collection. You'll find more details about the Properties collection later in this chapter—for now, we'll overlook the details of how to use the Properties collection and look only at how to change the tokens by using this object. The first step is to find the CommentTokens property by using code such as the following:
Sub GetTokensArray() Dim tokens As Object() Dim prop As EnvDTE.Property Dim props As EnvDTE.Properties props = DTE.Properties("Environment", "TaskList") prop = props.Item("CommentTokens") tokens = prop.Value End Sub
The CommentTokens property returns an array of strings that have a special format, and when this macro is run, it finds all the available tokens in the format TokenName: Priority, where TokenName is what should appear after the comment notation for the given language and Priority is the numerical value of an item in the EnvDTE.vsTaskPriority enumeration. In the preceding macro, the string for the TODO token is TODO:2 because the string to search for in the text editor is TODO and the priority that appears in the Task List for this token is vsTaskPriorityMedium (whose numerical value is 2).
Adding your own token to the list of tokens is a three-step process. Setting the list of tokens clears the current list (at least the tokens that are not read-only), so you need to preserve the known tokens so you don't overwrite the known tokens that the user might have created. First, you need to retrieve the list of current Task List tokens. Add your own token to the array of existing tokens, and then set the property with the expanded array. You can see this in the following macro, which adds a high-priority SECURITY token to the list of comment tokens:
Sub AddSecurityToken() Dim tokens As Object() Dim token As String Dim prop As EnvDTE.Property Dim props As EnvDTE.Properties 'Find the property holding the known tokens props = DTE.Properties("Environment", "TaskList") prop = props.Item("CommentTokens") tokens = prop.Value Add one to the list of known tokens to hold ' the new SECURITY token ReDim Preserve tokens(tokens.Length) 'Add the new token tokens(tokens.Length - 1) = "SECURITY:3" 'Set the list of known tokens prop.Value = tokens End Sub
To delete a token, you run similar code, but instead of adding an element to the array, you remove an element:
Sub RemoveSecurityToken() Dim tokens As Object() Dim newTokens As Object() Dim token As String Dim i As Integer = 0 Dim found As Boolean = False Dim prop As EnvDTE.Property Dim props As EnvDTE.Properties props = DTE.Properties("Environment", "TaskList") prop = props.Item("CommentTokens") tokens = prop.Value 'Don't want to shrink the array if ' the token is not available For Each token In tokens If token = "SECURITY:3" Then found = True Exit For End If Next 'If the SECURITY token was not found, then ' there is nothing to remove so we can exit If found = False Then Exit Sub End If 'Resize the newTokens array ReDim newTokens(tokens.Length - 2) 'Copy the list of tokens into the newTokens array ' skipping the SECURITY token For Each token In tokens If token <> "SECURITY:3" Then newTokens(i) = token i = i + 1 End If Next 'Set the list of tokens prop.Value = newTokens End Sub
If your add-in generates code to place in the text buffer and you want to insert a comment token that gives the user additional information about how to modify the code, you can use the TaskList.DefaultCommentToken property to find which token to insert. The following code creates a string containing a class, with the default comment token directing the user to where to insert code:
Sub InsertTLTokenCode() Dim classString As String Dim taskList As EnvDTE.TaskList taskList = DTE.Windows.Item(Constants.vsWindowKindTaskList).Object classString = "Public Class AClass" + Chr(13) classString = classString + Chr(9) + "'" + taskList.DefaultCommentToken classString = classString + ": Insert your code here" + Chr(13) classString = classString + "End Class" End Sub
Категории