Add a link to an existing item during project creation - visual-studio

I've created a custom VS template which uses an IWizard class to do some automatic actions when a user creates a project to set project properties and paths, I've managed to set some project properties like build path by saving the .csproj file with parameters inside $ signs and setting those parameters in the replacementDictionary, during the RunStarted method.
Unfortunately I'm having trouble adding items as links to the .csproj using the same method. I have a .cs file I need to add as an existing and as a link item to each project created, it's path would be determined by where the user chooses to save the project. I've got to the part where I know the path of the .cs file, (absolute and relative to the project's path).
Here's what I've tried so far:
Save the .csproj file with a section for the item, with placeholders for the path:
<Compile Include="$path_to_cs_file\cs_file_name.cs$">
<Link>$cs_file_name.cs$</Link>
</Compile>
I've tried doing this with both absolute and relative paths, but this for some reason makes VS replace the path with a completely different relative path under Documents and Settings\user\Local Settings.
In RunStarted, cast the automationObject as DTE and call it's ItemOperations.AddExistingItem method. Using either path results in errors (The parameter is incorrect).
In ProjectFinishedGenerating, save the project's path, then at RunFinished, create a Microsoft.Build.BuildEngine.Project object with that path, call DTE commands to save all files and unload the project, then call the project object's AddNewItem Method and SetMetaData on the resulting ProjectItem, afterwards I save the project and reload it with the DTE object, this, again results the same errors as before
I'd appreciate any help with the subject, I'm pretty much stumped. Thank you in advance.

