Open new window on click in WPF, Ninject and Caliburn.Micro - wpf

I'm trying to set up a WPF app to call the new window on a menu click with the data provider interface injected into the new viewmodel.
Followed many tutorials and created the Bootstrapper for Caliburn, a service locator and module for ninject. So far the main view doesn't need the IDataProvider but I'd like to open a new window on click event.
The Bootstrapper:
public class Bootstrapper : BootstrapperBase
{
public Bootstrapper()
{
Initialize();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<MainScreenViewModel>();
}
}
The Service Locator and Module:
public class ServiceLocator
{
private readonly IKernel _kernel;
public ServiceLocator()
{
_kernel = new StandardKernel(new ServiceModule());
}
public MainScreenViewModel MainScreenViewModel => _kernel.Get<MainScreenViewModel>();
public NewLayoutViewModel NewLayoutViewModel => _kernel.Get<NewLayoutViewModel>();
}
public class ServiceModule : NinjectModule
{
public override void Load()
{
Bind<ISqlite>().To<Sqlite>();
Bind<IDataProvider>().To<DataProvider>();
}
}
And this is where I got stuck:
public class MainScreenViewModel : Conductor<object>
{
private IWindowManager _windowManager;
public MainScreenViewModel()
{
_windowManager = new WindowManager();
}
public void NewLayout()
{
_windowManager.ShowWindow(new NewLayoutViewModel());
}
}
since the NewLayoutViewModel requires the IDataProvider.
Not sure, what am I missing, but in my understanding Ninject should take care of this di for NewLayoutViewModel.

Found a good solution from Tim Corey on YouTube.
Basically the answer is, if you not insist Ninjet, use Caliburn.Micro's build-in DI solution "SimpleContainer".

Related

Change the View on button Click in WPF MVVM Pattern

I have 3 buttons on one usercontrol (usercontrol1.xaml) in the Window . Now on-click of button 1 ,I want to switch the view to another usercontrol (usercontrol2.xaml), which again have 3 buttons and so on.
How to implement in MVVM Pattern in WPF?
Be aware that im using caliburn micro for this example
private IEventAggregator _eventAggregator => IoC.Get<IEventAggregator>(key: nameof(EventAggregator));
private IWindowManager _windowManager => IoC.Get<IWindowManager>(key: nameof(WindowManager));
public ShellViewModel(IEventAggregator eventAggregator)
{
_eventAggregator.Subscribe(this);
}
public string _firstName;
// public ShellViewModel page = new ShellViewModel();
public string FirstName
{
get {
return _firstName;
}
set
{
_firstName = value;
NotifyOfPropertyChange(() => FirstName);
}
}
public ICommand ConvertTextCommand
{
get { return new DelegateCommand(ConvertText); }
}
void ConvertText()
{
//string url = "https://www.google.com/";
string url = FirstName;
string result;
using (HttpClient client = new HttpClient())
{
using (HttpResponseMessage response = client.GetAsync(url).Result)
{
using (HttpContent content = response.Content)
{
result = content.ReadAsStringAsync().Result;
}
}
}
//(MainWindow)Application.Current.MainWindow).txtForm1TextBox.Text = "Some text";
//Application.Current.Resources.Add("PageSource", result);
// NavigationService.NavigateToViewModel<SecondViewModel>("Hello");
_windowManager.ShowWindow(new PageSourceViewModel(_eventAggregator), null);
_eventAggregator.PublishOnUIThread(result);
}
You can check caliburn micro and see that you can just create a new view model in a window manager instance
here is also 2 links to 2 tutorials that helped me solve this issue for MVVM
https://www.youtube.com/watch?v=laPFq3Fhs8k
https://www.youtube.com/watch?v=9kGcE9thwNw&list=LLy8ROdSzpPJnikdZQ1XPZkQ&index=30&t=0s
the first tutorial will help you to get a general idea. The second will help you with events and you can look back to my code and see how i handled a new window instance.
You can also call the same view model for a new instance of the same window like you said in the question
You will also need to make a boostrapper class. For my example i did it like this.
public class Bootstrapper : BootstrapperBase
{
private readonly SimpleContainer _container =
new SimpleContainer();
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
_container.Instance<IWindowManager>(new WindowManager());
_container.Singleton<IEventAggregator, EventAggregator>();
_container.PerRequest<ShellViewModel>();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
_container.Instance<SimpleContainer>(_container);
_container.Singleton<IWindowManager, WindowManager>(key: nameof(WindowManager))
.Singleton<IEventAggregator, EventAggregator>(key: nameof(EventAggregator));
DisplayRootViewFor<ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
return _container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}

Navigation between lazy loaded modules with Prism and WPF

