gpt4 book ai didi

c# - 使用 NInject 在 WPF 中注入(inject)没有无参数构造函数的 View 模型类

转载 作者:太空狗 更新时间:2023-10-30 00:39:55 25 4
gpt4 key购买 nike

我正在使用 NInject 解决我的第一个 WPF 应用程序的依赖关系。以下是我的代码片段。

我的 App.xaml.cs 是这样的。

public partial class App : Application
{
private IKernel container;

protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ConfigureContainer();
ComposeObjects();
}

private void ComposeObjects()
{
Current.MainWindow = this.container.Get<MainWindow>();
}

private void ConfigureContainer()
{
this.container = new StandardKernel();
container.Bind<ISystemEvents>().To<MySystemEvents>();

}
}

App.xaml 是这样的。

<Application x:Class="Tracker.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>

</Application.Resources>
</Application>

主窗口.xaml。

<Window x:Class="Tracker.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewmodel="clr-namespace:Tracker.ViewModel"
Title="MainWindow" Height="150" Width="350">
<Window.DataContext>
<viewmodel:TrackerViewModel>
</viewmodel:TrackerViewModel>
</Window.DataContext>
<Grid>
</Grid>
</Window>

主窗口.xaml.cs

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}

和 View 模型

internal class TrackerViewModel : System.ComponentModel.INotifyPropertyChanged
{
public TrackerViewModel(ISystemEvents systemEvents)
{
systemEvents.SessionSwitch += SystemEvents_SessionSwitch;
}

private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
}
}

现在,当我启动应用程序时,出现异常 InitializeComponent() 方法中的 PresentationFramework.dll 中发生类型为“System.NullReferenceException”的未处理异常

我知道它是因为 viewmodel 类没有无参数构造函数。但我无法理解为什么依赖注入(inject)器无法解决这个问题?我做错了什么吗?

如有任何帮助,我们将不胜感激。

最佳答案

首先推荐看书Dependency Injection in .NET ,尤其是有关 WPF 的部分。但即使您不阅读它,code download 中也有一个有用的示例为了这本书。


您已经确定需要从 App.xaml 文件中删除 StartupUri="MainWindow.xaml"

但是,当使用 DI 时,您不能以声明方式连接 DataContext,否则它只能使用默认构造函数。

<Window x:Class="WpfWithNinject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="150" Width="350">

</Window>

当涉及到 DI 时,WPF 中使用的模式有点令人困惑。主要问题是,如果您希望 ViewModel 能够控制自己的窗口环境,则 MainWindow 与其 ViewModel 之间存在循环依赖问题,因此您需要制作一个 Abstract Factory。为了实例化 ViewModel 以满足依赖关系。

创建 ViewModel 工厂

internal interface ITrackerViewModelFactory
{
TrackerViewModel Create(IWindow window);
}

internal class TrackerViewModelFactory : ITrackerViewModelFactory
{
private readonly ISystemEvents systemEvents;

public TrackerViewModelFactory(ISystemEvents systemEvents)
{
if (systemEvents == null)
{
throw new ArgumentNullException("systemEvents");
}

this.systemEvents = systemEvents;
}

public TrackerViewModel Create(IWindow window)
{
if (window == null)
{
throw new ArgumentNullException("window");
}

return new TrackerViewModel(this.systemEvents, window);
}
}

TrackerViewModel 也需要进行一些修改,以便它可以将 IWindow 接受到其构造函数中。这允许 TrackerViewModel 控制它自己的窗口环境,例如向用户显示模态对话框。

internal class TrackerViewModel : System.ComponentModel.INotifyPropertyChanged
{
private readonly IWindow window;

public TrackerViewModel(ISystemEvents systemEvents, IWindow window)
{
if (systemEvents == null)
{
throw new ArgumentNullException("systemEvents");
}
if (window == null)
{
throw new ArgumentNullException("window");
}

systemEvents.SessionSwitch += SystemEvents_SessionSwitch;
this.window = window;
}

private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
}

public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
}

调整窗口

您需要使用窗口的抽象类型 IWindow 和帮助管理每个窗口的 DI 的抽象 WindowAdapter 来稍微修复框架.

internal interface IWindow
{
void Close();

IWindow CreateChild(object viewModel);

void Show();

bool? ShowDialog();
}

