I am trying to access the regionManager of the Shell from the Viewmodel of a module in a WPF-Prism Application with Unity for view-based Navigation
Following is the constructor of the ViewModel for a Module.
public LeftSelectionViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
}
Seems can't construct the Viewmodel object(I use auto-wiring).
Works fine when I remove the parameter from the Constructor.
What is missing?
Related
I am creating a WPF application using .NET core 3.1
I have developed ASP.Net applications in the past and I was excited to use this in WPF. I did some searching and realized DI in WPF isn't as straightforward as it is in ASP.Net, meaning you have to register Views and ViewModels.
My structure is like this
MainWindow
|---BalanceIntegrationPage
|---BalanceIntegrationViewModel
Everything is handled in XAML with the MainWindow.xaml.cs having only generated code, and the BalanceIntegrationPage.xaml.cs has a single line added to it in the constructor
DataContext = new ScaleIntegrationViewModel();
That could not be handled in the xaml because DI requires parameters in the constructor.
Here is my app.xaml.cs:
protected override async void OnStartup(StartupEventArgs startupEventArgs)
{
base.OnStartup(startupEventArgs);
ServiceCollection services = new ServiceCollection();
services.AddScoped<MainWindow>();
services.AddScoped<ScaleInterfacePage>();
services.AddScoped<ScaleIntegrationViewModel>();
services.AddScoped<IScale>(provider => new Scale("1234"));
ServiceProvider serviceProvider = services.BuildServiceProvider();
MainWindow mainWindow = serviceProvider.GetService<MainWindow>();
mainWindow.Show();
}
My ScaleIntegrationViewModel looks like:
public ScaleIntegrationViewModel(IJMDataIntegration jmContext = null, IBalanceIntegrationContext localContext = null, IScale scale = null)
{
_jmContext = jmContext ?? new JMDataIntegration();
_localContext = localContext ?? new BalanceIntegrationContext();
_scale = scale ?? new Scale("1234");
//JK read from config
_commPort = "1234";
}
I also attempted to use the pattern described here
When I step through the code, my IScale object in the ViewModel constructor is always null.
Any Suggestions??
edit:
Based off a comment, I removed the ViewModel call in the page constructor and instead assigned it on the .xaml
This forced me to create a default paramaterless constructor which then breaks DI.
It almost starts to seem like I need to inject the services into the MainWindow ctor and then pass them down to everything I call from there. Makes no sense to me, since at that point, I may as well throw away DI and just new them up when I need them.
You are missing the configuration for some dependencies. From the code you've posted, you missed to configure IJMDataIntegration and IBalanceIntegrationContext:
protected override async void OnStartup(StartupEventArgs startupEventArgs)
{
base.OnStartup(startupEventArgs);
ServiceCollection services = new ServiceCollection();
services.AddScoped<MainWindow>();
services.AddScoped<ScaleInterfacePage>();
services.AddScoped<IJMDataIntegration, JMDataIntegration>();
services.AddScoped<IBalanceIntegrationContext, BalanceIntegrationContext>();
services.AddScoped<IScale>(provider => new Scale("1234"));
services.AddScoped<ScaleIntegrationViewModel>();
ServiceProvider serviceProvider = services.BuildServiceProvider();
MainWindow mainWindow = serviceProvider.GetService<MainWindow>();
mainWindow.Show();
}
Also, as already mentioned, you have to inject the view model into the MainWindow too. This where the dependency graph starts, the root of the application:
partial class MainWindow : Window
{
public MainWindow(ScaleIntegrationViewModel viewModel)
{
this.DataContext = viewModel;
}
}
To enable the full power of Dependency Injection (and make mocking easier) you should make use of Dependency Inversion throughout the application. This means you should only depend on interfaces and therefore only have interface types in your constructors:
partial class MainWindow : Window
{
public MainWindow(IScaleIntegrationViewModel viewModel)
{
this.DataContext = viewModel;
}
}
Controls like pages should be generated via DataTemplate and not instantiated directly in XAML. All you need to do, is to inject the page view models e.g. into another view model. Bind them to a ContentPresenter and define an implicit DataTemplate which targets the type of the page view model. This template contains the actual page. See this example.
Search for View-Model-First pattern if you need more details. Basically, a view can be defined as a data template and associated with a view model type. Data templates can be defined as resources, or they can be defined inline within the control that will display the view model. The content of the control is the view model instance, and the data template is used to visually represent it. This technique is an example of a situation in which the view model is instantiated first, followed by the creation of the view.
This is the preferred way, especially in conjunction with Dependency Injection.
Dependency injection means that you inject the dependencies, but don't construct them by yourself.
In your example, you construct the viewmodel manually inside of your page. This is not DI. You have to inject an instance of the viewmodel into the page.
You don't show us how you inject the page into the main window. I am not sure that using DI to inject a page into a window is a good decision. This is UI, and you can describe everything without DI (by using the data templating and pure XAML).
Anyway, to inject your viewmodel into the page, just introduce a constructor parameter in your page:
public ScaleInterfacePage(ScaleIntegrationViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
That's it, you're all set.
I am attempting to register an interface for a viewmodel, so that I can inject the interface into another view model. I have something similar to the following code in the app constructor in a shared .net standard class library in a Xamarin Forms app using Caliburn Micro.
public App(SimpleContainer container)
{
this._container = container;
_container.Singleton<ISetupViewModel, SetupViewModel>();
InitializeComponent();
DisplayRootView<SetupView>();
}
Using this will display the correct view is displayed, however the SetupViewModel doesn’t get created. But when I change the registration to not use the interface as below, then the SetupViewModel is created and resolved.
_container.Singleton<SetupViewModel>();
Is there any reason that I can't register a view model with an interface?
I have a module "Toolbar", "Toolbar" contains a UserControl and a ViewModel. The viewmodel is supposed to get a service called "IShapeService" injected into its constructor, however there's an issue.
I get the following error message when running my application:
The current type, Core.Services.IShapeService, is an interface and cannot be constructed.
Are you missing a type mapping?
The interface is mapped to the type in the ServicesModule which you can see below.
public class ServicesModule : IModule
{
private readonly IUnityContainer container;
public ServicesModule(IUnityContainer container)
{
this.container = container;
}
public void Initialize()
{
container.RegisterType<IShapeService, ShapeService>(new ContainerControlledLifetimeManager());
}
}
As far as I can tell by debugging, the issue is that the ServicesModule is not loaded early enough. I quickly tried using a PriorityAttribute (you can find it via google) on the module, but had no luck this time.
The weird thing is that it is working in another project with the same setup (as far I can see it is the same setup). The other project actually also uses the PriorityAttribute, but it is to determine tab order.
This is an image of the solution structure:
All projects reference the Core, but that is also it.
Can you spot what I'm doing wrong?
I'm writing a WPF/MVVM application using Prism/Unity. I'm having issues on how to transfer data from one usercontrol to another when navigating a region.
I have a region
This is populated by a user control("ContactsList") solely containing a grid, this grid is bound by ItemsSource & SelectedItem.
I want to navigate to ContractEdit and pass the Contact bound to SelectedItem.
LocalRegionManager.RequestNavigate(ContactRegions.MainRegion, ContactsUri.ContactsEdit);
I can pass a single value in he Datacontext or the Uri but I don't want to have to go back to the Database to get the data when I already have the whole Item in the list view model.
How can I pass a whole object from the list usercontrol to the edit control?
Thanks in advance.
Conclusion
I added the EventAggregator
public ListViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
{
_eventAggregator = eventAggregator;
_regionManager = regionManager;
}
Published the even when navigating
private void OnSendData()
{
_regionManager.RequestNavigate(ShellRegions.LeftRegion, ModuleAUris.Edit);
_eventAggregator.GetEvent<UserEvent>().Publish(_selectedItem);
}
Then subscribed to the even to get the object.
public EditViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
{
_regionManager = regionManager;
_eventAggregator = eventAggregator;
BackCommand = new DelegateCommand(OnBack);
_eventAggregator.GetEvent<UserEvent>().Subscribe((e) => UserEntity = e);
}
There is a handy example of this in the prism examples downloaded with the prism installer; where they use a separate file for subscribing/unsubscribing to an event. There's a desktop and Silverlight version.
It shows how to create a token used to unsubscribe from the event also.
Oli
You can have a NavigationManager that will know what is the current Contact. You just have to set it and get it for example in the NavigatedFrom and NavigatedTo, or somewhere else. It can either be in its own module or in the core project, as long as CurrentContact is an IContact.
You can also have a service in the list module that will access the current contact from other modules.
The service might be more appropriate if te NavigationManager has no other purpose.
edit
These services explained on msdn.
I am newbie in using Prism. I have created a sample application to understand it. I have some queries on, how it is working.
Question 1:
public class HelloModuleCls : IModule
{
IRegionManager regionManager;
IUnityContainer container;
public void Initialize()
{
RegisterViewAndServices(); // Injecting occurs here
if (regionManager.Regions.ContainsRegionWithName("ViewHolder"))
{
regionManager.Regions["ViewHolder"].Add
(
***container.Resolve().View***
);
}
}
protected void RegisterViewAndServices()
{
//since angular brace is not appearing, I am using ( , )....
container.RegisterType(IHelloView, HelloViewUC)();
container.RegisterType(IHelloViewModel, EmployeeVM)();
}
}
public class EmployeeVM : INotifyPropertyChanged, IHelloViewModel
{
public EmployeeVM(IHelloView targetview)
{
}
}
Upon hitting the line, container.Resolve().View,
the ctor of the VM, gets executed with, view type got injected in the param "targetview".
1. So, what will happen behind the scene, when that line got a hit...?
Question 2:
Project Name:- HelloModule (Another Silvelight Class Library referred in the startup Silverlight Application)
public class HelloModuleCls : IModule
{
public HelloModuleCls(IRegionManager rm, IUnityContainer um)
{
regionManager = rm;
container = um;
}
}
(Shell.Xaml) (In Startup Silverlight Application)
<ContentControl Prism:RegionManager.RegionName="ViewHolder" Grid.Row="0">
</ContentControl>
This Module is in another project. I am having the Region in the startup project. I have seen that, in the ctor of the HelloModuleCls, the region got which is used in the Startup project, got injected perfectly...
2. So, how the prism is passing those regions..... Is it like, once the region got created, then it will be injected to all the available Modules ctor or some other concept.
May I kindly know, how this is working out, So I can able to understand more.
Thanks in advance.
Answer 1: it resolve the one view, normally you want to register views with some key to be able to differentiate them.
Answer 2: it's the other way round, the regions exist in the application and the modules use them. They need to somehow know the name of the region, of course.
Unsorted comments:
RegisterType<ISomeInterface, OneOfItsImplementations>() lives in the Microsoft.Practices.Unity namespace, so add using Microsoft.Practices.Unity; to "see the angular brace appearing"
use something like regionManager.RegisterViewWithRegion("MyRegion", typeof(AView)) to make the view automatically appear in the region
to switch views in a region, have a look at prism's navigation features, like RegisterTypeForNavigation and RequestNavigate
the view uses the viewmodel, not the other way round. The viewmodel normally doesn't know about the view.