/// <summary> /// Notifies all of the listeners that a project has begun building. /// </summary> /// <returns>true if the build should continue; false if one or more of the listeners requested /// that the build should be canceled.</returns> public bool OnBuildBegin() { bool continueBuilding = true; // Let all of our listeners know that the build has started. Tracer.WriteLineInformation(classType, "OnBuildBegin", "Notifying all of our listeners that the build has started."); // There are some cases where the collection is changed while we're iterating it. // To be safe, we'll create a copy of the collection and iterate over that. // We just want a shallow copy, though, and not a deep (Clone) copy. ArrayList clone = new ArrayList(this.Values); foreach (IVsBuildStatusCallback eventItem in clone) { try { int continueFlag = Convert.ToInt32(continueBuilding); eventItem.BuildBegin(ref continueFlag); if (continueFlag == 0) { continueBuilding = false; } } catch (Exception e) { Tracer.WriteLine(classType, "OnBuildBegin", Tracer.Level.Warning, "There was an exception in one of the listener's event handling code: {0}", e.ToString()); } } return(continueBuilding); }
/// <summary> /// Generates a unique document name for a new node under the parent node. /// </summary> /// <param name="suggestedRoot">The suggested root to use for the unique name.</param> /// <param name="extension">The extension to use for the new file or folder (with or without the leading '.'). Can be null or empty.</param> /// <param name="isFolder">Indicates whether the new name is intended to be a file or a folder.</param> /// <returns>A unique document name for a new node under the this node.</returns> public string GenerateUniqueName(string suggestedRoot, string extension, bool isFolder) { Tracer.VerifyStringArgument(suggestedRoot, "suggestedRoot"); int suffixNumber = 0; bool foundUnique = false; string uniqueName = String.Empty; // Canonicalize the extension by setting it either to "" or prepend it with a '.' extension = ((extension == null || extension.Length == 0) ? String.Empty : PackageUtility.EnsureLeadingChar(extension, '.')); // We have to make sure that this item doesn't already exist in the hierarchy and the file system. while (!foundUnique) { if (suffixNumber == 0) { uniqueName = suggestedRoot + extension; } else { uniqueName = suggestedRoot + suffixNumber + extension; } // Look in the hierarchy to see if there is an existing item with the proposed name. foundUnique = true; foreach (Node node in this.Children) { if (PackageUtility.FileStringEquals(uniqueName, node.Caption)) { foundUnique = false; break; } } // If the name is unique within the hierarchy, we still need to check the file system. if (foundUnique) { string pathToCheck = Path.Combine(this.AbsoluteDirectory, uniqueName); if (isFolder && Directory.Exists(pathToCheck)) { foundUnique = false; } else if (!isFolder && File.Exists(pathToCheck)) { foundUnique = false; } else { // Ok, we found a unique name. break; } } // Increment the number to append to the root part of the path. suffixNumber++; } Tracer.WriteLineInformation(classType, "GenerateUniqueName", "Found a unique name for a new node. New name = '{0}'.", uniqueName); return(uniqueName); }
/// <summary> /// Checks whether the proposed caption already exists, meaning that there is not an existing file /// or folder at the root path or that there is not already a sibling hierarchy item with the same name. /// </summary> /// <param name="newCaption">The proposed caption.</param> /// <param name="newPath">The proposed new absolute file path.</param> /// <remarks>The method throws an exception if the caption already exists.</remarks> private void VerifyCaptionDoesNotExist(string newCaption, string newPath) { bool valid = true; // Make sure there isn't already a sibling with the same caption. The root node has no siblings. if (this.Parent != null) { foreach (Node sibling in this.Parent.Children) { bool thisIsSibling = Object.ReferenceEquals(sibling, this); bool captionsEqual = (PackageUtility.FileStringEquals(newCaption, sibling.Caption)); // We can have a file system node that is the same name as a virtual node. // For example, we can name a file/folder "Library References" if we want, // even though that is already in the hierarchy. bool isExactlyOneVirtual = ((this.IsVirtual && !sibling.IsVirtual) || (!this.IsVirtual && sibling.IsVirtual)); if (!thisIsSibling && captionsEqual && !isExactlyOneVirtual) { valid = false; break; } } } if (valid) { // Now check to see if the file system already contains a file/folder by the same name. valid = ((this.IsFile && !File.Exists(newPath)) || (this.IsFolder && !Directory.Exists(newPath))); } if (!valid) { Tracer.WriteLineInformation(classType, "VerifyCaption", "An existing file or folder named '{0}' already exists on the disk.", newCaption); throw new InvalidOperationException(SconceStrings.ErrorItemAlreadyExistsOnDisk(newCaption)); } }
/// <summary> /// Notifies all of the listeners that a project has finished building. /// </summary> /// <param name="success">true if the build operation completed successfully. On an up-to-date check, /// <paramref name="success"/> must be set to true when the project configuration is up to date and /// false when the project configuration is not up to date.</param> public void OnBuildEnd(bool success) { // Let all of our listeners know that the build has ended. if (success) { Tracer.WriteLineInformation(classType, "OnBuildEnd", "Notifying all of our listeners that the build has ended successfully."); } else { Tracer.WriteLineInformation(classType, "OnBuildEnd", "Notifying all of our listeners that the build has ended with errors."); } int successFlag = Convert.ToInt32(success); // There are some cases where the collection is changed while we're iterating it. // To be safe, we'll create a copy of the collection and iterate over that. // We just want a shallow copy, though, and not a deep (Clone) copy. ArrayList clone = new ArrayList(this.Values); foreach (IVsBuildStatusCallback eventItem in clone) { try { eventItem.BuildEnd(successFlag); } catch (Exception e) { Tracer.WriteLine(classType, "OnBuildEnd", Tracer.Level.Warning, "There was an exception in one of the listener's event handling code: {0}", e.ToString()); } } }
/// <summary> /// Does the actual work of changing the caption after all of the verifications have been done /// that it's Ok to move the file. /// </summary> /// <param name="newCaption">The new caption.</param> /// <param name="newPath">The new absolute path.</param> public override void MoveNodeOnCaptionChange(string newCaption, string newPath) { string oldPath = this.AbsolutePath; IVsUIShell vsUIShell = (IVsUIShell)this.Hierarchy.ServiceProvider.GetServiceOrThrow(typeof(SVsUIShell), typeof(IVsUIShell), classType, "MoveNodeOnCaptionChange"); // Tell the environment to stop listening to file change events on the old file. using (FileChangeNotificationSuspender notificationSuspender = new FileChangeNotificationSuspender(oldPath)) { // Make sure the environment says we can start the rename. if (!this.Hierarchy.AttachedProject.Tracker.CanRenameProject(oldPath, newPath)) { // If the user chose to not check out the solution file, then we want to throw the // save cancelled HRESULT. throw new COMException("User cancelled the solution file check out.", NativeMethods.OLE_E_PROMPTSAVECANCELLED); } // Move the file on the file system to match the new name. if (File.Exists(oldPath) && !File.Exists(newPath)) { Tracer.WriteLineInformation(classType, "MoveNodeOnCaptionChange", "Renaming the project file '{0}' to '{1}'.", oldPath, newPath); File.Move(oldPath, newPath); } // Tell the environment that we're done renaming the document. this.Hierarchy.AttachedProject.Tracker.OnProjectRenamed(oldPath, newPath); // Update the property browser. vsUIShell.RefreshPropertyBrowser(0); } }
/// <summary> /// Initializes this package. /// </summary> private void Initialize() { int hr = NativeMethods.S_OK; // If we have any services to proffer, let's do it now. if (this.services != null) { IProfferService ps = (IProfferService)this.GetService(typeof(IProfferService)); Tracer.Assert(ps != null, "We have services to proffer, but can't get an instance of IProfferService."); if (ps != null) { foreach (DictionaryEntry entry in this.services) { ProfferedService service = entry.Value as ProfferedService; if (service != null) { Type serviceType = (Type)entry.Key; Guid serviceGuid = serviceType.GUID; uint cookie; hr = ps.ProfferService(ref serviceGuid, this, out cookie); service.Cookie = cookie; if (NativeMethods.Failed(hr)) { string message = this.Context.NativeResources.GetString(ResId.IDS_E_FAILEDTOPROFFERSERVICE, serviceType.FullName); Tracer.Fail(message); throw new COMException(message, hr); } } } } } // Create the Project Factory and register our project types. Tracer.WriteLineInformation(classType, "Initialize", "Creating the project factory and registering our project types."); IVsRegisterProjectTypes regProjTypes = (IVsRegisterProjectTypes)this.Context.ServiceProvider.GetServiceOrThrow(typeof(SVsRegisterProjectTypes), typeof(IVsRegisterProjectTypes), classType, "Initialize"); this.projectFactory = this.CreateProjectFactory(); Guid projectGuid = this.ProjectTypeGuid; hr = regProjTypes.RegisterProjectType(ref projectGuid, this.projectFactory, out this.projectCookie); if (NativeMethods.Succeeded(hr)) { Tracer.WriteLine(classType, "Initialize", Tracer.Level.Information, "Successfully registered our project types."); } else { Tracer.Fail("Failed to register the Wix Project type. HRESULT = 0x{0}", hr.ToString("x")); } }
/// <summary> /// Called right after the value is removed from the collection. /// </summary> /// <param name="index">The index of the item removed from the collection.</param> /// <param name="value">The value just removed from the collection.</param> protected override void OnRemoveComplete(int index, object value) { Node node = (Node)value; // First remove the children of the node. FolderNode folderNode = node as FolderNode; if (folderNode != null) { folderNode.Children.Clear(); } // Remove the node from our lookup tables. this.idTable.Remove(node.HierarchyId); this.pathTable.Remove(node.AbsolutePath); // Set the node's parent to null. node.Parent = null; // This is useful information to trace, so we'll use the Hierarchy category and the Information level, // which will allow this to be traced by default. Tracer.WriteLineInformation(classType, "OnRemoveComplete", "Removed '{0}' from the hierarchy.", node); }
/// <summary> /// Makes sure that another build is not processing, resets the build flags, and lets /// the environment know that the build has started. /// </summary> /// <param name="outputPane">The <see cref="IVsOutputWindowPane"/> to use for writing messages to the environment.</param> /// <returns>true if the build should proceed; otherwise, false.</returns> protected virtual bool PrepareBuild(IVsOutputWindowPane outputPane) { // Check to make sure another build is not happening. if (this.IsBuilding) { string message = Package.Instance.Context.NativeResources.GetString(ResourceId.IDS_ANOTHERPROJECTBUILDING); Package.Instance.Context.ShowMessageBox(message, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, OLEMSGICON.OLEMSGICON_WARNING); Tracer.WriteLineInformation(classType, "PrepareBuild", "Another build is already running. Skipping this build."); return(false); } this.IsBuilding = true; this.buildSuccessful = false; this.outputPane = outputPane; // Let the environment know that we've started the build. this.cancelBuild = !this.eventListeners.OnBuildBegin(); if (this.CancelBuild) { this.FinishBuild(false); return(false); } return(true); }
int IVsProjectFactory.CreateProject(string pszFilename, string pszLocation, string pszName, uint grfCreateFlags, ref Guid iidProject, out IntPtr ppvProject, out int pfCanceled) { IntPtr pUnk = IntPtr.Zero; pfCanceled = 0; ppvProject = IntPtr.Zero; bool loadedSuccessfully = false; try { Tracer.VerifyStringArgument(pszFilename, "pszFilename"); __VSCREATEPROJFLAGS createFlags = (__VSCREATEPROJFLAGS)grfCreateFlags; // Get the right version of the project serializer. ProjectSerializer serializer = this.CreateSerializer(pszFilename); // Do we need to suppress any load failures from being reported to the end user. serializer.SilentFailures = ((createFlags & __VSCREATEPROJFLAGS.CPF_SILENT) == __VSCREATEPROJFLAGS.CPF_SILENT); // Now we need to load the project, either from a template file or from an existing file. bool openExisting = ((createFlags & __VSCREATEPROJFLAGS.CPF_OPENFILE) == __VSCREATEPROJFLAGS.CPF_OPENFILE); bool openFromTemplate = ((createFlags & __VSCREATEPROJFLAGS.CPF_CLONEFILE) == __VSCREATEPROJFLAGS.CPF_CLONEFILE); Tracer.Assert((openExisting && !openFromTemplate) || (!openExisting && openFromTemplate), "The grfCreateFlags are incorrect. You can't have both opening existing and opening from template. Flags={0}", createFlags); if (openExisting) { Tracer.WriteLineInformation(classType, "IVsProjectFactory.CreateProject", "Attempting to load project: File name={0} Location={1} Name={2} GUID={3}.", pszFilename, pszLocation, pszName, iidProject.ToString("B").ToUpper(CultureInfo.InvariantCulture)); loadedSuccessfully = serializer.Load(pszFilename); if (loadedSuccessfully) { Tracer.WriteLineInformation(classType, "IVsProjectFactory.CreateProject", "Successfully loaded project '{0}'.", pszFilename); } else { Tracer.WriteLineInformation(classType, "IVsProjectFactory.CreateProject", "There were errors in loading project '{0}'.", pszFilename); } } else { Tracer.WriteLineInformation(classType, "IVsProjectFactory.CreateProject", "Attempting to create a new project from a template: File name={0} Location={1} Name={2} GUID={3}.", pszFilename, pszLocation, pszName, iidProject.ToString("B").ToUpper(CultureInfo.InvariantCulture)); Tracer.VerifyStringArgument(pszLocation, "pszLocation"); Tracer.VerifyStringArgument(pszName, "pszName"); string destinationFile = Path.Combine(pszLocation, pszName); loadedSuccessfully = serializer.LoadFromTemplate(pszFilename, destinationFile); if (loadedSuccessfully) { Tracer.WriteLineInformation(classType, "IVsProjectFactory.CreateProject", "Successfully loaded project '{0}'.", pszFilename); } else { Tracer.WriteLineInformation(classType, "IVsProjectFactory.CreateProject", "There were errors in loading project '{0}'.", pszFilename); } } if (loadedSuccessfully) { // Once we've loaded the project, we need to return the COM object that the environment is requesting. pUnk = Marshal.GetIUnknownForObject(serializer.Project); int hr = Marshal.QueryInterface(pUnk, ref iidProject, out ppvProject); Tracer.Assert(NativeMethods.Succeeded(hr), "Cannot get the requested project interface ({0}): returned {1}", iidProject.ToString("B").ToUpper(CultureInfo.InvariantCulture), hr); NativeMethods.ThrowOnFailure(hr); } } catch (Exception e) { Package.Instance.Context.NotifyInternalError(e.ToString()); } finally { if (pUnk != IntPtr.Zero) { Marshal.Release(pUnk); } } return(loadedSuccessfully ? NativeMethods.S_OK : NativeMethods.E_FAIL); }
/// <summary> /// Opens the standard editor for this file type in Visual Studio. /// </summary> /// <param name="logicalView">The type of view in which to open the document.</param> /// <param name="existingDocumentData"> /// Passed through to the IVsUIShellOpenDocument.OpenStandardEditor or OpenSpecificEditor, which /// will then determine if the document is already opened and reused the open window. /// </param> /// <param name="physicalView"> /// Name of the physical view if we're opening with a specific editor. Not used if opening with a standard editor. /// </param> /// <param name="specificEditor">The GUID of the specific registered editor to use to open this node.</param> /// <returns>The <see cref="IVsWindowFrame"/> object that contains the opened document.</returns> private IVsWindowFrame Open(VsLogicalView logicalView, IntPtr existingDocumentData, Guid specificEditor, string physicalView) { Tracer.VerifyNonNullArgument(logicalView, "logicalView"); // Check to see if the file exists before we try to open it. if (!File.Exists(this.AbsolutePath)) { Context.ShowErrorMessageBox(SconceStrings.FileDoesNotExist(this.AbsolutePath)); return(null); } IVsWindowFrame windowFrame; Guid logicalViewGuid = logicalView.Value; Guid editorTypeGuid = specificEditor; bool useSpecificEditor = (specificEditor != Guid.Empty); int hr; // Get a IVsUIShellOpenDocument object so that we can use it to open the document. IVsUIShellOpenDocument vsUIShellOpenDocument = (IVsUIShellOpenDocument)this.ServiceProvider.GetServiceOrThrow(typeof(SVsUIShellOpenDocument), typeof(IVsUIShellOpenDocument), classType, "Open"); // Open the document. if (useSpecificEditor) { hr = vsUIShellOpenDocument.OpenSpecificEditor( 0, this.CanonicalName, ref editorTypeGuid, physicalView, ref logicalViewGuid, this.Caption, (IVsUIHierarchy)this.Hierarchy, this.HierarchyId, existingDocumentData, (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)Package.Instance, out windowFrame); } else { hr = vsUIShellOpenDocument.OpenStandardEditor( unchecked ((uint)__VSOSEFLAGS.OSE_ChooseBestStdEditor), this.CanonicalName, ref logicalViewGuid, this.Caption, (IVsUIHierarchy)this.Hierarchy, this.HierarchyId, existingDocumentData, (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)Package.Instance, out windowFrame); } string editorTypeName = useSpecificEditor ? "specific" : "standard"; if (NativeMethods.Succeeded(hr)) { Tracer.WriteLineInformation(classType, "Open", "Succeeded in opening '{0}' with a {1} editor.", this.AbsolutePath, editorTypeName); if (windowFrame != null) { // Get the document cookie and cache it. object pvar; hr = windowFrame.GetProperty((int)__VSFPROPID.VSFPROPID_DocCookie, out pvar); NativeMethods.ThrowOnFailure(hr); // pvar is an int, but we need a uint. We get an error if we try to immediately cast to uint // without first casting to an int. uint cookie = unchecked ((uint)(int)pvar); this.SetDocumentCookie(cookie); Tracer.WriteLineInformation(classType, "Open", "Document '{0}' has a cookie value of {1}", this.AbsolutePath, cookie); // Show the window frame of the open document. The documentation says we don't need to do this, but the reality is different. hr = windowFrame.Show(); Tracer.Assert(NativeMethods.Succeeded(hr), "Error in IVsWindowFrame.Show(): 0x{0:x}", hr); // Trace the running documents. VsHelperMethods.TraceRunningDocuments(); } else { Tracer.Fail("Open succeeded but we were returned a null IVsWindowFrame so we can't show the document."); } } else if (hr == NativeMethods.OLE_E_PROMPTSAVECANCELLED) { Tracer.WriteLineInformation(classType, "Open", "The user canceled out of the open dialog box."); } else { Tracer.Fail("Failed to open '{0}' with a {1} editor. Hr=0x{2:x}", this.AbsolutePath, editorTypeName, hr); NativeMethods.ThrowOnFailure(hr); } return(windowFrame); }
/// <summary> /// Does the actual work of changing the caption after all of the verifications have been done /// that it's Ok to move the file. /// </summary> /// <param name="newCaption">The new caption.</param> /// <param name="newPath">The new absolute path.</param> public override void MoveNodeOnCaptionChange(string newCaption, string newPath) { string oldCaption = this.Caption; string oldPath = this.AbsolutePath; string oldRelativePath = this.RelativePath; bool updatedWindowCaptions = false; bool removedNode = false; Node newNode = null; // If we are currently selected, cache the value so we can restore the selection // after the addition. bool wasSelected = this.Selected; // Tell the environment to stop listening to file change events on the old file. using (FileChangeNotificationSuspender notificationSuspender = new FileChangeNotificationSuspender(oldPath)) { // Make sure the environment says we can start the rename. if (!this.Hierarchy.AttachedProject.Tracker.CanRenameFile(oldPath, newPath)) { return; } // Move the file on the file system to match the new name. if (!this.IsVirtual && File.Exists(oldPath) && !File.Exists(newPath)) { Tracer.WriteLineInformation(classType, "MoveNodeOnCaptionChange", "Renaming the file '{0}' to '{1}'.", oldPath, newPath); string newDirectoryName = Path.GetDirectoryName(newPath); if (!Directory.Exists(newDirectoryName)) { Directory.CreateDirectory(newDirectoryName); } File.Move(oldPath, newPath); } try { // Update all of the windows that currently have this file opened in an editor. this.UpdateOpenWindowCaptions(newCaption); updatedWindowCaptions = true; // We have to remove the node and re-add it so that we can have the sorting preserved. // Also, if the extension has changed then we'll have to recreate a new type-specific // FileNode. The easy way is to remove ourself from the project then tell the project // to add an existing file. string newRelativePath = PackageUtility.MakeRelative(this.Hierarchy.RootNode.AbsoluteDirectory, newPath); // Remove ourself from the hierarchy. this.Parent.Children.Remove(this); removedNode = true; // We have now been removed from the hierarchy. Do NOT call any virtual methods or // methods that depend on our state after this point. // Re-add ourself as a new incarnation (different object). Our life ends here. newNode = this.Hierarchy.AddExistingFile(newRelativePath, true); if (newNode != null) { // We need to set our hierarchy Id to match the new hierachy Id in case Visual Studio // calls back into us for something. this.SetHierarchyId(newNode.HierarchyId); // Select the new node if we were previously selected. if (wasSelected) { newNode.Select(); } // Tell the RDT to rename the document. Context.RunningDocumentTable.RenameDocument(oldPath, newPath, newNode.HierarchyId); } } catch (Exception e) { if (ErrorUtility.IsExceptionUnrecoverable(e)) { throw; } // Rollback the file move Tracer.WriteLineWarning(classType, "MoveNodeOnCaptionChange", "There was an error in renaming the document. Exception: {0}", e); File.Move(newPath, oldPath); // Remove the node that we just added. if (newNode != null) { newNode.RemoveFromProject(); } // Re-add a new node since we've already removed the old node. if (removedNode || this.Parent == null) { newNode = this.Hierarchy.AddExistingFile(oldRelativePath, true); this.SetHierarchyId(newNode.HierarchyId); if (wasSelected) { newNode.Select(); } } // Rollback the caption update on open windows if (updatedWindowCaptions) { this.UpdateOpenWindowCaptions(oldCaption); } // Rethrow the exception throw; } // Tell the environment that we're done renaming the document. this.Hierarchy.AttachedProject.Tracker.OnFileRenamed(oldPath, newPath); // Update the property browser. IVsUIShell vsUIShell = (IVsUIShell)this.Hierarchy.ServiceProvider.GetServiceOrThrow(typeof(SVsUIShell), typeof(IVsUIShell), classType, "MoveNodeOnCaptionChange"); vsUIShell.RefreshPropertyBrowser(0); } }
/// <summary> /// Saves the attached project in the specified encoding. /// </summary> /// <param name="encoding">The encoding of the file. If null, <see cref="Encoding.UTF8"/> is used.</param> /// <param name="forceSave">Indicates whether to ignore the attached project's dirty flag when determining whether to save.</param> /// <returns>true if successful; otherwise, false.</returns> public bool Save(Encoding encoding, bool forceSave) { if (encoding == null) { encoding = Encoding.UTF8; } // If a project hasn't been attached yet, there's nothing to save. if (this.Project == null) { return(false); } // Check the dirty state of the project to see if we even need to save. if (!this.Project.IsDirty && !forceSave) { Tracer.WriteLineInformation(classType, "Save", "The project doesn't need to be saved."); return(true); } // At this point we know we have to save the project. string filePath = this.Project.FilePath; try { using (StreamWriter streamWriter = new StreamWriter(filePath, false, encoding)) { ProjectFileXmlWriter writer = new ProjectFileXmlWriter(streamWriter); writer.WriteStartDocument(); // <VisualStudioProject> writer.WriteStartElement(ElementNames.VisualStudioProject); // <Project> writer.WriteStartElement(this.ProjectElementName); this.WriteProjectAttributes(writer); // <BuildSettings> BuildSettings buildSettings = this.Project.BuildSettings; writer.WriteStartElement(ElementNames.BuildSettings); this.WriteBuildSettingsAttributes(writer); writer.WriteEndElement(); // <Configurations> writer.WriteStartElement(ElementNames.Configurations); foreach (ProjectConfiguration config in this.Project.ConfigurationProvider.ProjectConfigurations) { this.WriteConfigurationNode(writer, config); } writer.WriteEndElement(); // <References> writer.WriteStartElement(this.ReferencesElementName); foreach (ReferenceFileNode reference in this.Project.ReferencesNode.Children) { writer.WriteStartElement(this.ReferenceElementName); writer.WriteAttributeString(AttributeNames.RelativePath, reference.RelativePath); writer.WriteEndElement(); } writer.WriteEndElement(); // <Files> writer.WriteStartElement(ElementNames.Files); this.WriteFilesInNode(writer, this.Project.RootNode); writer.WriteEndElement(); writer.WriteEndDocument(); // Clear the project's dirty state. this.Project.ClearDirty(); } } catch (Exception e) { if (!this.SilentFailures) { string title = Package.Instance.Context.NativeResources.GetString(ResId.IDS_E_PROJECTFILESAVE_TITLE, filePath); string message = Package.Instance.Context.NativeResources.GetString(ResId.IDS_E_PROJECTFILESAVE, e.Message); Package.Instance.Context.ShowErrorMessageBox(title, message); } Tracer.Fail("There was an error in saving the file {0}: {1}", filePath, e.ToString()); return(false); } return(true); }