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

VSLangProjを使用してMyProjectへの参照を追加することができました。 ここまでは順調ですね。 結果のクラスは次のとおりです。

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

namespace MyProject
{
    class MyClass
    {
    }
}

何度もググっても見つからなかったのは、クラス コードに using ディレクティブを追加する方法です (この場合はNUnit.Framework を使用)。
簡単な方法は、クラス ドキュメントを直接操作する行を記述することです。
Visual Studio Extensibility を使用してプログラムで行う方法はありますか?

アップデート

作成されたクラスのCodeClassオブジェクトを取得しようとした後、投稿されたコードを試しました ほとんど変更を加えずに、DTE を介して型名で ProjectItem を検索します。更新されたコードは次のとおりです。

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

project.CodeModel.CodeElements.Count がゼロであるため、FindClass は常に null を返しますえ?

UPDATE 2
まあ、私を殴らないでください。元のコードには、projectPath変数に余分なバックスラッシュがありました。
これにより、project.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 enum のすべてのメンバーを試しました: それらのほとんどは例外をスローします: vsCMPart.vsCMPartBodyvsCMPart.vsCMPartHeadervsCMPart.vsCMPartNavigate、およびvsCMPart.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");
}  

using ディレクティブを含め、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);
    }  

今、私たちは問題の解決に非常に近づいています. 私の答えを見てください。(小説っぽくてすみません)

4

1 に答える 1

3

私が知る限り、using ディレクティブを CodeClass に追加する直接的な方法はありません。
私が見つけた唯一の方法はこれです:
確かに改善が必要ですが、うまくいきます。
このコードは、すべての using ディレクティブがクラス コードの先頭に一列に並んでいることを前提としています。
たとえば、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);
        }            
    } 

PS
この回答を受け入れる前に、他の誰かがより良い解決策を投稿する場合に備えて、しばらく待ちます。

編集
時間が経過しました。これ以上の投稿はありません。これを受け入れられた回答としてマークしました。

于 2016-12-11T18:49:26.060 に答える