I managed to "solve" this issue, what I did is the following:
Kept the placeholder in the csproj, but never added the related parameters to the replacement dictionary:
<Compile Include="$path_to_cs_file$\$cs_file_name.cs$">
<Link>$cs_file_name.cs$</Link>
</Compile>
At the ProjectFinishedGenerated method, unloaded the project, edited the csproj file to replace the paths, and reloaded the project:
projectFileName = project.FullName
// Unload file and manually add the linked item
dte.ExecuteCommand("File.SaveAll");
dte.ExecuteCommand("Project.UnloadProject"); // See Note Below
StreamReader reader = new StreamReader(projectFileName);
string content = reader.ReadToEnd();
reader.Close();
content = Regex.Replace(content, #"\$path_to_cs_file\$", ...);
content = Regex.Replace(content, #"\$cs_file_name\$", ...);
StreamWriter writer = new StreamWriter(projectFileName);
writer.Write(content);
writer.Close();
dte.ExecuteCommand("Project.ReloadProject");
Note: The above code assumes the project needed modifying is currently selected project, usually when ProjectFinishedGenerating runs this is the case, however in a multi-project template or if you've added a project manually to the solution this might not be the case, you'll have to call dte methods to choose your "main" project in the project explorer, then go on with unloading, editing, and reloading. The code to do so would look something like this:
UIHierarchy UIH = dte2.ToolWindows.SolutionExplorer;
UIHierarchyItem UIHItem = UIH.UIHierarchyItems.Item(1);
UIHItem.UIHierarchyItems.Item(testProjectName).Select(vsUISelectionType.vsUISelectionTypeSelect);

An alternative solution if you don't want to mess around with IWizard is to set CreateInPlace to true in your vstemplate in TemplateData.
<CreateInPlace>true</CreateInPlace>

I'm having exactly the same problem and it's driving me mad.
I have found one really dirty workaround however:
In my situation I am using the following in the RunStarted method:
EnvDTE.DTE dte = automationObject as EnvDTE.DTE;
string solutionPath = System.IO.Path.GetDirectory(dte.DTE.Solution.FullName);
This returns a path which includes the folder "documents". Calling System.IO.Directory.Exists() confirms that this is a valid directory, however on checking my file system, it seems that this does not exist. If you replace "documents" with "my documents", and then continue to use that path for the linked item, all works perfectly.
So it seems that VS is getting confused with the "documents" directory alias and therefore defaulting to some crazy "AppData" directory instead.
I hope this helps, but if you find a better way to do this, please let me know!

Related

Visual Studio Build Event Macros - Solution Configuration Name

In my Post-build event I call a batch file and pass it the current build config.
C:\Dev\Project\Build\build.bat /$(Configuration)
This passes the Project configuration name to the build script.
Is there anyway to pass the current Solution configuration name?
I created a VS2010 extension for this, it lets you use $(SolutionConfiguration) and $(SolutionPlaform). $(BuildMacro) build macros. You can download the source and build it yourself from here.
Showing some code, it's just about registering to UpdateSolution_Begin method
of IVsUpdateSolutionEvents VS and setting some Environment.SetEnvironmentVariable() there.
_IVsSolutionBuildManager = (IVsSolutionBuildManager)GetService<SVsSolutionBuildManager>();
_UpdateSolutionEvents = new UpdateSolutionEvents(); // IVsUpdateSolutionEvents
int hr;
uint pdwCookie;
hr = _IVsSolutionBuildManager.AdviseUpdateSolutionEvents(_UpdateSolutionEvents, out pdwCookie);
Not directly. When you use Edit/Macros on a property field, the only configuration name listed is the one for the project that you already have.
You can, however, define your own macro. For each solution configuration, create a new property sheet, use the "User Macros" tab to define a macro whose name is "SolutionConfiguration" and whose value is the name of the configuration, then add this property sheet to the appropriate project configuration of every project in the solution.
If there's a better way I'd love to learn of it.
There is property SolutionConfigurationContents witch is created by Msbulid during soluton file processing it contains solution configuration in it. When building from VS it will contains project (not solution) configuration.

Embedded Resource missing in Visual Studio 2010 when name ends with "No.xxx"

I've come across a strange behaviour in Visual Studio 2010.
When using embedded resources (files which are added to my C# project and for which the Build Action property is set to Embedded Resource), the files are included in the output assembly as binary data. Listing the resources is straightforward:
class Program
{
static void Main(string[] args)
{
string[] names = typeof (Program).Assembly.GetManifestResourceNames ();
foreach (var name in names)
{
System.Console.Out.WriteLine (name);
}
}
}
However, if the embedded resource file name ends with No.xxx (the extension is irrelevant), the file does not show up in the list. I cannot figure out any reason why Visual Studio 2010 would not include such a file. What did I miss?
Note: if I rename the embedded resource file in the solution explorer to something else, then everything works as expected.
Dan from the Microsoft Connect team has finally provided a valid explanation for this behaviour:
Hello, thanks for the report, this is actually working normally. The reason is that any resx files whose names match the pattern .VALIDCULTURE.resx are assumed to be specific to that culture. (This is how it has worked since VS2002, for better or worse)
In your case "no" is a valid culture (Norwegian, I guess) so the build process builds it into a satellite assembly. Here's what I got when I tried this. Note the "no" subfolder. If in your app you change your current culture to Norwegian, the resource load will load this set of resources.
So the problem has nothing to do with the word No itself, but rather with the fact that it is a valid, two-letter, culture name (in my case Norwegian). I checked, and indeed, there was a sub-folder in bin\Debug named No, containing a satellite assembly named Project.resources.dll in it.
Renaming the resource to end with .EN.xxx or .FR.xxx does, of course, exhibit the same behaviour.
As of MSBuild 16.9 you can include files like these by setting the WithCulture property to "false", as pointed out by #reduckted :-)
<EmbeddedResource Include="Resources.en.xml" WithCulture="false" />

"Run Custom Tool" for resx files in MVC2 project (from an external application/script)

I am trying to get the localization for my MVC project working with our existing infrastructure for editing string resources. We store all our resource string in database tables and have a front end web UI to edit them with, and an export application which generated the .resx files. This all works great, but I am having a little difficulty with a new project using MVC2 and VS2010.
I have asked another question on this, the answer to which almost got me there, but not quite.
I have now changed the resources to be in a Resources folder (instead of App_GlobalResources), as recommended by a number of people. And have the following settings against my .resx files ...
Build Action = Embedded Resource
Copy to Output Directory = Do not copy
Custom Tool = PublicResXFileCodeGenerator
Custom Tool Namespace = Resources
File Name = MyApp.resx
I have changed my export application to run the resgen.exe tool with the following parameters ...
string args = string.Format("/publicClass \"{0}\" /str:cs,Resources,{1},\"{2}\"", resourceFile, Path.GetFileNameWithoutExtension(resourceFile), csFilename);
... which generates an almost identical .designer.cs file as I get when I add the .resx file to my project initially. The only difference is the
The generated .designer.cs file differs slightly from the file I get when I run the resgen.exe tool from within my export application.
This is the code generated by VS2010 when I first add the .resx file to my Resources folder ...
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Resources.MyApp", typeof(MyApp).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
... the difference when I run the resgen.exe tool is that is prefixs MyCompany.MyApp to the namespace in the constructor to ResourceManager
new global::System.Resources.ResourceManager("MyCompany.MyApp.Resources.MyApp", typeof(MyApp).Assembly);
Now, this to me seems to be a bug in the resgen.exe tool, because I've told it that the Namespace for my resources is Resources, not MyCompany.MyApp.Resources.
So, is there a fix/work-around for this problem?
The only thing I can think to do at the moment is to post-process the generated .designer.cs file with powershell and fix it!
Finally, I have solved the problem.
I decided to simplify things a bit by breaking my resources out in to a new assembly called Resources. I then added my resx files and set the properties for them as below ...
Build Action = Embedded Resource
Copy to Output Directory = Do not copy
Custom Tool = PublicResXFileCodeGenerator
Custom Tool Namespace = Resources
File Name = MyApp.resx
I then changed my export application to run ...
resgen MyApp.resx /str:c#,Resources,MyApp,MyApp.designer.cs /publicClass
... and to delete *.resources from the folder (created by the resgen.exe utility, but not needed)
This got rid of the prefix on the constructor to ResourceManager, and then i just added a reference to my new Resources assembly to my web application.
I've run a few tests to make sure all is good, including going in to the .designer.cs file and deleting one of the properties to cause a compiler error. Then re-ran my export app, and everything worked again.
So, I am a happy bunny!

XNA Framework Importers

I am working on a game using the XNA framework. My game has several levels which I am storing the data in a plain old text file. In VS 2008 when I add the level file to the project and compile, I receive the following error message.
Error 1 Cannot autodetect which importer to use for "Levels\0.txt". There are no importers which handle this file type. Specify the importer that handles this file type in your project. F:\Projects\BrickBreaker\BrickBreaker\Content\Levels\0.txt BrickBreaker
The reason I am bringing this out is because if I change on of my levels and run the game, the level is not updated. I have found that the level is not updated because VS runs the game from the bin\debug folder and since the level files are not included in the project, they are not copied when they change. I also found that the platform sample that comes with the framework includes the level data in the project, that's where I got the technique from.
So, should I use a different file format or just deal with having to manually copy the new level files over?
Resolution - After digging into the answers on this post I found a solution. I added the text files to the project and set the build property to none. The error no longer occurs when compiling and the file is included in the project.
You can have Visual Studio just copy over the files if you want to the output directory. Under the properties of the text file in the project, choose Build Action: None and change the copy to output directory to as needed.
You can also check out the platformer sample. They use text files as their level format.
There is no content importer for text files. Ignore the content pipeline and just read the file in just as you would any other normal text file.
string line = string.empty;
using(StreamReader sr = new StreamReader("filename")){
while((line = sr.ReadLine()) != null){
//reads line by line until eof
//do whatever you want with the text
}
}
I had similar problem, and found it easier to have the files added to content project, and build action set to none, than to skip content pipeline altogether.

Visual Studio Extensibility: Adding existing folders to a project

I'm trying to use Visual Studio 2008's extensibility to write an addin that will create a project folder with various messages in it after parsing an interface. I'm having trouble at the step of creating/adding the folder, however. I've tried using
ProjectItem folder =
item.ProjectItem.Collection.AddFolder(newDirectoryName, string.Empty);
(item is my target file next to which I'm creating a folder with the same name but "Messages" appended to it) but it chokes when a folder already exists (no big surprise).
I tried deleting it if it already exists, such as:
DirectoryInfo dirInfo = new DirectoryInfo(newDirectoryParent +
newDirectoryName);
if (dirInfo.Exists)
{
dirInfo.Delete(true);
}
ProjectItem folder =
item.ProjectItem.Collection.AddFolder(newDirectoryName, string.Empty);
I can SEE that the folder gets deleted when in debug, but it still
seems to think the folder is still there and dies on a folder already
exists exception.
Any ideas???
Thanks.
AK
.... Perhaps the answer would lie in programmatically refreshing the project after the delete? How might this be done?
ProjectItem pi = null;
var dir = Path.Combine(
project.Properties.Item("LocalPath").Value.ToString(), SubdirectoryName);
if (Directory.Exists(dir))
pi = target.ProjectItems.AddFromDirectory(dir);
else
pi = target.ProjectItems.AddFolder(dir);
ProjectItems.AddFromDirectory will add the directory and everything underneath the directory to the project.
Yup, that was it...
DirectoryInfo dirInfo = new DirectoryInfo(newDirectoryParent + newDirectoryName);
if (dirInfo.Exists)
{
dirInfo.Delete(true);
item.DTE.ExecuteCommand("View.Refresh", string.Empty);
}
ProjectItem folder = item.ProjectItem.Collection.AddFolder(newDirectoryName, string.Empty);
If there's a more elegant way of doing this, it would be much appreciated...
Thanks.
This is my approach:
//Getting the current project
private DTE2 _applicationObject;
System.Array projs = (System.Array)_applicationObject.ActiveSolutionProjects;
Project proy=(Project)projs.GetValue(0);
//Getting the path
string path=proy.FullName.Substring(0,proy.FullName.LastIndexOf('\\'));
//Valitating if the path exists
bool existsDirectory= Directory.Exists(path + "\\Directory");
//Deleting and creating the Directory
if (existeClasses)
Directory.Delete(path + "\\Directory", true);
Directory.CreateDirectory(path + "\\Directory");
//Including in the project
proy.ProjectItems.AddFromDirectory(path + "\\Directory");
I am developing an extension for Visual Studio 2019 and had a similar issue. The question asked in the following page helped me out:
https://social.msdn.microsoft.com/Forums/en-US/f4a4f73b-3e13-40bf-99df-9c1bba8fe44e/include-existing-folder-path-as-project-item?forum=vsx
If the folder does not physically exist, you can use AddFolder(folderName). But if the folder is not included in the project while existing physically, you need to provide the full system path to the folder. (AddFolder(fullPath))
here's an idea i thought of because i've been using NAnt for so long and thought it might work.
Open the .csproj file in a text editor and add the directory as such:
<ItemGroup>
<compile include="\path\rootFolderToInclude\**\*.cs" />
</ItemGroup>
if an "ItemGroup" already esists, that's fine. Just add it into an existing one. Visual studio won't really know how to edit this entry, but it will scan the whole directory.
edit to whatever you'd like.

Resources