2016-12-05 2 views
4

Я изучаю расширяемость Visual Studio. Этот код из MSDN создает новый C# решение, содержащее проект с классом:Добавление директивы использования в класс программно

EnvDTE.DTE dte = this.GetService(typeof(Microsoft.VisualStudio.Shell.Interop.SDTE)) as EnvDTE.DTE; 
EnvDTE80.Solution2 solution = (EnvDTE80.Solution2)dte.Solution; 
try { 
    solution.Create(@"F:\Dev\Visual Studio 2013\Packages\Spikes\VSPNewSolution\Test\MySolution", "MySolution"); 

    string templatePath = solution.GetProjectTemplate("ConsoleApplication.zip", "CSharp"); 
    string projectPath = @"F:\Dev\Visual Studio 2013\Packages\Spikes\VSPNewSolution\\Test\MySolution\MyProject"; 

    /* 
    * from MZTools site : 
    * Once you have the template file name, you can add a project to the solution using the EnvDTE80.Solution.AddFromTemplate method. 
    * Note: this method returns null (Nothing) rather than the EnvDTE.Project created, 
    * so you may need to locate the created project in the Solution.Projects collection. 
    * See PRB: Solution.AddXXX and ProjectItems.AddXXX methods return Nothing (null). 
    */ 
    EnvDTE.Project project = solution.AddFromTemplate(templatePath, projectPath, "MyProject", false); 

    EnvDTE.ProjectItem projectItem; 
    String itemPath; 

    // Point to the first project 
    project = solution.Projects.Item(1); // try also "MyProject" 

    VSLangProj.VSProject vsProject = (VSLangProj.VSProject)project.Object; 
    vsProject.References.Add("NUnit.Framework"); 

    // Retrieve the path to the class template. 
    itemPath = solution.GetProjectItemTemplate("Class.zip", "CSharp"); 

    //Create a new project item based on the template, in this case, a Class. 
    projectItem = project.ProjectItems.AddFromTemplate(itemPath, "MyClass.cs"); 
} 
catch (Exception ex) { 
    System.Windows.Forms.MessageBox.Show("ERROR: " + ex.Message); 
} 

мне удалось добавить ссылку на MyProject с использованием VSLangProj.
Пока, так хорошо.
Полученный класс:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace MyProject 
{ 
    class MyClass 
    { 
    } 
} 

То, что я не нашел после того, как много googleing способ добавить с помощью директивы в коде класса (использованием NUnit.Framework; в данном случае).
Тривиальным способом было бы написать строку, непосредственно манипулирующую документу класса.
Есть ли способ сделать это программно с помощью расширяемости Visual Studio?

UPDATE

После нескольких попыток получить CodeClass объект для созданного класса, я попробовал код размещен в Finding a ProjectItem by type name via DTE с небольшими изменениями. Вот обновленный код:

