可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I've been following MSDN's Hello World guide to developing Visual Studio extensions (this article specifically deals with creating one as a Visual Studio toolbar command).
I am trying to list all projects contained in the current/active solution.
In the auto generated code for the Command template.
I have tried EnvDTE
's Solution
's Projects
property, but it shows zero projects.
There is a ActiveSolutionProjects
property as well, but it also shows an empty array.
How is this achieved ?
P.S.: I tried both DTE and DTE2 interfaces since it is confusing understanding which version to use, from the docs. I get a null service for DTE2, so I am going with DTE.
My Solution Explorer looks like:
Update: Bert Huijben, from gitter/extendvs, suggested the following, found at the VSSDK Extensibility Samples - but this too does not work (returns 0 elements, both within the constructor and within the callback function):
private Hashtable GetLoadedControllableProjectsEnum()
{
Hashtable mapHierarchies = new Hashtable();
IVsSolution sol = (IVsSolution)this.ServiceProvider.GetService(typeof(SVsSolution));
Guid rguidEnumOnlyThisType = new Guid();
IEnumHierarchies ppenum = null;
ErrorHandler.ThrowOnFailure(sol.GetProjectEnum((uint)__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION, ref rguidEnumOnlyThisType, out ppenum));
IVsHierarchy[] rgelt = new IVsHierarchy[1];
uint pceltFetched = 0;
while (ppenum.Next(1, rgelt, out pceltFetched) == VSConstants.S_OK &&
pceltFetched == 1)
{
IVsSccProject2 sccProject2 = rgelt[0] as IVsSccProject2;
if (sccProject2 != null)
{
mapHierarchies[rgelt[0]] = true;
}
}
return mapHierarchies;
}
回答1:
To get the EnvDTE.Project objects:
static private void FindProjectsIn(EnvDTE.ProjectItem item, List<EnvDTE.Project> results)
{
if (item.Object is EnvDTE.Project)
{
var proj = (EnvDTE.Project)item.Object;
if (new Guid(proj.Kind) != Utilities.ProjectTypeGuids.Folder)
{
results.Add((EnvDTE.Project)item.Object);
}
else
{
foreach (EnvDTE.ProjectItem innerItem in proj.ProjectItems)
{
FindProjectsIn(innerItem, results);
}
}
}
if (item.ProjectItems != null)
{
foreach (EnvDTE.ProjectItem innerItem in item.ProjectItems)
{
FindProjectsIn(innerItem, results);
}
}
}
static private void FindProjectsIn(EnvDTE.UIHierarchyItem item, List<EnvDTE.Project> results)
{
if (item.Object is EnvDTE.Project)
{
var proj = (EnvDTE.Project)item.Object;
if (new Guid(proj.Kind) != Utilities.ProjectTypeGuids.Folder)
{
results.Add((EnvDTE.Project)item.Object);
}
else
{
foreach (EnvDTE.ProjectItem innerItem in proj.ProjectItems)
{
FindProjectsIn(innerItem, results);
}
}
}
foreach (EnvDTE.UIHierarchyItem innerItem in item.UIHierarchyItems)
{
FindProjectsIn(innerItem, results);
}
}
static internal IEnumerable<EnvDTE.Project> GetEnvDTEProjectsInSolution()
{
List<EnvDTE.Project> ret = new List<EnvDTE.Project>();
EnvDTE80.DTE2 dte = (EnvDTE80.DTE2)ServiceProvider.GlobalProvider.GetService(typeof(EnvDTE.DTE));
EnvDTE.UIHierarchy hierarchy = dte.ToolWindows.SolutionExplorer;
foreach (EnvDTE.UIHierarchyItem innerItem in hierarchy.UIHierarchyItems)
{
FindProjectsIn(innerItem, ret);
}
return ret;
}
Notably recursion is necessary to dig into solution folders.
If you just want the file paths you can do this w/o using DTE:
static internal string[] GetProjectFilesInSolution()
{
IVsSolution sol = ServiceProvider.GlobalProvider.GetService(typeof(SVsSolution)) as IVsSolution;
uint numProjects;
ErrorHandler.ThrowOnFailure(sol.GetProjectFilesInSolution((uint)__VSGETPROJFILESFLAGS.GPFF_SKIPUNLOADEDPROJECTS, 0, null, out numProjects));
string[] projects = new string[numProjects];
ErrorHandler.ThrowOnFailure(sol.GetProjectFilesInSolution((uint)__VSGETPROJFILESFLAGS.GPFF_SKIPUNLOADEDPROJECTS, numProjects, projects, out numProjects));
//GetProjectFilesInSolution also returns solution folders, so we want to do some filtering
//things that don't exist on disk certainly can't be project files
return projects.Where(p => !string.IsNullOrEmpty(p) && System.IO.File.Exists(p)).ToArray();
}
回答2:
Unfortunately could not find any working solution here, decided to post my own solution:
/// <summary>
/// Queries for all projects in solution, recursively (without recursion)
/// </summary>
/// <param name="sln">Solution</param>
/// <returns>List of projects</returns>
static List<Project> GetProjects(Solution sln)
{
List<Project> list = new List<Project>();
list.AddRange(sln.Projects.Cast<Project>());
for (int i = 0; i < list.Count; i++)
// OfType will ignore null's.
list.AddRange(list[i].ProjectItems.Cast<ProjectItem>().Select(x => x.SubProject).OfType<Project>());
return list;
}
And if you don't know what references / namespaces to add, you can pick up project with source code from here:
https://github.com/tapika/cppscriptcore/blob/2a73f45474c8b2179774fd4715b8d8e80080f3ae/Tools/vsStart/Program.cs#L478
And check namespaces / references.
回答3:
Works for me:
Add a field in your package for dte.
Get the DTE service.
Reference the Solution.
using EnvDTE;
using EnvDTE80;
In your constructor:
dte = this.ServiceProvider.GetService(typeof(EnvDTE.DTE)) as EnvDTE80.DTE2;
In your command handler:
Integer count = ((EnvDTE.SolutionClass)dte.Solution).Projects.Count;
I get the correct count from this.
Screenshot (requested)
Code
//------------------------------------------------------------------------------
// <copyright file="Command1.cs" company="Company">
// Copyright (c) Company. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
using System;
using System.ComponentModel.Design;
using System.Globalization;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using EnvDTE;
using EnvDTE80;
namespace SolExpExt
{
internal sealed class Command1
{
public const int CommandId = 0x0100;
public static readonly Guid CommandSet = new Guid("beff5a1a-dff5-4f6a-95c8-fd7ea7411a7b");
private DTE2 dte;
private readonly Package package;
private IVsSolution sol;
private Command1(Package package)
{
if (package == null)
{
throw new ArgumentNullException("package");
}
this.package = package;
OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
if (commandService != null)
{
var menuCommandID = new CommandID(CommandSet, CommandId);
var menuItem = new MenuCommand(this.MenuItemCallback, menuCommandID);
commandService.AddCommand(menuItem);
}
dte = this.ServiceProvider.GetService(typeof(EnvDTE.DTE)) as EnvDTE80.DTE2;
}
public static Command1 Instance
{
get;
private set;
}
private IServiceProvider ServiceProvider
{
get
{
return this.package;
}
}
public static void Initialize(Package package)
{
Instance = new Command1(package);
}
private void MenuItemCallback(object sender, EventArgs e)
{
string message = $"There are {dte.Solution.Projects.Count} projects in this solution.";
string title = "Command1";
VsShellUtilities.ShowMessageBox(
this.ServiceProvider,
message,
title,
OLEMSGICON.OLEMSGICON_INFO,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
}
}
}
回答4:
The following, shamelessly taken from AutoFindReplace, works using VS2015 Community:
using EnvDTE;
.
.
protected override void Initialize()
{
base.Initialize();
IServiceContainer serviceContainer = this as IServiceContainer;
dte = serviceContainer.GetService(typeof(SDTE)) as DTE;
var solutionEvents = dte.Events.SolutionEvents;
solutionEvents.Opened += OnSolutionOpened;
var i = dte.Solution.Projects.Count; // Happy days !
}
All the code lines above pre-exist in the solution within VSPackage.cs except for "var i = dte.Solution.Projects.Count;" which I added locally to VSPackage.cs just after line 44. I then open the solution, hit F5, and within the Experimental Instance I opened JoePublic.Sln and hey presto the count was '2' correctly - Bingo ! Happy days !