Cosmos/source/MPF/12.0/ProjectContainerNode.cs
2017-04-03 23:55:10 -05:00

842 lines
38 KiB
C#

/********************************************************************************************
Copyright (c) Microsoft Corporation
All rights reserved.
Microsoft Public License:
This license governs use of the accompanying software. If you use the software, you
accept this license. If you do not accept the license, do not use the software.
1. Definitions
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
same meaning here as under U.S. copyright law.
A "contribution" is the original software, or any additions or changes to the software.
A "contributor" is any person that distributes its contribution under this license.
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
2. Grant of Rights
(A) Copyright Grant- Subject to the terms of this license, including the license conditions
and limitations in section 3, each contributor grants you a non-exclusive, worldwide,
royalty-free copyright license to reproduce its contribution, prepare derivative works of
its contribution, and distribute its contribution or any derivative works that you create.
(B) Patent Grant- Subject to the terms of this license, including the license conditions
and limitations in section 3, each contributor grants you a non-exclusive, worldwide,
royalty-free license under its licensed patents to make, have made, use, sell, offer for
sale, import, and/or otherwise dispose of its contribution in the software or derivative
works of the contribution in the software.
3. Conditions and Limitations
(A) No Trademark License- This license does not grant you rights to use any contributors'
name, logo, or trademarks.
(B) If you bring a patent claim against any contributor over patents that you claim are
infringed by the software, your patent license from such contributor to the software ends
automatically.
(C) If you distribute any portion of the software, you must retain all copyright, patent,
trademark, and attribution notices that are present in the software.
(D) If you distribute any portion of the software in source code form, you may do so only
under this license by including a complete copy of this license with your distribution.
If you distribute any portion of the software in compiled or object code form, you may only
do so under a license that complies with this license.
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give
no express warranties, guarantees or conditions. You may have additional consumer rights
under your local laws which this license cannot change. To the extent permitted under your
local laws, the contributors exclude the implied warranties of merchantability, fitness for
a particular purpose and non-infringement.
********************************************************************************************/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Project.Automation;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using MSBuild = Microsoft.Build.Evaluation;
namespace Microsoft.VisualStudio.Project
{
public abstract class ProjectContainerNode : ProjectNode,
IVsParentProject,
IBuildDependencyOnProjectContainer
{
#region fields
/// <summary>
/// Setting this flag to true will build all nested project when building this project
/// </summary>
private bool buildNestedProjectsOnBuild = true;
private ProjectElement nestedProjectElement;
/// <summary>
/// Defines the listener that would listen on file changes on the nested project node.
/// </summary>
///<devremark>
///This might need a refactoring when nested projects can be added and removed by demand.
/// </devremark>
private FileChangeManager nestedProjectNodeReloader;
#endregion
#region ctors
protected ProjectContainerNode()
{
}
#endregion
#region properties
/// <summary>
/// Returns teh object that handles listening to file changes on the nested project files.
/// </summary>
internal FileChangeManager NestedProjectNodeReloader
{
get
{
if(this.nestedProjectNodeReloader == null)
{
this.nestedProjectNodeReloader = new FileChangeManager(this.Site);
this.nestedProjectNodeReloader.FileChangedOnDisk += this.OnNestedProjectFileChangedOnDisk;
}
return this.nestedProjectNodeReloader;
}
}
#endregion
#region overridden properties
/// <summary>
/// This is the object that will be returned by EnvDTE.Project.Object for this project
/// </summary>
internal override object Object
{
get { return new OASolutionFolder<ProjectContainerNode>(this); }
}
#endregion
#region public overridden methods
/// <summary>
/// Gets the nested hierarchy.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="iidHierarchyNested">Identifier of the interface to be returned in ppHierarchyNested.</param>
/// <param name="ppHierarchyNested">Pointer to the interface whose identifier was passed in iidHierarchyNested.</param>
/// <param name="pItemId">Pointer to an item identifier of the root node of the nested hierarchy.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. If ITEMID is not a nested hierarchy, this method returns E_FAIL.</returns>
public override int GetNestedHierarchy(UInt32 itemId, ref Guid iidHierarchyNested, out IntPtr ppHierarchyNested, out uint pItemId)
{
pItemId = VSConstants.VSITEMID_ROOT;
ppHierarchyNested = IntPtr.Zero;
if(this.FirstChild != null)
{
for(HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling)
{
NestedProjectNode p = n as NestedProjectNode;
if(p != null && p.ID == itemId)
{
if(p.NestedHierarchy != null)
{
IntPtr iunknownPtr = IntPtr.Zero;
int returnValue = VSConstants.S_OK;
try
{
iunknownPtr = Marshal.GetIUnknownForObject(p.NestedHierarchy);
Marshal.QueryInterface(iunknownPtr, ref iidHierarchyNested, out ppHierarchyNested);
}
catch(COMException e)
{
returnValue = e.ErrorCode;
}
finally
{
if(iunknownPtr != IntPtr.Zero)
{
Marshal.Release(iunknownPtr);
}
}
return returnValue;
}
break;
}
}
}
return VSConstants.E_FAIL;
}
public override int IsItemDirty(uint itemId, IntPtr punkDocData, out int pfDirty)
{
HierarchyNode hierNode = this.NodeFromItemId(itemId);
Debug.Assert(hierNode != null, "Hierarchy node not found");
if(hierNode != this)
{
return ErrorHandler.ThrowOnFailure(hierNode.IsItemDirty(itemId, punkDocData, out pfDirty));
}
else
{
return ErrorHandler.ThrowOnFailure(base.IsItemDirty(itemId, punkDocData, out pfDirty));
}
}
public override int SaveItem(VSSAVEFLAGS dwSave, string silentSaveAsName, uint itemid, IntPtr punkDocData, out int pfCancelled)
{
HierarchyNode hierNode = this.NodeFromItemId(itemid);
Debug.Assert(hierNode != null, "Hierarchy node not found");
if(hierNode != this)
{
return ErrorHandler.ThrowOnFailure(hierNode.SaveItem(dwSave, silentSaveAsName, itemid, punkDocData, out pfCancelled));
}
else
{
return ErrorHandler.ThrowOnFailure(base.SaveItem(dwSave, silentSaveAsName, itemid, punkDocData, out pfCancelled));
}
}
protected override bool FilterItemTypeToBeAddedToHierarchy(string itemType)
{
if(String.Compare(itemType, ProjectFileConstants.SubProject, StringComparison.OrdinalIgnoreCase) == 0)
{
return true;
}
return base.FilterItemTypeToBeAddedToHierarchy(itemType);
}
/// <summary>
/// Called to reload a project item.
/// Reloads a project and its nested project nodes.
/// </summary>
/// <param name="itemId">Specifies itemid from VSITEMID.</param>
/// <param name="reserved">Reserved.</param>
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
public override int ReloadItem(uint itemId, uint reserved)
{
#region precondition
if(this.IsClosed)
{
return VSConstants.E_FAIL;
}
#endregion
NestedProjectNode node = this.NodeFromItemId(itemId) as NestedProjectNode;
if(node != null)
{
object propertyAsObject = node.GetProperty((int)__VSHPROPID.VSHPROPID_HandlesOwnReload);
if(propertyAsObject != null && (bool)propertyAsObject)
{
node.ReloadItem(reserved);
}
else
{
this.ReloadNestedProjectNode(node);
}
return VSConstants.S_OK;
}
return base.ReloadItem(itemId, reserved);
}
/// <summary>
/// Reloads a project and its nested project nodes.
/// </summary>
protected override void Reload()
{
base.Reload();
this.CreateNestedProjectNodes();
}
#endregion
#region IVsParentProject
public virtual int OpenChildren()
{
IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution;
Debug.Assert(solution != null, "Could not retrieve the solution from the services provided by this project");
if(solution == null)
{
return VSConstants.E_FAIL;
}
IntPtr iUnKnownForSolution = IntPtr.Zero;
int returnValue = VSConstants.S_OK; // be optimistic.
try
{
this.DisableQueryEdit = true;
this.EventTriggeringFlag = ProjectNode.EventTriggering.DoNotTriggerHierarchyEvents | ProjectNode.EventTriggering.DoNotTriggerTrackerEvents;
iUnKnownForSolution = Marshal.GetIUnknownForObject(solution);
// notify SolutionEvents listeners that we are about to add children
IVsFireSolutionEvents fireSolutionEvents = Marshal.GetTypedObjectForIUnknown(iUnKnownForSolution, typeof(IVsFireSolutionEvents)) as IVsFireSolutionEvents;
ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnBeforeOpeningChildren(this));
this.AddVirtualProjects();
ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnAfterOpeningChildren(this));
}
catch(Exception e)
{
// Exceptions are digested by the caller but we want then to be shown if not a ComException and if not in automation.
if(!(e is COMException) && !Utilities.IsInAutomationFunction(this.Site))
{
string title = null;
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
VsShellUtilities.ShowMessageBox(this.Site, title, e.Message, icon, buttons, defaultButton);
}
Trace.WriteLine("Exception : " + e.Message);
throw;
}
finally
{
this.DisableQueryEdit = false;
if(iUnKnownForSolution != IntPtr.Zero)
{
Marshal.Release(iUnKnownForSolution);
}
this.EventTriggeringFlag = ProjectNode.EventTriggering.TriggerAll;
}
return returnValue;
}
public virtual int CloseChildren()
{
int returnValue = VSConstants.S_OK; // be optimistic.
IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution;
Debug.Assert(solution != null, "Could not retrieve the solution from the services provided by this project");
if(solution == null)
{
return VSConstants.E_FAIL;
}
IntPtr iUnKnownForSolution = IntPtr.Zero;
try
{
iUnKnownForSolution = Marshal.GetIUnknownForObject(solution);
// notify SolutionEvents listeners that we are about to close children
IVsFireSolutionEvents fireSolutionEvents = Marshal.GetTypedObjectForIUnknown(iUnKnownForSolution, typeof(IVsFireSolutionEvents)) as IVsFireSolutionEvents;
ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnBeforeClosingChildren(this));
// If the removal crashes we never fire the close children event. IS that a problem?
this.RemoveNestedProjectNodes();
ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnAfterClosingChildren(this));
}
finally
{
if(iUnKnownForSolution != IntPtr.Zero)
{
Marshal.Release(iUnKnownForSolution);
}
}
return returnValue;
}
#endregion
#region IBuildDependencyOnProjectContainerNode
/// <summary>
/// Defines whether nested projects should be build with the parent project
/// </summary>
public virtual bool BuildNestedProjectsOnBuild
{
get { return this.buildNestedProjectsOnBuild; }
set { this.buildNestedProjectsOnBuild = value; }
}
/// <summary>
/// Enumerates the nested hierachies that should be added to the build dependency list.
/// </summary>
/// <returns></returns>
public virtual IVsHierarchy[] EnumNestedHierachiesForBuildDependency()
{
List<IVsHierarchy> nestedProjectList = new List<IVsHierarchy>();
// Add all nested project among projectContainer child nodes
for(HierarchyNode child = this.FirstChild; child != null; child = child.NextSibling)
{
NestedProjectNode nestedProjectNode = child as NestedProjectNode;
if(nestedProjectNode != null)
{
nestedProjectList.Add(nestedProjectNode.NestedHierarchy);
}
}
return nestedProjectList.ToArray();
}
#endregion
#region helper methods
internal protected void RemoveNestedProjectNodes()
{
for(HierarchyNode n = this.FirstChild; n != null; n = n.NextSibling)
{
NestedProjectNode p = n as NestedProjectNode;
if(p != null)
{
p.CloseNestedProjectNode();
}
}
// We do not care of file changes after this.
this.NestedProjectNodeReloader.FileChangedOnDisk -= this.OnNestedProjectFileChangedOnDisk;
this.NestedProjectNodeReloader.Dispose();
}
/// <summary>
/// This is used when loading the project to loop through all the items
/// and for each SubProject it finds, it create the project and a node
/// in our Hierarchy to hold the project.
/// </summary>
internal protected void CreateNestedProjectNodes()
{
// 1. Create a ProjectElement with the found item and then Instantiate a new Nested project with this ProjectElement.
// 2. Link into the hierarchy.
// Read subprojects from from msbuildmodel
__VSCREATEPROJFLAGS creationFlags = __VSCREATEPROJFLAGS.CPF_NOTINSLNEXPLR | __VSCREATEPROJFLAGS.CPF_SILENT;
if(this.IsNewProject)
{
creationFlags |= __VSCREATEPROJFLAGS.CPF_CLONEFILE;
}
else
{
creationFlags |= __VSCREATEPROJFLAGS.CPF_OPENFILE;
}
foreach (MSBuild.ProjectItem item in this.BuildProject.Items)
{
if(String.Compare(item.ItemType, ProjectFileConstants.SubProject, StringComparison.OrdinalIgnoreCase) == 0)
{
this.nestedProjectElement = new ProjectElement(this, item, false);
if(!this.IsNewProject)
{
AddExistingNestedProject(null, creationFlags);
}
else
{
// If we are creating the subproject from a vstemplate/vsz file
bool isVsTemplate = Utilities.IsTemplateFile(GetProjectTemplatePath(null));
if(isVsTemplate)
{
RunVsTemplateWizard(null, true);
}
else
{
// We are cloning the specified project file
AddNestedProjectFromTemplate(null, creationFlags);
}
}
}
}
this.nestedProjectElement = null;
}
/// <summary>
/// Add an existing project as a nested node of our hierarchy.
/// This is used while loading the project and can also be used
/// to add an existing project to our hierarchy.
/// </summary>
protected internal virtual NestedProjectNode AddExistingNestedProject(ProjectElement element, __VSCREATEPROJFLAGS creationFlags)
{
ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;
if(elementToUse == null)
{
throw new ArgumentNullException("element");
}
string filename = elementToUse.GetFullPathForElement();
// Delegate to AddNestedProjectFromTemplate. Because we pass flags that specify open project rather then clone, this will works.
Debug.Assert((creationFlags & __VSCREATEPROJFLAGS.CPF_OPENFILE) == __VSCREATEPROJFLAGS.CPF_OPENFILE, "__VSCREATEPROJFLAGS.CPF_OPENFILE should have been specified, did you mean to call AddNestedProjectFromTemplate?");
return AddNestedProjectFromTemplate(filename, Path.GetDirectoryName(filename), Path.GetFileName(filename), elementToUse, creationFlags);
}
/// <summary>
/// Let the wizard code execute and provide us the information we need.
/// Our SolutionFolder automation object should be called back with the
/// details at which point it will call back one of our method to add
/// a nested project.
/// If you are trying to add a new subproject this is probably the
/// method you want to call. If you are just trying to clone a template
/// project file, then AddNestedProjectFromTemplate is what you want.
/// </summary>
/// <param name="element">The project item to use as the base of the nested project.</param>
/// <param name="silent">true if the wizard should run silently, otherwise false.</param>
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Vs")]
protected internal void RunVsTemplateWizard(ProjectElement element, bool silent)
{
ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;
if(elementToUse == null)
{
throw new ArgumentNullException("element");
}
this.nestedProjectElement = elementToUse;
Automation.OAProject oaProject = GetAutomationObject() as Automation.OAProject;
if(oaProject == null || oaProject.ProjectItems == null)
throw new System.InvalidOperationException(SR.GetString(SR.InvalidAutomationObject, CultureInfo.CurrentUICulture));
Debug.Assert(oaProject.Object != null, "The project automation object should have set the Object to the SolutionFolder");
Automation.OASolutionFolder<ProjectContainerNode> folder = oaProject.Object as Automation.OASolutionFolder<ProjectContainerNode>;
// Prepare the parameters to pass to RunWizardFile
string destination = elementToUse.GetFullPathForElement();
string template = this.GetProjectTemplatePath(elementToUse);
object[] wizParams = new object[7];
wizParams[0] = EnvDTE.Constants.vsWizardAddSubProject;
wizParams[1] = Path.GetFileNameWithoutExtension(destination);
wizParams[2] = oaProject.ProjectItems;
wizParams[3] = Path.GetDirectoryName(destination);
wizParams[4] = Path.GetFileNameWithoutExtension(destination);
wizParams[5] = Path.GetDirectoryName(folder.DTE.FullName); //VS install dir
wizParams[6] = silent;
IVsDetermineWizardTrust wizardTrust = this.GetService(typeof(SVsDetermineWizardTrust)) as IVsDetermineWizardTrust;
if(wizardTrust != null)
{
Guid guidProjectAdding = Guid.Empty;
// In case of a project template an empty guid should be added as the guid parameter. See env\msenv\core\newtree.h IsTrustedTemplate method definition.
ErrorHandler.ThrowOnFailure(wizardTrust.OnWizardInitiated(template, ref guidProjectAdding));
}
try
{
// Make the call to execute the wizard. This should cause AddNestedProjectFromTemplate to be
// called back with the correct set of parameters.
EnvDTE.IVsExtensibility extensibilityService = (EnvDTE.IVsExtensibility)GetService(typeof(EnvDTE.IVsExtensibility));
EnvDTE.wizardResult result = extensibilityService.RunWizardFile(template, 0, ref wizParams);
if(result == EnvDTE.wizardResult.wizardResultFailure)
throw new COMException();
}
finally
{
if(wizardTrust != null)
{
ErrorHandler.ThrowOnFailure(wizardTrust.OnWizardCompleted());
}
}
}
/// <summary>
/// This will clone a template project file and add it as a
/// subproject to our hierarchy.
/// If you want to create a project for which there exist a
/// vstemplate, consider using RunVsTemplateWizard instead.
/// </summary>
protected internal virtual NestedProjectNode AddNestedProjectFromTemplate(ProjectElement element, __VSCREATEPROJFLAGS creationFlags)
{
ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;
if(elementToUse == null)
{
throw new ArgumentNullException("element");
}
string destination = elementToUse.GetFullPathForElement();
string template = this.GetProjectTemplatePath(elementToUse);
return this.AddNestedProjectFromTemplate(template, Path.GetDirectoryName(destination), Path.GetFileName(destination), elementToUse, creationFlags);
}
/// <summary>
/// This can be called directly or through RunVsTemplateWizard.
/// This will clone a template project file and add it as a
/// subproject to our hierarchy.
/// If you want to create a project for which there exist a
/// vstemplate, consider using RunVsTemplateWizard instead.
/// </summary>
protected internal virtual NestedProjectNode AddNestedProjectFromTemplate(string fileName, string destination, string projectName, ProjectElement element, __VSCREATEPROJFLAGS creationFlags)
{
// If this is project creation and the template specified a subproject in its project file, this.nestedProjectElement will be used
ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;
if(elementToUse == null)
{
// If this is null, this means MSBuild does not know anything about our subproject so add an MSBuild item for it
elementToUse = new ProjectElement(this, fileName, ProjectFileConstants.SubProject);
}
NestedProjectNode node = CreateNestedProjectNode(elementToUse);
node.Init(fileName, destination, projectName, creationFlags);
// In case that with did not have an existing element, or the nestedProjectelement was null
// and since Init computes the final path, set the MSBuild item to that path
if(this.nestedProjectElement == null)
{
string relativePath = node.Url;
if(Path.IsPathRooted(relativePath))
{
relativePath = this.ProjectFolder;
if(!relativePath.EndsWith("/\\", StringComparison.Ordinal))
{
relativePath += Path.DirectorySeparatorChar;
}
relativePath = new Url(relativePath).MakeRelative(new Url(node.Url));
}
elementToUse.Rename(relativePath);
}
this.AddChild(node);
return node;
}
/// <summary>
/// Override this method if you want to provide your own type of nodes.
/// This would be the case if you derive a class from NestedProjectNode
/// </summary>
protected virtual NestedProjectNode CreateNestedProjectNode(ProjectElement element)
{
return new NestedProjectNode(this, element);
}
/// <summary>
/// Links the nested project nodes to the solution. The default implementation parses all nested project nodes and calles AddVirtualProjectEx on them.
/// </summary>
protected virtual void AddVirtualProjects()
{
for(HierarchyNode child = this.FirstChild; child != null; child = child.NextSibling)
{
NestedProjectNode nestedProjectNode = child as NestedProjectNode;
if(nestedProjectNode != null)
{
nestedProjectNode.AddVirtualProject();
}
}
}
/// <summary>
/// Based on the Template and TypeGuid properties of the
/// element, generate the full template path.
///
/// TypeGuid should be the Guid of a registered project factory.
/// Template can be a full path, a project template (for projects
/// that support VsTemplates) or a relative path (for other projects).
/// </summary>
protected virtual string GetProjectTemplatePath(ProjectElement element)
{
ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;
if(elementToUse == null)
{
throw new ArgumentNullException("element");
}
string templateFile = elementToUse.GetMetadata(ProjectFileConstants.Template);
Debug.Assert(!String.IsNullOrEmpty(templateFile), "No template file has been specified in the template attribute in the project file");
string fullPath = templateFile;
if(!Path.IsPathRooted(templateFile))
{
RegisteredProjectType registeredProjectType = this.GetRegisteredProject(elementToUse);
// This is not a full path
Debug.Assert(registeredProjectType != null && (!String.IsNullOrEmpty(registeredProjectType.DefaultProjectExtensionValue) || !String.IsNullOrEmpty(registeredProjectType.WizardTemplatesDirValue)), " Registered wizard directory value not set in the registry.");
// See if this specify a VsTemplate file
fullPath = registeredProjectType.GetVsTemplateFile(templateFile);
if(String.IsNullOrEmpty(fullPath))
{
// Default to using the WizardTemplateDir to calculate the absolute path
fullPath = Path.Combine(registeredProjectType.WizardTemplatesDirValue, templateFile);
}
}
return fullPath;
}
/// <summary>
/// Get information from the registry based for the project
/// factory corresponding to the TypeGuid of the element
/// </summary>
private RegisteredProjectType GetRegisteredProject(ProjectElement element)
{
ProjectElement elementToUse = (element == null) ? this.nestedProjectElement : element;
if(elementToUse == null)
{
throw new ArgumentNullException("element");
}
// Get the project type guid from project elementToUse
string typeGuidString = elementToUse.GetMetadataAndThrow(ProjectFileConstants.TypeGuid, new Exception());
Guid projectFactoryGuid = new Guid(typeGuidString);
EnvDTE.DTE dte = this.ProjectMgr.Site.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
Debug.Assert(dte != null, "Could not get the automation object from the services exposed by this project");
if(dte == null)
throw new InvalidOperationException();
RegisteredProjectType registeredProjectType = RegisteredProjectType.CreateRegisteredProjectType(projectFactoryGuid);
Debug.Assert(registeredProjectType != null, "Could not read the registry setting associated to this project.");
if(registeredProjectType == null)
{
throw new InvalidOperationException();
}
return registeredProjectType;
}
/// <summary>
/// Reloads a nested project node by deleting it and readding it.
/// </summary>
/// <param name="node">The node to reload.</param>
protected virtual void ReloadNestedProjectNode(NestedProjectNode node)
{
if(node == null)
{
throw new ArgumentNullException("node");
}
IVsSolution solution = this.GetService(typeof(IVsSolution)) as IVsSolution;
if(solution == null)
{
throw new InvalidOperationException();
}
NestedProjectNode newNode = null;
try
{
// (VS 2005 UPDATE) When deleting and re-adding the nested project,
// we do not want SCC to see this as a delete and add operation.
this.EventTriggeringFlag = ProjectNode.EventTriggering.DoNotTriggerTrackerEvents;
// notify SolutionEvents listeners that we are about to add children
IVsFireSolutionEvents fireSolutionEvents = solution as IVsFireSolutionEvents;
if(fireSolutionEvents == null)
{
throw new InvalidOperationException();
}
ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnBeforeUnloadProject(node.NestedHierarchy));
int isDirtyAsInt = 0;
this.IsDirty(out isDirtyAsInt);
bool isDirty = (isDirtyAsInt == 0) ? false : true;
ProjectElement element = node.ItemNode;
node.CloseNestedProjectNode();
// Remove from the solution
this.RemoveChild(node);
// Now readd it
try
{
__VSCREATEPROJFLAGS flags = __VSCREATEPROJFLAGS.CPF_NOTINSLNEXPLR | __VSCREATEPROJFLAGS.CPF_SILENT | __VSCREATEPROJFLAGS.CPF_OPENFILE;
newNode = this.AddExistingNestedProject(element, flags);
newNode.AddVirtualProject();
}
catch(Exception e)
{
// We get a System.Exception if anything failed, thus we have no choice but catch it.
// Exceptions are digested by VS. Show the error if not in automation.
if(!Utilities.IsInAutomationFunction(this.Site))
{
string message = (String.IsNullOrEmpty(e.Message)) ? SR.GetString(SR.NestedProjectFailedToReload, CultureInfo.CurrentUICulture) : e.Message;
string title = string.Empty;
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL;
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK;
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
VsShellUtilities.ShowMessageBox(this.Site, title, message, icon, buttons, defaultButton);
}
// Do not digest exception. let the caller handle it. If in a later stage this exception is not digested then the above messagebox is not needed.
throw;
}
#if DEBUG
IVsHierarchy nestedHierarchy;
ErrorHandler.ThrowOnFailure(solution.GetProjectOfUniqueName(newNode.GetMkDocument(), out nestedHierarchy));
Debug.Assert(nestedHierarchy != null && Utilities.IsSameComObject(nestedHierarchy, newNode.NestedHierarchy), "The nested hierrachy was not reloaded correctly.");
#endif
this.SetProjectFileDirty(isDirty);
ErrorHandler.ThrowOnFailure(fireSolutionEvents.FireOnAfterLoadProject(newNode.NestedHierarchy));
}
finally
{
// In this scenario the nested project failed to unload or reload the nested project. We will unload the whole project, otherwise the nested project is lost.
// This is similar to the scenario when one wants to open a project and the nested project cannot be loaded because for example the project file has xml errors.
// We should note that we rely here that if the unload fails then exceptions are not digested and are shown to the user.
if(newNode == null || newNode.NestedHierarchy == null)
{
ErrorHandler.ThrowOnFailure(solution.CloseSolutionElement((uint)__VSSLNCLOSEOPTIONS.SLNCLOSEOPT_UnloadProject | (uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, this.InteropSafeIVsHierarchy, 0));
}
else
{
this.EventTriggeringFlag = ProjectNode.EventTriggering.TriggerAll;
}
}
}
/// <summary>
/// Event callback. Called when one of the nested project files is changed.
/// </summary>
/// <param name="sender">The FileChangeManager object.</param>
/// <param name="e">Event args containing the file name that was updated.</param>
private void OnNestedProjectFileChangedOnDisk(object sender, FileChangedOnDiskEventArgs e)
{
#region Pre-condition validation
Debug.Assert(e != null, "No event args specified for the FileChangedOnDisk event");
// We care only about time change for reload.
if((e.FileChangeFlag & _VSFILECHANGEFLAGS.VSFILECHG_Time) == 0)
{
return;
}
// test if we actually have a document for this id.
string moniker;
this.GetMkDocument(e.ItemID, out moniker);
Debug.Assert(NativeMethods.IsSamePath(moniker, e.FileName), " The file + " + e.FileName + " has changed but we could not retrieve the path for the item id associated to the path.");
#endregion
bool reload = true;
if(!Utilities.IsInAutomationFunction(this.Site))
{
// Prompt to reload the nested project file. We use the moniker here since the filename from the event arg is canonicalized.
string message = String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.QueryReloadNestedProject, CultureInfo.CurrentUICulture), moniker);
string title = string.Empty;
OLEMSGICON icon = OLEMSGICON.OLEMSGICON_INFO;
OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_YESNO;
OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST;
reload = (VsShellUtilities.ShowMessageBox(this.Site, message, title, icon, buttons, defaultButton) == NativeMethods.IDYES);
}
if(reload)
{
// We have to use here the interface method call, since it might be that specialized project nodes like the project container item
// is owerwriting the default functionality.
this.ReloadItem(e.ItemID, 0);
}
}
#endregion
}
}