| File: System\ComponentModel\Design\InheritedPropertyDescriptor.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms.Design\src\System.Windows.Forms.Design.csproj (System.Windows.Forms.Design) |
// 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); } } }