我正在使用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>
MainWindow.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>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
和viewmodel
internal class TrackerViewModel : System.ComponentModel.INotifyPropertyChanged
{
public TrackerViewModel(ISystemEvents systemEvents)
{
systemEvents.SessionSwitch += SystemEvents_SessionSwitch;
}
private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
}
}
现在,当我启动应用程序时,An unhandled exception of type 'System.NullReferenceException' occurred in PresentationFramework.dll
方法中出现异常InitializeComponent()
。
我知道它因为viewmodel类没有无参数构造函数。但我无法理解为什么依赖注入器无法解决这个问题?我做错了吗?
非常感谢任何帮助。
答案 0 :(得分:7)
首先,我建议您阅读本书Dependency Injection in .NET,尤其是关于WPF的部分。但即使你不读它,本书的code download也有一个有用的例子。
您已经知道需要从StartupUri="MainWindow.xaml"
文件中删除App.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因此可以满足依赖性。
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
来修复框架,并使用抽象来帮助管理每个窗口的WindowAdapter
的DI。
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
}
}
我认为您遇到的主要问题是您需要确保手动调用Show()
上的MainWindow
。
如果您确实希望将注册分解为另一个步骤,则可以使用一个或多个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
}
}