| File: System\Windows\Annotations\AnnotationResource.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // // Description: // Resource represents a portion of content. It can refer to the content // or directly contain the content, or both. They are used to model anchors // and cargos of annotations. // // Spec: Simplifying Store Cache Model.doc // using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; using MS.Internal; using MS.Internal.Annotations; namespace System.Windows.Annotations { /// <summary> /// Resource represents a portion of content. It can refer to the content /// or directly contain the content, or both. They are used to model anchors /// and cargos of annotations. /// </summary> [XmlRoot(Namespace = AnnotationXmlConstants.Namespaces.CoreSchemaNamespace, ElementName = AnnotationXmlConstants.Elements.Resource)] public sealed class AnnotationResource : IXmlSerializable, INotifyPropertyChanged2, IOwnedObject { //------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors /// <summary> /// Creates an instance of Resource. /// </summary> public AnnotationResource() { _id = Guid.NewGuid(); } /// <summary> /// Creates an instance of Resource with specified name. /// </summary> /// <param name="name">name used to distinguish this Resource /// from others in the same annotation; no validation is performed on the name</param> /// <exception cref="ArgumentNullException">name is null</exception> public AnnotationResource(string name) : this() { ArgumentNullException.ThrowIfNull(name); _name = name; _id = Guid.NewGuid(); } /// <summary> /// Creates an instance of Resource with the specified Guid as its Id. /// This constructor is for store implementations that use their own /// method of serialization and need a way to create a Resource with /// the correct read-only data. /// </summary> /// <param name="id">the new Resource's id</param> /// <exception cref="ArgumentException">id is equal to Guid.Empty</exception> public AnnotationResource(Guid id) { if (Guid.Empty.Equals(id)) throw new ArgumentException(SR.InvalidGuid, nameof(id)); // Guid is a struct and cannot be null _id = id; } #endregion Constructors //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods #region IXmlSerializable Implementation /// <summary> /// Returns the null. The annotations schema can be found at /// http://schemas.microsoft.com/windows/annotations/2003/11/core. /// </summary> public XmlSchema GetSchema() { return null; } /// <summary> /// Serializes this Resource to XML with the passed in XmlWriter. /// </summary> /// <param name="writer">the writer to serialize the Resource to</param> /// <exception cref="ArgumentNullException">writer is null</exception> public void WriteXml(XmlWriter writer) { ArgumentNullException.ThrowIfNull(writer); if (String.IsNullOrEmpty(writer.LookupPrefix(AnnotationXmlConstants.Namespaces.CoreSchemaNamespace))) { writer.WriteAttributeString(AnnotationXmlConstants.Prefixes.XmlnsPrefix, AnnotationXmlConstants.Prefixes.CoreSchemaPrefix, null, AnnotationXmlConstants.Namespaces.CoreSchemaNamespace); } writer.WriteAttributeString(AnnotationXmlConstants.Attributes.Id, XmlConvert.ToString(_id)); if (_name != null) { writer.WriteAttributeString(AnnotationXmlConstants.Attributes.ResourceName, _name); } // Use the actual field here to avoid creating the collection for no reason if (_locators != null) { foreach (ContentLocatorBase locator in _locators) { if (locator != null) { if (locator is ContentLocatorGroup) { LocatorGroupSerializer.Serialize(writer, locator); } else { ListSerializer.Serialize(writer, locator); } } } } // Use the actual field here to avoid creating the collection for no reason if (_contents != null) { foreach (XmlElement content in _contents) { content?.WriteTo(writer); } } } /// <summary> /// Deserializes an Resource from the XmlReader passed in. /// </summary> /// <param name="reader">reader to deserialize from</param> /// <exception cref="ArgumentNullException">reader is null</exception> public void ReadXml(XmlReader reader) { ArgumentNullException.ThrowIfNull(reader); XmlDocument doc = new XmlDocument(); ReadAttributes(reader); if (!reader.IsEmptyElement) { reader.Read(); // Reads the remainder of "Resource" start tag while (!(AnnotationXmlConstants.Elements.Resource == reader.LocalName && XmlNodeType.EndElement == reader.NodeType)) { if (AnnotationXmlConstants.Elements.ContentLocatorGroup == reader.LocalName) { ContentLocatorBase locator = (ContentLocatorBase)LocatorGroupSerializer.Deserialize(reader); InternalLocators.Add(locator); } else if (AnnotationXmlConstants.Elements.ContentLocator == reader.LocalName) { ContentLocatorBase locator = (ContentLocatorBase)ListSerializer.Deserialize(reader); InternalLocators.Add(locator); } else if (XmlNodeType.Element == reader.NodeType) { XmlElement element = doc.ReadNode(reader) as XmlElement; InternalContents.Add(element); } else { // The resource must contain a non-XmlElement child such as plain // text which is not part of the schema. throw new XmlException(SR.Format(SR.InvalidXmlContent, AnnotationXmlConstants.Elements.Resource)); } } } reader.Read(); // Reads the end of the "Resource" element (or whole element if empty) } #endregion IXmlSerializable Implementation #endregion Public Methods //------------------------------------------------------ // // Public Operators // //------------------------------------------------------ //------------------------------------------------------ // // Public Events // //------------------------------------------------------ #region Public Events /// <summary> /// /// </summary> event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged { add{ _propertyChanged += value; } remove{ _propertyChanged -= value; } } #endregion Public Events //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ #region Public Properties /// <summary> /// A Resource is given a unique Guid when it is first instantiated. /// </summary> /// <returns>the unique id of this Resource; this property will return an /// invalid Guid if the Resource was instantied with the default constructor - /// which should not be used directly</returns> public Guid Id { get { return _id; } } /// <summary> /// The name given this Resource to distinguish it from other anchors or /// cargos in an annotation. /// </summary> /// <value>the name of this resource; can be null</value> public string Name { get { return _name; } set { bool changed = false; if (_name == null) { if (value != null) { changed = true; } } else if (!_name.Equals(value)) { changed = true; } _name = value; if (changed) { FireResourceChanged("Name"); } } } /// <summary> /// Collection of zero or more Locators in this Resource. /// </summary> /// <returns>collection of Locators; never returns null</returns> public Collection<ContentLocatorBase> ContentLocators { get { return InternalLocators; } } /// <summary> /// Collection of zero or more XmlElements representing the /// contents of this Resource. /// </summary> /// <returns>collection of XmlElements which are the contents of /// this resource; never returns null</returns> public Collection<XmlElement> Contents { get { return InternalContents; } } #endregion Public Properties //------------------------------------------------------ // // Internal Properties // //------------------------------------------------------ #region Internal Properties bool IOwnedObject.Owned { get { return _owned; } set { _owned = value; } } /// <summary> /// Returns serializer for ContentLocator objects. Lazily /// creates the serializer for cases where its not needed. /// The property is internal so that other classes (ContentLocatorGroup) /// has access to the same serializer. /// </summary> internal static Serializer ListSerializer { get { if (s_ListSerializer == null) { s_ListSerializer = new Serializer(typeof(ContentLocator)); } return s_ListSerializer; } } #endregion Internal Properties //------------------------------------------------------ // // Private Properties // //------------------------------------------------------ #region Private Properties /// <summary> /// Returns the list of locators - used internally to /// lazily create the list when its needed. /// </summary> private AnnotationObservableCollection<ContentLocatorBase> InternalLocators { get { if (_locators == null) { _locators = new AnnotationObservableCollection<ContentLocatorBase>(); _locators.CollectionChanged += OnLocatorsChanged; } return _locators; } } /// <summary> /// Returns the list of contents - used internally to /// lazily create the list when its needed. /// </summary> private XmlElementCollection InternalContents { get { if (_contents == null) { _contents = new XmlElementCollection(); _contents.CollectionChanged += OnContentsChanged; } return _contents; } } /// <summary> /// Returns serializer for ContentLocatorGroup objects. Lazily creates /// the serializer for cases where its not needed. /// </summary> private static Serializer LocatorGroupSerializer { get { if (s_LocatorGroupSerializer == null) { s_LocatorGroupSerializer = new Serializer(typeof(ContentLocatorGroup)); } return s_LocatorGroupSerializer; } } #endregion Private Properties //------------------------------------------------------ // // Private Methods // //------------------------------------------------------ #region Private Methods /// <summary> /// Reads all attributes for the "Resource" element and throws /// appropriate exceptions if any required attributes are missing /// or unexpected attributes are found /// </summary> private void ReadAttributes(XmlReader reader) { Invariant.Assert(reader != null, "No reader passed in."); // Use a temporary variable to determine if a Guid value // was actually provided. The member variable is set in // the default ctor and can't be relied on for this. Guid tempId = Guid.Empty; // Read all the attributes while (reader.MoveToNextAttribute()) { string value = reader.Value; // Skip null values - they will be treated the same as if // they weren't specified at all if (value == null) continue; switch (reader.LocalName) { case AnnotationXmlConstants.Attributes.Id: tempId = XmlConvert.ToGuid(value); break; case AnnotationXmlConstants.Attributes.ResourceName: _name = value; break; default: if (!Annotation.IsNamespaceDeclaration(reader)) throw new XmlException(SR.Format(SR.UnexpectedAttribute, reader.LocalName, AnnotationXmlConstants.Elements.Resource)); break; } } if (Guid.Empty.Equals(tempId)) { throw new XmlException(SR.Format(SR.RequiredAttributeMissing, AnnotationXmlConstants.Attributes.Id, AnnotationXmlConstants.Elements.Resource)); } _id = tempId; // Name attribute is optional, so no need to check it // Move back to the parent "Resource" element reader.MoveToContent(); } /// <summary> /// Listens for change events from the list of locators. Fires a change event /// for this resource when an event is received. /// </summary> private void OnLocatorsChanged(object sender, NotifyCollectionChangedEventArgs e) { FireResourceChanged("Locators"); } /// <summary> /// Listens for change events from the list of contents. Fires a change event /// for this resource when an event is received. /// </summary> private void OnContentsChanged(object sender, NotifyCollectionChangedEventArgs e) { FireResourceChanged("Contents"); } /// <summary> /// Fires a change notification to the Annotation that contains /// this Resource. /// </summary> private void FireResourceChanged(string name) { if (_propertyChanged != null) { _propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(name)); } } #endregion Private Methods //------------------------------------------------------ // // 2Private Fields // //------------------------------------------------------ #region Private Fields /// <summary> /// Unique ID for this resource /// </summary> private Guid _id; /// <summary> /// Name given this resource by the developer. Used to differentiate /// between multiple resources on an annotation. /// </summary> private string _name; /// <summary> /// List of locators that refer to the data of this resource /// </summary> private AnnotationObservableCollection<ContentLocatorBase> _locators; /// <summary> /// List of instances of the data for this resource /// </summary> private XmlElementCollection _contents; /// <summary> /// Serializer we use for serializing and deserializing Locators. /// </summary> private static Serializer s_ListSerializer; /// <summary> /// Serializer we use for serializing and deserializing LocatorGroups. /// </summary> private static Serializer s_LocatorGroupSerializer; /// <summary> /// /// </summary> private bool _owned; /// <summary> /// /// </summary> private PropertyChangedEventHandler _propertyChanged; #endregion Private Fields } }