-
Notifications
You must be signed in to change notification settings - Fork 8
Description
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
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.