|
// 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.Design.Serialization;
using System.Reflection;
namespace System.ComponentModel.Design;
/// <summary>
/// A design surface is an object that contains multiple designers and presents a user-editable surface for them.
/// </summary>
public class DesignSurface : IDisposable, IServiceProvider
{
private DesignSurfaceServiceContainer _serviceContainer;
private DesignerHost _host;
private ICollection? _loadErrors;
/// <summary>
/// Creates a new DesignSurface.
/// </summary>
public DesignSurface() : this((IServiceProvider?)null)
{
}
/// <summary>
/// Creates a new DesignSurface given a parent service provider.
/// </summary>
/// <param name="parentProvider">
/// The parent service provider. If there is no parent used to resolve services this can be null.
/// </param>
public DesignSurface(IServiceProvider? parentProvider)
{
_serviceContainer = new DesignSurfaceServiceContainer(parentProvider);
// Configure our default services
ServiceCreatorCallback callback = new(OnCreateService);
ServiceContainer.AddService<ISelectionService>(callback);
ServiceContainer.AddService<IExtenderProviderService>(callback);
ServiceContainer.AddService<IExtenderListService>(callback);
ServiceContainer.AddService<ITypeDescriptorFilterService>(callback);
ServiceContainer.AddService<IReferenceService>(callback);
ServiceContainer.AddService(this);
_host = new DesignerHost(this);
}
/// <summary>
/// Creates a new DesignSurface.
/// </summary>
public DesignSurface(Type rootComponentType) : this(null, rootComponentType)
{
}
/// <summary>
/// Creates a new DesignSurface given a parent service provider.
/// </summary>
/// <param name="parentProvider">
/// The parent service provider. If there is no parent used to resolve services this can be null.
/// </param>
public DesignSurface(IServiceProvider? parentProvider, Type rootComponentType) : this(parentProvider)
{
ArgumentNullException.ThrowIfNull(rootComponentType);
BeginLoad(rootComponentType);
}
/// <summary>
/// Provides access to the design surface's container, which contains all components currently being designed.
/// </summary>
public IContainer ComponentContainer
{
get
{
ObjectDisposedException.ThrowIf(_host is null, this);
return ((IDesignerHost)_host).Container;
}
}
/// <summary>
/// Returns true if the design surface is currently loaded.
/// This will be <see langword="true"/> when a successful load has completed,
/// or <see langword="false"/> for all other cases.
/// </summary>
public bool IsLoaded { get; private set; }
/// <summary>
/// Returns a collection of LoadErrors or a void collection.
/// </summary>
public ICollection LoadErrors => _loadErrors ?? Array.Empty<object>();
/// <summary>
/// Returns true if DTEL (WSOD) is currently loading.
/// </summary>
public bool DtelLoading
{
get;
set;
}
/// <summary>
/// Provides access to the design surface's ServiceContainer. This property allows inheritors to add their own services.
/// </summary>
protected ServiceContainer ServiceContainer
{
get
{
ObjectDisposedException.ThrowIf(_serviceContainer is null, this);
return _serviceContainer;
}
}
/// <summary>
/// This property will return the view for the root designer. BeginLoad must have been called beforehand to
/// start the loading process. It is possible to return a view before the designer loader finishes loading because
/// the root designer, which supplies the view, is the first object created by the designer loader.
/// If a view is unavailable this method will throw an exception.
/// Possible exceptions:
/// The design surface is not loading or the designer loader has not yet created
/// a root designer: <see cref="InvalidOperationException"/>.
/// The design surface finished the load, but failed.
/// (Various. This will throw the first exception the designer loader added to the error collection).
/// </summary>
public object View
{
get
{
ObjectDisposedException.ThrowIf(_host is null, this);
IComponent? rootComponent = ((IDesignerHost)_host).RootComponent;
if (rootComponent is null)
{
// Check to see if we have any load errors. If so, use them.
if (_loadErrors is not null)
{
foreach (object o in _loadErrors)
{
if (o is Exception ex)
{
throw new InvalidOperationException(ex.Message, ex);
}
else if (o is not null)
{
throw new InvalidOperationException(o.ToString());
}
}
}
// loader didn't provide any help. Just generally fail.
throw new InvalidOperationException(SR.DesignSurfaceNoRootComponent)
{
HelpLink = SR.DesignSurfaceNoRootComponent
};
}
if (((IDesignerHost)_host).GetDesigner(rootComponent) is not IRootDesigner rootDesigner)
{
throw new InvalidOperationException(SR.DesignSurfaceDesignerNotLoaded)
{
HelpLink = SR.DesignSurfaceDesignerNotLoaded
};
}
ViewTechnology[]? designerViews = rootDesigner.SupportedTechnologies;
if (designerViews is null || designerViews.Length == 0)
{
throw new NotSupportedException(SR.DesignSurfaceNoSupportedTechnology)
{
HelpLink = SR.DesignSurfaceNoSupportedTechnology
};
}
// We just feed the available technologies back into the root designer. ViewTechnology itself is outdated.
return rootDesigner.GetView(designerViews[0]);
}
}
/// <summary>
/// Adds a event handler to listen to the Disposed event on the component.
/// </summary>
public event EventHandler? Disposed;
/// <summary>
/// Adds a event handler to listen to the Flushed event on the component.
/// This is called after the design surface has asked the designer loader to flush its state.
/// </summary>
public event EventHandler? Flushed;
/// <summary>
/// Called when the designer load has completed. This is called for successful loads as well as unsuccessful ones.
/// If code in this event handler throws an exception the designer will be unloaded.
/// </summary>
public event LoadedEventHandler? Loaded;
/// <summary>
/// Called when the designer load is about to begin the loading process.
/// </summary>
public event EventHandler? Loading;
/// <summary>
/// Called when the designer has completed the unloading
/// process.
/// </summary>
public event EventHandler? Unloaded;
/// <summary>
/// Called when a designer is about to begin reloading. When a designer reloads,
/// all of the state for that designer is recreated, including the designer's view. The view should be unparented at this time.
/// </summary>
public event EventHandler? Unloading;
/// <summary>
/// Called when someone has called the Activate method on IDesignerHost.
/// You should attach a handler to this event that activates the window for this design surface.
/// </summary>
public event EventHandler? ViewActivated;
/// <summary>
/// This method begins the loading process with the given designer loader. Designer loading can be asynchronous,
/// so the loading may continue to progress after this call has returned. Listen to the Loaded event to know
/// when the design surface has completed loading.
/// </summary>
public void BeginLoad(DesignerLoader loader)
{
ArgumentNullException.ThrowIfNull(loader);
ObjectDisposedException.ThrowIf(_host is null, this);
// Create the designer host. We need the host so we can begin the loading process.
_loadErrors = null;
_host.BeginLoad(loader);
}
/// <summary>
/// This method begins the loading process for a component of the given type.
/// This will create an instance of the component type and initialize a designer for that instance.
/// Loaded is raised before this method returns.
/// </summary>
public void BeginLoad(Type rootComponentType)
{
ArgumentNullException.ThrowIfNull(rootComponentType);
ObjectDisposedException.ThrowIf(_host is null, this);
BeginLoad(new DefaultDesignerLoader(rootComponentType));
}
/// <summary>
/// This method is called to create a component of the given type.
/// </summary>
[Obsolete("CreateComponent has been replaced by CreateInstance and will be removed after Beta2")]
protected internal virtual IComponent? CreateComponent(Type componentType)
{
return CreateInstance(componentType) as IComponent;
}
/// <summary>
/// This method is called to create a designer for a component.
/// </summary>
protected internal virtual IDesigner? CreateDesigner(IComponent component, bool rootDesigner)
{
ArgumentNullException.ThrowIfNull(component);
ObjectDisposedException.ThrowIf(_host is null, this);
return rootDesigner
? TypeDescriptor.CreateDesigner(component, typeof(IRootDesigner)) as IRootDesigner
: TypeDescriptor.CreateDesigner(component, typeof(IDesigner));
}
/// <summary>
/// This method is called to create an instance of the given type. If the type is a component
/// this will search for a constructor of type IContainer first, and then an empty constructor.
/// </summary>
protected internal virtual object? CreateInstance(Type type)
{
ArgumentNullException.ThrowIfNull(type);
// Locate an appropriate constructor for IComponents.
object? instance = null;
ConstructorInfo? ctor = TypeDescriptor.GetReflectionType(type).GetConstructor([]);
if (ctor is not null)
{
instance = TypeDescriptor.CreateInstance(this, type, [], []);
}
else
{
if (typeof(IComponent).IsAssignableFrom(type))
{
ctor = TypeDescriptor.GetReflectionType(type).GetConstructor(BindingFlags.Public | BindingFlags.Instance | BindingFlags.ExactBinding, null, [typeof(IContainer)], null);
}
if (ctor is not null)
{
instance = TypeDescriptor.CreateInstance(this, type, [typeof(IContainer)], [ComponentContainer]);
}
}
instance ??= Activator.CreateInstance(type, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.CreateInstance, null, null, null);
return instance;
}
/// <summary>
/// Creates a container suitable for nesting controls or components. Adding a component to a nested container
/// creates its designer and makes it eligible for all all services available from the design surface.
/// Components added to nested containers do not participate in serialization.
/// You may provide an additional name for this container by passing a value into containerName.
/// </summary>
public INestedContainer CreateNestedContainer(IComponent owningComponent)
{
return CreateNestedContainer(owningComponent, null);
}
/// <summary>
/// Creates a container suitable for nesting controls or components. Adding a component to a nested container
/// creates its designer and makes it eligible for all all services available from the design surface.
/// Components added to nested containers do not participate in serialization. You may provide an additional name
/// for this container by passing a value into containerName.
/// </summary>
public INestedContainer CreateNestedContainer(IComponent owningComponent, string? containerName)
{
ObjectDisposedException.ThrowIf(_host is null, this);
ArgumentNullException.ThrowIfNull(owningComponent);
return new SiteNestedContainer(owningComponent, containerName, _host);
}
/// <summary>
/// Disposes the design surface.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Protected override of Dispose that allows for cleanup.
/// </summary>
/// <param name="disposing">True if Dispose is being called or false if this is being invoked by a finalizer.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// technically we should raise this after we've destroyed ourselves.
// Unfortunately, too many things query us for services so they can detach.
Disposed?.Invoke(this, EventArgs.Empty);
// Destroying the host also destroys all components. In most cases destroying the root component
// will destroy its designer which also kills the view. So, we destroy the view below last
// (remember, this view is a "view container" so we are destroying the innermost view first and
// then destroying our own view).
try
{
try
{
_host?.DisposeHost();
}
finally
{
if (_serviceContainer is not null)
{
_serviceContainer.RemoveService<DesignSurface>();
_serviceContainer.Dispose();
}
}
}
finally
{
_host = null!;
_serviceContainer = null!;
}
}
}
/// <summary>
/// Flushes any design changes to the underlying loader.
/// </summary>
public void Flush()
{
_host?.Flush();
Flushed?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Retrieves a service in this design surface's service container.
/// </summary>
/// <param name="serviceType">The type of service to retrieve.</param>
/// <returns> An instance of the requested service or null if the service could not be found.</returns>
public object? GetService(Type serviceType)
{
return _serviceContainer?.GetService(serviceType);
}
/// <summary>
/// Called by the designer host in response to an Activate call on its interface.
/// </summary>
internal void OnViewActivate()
{
OnViewActivate(EventArgs.Empty);
}
/// <summary>
/// Private method that demand-creates services we offer.
/// </summary>
/// <param name="container">The service container requesting the service.</param>
/// <param name="serviceType">The type of service being requested.</param>
/// <returns>
/// A new instance of the service. It is an error to call this with a service type it doesn't know how to create.
/// </returns>
private object OnCreateService(IServiceContainer container, Type serviceType)
{
if (serviceType == typeof(ISelectionService))
{
return new SelectionService(container);
}
if (serviceType == typeof(IExtenderProviderService))
{
return new ExtenderProviderService();
}
if (serviceType == typeof(IExtenderListService))
{
return GetService(typeof(IExtenderProviderService))!;
}
if (serviceType == typeof(ITypeDescriptorFilterService))
{
return new TypeDescriptorFilterService();
}
Debug.Assert(serviceType == typeof(IReferenceService), $"Demand created service not supported: {serviceType.Name}");
return new ReferenceService(container);
}
/// <summary>
/// This is invoked by the designer host when it has finished the load.
/// </summary>
internal void OnLoaded(bool successful, ICollection? errors)
{
IsLoaded = successful;
_loadErrors = errors;
if (successful)
{
IComponent? rootComponent = ((IDesignerHost)_host).RootComponent;
if (rootComponent is null)
{
ArrayList newErrors = [];
Exception ex = new InvalidOperationException(SR.DesignSurfaceNoRootComponent)
{
HelpLink = SR.DesignSurfaceNoRootComponent
};
newErrors.Add(ex);
if (errors is not null)
{
newErrors.AddRange(errors);
}
errors = newErrors;
successful = false;
}
}
OnLoaded(new LoadedEventArgs(successful, errors));
}
/// <summary>
/// Called when the loading process has completed. This is invoked for both successful and unsuccessful loads.
/// The EventArgs passed into this method can be used to tell a successful from an unsuccessful load.
/// It can also be used to create a view for this design surface.
/// If code in this event handler or override throws an exception, the designer will be unloaded.
/// </summary>
protected virtual void OnLoaded(LoadedEventArgs e)
{
Loaded?.Invoke(this, e);
}
/// <summary>
/// Called when the loading process is about to begin.
/// </summary>
internal void OnLoading()
{
OnLoading(EventArgs.Empty);
}
/// <summary>
/// Called when the loading process is about to begin.
/// </summary>
protected virtual void OnLoading(EventArgs e)
{
Loading?.Invoke(this, e);
}
/// <summary>
/// This is invoked by the designer host after it has unloaded a document.
/// </summary>
internal void OnUnloaded()
{
OnUnloaded(EventArgs.Empty);
}
/// <summary>
/// Called when a designer has finished unloading a document.
/// </summary>
protected virtual void OnUnloaded(EventArgs e)
{
Unloaded?.Invoke(this, e);
}
/// <summary>
/// This is invoked by the designer host when it is about to unload a document.
/// </summary>
internal void OnUnloading()
{
OnUnloading(EventArgs.Empty);
IsLoaded = false;
}
/// <summary>
/// Called when a designer is about to begin reloading. When a designer reloads,
/// all of the state for that designer is recreated, including the designer's view.
/// The view should be unparented at this time.
/// </summary>
protected virtual void OnUnloading(EventArgs e)
{
Unloading?.Invoke(this, e);
}
/// <summary>
/// Called when someone has called the Activate method on IDesignerHost.
/// You should attach a handler to this event that activates the window for this design surface.
/// </summary>
protected virtual void OnViewActivate(EventArgs e)
{
ViewActivated?.Invoke(this, e);
}
/// <summary>
/// This is a simple designer loader that creates an instance of the given type and then calls EndLoad.
/// If a collection of objects was passed, this will simply add those objects to the container.
/// </summary>
private class DefaultDesignerLoader : DesignerLoader
{
private readonly Type _type;
public DefaultDesignerLoader(Type type)
{
_type = type;
}
public override void BeginLoad(IDesignerLoaderHost loaderHost)
{
loaderHost.CreateComponent(_type);
loaderHost.EndLoad(_type.FullName!, true, null);
}
public override void Dispose()
{
}
}
}
|