Hello I'm trying to setup an architecture where only one module gets booted when the app is launched. Then I'd like to lazy load other modules based on the user's actions.
To achieve this in my app.xaml.cs I have one module loaded at bootstrap time (MainModule), and an other has InitializationMode = InitializationMode.OnDemand
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
Type BlipModuleType = typeof(BlipModule);
moduleCatalog.AddModule(new ModuleInfo()
{
ModuleName = BlipModuleType.Name,
ModuleType = BlipModuleType.AssemblyQualifiedName,
InitializationMode = InitializationMode.OnDemand
});
moduleCatalog.AddModule<MainModule>();
}
then my main module, which displays the view correctly, has a single view registered to the only region available:
public class MainModule : IModule
{
private readonly IRegionManager _regionManager;
public MainModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void OnInitialized(IContainerProvider containerProvider)
{
_regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(ViewA));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
The lazy loaded module has the same structure, registering a different view (which works properly if i decide to use it as my main module)
public class BlipModule : IModule
{
private readonly IRegionManager _regionManager;
public BlipModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void OnInitialized(IContainerProvider containerProvider)
{
_regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(ViewB));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
finally I have a Command in the viewmodel of my MainModule ViewA, that is supposed to load the new module and navigate to it.
public class ViewAViewModel : BindableBase
{
const string BlipModuleName = "BlipModule";
public ReactiveCommand ChangeRoute { get; set; } = new ReactiveCommand();
public ViewAViewModel(IRegionManager regionManager, IModuleManager moduleManager)
{
ChangeRoute.Subscribe(res =>
{
moduleManager.LoadModule(BlipModuleName);
});
moduleManager.LoadModuleCompleted += (s, e) =>
{
if (e.ModuleInfo.ModuleName == BlipModuleName)
{
regionManager.RequestNavigate(RegionNames.ContentRegion, new Uri(BlipModuleName, UriKind.Relative));
}
};
}
}
The viewB of the BlipModule is actually loaded (I get a hit if I set a breakpoint in the view's constructor), but instead of the view I get a white page with "System.Object" inside of it.
Any idea? thanks!
You want to RegisterForNavigation instead of RegisterViewWithRegion.

How the Unity container will resolve the registered type

Recently I am going through some old code and found the below code
public class ProfileModule : IModule
{
private readonly IRegionManager regionManager;
private readonly IUnityContainer container;
private IEventAggregator eventAggregator;
public ProfileModule(IUnityContainer c, IRegionManager r, IEventAggregator e)
{
container = c;
regionManager = r;
eventAggregator = e;
}
public void Initialize()
{
// Create and add profiles as new Tab items
container.RegisterType<IProfileViewModel, Profile1ViewModel>(new ContainerControlledLifetimeManager());
regionManager.Regions[RegionNames.HomeRegion].Add(container.Resolve<ProfileView>());// HomeRegion is of type TabControl
container.RegisterType<IProfileViewModel, Profile2ViewModel>(new ContainerControlledLifetimeManager());
regionManager.Regions[RegionNames.HomeRegion].Add(container.Resolve<ProfileView>());
container.RegisterType<IProfileViewModel, Profile3ViewModel>(new ContainerControlledLifetimeManager());
regionManager.Regions[RegionNames.HomeRegion].Add(container.Resolve<ProfileView>());
}
}
Below is the ProfileView.xaml.cs
public partial class ProfileView : INotifyPropertyChanged
{
[InjectionConstructor]
public ProfileView(IProfileViewModel vm)
{
DataContext = vm;
InitializeComponent();
}
}
Below are the viewModels
public abstract class ProfileViewModelBase : IProfileViewModel, IDataErrorInfo, INotifyPropertyChanged
{
public ProfileViewModelBase(IEventAggregator eventAggregator, IRegionManager regionManager)
{
}
}
public class Profile1ViewModel : ProfileViewModelBase
{
[InjectionConstructor]
public Profile1ViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
: base (eventAggregator, regionManager)
{
}
}
public class Profile2ViewModel : ProfileViewModelBase
{
[InjectionConstructor]
public Profile2ViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
: base (eventAggregator, regionManager)
{
}
}
public class Profile3ViewModel : ProfileViewModelBase
{
[InjectionConstructor]
public Profile3ViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
: base (eventAggregator, regionManager)
{
}
}
The part of the code that is not clear for me is the ProfileModule.Initialise().
Everytime when the region manager is adding a view a new new instance of ProfileView is getting created and the viewModel is the one that is registered last.
First time ProfileView is created with Profile1ViewModel as a Datacontext.
Second time ProfileView is created with Profile2ViewModel as a Datacontext.
Third time ProfileView is created with Profile3ViewModel as a Datacontext.
How the container knows exactly which viewmodel to create when creating the view.
Also I understand , container.Resolve will return the view if it already got one, first time view is created and returned, second time I except same view will be returned, but a new view is created. same with third.
Can anyone explain what is happening?
Here goes:
What you can see inside the Initialize method is that after registering the IProfileViewModel the code is then immediately calling Resolve<ProfileView> which on the first Resolve is providing Profile1ViewModel to the ProfileView constructor. Then the second Register replaces the first registration with Profile2ViewModel. Therefore subsequent calls to Resolve will never give you an instance (or the singleton instance) of Profile1ViewModel.
If for some reason you really want to resolve the same instance of ProfileView then you need to Register this with the Unity container as a singleton like the below.
container.RegisterType(new ContainerControlledLifetimeManager());
This is obviously assuming you have an interface defined called IProfileView

Unity Bootstrapper my configure container does not work when call a View Model [duplicate]

This question already has answers here:
Prism 6 with Unity - resolving view models for views without naming convention
(2 answers)
Closed 6 years ago.
I use prism 6.1. I set up the Unity configuration in the method ConfigureContainer of Bootstrapper class. But, when the Prism framework try to call the View Model, it can't create this, and throw the exception 'No parameterless constructor defined for this object.'.
Bootstrapper.cs
public class Bootstrapper: UnityBootstrapper
{
...
protected override void ConfigureContainer()
{
base.ConfigureContainer();
this.Container.RegisterType<IMyService, MyService>(new ContainerControlledLifetimeManager());
this.Container.RegisterType<MyFormViewModel>(new ContainerControlledLifetimeManager());
}
...
}
MyFormModule.cs
public class MyFormModule : IModule
{
private readonly IRegionViewRegistry regionViewRegistry;
public SkypeActionModule(IRegionViewRegistry registry)
{
this.regionViewRegistry = registry;
}
public void Initialize()
{
regionViewRegistry.RegisterViewWithRegion("MainRegion", typeof(Views.MyFormView));
}
}
MyFormViewModel.cs
public class MyFormViewModel : BindableBase
{
private readonly IMyService myService;
public SkypeActionViewModel(IMyService myService)
{
this.myService = myService;
}
...
}
In this line throw the exception:
regionViewRegistry.RegisterViewWithRegion("MainRegion", typeof(Views.MyFormView));
In the prism documentation explains how to configurate unity, but this is in the Module class (in my case is MyFormModule). I don't understand that because in this way, I need to configurate that in each module, and I haven't the object this.container.RegisterType in my module class.
In others links, I found some configuration similar to "MEF" configuration, where the "DI" configuration are in the ConfigureContainer method. But it doesn't work for my, or something missing in my configuration.
Edit
I include the container in my module class. But I have the same problem. I suppose that is normal, because the problem is when Prism create the view model class MyFormViewModel.
public class MyFormModule : IModule
{
private readonly IRegionViewRegistry regionViewRegistry;
private readonly IUnityContainer container;
public MyFormModule(IRegionViewRegistry registry, IUnityContainer container)
{
this.regionViewRegistry = registry;
this.container = container;
}
public void Initialize()
{
this.container.RegisterType<IMyService, MyService>(new ContainerControlledLifetimeManager());
this.container.RegisterType<MyFormViewModel>();
this.container.RegisterType<MyFormView>();
regionViewRegistry.RegisterViewWithRegion("MainRegion", typeof(MyFormView));
}
}
Edit
Solution: I found the solution in this question on stackoverflow
Create the method BindViewModelToView on Bootstrap class:
public void BindViewModelToView<TViewModel, TView>()
{
ViewModelLocationProvider.Register(typeof(TView).ToString(), () => Container.Resolve<TViewModel>());
}
and then, in the method ConfigureViewModelLocator in Bootstrap class call all view models it to bind with views:
protected override void ConfigureViewModelLocator()
{
BindViewModelToView<ViewAVM, ViewA>();
BindViewModelToView<ViewAVM, ViewB>();
}
How do you tell prism to create the view model? Are you using ViewModelLocator.AutoWireViewModel="True"?
If so, you want to do something like
ViewModelLocationProvider.SetDefaultViewModelFactory( type =>
{
return Container.Resolve(type);
});
in the bootstrapper to make sure that your container is used for resolving the view model...

Cinch version of ViewModel command to close a View

Without wanting to bug sacha too much, does anyone know what the Cinch V2 way of closing a View from a ViewModel command?
Previously I have used a RelayCommand in the ViewModel base to accept the Escape keybinding command action and wired up a RequestClose event in the View code behind to do this.
Use CloseActivePopUpCommand.Execute(true) in the execute method to close a view.
I've included a short sample below.
[ExportViewModel("PickOperatorViewModel")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class PickOperatorViewModel : ViewModelBase
{
[ImportingConstructor]
public PickOperatorViewModel()
{
PickOperaterCommand = new SimpleCommand<Object, Object>(CanExecutePickOperaterCommand, ExecutePickOperaterCommand);
}
public SimpleCommand<Object, Object> PickOperaterCommand { get; private set; }
private void ExecutePickOperaterCommand(Object args)
{
CloseActivePopUpCommand.Execute(true);
}
private bool CanExecutePickOperaterCommand(Object args)
{
return true;
}
}

Resources