Can someone explain how the View and ViewModel are connected? I can't find anywhere the xaml or the xaml.cs for the View that references the ViewModel, nor anything in the ViewModel.cs file that references the View, yet they are somehow connected, and binding members from the ViewModel to the View work.
Also, in the constructor of each, there is only the InitializeComponent for the View and a basic constructor for the ViewModel (no declaration/definition of the View).
Thanks!
There are various options here.
Something has to set the View's DataContext to be an instance of the ViewModel. There are lots of options here:
This can be done directly in xaml (the View just instances the ViewModel directly).
This can be done in the View's constructor (this.DataContext = new MyViewModel();)
This can be handled via a DataTemplate
A "coordinating" class can wire these together (ie: a separate "presenter" class can construct both and set the DataContext appropriately)
The most common are to either have the View define the VM in the xaml (View-first), or to have everything based from a ViewModel-centric point of view, and have WPF automatically create the View based on the bound VM (ViewModel-first).
The former approach is what's used by a lot of toolkits, such as MVVM Light. The latter approach is what I used in my MVVM blog series, and used by some other toolkits.
A "clean" way for connecting the views to the view-models would be...
When you create the views, for each view, set its DataSource to its view-model:
E.g.
public class App
{
private void OnAppStart()
{
var model = new MainModel();
var vm = new MainVM();
var view = new MainWindow();
vm.Model = model;
view.DataSource = vm;
view.Show();
}
}
When the model you are viewing changes, update the VM:
public class MainVM
{
private void OnSelectedModelItemChanged()
{
this.SelectedItem = new ItemVM();
this.SelectedItem.Model = this.SelectedModelItem;
}
}
And use data templates to make view select the correct sub views for each VM.
The view contains an object of the view model class in the xaml.
The InitializeComponent function creates all the controls on the page, sets styles, etc.
As others have already shown, there are multiple options. Of course, whenever you hear of multiple options you have to wonder what are the advantages and disadvantages of each. Well, it just so turns out that all of them have major disadvantages except one.
The following approach involves no external libraries, no additional housekeeping classes and interfaces, almost no magic, and is very flexible because you can have viewmodels that contain other viewmodels, and you get to instantiate each one of them, so you can pass constructor parameters to them.
For the viewmodel of the main window:
using Wpf = System.Windows;
public partial class TestApp : Wpf.Application
{
protected override void OnStartup( Wpf.StartupEventArgs e )
{
base.OnStartup( e );
MainWindow = new MainView();
MainWindow.DataContext = new MainViewModel( e.Args );
MainWindow.Show();
}
}
For all other viewmodels:
This is in MainViewModel.cs:
using Collections = System.Collections.Generic;
public class MainViewModel
{
public SomeViewModel SomeViewModel { get; }
public OtherViewModel OtherViewModel { get; }
public Collections.IReadOnlyList<string> Arguments { get; }
public MainViewModel( Collections.IReadOnlyList<string> arguments )
{
Arguments = arguments;
SomeViewModel = new SomeViewModel( this );
OtherViewModel = new OtherViewModel( this );
}
}
This in MainView.xaml:
[...]
xmlns:local="clr-namespace:the-namespace-of-my-wpf-stuff"
[...]
<local:SomeView DataContext="{Binding SomeViewModel}" />
<local:OtherView DataContext="{Binding OtherViewModel}" />
[...]
As you can see, a viewmodel can simply be a member (child) of another viewmodel; in this case SomeViewModel and OtherViewModel are children of MainViewModel. Then, in the XAML file of MainView, you can just instantiate each of the child views and specify their DataContext by Binding to the corresponding child viewmodels.
Related
I'm using Prism with Unity IOC-Container in a WPF-Project. For all my other Views I'm using only one ViewModel per View. Because this View should be a Mask for both Input and Output of Data, I'd like to use two ViewModels.
For the current navigation to the View i use this Code:
_regionManager.RequestNavigate(RegionNames.ContentRegionName, typeof(Events).ToString());`
The Code Behind of my View:
public partial class Events : UserControl
{
public Events(EventsViewModel viewModel)
{
InitializeComponent();
}
}
One of the ViewModels:
public class EventsViewModel : BindableBase
{
public EventsViewModel()
{
// Some Code
}
// Some other Code
}
I heard about ViewModel Discovery, where you give the Constructor of the View an Interface instead of an actual ViewModel. But i could only find exacly this much information.
// Example of such a Method
public Events(IViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel
}
public Interface IViewModel
{
}
My question is now: How do I navigate to the View and tell it wich ViewModel it should get as DataContext? I'm relatively new to programming and the MVVM-Pattern and english is not my native language so maybe I missed some Information. I would be glad if someone had an answer for this. Thanks in advance.
Edit: Workaround
I came up with a workaround wich works for me. I used the method SetDefaultViewTypeToViewModelTypeResolver() from the ViewModelLocationProvider and customized it.
// Bootstrapper.cs
protected override void InitializeShell()
{
var window = (MainWindow)this.Shell;
Application.Current.MainWindow = window;
// Calling the method
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(ResolveViewModel);
var regionManager = Container.Resolve<IRegionManager>();
window._regionManager = regionManager;
globalRegionManager = regionManager;
regionManager.RegisterViewWithRegion(RegionNames.ContentRegionName, typeof(StartScreen));
regionManager.RegisterViewWithRegion(RegionNames.ContentRegionName, typeof(Stock));
window.Show();
}
// Property for handing over the desired ViewModel
public static Type DynamicViewModel { private get; set; }
private Type ResolveViewModel (Type viewType)
{
string _viewModel = null;
var name = viewType.FullName.Replace(".Views.", ".ViewModels.");
if (DynamicViewModel != null)
_viewModel = DynamicViewModel.ToString();
else
_viewModel = $"{name}ViewModel";
var fullName = IntrospectionExtensions.GetTypeInfo(viewType).Assembly.FullName;
var typeString = string.Format(CultureInfo.InvariantCulture, $"{_viewModel}, {fullName}");
DynamicViewModel = null;
return Type.GetType(typeString);
}
Then when I want to navigate, I hand over the ViewModel beforehand.
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
Bootstrapper.DynamicViewModel = typeof(EventsViewModel);
_regionManager.RequestNavigate(RegionNames.ContentRegionName, typeof(Events).ToString());
}
A little tricky but it seems to work without any Exceptions.
If there is a cleaner way I'm alway happy to here it. :)
Here are some techniques for getting the view model for a view (view first).
View discovery in views code behind constructor
public EventsView(EventsViewModel view_model)
{
InitializeComponent();
DataContext = view_model;
}
Explicitly newing up the view model in code behind constructor
public EventsView()
{
InitializeComponent();
DataContext = new EventsViewModel();
}
View model locator in the XAML for the view
<UserControl x:Class="EventsModule.Views.EventsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<TextBlock Text="{Binding EventName}"></TextBlock>
</Grid>
</UserControl>
As long as you register your view with the region manager using any one of the techniques below, any one of the above will work. Then navigate like you are doing in your question.
RegionManager.RegisterViewWithRegion(RegionNames.ContentRegionName, typeof(EventsView));
UnityContainer.RegisterType(typeof(object), typeof(EventsView), typeof(EventsView).FullName);
UnityContainer.RegisterTypeForNavigation<EventsView>(typeof(EventsView).FullName);
The first will activate the view in the region and is usually seen in the module Initialize method. Those last 2 are for registering a view for later navigation. The last one requires the Prism.Unity namespace.
I don't believe that a view can have more the one view model since the view object only has one DataContext property on it. You may have to extend one view model to include everything you need. Someone may come along and prove me wrong on this. I have seen where a view model is shared with more than one view, but never a view having more than one view model.
In my UWP app im using MVVM Light and its ViewModelLocator to get ViewModels.
My ViewModelLocator looks like this, im passing guid to GetInstance to get unique VM.
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MyViewModel>();
}
public MyViewModel MyVM => ServiceLocator.Current.GetInstance<MyViewModel>(Guid.NewGuid().ToString());
}
I have usercontrol which needs to have unique VM, as I can have multiple instances of this user control in my app. Here is how im getting the ViewModel:
<UserControl x:Class="My.App.Controls"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding Path=MyVM, Source={StaticResource ViewModelLocator}}">
...
</UserControl>
Is this the correct way of getting unique VM's? Or are the VM's still cached, not disposed, even when my user control is not used any more?
UPDATE
So it seems that my code works ok, I get unique MyVM instance every time.
Now the question is, what is the correct way to unregistered/dispose view model.
I can do it with SimpleIoc.Default.Unregister(guid) but with my current implementation it is not very straight forward to deliver Guid (which was used in creating VM) to my user control, so I can unregister the VM.
Overall if I just create my VM runtime im ViewModelLocator with out SimpleIoc, is there any other drawbacks than losing dependency injection?
public class ViewModelLocator
{
public MyViewModel MyVM => new MyViewModel();
}
I assume your UserControl must be used by some View (window).
The ViewModel controlling that View could spawn the required ViewModels for the UserControl. You could bind the UserControl to this 'SubViewModel' property and also dispose of them however you then wished.
You can add an additional property to your view model and call a custom removal method in the view disposing your view model.
The modified getter for the view model looks like this:
public MyViewModel MyVM
{
get
{
String id = Guid.NewGuid().ToString();
var instance = SimpleIoc.Default.GetInstance<MyViewModel>(id);
instance.ID = id;
return instance;
}
}
The method for disposing the view model locator looks like this:
public static void UnregisterMyVM(String id)
{
SimpleIoc.Default.Unregister<MyViewModel>(id);
}
In your view you got to listen for closing events and call the unregistration method there:
public MyView()
{
InitializeComponent();
this.Closed += ((sender, arguments) =>
{
var viewModel = ((MyViewModel)this.DataContext);
viewModel.Dispose();
ViewModelLocator.UnregisterSourceCodeViewer(viewModel.ID);
this.DataContext = null;
});
}
I've set my View's data context to it's associated ViewModel in the View's code behind. But after reading a question on the MVVM pattern, it's suggested to move this glue code to the View's XAML mark up.
Googling has shown me examples of setting the context below in XAML, by setting a namespace to the VM and setting data context.
Although in my case, the MainViewModel takes a parameter of a CustomerRepository instance, which I'm not sure how to set up in XAML, base on the previous example.
Does anyone know how to move the data context to the View's XAML?
This is how I've currently set the View's code behind in C#:
public partial class MainView : Window
{
private MainViewModel ViewModel { get; set; }
public MainView()
{
InitializeComponent();
ViewModel = new MainViewModel(CustomerRepository.Instance);
this.DataContext = ViewModel;
}
}
You can instantiate your view model in your xaml like this:
<Window x:Class="MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test"
xmlns:viewModel="clr-namespace:ViewModels">
<Window.DataContext>
<viewModel:ExampleViewModel/>
</Window.DataContext>
</Window>
You explained that your view model constructor has a parameter for your repository. Do you need this constructor for unit testing purposes?
Usually you can just new up your repository instance in a parameterless constructor of the view model.
public class MainViewModel : ObservableObject, INotifyPropertyChanged
{
private static CustomerRepository _customerRepository;
// existing constructor
public MainViewModel(CustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
// new parameterless constructor
public MainViewModel(CustomerRepository customerRepository)
{
if (DesignerProperties.GetIsInDesignMode(this))
{
_customerRepository = new CustomerRepository();
}
}
}
Check if is in design mode
There is one more thing you need to think of when creating a view model in xaml: The view models constructor is called at design time when you open the view.
So you need to wrap any code in the constructor which does not make any sense at design time into a "only when not in design time" condition.
If you do not do this, your view will open with an error in the designer.
This is explained in this response: Check if in design mode
For my project I need to know which View is using my ViewModel
So i created this ViewModel:
public class HistoriqueViewModel : INotifyPropertyChanged
{
public HistoriqueViewModel(MetroWindow view)
{
this.MetroWindow = view;
this.ExportCommand = new RelayCommand(Export_Ex);
}
private MetroWindow _metroWindow;
public MetroWindow MetroWindow
{
get { return _metroWindow; }
set
{
if (Equals(value, _metroWindow)) return;
_metroWindow = value;
OnPropertyChanged();
}
}
//.........
}
And in the View constructor:
public partial class ViewHisto : MetroWindow
{
public ViewHisto()
{
InitializeComponent();
DataContext=new HistoriqueMV(this) ;
}
}
It Work perfectly for me but I want to know if this Break the MVVM Pattern?
Yes, this breaks MVVM. A properly constructed view model shouldn't care about what the view is.
Nothing in your code really suggests why you are passing that reference (other than exposing the view as a public property, which is an even bigger no-no) but there are several ways around it:
Pass the view as an interface and hold/expose that
Use a mediator to pass whatever messages necessary between the view model/view
Have the view invoke whatever methods it needs on the view model, and have the view model raise events that the view can listen to.
Any of the above approaches will provide far better decoupling than the one you are going with.
One other thing, its "View Model", not "Model View"
I've been working on an MVVM application in C# but consistiently run into some problems when working with the collections of ViewModels my View digests. Specifically, they all tend to relate to the issue of the Model being a private member of the ViewModel.
An example of this is creating new ViewModels (as requested by the View). For some preamble (although you might not need these to help me) here are example Model and ViewModel classes:
Private Class Model()
{
public string Name { get; set; }
}
Public Class ViewModel()
{
Private Model _Model;
Public Void ViewModel(Model model)
{
_Model = model;
}
Public String Name
{
get
{
return _Model.Name;
}
set
{
_Model.Name = value;
}
}
}
The entire model is never directly exposed as a public member of the ViewModel. The MainWindowViewModel handles collections of Models (private, the view cant see these) and ViewModels (public for View digestion):
Public Class MainWindowViewModel
{
Private List<Model> _NamesModel;
Private ObservableCollection<ViewModel> _NamesViewModel;
Public Void MainWindowViewModel()
{
//Lets pretend we have a service that returns a list of models
_NamesModel = Service.Request();
foreach(Model model in _NamesModel)
{
ViewModel viewmodel = new ViewModel(model);
_NamesViewModel.Add(viewmodel);
}
}
Public ObservableCollection<ViewModel> NamesViewModel
{
get
{
return _NamesViewModel;
}
}
}
Now thats the preamble but now I have a problem. How do I add a new ViewModel? Do methods within my view create a new ViewModel and populate that? Being a purist, I'm assuming the View should not be allowed to create or populate Models at all. Should my ViewModel contain a constructor that accepts nothing (i.e. no underlying model) and instead creates a blank to populate?
These kinds of issues keep coming up with a "pure" MVVM approach. I've had to create a public method in my ViewModel (bool compare(Model model)) that will compare a model (ready for deletion etc.) to it's internal one. If the models were publicly exposed (breaking purity) then it would be much easier to do stuff like find the ViewModel thats connected to a Model.
I can sympathize with some of those problems. I recently wrote an MVVM application where similar questions came up frequently. One of the tricks is to decide - definitively - which class is going to be responsible for Model instances. Do you want it to be your MainWindowViewModel? Or your NameViewModel? You don't want to share the responsibilities of creating/deleting the model between both of those classes; you'll have quite a logistical nightmare.
Secondly, even a "pure" MVVM approach doesn't dictate that you can't expose the model publicly. You said yourself that doing so would save you a lot of headache: DO IT. MVVM dictates only that the ViewModel has no knowledge/access of the View. There are many "official" MVVM examples that go so far as to implement their Model using the INotifyPropertyChanged interface, and bind directly to properties on the Model.
Personally, I think I would dictate control of the NameModel to the NameViewModel. This means that you should remove the list of NameModels completely from the MainWindowViewModel. If you want to give the NameViewModel an optional constructor which takes a Model, that would be fine too.
I'm a fan of this approach:
public NameViewModel : ViewModelBase
{
public NameModel Model
{
get { /* get stuff */ }
set { /* set stuff */ }
}
// Default constructor creates its own new NameModel
public NameViewModel()
{
this.Model = new NameModel();
}
// Constructor has a specific model dictated to it
public NameViewModel(NameModel model)
{
this.Model = model;
}
//Model wrapper properties
public String Name
{
get { return Model.Name; }
set { Model.Name = value; }
}
}
and...
public class MainWindowViewModel
{
Private ObservableCollection<ViewModel> _NameViewModels;
Public Void MainWindowViewModel()
{
//Lets pretend we have a service that returns a list of models
var nameModels = Service.Request();
foreach(Model model in nameModels)
{
ViewModel viewmodel = new NameViewModel(model);
NameViewModel.Add(viewmodel);
}
}
Public ObservableCollection<ViewModel> NameViewModels
{
get
{
return _NameViewModels;
}
}
}
In this way your MainWindowViewModeldoesn't keep an entirely separate copy of the Models; it only tracks the NameViewModels. Each NameViewModel is responsible for its own underlying model, while still making the option available to have a specific model passed to it during construction.
All the creation-related issues can be resolved with introduction of factory design pattern. The factory will take care of creating view models basing on model that was provided.
public class MainWindowViewModel
{
private List<Model> _NamesModel;
private ObservableCollection<ViewModel> _NamesViewModel;
private IViewModelFactory factory;
public void MainWindowViewModel(IViewModelFactory factory)
{
//Lets pretend we have a service that returns a list of models
_NamesModel = Service.Request();
_NamesViewModel = factory.CreateNamesViewModels(_NamesModel);
}
public ObservableCollection<ViewModel> NamesViewModel
{
get
{
return _NamesViewModel;
}
}
}
What is more, you could even get rid of Service dependency in view model and move it to the factory itself, thus reducing the need to keep model in view model (admittedly though, removal of model might not work in more complex scenarios):
public ObservableCollection<ViewModel> CreateNamesViewModels()
{
var models = Service.Request();
return new ObservableCollection(models.Select(m => new ViewModel(m)));
}
Also, your main window view model can expose commands that utilize factory to create any new instances. This way, no model is leaking to view and also no creation details are exposed (since commands will hide actual implementation).