// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Windows.Markup; using System.Xml; using System.Windows.Data; using System.Windows; using System.Xaml; namespace ICSharpCode.WpfDesign.XamlDom { /// /// Represents a xaml object element. /// [DebuggerDisplay("XamlObject: {Instance}")] public sealed class XamlObject : XamlPropertyValue { XamlDocument document; XmlElement element; Type elementType; object instance; List properties = new List(); string contentPropertyName; XamlProperty nameProperty; string runtimeNameProperty; /// For use by XamlParser only. internal XamlObject(XamlDocument document, XmlElement element, Type elementType, object instance) { this.document = document; this.element = element; this.elementType = elementType; this.instance = instance; this.contentPropertyName = GetContentPropertyName(elementType); XamlSetTypeConverter = GetTypeConverterDelegate(elementType); ServiceProvider = new XamlObjectServiceProvider(this); CreateWrapper(); var rnpAttrs = elementType.GetCustomAttributes(typeof(RuntimeNamePropertyAttribute), true) as RuntimeNamePropertyAttribute[]; if (rnpAttrs != null && rnpAttrs.Length > 0 && !String.IsNullOrEmpty(rnpAttrs[0].Name)) { runtimeNameProperty = rnpAttrs[0].Name; } } /// For use by XamlParser only. internal void AddProperty(XamlProperty property) { #if DEBUG if (property.IsAttached == false) { foreach (XamlProperty p in properties) { if (p.IsAttached == false && p.PropertyName == property.PropertyName) throw new XamlLoadException("duplicate property:" + property.PropertyName); } } #endif properties.Add(property); } #region XamlPropertyValue implementation internal override object GetValueFor(XamlPropertyInfo targetProperty) { if (IsMarkupExtension) { var value = ProvideValue(); if (value is string && targetProperty != null && targetProperty.ReturnType != typeof(string)) { return XamlParser.CreateObjectFromAttributeText((string)value, targetProperty, this); } return value; } else { return instance; } } internal override XmlNode GetNodeForCollection() { return element; } #endregion XamlObject parentObject; /// /// Gets the parent object. /// public XamlObject ParentObject { get { return parentObject; } internal set { parentObject = value; } } internal override void OnParentPropertyChanged() { parentObject = (ParentProperty != null) ? ParentProperty.ParentObject : null; base.OnParentPropertyChanged(); } public XmlElement XmlElement { get { return element; } } public PositionXmlElement PositionXmlElement { get { return element as PositionXmlElement; } } XmlAttribute xmlAttribute; internal XmlAttribute XmlAttribute { get { return xmlAttribute; } set { xmlAttribute = value; element = VirtualAttachTo(XmlElement, value.OwnerElement); } } string GetPrefixOfNamespace(string ns, XmlElement target) { if (target.NamespaceURI == ns) return null; var prefix = target.GetPrefixOfNamespace(ns); if (!string.IsNullOrEmpty(prefix)) return prefix; var obj = this; while (obj != null) { prefix = obj.XmlElement.GetPrefixOfNamespace(ns); if (!string.IsNullOrEmpty(prefix)) return prefix; obj = obj.ParentObject; } return null; } XmlElement VirtualAttachTo(XmlElement e, XmlElement target) { var prefix = GetPrefixOfNamespace(e.NamespaceURI, target); XmlElement newElement = e.OwnerDocument.CreateElement(prefix, e.LocalName, e.NamespaceURI); foreach (XmlAttribute a in target.Attributes) { if (a.Prefix == "xmlns" || a.Name == "xmlns") { newElement.Attributes.Append(a.Clone() as XmlAttribute); } } while (e.HasChildNodes) { newElement.AppendChild(e.FirstChild); } XmlAttributeCollection ac = e.Attributes; while (ac.Count > 0) { newElement.Attributes.Append(ac[0]); } return newElement; } /// /// Gets the name of the content property for the specified element type, or null if not available. /// /// The element type to get the content property name for. /// The name of the content property for the specified element type, or null if not available. internal static string GetContentPropertyName(Type elementType) { var contentAttrs = elementType.GetCustomAttributes(typeof(ContentPropertyAttribute), true) as ContentPropertyAttribute[]; if (contentAttrs != null && contentAttrs.Length > 0) { return contentAttrs[0].Name; } return null; } internal delegate void TypeConverterDelegate(Object targetObject, XamlSetTypeConverterEventArgs eventArgs); internal TypeConverterDelegate XamlSetTypeConverter { get; private set; } internal static TypeConverterDelegate GetTypeConverterDelegate(Type elementType) { var attrs = elementType.GetCustomAttributes(typeof(XamlSetTypeConverterAttribute), true) as XamlSetTypeConverterAttribute[]; if (attrs != null && attrs.Length > 0) { var name = attrs[0].XamlSetTypeConverterHandler; var method=elementType.GetMethod(name, BindingFlags.Static|BindingFlags.Public|BindingFlags.NonPublic); return (TypeConverterDelegate) TypeConverterDelegate.CreateDelegate(typeof(TypeConverterDelegate), method); } return null; } private XamlType _systemXamlTypeForProperty = null; /// /// Gets a representing the . /// public XamlType SystemXamlTypeForProperty { get { if (_systemXamlTypeForProperty == null) _systemXamlTypeForProperty = new XamlType(this.ElementType, this.ServiceProvider.SchemaContext); return _systemXamlTypeForProperty; } } internal override void AddNodeTo(XamlProperty property) { XamlObject holder; if (!UpdateXmlAttribute(true, out holder)) { property.AddChildNodeToProperty(element); } UpdateMarkupExtensionChain(); } internal override void RemoveNodeFromParent() { if (XmlAttribute != null) { XmlAttribute.OwnerElement.RemoveAttribute(XmlAttribute.Name); xmlAttribute = null; } else { XamlObject holder; if (!UpdateXmlAttribute(false, out holder)) { element.ParentNode.RemoveChild(element); } } //TODO: PropertyValue still there //UpdateMarkupExtensionChain(); } //TODO: reseting path property for binding doesn't work in XamlProperty //use CanResetValue() internal void OnPropertyChanged(XamlProperty property) { XamlObject holder; if (!UpdateXmlAttribute(false, out holder)) { if (holder != null && holder.XmlAttribute != null) { holder.XmlAttribute.OwnerElement.RemoveAttributeNode(holder.XmlAttribute); holder.xmlAttribute = null; holder.ParentProperty.AddChildNodeToProperty(holder.element); bool isThisUpdated = false; foreach(XamlObject propXamlObject in holder.Properties.Where((prop) => prop.IsSet).Select((prop) => prop.PropertyValue).OfType()) { XamlObject innerHolder; bool updateResult = propXamlObject.UpdateXmlAttribute(true, out innerHolder); Debug.Assert(updateResult || innerHolder == null); if (propXamlObject == this) isThisUpdated = true; } if (!isThisUpdated) this.UpdateXmlAttribute(true, out holder); } } UpdateMarkupExtensionChain(); if (!element.HasChildNodes && !element.IsEmpty) { element.IsEmpty = true; } if (property == NameProperty) { if (NameChanged != null) NameChanged(this, EventArgs.Empty); } } void UpdateChildMarkupExtensions(XamlObject obj) { foreach (var prop in obj.Properties) { if (prop.IsSet) { var propXamlObject = prop.PropertyValue as XamlObject; if (propXamlObject != null) { UpdateChildMarkupExtensions(propXamlObject); } } else if (prop.IsCollection) { foreach (var propXamlObject in prop.CollectionElements.OfType()) { UpdateChildMarkupExtensions(propXamlObject); } } } if (obj.IsMarkupExtension && obj.ParentProperty != null) { obj.ParentProperty.UpdateValueOnInstance(); } } void UpdateMarkupExtensionChain() { UpdateChildMarkupExtensions(this); var obj = this.ParentObject; while (obj != null && obj.IsMarkupExtension && obj.ParentProperty != null) { obj.ParentProperty.UpdateValueOnInstance(); obj = obj.ParentObject; } } bool UpdateXmlAttribute(bool force, out XamlObject holder) { holder = FindXmlAttributeHolder(); if (holder == null && force && IsMarkupExtension) { holder = this; } if (holder != null && MarkupExtensionPrinter.CanPrint(holder)) { var s = MarkupExtensionPrinter.Print(holder); holder.XmlAttribute = holder.ParentProperty.SetAttribute(s); return true; } return false; } XamlObject FindXmlAttributeHolder() { var obj = this; while (obj != null && obj.IsMarkupExtension) { if (obj.XmlAttribute != null) { return obj; } obj = obj.ParentObject; } return null; } /// /// Gets the XamlDocument where this XamlObject is declared in. /// public XamlDocument OwnerDocument { get { return document; } } /// /// Gets the instance created by this object element. /// public object Instance { get { return instance; } } /// /// Gets whether this instance represents a MarkupExtension. /// public bool IsMarkupExtension { get { return instance is MarkupExtension; } } /// /// Gets whether there were load errors for this object. /// public bool HasErrors { get; internal set; } /// /// Gets the type of this object element. /// public Type ElementType { get { return elementType; } } /// /// Gets a read-only collection of properties set on this XamlObject. /// This includes both attribute and element properties. /// public IList Properties { get { return properties.AsReadOnly(); } } /// /// Gets the name of the content property. /// public string ContentPropertyName { get { return contentPropertyName; } } /// /// Gets which property name of the type maps to the XAML x:Name attribute. /// public string RuntimeNameProperty { get { return runtimeNameProperty; } } /// /// Gets which property of the type maps to the XAML x:Name attribute. /// public XamlProperty NameProperty { get { if(nameProperty == null && runtimeNameProperty != null) nameProperty = FindOrCreateProperty(runtimeNameProperty); return nameProperty; } } /// /// Gets/Sets the name of this XamlObject. /// public string Name { get { string name = GetXamlAttribute("Name"); if (String.IsNullOrEmpty(name)) { if (NameProperty != null && NameProperty.IsSet) name = (string)NameProperty.ValueOnInstance; } if (name == String.Empty) name = null; return name; } set { if (String.IsNullOrEmpty(value)) this.SetXamlAttribute("Name", null); else this.SetXamlAttribute("Name", value); } } /// /// Finds the specified property, or creates it if it doesn't exist. /// public XamlProperty FindOrCreateProperty(string propertyName) { if (propertyName == null) throw new ArgumentNullException("propertyName"); // if (propertyName == ContentPropertyName) // return foreach (XamlProperty p in properties) { if (!p.IsAttached && p.PropertyName == propertyName) return p; } PropertyDescriptorCollection propertyDescriptors = TypeDescriptor.GetProperties(instance); PropertyDescriptor propertyInfo = propertyDescriptors[propertyName]; XamlProperty newProperty; if (propertyInfo == null) { propertyDescriptors = TypeDescriptor.GetProperties(this.elementType); propertyInfo = propertyDescriptors[propertyName]; } if (propertyInfo != null) { newProperty = new XamlProperty(this, new XamlNormalPropertyInfo(propertyInfo)); } else { EventDescriptorCollection events = TypeDescriptor.GetEvents(instance); EventDescriptor eventInfo = events[propertyName]; if (eventInfo == null) { events = TypeDescriptor.GetEvents(this.elementType); eventInfo = events[propertyName]; } if (eventInfo != null) { newProperty = new XamlProperty(this, new XamlEventPropertyInfo(eventInfo)); } else { throw new ArgumentException("The property '" + propertyName + "' doesn't exist on " + elementType.FullName, "propertyName"); } } properties.Add(newProperty); return newProperty; } /// /// Finds the specified property, or creates it if it doesn't exist. /// public XamlProperty FindOrCreateAttachedProperty(Type ownerType, string propertyName) { if (ownerType == null) throw new ArgumentNullException("ownerType"); if (propertyName == null) throw new ArgumentNullException("propertyName"); foreach (XamlProperty p in properties) { if (p.IsAttached && p.PropertyTargetType == ownerType && p.PropertyName == propertyName) return p; } XamlPropertyInfo info = XamlParser.TryFindAttachedProperty(ownerType, propertyName); if (info == null) { throw new ArgumentException("The attached property '" + propertyName + "' doesn't exist on " + ownerType.FullName, "propertyName"); } XamlProperty newProperty = new XamlProperty(this, info); properties.Add(newProperty); return newProperty; } /// /// Gets an attribute in the x:-namespace. /// public string GetXamlAttribute(string name) { var value = element.GetAttribute(name, XamlConstants.XamlNamespace); if (string.IsNullOrEmpty(value)) { value = element.GetAttribute(name, XamlConstants.Xaml2009Namespace); } return value; } /// /// Sets an attribute in the x:-namespace. /// public void SetXamlAttribute(string name, string value) { XamlProperty runtimeNameProperty = null; bool isNameChange = false; if (name == "Name") { isNameChange = true; string oldName = GetXamlAttribute("Name"); if (String.IsNullOrEmpty(oldName)) { runtimeNameProperty = this.NameProperty; if (runtimeNameProperty != null) { if (runtimeNameProperty.IsSet) oldName = (string)runtimeNameProperty.ValueOnInstance; else runtimeNameProperty = null; } } if (String.IsNullOrEmpty(oldName)) oldName = null; NameScopeHelper.NameChanged(this, oldName, value); } if (value == null) element.RemoveAttribute(name, XamlConstants.XamlNamespace); else { var prefix = element.GetPrefixOfNamespace(XamlConstants.XamlNamespace); var prefix2009 = element.GetPrefixOfNamespace(XamlConstants.Xaml2009Namespace); if (!string.IsNullOrEmpty(prefix)) { var attribute = element.OwnerDocument.CreateAttribute(prefix, name, XamlConstants.XamlNamespace); attribute.InnerText = value; element.SetAttributeNode(attribute); } else if (!string.IsNullOrEmpty(prefix2009)) { var attribute = element.OwnerDocument.CreateAttribute(prefix, name, XamlConstants.Xaml2009Namespace); attribute.InnerText = value; element.SetAttributeNode(attribute); } else element.SetAttribute(name, XamlConstants.XamlNamespace, value); } if (isNameChange) { bool nameChangedAlreadyRaised = false; if (runtimeNameProperty != null) { var handler = new EventHandler((sender, e) => nameChangedAlreadyRaised = true); this.NameChanged += handler; try { runtimeNameProperty.Reset(); } finally { this.NameChanged -= handler; } } if (NameChanged != null && !nameChangedAlreadyRaised) NameChanged(this, EventArgs.Empty); } } /// /// Gets/Sets the associated with this XamlObject. /// public XamlObjectServiceProvider ServiceProvider { get; set; } MarkupExtensionWrapper wrapper; void CreateWrapper() { if (Instance is BindingBase) { wrapper = new BindingWrapper(this); } else if (Instance is StaticResourceExtension) { wrapper = new StaticResourceWrapper(this); } if (wrapper == null && IsMarkupExtension) { var markupExtensionWrapperAttribute = Instance.GetType().GetCustomAttributes(typeof(MarkupExtensionWrapperAttribute), false).FirstOrDefault() as MarkupExtensionWrapperAttribute; if(markupExtensionWrapperAttribute != null) { wrapper = MarkupExtensionWrapper.CreateWrapper(markupExtensionWrapperAttribute.MarkupExtensionWrapperType, this); } else { wrapper = MarkupExtensionWrapper.TryCreateWrapper(Instance.GetType(), this); } } } object ProvideValue() { if (wrapper != null) { return wrapper.ProvideValue(); } if (this.ParentObject != null && this.ParentObject.ElementType == typeof (Setter) && this.ElementType == typeof(DynamicResourceExtension)) return Instance; return (Instance as MarkupExtension).ProvideValue(ServiceProvider); } internal string GetNameForMarkupExtension() { string markupExtensionName = XmlElement.Name; // By convention a markup extension class name typically includes an "Extension" suffix. // When you reference the markup extension in XAML the "Extension" suffix is optional. // If present remove it to avoid bloating the XAML. if (markupExtensionName.EndsWith("Extension", StringComparison.Ordinal)) { markupExtensionName = markupExtensionName.Substring(0, markupExtensionName.Length - 9); } return markupExtensionName; } /// /// Is raised when the name of this XamlObject changes. /// public event EventHandler NameChanged; } class BindingWrapper : MarkupExtensionWrapper { public BindingWrapper(XamlObject xamlObject) : base(xamlObject) { } public override object ProvideValue() { var target = XamlObject.Instance as BindingBase; Debug.Assert(target != null); //TODO: XamlObject.Clone() var b = CopyBinding(target); return b.ProvideValue(XamlObject.ServiceProvider); } BindingBase CopyBinding(BindingBase target) { BindingBase b; if (target != null) { b = (BindingBase)Activator.CreateInstance(target.GetType()); } else { b = new Binding(); } foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(target)) { if (pd.IsReadOnly) { if (pd.Name.Equals("Bindings", StringComparison.Ordinal)) { var bindings = (Collection)pd.GetValue(target); var newBindings = (Collection)pd.GetValue(b); foreach (var binding in bindings) { newBindings.Add(CopyBinding(binding)); } } continue; } try { var val1 = pd.GetValue(b); var val2 = pd.GetValue(target); if (object.Equals(val1, val2)) continue; pd.SetValue(b, val2); } catch {} } return b; } } class StaticResourceWrapper : MarkupExtensionWrapper { public StaticResourceWrapper(XamlObject xamlObject) : base(xamlObject) { } public override object ProvideValue() { var target = XamlObject.Instance as StaticResourceExtension; return XamlObject.ServiceProvider.Resolver.FindResource(target.ResourceKey); } } }