internal class WindowAdapter : IWindow
{
private readonly Window wpfWindow;

public WindowAdapter(Window wpfWindow)
{
if (wpfWindow == null)
{
throw new ArgumentNullException("window");
}

this.wpfWindow = wpfWindow;
}

#region IWindow Members

public virtual void Close()
{
this.wpfWindow.Close();
}

public virtual IWindow CreateChild(object viewModel)
{
var cw = new ContentWindow();
cw.Owner = this.wpfWindow;
cw.DataContext = viewModel;
WindowAdapter.ConfigureBehavior(cw);

return new WindowAdapter(cw);
}

public virtual void Show()
{
this.wpfWindow.Show();
}

public virtual bool? ShowDialog()
{
return this.wpfWindow.ShowDialog();
}

#endregion

protected Window WpfWindow
{
get { return this.wpfWindow; }
}

private static void ConfigureBehavior(ContentWindow cw)
{
cw.WindowStartupLocation = WindowStartupLocation.CenterOwner;
cw.CommandBindings.Add(new CommandBinding(PresentationCommands.Accept, (sender, e) => cw.DialogResult = true));
}
}

public static class PresentationCommands
{
private readonly static RoutedCommand accept = new RoutedCommand("Accept", typeof(PresentationCommands));

public static RoutedCommand Accept
{
get { return PresentationCommands.accept; }
}
}

然后我们有一个专门用于 MainWindow 的窗口适配器,它确保 DataContext 属性使用 ViewModel 正确初始化。

internal class MainWindowAdapter : WindowAdapter
{
private readonly ITrackerViewModelFactory vmFactory;
private bool initialized;

public MainWindowAdapter(Window wpfWindow, ITrackerViewModelFactory viewModelFactory)
: base(wpfWindow)
{
if (viewModelFactory == null)
{
throw new ArgumentNullException("viewModelFactory");
}

this.vmFactory = viewModelFactory;
}

#region IWindow Members

public override void Close()
{
this.EnsureInitialized();
base.Close();
}

public override IWindow CreateChild(object viewModel)
{
this.EnsureInitialized();
return base.CreateChild(viewModel);
}

public override void Show()
{
this.EnsureInitialized();
base.Show();
}

public override bool? ShowDialog()
{
this.EnsureInitialized();
return base.ShowDialog();
}

#endregion

private void DeclareKeyBindings(TrackerViewModel vm)
{
//this.WpfWindow.InputBindings.Add(new KeyBinding(vm.RefreshCommand, new KeyGesture(Key.F5)));
//this.WpfWindow.InputBindings.Add(new KeyBinding(vm.InsertProductCommand, new KeyGesture(Key.Insert)));
//this.WpfWindow.InputBindings.Add(new KeyBinding(vm.EditProductCommand, new KeyGesture(Key.Enter)));
//this.WpfWindow.InputBindings.Add(new KeyBinding(vm.DeleteProductCommand, new KeyGesture(Key.Delete)));
}

private void EnsureInitialized()
{
if (this.initialized)
{
return;
}

var vm = this.vmFactory.Create(this);
this.WpfWindow.DataContext = vm;
this.DeclareKeyBindings(vm);

this.initialized = true;
}
}

组合根

最后,您需要一种创建对象图的方法。您在正确的地方这样做,但是将其分成许多步骤并没有给自己带来任何好处。将容器作为应用程序级变量不一定是好事 - 它打开容器作为 service locator 滥用。 .

public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);

// Begin Composition Root
var container = new StandardKernel();

// Register types
container.Bind<ISystemEvents>().To<MySystemEvents>();
container.Bind<ITrackerViewModelFactory>().To<TrackerViewModelFactory>();
container.Bind<Window>().To<MainWindow>();
container.Bind<IWindow>().To<MainWindowAdapter>();

// Build the application object graph
var window = container.Get<IWindow>();

// Show the main window.
window.Show();

// End Composition Root
}
}

我认为您遇到的主要问题是您需要确保在 MainWindow 上手动调用 Show()

如果您真的想将注册分解为另一个步骤,您可以使用一个或多个 Ninject Modules 来实现。 .

using Ninject.Modules;
using System.Windows;

public class MyApplicationModule : NinjectModule
{
public override void Load()
{
Bind<ISystemEvents>().To<MySystemEvents>();
Bind<ITrackerViewModelFactory>().To<TrackerViewModelFactory>();
Bind<Window>().To<MainWindow>();
Bind<IWindow>().To<MainWindowAdapter>();
}
}

然后 App.xaml.cs 文件将如下所示:

public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);

// Begin Composition Root
new StandardKernel(new MyApplicationModule()).Get<IWindow>().Show();

// End Composition Root
}
}

关于c# - 使用 NInject 在 WPF 中注入(inject)没有无参数构造函数的 View 模型类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32548317/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com