Как вывести список всех проектов в текущем решении с помощью EnvDTE?

Я следил за руководством MSDN Hello World по разработке расширений Visual Studio (в этой статье конкретно рассматривается его создание в виде команды панели инструментов Visual Studio).

Я пытаюсь перечислить все проекты, содержащиеся в текущем/активном решении.

В автоматически сгенерированном коде для шаблона Command.

Я попробовал свойство Projects EnvDTE Solution, но оно не показывает никаких проектов.

Также есть свойство ActiveSolutionProjects, но оно также показывает пустой массив.

Как это достигается?

P.S.: Я попробовал интерфейсы DTE и DTE2, так как из документации непонятно, какую версию использовать. Я получаю нулевую услугу для DTE2, поэтому я использую DTE.

введите описание изображения здесь

Мой обозреватель решений выглядит так:

введите описание изображения здесь


Обновление: Берт Хейбен из gitter/extendvs предложил следующее, найденное на примеры расширения VSSDK > - но это тоже не работает (возвращает 0 элементов как внутри конструктора, так и внутри функции обратного вызова):

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;
}

person Veverke    schedule 24.08.2016    source источник
comment
При беглом просмотре кода это должно работать. Что получает this.ServiceProvider? Изменится ли что-нибудь, если вы вместо этого сделаете GetService(typeof(SDTE)), но оставите приведения как есть?   -  person Jason Malinowski    schedule 25.08.2016
comment
В конструкторе Command1 решение, вероятно, еще не загружено. Попробуйте переместить свой код в MenuItemCallback.   -  person Sergey Vlasov    schedule 25.08.2016
comment
@JasonMalinowski: он получает System.__ComObject, который содержит свойство Solution, которое, в свою очередь, содержит свойство Projects. Я попробовал SDTE, как вы предложили, но SDTE - это пустой интерфейс, так что это мало помогает.   -  person Veverke    schedule 25.08.2016
comment
@SergeyVlasov: Пробовал переносить туда код, тоже самое.   -  person Veverke    schedule 25.08.2016
comment
@Veverke: я имел в виду, что вы используете SDTE в аргументе GetService, но вы все равно приводите его к обычному интерфейсу. Нет правила, что оба должны быть одинаковыми.   -  person Jason Malinowski    schedule 25.08.2016
comment
Говоря My Solution Explorer, вы правильно имеете в виду тот, который находится в экспериментальном экземпляре? Как вы запускаете как отладку, а затем открываете решение в экспериментальном экземпляре, который запускается?   -  person Paul Swetz    schedule 26.08.2016


Ответы (4)


Чтобы получить объекты EnvDTE.Project:

    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;
    }

В частности, рекурсия необходима, чтобы копаться в папках решений.

Если вам просто нужны пути к файлам, вы можете сделать это без использования 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();
    }
person jmoffatt    schedule 09.12.2016
comment
Это рекурсивное решение очень хорошее, но с двумя проблемами: 1. Оно будет аварийно завершать работу в незагруженных папках (необходимо проверять наличие null после каждого var proj = (EnvDTE.Project)item.Object;) и 2. Оно будет рекурсивным дважды для папок решений, заканчивая дубликатами проектов в списке, так что вы нужно else перед foreach (EnvDTE.UIHierarchyItem innerItem in item.UIHierarchyItems) и if (item.ProjectItems != null) - person Gone Coding; 24.01.2018
comment
'sol' всегда возвращает ноль. - person Latency; 29.11.2018
comment
Не работает для сложных иерархий решений. Я опубликую свое собственное решение. - person TarmoPikaro; 27.01.2019

К сожалению, здесь не удалось найти рабочего решения, решил опубликовать собственное решение:

/// <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;
}

А если вы не знаете, какие ссылки/пространства имен добавить, то можете забрать проект с исходниками отсюда:

https://github.com/tapika/cppscriptcore/blob/2a73f45474c8b2179774fd4715b8d8e80080f3ae/Tools/vsStart/Program.cs#L478

И проверьте пространства имен/ссылки.

person TarmoPikaro    schedule 27.01.2019

Работает на меня:

Добавьте в свой пакет поле для dte. Получить услугу DTE. Ссылка на решение.

using EnvDTE;
using EnvDTE80;

В вашем конструкторе:

dte = this.ServiceProvider.GetService(typeof(EnvDTE.DTE)) as EnvDTE80.DTE2;

В вашем обработчике команд:

Integer count = ((EnvDTE.SolutionClass)dte.Solution).Projects.Count;

Я получаю правильный счет от этого.

Скриншот (требуется)

введите здесь описание изображения

Код

//------------------------------------------------------------------------------
// <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);
        }
    }
}
person Mister Magoo    schedule 25.08.2016
comment
Можете ли вы поделиться снимком экрана всего окна IDE, показывающего обозреватель решений вместе с кодом? То, что вы предлагаете выше, все еще не работает для меня. Я все еще получаю 0 проектов. Итак, вы получаете 1 ? - person Veverke; 25.08.2016
comment
@Veverke это помогло? - person Mister Magoo; 25.08.2016
comment
Привет, спасибо за новый отзыв, я был в отпуске, проверю это и свяжусь с вами. - person Veverke; 31.08.2016

Следующее, беззастенчиво взятое из AutoFindReplace, работает с использованием 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 !
}

Все приведенные выше строки кода уже существуют в решении в VSPackage.cs, кроме "var i = dte.Solution.Projects". .Считать;" который я добавил локально в VSPackage.cs сразу после строки 44. Затем я открываю решение, нажимаю F5 и в экспериментальном экземпляре Я открыл JoePublic.Sln, и сразу же счет был "2" правильно - Бинго! Счастливые дни !

person Greg Trevellick    schedule 05.11.2016