You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
403 lines
11 KiB
403 lines
11 KiB
// 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.Windows.Markup; |
|
using System.Xml; |
|
using System.Windows.Data; |
|
using System.Windows; |
|
|
|
namespace ICSharpCode.WpfDesign.XamlDom |
|
{ |
|
/// <summary> |
|
/// Represents a xaml object element. |
|
/// </summary> |
|
[DebuggerDisplay("XamlObject: {Instance}")] |
|
public sealed class XamlObject : XamlPropertyValue |
|
{ |
|
XamlDocument document; |
|
XmlElement element; |
|
Type elementType; |
|
object instance; |
|
List<XamlProperty> properties = new List<XamlProperty>(); |
|
|
|
/// <summary>For use by XamlParser only.</summary> |
|
internal XamlObject(XamlDocument document, XmlElement element, Type elementType, object instance) |
|
{ |
|
this.document = document; |
|
this.element = element; |
|
this.elementType = elementType; |
|
this.instance = instance; |
|
|
|
var contentAttrs = elementType.GetCustomAttributes(typeof(ContentPropertyAttribute), true) as ContentPropertyAttribute[]; |
|
if (contentAttrs != null && contentAttrs.Length > 0) { |
|
this.contentPropertyName = contentAttrs[0].Name; |
|
} |
|
|
|
ServiceProvider = new XamlObjectServiceProvider(this); |
|
CreateWrapper(); |
|
} |
|
|
|
/// <summary>For use by XamlParser only.</summary> |
|
internal void AddProperty(XamlProperty property) |
|
{ |
|
#if DEBUG |
|
if (property.IsAttached == false) { |
|
foreach (XamlProperty p in properties) { |
|
if (p.IsAttached == false && p.PropertyName == property.PropertyName) |
|
Debug.Fail("duplicate property"); |
|
} |
|
} |
|
#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; |
|
|
|
/// <summary> |
|
/// Gets the parent object. |
|
/// </summary> |
|
public XamlObject ParentObject { |
|
get { |
|
return parentObject; |
|
} |
|
internal set { parentObject = value; } |
|
} |
|
|
|
internal override void OnParentPropertyChanged() |
|
{ |
|
parentObject = (ParentProperty != null) ? ParentProperty.ParentObject : null; |
|
base.OnParentPropertyChanged(); |
|
} |
|
|
|
internal XmlElement XmlElement { |
|
get { return element; } |
|
} |
|
|
|
XmlAttribute xmlAttribute; |
|
|
|
internal XmlAttribute XmlAttribute { |
|
get { return xmlAttribute; } |
|
set { |
|
xmlAttribute = value; |
|
element = VirualAttachTo(XmlElement, value.OwnerElement); |
|
} |
|
} |
|
|
|
static XmlElement VirualAttachTo(XmlElement e, XmlElement target) |
|
{ |
|
var prefix = target.GetPrefixOfNamespace(e.NamespaceURI); |
|
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; |
|
} |
|
|
|
internal override void AddNodeTo(XamlProperty property) |
|
{ |
|
if (!UpdateXmlAttribute(true)) { |
|
property.AddChildNodeToProperty(element); |
|
} |
|
UpdateMarkupExtensionChain(); |
|
} |
|
|
|
internal override void RemoveNodeFromParent() |
|
{ |
|
if (XmlAttribute != null) { |
|
XmlAttribute.OwnerElement.RemoveAttribute(XmlAttribute.Name); |
|
xmlAttribute = null; |
|
} else { |
|
if (!UpdateXmlAttribute(false)) { |
|
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) |
|
{ |
|
UpdateXmlAttribute(false); |
|
UpdateMarkupExtensionChain(); |
|
} |
|
|
|
void UpdateMarkupExtensionChain() |
|
{ |
|
var obj = this; |
|
while (obj != null && obj.IsMarkupExtension) { |
|
obj.ParentProperty.UpdateValueOnInstance(); |
|
obj = obj.ParentObject; |
|
} |
|
} |
|
|
|
bool UpdateXmlAttribute(bool force) |
|
{ |
|
var 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; |
|
} |
|
|
|
/// <summary> |
|
/// Gets the XamlDocument where this XamlObject is declared in. |
|
/// </summary> |
|
public XamlDocument OwnerDocument { |
|
get { return document; } |
|
} |
|
|
|
/// <summary> |
|
/// Gets the instance created by this object element. |
|
/// </summary> |
|
public object Instance { |
|
get { return instance; } |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether this instance represents a MarkupExtension. |
|
/// </summary> |
|
public bool IsMarkupExtension { |
|
get { return instance is MarkupExtension; } |
|
} |
|
|
|
/// <summary> |
|
/// Gets whether there were load errors for this object. |
|
/// </summary> |
|
public bool HasErrors { get; internal set; } |
|
|
|
/// <summary> |
|
/// Gets the type of this object element. |
|
/// </summary> |
|
public Type ElementType { |
|
get { return elementType; } |
|
} |
|
|
|
/// <summary> |
|
/// Gets a read-only collection of properties set on this XamlObject. |
|
/// This includes both attribute and element properties. |
|
/// </summary> |
|
public IList<XamlProperty> Properties { |
|
get { |
|
return properties.AsReadOnly(); |
|
} |
|
} |
|
|
|
string contentPropertyName; |
|
|
|
/// <summary> |
|
/// Gets the name of the content property. |
|
/// </summary> |
|
public string ContentPropertyName { |
|
get { |
|
return contentPropertyName; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Finds the specified property, or creates it if it doesn't exist. |
|
/// </summary> |
|
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) { |
|
newProperty = new XamlProperty(this, new XamlNormalPropertyInfo(propertyInfo)); |
|
} else { |
|
EventDescriptorCollection events = TypeDescriptor.GetEvents(instance); |
|
EventDescriptor 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; |
|
} |
|
|
|
/// <summary> |
|
/// Finds the specified property, or creates it if it doesn't exist. |
|
/// </summary> |
|
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; |
|
} |
|
|
|
/// <summary> |
|
/// Gets an attribute in the x:-namespace. |
|
/// </summary> |
|
public string GetXamlAttribute(string name) |
|
{ |
|
return element.GetAttribute(name, XamlConstants.XamlNamespace); |
|
} |
|
|
|
/// <summary> |
|
/// Sets an attribute in the x:-namespace. |
|
/// </summary> |
|
public void SetXamlAttribute(string name, string value) |
|
{ |
|
if (value == null) |
|
element.RemoveAttribute(name, XamlConstants.XamlNamespace); |
|
else |
|
element.SetAttribute(name, XamlConstants.XamlNamespace, value); |
|
} |
|
|
|
/// <summary> |
|
/// Gets/Sets the <see cref="XamlObjectServiceProvider"/> associated with this XamlObject. |
|
/// </summary> |
|
public XamlObjectServiceProvider ServiceProvider { get; set; } |
|
|
|
MarkupExtensionWrapper wrapper; |
|
|
|
void CreateWrapper() |
|
{ |
|
if (Instance is Binding) { |
|
wrapper = new BindingWrapper(); |
|
} else if (Instance is StaticResourceExtension) { |
|
wrapper = new StaticResourceWrapper(); |
|
} |
|
if (wrapper != null) { |
|
wrapper.XamlObject = this; |
|
} |
|
} |
|
|
|
object ProvideValue() |
|
{ |
|
if (wrapper != null) { |
|
return wrapper.ProvideValue(); |
|
} |
|
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; |
|
} |
|
} |
|
|
|
abstract class MarkupExtensionWrapper |
|
{ |
|
public XamlObject XamlObject { get; set; } |
|
public abstract object ProvideValue(); |
|
} |
|
|
|
class BindingWrapper : MarkupExtensionWrapper |
|
{ |
|
public override object ProvideValue() |
|
{ |
|
var target = XamlObject.Instance as Binding; |
|
//TODO: XamlObject.Clone() |
|
var b = new Binding(); |
|
foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(target)) { |
|
if (pd.IsReadOnly) 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.ProvideValue(XamlObject.ServiceProvider); |
|
} |
|
} |
|
|
|
class StaticResourceWrapper : MarkupExtensionWrapper |
|
{ |
|
public override object ProvideValue() |
|
{ |
|
var target = XamlObject.Instance as StaticResourceExtension; |
|
return XamlObject.ServiceProvider.Resolver.FindResource(target.ResourceKey); |
|
} |
|
} |
|
}
|
|
|