WPF mvvm DataTemplates change View from UserControl - wpf

I am new to WPF and the MVVM pattern. I am trying to build a 'step by step' or 'wizard' like application.
So the user should first login then select some data and finally the selected data should be stored somewhere. (This flow never changes!)
However I decided to use DataTemplates and different ViewModels for each Template and a MainViewModel for the Window which populates the Templates. (Think this should be ok regarding to different Posts here)
But now my problems are starting. I know how I can change the current view from the MainViewModel BUT I want to change the current View from the inner ViewModel. And I want to be able to collect data from one inner ViewModel to another and I have no clue how this can work.
MainViewModel:
public class MainWindowViewModel : ViewModelBase
{
public ViewModelBase CurrentView {get; set;}
public MainWindowViewModel()
{
CurrentView = new ViewModelA;
}
}
XAML:
<Window x:Class="PUSEImporter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VM="clr-namespace:PUSEImporter.ViewModel"
xmlns:V="clr-namespace:PUSEImporter.View">
<Window.DataContext>
<VM:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type VM:ViewModelA}">
<V:Detail />
</DataTemplate>
<DataTemplate DataType="{x:Type VM:ViewModelB}">
<V:Overview />
</DataTemplate>
</Window.Resources>
<ContentPresenter Content="{Binding CurrentView}"/>
</Window>
So think about a button in ViewModelA (or the View from ViewModelA) and now I want to switch to ViewModelB when someone clicks on the button. And not enough the data which is collected by ViewModelA should also be available in ViewModelB.
Is this possible and the preferred way of using this techniques or do I misunderstand some concepts?
(And if this is true how should i handle things like that?)
Thanks in advance!

There are many ways to achieve what you want. In MVVM, there's one view model to each view... therefore, if your main view has a child view, then your main view model should have a property of the type of another view model. In this instance, you can add a delegate to the child view model and register a handler for it in the main view model.
This will enable you to effectively pass a signal to the main view model from the child view model, which you can react to in the parent view model in any way you want to. Rather than explain the whole story again here, please see my answers from the Passing parameters between viewmodels and How to call functions in a main view model from other view models? posts here on Stack Overflow for more information on how to achieve this.

Related

Unable to add two UserControls in the same view

