Wpf Mvvm: ObservableCollection of ChildViewModels and Dependency Injection - wpf

I am a WPF application beginner and I'm currently using MvvmLight for my application.
I have a MainViewModel that holds a ObservableCollection of ChildViewModels(Type ViewModelBase). Each ChildViewModel is bound to a tab item in the XAML. So, I have a TabControl with tab items and each tab item has its own View and ViewModel.
This is my MainViewModel. For now, I have only one ChildViewModel which is DozentViewModel.
public class MainViewModel : ViewModelBase
{
private ObservableCollection<ViewModelBase> _vmCollection;
public ObservableCollection<ViewModelBase> VmCollection
{
get { return _vmCollection; }
set
{
Set(ref _vmCollection, value);
RaisePropertyChanged("VmCollection");
}
}
private DozentViewModel _dozentviewmodel;
public DozentViewModel Dozentviewmodel
{
get { return _dozentviewmodel; }
set
{
Set(ref _dozentviewmodel, value);
}
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(DozentViewModel dozentViewModel)
{
_dozentviewmodel = dozentViewModel;
VmCollection = new ObservableCollection<ViewModelBase>();
VmCollection.Add(_dozentviewmodel);
}
This is my ViewModelLocator code:
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<DozentViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public DozentViewModel DozentVM
{
get
{
return ServiceLocator.Current.GetInstance<DozentViewModel>();
}
}
I am trying to implement Dependency Injection by using Constructor Injection in the MainViewModel. The above code works but I am not sure if my implementation of DI is right. I would like to have an interface of IChildViewModel with which all my ChildViewModels will be implemented. If I use an interface like so, how can I achieve DI?

Related

Correct way to update property in ViewModel from Model

I'm fairly novice with WPF. It's my understanding that data changes in the model, and it should notify the viewmodel, and the view will bind to properties and things alike in the viewmodel. Is this correct? If so, I've been reading that the model should implement INotifyPropertyChanged, and look something like this
public class LoginModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public bool Authenticated { get; set; }
}
and in my ViewModel, I have a property "AuthResult", that should get the update from the Model property "Authenticated"
public partial class view1 : UserControl, INotifyPropertyChanged{
public bool AuthResult
{
get
{
return _authVal;
}
set
{
_authVal = value;
NotifyPropertyChanged("AuthResult");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
I know this current implementation is incorrect. I've found that I should be subscribing to the PropertyChanged notification from my model like so:
LoginModel.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(LoginModel_PropertyChanged);
void LoginModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == "Authenticated")
{
//do something
}
}
I don't see where the "AuthResult" property should be updated. Would I do something in the If statement like AuthResult = _model.Authenticated;?
EDITED:
and in my constructor?
LoginModel _model;
public view1(LoginModel model)
{
_model = model;
InitializeComponent();
}
If the model implements the INotifyPropertyChanged interface you can bind directly to it from the view:
<Button Content="Button" IsEnabled="{Binding Authenticated}" />
Note that the LoginModel class must raise the PropertyChanged event whenever the Authenticated property is set to a new value.
You could also expose the entire model entity through the view model class:
public class ViewModel
{
public ViewModel(LoginModel model)
{
Model = model;
}
public LoginModel Model { get; }
}
...and bind to it like this:
<Button Content="Button" IsEnabled="{Binding Model.Authenticated}" />
It is still the model class that must implement the INotifyPropertyChanged interface and raise change notifications.
Another option is for the view model to wrap any property of the model class that you want to be able to bind to from the view. Then you bind to a property of the view model class that in turn wraps a property of the model class something like this:
public class ViewModel
{
private readonly LoginModel _model;
public ViewModel(LoginModel model)
{
_model = model;
}
public bool AuthResult
{
get
{
return _model.Authenticated;
}
set
{
_model.Authenticated = value;
NotifyPropertyChanged("AuthResult");
}
}
}
<Button Content="Button" IsEnabled="{Binding AuthResult}" />
The benefit of using this latter approach is that view has no dependency upon the model class. It binds to the view model class only and this is how the MVVM design pattern typically is meant to be implemented.
But if you do bind to a (wrapper) property of the view model and want the view to be updated whenever a property of the model class is set, the model has to notify the view model that it has changed one way or another, i.e. it has to raise some kind of event or similar. And this typically means implementing the INotifyPropertyChanged interface. The view model can then subscribe to the PropertyChanged event of the model and raise its own PropertyChanged event for the data bound property whenever the model is updated, e.g.:
public class ViewModel
{
private readonly LoginModel _model;
public ViewModel(LoginModel model)
{
if (model == null)
throw new ArgumentNullException("model");
_model = model;
_model.PropertyChanged += OnModelChanged;
}
private void OnModelChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Authenticated")
NotifyPropertyChanged("AuthResult");
}
public bool AuthResult
{
get
{
return _model.Authenticated;
}
set
{
_model.Authenticated = value;
NotifyPropertyChanged("AuthResult");
}
}
}
Just use Model as member in the ViewModel
public class ViewModel
{
private Model _myModel;
public Model MyModel
{
get { return _myModel; }
set
{
if (Equals(_myModel, value)) return;
_myModel = value;
NotifyPropertyChanged(nameof(MyModel));
}
}
}
Then in xaml you can bind properties of Model
<CheckBox IsChecked="{Binding MyModel.Authenticated}" />
With this approach your ViewModel will be "build" around your Model.
In case you don't want that models implement INotifyPropertyChanged than create a "Facade" class of model in use it in same way as previous example.
public class ModelFacade : INotifyPropertyChanged
{
private Model _myModel;
public bool Authenticated
{
get { return _myModel.Authenticated; }
set
{
_myModel.Authenticated = value;
NotifyPropertyChanged(nameof(Authenticated));
}
}
}
public class ViewModel
{
private ModelFacade _myModel;
public ModelFacade MyModel
{
get { return _myModel; }
set
{
if (Equals(_myModel, value)) return;
_myModel = value;
NotifyPropertyChanged(nameof(MyModel));
}
}
}

how to inject service to view Model in mvvm light

I try to use dependency injection to inject service into view model. try this code
ViewModelLocator
public class ViewModelLocator
{
public static MainViewModel _main;
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<IAccountService, AccountService>();
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<LoginViewModel>();
}
/// <summary>
/// Gets the Main property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public IMainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<IMainViewModel>();
}
}
/// <summary>
/// Gets the Main property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public LoginViewModel Login
{
get
{
return ServiceLocator.Current.GetInstance<LoginViewModel>();
}
}
/// <summary>
/// Cleans up all the resources.
/// </summary>
public static void Cleanup()
{
}
}
MainViewModel
public interface IMainViewModel
{
}
public class MainViewModel : ViewModelBase
{
private readonly IAccountService _accountService;
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IAccountService accountService)
{
_accountService = accountService;
}
}
my LoginViewModel
public interface ILoginViewModel
{
ICommand Authorize { get; set; }
}
public class LoginViewModel : ViewModelBase, IMainViewModel
{
private IAccountService _accountService;
public LoginViewModel(IAccountService _accountService)
{
this._accountService = _accountService;
Authorize = new RelayCommand(() => CheckAuthorized(), () => true);
}
public ICommand Authorize { get; private set; }
}
and this my LoginView
public partial class Login : UserControl
{
ILoginViewModel _loginViewModel;
public Login(ILoginViewModel _loginViewModel)
{
this._loginViewModel = _loginViewModel;
InitializeComponent();
DataContext = _loginViewModel;
}
}
my question is, when i try to inject in my View, the object reference is null,
how can i use DI in mvvm light?
You are trying to inject ILoginViewModel into the Login view but seems like ILoginViewModel is not registered so you may need to register the same
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<IAccountService, AccountService>();
SimpleIoc.Default.Register<IMainViewModel, MainViewModel>(); //register view models
SimpleIoc.Default.Register<ILoginViewModel, LoginViewModel>();
}
thanks #Jason for pointing this out
perhaps by mistake LoginViewModel is implementing IMainViewModel instead of ILoginViewModel
so need to be corrected this way
public class LoginViewModel : ViewModelBase, ILoginViewModel
{
...
}
also implemtent IMainViewModel in MainViewModel to make it resolve via the container
public class MainViewModel : ViewModelBase, IMainViewModel
{
...
}

