Skip to content

Feature - Tool window injection #14

@tonyhallett

Description

@tonyhallett

The Community Toolkit works as follows

Uses reflection to invoke the static Initialize method of BaseToolWindow<T> types.
https://github.com/VsixCommunity/Community.VisualStudio.Toolkit/blob/5071b7e871e5ad3c585c858e35692f8debdb28f9/src/toolkit/Community.VisualStudio.Toolkit.Shared/ExtensionMethods/AsyncPackageExtensions.cs#L76

Which adds them ( as the internal IToolWindowProvider interface ) with the internal package AddToolWindow method.
https://github.com/VsixCommunity/Community.VisualStudio.Toolkit/blob/5071b7e871e5ad3c585c858e35692f8debdb28f9/src/toolkit/Community.VisualStudio.Toolkit.Shared/Windows/BaseToolWindow.cs#L70

https://github.com/VsixCommunity/Community.VisualStudio.Toolkit/blob/5071b7e871e5ad3c585c858e35692f8debdb28f9/src/toolkit/Community.VisualStudio.Toolkit.Shared/ToolkitPackage.cs#L19

You could reinvent all of the code of the base ToolkitPackage or...

Create a proxy derivation that used a public IToolWindowProvider obtained from the service provider.

public interface IToolWindowProvider
{
    Type PaneType { get; }

    string GetTitle(int toolWindowId);

    Task<FrameworkElement> CreateAsync(int toolWindowId, CancellationToken cancellationToken);
}

    public abstract class BaseDIToolWindowRegistration<T, TToolWindowProvider> : BaseToolWindow<T> where T : BaseToolWindow<T>, new() where TToolWindowProvider : IToolWindowProvider
    {
        private readonly TToolWindowProvider toolWindowProvider;

        public BaseDIToolWindowRegistration()
        {
            static Type GetToolkitPackageType()
            {
                // StackTrace / Assembly.GetCallingAssembly / Attribute / Source generator
            }
            var toolkitPackageType = GetToolkitPackageType();

// see https://github.com/VsixCommunity/Community.VisualStudio.Toolkit.DependencyInjection/issues/13 for 
// SToolkitServiceProviderContainer
// a static ServiceProvider property would be better

#pragma warning disable VSTHRD104 // Offer async methods
            var serviceProvider = ThreadHelper.JoinableTaskFactory.Run(async () => {
                await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
                var toolkitServiceProviderContainer = await VS.GetRequiredServiceAsync<SToolkitServiceProviderContainer, IToolkitServiceProviderContainer>();
                return toolkitServiceProviderContainer.Get(toolkitPackageType);
            });
#pragma warning restore VSTHRD104 // Offer async methods
            toolWindowProvider = (TToolWindowProvider)serviceProvider.GetRequiredService(typeof(TToolWindowProvider));
        }


        public override Type PaneType => toolWindowProvider.PaneType;

        public override Task<FrameworkElement> CreateAsync(int toolWindowId, CancellationToken cancellationToken)
        {
            return toolWindowProvider.CreateAsync(toolWindowId, cancellationToken);
        }

        public override string GetTitle(int toolWindowId)
        {
            return toolWindowProvider.GetTitle(toolWindowId);
        }
    }

    public static class Extensions
    {
       // AS BEFORE
        public static IServiceCollection RegisterCommands(this IServiceCollection services, ServiceLifetime serviceLifetime, params Assembly[] assemblies){}
        

        private static readonly Type _registrationType = typeof(BaseDIToolWindowRegistration<,>);
        private static Type? GetToolWindowProviderType(Type derivedType)
        {
            if (derivedType == null) return null;

            var baseType = derivedType.BaseType;
            while (baseType != null)
            {
                if (baseType.IsGenericType)
                {
                    var genericTypeDefinition = baseType.GetGenericTypeDefinition();
                    if (genericTypeDefinition == _registrationType)
                    {
                        return baseType.GenericTypeArguments[1];
                    }
                }
                baseType = baseType.BaseType;
            }
            return null;
        }

        public static IServiceCollection RegisterToolWindows(this IServiceCollection services, params Assembly[] assemblies)
        {
            if (!(assemblies?.Any() ?? false))
                assemblies = new Assembly[] { Assembly.GetCallingAssembly() };
            foreach (var assembly in assemblies)
            {
                var toolWindowProviderTypes = assembly.GetTypes().Select(t => GetToolWindowProviderType(t)).Where(t => t != null);

                foreach (var toolWindowProviderType in toolWindowProviderTypes)
                    services.Add(new ServiceDescriptor(toolWindowProviderType, toolWindowProviderType, ServiceLifetime.Singleton));
            }
            return services;

        }
    }

Usage

    public sealed class TestDIPackage : MicrosoftDIToolkitPackage<TestDIPackage>
    {
        protected override void InitializeServices(IServiceCollection services)
        {
            services.RegisterCommands(ServiceLifetime.Singleton);
            services.RegisterToolWindows();
            // register dependencies
            services.AddMEF(); // extension method available on request
        }

        protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
        {
            await base.InitializeAsync(cancellationToken, progress);
            this.RegisterToolWindows(); // as before
        }
    }

[Export(typeof(ProxyMeffed))]
public class ProxyMeffed
{
    public string GetTitle() => "Proxy !";

}
public class ProxyToolWindowProvider : IToolWindowProvider
{
    private readonly ProxyMeffed meffed;
    public ProxyToolWindowProvider(ProxyMeffed meffed)
    {
        this.meffed = meffed;
    }

    public Type PaneType => typeof(Pane);

    public async System.Threading.Tasks.Task<FrameworkElement> CreateAsync(int toolWindowId, CancellationToken cancellationToken)
    {
        await Task.Delay(0);
        return new ToolWindowControl();
    }

    public string GetTitle(int toolWindowId)
    {
        return meffed.GetTitle();
    }

    [Guid("82563071-D155-4ED3-AD14-CC434AC00D29")]
    internal class Pane : ToolWindowPane
    {
        public Pane()
        {
            // Set an image icon for the tool window
            BitmapImageMoniker = KnownMonikers.StatusInformation;
        }
    }


}

public class ProxyToolWindow : BaseDIToolWindowRegistration<ProxyToolWindow, ProxyToolWindowProvider> { }

We need to get to the specific service provider from the BaseDIToolWindowRegistration constructor. As mentioned in #13 the only current method, using the Vs service added DIToolkitPackage InitailizeAsync, is broken. The code above uses the quick and dirty workaround suggested in the issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions