Проблема с созданием шаблона и каталога Visual Studio - PullRequest
25 голосов
/ 07 октября 2010

Я пытаюсь сделать шаблон Visual Studio (2010) (мультипроект).Все кажется хорошим, за исключением того, что проекты создаются в подкаталоге решения.Это не то поведение, которое я ищу.

ZIP-файл содержит:

Folder1
+-- Project1
    +-- Project1.vstemplate
+-- Project2
    +-- Project2.vstemplate
myapplication.vstemplate

Вот мой корневой шаблон:

<VSTemplate Version="3.0.0" Type="ProjectGroup" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
    <TemplateData>
        <Name>My application</Name>
        <Description></Description>
        <Icon>Icon.ico</Icon>
        <ProjectType>CSharp</ProjectType>
  <RequiredFrameworkVersion>4.0</RequiredFrameworkVersion>
  <DefaultName>MyApplication</DefaultName>
  <CreateNewFolder>false</CreateNewFolder>
    </TemplateData>
    <TemplateContent>
        <ProjectCollection>
   <SolutionFolder Name="Folder1">
    <ProjectTemplateLink ProjectName="$safeprojectname$.Project1">Folder1\Project1\Project1.vstemplate</ProjectTemplateLink>
    <ProjectTemplateLink ProjectName="$safeprojectname$.Project2">Folder2\Project2\Project2.vstemplate</ProjectTemplateLink>
   </SolutionFolder>
        </ProjectCollection>
    </TemplateContent>
</VSTemplate>

И при созданииРешение с использованием этого шаблона, я в конечном итоге с каталогами, как это:

Projects
+-- MyApplication1
    +-- MyApplication1 // I'd like to have NOT this directory
        +-- Folder1
            +-- Project1
            +-- Project2
    solution file

Любая помощь?

РЕДАКТИРОВАТЬ:

Кажется, что изменение <CreateNewFolder>false</CreateNewFolder>, либо в trueили ложь, ничего не меняет.

Ответы [ 5 ]

8 голосов
/ 01 июня 2011

Чтобы создать решение на корневом уровне (не вкладывать их в подпапку), необходимо создать два шаблона: 1) Шаблон заглушки ProjectGroup с вашим мастером внутри, который создаст новый проект в конце из вашего 2) Шаблон проекта

используйте для этого следующий подход

1.Добавьте шаблон примерно так:

  <VSTemplate Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="ProjectGroup">
    <TemplateData>
      <Name>X Application</Name>
      <Description>X Shell.</Description>
      <ProjectType>CSharp</ProjectType>
      <Icon>__TemplateIcon.ico</Icon>
    </TemplateData>
    <TemplateContent>
    </TemplateContent>
    <WizardExtension>
    <Assembly>XWizard, Version=1.0.0.0, Culture=neutral</Assembly>
    <FullClassName>XWizard.FixRootFolderWizard</FullClassName>
    </WizardExtension>  
  </VSTemplate>

2.Добавить код в мастер

// creates new project at root level instead of subfolder.
public class FixRootFolderWizard : IWizard
{
    #region Fields

    private string defaultDestinationFolder_;
    private string templatePath_;
    private string desiredNamespace_;

    #endregion

    #region Public Methods
    ...
    public void RunFinished()
    {
        AddXProject(
            defaultDestinationFolder_,
            templatePath_,
            desiredNamespace_);
    }

    public void RunStarted(object automationObject,
        Dictionary<string, string> replacementsDictionary,
        WizardRunKind runKind, object[] customParams)
    {
        defaultDestinationFolder_ = replacementsDictionary["$destinationdirectory$"];
        templatePath_ = 
            Path.Combine(
                Path.GetDirectoryName((string)customParams[0]),
                @"Template\XSubProjectTemplateWizard.vstemplate");

         desiredNamespace_ = replacementsDictionary["$safeprojectname$"];

         string error;
         if (!ValidateNamespace(desiredNamespace_, out error))
         {
             controller_.ShowError("Entered namespace is invalid: {0}", error);
             controller_.CancelWizard();
         }
     }

     public bool ShouldAddProjectItem(string filePath)
     {
         return true;
     }