Where to Load AppSettings and Set ViewModel Properties Using MEF and Prism

I created a WPF MVVM app using MEF and Prism 5.0. I have MEF loading a module at startup called MobileRecruiterModule (below). I need to read some settings from App.config (or any config file really) and update the ViewModel properties with those config values so the View can pick them up.
Where is the appropriate place to load settings here? Do I do it in the MEF module (the thing that implements Microsoft.Practices.Prism.Modularity.IModule or in my View?
MobileRecruiterModule.cs
[ModuleExport(typeof (MobileRecruiterModule))]
public class MobileRecruiterModule : IModule
{
/// <summary>
/// The region manager
/// </summary>
[Import] public IRegionManager Region;
/// <summary>
/// Notifies the module that it has be initialized.
/// </summary>
public void Initialize()
{
Region.RegisterViewWithRegion(RegionNames.MainContentRegion, typeof (MobileRecruiterView));
}
...
}
MobileRecruiterView.xaml.cs
[Export("MobileRecruiterView")]
[PartCreationPolicy(CreationPolicy.Shared)]
[RegionMemberLifetime(KeepAlive = false)]
[Export]
public partial class MobileRecruiterView : UserControl
{
[Import]
public MobileRecruiterViewModel ViewModel
{
get { return (MobileRecruiterViewModel)DataContext; }
set { DataContext = value; }
}
[ImportingConstructor]
public MobileRecruiterView(MobileRecruiterViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
}
MobileRecruiterViewModel.cs
[Export]
public class MobileRecruiterViewModel : BindableBase
{
public string DatabaseServer { get; set; }
... and a few other properties that the XAML view binds to ...
}
I would suggest that you should load your settings in ViewModel constructor. Because your ViewModel is the DataContext for the View, you have to initialize it before you show it. I hope that you do not store any BLOB in it, so the time for *.config loading will be small enough to do it on UI thread.

MVVM Light pass parameters to child view model

I am new to MVVM and WPF.
I am using MVVM Light to make an application which contains a DataGrid within a window, which has a view model (MainViewModel) and another window for adding and editing records in the DataGrid, that also has its own view model (EditViewModel).
What I am worried about is the approach I am using to open the Add/Edit window from the MainViewModel. In the MainViewModel I have a property SelectedItem, which is bound to the SelectedItem property of the DataGrid and an IsEdit boolean property that indicates if the Add/Edit window should be launched in Add or Edit mode.
When the Add/Edit window gets opened in edit mode, in the constructor of its view model I have the following line:
MainViewModel mainViewModel = ServiceLocator.Current.GetInstance<MainViewModel>();
That obviously retrieves the current instance of the MainViewModel, which works perfectly fine, but I am not really sure it is the best way to do this.
Also if I have more than one instances of the Main window, that use the same MainViewModel instance and I open an instance of the Add/Edit window from both of them, the Add/Edit windows are going to get data from the same instance of the MainViewModel which may be a problem.
If I try to create a new instance of MainViewModel for each MainWindow I open, then I don't know how to pass the instance of the currently used MainViewModel to the EditViewModel.
I hope I made clear what I need to do. Tell me if I have missed something and I will add it:)
Thanks in advance
Hi if I havent misunderstood your problem incorrect you can do it this way:
Since i need IsRequired dependency Property in both MainView and EditView i created a class that extends Window class
public class ExtendedWindow:Window
{
public static readonly DependencyProperty IsRequiredProperty = DependencyProperty.Register("IsRequired", typeof(bool), typeof(ExtendedWindow));
public bool IsRequired
{
get { return (bool)GetValue(IsRequiredProperty); }
set { SetValue(IsRequiredProperty, value); }
}
}
MainView and ViewModel
public partial class MainWindow:ExtendedWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
EditView editView = new EditView();
**((EditViewModel)editView.DataContext).IsRequired = this.IsRequired;**
editView.Show();
}
}
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
IsRequired = true;
}
private bool isRequired;
public bool IsRequired
{
get { return isRequired; }
set { isRequired = value; Notify("IsRequired"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
EditView and ViewModel
public partial class EditView:ExtendedWindow
{
public EditView()
{
InitializeComponent();
DataContext = new EditViewModel();
}
}
public class EditViewModel : INotifyPropertyChanged
{
private bool isRequired;
public bool IsRequired
{
get { return isRequired; }
set { isRequired = value; Notify("IsRequired"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
This is just kind of dummy but can give you idea how you can do it. I have tried it in dummy and its working fine.

Broken binding with Prism, Silverlight and ViewFirst approach

The problem we are having is that we cannot get binding to work in our
prism silverlight application when using the view-model first
approach. The view first approach work fine. We have gone over the
official documentation and various web sites, but have still not
resolved the issue. Below is the code for both the view-model first,
and the view first approach. Are we missing something? Read about it on my blog http://silvercasts.blogspot.com
View-Model first approach:
Bootstrapper:
internal void RegisterLoginRegionAndView()
{
IRegionManager regionManager = Container.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion(ShellRegionNames.MainRegion,
() => Container.Resolve<IViewModel>().View);
}
ViewModel:
public ViewModel(IView view)
{
View = view;
View.SetModel(this);
User = new User();
User.Username = "TestUser";
}
ViewModel Interface:
public interface IViewModel
{
IView View { get; set; }
}
View Interface:
public interface IView
{
void SetModel(IViewModel model);
}
View Xaml:
<TextBox x:Name="Username" TextWrapping="Wrap" Text="{Binding User.Username}" />
View Code Behind:
public void SetModel(IViewModel viewModel)
{
this.DataContext = viewModel;
}
View first approach
Bootstrapper:
regionManager.RegisterViewWithRegion(ShellRegionNames.MainRegion, typeof(IView));
ViewModel:
public ViewModel()
{
User = new User();
User.Username = "TestUser";
}
View Code Behind:
public View(IViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
Your implementation of SetModel on your view needs to be as follows:
public void MyUserControl : UserControl, IView
{
//...
public void SetModel(IViewModel vm)
{
this.DataContext = vm;
}
}
If that's not there, it needs to be (you haven't posted your implementation of SetModel, but this would be the source of the issue in this case).
If this is not the issue, it's likely because your ViewModel does not implement INotifyPropertyChanged. I usually use a base ViewModel that does this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And then all of my ViewModels derive from that:
public class MyViewModel : ViewModelBase
{
private User _user;
public User User
{
get { return _user; }
set
{
_user = value;
OnPropertyChanged("User");
}
}
}
Note: in your case the "User" object should probably also be a ViewModel and also raise OnPropertyChanged for the Username property.
Hope this helps.
The obvious difference to me is that you set the DataContext in the "view first" approach, but not in the "view model first" approach. I'm not sure if Prism sets the DataContext for you (I'd guess that you're assuming that it does) but try setting the DataContext manually to see if this is the problem. In your ViewModel constructor you call View.SetModel(this) - does that call set the DataContext?
The problem was that I was using the SetModel method before the data object was instanced. Moving it like this:
public ViewModel(IView view)
{
View = view;
User = new User();
User.Username = "TestUser";
View.SetModel(this);
}
solved the problem.

Resources