我使用NInject来解决我的第一个WPF应用程序的依赖关系。以下是我的代码片段:
My 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>();
}
}
应用程序。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)
{
}
}
现在当我启动应用程序时,我在InitializeComponent()
方法中得到一个异常An unhandled exception of type 'System.NullReferenceException' occurred in PresentationFramework.dll
。
我知道这是因为视图模型类没有无参数构造函数。但我不能理解为什么依赖注入器不能解决这个问题?我做错了什么吗?
首先,我推荐阅读。net中的依赖注入这本书,特别是关于WPF的部分。但是,即使你没有读过它,在本书的代码下载中也有一个有用的例子。
您已经计算出您需要从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能够控制它自己的窗口环境,在主窗口和它的ViewModel之间有一个循环依赖问题,所以你需要创建一个抽象工厂来实例化ViewModel,这样依赖关系才能得到满足。
创建ViewModel Factory
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;
}
}
组合根
最后,你需要一种方法来创建对象图。你在正确的地方做了这件事,但是你把它分成许多步骤对你自己没有任何好处。而且,将容器作为应用程序级别的变量并不一定是一件好事——它使容器作为服务定位器被滥用。
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
}
}
trackerviewmodel将由自动生成的xaml设计器代码实例化,而不是通过ninject。我从来没有使用过ninject,但我认为你需要配置容器知道你的viewModel,然后注入viewModel ninject来解决它和它的依赖关系:
public class MainWindow : Window
{
[Inject]
public TrackerViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = ViewModel;
}
}