带有嵌套泛型的自定义 Window 会产生没有内部异常的 NullReferencEException
Custom Window with nested Generics produces NullReferencEException without Inner Exception
我创建了耦合 Window
<-> Controller
(我更喜欢称呼我的 ViewModels)class 关系,目的是处理大量样板代码window 和控制器使用。
不幸的是,这似乎导致了某种形式的运行时问题。我相信问题是我的 window 的 DataContext 的某种形式的错误路径,尽管事实上我还没有在 window...
上声明绑定
public abstract class ModelledWindow<TWindow, TController> : Window
where TWindow : ModelledWindow<TWindow, TController>
where TController : WindowControllerBase<TWindow, TController>
{
protected TController Controller { get; }
public ModelledWindow(TController controller)
{
DataContext = Controller = controller ?? throw new ArgumentNullException(nameof(controller));
}
}
public abstract class ControllerBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected T Mutate<T>(T value, [CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
return value;
}
}
public abstract class WindowControllerBase<TWindow, TController> : ControllerBase
where TWindow : ModelledWindow<TWindow, TController>
where TController : WindowControllerBase<TWindow, TController>
{
public ICommand ShowWindowCmd { get; }
private Func<TWindow> WindowBuilder { get; }
public WindowControllerBase(CommandFactory commandFactory, Func<TWindow> windowBuilder)
{
ShowWindowCmd = commandFactory.Compose(ShowWindow, CanShowWindow);
WindowBuilder = windowBuilder ?? throw new ArgumentNullException(nameof(windowBuilder));
}
private TWindow _window;
private void ShowWindow()
{
_window ??= WindowBuilder();
_window.Show();
}
private Visibility Visibility => _window?.Visibility ?? Visibility.Hidden;
private bool CanShowWindow() => Visibility != Visibility.Visible;
}
MainWindow.xaml:
<local:ModelledWindow
x:Class="PoeAutoClipboard.Wpf.MainWindow"
x:TypeArguments="local:MainWindow,local:MainWindowController"
xmlns:local="clr-namespace:PoeAutoClipboard.Wpf"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="PoeAutoClipboard"
Height="46"
Width="155"
>
<Grid>
</Grid>
</local:ModelledWindow>
MainWindow.xaml.cs:
public partial class MainWindow : ModelledWindow<MainWindow, MainWindowController>
{
public MainWindow(MainWindowController controller) : base(controller)
{
InitializeComponent();
}
}
public class MainWindowController : WindowControllerBase<MainWindow, MainWindowController>
{
public MainWindowController(
CommandFactory commandFactory,
Func<MainWindow> windowBuilder
) : base(commandFactory, windowBuilder)
{
}
}
我在我的应用程序层中通过依赖注入自动构建所有内容,如下所示:
public partial class App : Application
{
private const string ConfigurationPath = "AppSettings.json";
private ConfigurationController ConfigController { get; set; }
public App()
{
var config = new ConfigurationBuilder()
.AddJsonFile(ConfigurationPath, true, true)
.Build();
ConfigController = config.Get<ConfigurationController>() ?? new ConfigurationController();
}
private void App_OnStartup(object sender, StartupEventArgs e)
{
var services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
var mainWindow = serviceProvider.GetRequiredService<MainWindowController>();
mainWindow.ShowWindowCmd.Execute(null);
}
private void ConfigureServices(IServiceCollection services)
{
services.UseMVC<App>();
// Remove the auto registered "blank" config and add our loaded config VM instead
services.Remove(ServiceDescriptor.Singleton(typeof(ConfigurationController)));
services.AddSingleton(ConfigController);
// Services
services.AddSingleton<CommandFactory>();
}
}
当我检查 MainWindowController
和 MainWindow
时,一切似乎都很好。 Controller 不为 null,DataContext 已按我预期的方式分配。我可以检查 MainWindow.DataContext
及其预期的结果(MainWindowController
的非空实例)
此外,MainWindow.g.i.cs 似乎完全按照预期工作,它的构建是这样的:
public partial class MainWindow : PoeAutoClipboard.Wpf.ModelledWindow<PoeAutoClipboard.Wpf.MainWindow, PoeAutoClipboard.Wpf.MainWindowController>, System.Windows.Markup.IComponentConnector {
...
}
一切都编译并运行良好,但后来我在 MainWindow 的构造函数中遇到运行时异常:
private void App_OnStartup(object sender, StartupEventArgs e)
{
var services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
var mainWindow = serviceProvider.GetRequiredService<MainWindowController>();
mainWindow.ShowWindowCmd.Execute(null); <<<<<< Here (Calling method up stack)
}
public MainWindow(MainWindowController controller) : base(controller)
{
InitializeComponent(); <<<<<< Here (Actual exception thrown here)
}
例外是... 非常无用。
留言:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
堆栈跟踪:
at System.Windows.Markup.WpfXamlLoader.TransformNodes(XamlReader xamlReader, XamlObjectWriter xamlWriter, Boolean onlyLoadOneNode, Boolean skipJournaledProperties, Boolean shouldPassLineNumberInfo, IXamlLineInfo xamlLineInfo, IXamlLineInfoConsumer xamlLineInfoConsumer, XamlContextStack`1 stack, IStyleConnector styleConnector)
at System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings settings, Uri baseUri)
at System.Windows.Markup.WpfXamlLoader.LoadBaml(XamlReader xamlReader, Boolean skipJournaledProperties, Object rootObject, XamlAccessLevel accessLevel, Uri baseUri)
at System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream)
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
at PoeAutoClipboard.Wpf.MainWindow.InitializeComponent() in D:\Documents\Projects\Programming\CSharp\PoeAutoClipboard\PoeAutoClipboard.Wpf\MainWindow.xaml:line 1
at PoeAutoClipboard.Wpf.MainWindow..ctor(MainWindowController controller) in D:\Documents\Projects\Programming\CSharp\PoeAutoClipboard\PoeAutoClipboard.Wpf\MainWindow.xaml.cs:line 14
at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at PoeAutoClipboard.Wpf.WindowControllerBase`2.ShowWindow() in D:\Documents\Projects\Programming\CSharp\PoeAutoClipboard\PoeAutoClipboard.Wpf\ControllerBase.cs:line 34
at PoeAutoClipboard.Wpf.Extensions.CommandFactoryExtensions.<>c__DisplayClass4_0.<Compose>b__0(Object p) in D:\Documents\Projects\Programming\CSharp\PoeAutoClipboard\PoeAutoClipboard.Wpf\Extensions\CommandFactoryExtensions.cs:line 33
at PoeAutoClipboard.Wpf.Command.Execute(Object parameter) in D:\Documents\Projects\Programming\CSharp\PoeAutoClipboard\PoeAutoClipboard.Wpf\Command.cs:line 23
at PoeAutoClipboard.Wpf.App.App_OnStartup(Object sender, StartupEventArgs e) in D:\Documents\Projects\Programming\CSharp\PoeAutoClipboard\PoeAutoClipboard.Wpf\App.xaml.cs:line 38
at System.Windows.Application.OnStartup(StartupEventArgs e)
at System.Windows.Application.<.ctor>b__1_0(Object unused)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
没有内部异常。
有人知道我在这里错过了什么吗?此外,如果我将 App Startup 中的代码更改为此,它会抛出完全相同的异常:
private void App_OnStartup(object sender, StartupEventArgs e)
{
var services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
var mainWindow = serviceProvider.GetRequiredService<MainWindow>();
mainWindow.Show(); <<<<<< Breaks here, same exception, throws on same spot in MainWindow Constructor
}
这个问题似乎是泛型问题,我可以通过简单地手动添加一个具体类型为泛型的“辅助”中间基础抽象 class 来解决这个问题。我还需要第二个泛型来满足项目需要,但最终结果如下所示:
ControlledWindowBase.cs :
public abstract class ControlledWindowBase<TWindow, TController> : Window
where TWindow : ControlledWindowBase<TWindow, TController>
where TController : WindowControllerBase<TWindow, TController>
{
protected TController Controller { get; }
public ControlledWindowBase(TController controller)
{
DataContext = Controller = controller ?? throw new ArgumentNullException(nameof(controller));
}
}
WindowControllerBase.cs :
public abstract class WindowControllerBase<TWindow, TController> : ControllerBase
where TWindow : ControlledWindowBase<TWindow, TController>
where TController : WindowControllerBase<TWindow, TController>
{
public ICommand ShowCmd { get; private set; }
public ICommand HideCmd { get; private set; }
private Func<TWindow> WindowFactory { get; }
public WindowControllerBase(CommandFactory commandFactory, Func<TWindow> windowFactory)
{
ShowCmd = commandFactory.Compose(Show, CanShow);
HideCmd = commandFactory.Compose(Hide, CanHide);
WindowFactory = windowFactory ?? throw new ArgumentNullException(nameof(windowFactory));
}
private TWindow _window;
private void Show()
{
_window ??= WindowFactory();
_window.Show();
}
private bool CanShow()
{
return _window == null || _window.Visibility != System.Windows.Visibility.Visible;
}
private void Hide()
{
_window?.Hide();
}
private bool CanHide()
{
return _window != null && _window.Visibility == System.Windows.Visibility.Visible;
}
}
MainWindow.xaml.cs :
public abstract class MainWindowBase : ControlledWindowBase<MainWindow, MainWindowController>
{
public MainWindowBase(MainWindowController controller) : base(controller) { }
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : MainWindowBase
{
public MainWindow(MainWindowController controller) : base(controller)
{
InitializeComponent();
}
private void Window_OnDeactivated(object sender, EventArgs e)
{
Activate();
}
private void Window_OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
DragMove();
Controller.ConfigController.InitialTop = Top;
Controller.ConfigController.InitialLeft = Left;
}
}
}
public class MainWindowController : WindowControllerBase<MainWindow, MainWindowController>
{
public ApplicationController ApplicationController { get; }
public ConfigWindowController ConfigWindowController { get; }
public ConfigurationController ConfigController { get; }
public MainWindowController(
CommandFactory commandFactory,
Func<MainWindow> windowFactory,
ApplicationController applicationController,
ConfigWindowController configWindowController,
ConfigurationController configController
) : base(commandFactory, windowFactory)
{
ApplicationController = applicationController ?? throw new ArgumentNullException(nameof(applicationController));
ConfigWindowController = configWindowController ?? throw new ArgumentNullException(nameof(configWindowController));
ConfigController = configController ?? throw new ArgumentNullException(nameof(configController));
}
}
MainWindow.xaml :
<local:MainWindowBase
x:Class="PoeAutoClipboard.Wpf.MainWindow"
xmlns:local="clr-namespace:PoeAutoClipboard.Wpf"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="Transparent"
Title="PoeAutoClipboard"
Height="46"
Width="155"
WindowStyle="None"
AllowsTransparency="True"
Cursor="Hand"
ShowInTaskbar="False"
Topmost="true"
Icon="/Application.ico"
MouseDown="Window_OnMouseDown"
Deactivated="Window_OnDeactivated"
>
<Grid
Background="Transparent"
>
<tb:TaskbarIcon
IconSource="/Application.ico"
ToolTipText="PoeAutoClipboard">
<tb:TaskbarIcon.ContextMenu >
<ContextMenu>
<MenuItem
Header="Config"
InputGestureText="Ctrl+F12"
Command="{Binding ConfigWindowController.ShowCmd}"
>
<MenuItem.Icon>
<Image Source="/Gear.ico"/>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem
Header="Exit"
Command="{Binding ApplicationController.ExitCmd}"
>
<MenuItem.Icon>
<Image Source="/X.ico"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
<Label
Background="White"
BorderThickness="2"
BorderBrush="Black"
Content="Test"
HorizontalContentAlignment="Center"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="93"
/>
</Grid>
</local:MainWindowBase>
注意几点:
MainWindowBase
在 MainWindow.xaml.cs
中的存在纯粹是为了泛型的具体类型
由于它们在继承 MainWindow : MainWindowBase
时没有泛型,因此 xaml 不需要 x:TypeArguments
属性。
这似乎是让 WPF 与泛型一起玩得很好的“魔法酱”,一个单行抽象 class 来“强制”类型,你只需要为每个类型制作一个你的 window 的代码在后面。
我创建了耦合 Window
<-> Controller
(我更喜欢称呼我的 ViewModels)class 关系,目的是处理大量样板代码window 和控制器使用。
不幸的是,这似乎导致了某种形式的运行时问题。我相信问题是我的 window 的 DataContext 的某种形式的错误路径,尽管事实上我还没有在 window...
上声明绑定public abstract class ModelledWindow<TWindow, TController> : Window
where TWindow : ModelledWindow<TWindow, TController>
where TController : WindowControllerBase<TWindow, TController>
{
protected TController Controller { get; }
public ModelledWindow(TController controller)
{
DataContext = Controller = controller ?? throw new ArgumentNullException(nameof(controller));
}
}
public abstract class ControllerBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected T Mutate<T>(T value, [CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
return value;
}
}
public abstract class WindowControllerBase<TWindow, TController> : ControllerBase
where TWindow : ModelledWindow<TWindow, TController>
where TController : WindowControllerBase<TWindow, TController>
{
public ICommand ShowWindowCmd { get; }
private Func<TWindow> WindowBuilder { get; }
public WindowControllerBase(CommandFactory commandFactory, Func<TWindow> windowBuilder)
{
ShowWindowCmd = commandFactory.Compose(ShowWindow, CanShowWindow);
WindowBuilder = windowBuilder ?? throw new ArgumentNullException(nameof(windowBuilder));
}
private TWindow _window;
private void ShowWindow()
{
_window ??= WindowBuilder();
_window.Show();
}
private Visibility Visibility => _window?.Visibility ?? Visibility.Hidden;
private bool CanShowWindow() => Visibility != Visibility.Visible;
}
MainWindow.xaml:
<local:ModelledWindow
x:Class="PoeAutoClipboard.Wpf.MainWindow"
x:TypeArguments="local:MainWindow,local:MainWindowController"
xmlns:local="clr-namespace:PoeAutoClipboard.Wpf"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="PoeAutoClipboard"
Height="46"
Width="155"
>
<Grid>
</Grid>
</local:ModelledWindow>
MainWindow.xaml.cs:
public partial class MainWindow : ModelledWindow<MainWindow, MainWindowController>
{
public MainWindow(MainWindowController controller) : base(controller)
{
InitializeComponent();
}
}
public class MainWindowController : WindowControllerBase<MainWindow, MainWindowController>
{
public MainWindowController(
CommandFactory commandFactory,
Func<MainWindow> windowBuilder
) : base(commandFactory, windowBuilder)
{
}
}
我在我的应用程序层中通过依赖注入自动构建所有内容,如下所示:
public partial class App : Application
{
private const string ConfigurationPath = "AppSettings.json";
private ConfigurationController ConfigController { get; set; }
public App()
{
var config = new ConfigurationBuilder()
.AddJsonFile(ConfigurationPath, true, true)
.Build();
ConfigController = config.Get<ConfigurationController>() ?? new ConfigurationController();
}
private void App_OnStartup(object sender, StartupEventArgs e)
{
var services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
var mainWindow = serviceProvider.GetRequiredService<MainWindowController>();
mainWindow.ShowWindowCmd.Execute(null);
}
private void ConfigureServices(IServiceCollection services)
{
services.UseMVC<App>();
// Remove the auto registered "blank" config and add our loaded config VM instead
services.Remove(ServiceDescriptor.Singleton(typeof(ConfigurationController)));
services.AddSingleton(ConfigController);
// Services
services.AddSingleton<CommandFactory>();
}
}
当我检查 MainWindowController
和 MainWindow
时,一切似乎都很好。 Controller 不为 null,DataContext 已按我预期的方式分配。我可以检查 MainWindow.DataContext
及其预期的结果(MainWindowController
的非空实例)
此外,MainWindow.g.i.cs 似乎完全按照预期工作,它的构建是这样的:
public partial class MainWindow : PoeAutoClipboard.Wpf.ModelledWindow<PoeAutoClipboard.Wpf.MainWindow, PoeAutoClipboard.Wpf.MainWindowController>, System.Windows.Markup.IComponentConnector {
...
}
一切都编译并运行良好,但后来我在 MainWindow 的构造函数中遇到运行时异常:
private void App_OnStartup(object sender, StartupEventArgs e)
{
var services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
var mainWindow = serviceProvider.GetRequiredService<MainWindowController>();
mainWindow.ShowWindowCmd.Execute(null); <<<<<< Here (Calling method up stack)
}
public MainWindow(MainWindowController controller) : base(controller)
{
InitializeComponent(); <<<<<< Here (Actual exception thrown here)
}
例外是... 非常无用。
留言: System.NullReferenceException: 'Object reference not set to an instance of an object.'
堆栈跟踪:
at System.Windows.Markup.WpfXamlLoader.TransformNodes(XamlReader xamlReader, XamlObjectWriter xamlWriter, Boolean onlyLoadOneNode, Boolean skipJournaledProperties, Boolean shouldPassLineNumberInfo, IXamlLineInfo xamlLineInfo, IXamlLineInfoConsumer xamlLineInfoConsumer, XamlContextStack`1 stack, IStyleConnector styleConnector)
at System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings settings, Uri baseUri)
at System.Windows.Markup.WpfXamlLoader.LoadBaml(XamlReader xamlReader, Boolean skipJournaledProperties, Object rootObject, XamlAccessLevel accessLevel, Uri baseUri)
at System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream)
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
at PoeAutoClipboard.Wpf.MainWindow.InitializeComponent() in D:\Documents\Projects\Programming\CSharp\PoeAutoClipboard\PoeAutoClipboard.Wpf\MainWindow.xaml:line 1
at PoeAutoClipboard.Wpf.MainWindow..ctor(MainWindowController controller) in D:\Documents\Projects\Programming\CSharp\PoeAutoClipboard\PoeAutoClipboard.Wpf\MainWindow.xaml.cs:line 14
at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at PoeAutoClipboard.Wpf.WindowControllerBase`2.ShowWindow() in D:\Documents\Projects\Programming\CSharp\PoeAutoClipboard\PoeAutoClipboard.Wpf\ControllerBase.cs:line 34
at PoeAutoClipboard.Wpf.Extensions.CommandFactoryExtensions.<>c__DisplayClass4_0.<Compose>b__0(Object p) in D:\Documents\Projects\Programming\CSharp\PoeAutoClipboard\PoeAutoClipboard.Wpf\Extensions\CommandFactoryExtensions.cs:line 33
at PoeAutoClipboard.Wpf.Command.Execute(Object parameter) in D:\Documents\Projects\Programming\CSharp\PoeAutoClipboard\PoeAutoClipboard.Wpf\Command.cs:line 23
at PoeAutoClipboard.Wpf.App.App_OnStartup(Object sender, StartupEventArgs e) in D:\Documents\Projects\Programming\CSharp\PoeAutoClipboard\PoeAutoClipboard.Wpf\App.xaml.cs:line 38
at System.Windows.Application.OnStartup(StartupEventArgs e)
at System.Windows.Application.<.ctor>b__1_0(Object unused)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
没有内部异常。
有人知道我在这里错过了什么吗?此外,如果我将 App Startup 中的代码更改为此,它会抛出完全相同的异常:
private void App_OnStartup(object sender, StartupEventArgs e)
{
var services = new ServiceCollection();
ConfigureServices(services);
var serviceProvider = services.BuildServiceProvider();
var mainWindow = serviceProvider.GetRequiredService<MainWindow>();
mainWindow.Show(); <<<<<< Breaks here, same exception, throws on same spot in MainWindow Constructor
}
这个问题似乎是泛型问题,我可以通过简单地手动添加一个具体类型为泛型的“辅助”中间基础抽象 class 来解决这个问题。我还需要第二个泛型来满足项目需要,但最终结果如下所示:
ControlledWindowBase.cs :
public abstract class ControlledWindowBase<TWindow, TController> : Window
where TWindow : ControlledWindowBase<TWindow, TController>
where TController : WindowControllerBase<TWindow, TController>
{
protected TController Controller { get; }
public ControlledWindowBase(TController controller)
{
DataContext = Controller = controller ?? throw new ArgumentNullException(nameof(controller));
}
}
WindowControllerBase.cs :
public abstract class WindowControllerBase<TWindow, TController> : ControllerBase
where TWindow : ControlledWindowBase<TWindow, TController>
where TController : WindowControllerBase<TWindow, TController>
{
public ICommand ShowCmd { get; private set; }
public ICommand HideCmd { get; private set; }
private Func<TWindow> WindowFactory { get; }
public WindowControllerBase(CommandFactory commandFactory, Func<TWindow> windowFactory)
{
ShowCmd = commandFactory.Compose(Show, CanShow);
HideCmd = commandFactory.Compose(Hide, CanHide);
WindowFactory = windowFactory ?? throw new ArgumentNullException(nameof(windowFactory));
}
private TWindow _window;
private void Show()
{
_window ??= WindowFactory();
_window.Show();
}
private bool CanShow()
{
return _window == null || _window.Visibility != System.Windows.Visibility.Visible;
}
private void Hide()
{
_window?.Hide();
}
private bool CanHide()
{
return _window != null && _window.Visibility == System.Windows.Visibility.Visible;
}
}
MainWindow.xaml.cs :
public abstract class MainWindowBase : ControlledWindowBase<MainWindow, MainWindowController>
{
public MainWindowBase(MainWindowController controller) : base(controller) { }
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : MainWindowBase
{
public MainWindow(MainWindowController controller) : base(controller)
{
InitializeComponent();
}
private void Window_OnDeactivated(object sender, EventArgs e)
{
Activate();
}
private void Window_OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
DragMove();
Controller.ConfigController.InitialTop = Top;
Controller.ConfigController.InitialLeft = Left;
}
}
}
public class MainWindowController : WindowControllerBase<MainWindow, MainWindowController>
{
public ApplicationController ApplicationController { get; }
public ConfigWindowController ConfigWindowController { get; }
public ConfigurationController ConfigController { get; }
public MainWindowController(
CommandFactory commandFactory,
Func<MainWindow> windowFactory,
ApplicationController applicationController,
ConfigWindowController configWindowController,
ConfigurationController configController
) : base(commandFactory, windowFactory)
{
ApplicationController = applicationController ?? throw new ArgumentNullException(nameof(applicationController));
ConfigWindowController = configWindowController ?? throw new ArgumentNullException(nameof(configWindowController));
ConfigController = configController ?? throw new ArgumentNullException(nameof(configController));
}
}
MainWindow.xaml :
<local:MainWindowBase
x:Class="PoeAutoClipboard.Wpf.MainWindow"
xmlns:local="clr-namespace:PoeAutoClipboard.Wpf"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:tb="http://www.hardcodet.net/taskbar"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="Transparent"
Title="PoeAutoClipboard"
Height="46"
Width="155"
WindowStyle="None"
AllowsTransparency="True"
Cursor="Hand"
ShowInTaskbar="False"
Topmost="true"
Icon="/Application.ico"
MouseDown="Window_OnMouseDown"
Deactivated="Window_OnDeactivated"
>
<Grid
Background="Transparent"
>
<tb:TaskbarIcon
IconSource="/Application.ico"
ToolTipText="PoeAutoClipboard">
<tb:TaskbarIcon.ContextMenu >
<ContextMenu>
<MenuItem
Header="Config"
InputGestureText="Ctrl+F12"
Command="{Binding ConfigWindowController.ShowCmd}"
>
<MenuItem.Icon>
<Image Source="/Gear.ico"/>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem
Header="Exit"
Command="{Binding ApplicationController.ExitCmd}"
>
<MenuItem.Icon>
<Image Source="/X.ico"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
<Label
Background="White"
BorderThickness="2"
BorderBrush="Black"
Content="Test"
HorizontalContentAlignment="Center"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="93"
/>
</Grid>
</local:MainWindowBase>
注意几点:
MainWindowBase
在MainWindow.xaml.cs
中的存在纯粹是为了泛型的具体类型由于它们在继承
MainWindow : MainWindowBase
时没有泛型,因此 xaml 不需要x:TypeArguments
属性。
这似乎是让 WPF 与泛型一起玩得很好的“魔法酱”,一个单行抽象 class 来“强制”类型,你只需要为每个类型制作一个你的 window 的代码在后面。