I have two view models A and B. On a double click on view A I need to display view B.
How can I call a view B from a view Model A using the MVVM pattern?
I have looked around and I couldn't find a clear example that demonstrate this fundamental concept for the MVVM pattern.
c#
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Mvvm;
using System.Windows.Input;
namespace Example.ViewModels
{
public class ViewModelA : BindableBase
{
public ICommand ShowInfoCommand { get; private set; }
//Need to call view B
private void OnShowInfo(object obj)
{
//To Be Implemented
}
}
}
Well, here's an easy way to do this (assuming you have correctly implemented INotifyPropertyChanged):
Go to your App.xaml and declare some DataTemplates to connect the Views with the ViewModels:
<DataTemplate DataType="{x:Type ViewModels:ViewModelA}">
<Views:ViewA />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:ViewModelB}">
<Views:ViewB />
</DataTemplate>
Now whenever your application uses ViewModelA or ViewModelB, these DataTemplates will set the correct views.
You can have a content presenter or content control to control which view model to display:
<ContentControl Content="{Binding ViewModel}" />
Then, you will set the ViewModel, whenever you wish to change views:
//Need to call view B
private void OnShowInfo(object obj)
{
ViewModel = new ViewModelB();
}
Well, that's it. Your ViewModel binding of the ContentControl together with the DataTemplates will do the job!
Of course, there are plenty of different approaches to do this. It will depend on your requirements. I'm currently using NavigationService to handle this in one of my projects.
Related
Following This Sample I am hoping to be able a real MVVM pattern from the tutorial but based on my understanding the application is missing the Model and View classes!
I have the MapViewModel.cs like this
public class MapViewModel
{
public MapViewModel(){ }
private Map _map = new Map(Basemap.CreateStreets());
public Map Map
{
get { return _map; }
set { _map = value; }
}
}
and the MainWindow.xaml
<Window.Resources>
<local:MapViewModel x:Key="MapViewModel" />
</Window.Resources>
<Grid>
<esri:MapView Map="{Binding Map, Source={StaticResource MapViewModel}}" />
</Grid>
but whre are the "MapView and "MapModel classes? Can you please help me to extracts and create those classes from the MapViewModel and create a real MVVM model?
There are 3 layers in MVVM pattern:
model
view
viemodel
That class you pasted belongs to viewmodel layer. It has properties which should be bound to your view (xaml). The viewmodel represents state of the view.
Now, to the view layer belongs your xaml file. You set all the controls, windows and all bindings in there.
And the model layer should have all the logic and data providers for you viewmodel class, the example could be your BaseMap class.
Clarification
I am working with an MVVM solution. I have a 1 to 1 mapping between ViewModels and Views. All solutions I have seen follow a view first approach where the View type is resolved by an IoC container and has a ViewModel as a dependency. I need to reverse that somehow.
Original post:
I am currently trying to refactor a simple database viewing application from Caliburn Micro to Prism (which I am very new to). The application currently utilizes a ViewModel-First approach and the ShellViewModel maintains a list of ViewModels that is bound to a TabControl.
I can not find how to implement a similar approach in Prism. All solutions I have seen use a view first approach, but I have multiple states all mapping to one type of view and need to keep those states separate.
Is there a way I can configure prism to automatically inject a view when a viewmodel is assigned to a region?
Thank you.
Rachel pointed me to a solution in her comment to the original question.
Instead of trying to implement special prism functionality and prism regions, I have gone with a more straight forward MVVM implementation using DataTemplates.
ViewModel outline:
public abstract class ContainerViewModel : BindableBase
{
public ObservableCollection<ItemViewModel> Items { get; set; }
public ItemViewModel ActiveItem { get; set; }
protected virtual void Add(ItemViewModel item) { ... }
protected virtual void Remove(ItemViewModel item) { ... }
protected virtual void Activate(ItemViewModel item) { ... }
}
And XAML:
<TabControl Grid.Column="1" ItemsSource="{Binding Items}" SelectedItem="{Binding ActiveItem}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Table.TableName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type viewModels:QueryViewModel}">
<local:QueryView />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Have a look at this code project article (ignoring the part about child containers): http://www.codeproject.com/Articles/640573/ViewModel-st-Child-Container-PRISM-Navigation
I'd like to reuse a view for 2 different viewmodels, in my example MyEntityEditViewModel and MyEntityCreateViewModel. The view is basically just a form with a Save button, so pretty common layout.
I created both view models along with a parent view / view model (MyEntitySummaryViewModel) and now I'd like to define the form view using a ContentControl.
Summary view:
<ContentControl x:Name="ActiveItem" cal:View.Model="{Binding ActiveItem}" cal:View.Context="MyEntityDetailView" />
MyEntitySummaryViewModel:
public MyEntity SelectedEntity {
get { return _selectedEntity; }
set {
_selectedEntity = value;
NotifyOfPropertyChange();
ActivateItem(new MyEntityEditViewModel(_selectedEntitity));
}
}
public void Create() {
ActivateItem(new MyEntityCreateViewModel(new MyEntity()));
}
My problem is now that Caliburn tries to locate a 'MyEntityEditView' due to it's view locating conventions, even if I strictly defined the context of the ContentControl as a custom view. Is there a way around this? Or am I doing something completely wrong here?
If my understanding is right, You want 2 type of ViewModel to point on the same view. If so juste create a base classe for your Entity (EntityBaseViewModel) and Create a View (EntityBaseView).
To Bind a ContentControl set his x:Name so the name match a Property of your ViewModel.
Example:
View (ShellView):
<ContentControl x:Name="SelectedEntity"/>
ViewModel (ShellViewModel):
public EntityBaseViewModel SelectedEntity
{
get
{
return this._selectedEntity;
}
set
{
this._selectedEntity = value;
this.NotifyOfPropertyChange(() => SelectedEntity);
}
}
And Caliburn will find the View for the ViewModel and bind the DataContext if you did create your ViewModel / View along the naming convention like you said.
A little late to the party, but perhaps this will help someone. This video helped a lot for me - (Tim Corey, WPF and Caliburn with MVVM)
Setting up the ShellView with a control that points to ActiveItem as you mentioned, allows that control to display whatever view you tell it from the ShellViewModel code. I was also using Fody with this project, so that took care of the change notifications so you won't see those listed in code.
ShellView -
<Button x:Name="LoadMainPage" />
<Button x:Name="LoadSecondPage" />
<ContentControl x:Name="ActiveItem"/>
The ShellViewModel -
public class ShellViewModel : Conductor<object>.Collection.OneActive
{
public MainPageViewModel MainPageVM = new MainPageViewModel();
public SecondPageViewModel SecondPageVM = new SecondPageViewModel();
public ShellViewModel()
{
LoadMainPage(); // auto load main page on startup
}
public void LoadMainPage()
{
ActivateItem(MainPageVM);
}
public void LoadSecondPage()
{
ActivateItem(SecondPageVM);
}
}
Instead of creating a new instance of a ViewModel when using ActivateItem, you're just re-using the initial ones created. Or, if you DO prefer to create another instance each time that particular view is launched, then simply use the ActivateItem as you already have.
In your SecondPageViewModel for the view, which will occupy the space in the ContentControl for ActiveItem -
public class SecondPageViewModel : Screen
SecondPageView.xaml added as a User Control (and any other sub/child views you want to create) -
<UserControl x:Class="MyNamespace.Views.SecondPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyNamespace.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
</Grid>
</UserControl>
This will allow you to flip back and forth between whatever views you want from a base view/viewmodel, and display the contents of the child views (however many you want) that you choose within the ContentControl box.
Situation:
I'd like to create a flexible application which is ViewModel driven.
The basic flow is like this:
Design the main ViewModel
Create a UserControl as View and a DataTemplate for the main ViewModel to select this View
If there are sub components, the are modelled by sub ViewModels
Create a UserControl as View and a DataTemplate for the sub ViewModel to select this View
If a sub view model needs to be presented, it is done via a DataTemplate.
This approach can also be seen here (option 8).
So the main window xaml looks something like this:
<Window>
<!-- somehow I need to add the mapping from ViewModel to View -->
<Grid>
<!-- the main ViewModel -->
<ContentPresenter Content="{Binding Path=Content}"/>
</Grid>
</Window>
The Content property might contain a view model that contains a list of elements named Children and it's associated DataTemplate might look like this:
The children are also flexibly rendered by a suitable DataTemplate.
<UserControl>
<Grid>
<StackPanel>
<!-- display the child ViewModels in a list -->
<ItemsControl ItemsSource="{Binding Path=Children}" />
</StackPanel>
</Grid>
</UserControl>
Question:
How should I organize the ViewModels, Views and their DataTemplates so I don't need to hardwire them in the MainWindow?
How do I then connect this to the main window?
It would be nice if it is stub-able, i.e. I can see the result during design time with a design time dataContext.
Basically I want to bundle the View, ViewModel and DataTemplate and be able to use them in an application that doesn't need to know about the details (e.g. some sub ViewModel implements a certain interface and is injected into the main ViewModel).
Have you looked into Prism.
The framework allows you to define regions within your UI that views can be registered against. I believe this answers your 2nd question (2).
xmlns:cal="http://www.codeplex.com/prism"
<Window>
<!-- somehow I need to add the mapping from ViewModel to View -->
<Grid>
<!-- the main ViewModel -->
<ContentPresenter cal:RegionManager.RegionName="MainRegion"/>
</Grid>
</Window>
For your first question (1) we structure our entities in the following way:
View - we have an abstract base class that looks similar too:
public abstract class ViewBase<T> : UserControl, IView<T> where T: IViewModel
{
public T ViewModel
{
get
{
return this.viewModel;
}
protected set
{
this.viewModel = value;
this.DataContext = this.viewModel;
}
}
public ViewBase(IUnityContainer container)
{
this.ViewModel = container.Resolve<T>();
}
}
This then allows us to create Views in xaml using the following:
<ui:ViewBase x:Class="MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:ui="NAMESPACE FOR VIEWBASE"
xmlns:vm="NAMESPACE FOR VIEWMODEL"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:TypeArguments="vm:MYVIEWMODEL">
In the code behind of a View we do the following:
public partial class MyView : ViewBase<IMyViewModel>
This then makes use of the constructor in the base class to resolve the ViewModel and set it to it's DataContext.
This then allows you to design your view (3) as you intended and also removes the need for having a DataTemplate.
Using the UnityContainer we then register the views as follows:
this.container.RegisterType<IMyView, MyView>();
this.container.RegisterType<IMyViewModel, MyViewModel>();
this.regionManager.RegisterViewWithRegion("MainRegion", typeof(IMyView));
Note that "MainRegion" here matches the RegionName specified in the MainWindow xaml. You can expand this further to use a TabControl if you wanted to display multiple views in the same area, or even break your MainWindow down into different regions.
I hope this helps.
1) You can in each view add DataTemplates in UserControl.Resources, i.e.
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewmodels:Customer1ViewModel}">
<views:Customer1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:Customer2ViewModel}">
<views:Customer2View/>
</DataTemplate>
</UserControl.Resources>
Now you defined for each ViewModel appropriate View.
You put only data templates for ViewModels that you expect in that View,
i.e. children's ViewModels
2) Hm, your MainWindow also has to have a ViewModel, i.e put in MainWindow DataContext an instance of MainWindows's ViewModel. That ViewModel has to contain the property Content (in which you put ViewModel for content).
You can do that manually in App.xaml.cs
public partial class App : Application
{
public App()
{
this.Startup += App_Startup;
}
public void App_Startup(object sender, StartupEventArgs e)
{
this.MainWindow = new MainWindow();
//create view model and set data context
MainWindowViewModel vm = new MainWindowViewModel();
this.MainWindow.DataContext = vm;
//show window
this.MainWindow.ShowDialog(vm);
}
}
3) I'm not sure about this, you probably will not be able to see results in design time.
I'm not sure if I'm fully understanding what exactly you want, if this doesn't help,
please replay to this answer with further explanation.
I'm trying to bind a ContentControl's Content to a UserControl I have instantiated in my ViewModel. I cannot use the method with binding to a ViewModel and then have the UserControl be the DataTemplate of the ViewModel, as I need the Content of the ContentControl to be able to change frequently, using the same instance of the UserControls/Views, and not instantiate the views each time i re-bind.
However, when setting the UserControl-property to a UserControl-instance, and then when the view is rendered/data-bound I get: Must disconnect specified child from current parent Visual before attaching to new parent Visual. Even though I have not previously added this UserControl to anywhere, I just created this instance earlier and kept it in memory.
Are there a better way to achieve what I am doing?
In the ViewModel
public class MyViewModel : INotifyPropertyChanged
{
//...
private void LoadApps()
{
var instances = new List<UserControl>
{
new Instance1View(),
new Instance2View(),
new Instance3View(),
};
SwitchInstances(instances);
}
private void SwitchInstances(List<UserControl> instances)
{
CenterApp = instances[0];
}
//...
private UserControl _centerApp;
public UserControl CenterApp
{
get { return _centerApp; }
set
{
if (_centerApp == value)
{
return;
}
_centerApp = value;
OnPropertyChanged("CenterApp");
}
}
//...
}
In the View.xaml
<ContentControl Content="{Binding CenterApp}"></ContentControl>
Too long for a comment.
Building up on what #Kent stated in your comment, The whole point of MVVM is to disconnect the view-model from view related stuff(controls) which blocks the testing capability of GUI applications. Thus you having a UserControl / Button / whatever graphical view-related item negates the entire principle of MVVM.
You should if using MVVM comply with its standards and then re-address your problem.
With MVVM you normally have 1 view <-> 1 view-model
View knows about its View Model(Normally through DataContext). Reverse should not be coded into.
You try to put logic controlling the view in the view-model to allow testing logic(Commands and INPC properties)
... and quite a few more. It's pretty specific in the extents of view-model not having view related stuff for eg not even having properties in view-model like Visibility. You normally hold a bool and then in the view use a converter to switch it to the Visibility object.
Reading up a bit more into MVVM would certainly help you,
Now for something to address your current issue:
Following a MVVM structure,
your going to have ViewModels such as
Main: MyViewModel
Derive all instance ViewModels from a base to allow them being kept in a list.
List or hold individually Instance1ViewModel, Instance2ViewModel, Instance3ViewModel in MyViewModel (Either create it yourself or if your using an IOC container let it inject it)
Have MyViewModel expose a property just like your posted example:
Example:
// ViewModelBase is the base class for all instance View Models
private ViewModelBase _currentFrame;
public ViewModelBase CurrentFrame {
get {
return _currentFrame;
}
private set {
if (value == _currentFrame)
return;
_currentFrame = value;
OnPropertyChanged(() => CurrentFrame);
}
}
Now in your MyView.xaml View file you should(does'nt have to be top level) set the top-level DataContext to your MyViewModel
Your View's xaml can then be declared like:
Example:
...
<Window.Resources>
<DataTemplate DataType="{x:Type local:Instance1ViewModel}">
<local:Instance1View />
</DataTemplate>
<DataTemplate DataType="{x:Type local:Instance2ViewModel}">
<local:Instance2View />
</DataTemplate>
<DataTemplate DataType="{x:Type local:Instance3ViewModel}">
<local:Instance3View />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding Path=CurrentFrame}" />
</Grid>
...
Thats it!. Now you just switch the CurrentFrame property in your view-model and make it point to any of three instance view-models and the view will be correspondingly updated.
This gets you an MVVM compliant application, for your other issue of working around not having to recreate views dynamically based on DataTemplate you could follow the approaches suggested here and expand it for your own usage.