0

I am using the Castle Windsor V3.2.1 in my WPF MVVM Application.

This is my Installer:

    public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
    {
        container.AddFacility<TypedFactoryFacility>();
        container.Register(Component.For<IAbstructFactory>().AsFactory());

        container


              .Register(Component.For<IShell>().ImplementedBy<Shell>().LifestyleTransient())

            .Register(Types
                          .FromAssemblyInDirectory(new AssemblyFilter(AssemblyDirectory + "\\Map"))
                          .Pick()
                          .If(x => x.IsPublic)
                          .If(x => x.GetInterfaces().Length > 0)
                          .WithService
                          .FirstInterface()
                          .LifestyleTransient())


            .Register(Component.For<MainWindow>().LifestyleTransient());
    }

NOTE: I am registering FromDirectory named Map.

This is my Map Project:

MapViewModel

public class MapViewModel : IMapViewModel
{
    #region IMapViewModel Members

    IMapView _theMapView;
    IMapModel _theMapModel;

    /// <summary>
    /// Gets or sets the view.
    /// </summary>
    /// <value>
    /// The view.
    /// </value>
    public IMapView TheView
    {
        get 
        {
            return _theMapView;
        }
        set
        {
            _theMapView = value;
            _theMapView.TheMapViewModel = this;
        }
    }


    /// <summary>
    /// Gets or sets the model.
    /// </summary>
    /// <value>
    /// The model.
    /// </value>
    public IMapModel TheModel
    {
        get
        {
            return _theMapModel;
        }
        set
        {
            _theMapModel = value;
            CreateView();
        }
    }

    /// <summary>
    /// Creates the view.
    /// </summary>
    private void CreateView()
    {
        TheView = new MapView();
    }

    #endregion

    #region IViewModel Members

    /// <summary>
    /// Gets the help.
    /// </summary>
    /// <value>
    /// The help.
    /// </value>
    public IHelpManager Help
    {
        get
        {
            return HelpManager.Instance;
        }
    }

    #endregion
}

