Re-implementando WindowManager usando la combinación ModernUI + Caliburn.Micro

Aquí Caliburn.Micro se combinó con éxito con ModernUI. Pero si queremos usar varias ventanas, también necesitamos volver a implementar el Administrador de ventanas de Caliburn para que funcione correctamente con ModernUI. ¿Cómo puede hacerse esto?

ACTUALIZACIÓN: (pregunta adicional sobre IoC-Container / Dependency Injection)

Ok, como lo tengo: usé una Inyección Constructor aquí:

 public class BuildingsViewModel : Conductor { public BuildingsViewModel(IWindowManager _windowManager) { windowManager = _windowManager; } } 

En cuanto a BuildingsViewModel resolvió desde el contenedor IoC, el contenedor mismo inyectó la implementación IWindowManager interfaz IWindowManager debido a esta línea en el método Configure() Bootstrapper :

 container.Singleton(); 

Si resuelvo una instancia de objeto desde el contenedor, inyecta todas las dependencias necesarias. Como un arbol

1) Así que ahora me pregunto cómo puedo reemplazar esta línea usando una inyección (con interfaz). _windowManager.ShowWindow(new PopupViewModel());

2) Si quiero que todo mi proyecto coincida con el patrón DI, todas las instancias de objetos deben inyectarse en ModernWindowViewModel , ¿eso se resuelve desde el contenedor primero?

3) ¿Está bien usar el SimpleContainer de Caliburn para un proyecto completo, o mejor usar un marco maduro como el Castillo Windsor? ¿Debo evitar mezclar?

ACTUALIZACIÓN2:

4) La integración de un contenedor IoC en una aplicación existente requiere la creación de este contenedor primero (en el método Main() de la aplicación de consola, por ejemplo), ¿y luego todos los instanses de objetos deben crecer con dependencias inyectadas?

Simplemente cree su propio WindowManager derivado y anule el EnsureWindow :

 public class ModernWindowManager : WindowManager { protected override Window EnsureWindow(object rootModel, object view, bool isDialog) { var window = view as ModernWindow; if (window == null) { window = new ModernWindow(); window.SetValue(View.IsGeneratedProperty, true); } return window; } } 

Cualquier vista que desee utilizar como ventanas emergentes debe basarse en ModernWindow y debe usar un LinkGroupCollection o debe establecer la propiedad ContentSource de la ventana, de lo contrario no habrá contenido.

Posiblemente ViewModel-First hacer este View-First pero funciona ViewModel-First usando el método anterior.

Por ejemplo, para PopupView mi PopupView hice lo siguiente

PopupView.xaml

   

