Working with Project Items
Solutions manage a number of projects, and each project manages the files that are built into a program. Each project contains files that can be enumerated and programmed.
Enumerating Project Items
Files within a project are arranged hierarchically. A project can contain any number of files and one or more folders, which themselves can contain additional files and folders. To match this project hierarchy, the project object model is also arranged hierarchically, with the ProjectItems collection representing the nodes that contain items and the ProjectItem object representing each item within this collection. To enumerate this hierarchy, you use the ProjectItems and ProjectItem objects. The following macro walks the first level of the hierarchy of the ProjectItems and ProjectItem objects by obtaining the top-level ProjectItems object using the Project.ProjectItems property:
Sub EnumTopLevelProjectItems()
Dim projItem As EnvDTE.ProjectItem
Dim projectProjectItems As EnvDTE.ProjectItems
Dim project As EnvDTE.Project
'Find the first project in a solution:
project = DTE.Solution.Projects.Item(1)
'Retrieve the collection of project items:
projectProjectItems = project.ProjectItems
'Walk the list of items in the collection:
For Each projItem In projectProjectItems
MsgBox(projItem.Name)
Next
End Sub
Some items within a project, such as a folder, are both an item within the project hierarchy and a container of other files and folders. Because these folders are both items and collections of items, a folder is represented in the project model hierarchy with both a ProjectItem object and a ProjectItems object. You can determine whether a ProjectItem node is also a container of more ProjectItem nodes by calling the ProjectItem.ProjectItems property, which returns a ProjectItems collection if the node can contain subitems. You can enumerate all the ProjectItem and ProjectItems objects within a project by writing a recursive macro function such as this:
Sub EnumProjectItems(ByVal projItems As EnvDTE.ProjectItems)
Dim projItem As EnvDTE.ProjectItem
'Find all the ProjectItem objects in the given collection:
For Each projItem In projItems
MsgBox(projItem.Name)
'And walk any items the current item may contain:
EnumProjectItems(projItem.ProjectItems)
Next
End Sub
Sub EnumProject()
Dim project As EnvDTE.Project
'Find the first project in a solution:
project = DTE.Solution.Projects.Item(1)
EnumProjectItems(project.ProjectItems)
End Sub
The EnumProject macro first finds the ProjectItems collection of a given project, and then it calls the EnumProjectItems subroutine, which will find all the ProjectItem objects that the collection contains. If the ProjectItem object is itself a collection, it will recursively call the EnumProjectItems subroutine to display the items it contains.Folders aren't the only items that can contain a collection of ProjectItem objects. Some files, such as Windows Forms and Web Forms files, are also collections of files. Each of these file types has associated resource files (in the form of .resx files), and Web Forms files also have an associated code-behind file. In the default state, Solution Explorer won't give any indication of whether these files are containers for other files, but you can modify it to show the files that these file types contain. Choose Show All Files from the Project menu to show all form files as expandable in the tree view that makes up Solution Explorer. When the EnumProject macro (shown earlier) is run, the ProjectItem.ProjectItems property returns a collection that contains the ProjectItem objects for these subitems. Code such as the EnumProject macro will return the same values whether or not the Show All Files menu command has been selected. This command affects only the Solution Explorer user interface.You can combine the techniques for enumerating files and files within folders to find a specific item within a project. Suppose you've created a Windows Forms application solution and modified the project to look like that shown in Figure 8-1.
Figure 8-1. A Windows Forms application with nested resources

