Dependency injection with Unity on wpf MVVM application - wpf

I have been trying to set up dependency injection in a wpf application using Unity, but can't seem to fully understand how the views and viewmodels should be set up.
Have looked into another SO post --> Wpf Unity but can't seem to understand it quite yet. I have used Unity before, but just in a MVC application, so I know how to inject it in the contructors.
Here is my views and viewModels in the application.
Views:
MainWindow.xaml
BookingView.xaml
ContactDetailsView.xaml
ReservationsView.xaml
ViewModels:
MenuViewModel (MainWindow uses this viewModel)
BookingViewModel
ContactViewModel
ReservationsViewModel
My ViewModels all have Interfaces implemented, like IMenuViewModel, should the view also have an interface?
I guess that since the MainWindow is the starting point, it should be here to register the container right?
Update:
Have found something, but not sure if I have done it right. Here is what I have done so far!
1: Using startup method in app.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
IUnityContainer container = new UnityContainer();
container.RegisterType<IViewMainWindowViewModel, MainWindow>();
container.RegisterType<IViewMainWindowViewModel, MenuViewModel>();
var mainWindow = container.Resolve<MainWindow>(); // Creating Main window
mainWindow.Show();
}
}
2: Remove uri from start up.
3: Make IViewMainWindowViewModel interface in MainWindow class, the interface is empty.
public interface IViewMainWindowViewModel
{
}
4: Make a reference to this interface in the MainWindow
public partial class MainWindow : Window, IViewMainWindowViewModel
{
public MainWindow(IViewMainWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
5: Also for the MenuViewModel
public class MenuViewModel : IViewMainWindowViewModel
{
Code not shown!
}
This will not even start the application..
Update 2
My MainWindow class look like this:
public interface IViewMainWindowViewModel
{
}
public partial class MainWindow : Window, IViewMainWindowViewModel
{
public MainWindow(IViewMainWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
App class now look like this:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
IUnityContainer container = new UnityContainer();
container.RegisterType<IViewMainWindowViewModel, MainWindow>();
container.RegisterType<IViewMainWindowViewModel, MenuViewModel>();
container.Resolve<MainWindow>().Show();
//Do the same actions for all views and their viewmodels
}
I get an exception on this line when running the application
container.Resolve<MainWindow>().Show();
Update 3
In my MenuViewModel it has two command which will open two views, do I then need to inject those views in the MenuViewModel's constructor or can you just make another empty interface between MenuViewModel and BookingView as an example?

Let me show an example with explanations just for your MainWindows, as for the rest views and viewmodels steps to do are the same.
At first, you should create a contract between View and ViewModel. It shoud be some interface and let it call IViewMainWindowViewModel (keep in mind that name has to be different for other view and viewModels, for example IViewBookingViewViewModel):
public interface IViewMainWindowViewModel
{
/*This interface should not have any methods or commands. It is just
contract between View and ViewModels and helps to decide to Unity
container what it should inject(appropriate viewModel to necessary
View)*/
}
then in your viewmodel we should implement this interface:
public MenuViewModel:IViewMainWindowViewModel
{}
The view should inject this interface MainWindows.xaml.cs:
public partial class MainWindows : UserControl, IContentAView
{
public MainWindows(IViewMainWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
Delete StartupUri and override a method OnStartup in App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
IUnityContainer container = new UnityContainer();
container.RegisterType<IViewMainWindowViewModel, MainWindow>();
container.RegisterType<IViewMainWindowViewModel, MainWindowViewModel >();
container.Resolve<MainWindow>().Show();
//Do the same actions for all views and their viewmodels
}

Related

DataContext of usercontrol in WPF

I'm new to WPF and I'm trying to start a little project with a maximum of good practice. I'm using MVVM and dependency injection.
I have a concern which seems to be easy to understand but i can't find an answer (at this step, DataContext is not very clear for me).
The UserControlView of type UserControl contains just a button for testing.
This is the app class :
public App()
{
IServiceCollection services = new ServiceCollection();
services.AddSingleton<MainWindow>();
services.AddSingleton<UserControlViewModel>();
services.AddSingleton<UserControlView>();
_serviceProvider = services.BuildServiceProvider();
}
The user control is included in the Main windows like that :
<Grid>
<views:UserControlView/>
</Grid>
Now, in the OnStartup overrided method :
protected override void OnStartup(StartupEventArgs e)
{
MainWindow = _serviceProvider.GetRequiredService<MainWindow>();
MainWindow.DataContext = _serviceProvider.GetRequiredService<PaymentMeansViewModel>();
MainWindow.Show();
}
Like that it works, my button is correctly binded to the command.
But what is strange for me is that I have to set the 'UserControlViewModel' as the DataContext of the Main Window.
Isn'it possible to bind it to the 'UserControlView', something like :
protected override void OnStartup(StartupEventArgs e)
{
MainWindow = _serviceProvider.GetRequiredService<MainWindow>();
UserControlView testUC = _serviceProvider.GetRequiredService<UserControlView>();
testUC.DataContext = _serviceProvider.GetRequiredService<UserControlViewModel>();
MainWindow.Show();
}
Thanks for help.
Finally I did it.
I think (I hope I'm right) that I understood.
First of all, let's begin with the basic.
A view must have a viewmodel to bind the properties. A usercontrol is a kind of view "encapsulated" in a view. Therefore a usercontrol must have its own viewmodel and the view must have its own viewmodel.
The datacontext of the MainWindow is set in the app onstartup method :
MainWindow = new MainWindow()
{
DataContext = new MainWindowViewModel()
};
MainWindow must implement INotifyPropertyChanged. All view models must implement this interface. We can create a base class which will be derived in the view models :
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string? propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The DataContext of the usercontrol must be explicit in the xaml of the MainWindow:
<Grid>
<views:UserControlView DataContext="{Binding CurrentViewModel}"/>
</Grid>
"CurrentViewModel" is a DataContext, then it's a ViewModel, and as it is binded, it must be a property of the MainViewModel.
public class MainWindowViewModel : ViewModelBase
{
public ViewModelBase CurrentViewModel { get; }
public MainWindowViewModel()
{
CurrentViewModel=new UserControlViewModel();
}
}
Hope it can help.

OnNavigatedTo is not called in TabControl when selecting tab when using PRISM regions

I have registered my views for the TabControl with Region manager and views are shown properly when tab is selected.
The problem is that when I select new tab item OnNavigatedTo is not called for that view or its view model.
I'm using PRISM 6.3
UPDATE
ViewModel
`public class ValuationViewModel : IViewModel, INavigationAware
{
private IRegionManager _regionManager;
public string Title { get; set; }
public ValuationViewModel(IRegionManager regionManager)
{
Title = "PERFORM VALUATION";
_regionManager = regionManager;
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}`
View
`public partial class ValuationView : UserControl, IView
{
private IRegionManager _regionManager;
public ValuationView(ValuationViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
}
public IViewModel ViewModel
{
get
{
return (IViewModel)DataContext;
}
set
{
DataContext = value;
}
}
}`
Without code, nobody can give you the correct answer.
Its probably the best, if you show us your ViewModel for your "TabItem" View.
Assuming you registered your view and set your ViewModel in DataContext correctly, it could be possible that forget just a simple thing.
To manage your problem make sure you implemented the following things correctly:
Create a region for your TabControl
Register your view in that region
Make sure the DataContext is correctly set to your ViewModel
Make sure your ViewModel implemented INavigationAware
Update 1:
After testing a lot I found a simple answer unfortunately:
Members of INavigationAware (OnNavigatedTo, IsNavigationTarget & OnNavigatedFrom) are called when the NavigationService is navigating.
They aren't if you click on the TabItemHeader.
To solve your problem you have several options.
One option is to start a navigation request when the user click on the TabItemHeader ( bad approach).
In my opinion you should use the IActiveAware Interface ( https://msdn.microsoft.com/en-us/library/microsoft.practices.prism.iactiveaware(v=pandp.50).aspx).
It will solve your problem, because the navigation via RegionManager and the clicking on the TabItemHeader results in the same: INavigationAware.IsActive = true.
Now you are able to detect when your tab is shown or not and react.

How do I load (bind) an image to a property in my viewmodel using Prism and MVVM with ViewFirst View Injection?

Using Prism, MVVM with View First View Injection: Unable to load Image in Module
I am using WPF and Prism in an MVVM pattern using the View First, View Injection pattern.
In my solution I have a SG.BannerModule project in which I just want to bind an Image in my BannerView.xaml.
I use the below BannerViewModel that implements IBannerViewModel because I am using containers to resolve my BannerViewModel.
Here is the IBannerViewModel interface:
public interface IBannerViewModel : IViewModel
{
Uri BannerImageUri { get; set; }
}
I exposed the BannerImageUri because I am resolve the BannerViewModel thru the container in the
BannerMainModule using the Interface.
The BannerViewModel implementation is
public class BannerViewModel : ViewModelBase, IBannerViewModel
{
#region Properties
private IUnityContainer _container;
private ILogger _logger;
private Uri _bannerImageUri;
public Uri BannerImageUri
{
get { return new Uri("pack://SG.Website:,,,SG.BannerModule;component/Assets/SGBanner2.png"); }
set
{
if (value != _bannerImageUri)
{
_bannerImageUri = value;
OnPropertyChanged("BannerImageUri");
}
}
}
#endregion
#region Constructors
public BannerViewModel(IBannerView bannerView, IUnityContainer container)
: base(bannerView)
{
View = bannerView;
View.ViewModel = this;
_container = container;
_logger = _container.Resolve<ILogger>();
_logger.WriteToLogs("BannerViewModel Constructor");
}
#endregion
}
The Image is located in the Assets directory of my SG.BannerModule project.
I have set the build properties for the Image SGBanner2.png to be a resource and have added the image in the resources tab of the Properties Pane for the SG.BannerModule project.
I have set the data context of the BannerView.xaml in the BannerView.xaml.cs file in the following way because I am using View Injection.
public partial class BannerView : UserControl, IBannerView
{
public BannerView()
{
InitializeComponent();
}
public IViewModel ViewModel
{
get
{
return (IBannerViewModel) DataContext;
}
set { DataContext = value; }
}
}
Because of the View First View Injection pattern I set the DataContext in my in code behind and do not set any in the XAML.
My question is
Because I am binding this image, do I need to use an ImageSource converter? If this is the case, will it be a problem because the image is a png and not a bitmap?
Or is the problem the Pack Uri. Because I am using this module in a region in my SG.WebSite project, I am not sure that my pack Uri is correct but I have not been able to trouble shoot correctly why my image is not showing up in my shell window.
Stumped?
Thanks!
Just so you know, it appears you are using ViewModel first. Having your ViewModel resolve your View and then probably calling region.Add(viewModel.View).
This is the packURI syntax for local resource files:
pack://application:,,,/Subfolder/ResourceFile.xaml
For reference resource files:
pack://application:,,,/ReferencedAssembly;component/Subfolder/ResourceFile.xaml

Prism - How to import IRegionManager in ViewModel using MEF

How do we inject IRegionManager in the ViewModel using MEF Container. I have to switch view in my ViewModel's Command delegate. Here is the brief description of what I am doing. I have an entity called Product whose list is displayed in one View (ProductListView). In that view the user can select the Product and click on Edit button. This would switch the view and present a new View(ProductEditView). For activating a different view, I would need a reference to IRegionManager something like this
public class ProductListVM : NotificationObject { //The Product List View Model
[Import]
public IRegionManager RegionManager { get; set; }
private void EditProduct() { //EditCommand fired from ProductListView
IRegion mainContentRegion = RegionManager.Regions["MainRegion"];
//Switch the View in "MainContent" region.
....
}
}
The above code fails with NullReferenceException for RegionManager. This seems logical because the above View Model is constructed by WPF through DataContext property in Xaml and DI doesn't come into play, so it doesn't get a chance to import the RegionManager instance. How do we resolve the IRegionManager in this scenario.
The Container instance can be exported in the bootstrapper using following
container.ComposeExportedValue<CompositionContainer>(container);
Then in the viewmodel, the IRegionManager instance can be imported using the code
IServiceLocator serviceLocator = ServiceLocator.Current;
CompositionContainer container = serviceLocator.GetInstance<CompositionContainer>();
RegionManager = container.GetExportedValue<IRegionManager>();
However, referring a View in ViewModel is a violation of the MVVM pattern. But since I was following an article here to learn Prism , I had to get along the same. Also the article was in Silverlight and I had to find a way to import RegionManager in wpf, which is little different.
regards,
Nirvan.
Try using [ImportingConstructor] like this:
public class ProductView : Window
{
private IProductViewModel viewModel;
[ImportingConstructor]
public ProductView(IProductViewModel ViewModel)
{
this.viewModel = ViewModel;
this.DataContext = this.viewModel;
}
}
public class ProductViewModel: IProductViewModel, INotifyPropertyChanged
{
private IRegionManager regionManager;
private ICommand editUserCommand;
[ImportingConstructor]
public ProductViewModel(IRegionManager InsertedRegionManager)
{
this.regionManager = InsertedRegionManager;
editUserCommand = new DelegateCommand(ExecuteEditUserCommand, CanExecuteEditUserCommand);
}
public ICommand EditUserCommand
{
get {return this.editUserCommnad;}
}
private bool CanExecuteEditUserCommand()
{
return true;
}
private void ExecuteEditUserCommand()
{
this.regionManager......
}
}

Prism2/MVVM Close View from ViewModel

How do I close a View from its ViewModel?
I've a WPF window which has defined multiple Regions and being used as a Shell to host views for my application. I would like to have a View able to remove itself from the Region, or close it from a tabbed container. How can I accomplish this behavior from ViewModel.
Since your ViewModel doesn't (and shouldn't) have a reference to the View, you can't close it directly. However, what you can do is add an Event in your ViewModel to indicate that it wants to be closed.
Josh Smith has written an article showing how to do this (about halfway through the article).
This really depends on your app architecture, but here's how I do it with Prism.
First I want to say, it is ok to have your VM reference the View just as long as it is not a concrete implementation of the View, ie, references by interface.
I marry the View and ViewModel using dependency injection, very similar to how it's done in the StockTraderRI. So I have an IView and an IViewModel. IViewModel has a propery called "View" of type IView.
From the code layer (for me, usually the controller...see StockTraderRI) that works with your regions, add the mechanism to remove your view from the region.
For example:
myRegion.Remove(myIViewModel.View);
If regions are handled by a controller, you may want to put a simple event on the VM to notify when a VM wants to be "closed". You can also experiment with the IEventAggregator if you wish to use a weak eventing model. If the region is handled in the VM, simply add that code there.
This how my Login module looks like:
public class LoginModule : IModule
{
private readonly IUnityContainer container;
public LoginModule(IUnityContainer container)
{
this.container = container;
}
#region IModule Members
public void Initialize()
{
this.container.RegisterType<ILoginController, LoginController>(new ContainerControlledLifetimeManager());
this.container.RegisterType<ILoginView, LoginView>();
this.container.RegisterType<ILoginViewModel, LoginViewModel>();
ILoginController controller = this.container.Resolve<ILoginController>();
controller.Run();
}
#endregion
}
This is the controller:
public class LoginController : ILoginController
{
private readonly IRegionManager regionManager;
private readonly ILoginViewModel model;
public LoginController(IRegionManager regionManager, ILoginViewModel model)
{
this.regionManager = regionManager;
this.model = model;
model.RequestClose += new EventHandler(model_RequestClose);
}
void model_RequestClose(object sender, EventArgs e)
{
regionManager.Regions["LoginRegion"].Remove(model.View);
}
#region ILoginController Members
public void Run()
{
// Register views here
regionManager.Regions["LoginRegion"].Add(model.view);
}
#endregion
}
And this is my ViewModel:
public class LoginViewModel : ViewModelBase, ILoginViewModel
{
IEventAggregator _eventAggregator;
RelayCommand _loginCommand;
private readonly UserProfileRepository _userProfileRepository;
public event EventHandler RequestClose;
public ICommand LoginCommand
{
get
{
if (_loginCommand == null)
{
_loginCommand = new RelayCommand(
param => this.Login(),
param => this.IsValid());
}
return _loginCommand;
}
}
public LoginViewModel(IEventAggregator eventAggregator, UserProfileRepository userProfileRepository, ILoginView view)
{
this._eventAggregator = eventAggregator;
this._userProfileRepository = userProfileRepository;
this.View = view;
}
#region ILoginViewModel Members
public ILoginView View { get; private set; }
#endregion
}

Resources