File: System\Windows\Forms\Controls\PropertyGrid\PropertyGridInternal\MergePropertyDescriptor.cs
Web Access
Project: src\src\System.Windows.Forms\src\System.Windows.Forms.csproj (System.Windows.Forms)
// 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.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Globalization;
using static System.TrimmingConstants;
 
namespace System.Windows.Forms.PropertyGridInternal;
 
internal partial class MergePropertyDescriptor : PropertyDescriptor
{
    private readonly PropertyDescriptor[] _descriptors;
 
    private bool? _localizable;
    private bool? _readOnly;
    private bool? _canReset;
 
    private MultiMergeCollection? _collection;
 
    public MergePropertyDescriptor(PropertyDescriptor[] descriptors)
        : base(descriptors[0].Name, attrs: null)
    {
        _descriptors = descriptors;
    }
 
    public override Type ComponentType => _descriptors[0].ComponentType;
 
    public override TypeConverter Converter
    {
        [RequiresUnreferencedCode(PropertyDescriptorPropertyTypeMessage)]
        get => _descriptors[0].Converter;
    }
 
    public override string DisplayName => _descriptors[0].DisplayName;
 
    public override bool IsLocalizable
    {
        get
        {
            if (!_localizable.HasValue)
            {
                _localizable = true;
                foreach (PropertyDescriptor pd in _descriptors)
                {
                    if (!pd.IsLocalizable)
                    {
                        _localizable = false;
                        break;
                    }
                }
            }
 
            return _localizable.Value;
        }
    }
 
    public override bool IsReadOnly
    {
        get
        {
            if (!_readOnly.HasValue)
            {
                _readOnly = false;
                foreach (PropertyDescriptor pd in _descriptors)
                {
                    if (pd.IsReadOnly)
                    {
                        _readOnly = true;
                        break;
                    }
                }
            }
 
            return _readOnly.Value;
        }
    }
 
    public override Type PropertyType => _descriptors[0].PropertyType;
 
    public PropertyDescriptor this[int index] => _descriptors[index];
 
    public override bool CanResetValue(object component)
    {
        Debug.Assert(component is Array, "MergePropertyDescriptor::CanResetValue called with non-array value");
        if (!_canReset.HasValue)
        {
            _canReset = true;
            var a = (Array)component;
            for (int i = 0; i < _descriptors.Length; i++)
            {
                if (!_descriptors[i].CanResetValue(GetPropertyOwnerForComponent(a, i)!))
                {
                    _canReset = false;
                    break;
                }
            }
        }
 
        return _canReset.Value;
    }
 
    /// <summary>
    ///  This method attempts to copy the given value so unique values are always passed to each object.
    ///  If the value cannot be copied the original value will be returned.
    /// </summary>
    private static object? CopyValue(object? value)
    {
        // Null is always OK.
        if (value is null)
        {
            return value;
        }
 
        Type type = value.GetType();
 
        // Value types are always copies.
        if (type.IsValueType)
        {
            return value;
        }
 
        object? clonedValue = null;
 
        // ICloneable is the next easiest thing.
        if (value is ICloneable clone)
        {
            clonedValue = clone.Clone();
 
            if (clonedValue is not null)
            {
                return clonedValue;
            }
        }
 
        // Next, access the type converter.
        TypeConverter converter = TypeDescriptor.GetConverter(value);
        if (converter.CanConvertTo(typeof(InstanceDescriptor)))
        {
            // Instance descriptors provide full fidelity unless they are marked as incomplete.
            var instanceDescriptor = (InstanceDescriptor?)converter.ConvertTo(
                null,
                CultureInfo.InvariantCulture,
                value,
                typeof(InstanceDescriptor));
 
            if (instanceDescriptor is not null && instanceDescriptor.IsComplete)
            {
                clonedValue = instanceDescriptor.Invoke();
                if (clonedValue is not null)
                {
                    return clonedValue;
                }
            }
        }
 
