// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Text; using System.Xml; using System.Windows; using System.Windows.Markup; namespace ICSharpCode.WpfDesign.XamlDom { /// /// Describes a property on a . /// [DebuggerDisplay("XamlProperty: {PropertyName}")] public sealed class XamlProperty { XamlObject parentObject; internal readonly XamlPropertyInfo propertyInfo; XamlPropertyValue propertyValue; CollectionElementsCollection collectionElements; bool isCollection; bool isResources; static readonly IList emptyCollectionElementsArray = new XamlPropertyValue[0]; // for use by parser only internal XamlProperty(XamlObject parentObject, XamlPropertyInfo propertyInfo, XamlPropertyValue propertyValue) : this(parentObject, propertyInfo) { PossiblyNameChanged(null, propertyValue); this.propertyValue = propertyValue; if (propertyValue != null) { propertyValue.ParentProperty = this; } UpdateValueOnInstance(); } internal XamlProperty(XamlObject parentObject, XamlPropertyInfo propertyInfo) { this.parentObject = parentObject; this.propertyInfo = propertyInfo; if (propertyInfo.IsCollection) { isCollection = true; collectionElements = new CollectionElementsCollection(this); if (propertyInfo.Name.Equals(XamlConstants.ResourcesPropertyName, StringComparison.Ordinal) && propertyInfo.ReturnType == typeof(ResourceDictionary)) { isResources = true; } } } /// /// Gets the parent object for which this property was declared. /// public XamlObject ParentObject { get { return parentObject; } } /// /// Gets the property name. /// public string PropertyName { get { return propertyInfo.Name; } } /// /// Gets the type the property is declared on. /// public Type PropertyTargetType { get { return propertyInfo.TargetType; } } /// /// Gets if this property is an attached property. /// public bool IsAttached { get { return propertyInfo.IsAttached; } } /// /// Gets if this property is an event. /// public bool IsEvent { get { return propertyInfo.IsEvent; } } /// /// Gets the return type of the property. /// public Type ReturnType { get { return propertyInfo.ReturnType; } } /// /// Gets the type converter used to convert property values to/from string. /// public TypeConverter TypeConverter { get { return propertyInfo.TypeConverter; } } /// /// Gets the category of the property. /// public string Category { get { return propertyInfo.Category; } } /// /// Gets the value of the property. Can be null if the property is a collection property. /// public XamlPropertyValue PropertyValue { get { return propertyValue; } set { SetPropertyValue(value); } } /// /// Gets if the property represents the FrameworkElement.Resources property that holds a locally-defined resource dictionary. /// public bool IsResources { get { return isResources; } } /// /// Gets if the property is a collection property. /// public bool IsCollection { get { return isCollection; } } /// /// Gets the collection elements of the property. Is empty if the property is not a collection. /// public IList CollectionElements { get { return collectionElements ?? emptyCollectionElementsArray; } } /// /// Gets if the property is set. /// public bool IsSet { get { return propertyValue != null || _propertyElement != null; // collection } } /// /// Occurs when the value of the IsSet property has changed. /// public event EventHandler IsSetChanged; /// /// Occurs when the value of the property has changed. /// public event EventHandler ValueChanged; /// /// Occurs when MarkupExtension evaluated PropertyValue dosn't changed but ValueOnInstance does. /// public event EventHandler ValueOnInstanceChanged; void SetPropertyValue(XamlPropertyValue value) { // Binding... //if (IsCollection) { // throw new InvalidOperationException("Cannot set the value of collection properties."); //} bool wasSet = this.IsSet; PossiblyNameChanged(propertyValue, value); //reset expression var xamlObject = propertyValue as XamlObject; if (xamlObject != null && xamlObject.IsMarkupExtension) propertyInfo.ResetValue(parentObject.Instance); ResetInternal(); propertyValue = value; propertyValue.ParentProperty = this; propertyValue.AddNodeTo(this); UpdateValueOnInstance(); ParentObject.OnPropertyChanged(this); if (!wasSet) { if (IsSetChanged != null) { IsSetChanged(this, EventArgs.Empty); } } if (ValueChanged != null) { ValueChanged(this, EventArgs.Empty); } } internal void UpdateValueOnInstance() { if (PropertyValue != null) { try { ValueOnInstance = PropertyValue.GetValueFor(propertyInfo); } catch { Debug.WriteLine("UpdateValueOnInstance() failed"); } } } /// /// Resets the properties value. /// public void Reset() { if (IsSet) { propertyInfo.ResetValue(parentObject.Instance); ResetInternal(); ParentObject.OnPropertyChanged(this); if (IsSetChanged != null) { IsSetChanged(this, EventArgs.Empty); } if (ValueChanged != null) { ValueChanged(this, EventArgs.Empty); } } } void ResetInternal() { if (propertyValue != null) { propertyValue.RemoveNodeFromParent(); propertyValue.ParentProperty = null; propertyValue = null; } if (_propertyElement != null) { _propertyElement.ParentNode.RemoveChild(_propertyElement); _propertyElement = null; } } XmlElement _propertyElement; internal void ParserSetPropertyElement(XmlElement propertyElement) { XmlElement oldPropertyElement = _propertyElement; if (oldPropertyElement == propertyElement) return; _propertyElement = propertyElement; if (oldPropertyElement != null && IsCollection) { Debug.WriteLine("Property element for " + this.PropertyName + " already exists, merging.."); foreach (XamlPropertyValue val in this.collectionElements) { val.RemoveNodeFromParent(); val.AddNodeTo(this); } oldPropertyElement.ParentNode.RemoveChild(oldPropertyElement); } } internal void AddChildNodeToProperty(XmlNode newChildNode) { if (this.IsCollection) { // this is the default collection InsertNodeInCollection(newChildNode, collectionElements.Count); return; } if (_propertyElement == null) { if (PropertyName == parentObject.ContentPropertyName) { parentObject.XmlElement.InsertBefore(newChildNode, parentObject.XmlElement.FirstChild); return; } _propertyElement = parentObject.OwnerDocument.XmlDocument.CreateElement( this.PropertyTargetType.Name + "." + this.PropertyName, parentObject.OwnerDocument.GetNamespaceFor(this.PropertyTargetType) ); parentObject.XmlElement.InsertBefore(_propertyElement, parentObject.XmlElement.FirstChild); } _propertyElement.AppendChild(newChildNode); } internal void InsertNodeInCollection(XmlNode newChildNode, int index) { Debug.Assert(index >= 0 && index <= collectionElements.Count); XmlElement collection = _propertyElement; if (collection == null) { if (collectionElements.Count == 0 && this.PropertyName != this.ParentObject.ContentPropertyName) { // we have to create the collection element _propertyElement = parentObject.OwnerDocument.XmlDocument.CreateElement( ParentObject.ElementType.Name + "." + this.PropertyName, parentObject.OwnerDocument.GetNamespaceFor(ParentObject.ElementType) ); if (this.IsResources) { parentObject.XmlElement.PrependChild(_propertyElement); } else { parentObject.XmlElement.AppendChild(_propertyElement); } collection = _propertyElement; } else { // this is the default collection collection = parentObject.XmlElement; } } if (collectionElements.Count == 0) { // collection is empty -> we may insert anywhere collection.AppendChild(newChildNode); } else if (index == collectionElements.Count) { // insert after last element in collection collection.InsertAfter(newChildNode, collectionElements[collectionElements.Count - 1].GetNodeForCollection()); } else { // insert before specified index collection.InsertBefore(newChildNode, collectionElements[index].GetNodeForCollection()); } } internal XmlAttribute SetAttribute(string value) { string name; var element = ParentObject.XmlElement; if (IsAttached) { name = PropertyTargetType.Name + "." + PropertyName; string ns = ParentObject.OwnerDocument.GetNamespaceFor(PropertyTargetType); string prefix = element.GetPrefixOfNamespace(ns); if (String.IsNullOrEmpty(prefix)) { prefix = ParentObject.OwnerDocument.GetPrefixForNamespace(ns); } if (!string.IsNullOrEmpty(prefix)) { element.SetAttribute(name, ns, value); return element.GetAttributeNode(name, ns); } } else { name = PropertyName; } element.SetAttribute(name, value); return element.GetAttributeNode(name); } internal string GetNameForMarkupExtension() { string name; if (IsAttached) name = PropertyTargetType.Name + "." + PropertyName; else name = PropertyName; var element = ParentObject.XmlElement; string ns = ParentObject.OwnerDocument.GetNamespaceFor(PropertyTargetType); var prefix = element.GetPrefixOfNamespace(ns); if (string.IsNullOrEmpty(prefix)) return name; else return prefix + ":" + name; } /// /// used internally by the XamlParser. /// Add a collection element that already is part of the XML DOM. /// internal void ParserAddCollectionElement(XmlElement collectionPropertyElement, XamlPropertyValue val) { if (collectionPropertyElement != null && _propertyElement == null) { ParserSetPropertyElement(collectionPropertyElement); } collectionElements.AddInternal(val); val.ParentProperty = this; if (collectionPropertyElement != _propertyElement) { val.RemoveNodeFromParent(); val.AddNodeTo(this); } } /// /// Gets/Sets the value of the property on the instance without updating the XAML document. /// public object ValueOnInstance { get { if (IsEvent) { if (propertyValue != null) return propertyValue.GetValueFor(null); else return null; } else { return propertyInfo.GetValue(parentObject.Instance); } } set { propertyInfo.SetValue(parentObject.Instance, value); if (ValueOnInstanceChanged != null) ValueOnInstanceChanged(this, EventArgs.Empty); } } /// /// Gets if this property is considered "advanced" and should be hidden by default in a property grid. /// public bool IsAdvanced { get { return propertyInfo.IsAdvanced; } } /// /// Gets the dependency property. /// public DependencyProperty DependencyProperty { get { return propertyInfo.DependencyProperty; } } void PossiblyNameChanged(XamlPropertyValue oldValue, XamlPropertyValue newValue) { if (PropertyName == "Name" && ReturnType == typeof(string)) { string oldName = null; string newName = null; var oldTextValue = oldValue as XamlTextValue; if (oldTextValue != null) oldName = oldTextValue.Text; var newTextValue = newValue as XamlTextValue; if (newTextValue != null) newName = newTextValue.Text; var obj = ParentObject; while (obj != null) { var nameScope = obj.Instance as INameScope; if (nameScope == null) { if (obj.Instance is DependencyObject) nameScope = NameScope.GetNameScope((DependencyObject)obj.Instance); } if (nameScope != null) { if (oldName != null) { try { nameScope.UnregisterName(oldName); } catch (Exception x) { Debug.WriteLine(x.Message); } } if (newName != null) { nameScope.RegisterName(newName, ParentObject.Instance); } break; } obj = obj.ParentObject; } } } /*public bool IsAttributeSyntax { get { return attribute != null; } } public bool IsElementSyntax { get { return element != null; } } public bool IsImplicitDefaultProperty { get { return attribute == null && element == null; } }*/ } }