如何在 Visual Studio 中隐藏自定义工具生成的文件
Posted
技术标签:
【中文标题】如何在 Visual Studio 中隐藏自定义工具生成的文件【英文标题】:How to hide files generated by custom tool in Visual Studio 【发布时间】:2011-02-27 01:15:48 【问题描述】:我希望隐藏我的自定义工具生成的文件,但我找不到任何有关如何完成此操作的文档。
我正在寻找的一个示例是 WPF 代码隐藏文件。这些文件不会显示在 Visual Studio 项目视图中,但会与项目一起编译并在 IntelliSense 中可用。 WPF 代码隐藏文件(例如 Window1.g.i.cs)由自定义工具生成。
【问题讨论】:
您将生成的文件保存在哪里(相对于源文件)? 输出目录与输入目录相同。 你说WPF代码隐藏文件是什么意思?如果我创建一个 WPF 应用程序,我会得到一个名为 MainWindow.xaml 的文件,该文件可以展开以显示我认为是代码隐藏文件 MainWindow.xaml.cs。 有一个自动生成的隐藏文件。构建项目时查看输出窗口。 【参考方案1】:解决方案是创建一个 Target,将您的文件添加到 Compile ItemGroup,而不是将它们显式添加到您的 .csproj 文件中。这样,Intellisense 将看到它们并将它们编译到您的可执行文件中,但它们不会显示在 Visual Studio 中。
简单示例
您还需要确保您的目标已添加到 CoreCompileDependsOn
属性中,以便在编译器运行之前执行。
这是一个非常简单的例子:
<PropertyGroup>
<CoreCompileDependsOn>$(CoreCompileDependsOn);AddToolOutput</CoreCompileDependsOn>
</PropertyGroup>
<Target Name="AddToolOutput">
<ItemGroup>
<Compile Include="HiddenFile.cs" />
</ItemGroup>
</Target>
如果您将其添加到 .csproj 文件的底部(就在 </Project>
之前),您的“HiddenFile.cs”将包含在您的编译中,即使它没有出现在 Visual Studio 中。
使用单独的 .targets 文件
您通常会将其放置在单独的 .targets 文件中,而不是将其直接放在您的 .csproj 文件中:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
</Project>
并使用<Import Project="MyTool.targets">
导入您的.csproj。建议使用 .targets 文件,即使是一次性使用,因为它将您的自定义代码与 Visual Studio 维护的 .csproj 中的内容分开。
构造生成的文件名
如果您正在创建通用工具和/或使用单独的 .targets 文件,您可能不想明确列出每个隐藏文件。相反,您希望从项目中的其他设置生成隐藏文件名。例如,如果您希望所有资源文件在“obj”目录中都有相应的工具生成文件,那么您的目标将是:
<Target Name="AddToolOutput">
<ItemGroup>
<Compile Include="@(Resource->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')" />
</ItemGroup>
</Target>
“IntermediateOutputPath”属性是我们都知道的“obj”目录,但是如果您的 .targets 的最终用户自定义了它,您的中间文件仍然会在同一个位置找到。如果您希望生成的文件位于主项目目录中而不是“obj”目录中,则可以将其关闭。
如果您只想部分由您的自定义工具处理现有项目类型的文件?例如,您可能希望为所有带有“.xyz”扩展名的页面和资源文件生成文件。
<Target Name="AddToolOutput">
<ItemGroup>
<MyToolFiles Include="@(Page);@(Resource)" Condition="'%(Extension)'=='.xyz' />
<Compile Include="@(MyToolFiles->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')"/>
</ItemGroup>
</Target>
请注意,您不能在*** ItemGroup 中使用 %(Extension) 之类的元数据语法,但可以在 Target 中使用。
使用自定义项目类型(又名构建操作)
以上处理具有现有项目类型的文件,例如页面、资源或编译(Visual Studio 将此称为“构建操作”)。如果您的项目是一种新类型的文件,您可以使用您自己的自定义项目类型。例如,如果您的输入文件称为“Xyz”文件,则您的项目文件可以将“Xyz”定义为有效的项目类型:
<ItemGroup>
<AvailableItemName Include="Xyz" />
</ItemGroup>
之后,Visual Studio 将允许您在文件属性的构建操作中选择“Xyz”,从而将其添加到您的 .csproj:
<ItemGroup>
<Xyz Include="Something.xyz" />
</ItemGroup>
现在您可以使用“Xyz”项目类型来创建工具输出的文件名,就像我们之前使用“资源”项目类型所做的那样:
<Target Name="AddToolOutput">
<ItemGroup>
<Compile Include="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')" />
</ItemGroup>
</Target>
使用自定义项目类型时,您可以通过将项目映射到另一种项目类型(也称为构建操作)来使您的项目也由内置机制处理。如果您的“Xyz”文件确实是 .cs 文件或 .xaml 或者需要制作它们,这将非常有用
嵌入式资源。例如,您可以编译所有带有 Xyz 的“构建操作”的文件:
<ItemGroup>
<Compile Include="@(Xyz)" />
</ItemGroup>
或者如果你的“Xyz”源文件应该存储为嵌入式资源,你可以这样表达:
<ItemGroup>
<EmbeddedResource Include="@(Xyz)" />
</ItemGroup>
请注意,如果您将第二个示例放在 Target 中,则第二个示例将不起作用,因为直到核心编译之前才会评估目标。要在 Target 中进行这项工作,您必须在 PrepareForBuildDependsOn 属性中列出目标名称,而不是 CoreCompileDependsOn。
从 MSBuild 调用您的自定义代码生成器
在创建 .targets 文件之后,您可能会考虑直接从 MSBuild 调用您的工具,而不是使用单独的预构建事件或 Visual Studio 有缺陷的“自定义工具”机制。
为此:
-
创建一个引用 Microsoft.Build.Framework 的类库项目
添加代码以实现您的自定义代码生成器
添加一个实现 ITask 的类,并在 Execute 方法中调用您的自定义代码生成器
将
UsingTask
元素添加到您的 .targets 文件中,并在您的目标中添加对新任务的调用
这就是实现 ITask 所需的全部内容:
public class GenerateCodeFromXyzFiles : ITask
public IBuildEngine BuildEngine get; set;
public ITaskHost HostObject get; set;
public ITaskItem[] InputFiles get; set;
public ITaskItem[] OutputFiles get; set;
public bool Execute()
for(int i=0; i<InputFiles.Length; i++)
File.WriteAllText(OutputFiles[i].ItemSpec,
ProcessXyzFile(
File.ReadAllText(InputFiles[i].ItemSpec)));
private string ProcessXyzFile(string xyzFileContents)
// Process file and return generated code
这里是 UsingTask 元素和调用它的 Target:
<UsingTask TaskName="MyNamespace.GenerateCodeFromXyzFiles" AssemblyFile="MyTaskProject.dll" />
<Target Name="GenerateToolOutput">
<GenerateCodeFromXyzFiles
InputFiles="@(Xyz)"
OutputFiles="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">
<Output TaskParameter="OutputFiles" ItemGroup="Compile" />
</GenerateCodeFromXyzFiles>
</Target>
请注意,此目标的 Output 元素将输出文件列表直接放入 Compile,因此无需使用单独的 ItemGroup 来执行此操作。
旧的“自定义工具”机制存在缺陷以及为什么不使用它
关于 Visual Studio 的“自定义工具”机制的说明:在 NET Framework 1.x 中,我们没有 MSBuild,因此我们不得不依赖 Visual Studio 来构建我们的项目。为了在生成的代码上获得 Intellisense,Visual Studio 有一种称为“自定义工具”的机制,可以在文件的“属性”窗口中进行设置。该机制在几个方面存在根本缺陷,这就是它被 MSBuild 目标取代的原因。 “自定义工具”功能的一些问题是:
-
每当编辑和保存文件时,“自定义工具”都会构建生成的文件,而不是在编译项目时。这意味着从外部修改文件的任何内容(例如修订控制系统)都不会更新生成的文件,并且您经常会在可执行文件中获得过时的代码。
“自定义工具”的输出必须与您的源代码树一起提供,除非您的收件人同时拥有 Visual Studio 和您的“自定义工具”。
“自定义工具”必须安装在注册表中,不能简单地从项目文件中引用。
“自定义工具”的输出未存储在“obj”目录中。
如果您使用的是旧的“自定义工具”功能,我强烈建议您切换到使用 MSBuild 任务。它与 Intellisense 配合得很好,甚至可以让您在不安装 Visual Studio 的情况下构建您的项目(您只需要 NET Framework)。
您的自定义构建任务何时运行?
通常您的自定义构建任务将运行:
Visual Studio 在后台打开解决方案时,如果生成的文件不是最新的 在您在 Visual Studio 中保存输入文件之一时在后台进行 任何时候构建,如果生成的文件不是最新的 任何时候重建更准确地说:
-
在 Visual Studio 启动时以及每次在 Visual Studio 中保存任何文件时都会运行 IntelliSense 增量构建。如果输出文件丢失任何输入文件比生成器输出新,这将运行您的生成器。
每当您在 Visual Studio 中使用任何“构建”或“运行”命令(包括菜单选项并按 F5)或从命令行运行“MSBuild”时,都会运行常规增量构建。与 IntelliSense 增量构建一样,它也只会在生成的文件不是最新的情况下运行您的生成器
每当您在 Visual Studio 中使用任何“重建”命令或从命令行运行“MSBuild /t:Rebuild”时,都会运行常规完整构建。如果有任何输入或输出,它将始终运行您的生成器。
您可能希望强制生成器在其他时间运行,例如当某些环境变量发生变化时,或者强制它同步运行而不是在后台运行。
要让生成器在没有更改输入文件的情况下重新运行,最好的方法通常是向目标添加一个额外的输入,这是一个存储在“obj”目录中的虚拟输入文件。然后,每当环境变量或某些外部设置发生更改,会迫使您的生成器工具重新运行时,只需触摸此文件(即创建它或更新其修改日期)。
要强制生成器同步运行,而不是等待 IntelliSense 在后台运行它,只需使用 MSBuild 构建您的特定目标。这可以像执行“MSBuild /t:GenerateToolOutput”一样简单,或者 VSIP 可以提供一种内置方式来调用自定义构建目标。或者,您可以简单地调用 Build 命令并等待它完成。
请注意,本节中的“输入文件”指的是目标元素的“输入”属性中列出的任何内容。
结语
您可能会收到来自 Visual Studio 的警告,它不知道是否信任您的自定义工具 .targets 文件。要解决此问题,请将其添加到 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\MSBuild\SafeImports 注册表项。
下面是一个实际的 .targets 文件在所有部分都到位后的样子的摘要:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CoreCompileDependsOn>$(CoreCompileDependsOn);GenerateToolOutput</CoreCompileDependsOn>
</PropertyGroup>
<UsingTask TaskName="MyNamespace.GenerateCodeFromXyzFiles" AssemblyFile="MyTaskProject.dll" />
<Target Name="GenerateToolOutput" Inputs="@(Xyz)" Outputs="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">
<GenerateCodeFromXyzFiles
InputFiles="@(Xyz)"
OutputFiles="@(Xyz->'$(IntermediateOutputPath)%(FileName)%(Extension).g.cs')">
<Output TaskParameter="OutputFiles" ItemGroup="Compile" />
</GenerateCodeFromXyzFiles>
</Target>
</Project>
如果您有任何问题或这里有任何您不明白的地方,请告诉我。
【讨论】:
感谢您的详细回复。只有一个问题:自定义工具方法允许我从我的 VSIP 中以编程方式触发生成文件的构建。我能用你的方法做类似的事情吗? 是的,有几种方法可以控制自定义构建任务何时运行。我添加了一个新的“您的自定义构建任务何时运行?”我的答案部分解释了它是如何工作的并提供了一些选项。 顺便说一句,对于 VS2010,请改用HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\10.0\MSBuild\SafeImports
。
Ray,Visual Studio 抱怨它无法识别最终 .targets 文件中的“ItemGroup”属性。示例正确吗?
非常好的答案。虽然有一些更正...在 【参考方案2】:
要在 Visual Studio 中隐藏项目,请将 Visible
元数据属性添加到项目。 InProject
元数据显然也是这样做的。
可见:http://msdn.microsoft.com/en-us/library/ms171468(VS.90).aspx
项目内:http://blogs.msdn.com/b/jomo_fisher/archive/2005/01/25/360302.aspx
<ItemGroup>
<Compile Include="$(AssemblyInfoPath)">
<!-- either: -->
<InProject>false</InProject>
<!-- or: -->
<Visible>false</Visible>
</Compile>
</ItemGroup>
【讨论】:
这比接受的答案要容易得多,因为它有更多的选票并且效果很好。【参考方案3】:我知道的唯一方法是添加生成的文件以依赖于您希望它隐藏在后面的文件 - 在 proj 文件中。
例如:
<ItemGroup>
<Compile Include="test.cs" />
<Compile Include="test.g.i.cs">
<DependentUpon>test.cs</DependentUpon>
</Compile>
</ItemGroup>
如果您删除了 DependentUpon 元素,则该文件将显示在另一个文件旁边而不是在其后面...您的生成器如何添加文件?你能告诉我们用例以及你希望它如何工作吗?
【讨论】:
我试图完全隐藏生成的文件。根据定义,自定义工具生成的文件依赖于自定义工具,Visual Studio 管理它们的生成。【参考方案4】:我想你想看看这里:http://msdn.microsoft.com/en-us/library/ms171453.aspx。
特别是“在执行期间创建项目”部分。
【讨论】:
这个文件需要在设计时存在。我需要它包含的类通过智能感知可供用户使用,就像背后的 WPF 代码一样。以上是关于如何在 Visual Studio 中隐藏自定义工具生成的文件的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Visual Studio Code 中隐藏缩进指南?
如何在 Visual Studio 中隐藏自定义工具生成的文件
Visual Studio 2017中应用程序的自定义安装程序
如何禁用自动隐藏解决方案资源管理器(Visual Studio 2017)