     #endregion
 }

 public void AddXProject(
     string defaultDestinationFolder,
     string templatePath,
     string desiredNamespace)
 {
     var dte2 = (DTE) System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.10.0");
     var solution = (EnvDTE100.Solution4) dte2.Solution;

     string destinationPath =
         Path.Combine(
             Path.GetDirectoryName(defaultDestinationFolder),
             "X");

     solution.AddFromTemplate(
         templatePath,
         destinationPath,
         desiredNamespace,
         false);
     Directory.Delete(defaultDestinationFolder);
}
5 голосов
/ 20 июля 2015

Это основано на ответе @ drweb86 с некоторыми улучшениями и объяснениями.
Пожалуйста, обратите внимание на несколько вещей:

  1. Настоящий шаблон со ссылками на проекты находится в некоторой фиктивной папке, поскольку у вас не может быть более одного корневого vstemplate. (Visual studio вообще не будет отображать ваш шаблон при таких условиях).
  2. Все подпроекты \ шаблоны должны находиться в реальной папке с файлами шаблонов.
    Пример внутренней структуры почтового шаблона:

    RootTemplateFix.vstemplate
    -> Template Folder
       YourMultiTemplate.vstemplate
            -->Sub Project Folder 1
               SubProjectTemplate1.vstemplate
            -->Sub Project Folder 2
               SubProjectTemplate2.vstemplate
            ...
    
  3. В мастере корневых шаблонов вы можете запустить форму выбора пользователя и добавить их в статическую переменную. Мастера могут копировать эти глобальные параметры в свой личный словарь.

