Inside Microsoft® Visual Studio® .NET 2003 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Inside Microsoft® Visual Studio® .NET 2003 [Electronic resources] - نسخه متنی

Brian Johnson

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید










The Task List Window


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.


Task List Items


The EnvDTE.TaskList object lets you get to the items in the Task List 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 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 to build a wide range of new tools. Here are examples of tools you can build that use the Task List:

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 alongside compiler errors.

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 .NET Task List, reminding you to, among other things, pick up a gift on your way home from work for an anniversary or a birthday. (Such a tool can save you from a lot of grief.)


You can build such tools because you insert new task items into the Task List using the TaskItems.Add method. TaskItems.Add offers a great deal of flexibility in what elements 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 .NET:


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 .NET 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. When the user right-clicks on the Task List and chooses Show Tasks, either all tasks can be shown or they can be filtered to show only the tasks belonging to a category group. You can create new category groups 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 .NET doesn't currently use the SubCategory argument of the Add method; your add-in or macro can leave it blank.


Description


The description of a task appears in the Description column of the Task List, 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 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

After you run this macro and choose Sort By | Category from the Task List shortcut menu, the Task List appears as shown in Figure 10-1.


Figure 10-1. Task List items added by a macro and the categories they're sorted by




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 .NET programming language, but if it's supplied, an icon appears in the first column of the Task Listthe priority columnto 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. The Task List should appear as shown in Figure 10-2 after you run the macro.


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


Figure 10-2. The Task List showing items with different priority values




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 10-2.

Table 10-2. Predefined Icons for Task List Items

Icon Image


Constant




vsTaskIcon.vsTaskIconComment




vsTaskIcon.vsTaskIconUser




vsTaskIcon.vsTaskIconSquiggle




vsTaskIcon.vsTaskIconShortcut




vsTaskIcon.vsTaskIconCompile

Left out of this table is the default icon, vsTaskIconNonea blank iconwhich appears 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 16. Any pixel in the image that has a background RGB color of 0,255,0 will bleed through the image, showing the background of the Task List. To set the image, you must first load the bitmap into an IPictureDisp object and then pass it as the Icon parameter. An IPictureDisp is the COM way of passing around a bitmap object. To create an IPictureDisp object in a .NET add-in, you must write a little 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 to the class that implements your add-in that might seem magical:

[View full width]

[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 filename of the custom bitmap:


object objIPictureDisp = null;
string filename = "C:\SomeImage.eps";
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, an exception is generated. This happens because the Visual Studio .NET Macros editor runs your macros in a separate process. When a method or property is called on the Visual Studio .NET 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 .NET program, across the process boundaries. However, objects such as IPicture and IPictureDisp can't be marshaled across processes, so if you try to create an IPicture and IPictureDisp and pass it to the TaskItems.Add method from a macro, an exception will be generated. This limitation prevents you from creating a task with a custom image from a macro, but you can create and use a custom bitmap from within an add-in because add-ins are loaded into the same process as Visual Studio .NET.


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 integer, respectively, that fill out the File and Line columns of the Task List. These can contain any values you wantthey'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. This is because the object model makes no assumptions about the data in the file; if the file points to a binary file, opening and placing the caret on a line might not do what you expect; rather than do something that might be incorrect, it does nothing. However, you can still connect an event handler onto the task item that was created, watch for a TaskNavigate event (discussed later), and then manually open the file using code.


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 Listwhether it was created using the TaskItems.Add object or created by Visual Studio .NET itself and obtained by the TaskItems collectionyou 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 .NET 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 Task­Items.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 Click Here To Add A New Task Item in the Task List, it can be deleted using the object model. If the item was created by IntelliSense or by a compiler, 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.

The 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 .NET 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. 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 using the TaskNavigated event.


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 .NET 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 .NET 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 argumenta 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 eventwhether the item was added by an add-in or macro or by Visual Studio .NET itself. But if you specify a category for this argumentthe same category string you can pass as the first argument to the Task­Items.Add methodonly 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 adding 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 Visual Studio .NET 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 Visual C++ and C# with the description Fix this later, and the comment 'TODO: Fix this later does the same for a Visual Basic .NET 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. Figure 10-3 shows the available tokens: HACK, TODO, UNDONE, and UnresolvedMergeConflict.


Figure 10-3. Task List token options



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 through the object model. To program these tokens, you use the Properties collection. You'll find more details about the Properties collection later in this chapterfor now, we'll overlook the details of how to use the Properties collection and look only at how to change the tokens using this object. The first step is to find the CommentTokens property 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. You add your own token to the array of existing tokens, and then you 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


/ 118