使用NInject在WPF中注入没有无参数构造函数的viewmodel类 [英] injecting viewmodel class without parameterless constructor in WPF with NInject

查看:98
本文介绍了使用NInject在WPF中注入没有无参数构造函数的viewmodel类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm using NInject to resolve the dependency for my first WPF application. Following are my code snippets.

我的App.xaml.cs就像这样.

My App.xaml.cs goes like.

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就是这样.

App.xaml goes like this.

<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.

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

MainWindow.xaml.cs

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

和viewmodel

and 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.

Now when I launch the application, I get an exception An unhandled exception of type 'System.NullReferenceException' occurred in PresentationFramework.dll in InitializeComponent() method.

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

I know its because of the viewmodel class not have parameterless constructor. But I am not able to undestand why dependency injector is not able to resolve this? Am I doing something wrong?

任何帮助将不胜感激.

推荐答案

首先,我建议阅读

First of all, I recommend reading the book Dependency Injection in .NET, especially the section about WPF. But even if you don't read it, there is a helpful example in the code download for the book.

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

You have already worked out that you need to remove the StartupUri="MainWindow.xaml" from your App.xaml file.

但是,在使用DI时,请勿声明性地连接DataContext,否则它将只能与默认构造函数一起使用.

However, when using DI you must not wire up the DataContext declaratively otherwise it will only be able to work with the default constructor.

<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之间存在循环依赖问题,因此您需要创建

The pattern that is used in WPF is a bit confusing when it comes to DI. The main issue is that if you want your ViewModel to be able to control its own windowing environment, there is a circular dependency issue between the MainWindow and its ViewModel, so you will need to make an Abstract Factory in order to instantiate the ViewModel so the dependencies can be satisfied.

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可以控制自己的窗口环境,例如向用户显示模式对话框.

The TrackerViewModel also needs to have some rework so it can accept the IWindow into its constructor. This allows the TrackerViewModel to control its own windowing environment, such as showing modal dialog boxes to the user.

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;
}

适应窗口

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

Adapting the Window

You need to fix up the framework a bit with an abstract type for the windows, IWindow, and an abstraction to help manage DI of each of the windows, 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提供了专用的窗口适配器,该窗口适配器可确保使用ViewModel正确初始化DataContext属性.

Then we have a specialized window adapter for the MainWindow which ensures the DataContext property is initialized correctly with the 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;
    }
}

组成根

最后,您需要一种创建对象图的方法.您在正确的位置进行了此操作,但没有通过将其分成许多步骤来对自己有所帮助.并且将容器作为应用程序级变量不一定是一件好事-它以我认为您遇到的主要问题是您需要确保手动调用MainWindow上的Show().

I think the main issue you are having is that you need to ensure to call Show() on the MainWindow manually.

如果您确实希望将注册分到另一个步骤,则可以使用一个或多个

If you really do want to break the registration out into another step, you can do so by using one or more 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
    }
}

这篇关于使用NInject在WPF中注入没有无参数构造函数的viewmodel类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