Проблема с визуальным шаблоном студии и созданием каталога
Я пытаюсь создать шаблон Visual Studio (2010) (многопроект). Все кажется хорошим, за исключением того, что проекты создаются в подкаталоге решения. Это не то поведение, которое я ищу.
Почтовый файл содержит:
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
Любая помощь?
EDIT:
Кажется, что изменение <CreateNewFolder>false</CreateNewFolder>
, либо к true, либо false, ничего не меняет.
Ответы
Ответ 1
Чтобы создать решение на корневом уровне (не вставляя их в подпапку), вы должны создать два шаблона:
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);
}
Ответ 2
Это основано на ответе @drweb86 с некоторыми улучшениями и пояснениями.
Пожалуйста, обратите внимание на несколько вещей:
- Настоящий шаблон с ссылками на проекты находится под какой-то фиктивной папкой, так как у вас не может быть более одного корневого vstemplate. (Visual Studio не будет отображать ваш шаблон вообще при таком условии).
-
Все подпроекты\шаблоны должны находиться под папкой реального шаблона.
Пример внутренней структуры шаблона Zip:
RootTemplateFix.vstemplate
-> Template Folder
YourMultiTemplate.vstemplate
-->Sub Project Folder 1
SubProjectTemplate1.vstemplate
-->Sub Project Folder 2
SubProjectTemplate2.vstemplate
...
- В мастере корневого шаблона вы можете запустить свою форму выбора пользователя и добавить ее в статическую переменную. Sub wizards могут скопировать эти глобальные параметры в их частный словарь.
Пример:
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;
}
}
- Обратите внимание, что удаление исходной папки назначения происходит через другой поток.
Причина в том, что решение сгенерировано после, ваш мастер завершает работу, и эта целевая папка будет воссоздана.
Используя ohter thread, мы предполагаем, что решение и конечная папка назначения будут созданы, и только тогда мы сможем безопасно удалить эту папку.
Ответ 3
Многопроектные шаблоны очень сложны. Я обнаружил, что обработка $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.
Ответ 4
Другое решение с использованием только мастера:
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>
в порядке наименьшей ссылки. из-за удаления и добавления проектов.
Ответ 5
На самом деле есть обходной путь, он уродлив, но после того, как diggin "в Интернете я не мог ничего придумать. Таким образом, при создании нового экземпляра многопроектного решения вам необходимо снять флажок" Создать новую папку" в диалоговом окне. И прежде чем вы начнете, структура каталогов должна выглядеть как
Projects
{no dedicated folder yet}
После создания решения структура будет следующей:
Projects
+--MyApplication1
+-- Project1
+-- Project2
solution file
Таким образом, единственное незначительное отличие от желаемой структуры - это место файла решения. Итак, первое, что вам нужно сделать после создания и отображения нового решения - выберите решение и выберите "Сохранить как" в меню, а затем переместите файл в папку MyApplication1
. Затем удалите предыдущий файл решения, и вы здесь, структура файла выглядит следующим образом:
Projects
+--MyApplication1
+-- Project1
+-- Project2
solution file