/*************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. This code is licensed under the Visual Studio SDK license terms. THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR 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 { [CLSCompliant(false), ComVisible(true)] public abstract class ProjectContainerNode : ProjectNode, IVsParentProject, IBuildDependencyOnProjectContainer { #region fields /// /// Setting this flag to true will build all nested project when building this project /// private bool buildNestedProjectsOnBuild = true; private ProjectElement nestedProjectElement; /// /// Defines the listener that would listen on file changes on the nested project node. /// /// ///This might need a refactoring when nested projects can be added and removed by demand. /// private FileChangeManager nestedProjectNodeReloader; #endregion #region ctors protected ProjectContainerNode() { } #endregion #region properties /// /// Returns teh object that handles listening to file changes on the nested project files. /// 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 /// /// This is the object that will be returned by EnvDTE.Project.Object for this project /// internal override object Object { get { return new OASolutionFolder(this); } } #endregion #region public overridden methods /// /// Gets the nested hierarchy. /// /// The item id. /// Identifier of the interface to be returned in ppHierarchyNested. /// Pointer to the interface whose identifier was passed in iidHierarchyNested. /// Pointer to an item identifier of the root node of the nested hierarchy. /// 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. [CLSCompliant(false)] 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); } /// /// Called to reload a project item. /// Reloads a project and its nested project nodes. /// /// Specifies itemid from VSITEMID. /// Reserved. /// If the method succeeds, it returns S_OK. If it fails, it returns an error code. 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); } /// /// Reloads a project and its nested project nodes. /// 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 /// /// Defines whether nested projects should be build with the parent project /// public virtual bool BuildNestedProjectsOnBuild { get { return this.buildNestedProjectsOnBuild; } set { this.buildNestedProjectsOnBuild = value; } } /// /// Enumerates the nested hierachies that should be added to the build dependency list. /// /// public virtual IVsHierarchy[] EnumNestedHierachiesForBuildDependency() { List nestedProjectList = new List(); // 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(); } /// /// 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. /// 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; } /// /// 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. /// 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); } /// /// 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. /// /// The project item to use as the base of the nested project. /// true if the wizard should run silently, otherwise false. [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 folder = oaProject.Object as Automation.OASolutionFolder; // 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()); } } } /// /// 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. /// 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); } /// /// 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. /// 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; } /// /// 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 /// protected virtual NestedProjectNode CreateNestedProjectNode(ProjectElement element) { return new NestedProjectNode(this, element); } /// /// Links the nested project nodes to the solution. The default implementation parses all nested project nodes and calles AddVirtualProjectEx on them. /// protected virtual void AddVirtualProjects() { for(HierarchyNode child = this.FirstChild; child != null; child = child.NextSibling) { NestedProjectNode nestedProjectNode = child as NestedProjectNode; if(nestedProjectNode != null) { nestedProjectNode.AddVirtualProject(); } } } /// /// 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). /// 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; } /// /// Get information from the registry based for the project /// factory corresponding to the TypeGuid of the element /// 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; } /// /// Reloads a nested project node by deleting it and readding it. /// /// The node to reload. 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, 0)); } else { this.EventTriggeringFlag = ProjectNode.EventTriggering.TriggerAll; } } } /// /// Event callback. Called when one of the nested project files is changed. /// /// The FileChangeManager object. /// Event args containing the file name that was updated. 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 } }