MapView

    public partial class MapView : IMapView
    {
        private IMapViewModel _theMapViewModel;

        /// <summary>
        /// The View Model.
        /// </summary>
        public IMapViewModel TheMapViewModel 
        { 
            get
            {
                return _theMapViewModel;
            }
            set 
            {
                _theMapViewModel = value;
                DataContext = _theMapViewModel.TheModel;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MapView"/> class.
        /// </summary>
        public MapView()
        {
            InitializeComponent();
        }
   }

This is my MainWindow Class:

    /// <summary>
    /// Initializes a new instance of the <see cref="MainWindow"/> class.
    /// </summary>
    /// <param name="context">The context.</param>
    public MainWindow(IAbstructFactory context)
    {
        InitializeComponent();

        IMapViewModel theMapViewModel = context.Create<IMapViewModel>();
        context.Release(theMapViewModel);
    }

My Question Is:

I would like to think there is a better way of creating the view than I am currently using.

  1. I am creating the MapViewModel in the MainWindow.
  2. As you can see in the MapViewModel - When it is created, the MapModel is injected by the Windsor - which is working good.

When I tried to do the same with the MapView meaning - I am adding a call in the main like so:

IMapView theMapView = context.Create<IMapView>() 

I am getting nothing and application is crashed.

Why isn't the view being injected as the ViewModel do though they are both in the same assembly ?

4

2 回答 2

0

I don't have an exact answer for you. However, I do have a code sample and an explanation of how it works that you can fiddle with to hopefully help you get on the right track.

When I do MVVM, it looks like the following:

  1. I have a view that implements an interface that defines the view (much like you have). The view has zero code behind (caveat: using third party controls that weren't really developed with MVVM in mind). The view has a reference to the view model. Because I like to solve dependency inversion with containers and I like castle Windsor, it ends up that the view takes the view model as a constructor argument.

  2. The view model implements an interface that defines what the view model is supposed to do (also what you've got). The view model has absolutely zero knowledge of it's view (a little different than your picture above). The view model takes a reference to a model object that it manipulates via constructor injection as well.

  3. If a view is capable of spawning other views, it will have a factory that it can use to accomplish this.

  4. All view/view model construction is done via either the initial resolve at application start OR a view factory used by whatever needs to spawn the other views.

Here's a console application that demonstrates some of the castle stuff that I do.

Of note in the example code: IView & IViewModel are used by castle for registration purposes mainly. I sometimes end up with base definitions of functionality, in which case I'll usually make an abstract base class that all of my view/viewmodels inherit from.

IViewFactory is the definition of the typed factory for castle. An important note, which I believe that you understand by looking at your code is that all transient lifestyle objects which are tracked by castle (tracked is default behavior and suggested) need to be released. So my IViewFactory defines a destroy as well as implements IDisposable (when your factory is disposed all components it created are released). The main pattern would be that when a window that can create views goes away, you can dispose the view factory and release all it's components.

My WpfInstaller will register all of the IView and IViewModel found in the application directory.

The WpfViewCreaterFacility sets a custom component activator for anything that's an IView. The activator will automatically assign the IViewModel to the DataContext of the view.

The app represents a system that has one main window, and that main window can spawn child windows, of which I've defined two.

If you have any questions let me know, here's the code:

using Castle.Core;
using Castle.Facilities.TypedFactory;
using Castle.MicroKernel;
using Castle.MicroKernel.ComponentActivator;
using Castle.MicroKernel.Context;
using Castle.MicroKernel.Facilities;
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using ViewFun.Castle;
using ViewFun.Common;
using ViewFun.View;
using ViewFun.ViewModels;

namespace ViewFun
{
    namespace Common
    {
        public interface IView
        {
            void PrintViewModelName();
        }

        public interface IViewModel
        {
        }

        public interface IViewFactory : IDisposable
        {
            TView CreateView<TView>();
            void DestroyView<TView>(TView view);
        }
    }

    namespace ViewModels
    {
        public interface IMainViewModel : IViewModel
        {
        }

        public interface ISecondaryViewModel : IViewModel
        {
        }

        public interface ISecondary2ViewModel : IViewModel
        {
        }

        public class MainViewModel : IMainViewModel
        {
        }
        public class SecondaryViewModel : ISecondaryViewModel
        {
        }
        public class Secondary2ViewModel : ISecondary2ViewModel
        {
        }
    }

    namespace View
    {
        public interface IMainView : IView, IDisposable
        {
            void ShowView<TView>()
                where TView : IView;
        }

        public interface ISecondaryView : IView
        {
        }

        public interface ISecondaryView2 : IView
        {
        }
    }

    namespace WPFImplementation
    {
        public class MainWindow : Window, IMainView, IDisposable
        {
            private readonly IViewFactory viewFactory;

            public MainWindow(IMainViewModel viewModel, IViewFactory viewFactory)
            {
                this.viewFactory = viewFactory;
            }

            public void PrintViewModelName()
            {
                Console.WriteLine(string.Format("The main window is of type {0} with a view model type of {1}", this.GetType(), this.DataContext.GetType().Name));
                Console.WriteLine();
                Console.WriteLine();
            }

            public void ShowView<TView>()
                where TView : IView
            {
                IView view = this.viewFactory.CreateView<TView>();
                Console.WriteLine(view.GetType().Name);
                view.PrintViewModelName();
            }

            public void Dispose()
            {
                this.viewFactory.Dispose();
            }
        }

        public class SecondaryWPFView1 : Window, ISecondaryView
        {
            public SecondaryWPFView1(ISecondaryViewModel viewModel)
            {
            }

            public void PrintViewModelName()
            {
                Console.WriteLine(string.Format("One of the secondary windows is of type {0} with a view model type of {1}", this.GetType(), this.DataContext.GetType().Name));
                Console.WriteLine();
                Console.WriteLine();
            }
        }

        public class SecondaryWPFView2 : Window, ISecondaryView2
        {
            public SecondaryWPFView2(ISecondary2ViewModel viewModel)
            {
            }

            public void PrintViewModelName()
            {
                Console.WriteLine(string.Format("The other secondary window is of type {0} with a view model type of {1}", this.GetType(), this.DataContext.GetType().Name));
                Console.WriteLine();
                Console.WriteLine();
            }
        }
    }

    namespace Castle
    {
        public class WpfInstaller : IWindsorInstaller
        {
                private static string AssemblyDirectory
                {
                    get
                    {
                        string codeBase = Assembly.GetExecutingAssembly().CodeBase;
                        var uri = new UriBuilder(codeBase);
                        string path = Uri.UnescapeDataString(uri.Path);
                        return Path.GetDirectoryName(path);
                    }
                }

                public void Install(IWindsorContainer container, IConfigurationStore store)
                {
                    container.AddFacility<WpfViewCreaterFacility>()
                        .Register(
                            Classes.FromAssemblyInDirectory(new AssemblyFilter(AssemblyDirectory))
                                .BasedOn<IView>()
                                .Configure(c => c.LifestyleTransient().Named(c.Implementation.Name))
                                .WithService.Base()
                                .WithService.FromInterface(typeof(IView)),
                            Classes.FromAssemblyInDirectory(new AssemblyFilter(AssemblyDirectory))
                                .BasedOn<IViewModel>()
                                .Configure(c => c.LifestyleTransient().Named(c.Implementation.Name))
                                .WithService.Base()
                                .WithService.FromInterface(typeof(IViewModel)),
                            Component.For<IViewFactory>()
                                .AsFactory()
                                .LifestyleTransient());
                }
        }

        public class WpfViewCreaterFacility : AbstractFacility
        {
            protected override void Init()
            {
                this.Kernel.ComponentModelCreated += this.RegisterComponentActivator;
            }

            private void RegisterComponentActivator(ComponentModel model)
            {
                bool isView = typeof(IView).IsAssignableFrom(model.Services.First());
                if (!isView)
                {
                    return;
                }

                if (model.CustomComponentActivator == null)
                {
                    model.CustomComponentActivator = typeof(WpfViewCreater);
                }
            }
        }

        public class WpfViewCreater : DefaultComponentActivator
        {
            public WpfViewCreater(
                ComponentModel model,
                IKernel kernel,
                ComponentInstanceDelegate onCreation,
                ComponentInstanceDelegate onDestruction)
                : base(model, kernel, onCreation, onDestruction)
            {
            }

            protected override object CreateInstance(
                CreationContext context, ConstructorCandidate constructor, object[] arguments)
            {
                object component = base.CreateInstance(context, constructor, arguments);

                var frameworkElement = component as FrameworkElement;

                if (frameworkElement != null && arguments != null)
                {
                    object viewModel = arguments.FirstOrDefault(vm => vm is IViewModel);
                    if (viewModel != null)
                    {
                        frameworkElement.DataContext = viewModel;
                    }
                }

                return component;
            }
        }
    }

    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            IWindsorContainer container = new WindsorContainer();
            container.AddFacility<TypedFactoryFacility>();
            container.Install(new WpfInstaller());

            IMainView mainView = container.Resolve<IMainView>();
            mainView.PrintViewModelName();
            mainView.ShowView<ISecondaryView>();
            mainView.ShowView<ISecondaryView2>();

            mainView.Dispose();

            Console.ReadLine();
        }
    }
}
于 2013-11-12T18:07:26.303 回答
0

My Findings:

I found out (And this is thanks to @greyalien007 answer) that my problem was consisted of two issues:

  1. I was registering my Map assembly in a wrong way which caused the IMapView interface to not being registered. so I changed registration to be: container.Register(Types.FromAssemblyInDirectory(new AssemblyFilter(AssemblyDirectory + "\\Map")) .Where(type => type.Name.StartsWith("Map")) .WithService.AllInterfaces());
  2. So now what left for me to do - is to add a property to my MapView to inject the MapViewModel into: /// <summary> /// The View Model. /// (Automatically Injected by the Castle Windsor Framework) /// </summary> public IMapViewModel TheMapViewModel { get { return _theMapViewModel; } set { _theMapViewModel = value; DataContext = _theMapViewModel.TheModel; } }
  3. Having that done, all I needed is to create them in my Main like so: IMapViewModel theMapViewModel = context.Create<IMapViewModel>(); IMapView theMapView = context.Create<IMapView>();

And now I have a Main Project based on Castle - Who reference only the Interfaces Assembly and using the Castle framework to access and create the desired assemblies.

Thanks.

于 2013-11-13T12:08:16.117 回答