Пример:

   public class WebAppRootWizard : IWizard
   {
    private EnvDTE._DTE _dte;
    private string _originalDestinationFolder;
    private string _solutionFolder;
    private string _realTemplatePath;
    private string _desiredNamespace;

    internal readonly static Dictionary<string, string> GlobalParameters = new Dictionary<string, string>();

    public void BeforeOpeningFile(ProjectItem projectItem)
    {
    }

    public void ProjectFinishedGenerating(Project project)
    {
    }

    public void ProjectItemFinishedGenerating(ProjectItem
        projectItem)
    {
    }

    public void RunFinished()
    {
        //Run the real template
        _dte.Solution.AddFromTemplate(
            _realTemplatePath,
            _solutionFolder,
            _desiredNamespace,
            false);

        //This is the old undesired folder
        ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(DeleteDummyDir), _originalDestinationFolder);
    }

    private void DeleteDummyDir(object oDir)
    {
        //Let the solution and dummy generated and exit...
        System.Threading.Thread.Sleep(2000);

        //Delete the original destination folder
        string dir = (string)oDir;
        if (!string.IsNullOrWhiteSpace(dir) && Directory.Exists(dir))
        {
            Directory.Delete(dir);
        }
    }

    public void RunStarted(object automationObject,
        Dictionary<string, string> replacementsDictionary,
        WizardRunKind runKind, object[] customParams)
    {
        try
        {
            this._dte = automationObject as EnvDTE._DTE;

            //Create the desired path and namespace to generate the project at
            string temlateFilePath = (string)customParams[0];
            string vsixFilePath = Path.GetDirectoryName(temlateFilePath);
            _originalDestinationFolder = replacementsDictionary["$destinationdirectory$"];
            _solutionFolder = replacementsDictionary["$solutiondirectory$"];
            _realTemplatePath = Path.Combine(
                vsixFilePath,
                @"Template\BNHPWebApplication.vstemplate");
            _desiredNamespace = replacementsDictionary["$safeprojectname$"];

            //Set Organization
            GlobalParameters.Add("$registeredorganization$", "My Organization");

            //User selections interface
            WebAppInstallationWizard inputForm = new WebAppInstallationWizard();
            if (inputForm.ShowDialog() == DialogResult.Cancel)
            {
                throw new WizardCancelledException("The user cancelled the template creation");
            }

            // Add user selection parameters.
            GlobalParameters.Add("$my_user_selection$",
                inputForm.Param1Value);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    public bool ShouldAddProjectItem(string filePath)
    {
        return true;
    }
}    
  1. Обратите внимание, что исходное удаление папки назначения выполняется через другой поток.
    Причина в том, что решение генерируется после , когда ваш мастер завершает работу, и эта папка назначения будет воссоздана.
    Используя другой поток, мы предполагаем, что папка с решением и конечным пунктом назначения будет создана, и только тогда мы сможем безопасно удалить эту папку.
4 голосов
/ 17 декабря 2015

Другое решение с использованием одного мастера:

    public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
    {
        try
        {
            _dte = automationObject as DTE2;
            _destinationDirectory = replacementsDictionary["$destinationdirectory$"];
            _safeProjectName = replacementsDictionary["$safeprojectname$"];

            //Add custom parameters
        }
        catch (WizardCancelledException)
        {
            throw;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex + Environment.NewLine + ex.StackTrace, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            throw new WizardCancelledException("Wizard Exception", ex);
        }
    }

    public void RunFinished()
    {
        if (!_destinationDirectory.EndsWith(_safeProjectName + Path.DirectorySeparatorChar + _safeProjectName))
            return;

        //The projects were created under a seperate folder -- lets fix it
        var projectsObjects = new List<Tuple<Project,Project>>();
        foreach (Project childProject in _dte.Solution.Projects)
        {
            if (string.IsNullOrEmpty(childProject.FileName)) //Solution Folder
            {
                projectsObjects.AddRange(from dynamic projectItem in childProject.ProjectItems select new Tuple<Project, Project>(childProject, projectItem.Object as Project));
            }
            else
            {
                projectsObjects.Add(new Tuple<Project, Project>(null, childProject));
            }
        }

        foreach (var projectObject in projectsObjects)
        {
            var projectBadPath = projectObject.Item2.FileName;
            var projectGoodPath = projectBadPath.Replace(
                _safeProjectName + Path.DirectorySeparatorChar + _safeProjectName + Path.DirectorySeparatorChar, 
                _safeProjectName + Path.DirectorySeparatorChar);

            _dte.Solution.Remove(projectObject.Item2);

            Directory.Move(Path.GetDirectoryName(projectBadPath), Path.GetDirectoryName(projectGoodPath));

            if (projectObject.Item1 != null) //Solution Folder
            {
                var solutionFolder = (SolutionFolder)projectObject.Item1.Object;
                solutionFolder.AddFromFile(projectGoodPath);
            }
            else
            {
                _dte.Solution.AddFromFile(projectGoodPath);
            }
        }

        ThreadPool.QueueUserWorkItem(dir =>
        {
            System.Threading.Thread.Sleep(2000);
            Directory.Delete(_destinationDirectory, true);
        }, _destinationDirectory);
    }

Это поддерживает один уровень папки решения (если вы хотите, вы можете сделать мое решение рекурсивным для поддержки всех уровней)

Удостоверьтесь, что поместит проекты в тег <ProjectCollection> в порядке большинства ссылок на наименее ссылки.из-за удаления и добавления проектов.

3 голосов
/ 23 апреля 2014

Многопроектные шаблоны очень сложны.Я обнаружил, что обработка $safeprojectname$ делает практически невозможным создание многопроектного шаблона и правильную замену значений пространства имен.Мне пришлось создать собственный мастер, который высвечивает новую переменную $saferootprojectname$, которая всегда является значением, которое пользователь вводит в имя для нового проекта.

В SideWaffle (который представляет собой пакет шаблонов со многими шаблонами) у нас есть пара многопроектных шаблонов.SideWaffle использует пакет TemplateBuilder NuGet.В TemplateBuilder есть мастера, которые понадобятся вам для вашего многопроектного шаблона.

У меня есть 6-минутное видео по созданию шаблонов проектов с TemplateBuilder .Для шаблонов с несколькими проектами процесс немного сложнее (но все же намного лучше, чем без TemplateBuilder. У меня есть пример шаблона с несколькими проектами в источниках SideWaffle на https://github.com/ligershark/side-waffle/tree/master/TemplatePack/ProjectTemplates/Web/_Sample%20Multi%20Project.

1 голос
/ 21 июля 2015

На самом деле есть обходной путь, это уродливо, но после копания в Интернете я не смог придумать ничего лучшего.Таким образом, при создании нового экземпляра мультипроектного решения необходимо снять флажок «создать новую папку» в диалоговом окне.И перед началом работы структура каталогов должна выглядеть следующим образом:

 Projects
{no dedicated folder yet}

. После создания решения структура будет выглядеть следующим образом:

Projects
    +--MyApplication1
         +-- Project1
         +-- Project2
    solution file

Таким образом, единственное небольшое отличие от желаемой структурыместо файла решения.Итак, первое, что вы должны сделать после генерации и показа нового решения - выберите решение и выберите «Сохранить как» в меню, затем переместите файл в папку MyApplication1.Затем удалите предыдущий файл решения, и вот, пожалуйста, структура файла выглядит следующим образом:

Projects
    +--MyApplication1
         +-- Project1
         +-- Project2
         solution file
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...