To help you more easily maintain files within a solution, Visual Studio makes available various utility projects. These utility projects allow you to keep track of files that are not part of any other project that is loaded into a solution. Because any file type can be stored within these projects, such as program source files or Microsoft Word documents, these projects can't be compiled into a program. And because utility projects are not associated with any particular programming language, they are available to all users of Visual Studio and don't require Visual Basic, Visual C#, or Visual C++ to be installed.
When you're working with a solution, you might need to open files that are not part of an existing project. When you open such a file, it is automatically added to a project called Miscellaneous Files. A project file isn't created on disk for this project, as with other project types, but you get a convenient way of locating files that are open but are not part of any other project that is open within the solution. You can think of the Miscellaneous Files project as a list of most recently used open documents—when you open a file, an item for that file is added to the project, and when you close the file, it is removed. By default, the Miscellaneous Files project and the files it contains don't appear in the Solution Explorer tree hierarchy, but you can easily make them visible by opening the Tools Options dialog box, selecting the Environment | Documents node, and selecting the Show Miscellaneous Files In Solution Explorer check box.
The Miscellaneous Files project has a unique name associated with it that, unlike with other projects, doesn't change over time. This name, <MiscFiles>, is defined by the constant vsMiscFilesProjectUniqueName. The following macro retrieves the Project object for the Miscellaneous Files project:
Sub FindMiscFilesProject() Dim project As EnvDTE.Project Dim projects As EnvDTE.Projects projects = DTE.Solution.Projects project = projects.Item(EnvDTE.Constants.vsMiscFilesProjectUniqueName) End Sub
When the first file is opened within the Miscellaneous Files project, an item is added to the Solution.Projects collection that implements the Project interface. It works just as the Project interface implemented by projects such as Visual Basic or Visual C#, except that a few of the properties will return null or Nothing or throw a System.NotImplementedException when called. Table 8-1 lists the methods and properties of the Project object that return a meaningful value for the Miscellaneous Files project and the ProjectItem and ProjectItems objects contained within this project.
Project | ProjectItems | ProjectItem |
---|---|---|
DTE | DTE | DTE |
ProjectItems | Parent | Collection |
Name (read-only) | Item | Name (read-only) |
UniqueName | GetEnumerator / _NewEnum | FileCount |
Kind | Kind | Kind |
FullName | Count | FileNames |
ContainingProject | SaveAs | |
Save | ||
IsOpen | ||
Open | ||
Delete | ||
Remove | ||
ExpandView | ||
ContainingProject | ||
IsDirty |
You can add new files to the Miscellaneous Files project by using the ItemOperations.NewFile method, which has the following method signature:
public EnvDTE.Window NewFile(string Item = "General\Text File", string Name = "", string ViewKind = "{}")
By applying the techniques we used earlier to calculate the first parameter for the ItemOperations.AddNewItem method, we can find the value that should be passed to the NewFile method. The second parameter also has the same meaning as the second parameter of the ItemOperations.AddNewItem method—the name of the file (with extension) that is to be added—and if the empty string is passed, a default name is calculated. The last argument specifies which view the file should be opened in when it is added. These values can be found within the EnvDTE.Constants class and begin with the name vsViewKind.
The Miscellaneous Files project lists files that are temporarily open in an editor, and when those files are closed, they are removed from that project. But what if you want to associate a file with a solution, not have that file built as part of a project, and have that file stay within your solution when the editor window for that file has been closed? Solution folders provide a way for you to satisfy these requirements and more. Solution folders can be created by right-clicking on the solution node within the Solution Explorer tool window, and choosing Add | New Solution Folder. Solution folders can also be created within existing solution folders by right-clicking on a solution folder and selecting Add | New Solution Folder. Because solution folders can be nested within one another, you can create a hierarchy of folders, each containing files on disk. Often I need to add a bunch of files to a solution that are not part of a project, and, to keep those files organized, I like to mirror the folder structure on disk with solution folders. Not only can you organize files on disk with solution folders, but you can also use them to organize projects. If your solution contains many projects, you can create a solution folder that contains, for example, all the Web applications in your project, one solution folder for all class libraries, etc. New or existing projects can be added to a solution folder from the context menu for a solution folder, or if the project you want to move is located under the solution node in the Solution Explorer tool window, you can just drag the project into the appropriate solution folder.
A Solution Folder also has a programmatic interface to this functionality. To create a solution folder within a solution, simply call the Solution2.AddSolutionFolder method, supplying a name for the new folder:
Sub CreateSolutionFolder() Dim solution2 As EnvDTE80.Solution2 solution2 = CType(DTE.Solution, EnvDTE80.Solution2) solution2.AddSolutionFolder("My Folder") End Sub
The AddSolutionFolder method returns an EnvDTE.Project object, which works as any other Project object, such as that available from Visual C# or Visual Basic projects, except you will find that many methods, such as the Save method, will not work for a solution folder because it has no meaning for that project type. The Object property on the Project interface for a solution folder returns an object that you can then cast into the interface EnvDTE80. SolutionFolder. The SolutionFolder interface also has a method named AddSolutionFolder, which will allow you to create a nested folder. This macro is a modified version of the one to add a folder to the solution, but it will also create a nested folder:
Sub CreateSolutionFolder() Dim solution2 As EnvDTE80.Solution2 Dim project As EnvDTE.Project Dim solutionFolder As EnvDTE80.SolutionFolder solution2 = CType(DTE.Solution, EnvDTE80.Solution2) project = solution2.AddSolutionFolder("MyFolder") solutionFolder = CType(project.Object, EnvDTE80.SolutionFolder) solutionFolder.AddSolutionFolder("My Other Folder") End Sub
The SolutionFolder interface will also allow you to programmatically add projects or files. The method AddFromFile will take the path to an existing project file (a file with an extension such as .csproj, .vbproj, or .vcproj), and add it to a solution folder. Here is an example of the use of this method, which will first create a solution folder named MyFolder and then will add an existing Visual C# project named ConsoleApplication to that solution folder:
Sub ProjectAdd() Dim project As EnvDTE.Project Dim solutionFolder As EnvDTE80.SolutionFolder Dim solution2 As EnvDTE80.Solution2 solution2 = CType(DTE.Solution, EnvDTE80.Solution2) project = solution2.AddSolutionFolder("MyFolder") solutionFolder = project.Object solutionFolder.AddFromFile("C:\Project\ConsoleApplication1.csproj") End Sub
The AddFromTemplate method takes a path to a .vstemplate file, a .vsz file, or an existing project to clone; a path to store the project in; and a name of the new project to create. AddFromTemplate then generates a new project based upon the wizard or the existing project. This macro uses the GetProjectTemplate method of the Solution2 interface to find the path to the Visual C# Console Application template, and then creates a project based upon this template within a new solution folder.
Sub NewProjectAdd() Dim project As EnvDTE.Project Dim solutionFolder As EnvDTE80.SolutionFolder Dim solution2 As EnvDTE80.Solution2 Dim CSConsoleTemplatePath As String solution2 = CType(DTE.Solution, EnvDTE80.Solution2) project = solution2.AddSolutionFolder("MyFolder") solutionFolder = project.Object CSConsoleTemplatePath = solution2.GetProjectTemplate _ ("ConsoleApplication.zip", "CSharp") solutionFolder.AddFromTemplate(CSConsoleTemplatePath, _ "C:\Projects\TestProject", "NewProject") End Sub
Adding an existing or new file to a solution folder is done within the Project object by using the AddFrom methods, just as you would add an item to a Visual C# or Visual J# project.
Once you have a solution folder with items located within that folder, you can use the folder as a Project object to locate an item contained within it. For example, suppose you have a solution folder with a project loaded into it. The following macro finds the Project for the solution folder and then obtains the collection of ProjectItems for the solution folder. Even though a project within a solution folder is a project, it is still an item, and so the solution folder allows you to get to a list of ProjectItem objects by using the ProjectItems property. Once you have this ProjectItem object, you can call the Object property to retrieve the object specific to that project item node, which is a project.
Sub FindProjectInSolutionFolder() Dim slnFolderProject As Project Dim containedProject As Project Dim projectAsAProjectItem As ProjectItem 'Find the solution folder. This code expects one folder and ' no other items within the solution slnFolderProject = DTE.Solution.Projects.Item(1) 'Find the project item for the project in the folder projectAsAProjectItem = slnFolderProject.ProjectItems.Item(1) 'Convert the project item into a Project containedProject = projectAsAProjectItem.Object MsgBox(containedProject.Name) End Sub
This is the only way that you can get to a Project object for a project that is located within a solution folder. The Solution.Projects.Item method will not return a project's object for a project in a solution folder because the Projects.Item method looks only at the projects directly underneath the solution node in Solution Explorer.
If you have a reference to a Project object, either a project such as a Visual J# project or a Project object for a solution folder, and that project is contained within a solution folder, you can find the ProjectItem object of that project within the solution folder by using the ParentProjectItem property. This macro will find a Project object within a solution folder and then walk back up the hierarchy to find the solution folder that contains it.
Sub FindParentProject() 'First, find the project nested in the solution folder: Dim nestedProject As Project Dim solutionFolder As Project solutionFolder = DTE.Solution.Projects.Item(1) nestedProject = solutionFolder.ProjectItems.Item(1).Object 'Now, find the solution folder parent of the nested project Dim parentProjectItem As ProjectItem Dim parentProject As Project parentProjectItem = nestedProject.ParentProjectItem parentProject = parentProjectItem.ContainingProject 'Make sure the parent project and the solution folder are the same MsgBox(parentProject.UniqueName = solutionFolder.UniqueName) End Sub
All the project types we've discussed so far have implemented a Project object that can be used by a macro or an add-in. However, a few project types, such as a database project or a project that has been unloaded by using the Project | Unload Project command, don't implement the Project object themselves. To allow some programmability for these project types, Visual Studio supports the unmodeled project type. An unmodeled project provides an implementation of the Project object that supports only the properties common among all project types, which are DTE, Kind, and Name. All other properties and methods on this implementation of the Project object return values that have no useful meaning or generate an exception when called and shouldn't be used by a macro or an add-in. You can distinguish an unmodeled project from other project types by checking the Project.Kind property, which returns the constant EnvDTE.Constants.vsProjectKindUnmodeled if the project is an unmodeled project. The following macro enumerates all the projects loaded into a solution and determines which ones are unmodeled:
Sub FindUnmodeledProjects() Dim project As EnvDTE.Project For Each project In DTE.Solution.Projects If (project.Kind = EnvDTE.Constants.vsProjectKindUnmodeled) Then MsgBox(project.Name + " is unmodeled") End If Next End Sub