|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Drawing.Design;
using System.Globalization;
using System.Reflection;
namespace System.ComponentModel.Design;
/// <summary>
/// Describes and represents inherited properties in an inherited class.
/// </summary>
internal sealed class InheritedPropertyDescriptor : PropertyDescriptor
{
private PropertyDescriptor _propertyDescriptor;
private object? _defaultValue;
private static readonly object s_noDefault = new();
/// <summary>
/// Initializes a new instance of the <see cref="InheritedPropertyDescriptor"/> class.
/// </summary>
public InheritedPropertyDescriptor(PropertyDescriptor propertyDescriptor, object component) : base(propertyDescriptor, [])
{
Debug.Assert(propertyDescriptor is not InheritedPropertyDescriptor, $"Recursive inheritance propertyDescriptor {propertyDescriptor}");
_propertyDescriptor = propertyDescriptor;
InitInheritedDefaultValue(component);
// Check to see if this property points to a collection of objects that are not IComponents.
// We cannot serialize the delta between two collections if they do not contain components,
// so if we detect this case we will make the property invisible to serialization.
// We only do this if there are already items in the collection. Otherwise, it is safe.
bool readOnlyCollection = false;
if (typeof(ICollection).IsAssignableFrom(propertyDescriptor.PropertyType) &&
propertyDescriptor.Attributes.Contains(DesignerSerializationVisibilityAttribute.Content))
{
if (propertyDescriptor.GetValue(component) is ICollection { Count: > 0 } collection)
{
// Trawl Add and AddRange methods looking for the first compatible serializable method.
// All we need is the data type.
bool addComponentExists = false;
bool addNonComponentExists = false;
foreach (MethodInfo method in TypeDescriptor.GetReflectionType(collection).GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
ParameterInfo[] parameters = method.GetParameters();
if (parameters is [{ } parameter])
{
string name = method.Name;
Type? collectionType = null;
if (name.Equals("AddRange") && parameter.ParameterType.IsArray)
{
collectionType = parameter.ParameterType.GetElementType();
}
else if (name.Equals("Add"))
{
collectionType = parameter.ParameterType;
}
if (collectionType is not null)
{
if (!typeof(IComponent).IsAssignableFrom(collectionType))
{
addNonComponentExists = true;
}
else
{
// this collection has at least one Add(IComponent) method.
addComponentExists = true;
break;
}
}
}
}
if (addNonComponentExists && !addComponentExists)
{
// Must mark this object as read-only if we can add only non-components to it
List<Attribute> attributes = new(AttributeArray!)
{
DesignerSerializationVisibilityAttribute.Hidden,
ReadOnlyAttribute.Yes,
new EditorAttribute(typeof(UITypeEditor), typeof(UITypeEditor)),
new TypeConverterAttribute(typeof(ReadOnlyCollectionConverter))
};
AttributeArray = [.. attributes];
readOnlyCollection = true;
}
}
}
if (!readOnlyCollection)
{
if (_defaultValue != s_noDefault)
{
List<Attribute> attributes = new(AttributeArray!)
{
new DefaultValueAttribute(_defaultValue)
};
AttributeArray = [.. attributes];
}
}
}
/// <summary>
/// Gets the type of the component this property descriptor is bound to.
/// </summary>
public override Type ComponentType => _propertyDescriptor.ComponentType;
/// <summary>
/// Gets a value indicating whether this property is read only.
/// </summary>
public override bool IsReadOnly => _propertyDescriptor.IsReadOnly || Equals(Attributes[typeof(ReadOnlyAttribute)], ReadOnlyAttribute.Yes);
internal object? OriginalValue { get; private set; }
internal PropertyDescriptor PropertyDescriptor
{
get => _propertyDescriptor;
set
{
Debug.Assert(value is not InheritedPropertyDescriptor, $"Recursive inheritance propertyDescriptor {_propertyDescriptor}");
_propertyDescriptor = value;
}
}
/// <summary>
/// Gets the type of the property.
/// </summary>
public override Type PropertyType => _propertyDescriptor.PropertyType;
/// <summary>
/// Indicates whether reset will change the value of the component.
/// </summary>
public override bool CanResetValue(object component)
{
// We always have a default value, because we got it from the component when we were constructed.
if (_defaultValue == s_noDefault)
{
return _propertyDescriptor.CanResetValue(component);
}
else
{
return !Equals(GetValue(component), _defaultValue);
}
}
[return: NotNullIfNotNull(nameof(value))]
private object? ClonedDefaultValue(object? value)
{
// if we have a persist contents guy, we'll need to try to clone the value because otherwise
// we won't be able to tell when it's been modified.
if (value is null ||
!_propertyDescriptor.TryGetAttribute(out DesignerSerializationVisibilityAttribute? dsva) ||
dsva.Visibility != DesignerSerializationVisibility.Content)
{
return value;
}
if (value is ICloneable cloneable)
{
// if it's cloneable, clone it...
return cloneable.Clone();
}
// otherwise, we'll just have to always spit it.
return s_noDefault;
}
/// <summary>
/// We need to merge in attributes from the wrapped property descriptor here.
/// </summary>
protected override void FillAttributes(IList attributeList)
{
base.FillAttributes(attributeList);
foreach (Attribute attr in _propertyDescriptor.Attributes)
{
attributeList.Add(attr);
}
}
/// <summary>
/// Gets the current value of the property on the component, invoking the getXXX method.
/// </summary>
public override object? GetValue(object? component)
{
return _propertyDescriptor.GetValue(component);
}
private void InitInheritedDefaultValue(object component)
{
try
{
object? currentValue;
// Don't just get the default value. Check to see if the propertyDescriptor has indicated ShouldSerialize,
// and if it hasn't try to use the default value.
// We need to do this for properties that inherit from their parent.
// If we are processing properties on the root component,
// we always favor the presence of a default value attribute.
// The root component is always inherited but some values should always be written into code.
if (!_propertyDescriptor.ShouldSerializeValue(component))
{
if (_propertyDescriptor.TryGetAttribute(out DefaultValueAttribute? defaultAttribute))
{
_defaultValue = defaultAttribute.Value;
currentValue = _defaultValue;
}
else
{
_defaultValue = s_noDefault;
currentValue = _propertyDescriptor.GetValue(component);
}
}
else
{
_defaultValue = _propertyDescriptor.GetValue(component);
currentValue = _defaultValue;
_defaultValue = ClonedDefaultValue(_defaultValue);
}
SaveOriginalValue(currentValue);
}
catch
{
// If the property get blows chunks, then the default value is NoDefault and we resort to the base property descriptor.
_defaultValue = s_noDefault;
}
ShouldSerializeValue(component);
}
/// <summary>
/// Resets the default value for this property on the component.
/// </summary>
public override void ResetValue(object component)
{
if (_defaultValue == s_noDefault)
{
_propertyDescriptor.ResetValue(component);
}
else
{
SetValue(component, _defaultValue);
}
}
private void SaveOriginalValue(object? value)
{
if (value is ICollection collection)
{
OriginalValue = new object[collection.Count];
collection.CopyTo((Array)OriginalValue, 0);
}
else
{
OriginalValue = value;
}
}
/// <summary>
/// Sets the value to be the new value of this property on the component by invoking the setXXX method on the component.
/// </summary>
public override void SetValue(object? component, object? value)
{
_propertyDescriptor.SetValue(component, value);
}
/// <summary>
/// Indicates whether the value of this property needs to be persisted.
/// </summary>
public override bool ShouldSerializeValue(object component)
{
if (IsReadOnly)
{
return _propertyDescriptor.ShouldSerializeValue(component) && Attributes.Contains(DesignerSerializationVisibilityAttribute.Content);
}
if (_defaultValue == s_noDefault)
{
return _propertyDescriptor.ShouldSerializeValue(component);
}
else
{
return !Equals(GetValue(component), _defaultValue);
}
}
private class ReadOnlyCollectionConverter : TypeConverter
{
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if (destinationType == typeof(string))
{
return SR.GetResourceString(SR.InheritanceServiceReadOnlyCollection);
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
|