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.
Related
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"
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.
I'm fairly new to the Silverlight and the MVVM / Prism pattern so this may be a stupid question.
I have a View which has custom controls within it. These custom controls are actually Views too and have ViewModels to drive them.
Currently to add these 'child' Views to the View I'm using (see Fig.1) and then in the ViewModel I have an Initialise() method which resolves the child View and injects it (see Fig.2).
Fig.1
<UserControl
x:Class="ProjectA.Module.Screens.Views.PlatformScreenView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Regions="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation"
>
<Grid x:Name="LayoutRoot">
<ContentControl
Regions:RegionManager.RegionName="FeaturesSelectionControl"
/>
</Grid>
Fig.2
public void Initialise()
{
IRegion featuresRegion = _regionManager.Regions["FeaturesSelectionControl"];
featuresRegion.Add(_container.Resolve<IFeatureSelectionControlViewModel>().View);
}
My question is do I have to do this for every control I want to add? I understand why it works this way but it seems like quite a bit of code and also I need to keep track of all the region names and ensure I don't have any clashes etc. Is there a simpler way of doing this without regions and just in XAML?
I've seen a snippet of XAML on StackOverflow here but not sure how it works and if it's what I want -
<ContentControl Content="{Binding SmartFormViewModel}"/>
Any help is much appreciated.
James
Edit after clarification:
It appears you don't want to use RegionManager at all, which is fine. What I would suggest then is this:
Create an interface for your Modules to use to register view creation methods:
public interface IViewRegistry
{
void RegisterMainView(Func<object> ViewCreationDelegate);
}
Your modules will use this like this:
public MyModule : IModule
{
IViewRegistry _registry;
public MyModule(IViewRegistry registry)
{
_registry = registry;
}
public void Initialize()
{
_registry.RegisterMainView(() =>
{
var vm = new MyViewModel();
var view = new MyView();
var view.DataContext = vm;
return view;
});
}
}
Then in your shell, you first implement the view registry (this is a simplification... you'd probably want something more robust)
public ViewRegistry : IViewRegistry
{
public static List<Func<object>> ViewFactories
{
get { return _viewFactories; }
}
static List<Func<object>> _viewFactories = new List<Func<object>>();
public void RegisterMainView(Func<object> viewFactory)
{
_viewFactories.Add(viewFactory);
}
}
And lastly, here's how your shell would show that stuff. Here's its ViewModel first:
public ShellViewModel : ViewModel
{
public ObservableCollection<object> MainViews
{
...
}
public ShellViewModel()
{
MainViews = new ObservableCollection<object>(ViewRegistry.Select(factory => factory()));
}
}
And here's your View (look ma, no RegionManager!):
<UserControl ...>
<Grid>
<ItemsControl ItemsSource="{Binding MainViews}" />
</Grid>
</UserControl>
The region manager sort of attempts to give you everything I've written here, plus a lot of extensibility points, but if you don't like RegionManager or you find it doesn't fit your needs for some reason, this is how you would do this in Silverlight without it.
Further Edits:
After some more commentary from the OP, I think I understand that the OP just wants to show a view within another view without having to use RegionManager. It appears the OP is using RegionManager to show every view on the screen, which is probably overkill.
The scenario I was given included an Address View and associated ViewModel being used from a different parent control. This is what I do (whether right or wrong):
<UserControl x:Class="Views.MyParentView" ...>
<StackPanel>
<TextBlock>Blah blah blah some other stuff... blah blah</TextBlock>
<myViews:AddressView DataContext="{Binding AddressVM}" />
</StackPanel>
</UserControl>
And here's the parent view's viewModel:
public ParentViewModel : ViewModel
{
public AddressViewModel AddressVM
{
...
}
public ParentViewModel()
{
AddressVM = new AddressViewModel();
}
}
That's it. This prevents you from having to work too hard to show these views.
RegionManager is really appropriate for decoupling the parent container view from the subview, but if both of these live in the same place (same module / assembly) there is no reason to use RegionManager to show these views.