Dependency Injection with MVVM and Child Windows - c#

I am using MVVM Light and I'm currently using SimpleIoC that comes with the package. I'm getting a bit stuck with the dependency injection. I have a bunch of services that I want to use in my view models, however most windows are a List-Edit paradigm, i.e. one screen lists all of type Person and then you can Add or Edit a Person via a new screen.
When I was doing all code in the code behind my code for adding and editing a record was as follows:
View
private void btnEdit_Click(object sender, RoutedEventArgs e)
{
_viewModel.Edit();
}
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
_viewModel.Add();
}
View Model
public void Add()
{
var f = new TypeDetails();
f.Show();
}
public void Edit()
{
if (SelectedItem == null)
return;
var f = new TypeDetails(SelectedItem.Id);
f.Show();
}
The constructor of TypeDetails is as follows:
public TypeDetails(int id = 0)
{
InitializeComponent();
_viewModel = new TypeDetailsViewModel(id);
DataContext = _viewModel;
}
What would the best be to implement this type functionality with MVVM Light? I have been using the ViewModelLocator class for the List screens, however I cannot see a way to do this using the SimpleIoC. My way round so far has been to keep the constructor the same, which works fine until I need to inject dependencies into the TypeDetailsViewModel such as a service. With a service the constructor of TypeDetailsViewModel would be:
public TypeDetailsViewModel(ISomeService someService, int id = 0)
{
...
}
But that means in my view constructor I have to build these dependencies one at a time and manually inject them...
public TypeDetails(int id = 0)
{
InitializeComponent();
_viewModel = new TypeDetailsViewModel(SimpleIoC.Current.GetInstance<ISomeService>(),id);
DataContext = _viewModel;
}
Is there a better way to do this?