Using the ProjectItem object and ProjectItems collection, you can write a macro such as the following to locate the Bitmap1.bmp file:
Sub FindBitmap()
Dim project As EnvDTE.Project
Dim projectProjectItems As EnvDTE.ProjectItems
Dim resourcesProjectItem As EnvDTE.ProjectItem
Dim resourcesProjectItems As EnvDTE.ProjectItems
Dim bitmapsProjectItem As EnvDTE.ProjectItem
Dim bitmapsProjectItems As EnvDTE.ProjectItems
Dim bitmapProjectItem As EnvDTE.ProjectItem
'Get the project:
project = DTE.Solution.Item(1)
'Get the list of items in the project:
projectProjectItems = project.ProjectItems
'Get the item for the Resources folder:
resourcesProjectItem = projectProjectItems.Item("Resources")
'Get the collection of items in the Resources folder:
resourcesProjectItems = resourcesProjectItem.ProjectItems
'Get the item for the Bitmaps folder:
bitmapsProjectItem = resourcesProjectItems.Item("Bitmaps")
'Get the collection of items in the Bitmaps folder:
bitmapsProjectItems = bitmapsProjectItem.ProjectItems
'Get the item for the Bitmap1.bmp file:
bitmapProjectItem = bitmapsProjectItems.Item("Bitmap1.bmp")
MsgBox(bitmapProjectItem.Name)
End Sub
You can walk down the tree of the ProjectItem and ProjectItems hierarchy to find a specific file, but sometimes you might need a quicker and easier way of locating the ProjectItem object for a file with a specific filename in a project. You can use the FindProjectItem method of the Solution object to find an item by passing a portion of the file path to where the file is located on disk. For example, suppose two add-in projects have been created (using the Add-in Wizard) in a folder you created called Addins located in the root of drive C. Each of these two add-ins, MyAddin1 and MyAddin2, contains a file named Connect.cs. You could use the following macro to locate the Connect.cs file in either project:
Sub FindItem()
Dim projectItem As EnvDTE.ProjectItem
projectItem = DTE.Solution.FindProjectItem("Connect.cs")
End Sub
However, because FindProjectItem returns any file that matches this filename, you can't tell which ProjectItem will be returnedthe ProjectItem object for the Connect.cs in MyAddin1 or the ProjectItem object for Connect.cs in MyAddin2. To refine the search, you can supply a bit more of the file path as the specified filename, as shown in the following macro, which adds the name of the folder on disk that contains the MyAddin1 version of Connect.cs:
Sub FindItemWithFolder()
Dim projectItem As EnvDTE.ProjectItem
projectItem = DTE.Solution.FindProjectItem("MyAddin1\Connect.cs")
End Sub
Of course, just as you can specify a portion of the path to find the ProjectItem, you can use the whole path to zero in on the exact item you want:
Sub FindItemWithFullPath()
Dim projectItem As EnvDTE.ProjectItem
projectItem = _
DTE.Solution.FindProjectItem("C:\Addins\MyAddin1\Connect.cs")
End Sub
Adding and Removing Project Items
You can add new files to a project in two ways. The first way is to use the AddFromDirectory, AddFromFile, AddFromFileCopy, and AddFromTemplate methods of the ProjectItems interface (which we'll discuss in more detail in Chapter 9). The second way is to use the ItemOperations object. This object offers a number of file manipulation methods to help make working with files easier. The difference between using the methods of the ProjectItems object and the methods of ItemOperations is that the ProjectItems object gives an add-in or a macro more fine-grained control over where within a project the new file is created. The ItemOperations object is more user-interface-oriented; it adds the new file to the project or folder that is selected in Solution Explorer, or, if a file is selected, it adds the item to the project or the folder containing that file. These features help make macro recording possible. If you start the macro recorder and add a file using Solution Explorer, a call to one of the methods of the ItemOperations object is recorded into the macro. The selected item is where files are added when the proper method is called.One method of the ItemOperations object, AddExistingItem, takes as its only argument the path to a file on disk and adds this file to the selected project or folder within a project. Depending on the type of project, the file might be copied to the project folder before being added or a reference might be added to the file without copying the file. Visual Basic .NET and C# projects are folder-based, which means that the project hierarchy shown in Solution Explorer is mirrored on disk, and any files within the project must be in the folder containing the project or in one of its subfolders. Visual C++ projects work a little differently: a file that is part of the project can be located anywhere on disk, and it doesn't need to be within the folder containing the project or a child folder. For instance, suppose a file named file.txt is located in the C:\ root folder. If we run the macro
Sub AddExistingItem()
DTE.ItemOperations.AddExistingItem("C:\file.txt")
End Sub
and the item selected in Solution Explorer is a C# or a Visual Basic .NET project or one of its children, file.txt will be copied into the folder or subfolder containing the project file, and then added to the project. But if the selected item is a Visual C++ project, the file will be left in place and a reference will be added to this file.While AddExistingItem inserts a file from disk into a project, AddNewItem creates a new file and adds it to the project. This method takes two arguments and has the following method signature:
public EnvDTE.ProjectItem AddNewItem(string Item = "General\Text File",
string Name = ")
You can add a new item through the user interface by right-clicking a project and choosing Add | Add New Item from the shortcut menu item, which brings up the Add New Item dialog box. When you perform these steps for a C# project, you'll see the dialog box shown in Figure 8-2.
Figure 8-2. The Add New Item dialog box for a C# project

The Add New Item dialog box is related to the AddNewItem method in that the first parameter of AddNewItem is the type of file to be added and you can find this file type using the dialog box. The file type is calculated by taking the path to the item selected in the tree view, with each portion of the path separated by a backslash, and then taking the title of the item in the list on the right side of the dialog box. So, for example, when a Windows Forms file is added to the project, the top-most node of the tree view (Local Project Items) is concatenated with the backslash character. Next, the string "UI" is appended to this string because it is the tree node that contains the Windows Forms item to be added, followed by another backslash. Finally, the name of the item shown in the right panel of the dialog box, the string "Windows Form" is added, resulting in the string that can be passed to AddNewItem: "Local Project Items\UI\Windows Form". The second argument of this method is simply the name of the file to create, with the file extension. If the filename parameter passed is an empty string, a default file name is generated and used.You might occasionally need to remove an item that has been added to a project because you no longer need it. The ProjectItem object supports two methods for removing items from the project, Remove and Delete. These two methods both remove an item from the project, but Delete is more destructive because it also erases the file from disk by moving it into the computer's Recycle Bin.