I have a simple application here but I am unsure of how my ViewModel is being created. I am assuming it's from the unity container but I am unsure and curious. The module initializes and registers the view with the region. The view's code behind has the ViewModel initialized in it's constructor and the ViewModel calls some services that were previously registered.
My question is how is the ViewModel created in the View's code behind when I've never registered the ViewModel type with the unity container? Is there some magic happening in the RegisterViewWithRegion method?
AlarmsModule.cs: This simply registers the view with the region
[Module(ModuleName = "AlarmsModule")]
public class AlarmsModule : IModule
{
[Dependency]
public IRegionManager regionManager { get; set; }
public void Initialize()
{
regionManager.RegisterViewWithRegion("AlarmsRegion", typeof(AlarmPanel.View));
}
}
View.xaml.cs:
public partial class View : UserControl
{
public View(ViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
}
ViewModel.cs
public class ViewModel: DependencyObject
{
IEventAggregator _eventAggregator;
public ObservableCollection<IAlarmContainer> AlarmList { get; set; }
IAlarmService _alarmService;
public ViewModel(IAlarmService alarmService)
{
//Adding an alarm from the alarm service, which is injected into this viewModel
AlarmList = alarmService.AlarmList;
}
}
The view model is created by the unity container in the DoGetInstance method of the UnityServiceLocatorAdapter class in the Prism.Unity assembly which is in turn called by the RegisterViewWithRegion method through some other methods of the RegionViewRegistry class.
Unity is able to resolve the view model type automatically provided that it has a default parameterless constructor.
You could verify this yourself using the following code:
var view = unityContainer.Resolve(typeof(View), null); //will automatically resolve the view model type and inject the view with an instance of it
Related
I really made a search for this topic and did not find anything, and because of that, I am asking the question here.
I have a WPF application with Prism installed.
I have wired the view-model with the view automatically by name convention
<UserControl x:Class="Views.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
and the model in the 'Model' like this
public class ViewAViewModel {
public ViewAViewModel () {
// time-resource consuming operations
}
}
the automatic binding work perfectly without a problem and the view and its corresponding view-model is matching, but the problem here.
I have a lot of those views say (50) and for every one of them, the view-model will be created with constructor exhausting the processes. This will make the startup of the application longer and also it will create a lot of view-models objects and put them in the RAM without being sure that they will be used at all.
What I need is to create the view-model class when the view is activated (I mean when the view is navigated to). Is this possible and if yes how?
Update
here is how I register the view with the Module, this is causing all the views to be created when the startup of the module.
public class Module1 : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("region1", typeof(View1));
regionManager.RegisterViewWithRegion("region1", typeof(View2));
is there any way to delay the creating of the views, until the navigation request come?
You could use navigation, for each view.
Or you must create an interfaces for your view and view model.
An example:
public interface IMyView
{
IMyViewModel ViewModel { get; set; }
}
public interface IMyViewModel
{
}
In the module or app.cs, in the method RegisterTypes you should register these.
containerRegistry.Register<IMyView, MyView>();
containerRegistry.Register<IMyViewModel, MyViewModel>();
You must implement IMyView interface in your MyView.cs class.
public partial class MyView : UserControl, IMyView
{
public MyView(IMyViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
}
public IMyViewModel ViewModel
{
get => DataContext as IMyViewModel;
set => DataContext = value;
}
}
After you could use it:
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
var firstView = containerProvider.Resolve<IMyView>();
regionManager.AddToRegion(RegionNames.MainRegion, firstView);
}
In such case you shouldn't use ViewModelLocator.AutoWireViewModel in your view.
I'm trying to inject INavigationService into my ViewModel, so I can navigate between pages, but I don't know how to register ViewModel with parameter. Here's my App:
<prism:PrismApplication xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Unity;assembly=Prism.Unity.Forms"
x:Class="PrismDemo.App" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<prism:PrismApplication.Resources>
<ResourceDictionary>
</ResourceDictionary>
</prism:PrismApplication.Resources>
</prism:PrismApplication>
and...
public partial class App
{
INavigationService _navigationService;
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void OnInitialized()
{
InitializeComponent();
_navigationService = NavigationService;
NavigationService.NavigateAsync("MainNavigationPage/Start");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<MainNavigationPage>();
Container.RegisterTypeForNavigation<StartPage, StartPageViewModel>("Start");
Container.RegisterTypeForNavigation<MiddlePage, MiddlePageViewModel>("Middle");
Container.RegisterTypeForNavigation<LastPage, LastPageViewModel>("Last");
}
}
How can I inject _navigationService into ViewModels?
Thanks for help
You don't actually have to register the ViewModel directly as long as you have followed the convention {MyProjectName}.Views.{MyPage} and {MyProjectName}.ViewModels.{MyPageViewModel}
To use INavigationService in the ViewModel simply add it to the Constructor. Remember it is a named service so it must be named navigationService. You can check out several examples in the Samples Repo.
public class MyPageViewModel
{
INavigationService _navigationService { get; }
public MyPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
NavigateCommand = new DelegateCommand<string>(OnNavigateCommandExecuted);
}
public DelegateCommand<string> NavigateCommand { get; }
public async void OnNavigateCommandExecuted(string path) =>
await _navigationService.NavigateAsync(path);
}
If you are using Prism, than you should inherit your App from PrismApplication:
instead of public partial class App should be public partial class App : PrismApplication.
No need to declare INavigationService _navigationService; in your App class, because it's already declared in PrismApplication as NavigationService property.
Prism uses IUnityContainer as Dependency Injection framework, which means for you, that all, that you will register in container:
Container.RegisterType<ISomeService, SomeServiceImplementation>();
Unity container will automatically inject in constructor:
Define a constructor in the target class that takes as a parameter the concrete type of the dependent class. The Unity container will instantiate and inject an instance.
PrismApplication registers INavigationService in a container, so, as I understand, you don't need to register your own instance and you could just add parameter of type INavigationService in constructor of view model and unity container will inject instance of registered type. The important thing, that you should give an exactly the navigationService name for your parameter, because Prism "expects" such parameter name for navigation service.
I am using Prism + Unity in a wpf application using MVVM. I am a beginner with Prism and Unity.
I want to be able to close the current view. The various solutions and articles I've read state that the best way to do this is from the view model. But the view model needs a region manager object in order to close the view. Ok, so let's set up constructor injection. Never tried this before but there are plenty of questions on SO that deal with this.
Let me start with explaining how things are wired together. I have a bootstrapper class that handles the registering of types and instances.
Here is how my view and view model is registered:
container.RegisterType<IViewModel, ViewAccountsViewModel>(new InjectionConstructor(new ResolvedParameter(typeof(RegionManager))));
container.RegisterType<ViewAccountsView>();
Here is the module for the view accounts view:
public class ViewAccountsModule : IModule
{
private readonly IRegionManager regionManager;
private readonly IUnityContainer container;
public ViewAccountsModule(IUnityContainer container, IRegionManager regionManager)
{
this.container = container;
this.regionManager = regionManager;
}
/// <summary>
///
/// </summary>
public void Initialize()
{
regionManager.RegisterViewWithRegion("MainRegion", () => this.container.Resolve<ViewAccountsView>());
}
}
In my ViewAccountsView.xaml, I am setting the data context like so:
<Grid.DataContext>
<vm:ViewAccountsViewModel/>
</Grid.DataContext>
And my view model constructor:
[InjectionConstructor]
public ViewAccountsViewModel(IRegionManager regionManager)
{
if (regionManager == null) throw new ArgumentNullException("regionManager");
this.regionManager = regionManager;
}
When I compile the solution, I receive an error that the type "ViewAccountsViewModel" does not include any accessible constructors. If I add a default constructor to my view model, the view displays but I cannot remove the view from the region. I get an argument null exception.
Here is the code for removing the view:
regionManager.Regions["MainRegion"].Remove(regionManager.Regions["MainRegion"].GetView("ViewAccountsView"));
I am still very much a beginner with IoC and DI. Is there something I have missed?
Unity will handle injecting all dependencies it knows about for you. By default, Unity will call the constructor with the most parameters. You usually use InjectionConstructor to either tell Unity to choose a different constructor when it creates the objects for you, or if you want to pass it custom parameters.
Registration:
container.RegisterType<IViewModel, ViewAccountsViewModel>();
// If you plan to have multiple IViewModels, it will need to have a name
// container.RegisterType<IViewModel, ViewAccountsViewModel>("ViewAccountsViewModelName");
container.RegisterType<ViewAccountsView>();
ViewModel:
// If you decide later you need other dependencies like IUnityContainer, you can just set
// it in your constructor and Unity will give it to you automagically through the power
// of Dependency Injection
// public ViewAccountsViewModel(IRegionManager regionManager, IUnityContainer unityContainer)
public ViewAccountsViewModel(IRegionManager regionManager)
{
this.regionManager = regionManager;
}
View Code Behind:
// If you have a named IViewModel
// public ViewAccountsView([Dependency("ViewAccountsViewModelName")]IViewModel viewModel)
public ViewAccountsView(IViewModel viewModel)
{
this.InitializeComponent();
this.DataContext = viewModel;
}
I build up a custom TreeView class, with settings for each node such as "name/background" etc. I also have a ICommand property that can be set so that each node can have a custom method executed if necessary.
I build all of this in a "treeview service class", which then sends the menu to the usercontrol via MVVMLight Messenger. This all works just fine, but my problem is that if i dont specify a custom command for the node, i want it to execute a "default action", which should be located in the viewmodel that recieves the message from the Messenger service.
Basically my question is: How do i expose a RelayCommand in the MainViewModel , so that i can reference it from another viewmodel (or my service class), when building my tree.
To reference ViewModel A in ViewModel B you can use MVVMLight´s ViewModelLocator like in the Template samples:
Your ViewModelLocator class:
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
// Register your services
//...
// Register your ViewModels
SimpleIoc.Default.Register<MainViewModel>();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
}
and in your NodeViewModel you could access your default command for example like this:
public class NodeViewModel : ViewModelBase
{
private ViewModelLocator locator = new ViewModelLocator();
public RelayCommand NodeCommand
{
get
{
return locator.Main.DefaultCommand;
}
}
}
You can find a full small sample when you create a MVVM Light project by using the MVVM Light visual studio templates.
hope this helps!
I believe RelayCommand is an ICommand. You can just expose it as a property on the viewmodel:
public ICommand MyCommand { get; set;}
[Export]
public sealed class MainViewModel : NotificationObject
{
[Import]
public ISomeService MyService { get; private set; }
...
}
In order to INJECT this class as the DataContext to my View, I have to mark it as Export so MEF creates an instance of it in the Catalog. The problem is that the main window needs to create other windows and pass in orders, I'm not sure how to go about that without breaking the MVVM approach.
I figure that an ICommand will trigger something on my MainViewModel to generate a new ViewModel, but then after that happens I can't really force a new Window (view) to open up from the ViewModel. Plus, I can't even really create a new ViewModel from my MainViewModel because then MEF won't really work, right?
[Export]
public sealed class MainViewModel : NotificationObject
{
[Import]
public ISomeService MyService { get; private set; }
private ObservableCollection<IOrderViewModel> Orders { get; set; }
public void OpenOrder(int id)
{
//Pseudo-code to ensure that duplicate orders are not opened)
//Else create/open the new order
var order = new OrderViewModel(id);
OpenOrders.Add(order);
}
}
2 problems here:
Since I "newed" the OrderViewModel services are not autoloaded via MEF.
How does this code on my ViewModel layer (appropriate layer) create the necessary view as a NEW WINDOW (child of the main window), and then link this new OrderViewModel as the DataContext?
The way to avoid 'new-ing' the OrderViewModel is to use a factory:
[Export]
public class OrderViewModelFactory
{
[Import]
public ISomeDependency ImportedDependency { get; set; }
public OrderViewModel Create(int id)
{
return new OrderViewModel(id, this.ImportedDependency);
}
}
Then import the factory into your MainViewModel as a dependency and MEF will take care of filling everything in as required.
To get around the problem of instantiating windows, we have created a DialogService that does something like:
[Export]
public class DialogService
{
public bool? ShowDialog(string regionName, object data = null)
{
var window = new Window();
var presenter = new ContentControl();
presenter.SetProperty(RegionManager.RegionName, regionName);
window.Content = presenter;
if (data != null) window.DataContext = data;
return window.ShowDialog();
}
}
One technique I use is what I call the Navigation service. Note this is different from WPF's built in navigation framework. Your viewmodel could have an instance injected directly or you can use the EventAggregator pattern to fire a request to navigate that then gets handled by the navigation service. Having the navigation service injected directly means that it can be injected with other objects like ViewModelFactories. Regardless how you do it, at some point you're going to have to have an object that knows how to create the viewmodel properly resolved by your container.