EnvDTE.DTE dte = this.GetService(typeof(Microsoft.VisualStudio.Shell.Interop.SDTE)) as EnvDTE.DTE; 
EnvDTE80.Solution2 solution = (EnvDTE80.Solution2)dte.Solution; 
try { 

    string solutionPath = @"F:\Dev\Visual Studio 2013\Packages\Spikes\VSPNewSolution\Test\MySolution"; 
    solution.Create(solutionPath, "MySolution"); 

    string templatePath = solution.GetProjectTemplate("ConsoleApplication.zip", "CSharp"); 
    string projectPath = @"F:\Dev\Visual Studio 2013\Packages\Spikes\VSPNewSolution\\Test\MySolution\MyProject"; 

    EnvDTE.Project project = solution.AddFromTemplate(templatePath, projectPath, "MyProject", false); 

    EnvDTE.ProjectItem projectItem; 
    String itemPath; 

    foreach (EnvDTE.Project p in solution.Projects) { 
     if (p.Name == "MyProject") { 
      project = p; 
      break; 
     } 
    } 

    VSLangProj.VSProject vsProject = (VSLangProj.VSProject)project.Object; 
    vsProject.References.Add("NUnit.Framework"); 

    itemPath = solution.GetProjectItemTemplate("Class.zip", "CSharp"); 
    projectItem = project.ProjectItems.AddFromTemplate(itemPath, "MyClass.cs"); 

    // I decided to save both, just in case 
    solution.SaveAs(solutionPath + @"\MySolution.sln"); 
    project.Save(); 

    EnvDTE.CodeClass codeClass = FindClass(project, "MyClass.cs"); 

    // Display the source code for the class (from MSDN). 

    if (codeClass != null) { 
     EnvDTE.TextPoint start = codeClass.GetStartPoint(EnvDTE.vsCMPart.vsCMPartWhole); 
     EnvDTE.TextPoint finish = codeClass.GetEndPoint(EnvDTE.vsCMPart.vsCMPartWhole); 
     string src = start.CreateEditPoint().GetText(finish); 
     System.Windows.Forms.MessageBox.Show(src, codeClass.FullName + "Source"); 
    } 
} 
catch (Exception ex) { 
    System.Windows.Forms.MessageBox.Show("ERROR: " + ex.Message); 
    } 
} 

private CodeClass FindClass(Project project, string className) { 
    return FindClass(project.CodeModel.CodeElements, className); 
} 

private CodeClass FindClass(CodeElements elements, string className) { 
    foreach (CodeElement element in elements) { 
     if (element is CodeNamespace || element is CodeClass) { 
      CodeClass c = element as CodeClass; 
      if (c != null && c.Access == vsCMAccess.vsCMAccessPublic) { 
       if (c.FullName == className) 
        return c; 

       CodeClass subClass = FindClass(c.Members, className); 
       if (subClass != null) 
        return subClass; 
      } 

      CodeNamespace ns = element as CodeNamespace; 
      if (ns != null) { 
       CodeClass cc = FindClass(ns.Members, className); 
       if (cc != null) 
        return cc; 
      } 
     } 
    } 
    return null; 
} 

Ну, получается, что FindClass всегда возвращает нуль, поскольку project.CodeModel.CodeElements.Count равен нулю. Duh?

UPDATE 2
Ну, пожалуйста, не бейте me.The исходный код имел избыточную обратную косую черту в projectPath переменной.
Это вызвало проект.CodeModel.CodeElements.Count равным нулю.
Кроме того, FindClass требует класса FullName без расширения и ищет только общественных классов.
Я исправил код, но все же получил null в свою очередь (по моей собственной ошибке, я думаю: я, должно быть, что-то пропустил).
В любом случае, FindClass ищет данный класс в все проект CodeElements, включая классы в ссылки на проект.
Это лишний случай в моем случае, так как я ищу класс, локальный для проекта.
Итак, я написал функцию, которая просто делает это.
Вот оно:

public static CodeClass FindClassInProjectItems(Project project, string className) { 
      CodeClass result = null; 
      foreach (EnvDTE.ProjectItem pi in project.ProjectItems) {     
       if (pi.Name == className + ".cs") { 
        if (pi.FileCodeModel != null) { 
         foreach (EnvDTE.CodeElement ce in pi.FileCodeModel.CodeElements) { 
          if (ce is EnvDTE.CodeClass) { 
           result = ce as EnvDTE.CodeClass; 
           break; 
          } 
          else if (ce is EnvDTE.CodeNamespace) { 
           CodeNamespace ns = ce as CodeNamespace; 

           if (ns.Name == project.Name) { 
            foreach (CodeElement sce in ns.Members) { 
             if (sce is CodeClass && sce.Name == className) { 
              result = sce as CodeClass; 
              break; 
             } 
            } 
           }          
          } 
         } 
        } 
       }   
      } 
      return result; 
     } 

Он работает таким образом, я создал статический ClassFinder класс и добавил функцию.
Следующим шагом было получить полный исходный код класса, включая директивы using.
Я нашел образец на MSDN here, это важный код:

