Add TypeDescriptionProviders to XLinq (dotnet/corefx#33082)
authorEric StJohn <ericstj@microsoft.com>
Mon, 29 Oct 2018 23:35:00 +0000 (16:35 -0700)
committerGitHub <noreply@github.com>
Mon, 29 Oct 2018 23:35:00 +0000 (16:35 -0700)
* Bring over XComponentModel code from desktop

* Format code and add License.

* Add TypeDescriptionProviders to XLinq

This adds TypeDescriptionProviders to XAttribute and XElement

To do this without introducing a TypeConverter dependency in XML I had to push
TypeDescriptionProviderAttribute down.

This still causes TypeConverter to gain a dependency on XML, but right now that's the direction
we've been heading with TypeConverter.

I had to use the string overload of TypeDescriptionProviderAttribute in order to have a soft
dependency on TypeConverter.

* Format XComponentModel source for access / naming

* Remove TypeDescriptionProvider from reference assemblies

I decided to remove this from the reference assemblies.  None  of the usages I found would require
presense in the reference assemblies and it would expose too much internal surface which we might
want to change in the future (type name, and assembly).

Commit migrated from https://github.com/dotnet/corefx/commit/a01c44c78d37267c4e30cb1d647376e103557493

13 files changed:
src/libraries/System.ComponentModel.TypeConverter/ref/System.ComponentModel.TypeConverter.cs
src/libraries/System.ComponentModel.TypeConverter/src/ILLinkTrim.xml
src/libraries/System.ComponentModel.TypeConverter/src/MS/Internal/Xml/Linq/ComponentModel/XComponentModel.cs [new file with mode: 0644]
src/libraries/System.ComponentModel.TypeConverter/src/System.ComponentModel.TypeConverter.csproj
src/libraries/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj
src/libraries/System.ComponentModel.TypeConverter/tests/XTypeDescriptionProviderTests.cs [new file with mode: 0644]
src/libraries/System.ObjectModel/ref/System.ObjectModel.cs
src/libraries/System.ObjectModel/src/System.ObjectModel.csproj
src/libraries/System.ObjectModel/src/System/ComponentModel/TypeDescriptionProviderAttribute.cs [moved from src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/TypeDescriptionProviderAttribute.cs with 100% similarity]
src/libraries/System.Private.Xml.Linq/src/System.Private.Xml.Linq.csproj
src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XAttribute.cs
src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XElement.cs
tools-local/DefaultGenApiDocIds.txt

index e733b30..70ad7c1 100644 (file)
@@ -14,6 +14,7 @@ using System.Runtime.Serialization;
 [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.ComponentModel.ISupportInitialize))]
 // moved to System.ObjectModel
 [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.ComponentModel.TypeConverterAttribute))]
+[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.ComponentModel.TypeDescriptionProviderAttribute))]
 
 namespace System
 {
@@ -583,13 +584,6 @@ namespace System.ComponentModel
         public virtual System.ComponentModel.ICustomTypeDescriptor GetTypeDescriptor(System.Type objectType, object instance) { throw null; }
         public virtual bool IsSupportedType(System.Type type) { throw null; }
     }
