Просто создайте свой собственный производный WindowManager
и переопределите 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;
}
}
Любые представления, которые вы хотите использовать в качестве всплывающих окон, должны основываться на ModernWindow
и должны либо использовать LinkGroupCollection
, либо вы должны установить свойство ContentSource
окна, иначе содержимого не будет.
Возможно, вы могли бы сделать это View-First
, но оно работает ViewModel-First
, используя описанный выше метод.
например чтобы открыть мой PopupView
, я сделал следующее
PopupView.xaml
<mui:ModernWindow x:Class="TestModernUI.ViewModels.PopupView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mui="http://firstfloorsoftware.com/ModernUI"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" ContentSource="/ViewModels/ChildView.xaml">
</mui:ModernWindow>
PopupViewModel.cs
public class PopupViewModel : Screen
{
// Blah
}
Код для всплывающего представления из другой ViewModel:
public void SomeMethod()
{
_windowManager.ShowWindow(new PopupViewModel()); // Or use injection etc
}
Не забудьте зарегистрировать ModernWindowManager
вместо WindowManager
в своем контейнере!
например. с помощью SimpleContainer CM
container.Singleton<IWindowManager, ModernWindowManager>();
Очевидно, единственный недостаток, который я вижу в вышеизложенном, заключается в том, что вы не можете поместить контент непосредственно в ModernWindow
, поэтому вам нужно иметь два UserControls
для каждого всплывающего окна!
Обходным путем может быть изменение EnsureWindow
в ModernWindowManager
, чтобы он создал UserControl
на основе ModernWindow
и установил ContentSource
в URI представления, которое вы хотите загрузить, это вызовет загрузчик контента и подключит ваш ViewModel
. Я обновлю, если у меня будет минутка, чтобы попробовать.
Обновление:
Итак, на данный момент это очень хакерский, но это может быть отправной точкой для чего-то полезного. В основном я создаю URI на основе пространства имен и имени представления.
Я уверен, что есть более надежный способ сделать это, но для моего тестового проекта он работает:
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;
}
Мое полное пространство имен для моего представления было TestModernUI.ViewModels.PopupView
, а сгенерированный URI был /ViewModels/PopupView.xaml
, который затем автоматически загружался и связывался через загрузчик контента.
Обновление 2
К вашему сведению, вот мой метод настройки Bootstrapper
:
protected override void Configure()
{
container = new SimpleContainer();
container.Singleton<IWindowManager, ModernWindowManager>();
container.Singleton<IEventAggregator, EventAggregator>();
container.PerRequest<ChildViewModel>();
container.PerRequest<ModernWindowViewModel>();
container.PerRequest<IShell, ModernWindowViewModel>();
}
Здесь я создаю контейнер и регистрирую несколько типов.
Службы CM, такие как WindowManager
и EventAggregator
, зарегистрированы для соответствующих интерфейсов и как синглтоны, поэтому во время выполнения будет доступен только 1 экземпляр каждой из них.
Модели представления зарегистрированы как PerRequest
, что создает новый экземпляр каждый раз, когда вы запрашиваете его из контейнера — таким образом, вы можете иметь одно и то же всплывающее окно несколько раз без странного поведения!
Эти зависимости внедряются в конструктор любых объектов, разрешенных во время выполнения.
Обновление 3
В ответ на ваши вопросы IoC:
1) So now I wonder how can I replace this line using an injection(with interface)? _windowManager.ShowWindow(new PopupViewModel());
Поскольку ваши модели представления теперь обычно нуждаются в зависимостях, вам нужно каким-то образом внедрить их в экземпляры. Если бы у PopupViewModel
было несколько зависимостей, вы могли бы внедрить их в родительский класс, но это каким-то образом связало бы родительскую модель представления с PopupViewModel
.
Есть несколько других методов, которые вы можете использовать для получения экземпляра PopupViewModel
.
Введите его!
Если вы зарегистрируете PopupViewModel
как PerRequest
, вы будете получать его новый экземпляр каждый раз, когда запрашиваете его. Если вам нужен только один экземпляр всплывающего окна в вашей модели представления, вы можете просто ввести его:
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);
}
}
Единственным недостатком является то, что экземпляр будет одним и тем же, если вам нужно использовать его несколько раз в одной и той же модели представления, хотя вы могли бы внедрить несколько экземпляров PopupViewModel
, если бы знали, сколько вам нужно одновременно
Используйте некоторую форму внедрения по запросу
Для зависимостей, которые потребуются позже, вы можете использовать инъекцию по требованию, например фабрику.
Я не думаю, что Caliburn или SimpleContainer поддерживают фабрики из коробки, поэтому альтернативой является использование IoC.Get<T>
. IoC
— это статический класс, который позволяет вам получить доступ к вашему контейнеру DI после создания экземпляра.
public void ShowPopup()
{
var popup = IoC.Get<PopupViewModel>();
_windowManager.ShowWindow(popup);
}
Вам нужно убедиться, что вы правильно зарегистрировали контейнер в своем загрузчике и делегировали любые вызовы методов IoC
CM контейнеру - IoC.Get<T>
вызывает GetInstance
и другие методы загрузчика:
Вот пример:
public class AppBootstrapper : BootstrapperBase {
SimpleContainer container;
public AppBootstrapper() {
Initialize();
}
protected override void Configure() {
container = new SimpleContainer();
container.Singleton<IWindowManager, ModernWindowManager>();
container.Singleton<IEventAggregator, EventAggregator>();
container.PerRequest<IShell, ModernWindowViewModel>();
// Register viewmodels etc here....
}
// IoC.Get<T> 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<T> or IoC.GetAllInstances(Type type) ....
protected override IEnumerable<object> 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<IShell>();
}
Castle.Windsor поддерживает фабрики, так что вы можете Resolve
и Release
своих компонентов и более явно управлять их временем жизни, но я не буду вдаваться в подробности здесь.
2) If I want my whole project match DI pattern, all objects instances must be injected into ModernWindowViewModel, that resolves from container first?
Вам нужно только внедрить зависимости, которые нужны ModernWindowViewModel
. Все, что требуется детям, автоматически разрешается и вводится, например:
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;
}
}
В приведенной выше ситуации, если вы разрешите ParentViewModel
из контейнера, ChildViewModel
получит все свои зависимости. Вам не нужно вводить их в родителя.
3) Is it okay to use Caliburn's SimpleContainer for whole project, or better use mature framework like Castle Windsor? Should I avoid mixing?
Вы можете смешивать, но это может привести к путанице, поскольку они не будут работать друг с другом (один контейнер не будет знать о другом). Просто придерживайтесь одного контейнера, и SimpleContainer
будет в порядке — Castle Windsor имеет гораздо больше функций, но они могут вам никогда не понадобиться (я использовал только некоторые из расширенных функций)
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?
Да, вы создаете контейнер, затем разрешаете корневой компонент (в 99,9% приложений есть один основной компонент, который называется корнем композиции), и затем строится полное дерево.
Вот пример загрузчика для сервисного приложения. Я использую Castle Windsor и хотел иметь возможность размещать движок в службе Windows, в приложении WPF или даже в окне консоли (для тестирования/отладки):
// 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<TypedFactoryFacility>();
_container.AddFacility<WcfFacility>();
// 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<ILogService>();
}
/// <summary>
/// Gets the engine instance after initialisation or returns null if initialisation failed
/// </summary>
/// <returns>The active engine instance</returns>
public IIntegrationEngine GetEngine()
{
try
{
return _container.Resolve<IIntegrationEngine>();
}
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;
}
}
Как только загрузчик создан, он настраивает контейнер и регистрирует все службы, а также DLL-плагины. Вызов GetEngine
запускает приложение, разрешая Engine
из контейнера, который создает полное дерево зависимостей.
Я сделал это для того, чтобы это позволило мне создать сервисную или консольную версию приложения вот так:
Код услуги:
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();
}
}
Консольное приложение:
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();
}
}
Вот часть реализации IIntegrationEngine
public class IntegrationEngine : IIntegrationEngine
{
private readonly IScheduler _scheduler;
private readonly ICommsService _commsService;
private readonly IEngineStateService _engineState;
private readonly IEnumerable<IEngineComponent> _components;
private readonly ConfigurationManager _configurationManager;
private readonly ILogService _logService;
public IntegrationEngine(ICommsService commsService, IEngineStateService engineState, IEnumerable<IEngineComponent> 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();
}
Все остальные компоненты имеют зависимости, но я не добавляю их в IntegrationEngine
— они обрабатываются контейнером.
person
Charleh
schedule
18.03.2015
WindowManager
и переопределитьEnsureWindow
. Здесь CM создает экземпляр окна и предоставляет его для интерфейсаIWindowManager
. Исходный код находится здесь: github.com/Caliburn-Micro/Caliburn.Micro/blob/master/src/ — возможно, вам придется немного подправить, поскольку ModernUI отображается первым, поэтому вы можете изменить методыShowXX
, чтобы связать окно. правильно. Вы также можете изменить вложенный классWindowConductor
, так какModernWindow
может иметь другие методы. - person Charleh   schedule 17.03.2015ModernWindow
ожидает, что вы загрузите контент через Uri, вам придется либо использоватьModernDialog
, либо предоставить контент каким-либо другим способом. Вы просто хотите убедиться, что новое окно имеет правильный стиль, или вы хотите предоставить всплывающее окно, которое позволяет использовать все функции навигации, которые предоставляет ваш основной вид? - person Charleh   schedule 17.03.2015