[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.
Related
So I am implementing MVVM pattern into my WPF application. Inside of my view model, I have a dependency of a repository object that gets passed in in a constructor:
public class RequestListViewModel : INotifyPropertyChanged
{
private readonly RequestRepository repository;
public event PropertyChangedEventHandler? PropertyChanged;
public RequestListViewModel(RequestRepository repository)
{
this.repository = repository;
}
}
If I am using data binding in XAML, how do I pass my repository object into my view model? One way is I can just new up a repository and make my view models only have a default constructor, but I want to have the flexibility to switch it out during runtime. Currently, I am using manual dependency injection to wire everything up and I know there is a container I can use but what is stopping from pursing that route is how would the container know when to instantiate the view model?
Thanks!
EDIT: Including manual (poorman) DI:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var databaseService = new RequestDbContext();
var addRequest = new AddRequest(databaseService);
var getAllRequestList = new GetAllRequestList(databaseService);
var requestRepository = new RequestRepository(getAllRequestList, addRequest);
var requestListViewModel = new RequestListViewModel(requestRepository);
Application.Current.MainWindow = new MainWindow(requestListViewModel);
Application.Current.MainWindow.Show();
}
}
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 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
I am working on a Windows Phone 8.1 application and I have a base class with public property.
public class ViewModelBase
{
public ISomeClass MyProp {get;set;}
}
My derived class looks like this
public class MainViewModel : ViewModelBase
{
private readonly INavigation _navigation;
public MainViewModel(INavigation navigation)
{
_navigation = navigation;
}
}
In my App.cs I have
var builder = new ContainerBuilder();
builder.RegisterType<Navigation>().As<INavigation>();
builder.RegisterType<SomeClass>().As<ISomeClass>();
builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource());
When MainViewModel is created my INavigation is resolved but MyProp is null.
I have tried
builder.Register(c => new ViewModelBase { MyProp = c.Resolve<ISomeClass>() });
builder.Register(c => new ViewModelBase()).OnActivated(e => e.Instance.MyProp = e.Context.Resolve<ISomeClass>());
builder.RegisterType<ViewModelBase>().PropertiesAutowired();
but none of it works!
Solution posted here
http://bling.github.io/blog/2009/09/07/member-injection-module-for-autofac/
works but I don't like it :)
I don't want to use constructor injection in this case.
Thank you.
This will load up all classes that inherit ViewModelBase and inject only the specific properties that you want. A lot of the time, you don't want the other properties on the child class to be injected.
builder.RegisterAssemblyTypes( GetType().Assembly )
.AssignableTo<ViewModelBase>()
.OnActivated( args =>
{
var viewModel = args.Instance as ViewModelBase;
if( viewModel != null )
{
viewModel.MyProp = args.Context.Resolve<ISomeClass>();
}
} );
You must make sure that your viewmodel class, MainViewModel, is registered with property injection. Currently, all you have registered with property injection is ViewModelBase, but think about what you are resolving. You will never resolve ViewModelBase, you're resolving MainViewModels. So that is what needs to be registered in the container.
Try:
builder.RegisterType<MainViewModel>().PropertiesAutowired();
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;}