2016-12-05 51 views
4

我正在学习Visual Studio扩展性。 这从MSDN代码创建一个包含有一个班的一个项目一个新的C#解决方案:以编程方式将using指令添加到类

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

我设法添加一个参考使用VSLangProjMyProject的
到目前为止,这么好。
生成的类是:

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

namespace MyProject 
{ 
    class MyClass 
    { 
    } 
} 

我很多googleing后没发现什么是类中的代码添加using指令 的方式(使用NUnit.Framework ;在这种情况下)。
简单的方法是编写直接操作类文档的行。
有没有办法使用Visual Studio Extensibility以编程方式执行它?

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始终返回null,因为project.CodeModel.CodeElements.Count为零。 Duh?

更新2
好了,请不要打me.The原代码必须在projectPath可变盈余反斜杠。
这导致project.CodeModel.CodeElements.Count为零。
另外,FindClass要求类别姓名没有扩展名和搜索public只有类。
我纠正了代码,但仍然得到了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.vsCMPartBodyvsCMPart.vsCMPartHeadervsCMPart.vsCMPartNavigatevsCMPart.vsCMPartWholeWithAttributes
vsCMPart.vsCMPartHeadervsCMPart.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没有直接的方式。
我发现的唯一方法是这样的:
它当然需要改进,但它的工作原理。
此代码假定所有使用指令都是连续的,位于类代码的顶部。
例如,如果命名空间中存在using指令,它将无法正常工作。
它在上次找到后添加给定的指令。
它确实不是检查代码以确定指令是否已经存在。

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

附:
在接受这个答案之前,我会等一会儿,以防其他人发布更好的解决方案。

编辑
时间流逝,没有关于这个问题的更多文章。我将此标记为接受的答案。

相关问题