/// <summary> /// Notifies all of our event listeners that an item in the hierarchy has changed. /// </summary> /// <param name="node">The <see cref="Node"/> that has changed.</param> /// <param name="propertyId">The property that has changed.</param> public void OnPropertyChanged(Node node, __VSHPROPID propertyId) { Tracer.VerifyNonNullArgument(node, "node"); object newValue; node.GetProperty(propertyId, out newValue); // 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); for (int i = 0; i < clone.Count; i++) { IVsHierarchyEvents eventItem = (IVsHierarchyEvents)clone[i]; Tracer.WriteLineVerbose(classType, "OnPropertyChanged", "Notifying event listener {0} that '{1}' has changed its {2} property to '{3}'.", this.CookieOf(i), node.Caption, propertyId, newValue); try { eventItem.OnPropertyChanged(node.HierarchyId, (int)propertyId, 0); } catch (Exception e) { Tracer.WriteLineWarning(classType, "OnPropertyChanged", "There was an exception in the event listener {0} event handling code: {1}", this.CookieOf(i), e.ToString()); } } }
/// <summary> /// Verifies that the current node is named <paramref name="name"/>, showing a message box to the /// user if it is not. /// </summary> /// <param name="node">The <see cref="XmlNode"/> to check.</param> /// <param name="name">The expected name of the current node.</param> /// <returns>true if the current node is named <paramref name="name"/>; otherwise, false.</returns> protected bool VerifyNode(XmlNode node, string name) { if (node.Name != name) { Tracer.WriteLineWarning(classType, "VerifyNode", "Missing '{0}' element in the project file '{1}'.", name, this.Project.FilePath); if (!this.SilentFailures) { string message = Package.Instance.Context.NativeResources.GetString(ResId.IDS_E_PROJFILE_MISSINGSECTION, this.Project.FilePath, name); Package.Instance.Context.ShowErrorMessageBox(message); } return(false); } return(true); }
/// <summary> /// Gets the required attribute from the current node. If the attribute does not exist /// or cannot be converted to the target type, then a message box is shown to the user. /// </summary> /// <param name="node">The <see cref="XmlNode"/> to read.</param> /// <param name="name">The name of the attribute to retrieve.</param> /// <param name="value">The value of the attribute.</param> /// <returns>true if the attribute exists and the value can be converted to the target /// type; otherwise, false.</returns> protected bool GetRequiredAttribute(XmlNode node, string name, out string value) { XmlAttribute attribute = node.Attributes[name]; if (attribute == null) { value = null; Tracer.WriteLineWarning(classType, "GetRequiredAttribute", "Missing required attribute '{0}' from '{1}'.", name, node.Name); if (!this.SilentFailures) { string message = Package.Instance.Context.NativeResources.GetString(ResId.IDS_E_PROJFILE_MISSINGATTRIBUTE, this.Project.FilePath, name, node.Name); Package.Instance.Context.ShowErrorMessageBox(message); } return(false); } value = attribute.Value; return(true); }
/// <summary> /// Gets a string from the resource file and formats it using the specified arguments. /// </summary> /// <param name="name">The resource identifier of the string to retrieve.</param> /// <param name="args">An array of objects to use in the formatting. Can be null or empty.</param> /// <returns>A formatted string from the resource file.</returns> public string GetString(string name, params object[] args) { string returnString = this.missingManifestString; // If we previously tried to get a string from the resource file and we had a // MissingManifestResourceException, then we don't want to try again. Exceptions // are expensive especially when we could be getting lots of strings. if (!this.isMissingManifest) { try { // First give the subclass a chance to retrieve the string if (this.IsStringDefined(name)) { returnString = this.manager.GetString(name, CultureInfo.CurrentUICulture); } //\ Try getting the string from our assembly if the subclass can't handle it else if (SconceStrings.IsValidStringName(name)) { returnString = thisAssemblyManager.GetString(name, CultureInfo.CurrentUICulture); } else { Tracer.WriteLineWarning(classType, "GetString", "The string id '{0}' is not defined.", name); returnString = name; } // Format the message if there are arguments specified. Note that we format // using the CurrentCulture and not the CurrentUICulture (although in almost all // cases it will be the same). if (returnString != null && args != null && args.Length > 0) { returnString = String.Format(CultureInfo.CurrentCulture, returnString, args); } } catch (MissingManifestResourceException e) { this.isMissingManifest = true; Tracer.Fail("The resource cannot be found in the assembly: {0}", e); } } return(returnString); }
/// <summary> /// Suspends any file change notifications to the environment for the wrapped file in /// preparation for a multi-stage file operation such as a rename. /// </summary> public void Suspend() { // We only want to suspend once. if (this.suspended) { return; } // Get the environment's change notifier. IVsFileChangeEx changeNotifier = Package.Instance.Context.GetService(typeof(SVsFileChangeEx)) as IVsFileChangeEx; if (changeNotifier == null) { Tracer.WriteLineWarning(classType, "Suspend", "Could not get an instance of the IVsChangeEx interface."); return; } // Tell the environment to stop sending change notifications for the file. int hr = changeNotifier.IgnoreFile(0, this.FilePath, IgnoreChanges); Tracer.WriteLineIf(classType, "Suspend", Tracer.Level.Warning, NativeMethods.Failed(hr), "Could not tell the environment to ignore file changes to '{0}': Hr=0x{1:x}", this.FilePath, hr); NativeMethods.ThrowOnFailure(hr); // Get the IVsDocDataFileChangeControl interface from the DocumentData. We need this // to suspend file change notifications to all editors. DocumentInfo docInfo = Package.Instance.Context.RunningDocumentTable.FindByPath(this.FilePath); if (docInfo != null) { this.docDataFileChangeControl = docInfo.DocumentData as IVsDocDataFileChangeControl; if (this.docDataFileChangeControl != null) { hr = this.docDataFileChangeControl.IgnoreFileChanges(IgnoreChanges); NativeMethods.ThrowOnFailure(hr); } } // At this point we can consider ourself suspended. this.suspended = true; Tracer.WriteLineVerbose(classType, "Suspend", "Suspended file change notifications for '{0}'.", this.FilePath); }
/// <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> /// Reads the <WindowsInstallerXml> node. /// </summary> /// <param name="node">The <see cref="XmlNode"/> to parse.</param> /// <returns>true if the node was read correctly; otherwise, false.</returns> protected bool ReadProjectNode(XmlNode node) { if (!this.VerifyNode(node, this.ProjectElementName)) { return(false); } // Read and validate all of the required attributes. // ------------------------------------------------- // SchemaVersion Version fileSchemaVersion = this.SchemaVersion; string schemaString = XmlHelperMethods.GetAttributeString(node, AttributeNames.SchemaVersion, this.SchemaVersion.ToString()); try { fileSchemaVersion = new Version(schemaString); } catch (Exception e) { Tracer.WriteLineWarning(classType, "ReadProjectNode", "Cannot parse the SchemaVersion attribute {0}: {1}.", schemaString, e.ToString()); } if (fileSchemaVersion < this.SchemaVersion) { // Right now we only support version 1.0 schemas, but if we ever change the schema // with new versions then we would need to ask the user if he/she wants to upgrade // the project to the new schema version. } else if (fileSchemaVersion > this.SchemaVersion) { string projectFileName = Path.GetFileName(this.Project.FilePath); string message = Package.Instance.Context.NativeResources.GetString(ResId.IDS_E_PROJECTFILENEWERVERSION, projectFileName); Package.Instance.Context.ShowErrorMessageBox(message); return(false); } // ProjectGuid this.Project.ProjectGuid = XmlHelperMethods.GetAttributeGuid(node, AttributeNames.ProjectGuid, Guid.NewGuid()); // Read the rest of the nodes in the project file in any order. // ----------------------------------------------------------- foreach (XmlNode childNode in node.ChildNodes) { bool success = true; switch (childNode.Name) { case ElementNames.BuildSettings: success = this.ReadBuildSettingsNode(childNode); break; case ElementNames.Configurations: success = this.ReadCollectionNode(childNode, ElementNames.Configurations, ElementNames.Configuration, new ReadCollectionItem(this.ReadConfigurationNode)); break; case ElementNames.Files: success = this.ReadCollectionNode(childNode, ElementNames.Files, ElementNames.File, new ReadCollectionItem(this.ReadFileNode)); break; } // We can't have this in the switch block because the reference node is not a constant string value. if (childNode.Name == this.ReferencesElementName) { success = this.ReadCollectionNode(childNode, this.ReferencesElementName, this.ReferenceElementName, new ReadCollectionItem(this.ReadLibraryReferenceNode)); } if (!success) { this.Project.Unavailable = true; return(false); } } return(true); }
public static void TraceRunningDocuments() { // Get the RDT (Running Document Table) IVsRunningDocumentTable rdt = Package.Instance.Context.ServiceProvider.GetService(typeof(IVsRunningDocumentTable)) as IVsRunningDocumentTable; if (rdt == null) { Tracer.WriteLineWarning(classType, "TraceRunningDocuments", "Cannot get an instance of IVsRunningDocumentTable to use for enumerating the running documents."); return; } // Get the enumerator for the currently running documents. IEnumRunningDocuments enumerator; int hr = rdt.GetRunningDocumentsEnum(out enumerator); if (NativeMethods.Failed(hr)) { Tracer.WriteLineWarning(classType, "TraceRunningDocuments", "Cannot get an instance of IEnumRunningDocuments to use for enumerating the running documents."); return; } // Enumerate. StringCollection traceLines = new StringCollection(); uint[] cookies = new uint[1]; uint fetchCount; while (true) { hr = enumerator.Next(1, cookies, out fetchCount); if (NativeMethods.Failed(hr)) { Tracer.WriteLineWarning(classType, "TraceRunningDocuments", "The enumeration failed for the running documents. Hr=0x{0:X}", hr); return; } if (fetchCount == 0) { break; } uint cookie = cookies[0]; // We shouldn't be getting a nil cookie. if (cookie == DocumentInfo.NullCookie) { Tracer.WriteLineWarning(classType, "TraceRunningDocuments", "There is a null cookie value in the RDT, which shouldn't be happening."); } else { // Now we have a document cookie, so let's get some information about it. DocumentInfo docInfo = Package.Instance.Context.RunningDocumentTable.FindByCookie(cookie); string traceMessage; if (docInfo == null) { traceMessage = PackageUtility.SafeStringFormatInvariant("The document with cookie '{0}' could not be found in the RDT. There's something weird going on.", cookie); } else { // Here's where we actually do the trace finally. traceMessage = PackageUtility.SafeStringFormatInvariant("RDT document: Cookie={0} Path={1} IsOpen={2} IsDirty={3}", docInfo.Cookie, docInfo.AbsolutePath, docInfo.IsOpen, docInfo.IsDirty); } // We don't want to trace immediately because we want all of these lines to appear together. If we // trace immediately, then the messages will be split up. traceLines.Add(traceMessage); } } // Now trace all of the messages at once. foreach (string traceMessage in traceLines) { Tracer.WriteLine(classType, "TraceRunningDocuments", Tracer.Level.Information, traceMessage); } }