// Display the source code for the class. 
TextPoint start = cls.GetStartPoint(vsCMPart.vsCMPartWhole); 
TextPoint finish = cls.GetEndPoint(vsCMPart.vsCMPartWhole); 
string src = start.CreateEditPoint().GetText(finish); 

На самом деле, первая линия бросает исключение.
Так что я попытался все члены vsCMPart перечислимого: большинство из них сгенерирует исключение, за исключением: vsCMPart.vsCMPartBody, vsCMPart.vsCMPartHeader, vsCMPart.vsCMPartNavigate и vsCMPart.vsCMPartWholeWithAttributes.
vsCMPart.vsCMPartHeader и vsCMPart.vsCMPartWholeWithAttributes возвращает тот же результат (по крайней мере, в данном случае),
в то время как другие не возвращают весь код.
Чтобы краток:

private void DisplayClassSource(CodeClass codeClass) { 
    EnvDTE.TextPoint start = codeClass.GetStartPoint(vsCMPart.vsCMPartHeader); 
    EnvDTE.TextPoint finish = codeClass.GetEndPoint(); 
    string source = start.CreateEditPoint().GetText(finish);   
    System.Windows.Forms.MessageBox.Show(source, codeClass.FullName + "Class source"); 
} 

private void DisplayNamespaceSource(CodeNamespace codeNamespace) { 
    EnvDTE.TextPoint start = codeNamespace.GetStartPoint(EnvDTE.vsCMPart.vsCMPartWholeWithAttributes); 
    EnvDTE.TextPoint finish = codeNamespace.GetEndPoint(); 
    string src = start.CreateEditPoint().GetText(finish); 
    System.Windows.Forms.MessageBox.Show(src, codeNamespace.FullName + "Namespace source"); 
} 

Если мы хотим, чтобы исходный код, как он появляется в IDE, в том числе с использованием директив,
мы должны использовать объект classCode.ProjectItem:

private void DisplayClassFullSource(CodeClass codeClass) { 
     System.Text.StringBuilder sb = new System.Text.StringBuilder(); 
     foreach (CodeElement ce in codeClass.ProjectItem.FileCodeModel.CodeElements) { 
      if (ce.Kind == vsCMElement.vsCMElementImportStmt) { 
       // this is a using directive 
       // ce.Name throws an exception here ! 
       sb.AppendLine(GetImportCodeLines(ce)); 
      } 
      else if (ce.Kind == vsCMElement.vsCMElementNamespace) { 
       sb.AppendLine(); 
       sb.AppendLine(GetNamespaceCodeLines(ce)); 
      } 
     } 

     System.Windows.Forms.MessageBox.Show(sb.ToString(), codeClass.FullName + "class source"); 
    } 

    private static string GetImportCodeLines(CodeElement ce) { 
     TextPoint start = ce.GetStartPoint(vsCMPart.vsCMPartWholeWithAttributes); 
     TextPoint finish = ce.GetEndPoint(vsCMPart.vsCMPartWholeWithAttributes); 
     return start.CreateEditPoint().GetText(finish); 
    } 

    private string GetNamespaceCodeLines(CodeElement ce) { 
     EnvDTE.TextPoint start = ce.GetStartPoint(vsCMPart.vsCMPartWholeWithAttributes); 
     //EnvDTE.TextPoint finish = codeClass.GetEndPoint(EnvDTE.vsCMPart.vsCMPartWhole); // ERROR : the method or operation is not implemented 
     EnvDTE.TextPoint finish = ce.GetEndPoint(); 
     return start.CreateEditPoint().GetText(finish); 
    } 

Теперь мы очень близки к решению проблемы. См. Мой ответ. (извините, если это похоже на роман)

ответ

1

Нет прямого способа, насколько я могу сказать, добавить директиву использования CodeClass.
Единственный способ, который я нашел, это:
Он, безусловно, нуждается в улучшении, но он работает.
Этот код предполагает, что все используемые директивы находятся в строке в верхней части кода класса.
Он не будет работать должным образом, если, например, имеется указатель на использование в пространстве имен.
Он добавляет данную директиву сразу после последнего найденного.
Это не Проверьте код, чтобы определить, присутствует ли данная директива.