-    [System.AttributeUsageAttribute((System.AttributeTargets)(4), Inherited = true)]
-    public sealed partial class TypeDescriptionProviderAttribute : System.Attribute
-    {
-        public TypeDescriptionProviderAttribute(string typeName) { }
-        public TypeDescriptionProviderAttribute(System.Type type) { }
-        public string TypeName { get { throw null; } }
-    }
     public sealed partial class TypeDescriptor
     {
         internal TypeDescriptor() { }
index e9667cd..c771412 100644 (file)
@@ -1,5 +1,6 @@
 <linker>
   <assembly fullname="System.ComponentModel.TypeConverter">
+    <type fullname="MS.Internal.Xml.Linq.ComponentModel.*" />
     <type fullname="System.ComponentModel.TypeDescriptor">
       <!-- called through reflection by System.ComponentModel.DefaultValueAttribute -->
       <method name="ConvertFromInvariantString" />
diff --git a/src/libraries/System.ComponentModel.TypeConverter/src/MS/Internal/Xml/Linq/ComponentModel/XComponentModel.cs b/src/libraries/System.ComponentModel.TypeConverter/src/MS/Internal/Xml/Linq/ComponentModel/XComponentModel.cs
new file mode 100644 (file)
index 0000000..118a432
--- /dev/null
@@ -0,0 +1,576 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Xml.Linq;
+
+namespace MS.Internal.Xml.Linq.ComponentModel
+{
+    internal class XTypeDescriptionProvider<T> : TypeDescriptionProvider
+    {
+        public XTypeDescriptionProvider() : base(TypeDescriptor.GetProvider(typeof(T)))
+        {
+        }
+
+        public override ICustomTypeDescriptor GetTypeDescriptor(Type type, object instance)
+        {
+            return new XTypeDescriptor<T>(base.GetTypeDescriptor(type, instance));
+        }
+    }
+
+    internal class XTypeDescriptor<T> : CustomTypeDescriptor
+    {
+        public XTypeDescriptor(ICustomTypeDescriptor parent) : base(parent)
+        {
+        }
+
+        public override PropertyDescriptorCollection GetProperties()
+        {
+            return GetProperties(null);
+        }
+
+        public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
+        {
+            PropertyDescriptorCollection properties = new PropertyDescriptorCollection(null);
+            if (attributes == null)
+            {
+                if (typeof(T) == typeof(XElement))
+                {
+                    properties.Add(new XElementAttributePropertyDescriptor());
+                    properties.Add(new XElementDescendantsPropertyDescriptor());
+                    properties.Add(new XElementElementPropertyDescriptor());
+                    properties.Add(new XElementElementsPropertyDescriptor());
+                    properties.Add(new XElementValuePropertyDescriptor());
+                    properties.Add(new XElementXmlPropertyDescriptor());
+                }
+                else if (typeof(T) == typeof(XAttribute))
+                {
+                    properties.Add(new XAttributeValuePropertyDescriptor());
+                }
+            }
+            foreach (PropertyDescriptor property in base.GetProperties(attributes))
+            {
+                properties.Add(property);
+            }
+            return properties;
+        }
+    }
+
+    internal abstract class XPropertyDescriptor<T, TProperty> : PropertyDescriptor where T : XObject
+    {
+        public XPropertyDescriptor(string name) : base(name, null)
+        {
+        }
+
+        public override Type ComponentType
+        {
+            get { return typeof(T); }
+        }
+
+        public override bool IsReadOnly
+        {
+            get { return true; }
+        }
+
+        public override Type PropertyType
+        {
+            get { return typeof(TProperty); }
+        }
+
+        public override bool SupportsChangeEvents
+        {
+            get { return true; }
+        }
+
+        public override void AddValueChanged(object component, EventHandler handler)
+        {
+            bool hasValueChangedHandler = GetValueChangedHandler(component) != null;
+            base.AddValueChanged(component, handler);
+            if (hasValueChangedHandler)
+                return;
+            T c = component as T;
+            if (c != null && GetValueChangedHandler(component) != null)
+            {
+                c.Changing += new EventHandler<XObjectChangeEventArgs>(OnChanging);
+                c.Changed += new EventHandler<XObjectChangeEventArgs>(OnChanged);
+            }
+        }
+
+        public override bool CanResetValue(object component)
+        {
+            return false;
+        }
+
+        public override void RemoveValueChanged(object component, EventHandler handler)
+        {
+            base.RemoveValueChanged(component, handler);
+            T c = component as T;
+            if (c != null && GetValueChangedHandler(component) == null)
+            {
+                c.Changing -= new EventHandler<XObjectChangeEventArgs>(OnChanging);
+                c.Changed -= new EventHandler<XObjectChangeEventArgs>(OnChanged);
+            }
+        }
+
+        public override void ResetValue(object component)
+        {
+        }
+
+        public override void SetValue(object component, object value)
+        {
+        }
+
+        public override bool ShouldSerializeValue(object component)
+        {
+            return false;
+        }
+
+        protected virtual void OnChanged(object sender, XObjectChangeEventArgs args)
+        {
+        }
+
+        protected virtual void OnChanging(object sender, XObjectChangeEventArgs args)
+        {
+        }
+    }
+
+    internal class XElementAttributePropertyDescriptor : XPropertyDescriptor<XElement, object>
+    {
+        private XDeferredSingleton<XAttribute> _value;
+        private XAttribute _changeState;
+
+        public XElementAttributePropertyDescriptor() : base("Attribute")
+        {
+        }
+
+        public override object GetValue(object component)
+        {
+            return _value = new XDeferredSingleton<XAttribute>((e, n) => e.Attribute(n), component as XElement, null);
+        }
+
+        protected override void OnChanged(object sender, XObjectChangeEventArgs args)
+        {
+            if (_value == null)
+                return;
+            switch (args.ObjectChange)
+            {
+                case XObjectChange.Add:
+                    XAttribute a = sender as XAttribute;
+                    if (a != null && _value.element == a.Parent && _value.name == a.Name)
+                    {
+                        OnValueChanged(_value.element, EventArgs.Empty);
+                    }
+                    break;
+                case XObjectChange.Remove:
+                    a = sender as XAttribute;
+                    if (a != null && _changeState == a)
+                    {
+                        _changeState = null;
+                        OnValueChanged(_value.element, EventArgs.Empty);
+                    }
+                    break;
+            }
+        }
+
+        protected override void OnChanging(object sender, XObjectChangeEventArgs args)
+        {
+            if (_value == null)
+                return;
+            switch (args.ObjectChange)
+            {
+                case XObjectChange.Remove:
+                    XAttribute a = sender as XAttribute;
+                    _changeState = a != null && _value.element == a.Parent && _value.name == a.Name ? a : null;
+                    break;
+            }
+        }
+    }
+
+    internal class XElementDescendantsPropertyDescriptor : XPropertyDescriptor<XElement, IEnumerable<XElement>>
+    {
+        private XDeferredAxis<XElement> _value;
+        private XName _changeState;
+
+        public XElementDescendantsPropertyDescriptor() : base("Descendants")
+        {
+        }
+
+        public override object GetValue(object component)
+        {
+            return _value = new XDeferredAxis<XElement>((e, n) => n != null ? e.Descendants(n) : e.Descendants(), component as XElement, null);
+        }
+
+        protected override void OnChanged(object sender, XObjectChangeEventArgs args)
+        {
+            if (_value == null)
+                return;
+            switch (args.ObjectChange)
+            {
+                case XObjectChange.Add:
+                case XObjectChange.Remove:
+                    XElement e = sender as XElement;
+                    if (e != null && (_value.name == e.Name || _value.name == null))
+                    {
+                        OnValueChanged(_value.element, EventArgs.Empty);
+                    }
+                    break;
+                case XObjectChange.Name:
+                    e = sender as XElement;
+                    if (e != null && _value.element != e && _value.name != null && (_value.name == e.Name || _value.name == _changeState))
+                    {
+                        _changeState = null;
+                        OnValueChanged(_value.element, EventArgs.Empty);
+                    }
+                    break;
+            }
+        }
+
+        protected override void OnChanging(object sender, XObjectChangeEventArgs args)
+        {
+            if (_value == null)
+                return;
+            switch (args.ObjectChange)
+            {
+                case XObjectChange.Name:
+                    XElement e = sender as XElement;
+                    _changeState = e != null ? e.Name : null;
+                    break;
+            }
+        }
+    }
+
+    internal class XElementElementPropertyDescriptor : XPropertyDescriptor<XElement, object>
+    {
+        private XDeferredSingleton<XElement> _value;
+        private XElement _changeState;
+
+        public XElementElementPropertyDescriptor() : base("Element")
+        {
+        }
+
+        public override object GetValue(object component)
+        {
+            return _value = new XDeferredSingleton<XElement>((e, n) => e.Element(n), component as XElement, null);
+        }
+
+        protected override void OnChanged(object sender, XObjectChangeEventArgs args)
+        {
+            if (_value == null)
+                return;
+            switch (args.ObjectChange)
+            {
+                case XObjectChange.Add:
+                    XElement e = sender as XElement;
+                    if (e != null && _value.element == e.Parent && _value.name == e.Name && _value.element.Element(_value.name) == e)
+                    {
+                        OnValueChanged(_value.element, EventArgs.Empty);
+                    }
+                    break;
+                case XObjectChange.Remove:
+                    e = sender as XElement;
+                    if (e != null && _changeState == e)
+                    {
+                        _changeState = null;
+                        OnValueChanged(_value.element, EventArgs.Empty);
+                    }
+                    break;
+                case XObjectChange.Name:
+                    e = sender as XElement;
+                    if (e != null)
+                    {
+                        if (_value.element == e.Parent && _value.name == e.Name && _value.element.Element(_value.name) == e)
+                        {
+                            OnValueChanged(_value.element, EventArgs.Empty);
+                        }
+                        else if (_changeState == e)
+                        {
+                            _changeState = null;
+                            OnValueChanged(_value.element, EventArgs.Empty);
+                        }
+                    }
+                    break;
+            }
+        }
+
+        protected override void OnChanging(object sender, XObjectChangeEventArgs args)
+        {
+            if (_value == null)
+                return;
+            switch (args.ObjectChange)
+            {
+                case XObjectChange.Remove:
+                case XObjectChange.Name:
+                    XElement e = sender as XElement;
+                    _changeState = e != null && _value.element == e.Parent && _value.name == e.Name && _value.element.Element(_value.name) == e ? e : null;
+                    break;
+            }
+        }
+    }
+
+    internal class XElementElementsPropertyDescriptor : XPropertyDescriptor<XElement, IEnumerable<XElement>>
+    {
+        private XDeferredAxis<XElement> _value;
+        private object _changeState;
+
+        public XElementElementsPropertyDescriptor() : base("Elements")
+        {
+        }
+
+        public override object GetValue(object component)
+        {
+            return _value = new XDeferredAxis<XElement>((e, n) => n != null ? e.Elements(n) : e.Elements(), component as XElement, null);
+        }
+
+        protected override void OnChanged(object sender, XObjectChangeEventArgs args)
+        {
+            if (_value == null)
+                return;
+            switch (args.ObjectChange)
+            {
+                case XObjectChange.Add:
+                    XElement e = sender as XElement;
+                    if (e != null && _value.element == e.Parent && (_value.name == e.Name || _value.name == null))
+                    {
+                        OnValueChanged(_value.element, EventArgs.Empty);
+                    }
+                    break;
+                case XObjectChange.Remove:
+                    e = sender as XElement;
+                    if (e != null && _value.element == (_changeState as XContainer) && (_value.name == e.Name || _value.name == null))
+                    {
+                        _changeState = null;
+                        OnValueChanged(_value.element, EventArgs.Empty);
+                    }
+                    break;
+                case XObjectChange.Name:
+                    e = sender as XElement;
+                    if (e != null && _value.element == e.Parent && _value.name != null && (_value.name == e.Name || _value.name == (_changeState as XName)))
+                    {
+                        _changeState = null;
+                        OnValueChanged(_value.element, EventArgs.Empty);
+                    }
+                    break;
+            }
+        }
+
+        protected override void OnChanging(object sender, XObjectChangeEventArgs args)
+        {
+            if (_value == null)
+                return;
+            switch (args.ObjectChange)
+            {
+                case XObjectChange.Remove:
+                    XElement e = sender as XElement;
+                    _changeState = e != null ? e.Parent : null;
+                    break;
+                case XObjectChange.Name:
+                    e = sender as XElement;
+                    _changeState = e != null ? e.Name : null;
+                    break;
+            }
+        }
+    }
+
+    internal class XElementValuePropertyDescriptor : XPropertyDescriptor<XElement, string>
+    {
+        private XElement _element;
+
+        public XElementValuePropertyDescriptor() : base("Value")
+        {
+        }
+
+        public override bool IsReadOnly
+        {
+            get { return false; }
+        }
+
+        public override object GetValue(object component)
+        {
+            _element = component as XElement;
+            if (_element == null)
+                return string.Empty;
+            return _element.Value;
+        }
+
+        public override void SetValue(object component, object value)
+        {
+            _element = component as XElement;
+            if (_element == null)
+                return;
+            _element.Value = value as string;
+        }
+
+        protected override void OnChanged(object sender, XObjectChangeEventArgs args)
+        {
+            if (_element == null)
+                return;
+            switch (args.ObjectChange)
+            {
+                case XObjectChange.Add:
+                case XObjectChange.Remove:
+                    if (sender is XElement || sender is XText)
+                    {
+                        OnValueChanged(_element, EventArgs.Empty);
+                    }
+                    break;
+                case XObjectChange.Value:
+                    if (sender is XText)
+                    {
+                        OnValueChanged(_element, EventArgs.Empty);
+                    }
+                    break;
+            }
+        }
+    }
+
+    internal class XElementXmlPropertyDescriptor : XPropertyDescriptor<XElement, string>
+    {
+        private XElement _element;
+
+        public XElementXmlPropertyDescriptor() : base("Xml")
+        {
+        }
+
+        public override object GetValue(object component)
+        {
+            _element = component as XElement;
+            if (_element == null)
+                return string.Empty;
+            return _element.ToString(SaveOptions.DisableFormatting);
+        }
+
+        protected override void OnChanged(object sender, XObjectChangeEventArgs args)
+        {
+            if (_element == null)
+                return;
+            OnValueChanged(_element, EventArgs.Empty);
+        }
+    }
+
+    internal class XAttributeValuePropertyDescriptor : XPropertyDescriptor<XAttribute, string>
+    {
+        private XAttribute _attribute;
+
+        public XAttributeValuePropertyDescriptor() : base("Value")
+        {
+        }
+
+        public override bool IsReadOnly
+        {
+            get { return false; }
+        }
+
+        public override object GetValue(object component)
+        {
+            _attribute = component as XAttribute;
+            if (_attribute == null)
+                return string.Empty;
+            return _attribute.Value;
+        }
+
+        public override void SetValue(object component, object value)
+        {
+            _attribute = component as XAttribute;
+            if (_attribute == null)
+                return;
+            _attribute.Value = value as string;
+        }
+
+        protected override void OnChanged(object sender, XObjectChangeEventArgs args)
+        {
+            if (_attribute == null)
+                return;
+            if (args.ObjectChange == XObjectChange.Value)
+            {
+                OnValueChanged(_attribute, EventArgs.Empty);
+            }
+        }
+    }
+
+    internal class XDeferredAxis<T> : IEnumerable<T>, IEnumerable where T : XObject
+    {
+        private Func<XElement, XName, IEnumerable<T>> _func;
+        internal XElement element;
+        internal XName name;
+
+        public XDeferredAxis(Func<XElement, XName, IEnumerable<T>> func, XElement element, XName name)
+        {
+            if (func == null)
+                throw new ArgumentNullException("func");
+            if (element == null)
+                throw new ArgumentNullException("element");
+            _func = func;
+            this.element = element;
+            this.name = name;
+        }
+
+        public IEnumerator<T> GetEnumerator()
+        {
+            return _func(element, name).GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        public IEnumerable<T> this[string expandedName]
+        {
+            get
+            {
+                if (expandedName == null)
+                    throw new ArgumentNullException("expandedName");
+                if (name == null)
+                {
+                    name = expandedName;
+                }
+                else if (name != expandedName)
+                {
+                    return Enumerable.Empty<T>();
+                }
+                return this;
+            }
+        }
+    }
+
+    internal class XDeferredSingleton<T> where T : XObject
+    {
+        private Func<XElement, XName, T> _func;
+        internal XElement element;
+        internal XName name;
+
+        public XDeferredSingleton(Func<XElement, XName, T> func, XElement element, XName name)
+        {
+            if (func == null)
+                throw new ArgumentNullException("func");
+            if (element == null)
+                throw new ArgumentNullException("element");
+            _func = func;
+            this.element = element;
+            this.name = name;
+        }
+
+        public T this[string expandedName]
+        {
+            get
+            {
+                if (expandedName == null)
+                    throw new ArgumentNullException("expandedName");
+                if (name == null)
+                {
+                    name = expandedName;
+                }
+                else if (name != expandedName)
+                {
+                    return null;
+                }
+                return _func(element, name);
+            }
+        }
+    }
+}
index 4f26594..a6731dd 100644 (file)
@@ -10,6 +10,7 @@
     <Configurations>netcoreapp-Debug;netcoreapp-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release</Configurations>
   </PropertyGroup>
   <ItemGroup>
+    <Compile Include="MS\Internal\Xml\Linq\ComponentModel\XComponentModel.cs" />
     <Compile Include="System\ComponentModelSwitches.cs" />
     <Compile Include="System\ComponentModel\ArrayConverter.cs" />
     <Compile Include="System\ComponentModel\BaseNumberConverter.cs" />
@@ -80,7 +81,6 @@
     <Compile Include="System\ComponentModel\RefreshEventHandler.cs" />
     <Compile Include="System\ComponentModel\TypeDescriptor.cs" />
     <Compile Include="System\ComponentModel\TypeDescriptionProvider.cs" />
-    <Compile Include="System\ComponentModel\TypeDescriptionProviderAttribute.cs" />
     <Compile Include="System\ComponentModel\WeakHashtable.cs" />
     <Compile Include="System\ComponentModel\Design\IDictionaryService.cs" />
     <Compile Include="System\ComponentModel\Design\IExtenderListService.cs" />
     <Reference Include="System.Threading" />
     <Reference Include="System.Threading.Thread" />
     <Reference Include="System.Threading.Timer" />
+    <Reference Include="System.Xml.XDocument" />
   </ItemGroup>
 </Project>
\ No newline at end of file
index 87dfcc3..883fc44 100644 (file)
     <Compile Include="Design\DesignerOptionServiceTests.cs" />
     <Compile Include="Design\DesignerTransactionTests.cs" />
     <Compile Include="Security\Authentication\ExtendedProtection\ExtendedProtectionPolicyTypeConverterTests.cs" />
+    <Compile Include="XTypeDescriptionProviderTests.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(CommonTestPath)\System\Diagnostics\RemoteExecutorConsoleApp\RemoteExecutorConsoleApp.csproj">
diff --git a/src/libraries/System.ComponentModel.TypeConverter/tests/XTypeDescriptionProviderTests.cs b/src/libraries/System.ComponentModel.TypeConverter/tests/XTypeDescriptionProviderTests.cs
new file mode 100644 (file)
index 0000000..9bf3d16
--- /dev/null
@@ -0,0 +1,323 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using Xunit;
+
+namespace System.Xml.Linq.Tests
+{
+    public class XTypeDescriptionProviderTests
+    {
+        [Fact]
+        public void XAttributeValuePropertyDescriptor()
+        {
+            var xatt = new XAttribute("someAttribute", "someValue");
+            var props = TypeDescriptor.GetProperties(xatt);
+
+            var xattrPD = props["Value"];
+            Assert.NotNull(xattrPD);
+            Assert.False(xattrPD.IsReadOnly);
+            Assert.Equal(typeof(XAttribute), xattrPD.ComponentType);
+            Assert.Equal(typeof(string), xattrPD.PropertyType);
+            Assert.True(xattrPD.SupportsChangeEvents);
+            Assert.False(xattrPD.CanResetValue(xatt));
+            Assert.False(xattrPD.ShouldSerializeValue(xatt));
+            
+            Assert.Equal(xatt.Value, xattrPD.GetValue(xatt));
+
+            bool valueChanged = false;
+            xattrPD.AddValueChanged(xatt, (o, e) =>
+            {
+                valueChanged = true;
+            });
+            var newValue = "SomeNewValue";
+            xattrPD.SetValue(xatt, newValue);
+
+            Assert.True(valueChanged);
+            Assert.Equal(newValue, xatt.Value);
+        }
+        
+        [Fact]
+        public void XElementAttributePropertyDescriptor()
+        {
+            var xel = new XElement("someElement");
+            var props = TypeDescriptor.GetProperties(xel);
+            
+            var xelAttPD = props["Attribute"];
+            Assert.NotNull(xelAttPD);
+            Assert.True(xelAttPD.IsReadOnly);
+            Assert.Equal(typeof(XElement), xelAttPD.ComponentType);
+            Assert.Equal(typeof(object), xelAttPD.PropertyType);
+            Assert.True(xelAttPD.SupportsChangeEvents);
+            Assert.False(xelAttPD.CanResetValue(xel));
+            Assert.False(xelAttPD.ShouldSerializeValue(xel));
+            
+            bool valueChanged = false;
+            xelAttPD.AddValueChanged(xel, (o, e) =>
+            {
+                valueChanged = true;
+            });
+
+            var attr1 = new XAttribute("attr1", "value");
+            xel.Add(attr1);
+            Assert.False(valueChanged); // Cannot be triggered until one call to GetValue;
+
+            // value here is a private object, it has a single item indexer that returns an XAttribute from a name.
+            // once you call it once with a name it has the behavior of "binding" the value to that name
+            // so that changes to that name will trigger changed events.
+            // once bound it cannot be rebound to a different name.
+            // only one value (the latest) is tracked by the property descriptor.
+            object value = xelAttPD.GetValue(xel);
+
+            // not exposed, must use reflection to get at it?!
+            var getItemMethod = value.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+            Func<string, XAttribute> getAttribute = (name) => (XAttribute)getItemMethod.Invoke(value, new string[] { name });
+            
+            Assert.Equal(attr1, getAttribute(attr1.Name.ToString()));
+
+            attr1.Remove();
+            Assert.True(valueChanged);
+
+            // has been removed
+            Assert.Null(getAttribute(attr1.Name.ToString()));
+
+            valueChanged = false;
+            var attr2 = new XAttribute("attr2", "value2");
+            Assert.Null(getAttribute(attr2.Name.ToString()));
+            Assert.False(valueChanged);
+            xel.Add(attr2);
+            Assert.False(valueChanged);  // value is bound to attr1
+            xel.Add(attr1);
+            Assert.True(valueChanged);
+
+            attr2.Remove();
+            attr1.Remove();
+            valueChanged = false;
+
+            // get a new value that hasn't be bound
+            value = xelAttPD.GetValue(xel);
+            // bind it to attr2, which hasn't been added
+            Assert.Null(getAttribute(attr2.Name.ToString()));
+            Assert.False(valueChanged);
+            xel.Add(attr1);
+            Assert.False(valueChanged);
+            xel.Add(attr2);
+            Assert.True(valueChanged);
+            Assert.Equal(attr2, getAttribute(attr2.Name.ToString()));
+            Assert.Null(getAttribute(attr1.Name.ToString()));
+
+            valueChanged = false;
+            attr1.Remove();
+            Assert.False(valueChanged);
+            attr2.Remove();
+            Assert.True(valueChanged);
+        }
+        
+        [Fact]
+        public void XElementDescendantsPropertyDescriptor()
+        {
+            var xel = new XElement("someElement");
+            var props = TypeDescriptor.GetProperties(xel);
+            
+            var xelDesPD = props["Descendants"];
+            Assert.NotNull(xelDesPD);
+            Assert.True(xelDesPD.IsReadOnly);
+            Assert.Equal(typeof(XElement), xelDesPD.ComponentType);
+            Assert.Equal(typeof(IEnumerable<XElement>), xelDesPD.PropertyType);
+            Assert.True(xelDesPD.SupportsChangeEvents);
+            Assert.False(xelDesPD.CanResetValue(xel));
+            Assert.False(xelDesPD.ShouldSerializeValue(xel));
+
+            var dess = (IEnumerable<XElement>)xelDesPD.GetValue(xel);
+
+            bool valueChanged = false;
+            xelDesPD.AddValueChanged(xel, (o, e) =>
+            {
+                valueChanged = true;
+            });
+
+            xel.Add(new XElement("c1", new XElement("gc1", new XElement("ggc1"))), new XElement("c2"));
+            Assert.True(valueChanged);
+
+            Assert.Equal(dess, xel.Descendants());
+
+            valueChanged = false;
+            xel.Element("c1").Remove();
+            Assert.True(valueChanged);
+        }
+        
+        [Fact]
+        public void XElementElementPropertyDescriptor()
+        {
+            var xel = new XElement("someElement");
+            var props = TypeDescriptor.GetProperties(xel);
+            
+            var xelElPD = props["Element"];
+            Assert.True(xelElPD.IsReadOnly);
+            Assert.Equal(typeof(XElement), xelElPD.ComponentType);
+            Assert.Equal(typeof(object), xelElPD.PropertyType);
+            Assert.True(xelElPD.SupportsChangeEvents);
+            Assert.False(xelElPD.CanResetValue(xel));
+            Assert.False(xelElPD.ShouldSerializeValue(xel));
+
+            bool valueChanged = false;
+            xelElPD.AddValueChanged(xel, (o, e) =>
+            {
+                valueChanged = true;
+            });
+
+            var el1 = new XElement("el1");
+            xel.Add(el1);
+            Assert.False(valueChanged); // Cannot be triggered until one call to GetValue;
+
+            // value here is a private object, it has a single item indexer that returns an XAttribute from a name.
+            // once you call it once with a name it has the behavior of "binding" the value to that name
+            // so that changes to that name will trigger changed events.
+            // once bound it cannot be rebound to a different name.
+            // only one value (the latest) is tracked by the property descriptor.
+            object value = xelElPD.GetValue(xel);
+
+            // not exposed, must use reflection to get at it?!
+            var getItemMethod = value.GetType().GetMethod("get_Item", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
+            Func<string, XElement> getElement = (name) => (XElement)getItemMethod.Invoke(value, new string[] { name });
+
+            Assert.Equal(el1, getElement(el1.Name.ToString()));
+
+            el1.Remove();
+            Assert.True(valueChanged);
+
+            // has been removed
+            Assert.Null(getElement(el1.Name.ToString()));
+
+            valueChanged = false;
+            var el2 = new XElement("el2");
+            Assert.Null(getElement(el2.Name.ToString()));
+            Assert.False(valueChanged);
+            xel.Add(el2);
+            Assert.False(valueChanged);  // value is bound to attr1
+            xel.Add(el1);
+            Assert.True(valueChanged);
+
+            el2.Remove();
+            el1.Remove();
+            valueChanged = false;
+
+            // get a new value that hasn't be bound
+            value = xelElPD.GetValue(xel);
+            // bind it to attr2, which hasn't been added
+            Assert.Null(getElement(el2.Name.ToString()));
+            Assert.False(valueChanged);
+            xel.Add(el1);
+            Assert.False(valueChanged);
+            xel.Add(el2);
+            Assert.True(valueChanged);
+            Assert.Equal(el2, getElement(el2.Name.ToString()));
+            Assert.Null(getElement(el1.Name.ToString()));
+
+            valueChanged = false;
+            el1.Remove();
+            Assert.False(valueChanged);
+            el2.Remove();
+            Assert.True(valueChanged);
+        }
+        
+        [Fact]
+        public void XElementElementsPropertyDescriptor()
+        {
+            var xel = new XElement("someElement");
+            var props = TypeDescriptor.GetProperties(xel);
+            
+            var xelElsPD = props["Elements"];
+            Assert.NotNull(xelElsPD);
+            Assert.True(xelElsPD.IsReadOnly);
+            Assert.Equal(typeof(XElement), xelElsPD.ComponentType);
+            Assert.Equal(typeof(IEnumerable<XElement>), xelElsPD.PropertyType);
+            Assert.True(xelElsPD.SupportsChangeEvents);
+            Assert.False(xelElsPD.CanResetValue(xel));
+            Assert.False(xelElsPD.ShouldSerializeValue(xel));
+
+            var els = (IEnumerable<XElement>)xelElsPD.GetValue(xel);
+
+            bool valueChanged = false;
+            xelElsPD.AddValueChanged(xel, (o, e) =>
+            {
+                valueChanged = true;
+            });
+
+            xel.Add(new XElement("c1"), new XElement("c2"));
+            Assert.True(valueChanged);
+
+            Assert.Equal(els, xel.Elements());
+
+            valueChanged = false;
+            xel.Element("c1").Remove();
+            Assert.True(valueChanged);
+        }
+
+        [Fact]
+        public void XElementValuePropertyDescriptor()
+        {
+            var xel = new XElement("someElement", "someValue");
+            var props = TypeDescriptor.GetProperties(xel);
+            
+            var xelValPD = props["Value"];
+            Assert.NotNull(xelValPD);
+            Assert.False(xelValPD.IsReadOnly);
+            Assert.Equal(typeof(XElement), xelValPD.ComponentType);
+            Assert.Equal(typeof(string), xelValPD.PropertyType);
+            Assert.True(xelValPD.SupportsChangeEvents);
+            Assert.False(xelValPD.CanResetValue(xel));
+            Assert.False(xelValPD.ShouldSerializeValue(xel));
+
+            Assert.Equal(xel.Value, xelValPD.GetValue(xel));
+
+            bool valueChanged = false;
+            xelValPD.AddValueChanged(xel, (o, e) =>
+            {
+                valueChanged = true;
+            });
+            var newValue = "SomeNewValue";
+            xelValPD.SetValue(xel, newValue);
+
+            Assert.True(valueChanged);
+            Assert.Equal(newValue, xel.Value);
+
+            valueChanged = false;
+            xel.Value = "AnotherValue";
+            Assert.True(valueChanged);
+            Assert.Equal(xel.Value, xelValPD.GetValue(xel));
+        }
+
+        [Fact]
+        public void XElementXmlPropertyDescriptor()
+        {
+            var xel = new XElement("someElement");
+            var props = TypeDescriptor.GetProperties(xel);
+            
+            var xelXmlPD = props["Xml"];
+            Assert.NotNull(xelXmlPD);
+            Assert.True(xelXmlPD.IsReadOnly);
+            Assert.Equal(typeof(XElement), xelXmlPD.ComponentType);
+            Assert.Equal(typeof(string), xelXmlPD.PropertyType);
+            Assert.True(xelXmlPD.SupportsChangeEvents);
+            Assert.False(xelXmlPD.CanResetValue(xel));
+            Assert.False(xelXmlPD.ShouldSerializeValue(xel));
+
+            Assert.Equal(xel.ToString(), xelXmlPD.GetValue(xel));
+
+            bool valueChanged = false;
+            xelXmlPD.AddValueChanged(xel, (o, e) =>
+            {
+                valueChanged = true;
+            });
+            xel.Value = "abc123";
+            Assert.True(valueChanged);
+            Assert.Equal(xel.ToString(), xelXmlPD.GetValue(xel));
+        }
+    }
+}
index 5d001a1..4ab539c 100644 (file)
@@ -206,6 +206,13 @@ namespace System.ComponentModel
         public override bool Equals(object obj) { throw null; }
         public override int GetHashCode() { throw null; }
     }
+    [System.AttributeUsageAttribute((System.AttributeTargets)(4), Inherited = true)]
+    public sealed partial class TypeDescriptionProviderAttribute : System.Attribute
+    {
+        public TypeDescriptionProviderAttribute(string typeName) { }
+        public TypeDescriptionProviderAttribute(System.Type type) { }
+        public string TypeName { get { throw null; } }
+    }
 }
 namespace System.Reflection
 {
index 45da739..b697c02 100644 (file)
@@ -22,6 +22,7 @@
     <Compile Include="System\ComponentModel\PropertyChangingEventArgs.cs" />
     <Compile Include="System\ComponentModel\PropertyChangingEventHandler.cs" />
     <Compile Include="System\ComponentModel\TypeConverterAttribute.cs" />
+    <Compile Include="System\ComponentModel\TypeDescriptionProviderAttribute.cs" />
     <Compile Include="System\Reflection\ICustomTypeProvider.cs" />
     <Compile Include="System\Windows\Markup\ValueSerializerAttribute.cs" />
     <Compile Include="System\Windows\Input\ICommand.cs" />
index b46a578..701858e 100644 (file)
@@ -56,6 +56,7 @@
     <Reference Include="System.Diagnostics.Debug" />
     <Reference Include="System.Diagnostics.Tools" />
     <Reference Include="System.Linq" />
+    <Reference Include="System.ObjectModel" />
     <Reference Include="System.Resources.ResourceManager" />
     <Reference Include="System.Runtime" />
     <Reference Include="System.Runtime.Extensions" />
index 0bc03a6..c258203 100644 (file)
@@ -17,6 +17,7 @@ namespace System.Xml.Linq
     /// An XML attribute is a name/value pair associated with an XML element.
     /// </remarks>
     [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Justification = "Reviewed.")]
+    [System.ComponentModel.TypeDescriptionProvider("MS.Internal.Xml.Linq.ComponentModel.XTypeDescriptionProvider`1[[System.Xml.Linq.XAttribute, System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]],System.ComponentModel.TypeConverter")]
     public class XAttribute : XObject
     {
         /// <summary>
index 964d119..9cfcb18 100644 (file)
@@ -32,6 +32,7 @@ namespace System.Xml.Linq
     ///   </list>
     /// </remarks>
     [XmlSchemaProvider(null, IsAny = true)]
+    [System.ComponentModel.TypeDescriptionProvider("MS.Internal.Xml.Linq.ComponentModel.XTypeDescriptionProvider`1[[System.Xml.Linq.XElement, System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]],System.ComponentModel.TypeConverter")]
     public class XElement : XContainer, IXmlSerializable
     {
         /// <summary>
index 4c0ac6e..aab2ef4 100644 (file)
@@ -5,6 +5,7 @@ T:System.ComponentModel.Design.Serialization.DesignerSerializerAttribute
 T:System.ComponentModel.Design.Serialization.RootDesignerSerializerAttribute
 T:System.ComponentModel.EditorAttribute
 T:System.ComponentModel.ToolboxItemAttribute
+T:System.ComponentModel.TypeDescriptionProviderAttribute
 T:System.Configuration.ConfigurationPropertyAttribute
 T:System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute
 T:System.Diagnostics.CodeAnalysis.SuppressMessageAttribute