        // If that didn't work, try conversion to/from string.
        if (converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string)))
        {
            string stringRepresentation = converter.ConvertToInvariantString(value)!;
            clonedValue = converter.ConvertFromInvariantString(stringRepresentation);
            if (clonedValue is not null)
            {
                return clonedValue;
            }
        }
 
        if (clonedValue is not null)
        {
            return clonedValue;
        }
 
        // We failed. This object's reference will be set on each property.
        return value;
    }
 
    protected override AttributeCollection CreateAttributeCollection() => new MergedAttributeCollection(this);
 
    private object? GetPropertyOwnerForComponent(Array a, int i)
    {
        object? propertyOwner = a.GetValue(i);
        if (propertyOwner is ICustomTypeDescriptor descriptor)
        {
            propertyOwner = descriptor.GetPropertyOwner(_descriptors[i]);
        }
 
        return propertyOwner;
    }
 
    [RequiresUnreferencedCode($"{EditorRequiresUnreferencedCode} {PropertyDescriptorPropertyTypeMessage}")]
    public override object? GetEditor(Type editorBaseType) => _descriptors[0].GetEditor(editorBaseType);
 
    public override object? GetValue(object? component)
    {
        Debug.Assert(component is Array, "MergePropertyDescriptor::GetValue called with non-array value");
        return GetValue((Array)component, out _);
    }
 
    public object? GetValue(Array components, out bool allEqual)
    {
        allEqual = true;
        object? @object = _descriptors[0].GetValue(GetPropertyOwnerForComponent(components, 0));
 
        if (@object is ICollection collection)
        {
            if (_collection is null)
            {
                _collection = new MultiMergeCollection(collection);
            }
            else if (_collection.Locked)
            {
                return _collection;
            }
            else
            {
                _collection.SetItems(collection);
            }
        }
 
        for (int i = 1; i < _descriptors.Length; i++)
        {
            object? currentObject = _descriptors[i].GetValue(GetPropertyOwnerForComponent(components, i));
 
            if (_collection is not null)
            {
                if (!_collection.ReinitializeIfNotEqual((ICollection)currentObject!))
                {
                    allEqual = false;
                    return null;
                }
            }
            else if ((@object is null && currentObject is null) || (@object is not null && @object.Equals(currentObject)))
            {
                continue;
            }
            else
            {
                allEqual = false;
                return null;
            }
        }
 
        if (allEqual && _collection is not null && _collection.Count == 0)
        {
            return null;
        }
 
        return _collection ?? @object;
    }
 
    internal object?[] GetValues(Array components)
    {
        object?[] values = new object?[components.Length];
 
        for (int i = 0; i < components.Length; i++)
        {
            values[i] = _descriptors[i].GetValue(GetPropertyOwnerForComponent(components, i));
        }
 
        return values;
    }
 
    public override void ResetValue(object component)
    {
        Debug.Assert(component is Array, "MergePropertyDescriptor::ResetValue called with non-array value");
        var array = (Array)component;
        for (int i = 0; i < _descriptors.Length; i++)
        {
            _descriptors[i].ResetValue(GetPropertyOwnerForComponent(array, i)!);
        }
    }
 
    private void SetCollectionValues(Array a, IList listValue)
    {
        try
        {
            if (_collection is not null)
            {
                _collection.Locked = true;
            }
 
            // Now we have to copy the value into each property.
            object[] values = new object[listValue.Count];
 
            listValue.CopyTo(values, 0);
 
            for (int i = 0; i < _descriptors.Length; i++)
            {
                if (_descriptors[i].GetValue(GetPropertyOwnerForComponent(a, i)) is not IList properties)
                {
                    continue;
                }
 
                properties.Clear();
                foreach (object value in values)
                {
                    properties.Add(value);
                }
            }
        }
        finally
        {
            if (_collection is not null)
            {
                _collection.Locked = false;
            }
        }
    }
 
    public override void SetValue(object? component, object? value)
    {
        Debug.Assert(component is Array, "MergePropertyDescriptor::SetValue called with non-array value");
        var array = (Array)component;
        if (value is IList list && typeof(IList).IsAssignableFrom(PropertyType))
        {
            SetCollectionValues(array, list);
        }
        else
        {
            for (int i = 0; i < _descriptors.Length; i++)
            {
                object? clonedValue = CopyValue(value);
                _descriptors[i].SetValue(GetPropertyOwnerForComponent(array, i), clonedValue);
            }
        }
    }
 
    public override bool ShouldSerializeValue(object component)
    {
        Debug.Assert(component is Array, "MergePropertyDescriptor::ShouldSerializeValue called with non-array value");
        var array = (Array)component;
        for (int i = 0; i < _descriptors.Length; i++)
        {
            if (!_descriptors[i].ShouldSerializeValue(GetPropertyOwnerForComponent(array, i)!))
            {
                return false;
            }
        }
 
        return true;
    }
}