visual-studio Visual Studio 模板和目录创建问题
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3882764/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Issue with visual studio template & directory creation
提问by Fabian Vilers
I'm trying to make a Visual Studio (2010) template (multi-project). Everything seems good, except that the projects are being created in a sub-directory of the solution. This is not the behavior I'm looking for.
我正在尝试制作一个 Visual Studio (2010) 模板(多项目)。一切似乎都很好,只是项目是在解决方案的子目录中创建的。这不是我正在寻找的行为。
The zip file contains:
压缩文件包含:
Folder1
+-- Project1
+-- Project1.vstemplate
+-- Project2
+-- Project2.vstemplate
myapplication.vstemplate
Here's my root template:
这是我的根模板:
<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>
And, when creating the solution using this template, I end up with directories like this:
而且,在使用此模板创建解决方案时,我最终会得到如下目录:
Projects
+-- MyApplication1
+-- MyApplication1 // I'd like to have NOT this directory
+-- Folder1
+-- Project1
+-- Project2
solution file
Any help?
有什么帮助吗?
EDIT:
编辑:
It seems that modifying <CreateNewFolder>false</CreateNewFolder>, either to true or false, doesn't change anything.
似乎将 修改<CreateNewFolder>false</CreateNewFolder>为 true 或 false 并没有改变任何东西。
回答by Siarhei Kuchuk
To create solution at root level (not nest them in subfolder) you must create two templates: 1) ProjectGroup stub template with your wizard inside that will create new project at the end from your 2) Project template
要在根级别创建解决方案(不要将它们嵌套在子文件夹中),您必须创建两个模板:1)ProjectGroup 存根模板,其中包含您的向导,将在最后从您的 2)项目模板创建新项目
use the following approach for that
使用以下方法
1. Add template something like this
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. Add code to wizard
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);
}
回答by Yaron
This is based on @drweb86 answer with some improvments and explanations.
Please notice few things:
这是基于@drweb86 的答案,并进行了一些改进和解释。
请注意以下几点:
- The real template with projects links is under some dummy folder since you can't have more than one root vstemplate. (Visual studio will not display your template at all at such condition).
All the sub projects\templates have to be located under the real template file folder.
Zip template internal structure example:RootTemplateFix.vstemplate -> Template Folder YourMultiTemplate.vstemplate -->Sub Project Folder 1 SubProjectTemplate1.vstemplate -->Sub Project Folder 2 SubProjectTemplate2.vstemplate ...- On the root template wizard you can run your user selection form and add them into a static variable. Sub wizards can copy these Global Parameters into their private dictionary.
- 带有项目链接的真实模板位于某个虚拟文件夹下,因为您不能拥有多个根 vstemplate。(在这种情况下,Visual Studio 根本不会显示您的模板)。
所有子项目\模板必须位于真正的模板文件夹下。
Zip模板内部结构示例:RootTemplateFix.vstemplate -> Template Folder YourMultiTemplate.vstemplate -->Sub Project Folder 1 SubProjectTemplate1.vstemplate -->Sub Project Folder 2 SubProjectTemplate2.vstemplate ...- 在根模板向导中,您可以运行用户选择表单并将它们添加到静态变量中。子向导可以将这些全局参数复制到他们的私人字典中。
Example:
例子:
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;
}
}
- Notice that the original destination folder deletion is done via a different thread.
The reason is that the solution is generated afteryour wizard ends and this destination folder will get recreated.
By using ohter thread we assume that the solution and final destination folder will get created and only then we can safely delete this folder.
- 请注意,原始目标文件夹的删除是通过不同的线程完成的。
原因是向导结束后会生成解决方案,并且将重新创建此目标文件夹。
通过使用其他线程,我们假设将创建解决方案和最终目标文件夹,然后我们才能安全地删除此文件夹。
回答by EliSherer
Another solution with using a Wizard alone:
单独使用向导的另一种解决方案:
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);
}
This supports one level of solution folder (if you want you can make my solution recursive to support every levels)
这支持一级解决方案文件夹(如果您愿意,可以使我的解决方案递归以支持每个级别)
Make Sureto put the projects in the <ProjectCollection>tag in order of most referenced to least referenced. because of the removal and adding of projects.
确保<ProjectCollection>按照引用最多到最少引用的顺序将项目放在标签中。因为删除和添加了项目。
回答by Sayed Ibrahim Hashimi
Multi-project templates are very tricky. I've found that the handling of $safeprojectname$makes it almost impossible to create a multi-project template and have the namespace values replaced correctly. I've had to create a custom wizard which light up a new variable $saferootprojectname$which is always the value that the user enters into the name for the new project.
多项目模板非常棘手。我发现处理$safeprojectname$使得几乎不可能创建多项目模板并正确替换命名空间值。我不得不创建一个自定义向导,它点亮一个新变量$saferootprojectname$,该变量始终是用户输入新项目名称的值。
In SideWaffle(which is a template pack with many templates) we have a couple multi-project templates. SideWaffle uses the TemplateBuilder NuGet package. TemplateBuilder has the wizards that you'll need for your multi-project template.
在SideWaffle(这是一个包含许多模板的模板包)中,我们有几个多项目模板。SideWaffle 使用 TemplateBuilder NuGet 包。TemplateBuilder 具有您的多项目模板所需的向导。
I have a 6 minute video on creating project templates with TemplateBuilder. For multi-project templates the process is a bit more cumbersome (but still much better than w/o TemplateBuilder. I have a sample multi-project template in the SideWaffle sources at https://github.com/ligershark/side-waffle/tree/master/TemplatePack/ProjectTemplates/Web/_Sample%20Multi%20Project.
我有一个关于使用 TemplateBuilder 创建项目模板的6 分钟视频。对于多项目模板,该过程有点麻烦(但仍然比没有 TemplateBuilder 好得多。我在https://github.com/ligershark/side-waffle/的 SideWaffle 源中有一个示例多项目模板tree/master/TemplatePack/ProjectTemplates/Web/_Sample%20Multi%20Project。
回答by Konstantin Chernov
Actually there is a workaround, it is ugly, but after diggin' the web I couldnt invent anything better. So, when creating a new instance of multiproject solution, you have to uncheck the 'create new folder' checkbox in the dialog. And before you start the directory structure should be like
实际上有一个解决方法,它很难看,但是在挖掘网络之后我无法发明任何更好的东西。因此,在创建多项目解决方案的新实例时,您必须取消选中对话框中的“创建新文件夹”复选框。在你开始之前,目录结构应该是这样的
Projects
{no dedicated folder yet}
After you create a solution a structure would be the following:
创建解决方案后,结构如下:
Projects
+--MyApplication1
+-- Project1
+-- Project2
solution file
So the only minor difference from the desired structure is the place of the solution file. So the first thing you should do after the new solution is generated and shown - select the solution and select "Save as" in menu, then move the file into the MyApplication1folder. Then delete the previous solution file and here you are, the file structure is like this:
因此,与所需结构的唯一细微差别是解决方案文件的位置。因此,在生成并显示新解决方案后,您应该做的第一件事是选择解决方案并在菜单中选择“另存为”,然后将文件移动到文件MyApplication1夹中。然后把之前的解决方案文件删掉就到了,文件结构是这样的:
Projects
+--MyApplication1
+-- Project1
+-- Project2
solution file