PopupViewModel.cs

 public class PopupViewModel : Screen { // Blah } 

Código para desplegar la vista desde otro ViewModel:

 public void SomeMethod() { _windowManager.ShowWindow(new PopupViewModel()); // Or use injection etc } 

¡No olvide registrar ModernWindowManager en lugar de WindowManager en su contenedor!

por ejemplo, utilizando el SimpleContainer de CM

 container.Singleton(); 

Obviamente, el único inconveniente que puedo ver de lo anterior es que parece que no puede colocar el contenido directamente en una ModernWindow , por lo que debe tener dos UserControls de UserControls para cada ventana emergente.

Una solución alternativa sería cambiar EnsureWindow en ModernWindowManager para que cree un UserControl basado en ModernWindow y configure ContentSource en el URI de la vista que desea cargar, esto activará el cargador de contenido y conectará su ViewModel . Voy a actualizar si tengo un minuto para probarlo.

Actualizar:

Ok, por el momento es muy intrincado, pero este podría ser un punto de partida para algo útil. Básicamente estoy generando un URI basado en el espacio de nombres y el nombre de la vista.

Estoy seguro de que hay una forma más confiable de hacer esto, pero para mi proyecto de prueba funciona:

 protected override Window EnsureWindow(object rootModel, object view, bool isDialog) { var window = view as ModernWindow; if (window == null) { window = new ModernWindow(); // Get the namespace of the view control var t = view.GetType(); var ns = t.Namespace; // Subtract the project namespace from the start of the full namespace ns = ns.Remove(0, 12); // Replace the dots with slashes and add the view name and .xaml ns = ns.Replace(".", "/") + "/" + t.Name + ".xaml"; // Set the content source to the Uri you've made window.ContentSource = new Uri(ns, UriKind.Relative); window.SetValue(View.IsGeneratedProperty, true); } return window; } 

Mi espacio de nombres completo para mi vista fue TestModernUI.ViewModels.PopupView y el URI generado fue /ViewModels/PopupView.xaml que luego se cargó y enlazó a través del cargador de contenido de forma automática.

Actualización 2

FYI aquí es mi método de configuración Bootstrapper :

 protected override void Configure() { container = new SimpleContainer(); container.Singleton(); container.Singleton(); container.PerRequest(); container.PerRequest(); container.PerRequest(); } 

Aquí creo el contenedor, y registro algunos tipos.

Los servicios de CM, como WindowManager y EventAggregator están registrados en sus respectivas interfaces y como singletons, por lo que solo una instancia de cada uno estará disponible en el tiempo de ejecución.

Los modelos de vista se registran como PerRequest que crea una nueva instancia cada vez que solicite una del contenedor. De esta manera, puede tener la misma ventana emergente varias veces sin un comportamiento extraño.

Estas dependencias se inyectan en el constructor de cualquier objeto resuelto en el tiempo de ejecución.

Actualización 3

En respuesta a tus preguntas de IoC:

1) So now I wonder how can I replace this line using an injection(with interface)? _windowManager.ShowWindow(new PopupViewModel());

Ya que sus modelos de vista ahora normalmente necesitarán dependencias, debe tener alguna forma de inyectarlos en las instancias. Si PopupViewModel tuviera varias dependencias, podría inyectarlas en la clase principal, pero esto uniría el modelo de PopupViewModel principal a PopupViewModel de alguna manera.

Hay un par de otros métodos que puede utilizar para obtener una instancia de PopupViewModel .

¡Inyectalo!

Si registra PopupViewModel como PerRequest , obtendrá una nueva instancia de él cada vez que lo solicite. Si solo necesita una instancia emergente en su modelo de vista, puede inyectarlo:

 public class MyViewModel { private PopupViewModel _popup; private IWindowManager _windowManager; public MyViewModel(PopupViewModel popup, IWindowManager windowManager) { _popup = popup; _windowManager = windowManager; } public void ShowPopup() { _windowManager.ShowPopup(_popup); } } 

El único inconveniente es que la instancia será la misma si necesita usarla varias veces en el mismo modelo de vista, aunque podría inyectar varias instancias de PopupViewModel si supiera cuántas necesita al mismo tiempo.

Utilice alguna forma de inyección bajo demanda

Para las dependencias que se requieren más adelante, puede utilizar la inyección a pedido, como una fábrica.

No creo que Caliburn o SimpleContainer sean compatibles con las fábricas fuera de la caja, por lo que la alternativa es usar IoC.Get . IoC es una clase estática que le permite acceder a su contenedor DI después de la instanciación

  public void ShowPopup() { var popup = IoC.Get(); _windowManager.ShowWindow(popup); } 

IoC.Get asegurarse de haber registrado correctamente el contenedor en su bootstrapper y de haber delegado todas las llamadas a los métodos IoC de CM en el contenedor – IoC.Get llama a GetInstance del bootstrapper y otros métodos:

Aquí hay un ejemplo:

 public class AppBootstrapper : BootstrapperBase { SimpleContainer container; public AppBootstrapper() { Initialize(); } protected override void Configure() { container = new SimpleContainer(); container.Singleton(); container.Singleton(); container.PerRequest(); // Register viewmodels etc here.... } // IoC.Get or IoC.GetInstance(Type type, string key) .... protected override object GetInstance(Type service, string key) { var instance = container.GetInstance(service, key); if (instance != null) return instance; throw new InvalidOperationException("Could not locate any instances."); } // IoC.GetAll or IoC.GetAllInstances(Type type) .... protected override IEnumerable GetAllInstances(Type service) { return container.GetAllInstances(service); } // IoC.BuildUp(object obj) .... protected override void BuildUp(object instance) { container.BuildUp(instance); } protected override void OnStartup(object sender, System.Windows.StartupEventArgs e) { DisplayRootViewFor(); } 

Castle.Windsor es compatible con fábricas para que pueda Resolve y Release sus componentes y administrar su vida útil de manera más explícita, pero no entraré en eso aquí

2) If I want my whole project match DI pattern, all objects instances must be injected into ModernWindowViewModel, that resolves from container first?

Solo necesita inyectar las dependencias que necesita ModernWindowViewModel . Todo lo que requieren los niños se resuelve e inyecta automáticamente, por ejemplo:

 public class ParentViewModel { private ChildViewModel _child; public ParentViewModel(ChildViewModel child) { _child = child; } } public class ChildViewModel { private IWindowManager _windowManager; private IEventAggregator _eventAggregator; public ChildViewModel(IWindowManager windowManager, IEventAggregator eventAggregator) { _windowManager = windowManager; _eventAggregator = eventAggregator; } } 

En la situación anterior, si resuelve ParentViewModel desde el contenedor, ChildViewModel obtendrá todas sus dependencias. No necesitas inyectarlos en el padre.

3) Is it okay to use Caliburn's SimpleContainer for whole project, or better use mature framework like Castle Windsor? Should I avoid mixing?

Puede mezclar, pero puede ser confuso ya que no funcionarán entre sí (un contenedor no sabrá del otro). Solo quédate con un contenedor y SimpleContainer está bien. Castle Windsor tiene muchas más funciones, pero es posible que nunca las necesites (solo he usado algunas de las funciones avanzadas)

4) Integrating an IoC container into an existing application requires creating this container first(in Main() method of console app for example), and then all object instanses must grow from it with injected dependencies?

Sí, crea el contenedor, luego resuelve el componente raíz (en el 99.9% de las aplicaciones hay un componente principal que se llama la raíz de la composición), y esto construye el árbol completo.

Este es un ejemplo de un bootstrapper para una aplicación basada en servicios. Estoy usando Castle Windsor y quería poder alojar el motor en un servicio de Windows o en una aplicación WPF o incluso en una ventana de consola (para pruebas / depuración):

 // The bootstrapper sets up the container/engine etc public class Bootstrapper { // Castle Windsor Container private readonly IWindsorContainer _container; // Service for writing to logs private readonly ILogService _logService; // Bootstrap the service public Bootstrapper() { _container = new WindsorContainer(); // Some Castle Windsor features: // Add a subresolver for collections, we want all queues to be resolved generically _container.Kernel.Resolver.AddSubResolver(new CollectionResolver(_container.Kernel)); // Add the typed factory facility and wcf facility _container.AddFacility(); _container.AddFacility(); // Winsor uses Installers for registering components // Install the core dependencies _container.Install(FromAssembly.This()); // Windsor supports plugins by looking in directories for assemblies which is a nice feature - I use that here: // Install any plugins from the plugins directory _container.Install(FromAssembly.InDirectory(new AssemblyFilter("plugins", "*.dll"))); _logService = _container.Resolve(); } ///  /// Gets the engine instance after initialisation or returns null if initialisation failed ///  /// The active engine instance public IIntegrationEngine GetEngine() { try { return _container.Resolve(); } catch (Exception ex) { _logService.Fatal(new Exception("The engine failed to initialise", ex)); } return null; } // Get an instance of the container (for debugging) public IWindsorContainer GetContainer() { return _container; } } 

Una vez que se crea el bootstrapper, configura el contenedor y registra todos los servicios y también los dlls de complementos. La llamada a GetEngine inicia la aplicación resolviendo el Engine desde el contenedor que crea el árbol de dependencia completo.

Hice esto para que me permita crear un servicio o una versión de consola de la aplicación como esta:

Código de servicio:

 public partial class IntegrationService : ServiceBase { private readonly Bootstrapper _bootstrapper; private IIntegrationEngine _engine; public IntegrationService() { InitializeComponent(); _bootstrapper = new Bootstrapper(); } protected override void OnStart(string[] args) { // Resolve the engine which resolves all dependencies _engine = _bootstrapper.GetEngine(); if (_engine == null) Stop(); else _engine.Start(); } protected override void OnStop() { if (_engine != null) _engine.Stop(); } } 

Aplicación de consola:

 public class ConsoleAppExample { private readonly Bootstrapper _bootstrapper; private IIntegrationEngine _engine; public ConsoleAppExample() { _bootstrapper = new Bootstrapper(); // Resolve the engine which resolves all dependencies _engine = _bootstrapper.GetEngine(); _engine.Start(); } } 

Aquí está parte de la implementación de IIntegrationEngine

 public class IntegrationEngine : IIntegrationEngine { private readonly IScheduler _scheduler; private readonly ICommsService _commsService; private readonly IEngineStateService _engineState; private readonly IEnumerable _components; private readonly ConfigurationManager _configurationManager; private readonly ILogService _logService; public IntegrationEngine(ICommsService commsService, IEngineStateService engineState, IEnumerable components, ConfigurationManager configurationManager, ILogService logService) { _commsService = commsService; _engineState = engineState; _components = components; _configurationManager = configurationManager; _logService = logService; // The comms service needs to be running all the time, so start that up commsService.Start(); } 

Todos los demás componentes tienen dependencias, pero no los inyecto en IntegrationEngine , son manejados por el contenedor