I have a List View in which there are two Child Views. One is the Display View and another is Edit View. Here is how I have defined the List (Parent) view. Note that I want the two child UserControl's to occupy different space in the Parent.
<UserControl x:Class="RelayAnalysis_UI.Views.Relays.RelayListView"
....
<ContentControl x:Name="GroupDetail" Grid.Row="2" />
<TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource TabControlStyle}" Margin="5,0,0,0"/>
</UserControl>
Then In my view model, I activate these items based on user responses in the following manner
**View Model **
[Export(typeof(RelayListViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class RelayListViewModel : Conductor<IScreen>.Collection.OneActive, IHandle<Group> {
....
public void Edit() { //Requested Edit
RelayEditViewModel viewModel = TryAndLocateViewModel(SelectedRelay.Group.Rack.Id, SelectedRelay.Group.Id);
ActivateItem(viewModel);
}
....
public void ViewGroupDetail(Relay relay) { //Requested View
GroupDetailViewModel viewModel = container.GetExportedValue<GroupDetailViewModel>();
ActivateItem(viewModel);
}
The above code works but the Detail View is loaded in the Tabs space (the space meant for the Edit View). Actually, the ActivateItem(viewModel) does pick up the correct type of view to display but it is loaded in the wrong place for the Display View, that is, the Display View is loaded in the Edit View's space on the screen. Surely I am missing some obvious stuff.
In summary, how do we get two UserControls defined in a Parent UserControl to activate in its own space?
Edit - 1:
Here are two Screen Shots which show where I need to load the Edit and Detail Views respectively.
As you can see in the second screenshot, the Detail View gets loaded in the Detail Area as well as the Edit Area(Tabs). I wan't the Detail View only to appear in the Detail Area. The Edit Area is only for the Edit View.
Here is the code that I have used to generate the screen shots.
The Main View that houses both views
<UserControl x:Class="RelayAnalysis_UI.Views.Relays.RelayListView"
<Grid>
....
<ContentControl x:Name="GroupDetail" HorizontalContentAlignment="Left"
cal:View.Context="GroupDetail" cal:View.Model="{Binding ActiveItem}"/>
<TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource TabControlStyle}" Margin="5,0,0,0"
cal:View.Context="RelayEdit" cal:View.Model="{Binding ActiveItem}"/>
</Grid>
</UserControl>
Edit 2:
I think I am very close to get it working. As per your suggestions I modified the Main(Parent) container as below.
<UserControl x:Class="RelayAnalysis_UI.Views.Relays.RelayListView"
<ContentControl x:Name="GroupDetail" HorizontalContentAlignment="Left" />
<TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource TabControlStyle}" Margin="5,0,0,0" />
The Edit Screen and Detail Screens now appear in their proper places. However, the Detail ViewModels OnActivate is not called upon so I get a blank Detail View with no variables populated. All loading of Details View field is done on the OnActivate() override. Here is how my GroupDetailViewModel is defined
[Export(typeof(GroupDetailViewModel))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class GroupDetailViewModel : Screen {
...
protected override void OnActivate() {
base.OnActivate();
..
}
So certainly, I am missing some attribute. Or will I have to call some method on the GroupDetailViewModel to load the details manually ?
Removed original answer because it was long and doesn't really help out much
Edit:
Ok so disregard the above - it looks like you are trying to load two different views over two different viewmodels, which as far as I know is not what Context is designed for. The Context property loads two different views over the same viewmodel e.g. in your XAML:
<ContentControl x:Name="GroupDetail" HorizontalContentAlignment="Left"
cal:View.Context="GroupDetail"
cal:View.Model="{Binding ActiveItem}"/>
<TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource TabControlStyle}" Margin="5,0,0,0"
cal:View.Context="RelayEdit"
cal:View.Model="{Binding ActiveItem}"/>
Given a VM with a name of RelayEditViewModel activated via ActivateItem() CM will try to load the following views:
RelayEdit.GroupDetail for the content control
RelayEdit.RelayEdit for the tab control
See:
http://caliburnmicro.codeplex.com/wikipage?title=View%2fViewModel%20Naming%20Conventions&referringTitle=Documentation
...
If you try to load another ViewModel, the same conventions will apply to find the view
GroupDetailViewModel results in
GroupDetail.GroupDetail for the content control
GroupDetail.RelayEdit for the tab control
It sounds like this isn't what you want (and I'm not sure why anything is loading at all - what namespace are your views in? Have you customised the view locator?)
I'm still trying to get my head round the lifecycle support you require but it sounds like you want the edit view to be lifecycle managed (since you want the load/save/guard type support) but the detail view is to be read-only and doesn't care if it's closed without being guarded
In that case you probably want to add a property to your ViewModel which will hold a reference to the details viewmodel but don't activate it ... just set the property without calling ActivateItem(vm)
example:
[Export(typeof(RelayListViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class RelayListViewModel : Conductor<IScreen>.Collection.OneActive, IHandle<Group> {
....
// Backing field + prop to hold the details view - the content control will bind to this
private IScreen _details;
public IViewAware Details { get { } set { } } // Implement standard NotifyOfPropertyChange here for this property
public void Edit() { //Requested Edit
RelayEditViewModel viewModel = TryAndLocateViewModel(SelectedRelay.Group.Rack.Id, SelectedRelay.Group.Id);
ActivateItem(viewModel);
}
....
public void ViewGroupDetail(Relay relay) { //Requested View
GroupDetailViewModel viewModel = container.GetExportedValue<GroupDetailViewModel>();
// Instead of activating, just assign the VM to the property and make sure Details calls NotifyOfPropertyChange to let CM know to start the binding logic
Details = viewModel;
}
Then in your XAML
<!-- Just bind the details view to the Details property -->
<ContentControl x:Name="Details" HorizontalContentAlignment="Left" />
<!-- Leave this as-is, as it's working ok -->
<TabControl x:Name="Items" Grid.Column="0" Style="{StaticResource TabControlStyle}" Margin="5,0,0,0" />
(I've assumed that you are using the TabControls default conventions above, but tweak if neccessary)
You can use the same VM for both the details and the edit view as long as you set the Context property accordingly.
Let me know if that helps
Edit:
Just to answer the question about MVVM and coupling etc...
All you are doing is composing a more complex viewmodel from several simpler viewmodels (and therefore a more complex view from several simpler views). As long as your reference to the details VM is not a concrete type, there is very loose coupling between the VMs. You could assign ANY viewmodel type that implements that interface into the Detail property on the main VM and CM will try to locate the view for it and build the interface. This is perfectly fine (you can use your IoC to get the type for the details window if needed)
If your details view needs lifecycle you should inherit from Screen, but I doubt that your details view needs activation (since it's just a details view and is ready only) so just implementing IViewAware and inheriting from PropertyChangedBase will do. The edit view, however, needs to have lifecycle and therefore should inherit from Screen.
Conductor already contains an ActiveItem property, and provides management of lifecycle for child items activated via ActivateItem(), all you are doing is creating an extra 'bolt-on' property for your conductor which references the additional vm (i.e. you need ActiveItem and Details)

How do i design a composite view and view model using Silverlight and MVVM?

I want to create a "Wizard" in my Silverlight MVVM application. The Wizard should contain multiple steps between which you can navigate using "next" and "previous".
The problem I am facing is the relationship between views and view models.
I want there to be a view and view model for the Wizard itself. My instinct tells me that there should be one view/view model pair for each step in the wizard.
What is a good approach for maintaining these kinds of relationships, where one view model holds several other view models and the view actually consists of several smaller views?
Are there any patterns or practices that I can use?
I know this question might be subjective, but give me the rough cuts of an approach and I'll award you an answer!
I'd propose main wizard viewModel which has a collection of steps view models and handles navigation between them. While navigating it should call validation methods in step viewModels:
WizardVM:
public class WizardVM
{
// this commands should support CanExecute
public ICommand GotoNextCommand { get; private set; } // should open next step VM
public ICommand GotoBackCommand { get; private set; } // should open previous step VM
// this prop should be set by 'GotoNext', 'GotoBack' commands
public object CurrentStep { get; private set; }
// probably internally you will have a list of all steps:
private ICollection<object> _stepViewModels = ...;
}
WizardView:
<StackPanel>
<ContentPresenter Content="{Binding CurrentStep}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding GotoBackCommand}">Back</Button>
<Button Command="{Binding GotoNextCommand}">Next</Button>
</StackPanel>
</StackPanel>
UPDATE:
Views can be coupled with ViewModels via Datatemplating. For example add this into resources in App.Xaml:
<DataTemplate DataType="{x:Type local:Step1ViewModel}">
<Step1View />
</DateTemplate>
<DataTemplate DataType="{x:Type local:Step2ViewModel}">
<Step2View />
</DateTemplate>
Your viewModels should know absolutely nothing about views. It means that WizardVM should
expose only other viewModels but not views. It's a rule of thumb for MVVM.
UPDATE2 Oops, I forgot that Silverlight doesn't have DataTemplating yet. In silverlight I would still expose ViewModels but bind them to ContentPresenters using a converter which will convert a viewModel into corresponding view.

how to build dynamic grid and binding to xaml using mvvm

I'm planning a WPF application which will build dynamic grid with textblocks in the viewmodel and then refresh interface (xaml) with the new grid.
I've done the firts step, but i have problems to refresh the view with the new grid.
Is there any example code of how to bind the grid to the xaml that I can have a look at?? I really can't figure this out!
Thanks
You may be approaching this slightly wrongly, hard to say from the question-
Generally to show a dynamic set of UI elements in MVVM you bind the ItemsSource property of an ItemsControl to an ObservableCollection. The ItemsControl ItemsTemplate property converts the YourViewModel object into a UIElement which can be a TextBlock or whatever style you want.
So as an example:
// model
class Person
{
public string Name {get; private set;}
}
// view model
class MainViewModel
{
public ObservableCollection<Person> People {get; private set;}
}
//view
<UserControl DataContext="{Binding MyMainViewModelObject}">
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemsTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>/
</ItemsControl.ItemsTemplate>
</ItemsControl>
</UserControl>
I havent tested that code, it is just to illustrate. There are other ways of dissecting the problem into MVVM, it all depends on the situation. You would have to give more details for us to help you out with that. Rarely in WPF is there a need to use code to create or add UI elements to other UIElements etc.
A point to note more along the exact lines of the question however is that an ItemsControl can either bind to a bunch of regular objects and use it's template to create UIElements from them, OR it can bind to a list of UIElements, in which case the template is not applied (sounds like this is the situation you have).

how to load wpf usercontrol in MVVM pattern

I'm creating a wpf user control which is in mvvm pattern.
So we have : view(with no code in codebehind file), viewmodel,model,dataaccess files.
I have MainWindow.xaml as a view file, which I need to bind with MainWindowModel.cs.
Usually, in a a wpf application we can do this with onStartUp event in App.xaml file. But in user control, as we do not have App.xaml...How do I achieve it ?
Please help :(...Thanks in Advance !!!
You can use a ContentControl, with a DataTemplate to bind the UserControl (View) to the ViewModel :
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<v:MyUserControl />
</DataTemplate>
...
<ContentControl Content="{Binding Current}" />
WPF will pick the DataTemplate automatically based on the type of the Content
I know this is an old, answered question, but I have a different approach. I like to make implicit relationships in the App.xaml file:
<Application.Resources>
<DataTemplate DataType="{x:Type ViewModels:KioskViewModel}">
<Views:KioskView />
</DataTemplate>
</Application.Resources>
With this, there is no need to set a DataContext anywhere.
UPDATE >>>
In response to #Vignesh Natraj's request, here is a fuller explanation:
Once you have set up the DataTemplate in a Resources element, you can display the KioskView in this example by adding an instance of the KioskViewModel anywhere in your XAML. This could be filling the MainWindow, or just inside a particular section of the screen. You could also host multiple instances of the KioskViewModel in a ListBox and it will generate multiple KioskView instances.
You can add an instance of the KioskViewModel to your XAML in a couple of ways, depending on your requirements. One way is to declare the XML namespace for the project that contains the KioskViewModel.cs file and simply add an instance of it in a ContentControl to the page where you want your view to appear. For example, if you had a UserControl called MainView and the KioskViewModel.cs file was in a Kiosk.ViewModels namespace, you could use basic XAML like this:
<UserControl x:Class="Kiosk.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:Kiosk.ViewModels">
<UserControl.Resources>
<ViewModels:KioskViewModel x:Key="KioskViewModel" />
<DataTemplate DataType="{x:Type ViewModels:KioskViewModel}">
<Views:KioskView />
</DataTemplate>
</UserControl.Resources>
<ContentControl Content="{StaticResource KioskViewModel}" />
</UserControl>
I prefer to use the MVVM design pattern with WPF, so I would have a base view model class providing useful functionality such as implementing the essential INotifyPropertyChanged interface. I then have a property called ViewModel in the main (top level) view model of type BaseViewModel. This provides me with a nice way to change the ViewModel property to any view model that has derived from BaseViewModel and therefore to be able to change the associated view from the view model.
For example, in the MainViewModel.cs class that is bound to MainView there is a field and relating property:
private BaseViewModel viewModel = new KioskViewModel();
public BaseViewModel ViewModel
{
get { return viewModel; }
set { viewModel = value; NotifyPropertyChanged("ViewModel"); }
}
As you can see, it starts off as a KioskViewModel instance, but can be changed to any other view at any time in response to user interaction. For this setup, the XAML is very similar, but instead of declaring an instance of the view model in the Resources element, we bind to the property in the MainViewModel:
<UserControl x:Class="Kiosk.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModels="clr-namespace:Kiosk.ViewModels">
<ContentControl Content="{Binding ViewModel}" />
</UserControl>
Note that for this example, we would need to declare two (or more to make this approach useful) DataTemplates in the App.xaml file:
<Application.Resources>
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:KioskViewModel}">
<Views:KioskView />
</DataTemplate>
</Application.Resources>
I've been using MVVM Light Toolkit which has a ViewModelLocator class that you can put properties to the viewmodels in. You then create a reference to the ViewModelLocator in your Mainwindow.xaml like so:
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True"/>
In the grid panel, or whatever you're using, you can then set the datacontext like this:
<Grid DataContext="{Binding MainWindowViewModel, Source={StaticResource Locator}}">
...
</Grid>
You could also go with MEFedMVVM which potentially adds a bit more flexibility in terms of being able to swap different viewModel implementations into the view.
The flexibility in both of these libraries is that you don't have to use their ViewModel base classes if you don't want to - the ViewModelLocator and the MEFedMVVM can work with any class.
There are endless ways to do it, wich all fall in one of the two categories:"view first" or "model first".
In a "view first" mode the view (e.g. your mainwindow) is created first and then (e.g. in the codebehind) the View instantiates the ViewModel and sets it as its datacontext):
private void WindowLoaded(object sender, EventArgs args)
{
this.DataContext = ViewModelService.GetViewModelX();
}
In a "model first" mode the ViewModel is there first and then instanciated the View.
// method of the viewmodel
public void LoadView()
{
// in this example the view abstracted using an interface
this.View = ViewService.GetViewX();
this.View.SetDataContext(this);
this.View.Show();
}
The examples given here are just one way of many. You could look at the various MVVM frameworks and see how they do it.
We can use ObjectDataProvider to call a method inside an object ..as follows :
<ObjectDataProvider ObjectType="{x:Type local:TemperatureScale}"
MethodName="ConvertTemp"
x:Key="convertTemp">
Is there anyway to do the same using DataTemplate
You can probably look at MSDN. I find it as a good resource, though it doesn't explain how to use usercontrols,you will find your way out.

How to connect ViewModels to their appropriate Views dynamically?

I have a WPF application which has a MainView.xaml Window which loads numerous page objects at runtime, loads them into ViewModels, and displays them dynamically in the menu.
My MainViewModel has an ObservableCollection of ViewModels and I bind these each to their appropriate Views in the MainView.xaml file.
However, is there a way to automate this so I don't have to make these manual entries each time I add a page?
<Window.Resources>
<DataTemplate DataType="{x:Type vm:PageItemManageCustomersViewModel}">
<v:PageItemManageCustomersView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:PageItemManageEmployeesViewModel}">
<v:PageItemManageEmployeesView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:PageItemReportsViewModel}">
<v:PageItemReportsView/>
</DataTemplate>
</Window.Resources>
Isn't this something that a "ServiceLocator" or "Container" should be doing, hooking up the Views to the ViewModels? I've read that the above is a common way to match up the ViewModels and Views in the MVVM pattern, but it comes across as a bit static to me. Would appreciate any thoughts.
Another option is to use a class for resolving ViewModels based on some key. You can then use this in your main view to resolve the correct viewmodel for your controls.
public static class ViewModelFactory
{
public ViewModelBase Create(string someKeyHere)
{
//Some logic to resolve a view model
}
}

Resources