First off I would look into the "RelayCommand" class which is part of MVVM Light. It will remove the need for events in your code behind. Start with that.
You should always favor "Constructor Injection" instead of the ServiceLocator (ex: SimpleIoC.Current.GetInstance())
Your ViewModel constructor should only be injecting services and not primitive types like "int". In your example "int id" should be the parameter of a method and not injected.
Ex: Instead, your TypeDetailsViewModel should look more like:
public TypeDetailsViewModel(ISomeService someService)
{
TypeDetail GetDetailsCommand(int id)
{
...
}
}
Lastly, your Models should never have any reference to your ViewModels.
For your DataContext, you can use a ViewModelLocator (ViewModels in ViewModelLocator MVVM Light)
To hook up your View and ViewModel to use the GetDetailsCommand, you can use the EventToCommand behavior (http://msdn.microsoft.com/en-us/magazine/dn237302.aspx). Ex: The OnLoaded event on the View calls the GetDetailsCommand on your ViewModel.

Related

Using MvvmLight.Messaging.Messenger to instantiate new View + ViewModel (Window)

I have my MainView and an associated MainViewViewModel which are linked by ViewModelLocator.
Within MainViewViewModel there is a command which should trigger a new Window to open which has it's own View and ViewModel (NewView and NewViewViewModel).
In a lot of the examples I've seen it is suggested to use Mvvmlight's Messenger to do something like this:
public class MainViewViewModel
{
private void OpenNewWindow()
{
Messenger.Default.Send(new NotificationMessage("NewView"));
}
}
And then register the NewViewViewModel and handle the message like this:
public class NewViewViewModel
{
public NewViewViewModel()
{
Messenger.Default.Register<NotificationMessage>(this, NotificationMessageReceived);
}
private void NotificationMessageReceived(NotificationMessage obj)
{
if (obj.Notification == "NewView")
{
NewView view = new NewView();
view.Show();
}
}
}
However, this doesn't work because the NewViewViewModel isn't yet instantiated (so isn't registered with Messenger). Additionally, this doesn't fit with MVVM because NewViewViewModel is responsible for creating NewView.
What is the correct way to achieve a simple command which instantiates and opens a new View and ViewModel pair which are linked via ViewModelLocator and setting of DataContext="{Binding NewView, Source={StaticResource Locator}}" in NewView.xml?
Use a window service:
MVVM show new window from VM when seperated projects
You may either inject the view model to with an IWindowService implementation or use a static WindowService class:
public static class WindowService
{
public static void OpenWindow()
{
NewView view = new NewView();
view.Show();
}
}
Dependency injection is obviously preferable for being able to unit test the view model(s) and switch implementations of IWindowService at runtime.

Passing parameters to the ViewModel using a custom INavigationService Implementation?

I've built a small app using MVVM Light, and I've reached a point in which I need to pass parameters between a few different ViewModels in my app. I've explored several different options, but I'm not a huge fan of them really. The most promising I've encountered so far is simply passing messages between the ViewModels, but this is somewhat limiting as the application has the potential to have multiple of the same View open at once, and I need to isolate the parameters to a singular instance of a View/ViewModel.
I'm not currently using the built in INavigationService provided by MVVM Light, but I've made one incredibly similar (and if I can solve the parameter injection, I'll likely switch).
Here is a trimmed down version of my navigation service:
public class NavigationService : INavigationService
{
/* this implementation will not allow us to have the same window open
more than once. However, for this application, that should be sufficient.
*/
public NavigationService()
{
_openPages = new Dictionary<string, Window>();
}
private readonly Dictionary<string, Window> _openPages;
public void ClosePage(string pageKey)
{
if (!_openPages.ContainsKey(pageKey)) return;
var window = _openPages[pageKey];
window.Close();
_openPages.Remove(pageKey);
}
public IEnumerable<string> OpenPages => _openPages.Keys;
public void NavigateTo(string pageKey)
{
if (!AllPages.ContainsKey(pageKey))
throw new InvalidPageException(pageKey);
// Don't re-open a window that's already open
if (_openPages.ContainsKey(pageKey))
{
_openPages[pageKey].Activate();
return;
}
var page = (Window) Activator.CreateInstance(AllPages[pageKey]);
page.Show();
page.Closed += OnWindowClosedHandler;
_openPages.Add(pageKey, page);
}
// Probably a better way to remove this.
private void OnWindowClosedHandler(object sender, EventArgs args)
{
foreach (var item in _openPages.Where(kvp => kvp.Value == sender).ToList())
{
_openPages.Remove(item.Key);
}
}
// Reflection might work for this.
// Might also consider making this more dynamic so it isn't hard-coded into my service
private readonly Dictionary<string, Type> AllPages = new Dictionary<string, Type>
{
["AddPatientView"] = typeof(AddPatientView),
["CheckInView"] = typeof(CheckInView),
["MainView"] = typeof(MainWindow),
["PatientLookupView"] = typeof(PatientLookupView),
["PatientDetailsView"] = typeof(PatientDetailsView)
};
}
Most of my ViewModels use dependency injection to wire-up other injected services, like so:
public class CheckInViewModel : ViewModelBase
{
public CheckInViewModel(ILicenseValidationService licenseValidationService,
IPatientFetchService patientFetchService,
IPatientCheckInService patientCheckInService)
{
if (IsInDesignMode)
{
Title = "Find Member (Design)";
}
else
{
Title = "Find Member";
CanFetch = true;
FindMemberCommand = new RelayCommand(async () => await FindMemberHandler(), () => CanFetch);
CheckInPatientCommand = new RelayCommand<Window>(async (window) => await CheckInPatientHandler(window),
(window) => Patient?.PatientId != null);
_licenseValidationService = licenseValidationService;
_patientFetchService = patientFetchService;
_patientCheckInService = patientCheckInService;
}
}
}
I would like to implement some method of injecting other parameters alongside my injected services. Has anything like this been done in a relatively straightforward way?
The way dependency injection works in almost all cases is when you resolve or getinstance a type that then will use the constructor with the most parameters in providing you with an object.
If you register a concrete object against an interface ( or just a type ) then later resolve/getinstance a class which uses one of those things in it's ctor then DI provides that instance you registered.
With MVVMLight you have SimpleIoc and SimpleIoc.Default is equivalent to that static service you're thinking about.
There is a catch with simpleioc. It's very simple.
With simpleioc once you getinstance a viewmodel of a given type then that is a singleton. You can force a different instance by passing a unique key but they're all cached. You can getinstance with parameters and maybe that replaces the current object. I'm not sure offhand. A more sophisticated DI container might be advisable.
Other than that.
Since you're using different windows this creates a bit of a complication in that you want to instantiate a window and that will have a datacontext you need to provide somehow with your parameters.
What you could use is viewmodel first.
You get inavigationservice out DI or resources or a static.
You have a DoWindow(Object vm) method.
When you want to navigate you presumably know the parameters for the vm. New up your viewmodel with parameters. New up a window you use for all views. Set it's content to your viewmodel. That is templated out into what you have as windows now. Except you make them usercontrols. Use Datatype="vmtype" to associate view as template with viewmodel. Bind the title of your window to Content.Title and of course add a Title property to a base viewmodel.
ALternatively with a single window app you can have a contentcontrol fills the area yor views will be shown in. Bind the content of that to a currentviewmodel property and you can use viewmodel first navigation within that window.

Getting around circular dependency

Could I get help with a little issue I am encountering regarding splitting projects into different tiers. In my ViewModel logic I have code where I create a new instance of a window when a button is clicked (I use ICommand interface for that)
The problem is however, that this requires my View folder which is in the presentation layer, I can’t reach it as my presentation layer is dependent on my ViewModel in the logic layer.
I would just move the code that deals with the creation of the pages to the view code behind but I also pass the current instance of a viewmodel as a parameter for that new window being created (for eventhandling purposes).
Any help is much appreciated! Thanks.
A view model shouldn't create instances of windows. What you could do is to inject your view model with a service that is responsible for creating windows, e.g.:
public class MainWindowViewModel
{
private readonly IWindowService _windowService;
public MainWindowViewModel(IWindowService windowService)
{
_windowService = windowService;
CreateWindowCommand = new DelegateCommand(() =>
{
_windowService.CreateWindow(new SomeViewModel());
});
}
public ICommand CreateWindowCommand { get; }
}
Define the IWindowService interface in the view model project and the concrete implementation of it in the view/presentation project:
public class WindowService : IWindowService
{
public void CreateWindow(SomeViewModel vm)
{
Window win = new Window();
win.DataContext = vm;
win.Show();
}
}

MVVM How to set datacontext when viewmodel uses async

After hours of searching I am still without answer to this question. I have read this nice writing about async MVVM and made my viewmodel to use factory method.
public class MainViewModel
{
// sic - public, contrary to the pattern in the article I cite
// so I can create it in the Xaml as below
public MainViewModel()
{
}
private async Task InitializeAsync()
{
await DoSomethingAsync();
}
public static async Task<MainViewModel> CreateAsync()
{
var ret = new MainViewModel();
await ret.InitializeAsync();
return ret;
}
}
This is clear for me, but I can't understand how to make instance of MainViewModel and set it to datacontext in MainPage. I can't simply write
<Page.DataContext>
<viewModel:MainViewModel/>
</Page.DataContext>
because I should use MainViewModel.CreateAsync()-method. And I can't do it on code-behind, which I even want to do, because code-behind -constructor is normal method, not an async-method. So which is proper way to continue?
made my viewmodel to use factory method
I'm normally a fan of that approach - it's my favorite way to work around the "no async constructors" limitation. However, it doesn't work well in the MVVM pattern.
This is because VMs are your UI, logically speaking. And when a user navigates to a screen in an app, the app needs to respond immediately (synchronously). It doesn't necessarily have to display anything useful, but it does need to display something. For this reason, VM construction must be synchronous.
So, instead of trying to asynchronously construct your VM, first decide what you want your "loading" or "incomplete" UI to look like. Your (synchronous) VM constructor should initialize to that state, and it can kick off some asynchronous work that updates the VM when it completes.
This is not too hard to do by hand, or you can use the NotifyTaskCompletion approach that I described in an MSDN article on async MVVM data binding to drive the state transition using data bindings.
You have to initalize the viewmodel before the window is open. Go to your App.xaml file and remove the part: StartupUri="MainWindow.xaml". Then you go to the App.xaml.cs and add this:
protected async override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var mainWindow = new MainWindow { DataContext = await CreateAsync() };
mainWindow.Show();
}
I would re-factor. Make the MainViewModel construction / instantiation lightweight. Then create a Load or Initialize method on your VM. From the code-behind create an instance, set it to the DataContext, then invoke the init method and let it run.
E.g.
/// <summary>Interaction logic for MainWindow.xaml</summary>
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
var dc = new MainViewModel();
dc.Initialize("Hello", " ", "world");
this.DataContext = dc;
}
}
public class MainViewModel
{
/// <summary>Simple constructor</summary>
public MainViewModel() { }
public void Initialize(params object[] arguments)
{
//use the task to properly start a new thread as per:
//http://stackoverflow.com/a/14904107/1144090 and
//https://msdn.microsoft.com/en-us/library/hh965065.aspx
//(what would happen if we simply invoke init async here?)
this.InitializeAsync(arguments)
.ContinueWith(result =>
{
if (!result.IsFaulted)
return;
MessageBox.Show("Unexpected error: " + Environment.NewLine + result.Exception.ToString());
});
}
private async Task InitializeAsync(params object[] arguments)
{
await Task.Delay(2333);
MessageBox.Show(String.Concat(arguments));
}
}
Note that this is the quick-and-dirty solution, the other two answers (paired with a dependency injection framework) will give you proper high-level structure for your solution.
Firstly, you should make default constructor as private to avoid misusing your class (the article you cite does this - the constructor is private).
The approach you are using to set DataContext is not suitable for MVVM pattern (the View shouldn't create its ViewModel itself).
You should create your View and ViewModel in the higher level layer and have that layer bind them. Says if the Page is your main View you should create them in App.xaml.cs by overriding OnStartup, something like this:
var page = new Page();
var dataService = new YourDataService(); // iff Create or the ctor require arguments
var viewModel = await MainViewModel.CreateAsync(dataService);
page.DataContext = viewModel;
page.Show();

MVVM light, send message via Messenger from ViewModel to new child window, which is not initialized yet

I've following architecture:
desktop application, .Net 4.5, C#, WPF, MVVM Light, Messenger, IoC - ViewModel locator, so ViewModels doen't know anyhing about Views.
I have main view with data grid of some elements, and I want to display details of each individual element in new/child windows after double click on data grid.
I've bind event double click on main view to main view model. From this event handler in main view model, message is sent via Messanger.
New view (new/child window) is created in main view via delegate of also double click.
New/child window is a view which locate his view model and this view model register to the specific message in his constructor.
The problem is that new/child window (new view, and view model so on) is created too late, because message is already sent when new view model register for it.
Do you know maybe some patterns for such architecture. Any ideas will be appreciated.
It would help to know exactly what you try to do.
If your problem is just to display a detailed Window when double click on a row, I would say: create only one childWindow at start, and play with its visbility when required.
If you really need a new window each time, you could create it from your viewModel with an injected service for example.
In any case, you never has to create your window from main view! Either you create one window at start, either you dynamically create it from view model.
You cannot hope to create it from view and send the message in your view model.
Edit about the injected service, you could use something like that:
public interface IWindowService
{
void Open<TWindow>(ViewModelBase viewModel)
where TWindow : Window;
}
public class WindowService : IWindowService
{
private readonly IUIDispatcher _dispatcher;
public WindowService(IUIDispatcher dispatcher)
{
_dispatcher = dispatcher;
}
public void Open<TWindow>(ViewModelBase viewModel)
where TWindow : Window
{
_dispatcher.Run(() => OpenThreadSafe<TWindow>(viewModel));
}
private static void OpenThreadSafe<TWindow>(ViewModelBase viewModel) where TWindow : Window
{
var view = (TWindow) Activator.CreateInstance(typeof(TWindow), viewModel);
view.Show();
}
}
public class UIDispatcher : IUIDispatcher
{
public void Run(Action action)
{
var dispatcher = DispatcherHelper.UIDispatcher;
if (dispatcher == null)
{
action();
return;
}
DispatcherHelper.CheckBeginInvokeOnUI(action);
}
Note this DispatcherHelper come from MVVMlight, but you could erplace it easily.
Hope it helps.
The problem is that the ViewModel Locator creates the viewmodel instance only when it is needed (lazy loading).
just configure the ViewModelLocator to instantiate the viewmodel eager instead of lazy. This is done by passing the parameter "true" to the IoC Container.
Sample:
namespace Administration.ViewModel
{
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
//Eager Loading
SimpleIoc.Default.Register<UserManagementViewModel>(true);
//Lazy Loading
SimpleIoc.Default.Register<InformationManagementViewModel>();
}
public UserManagementViewModel UserManagementViewModel
{
get
{
return ServiceLocator.Current.GetInstance<UserManagementViewModel>();
}
}
public InformationManagementViewModel InformationManagementViewModel
{
get
{
return ServiceLocator.Current.GetInstance<InformationManagementViewModel>();
}
}
public static void Cleanup()
{
SimpleIoc.Default.Unregister<UserManagementViewModel>();
SimpleIoc.Default.Unregister<InformationManagementViewModel>();
}
}
}

Categories

Resources