|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.ComponentModel.Design;
/// <summary>
/// This service allows clients to work with all references on a form, not just the top-level sited components.
/// </summary>
internal sealed class ReferenceService : IReferenceService, IDisposable
{
private static readonly Attribute[] s_attributes = [DesignerSerializationVisibilityAttribute.Content];
private IServiceProvider _provider; // service provider we use to get to other services
private List<IComponent>? _addedComponents; // list of newly added components
private List<IComponent>? _removedComponents; // list of newly removed components
private List<ReferenceHolder>? _references; // our current list of references
private bool _populating;
/// <summary>
/// Constructs the ReferenceService.
/// </summary>
internal ReferenceService(IServiceProvider provider)
{
_provider = provider;
}
/// <summary>
/// Creates an entry for a top-level component and it's children.
/// </summary>
private static void CreateReferences(IComponent component, List<ReferenceHolder> references)
{
CreateReferences(string.Empty, component, component, references);
}
/// <summary>
/// Recursively creates references for namespaced objects.
/// </summary>
private static void CreateReferences(string trailingName, object? reference, IComponent sitedComponent, List<ReferenceHolder> references)
{
if (reference is null)
{
return;
}
references.Add(new ReferenceHolder(trailingName, reference, sitedComponent));
foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(reference, s_attributes))
{
if (property.IsReadOnly)
{
CreateReferences($"{trailingName}.{property.Name}", property.GetValue(reference), sitedComponent, references);
}
}
}
/// <summary>
/// Demand populates the _references variable.
/// </summary>
[MemberNotNull(nameof(_references))]
private void EnsureReferences()
{
// If the references are null, create them for the first time and
// connect up our events to listen to changes to the container.
// Otherwise, check to see if the added or removed lists contain anything for us to sync up.
if (_references is null)
{
ObjectDisposedException.ThrowIf(_provider is null, typeof(IReferenceService));
IComponentChangeService? cs = _provider.GetService<IComponentChangeService>();
Debug.Assert(cs is not null, "Reference service relies on IComponentChangeService");
if (cs is not null)
{
cs.ComponentAdded += OnComponentAdded;
cs.ComponentRemoved += OnComponentRemoved;
cs.ComponentRename += OnComponentRename;
}
if (_provider.GetService(typeof(IContainer)) is not IContainer container)
{
Debug.Fail("Reference service cannot operate without IContainer");
throw new InvalidOperationException();
}
_references = new(container.Components.Count);
foreach (IComponent component in container.Components)
{
CreateReferences(component, _references);
}
}
else if (!_populating)
{
_populating = true;
try
{
if (_addedComponents is not null && _addedComponents.Count > 0)
{
// There is a possibility that this component already exists. If it does, just remove it first and then re-add it.
foreach (IComponent ic in _addedComponents)
{
RemoveReferences(ic);
CreateReferences(ic, _references);
}
_addedComponents.Clear();
}
if (_removedComponents is not null && _removedComponents.Count > 0)
{
foreach (IComponent ic in _removedComponents)
{
RemoveReferences(ic);
}
_removedComponents.Clear();
}
}
finally
{
_populating = false;
}
}
}
/// <summary>
/// Listens for component additions to find all the references it contributes.
/// </summary>
[MemberNotNull(nameof(_addedComponents))]
private void OnComponentAdded(object? sender, ComponentEventArgs cevent)
{
_addedComponents ??= [];
IComponent compAdded = cevent.Component!;
if (compAdded.Site is not INestedSite)
{
_addedComponents.Add(compAdded);
_removedComponents?.Remove(compAdded);
}
}
/// <summary>
/// Listens for component removes to delete all the references it holds.
/// </summary>
[MemberNotNull(nameof(_removedComponents))]
private void OnComponentRemoved(object? sender, ComponentEventArgs cevent)
{
_removedComponents ??= [];
IComponent compRemoved = cevent.Component!;
if (compRemoved.Site is not INestedSite)
{
_removedComponents.Add(compRemoved);
_addedComponents?.Remove(compRemoved);
}
}
/// <summary>
/// Listens for component removes to delete all the references it holds.
/// </summary>
private void OnComponentRename(object? sender, ComponentRenameEventArgs cevent)
{
foreach (ReferenceHolder reference in _references!)
{
if (ReferenceEquals(reference.SitedComponent, cevent.Component))
{
reference.ResetName();
return;
}
}
}
/// <summary>
/// Removes all the references that this component owns.
/// </summary>
private void RemoveReferences(IComponent component)
{
if (_references is not null)
{
int size = _references.Count;
for (int i = size - 1; i >= 0; i--)
{
if (ReferenceEquals(_references[i].SitedComponent, component))
{
_references.RemoveAt(i);
}
}
}
}
/// <summary>
/// Cleanup and detach from our events.
/// </summary>
void IDisposable.Dispose()
{
if (_references is not null)
{
if (_provider.TryGetService(out IComponentChangeService? cs))
{
cs.ComponentAdded -= OnComponentAdded;
cs.ComponentRemoved -= OnComponentRemoved;
cs.ComponentRename -= OnComponentRename;
}
_references = null;
_provider = null!;
}
}
/// <summary>
/// Finds the sited component for a given reference, returning null if not found.
/// </summary>
IComponent? IReferenceService.GetComponent(object reference)
{
ArgumentNullException.ThrowIfNull(reference);
EnsureReferences();
foreach (ReferenceHolder holder in _references)
{
if (ReferenceEquals(holder.Reference, reference))
{
return holder.SitedComponent;
}
}
return null;
}
/// <summary>
/// Finds name for a given reference, returning null if not found.
/// </summary>
string? IReferenceService.GetName(object reference)
{
ArgumentNullException.ThrowIfNull(reference);
EnsureReferences();
foreach (ReferenceHolder holder in _references)
{
if (ReferenceEquals(holder.Reference, reference))
{
return holder.Name;
}
}
return null;
}
/// <summary>
/// Finds a reference with the given name, returning null if not found.
/// </summary>
object? IReferenceService.GetReference(string name)
{
ArgumentNullException.ThrowIfNull(name);
EnsureReferences();
foreach (ReferenceHolder holder in _references)
{
if (string.Equals(holder.Name, name, StringComparison.OrdinalIgnoreCase))
{
return holder.Reference;
}
}
return null;
}
/// <summary>
/// Returns all references available in this designer.
/// </summary>
object[] IReferenceService.GetReferences()
{
EnsureReferences();
object[] references = new object[_references.Count];
for (int i = 0; i < references.Length; i++)
{
references[i] = _references[i].Reference;
}
return references;
}
/// <summary>
/// Returns all references available in this designer that are assignable to the given type.
/// </summary>
object[] IReferenceService.GetReferences(Type baseType)
{
ArgumentNullException.ThrowIfNull(baseType);
EnsureReferences();
List<object> results = new(_references.Count);
foreach (ReferenceHolder holder in _references)
{
object reference = holder.Reference;
if (baseType.IsInstanceOfType(reference))
{
results.Add(reference);
}
}
return [.. results];
}
/// <summary>
/// The class that holds the information about a reference.
/// </summary>
private sealed class ReferenceHolder
{
private readonly string _trailingName;
private string? _fullName;
/// <summary>
/// Creates a new reference holder.
/// </summary>
internal ReferenceHolder(string trailingName, object reference, IComponent sitedComponent)
{
_trailingName = trailingName;
Reference = reference;
SitedComponent = sitedComponent;
Debug.Assert(trailingName is not null, "Expected a trailing name");
Debug.Assert(reference is not null, "Expected a reference");
Debug.Assert(sitedComponent is not null, "Expected a sited component");
Debug.Assert(sitedComponent.Site is not null, $"Sited component is not really sited: {sitedComponent}");
Debug.Assert(TypeDescriptor.GetComponentName(sitedComponent) is not null, $"Sited component has no name: {sitedComponent}");
}
/// <summary>
/// Resets the name of this reference holder. It will be re-acquired on demand
/// </summary>
internal void ResetName()
{
_fullName = null;
}
/// <summary>
/// The name of the reference we are holding.
/// </summary>
[MemberNotNull(nameof(_fullName))]
internal string Name
{
get
{
if (_fullName is null)
{
if (SitedComponent is not null)
{
string? siteName = TypeDescriptor.GetComponentName(SitedComponent);
if (siteName is not null)
{
_fullName = $"{siteName}{_trailingName}";
}
Debug.Assert(SitedComponent.Site is not null, $"Sited component is not really sited: {SitedComponent}");
Debug.Assert(siteName is not null, $"Sited component has no name: {SitedComponent}");
}
_fullName ??= string.Empty;
}
return _fullName;
}
}
/// <summary>
/// The reference we are holding.
/// </summary>
internal object Reference { get; }
/// <summary>
/// The sited component associated with this reference.
/// </summary>
internal IComponent SitedComponent { get; }
}
}
|