2016-12-05 47 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); 
     }    
    } 

附:
在接受這個答案之前,我會等一會兒,以防其他人發佈更好的解決方案。

編輯
時間流逝,沒有關於這個問題的更多文章。我將此標記爲接受的答案。

相關問題