private void AddUsingDirectiveToClass(CodeClass codeClass, string directive) { 
     CodeElement lastUsingDirective = null; 

     foreach (CodeElement ce in codeClass.ProjectItem.FileCodeModel.CodeElements) { 
      if (ce.Kind == vsCMElement.vsCMElementImportStmt) { 
       // save it 
       lastUsingDirective = ce; 
      } 
      else { 
       if (lastUsingDirective != null) { 
        // insert given directive after the last one, on a new line 
        EditPoint insertPoint = lastUsingDirective.GetEndPoint().CreateEditPoint(); 
        insertPoint.Insert("\r\nusing " + directive + ";"); 
       } 
      } 
     } 
    } 

Таким образом, окончательный рабочий код

 EnvDTE.DTE dte = this.GetService(typeof(Microsoft.VisualStudio.Shell.Interop.SDTE)) as EnvDTE.DTE; 
     EnvDTE80.Solution2 solution = (EnvDTE80.Solution2)dte.Solution; 
     try { 
      /* 
      * NOTE while the MSDN sample states you must open an existing solution for the code to work, 
      * it works also without opening a solution. 
      */ 
      string solutionPath = @"F:\Dev\Visual Studio 2013\Packages\Spikes\VSPNewSolution\Test\MySolution"; 
      solution.Create(solutionPath, "MySolution"); 

      string templatePath = solution.GetProjectTemplate("ConsoleApplication.zip", "CSharp"); 
      string projectPath = solutionPath + @"\MyProject"; 

      /* 
      * from MZTools site : 
      * Once you have the template file name, you can add a project to the solution using the EnvDTE80.Solution.AddFromTemplate method. 
      * Note: this method returns null (Nothing) rather than the EnvDTE.Project created, 
      * so you may need to locate the created project in the Solution.Projects collection. 
      * See PRB: Solution.AddXXX and ProjectItems.AddXXX methods return Nothing (null). 
      */ 
      EnvDTE.Project project = solution.AddFromTemplate(templatePath, projectPath, "MyProject", false); 

      // the following code would do since there is only a single project 
      //project = solution.Projects.Item(1); 

      // tried this : 
      // project = solution.Projects.Item("MyProject"); 
      // but it throws an invalid argument exception 

      // search project by name 
      foreach (EnvDTE.Project p in solution.Projects) { 
       if (p.Name == "MyProject") { 
        project = p; 
        break; 
       } 
      } 

      // add a reference to NUnit 
      VSLangProj.VSProject vsProject = (VSLangProj.VSProject)project.Object; 
      vsProject.References.Add("NUnit.Framework"); 

      // Retrieve the path to the class template. 
      string itemPath = solution.GetProjectItemTemplate("Class.zip", "CSharp"); 

      //Create a new project item based on the template, in this case, a Class. 

      /* 
      * Here we find the same problem as with solution.AddFromTemplate(...) (see above) 
      */ 
      EnvDTE.ProjectItem projectItem = project.ProjectItems.AddFromTemplate(itemPath, "MyClass.cs"); 

      solution.SaveAs(solutionPath + @"\MySolution.sln");         
      project.Save(); 

      // retrieve the new class we just created 
      EnvDTE.CodeClass codeClass = ClassFinder.FindClassInProjectItems(project, "MyClass");        

      if (codeClass != null) { 
       DisplayClassFullSource(codeClass); 
       AddUsingDirectiveToClass(codeClass, "NUnit.Framework"); 
       project.Save(); 
       DisplayClassFullSource(codeClass); 
      } 


     } 
     catch (Exception ex) { 
      System.Windows.Forms.MessageBox.Show("ERROR: " + ex.Message); 
     }    
    } 

P.S.
Прежде чем принять этот ответ, я подожду некоторое время, если кто-то другой опубликует лучшее решение.

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