MVVM Read data from view to model - c#

I want to read user input data from view (e.g. filter criteria for querying data such as date, name, etc.) to viewmodel. To do that, I used two-way binding between viewmodel and view elements (i.e. textbox in this case). The view is automatically loaded when a viewmodel is assigned as follows:
<DataTemplate x:Shared="False" DataType="{x:Type vm:MyViewModel}">
<view:MyView/>
</DataTemplate>
If the view is loaded the first time, every thing is fine. But if user reloads the view, then only the viewmodel is created and the view is reused (I already set x:Shared="False" as you can see). In that case, all user input (e.g. filter criteria) is lost on the newly created viewmodel. Could you please tell me what is the suitable approach to solve this problem?

Do not recreate ViewModels but have static references to each after they have been created for the first time. You could utilize e.g. MVVM Light to help accomplish this.
Example:
namespace SomeNamespace.ViewModel
{
// This class contains static references to all the view models
// in the application and provides an entry point for the bindings.
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<LoginViewModel>();
}
// Reference to your viewmodel
public LoginViewModel LoginVM
{
get { return ServiceLocator.Current.GetInstance<LoginViewModel>(); }
}
...
}
...
}
Where ViewModelLocator is defined in App.xaml as
<Application.Resources>
<vm:ViewModelLocator xmlns:vm="clr-namespace:SomeNamespace.ViewModel"
x:Key="Locator" d:IsDataSource="True" />
And in your Views bind DataContext to Locators properties.
<phone:PhoneApplicationPage
x:Class="SomeNamespace.View.LoginPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
DataContext="{Binding Source={StaticResource Locator}, Path=LoginVM}">
...
</phone:PhoneApplicationPage>

Related

WPF: Cannot get View to change dynamically using DataTemplate

I have a problem designing my WPF application. I cannot get the Views to change dynamically. The code to call another View is contained in the Views themselves. (I am trying to implement the MVVM pattern. I do not want any code behind in the View xaml files other than assigning the DataContext. An exception is made in the xaml file of the MainWindow).
Basically, I have a Window that contains a UserControl. The UserControl is my View and it is connected to another class serving as ViewModel through Datacontext.
What I want to do is to dynamically change this View/ViewModel pairs contained in the Window.
My idea was define a static property in the ViewModel of the MainWindow and store the ViewModel of the current View in it. Then I planned to use DataTemplates to automatically load a new View whenever a new ViewModel is stored in the static property.
I decided to use a static property because the code to load another ViewModel is contained in the ViewModels itself and I needed a central point with access from everywhere.
So far so good. My initial View loads and displays correctly.
However, pressing a button in that View to load the next View fails although the new ViewModel is correctly assigned to the static property.
I tried several things.
I defined DataTriggers within the ContentControl to react to changes in the static property. No help.
Implementing INotifyProperty and DependencyProperty failed in the end because of the static nature of the property (or I did something wrong).
I just can’t get it to work.
Do you have any ideas why this would be?
Do you have an idea how I could solve my general problem of dynamically displaying Views without using a static property in my MainWindow. I believe this is causing problems and I have a notion that I am not using the most elegant method. (I do want to maintain the concept of each View holding the code to load any other View)
This is a code fragment from the MainWindow:
<UserControl>
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:StartViewModel}">
<v:StartView></v:StartView>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:OverviewViewModel}">
<v:OverviewView></v:OverviewView>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentControl Content="{Binding ActiveViewModel}">/ContentControl>
</Grid>
</UserControl>
Code behind:
DataContext = new MainViewModel();
MainViewModel contains the definition for the property ActiveViewModel. The constructor for the class is static. All ViewModels inherit from BaseViewModel class:
private static BaseViewModel activeViewModel;
static public BaseViewModel ActiveViewModel
{
get { return activeViewModel; }
set { activeViewModel = value; }
}
Thanks a lot for your help.
Bye,
Eskender

Reusing views for multiple view models

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.

How to bundle View, ViewModel and DataTemplate in a WPF application for easy reuse?

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.

dynamically display different View for different ViewModel but the same attribute

Ok, I'm trying to get to grips with MVVM. I have an application that has multiple options for image capture. Depending on the mode, the image is either loaded from an existing file, or captured from a camera.
I'm writing a page using the MVVM pattern which represents the configuration of the image capture device.
The model consists of two classes which expose the specific (and non common) values for each of the modes which conform to a common interface of IImageSource.
Each of the two model classes have a contextually defined viewmodel:
CameraSourceViewModel
FileSourceViewModel
and two corresponding views.
CameraSourceView
FileSourceView
The model has an attribute which returns IImageSource.
I'm currently using third view, ImageSourceView as the page. I'm handling the loading event which gets the value from the model, then, depending on the type will instantiate the correct viewmodel and the correct view to go with it and then adds that as it's content. However, this seems to be going against the spirit of MVVM in that I've now written some decision code in the code behind
Is there a more elegant/ better way of determining which viewmodel/ view should be instantiated and used?
Actually, you shouldn't need a TemplateSelector, since the two ViewModels will have different types. You can declare DataTemplates in XAML as resources with the model type as key, so that WPF chooses the correct DataTemplate automatically:
Have a main ViewModel, which exposes a ImageSourceViewModel property. This property would either return a CameraSourceViewModel or a FileSourceViewModel, as appropriate.
In your page, the DataContext would be the main ViewModel, and you'd have XAML like this:
Code Example:
<Page x:Class="Page1"
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:my="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1">
<Page.Resources>
<DataTemplate DataType="{x:Type my:CameraSourceViewModel}">
<my:CameraSourceView/>
</DataTemplate>
<DataTemplate DataType="{x:Type my:FileSourceViewModel}">
<my:FileSourceView/>
</DataTemplate>
</Page.Resources>
<Grid>
<ContentControl Content="{Binding ImageSourceViewModel}"/>
</Grid>
</Page>
Here's some idea about what you could do:
Have a ImageSourceViewModel, that is the ViewModel of your ImageSourceView view. It would be the role of this viewModel to get "your value" from the model, and expose it as a public property of type IImageSource.
Then, in your ImageSourceView view, you could use a template selector to change the content of the view, depending on the concrete type of the exposed IImageSource property.
See http://www.codeproject.com/Articles/418250/WPF-Based-Dynamic-DataTemplateSelector
You have two palces where you need decide which type to use in run time:
ViewModel
View
On ViewModel level just use ViewModel factory, so just by an EventType/ValueType instantiates an appropriate ViewModel:
private IImageSourceViewModel ProcessEvent(IEvent someEvent)
{
return viewModelFactory.Create(someEvent.Type)
}
Then on View level just use DataTemplateSelector which accepts via binding already resolved ViewModel instance and then decides which View to use:
MainView XAML:
<ContentControl
Content="{Binding ImageSourceViewModel}"
ContentTemplateSelector =
"{StaticResource ImageSourceViewDataTemplateSelector}">
</ContentControl>
ImageSourceViewDataTemplateSelector:
private sealed class ImageSourceViewDataTemplateSelector: DataTemplateSelector
{
public ImageSourceViewDataTemplateSelector(... dependencies if any...)
{
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
DataTemplate dataTemplate = null;
IImageSourceViewModel instance = item as IImageSourceViewModel;
// move out into the constructor
var dataTemplateFactory = new Dictionary<Type, Func<DataTemplate>>
{
{ typeof(ICameraSourceViewModel), (x) => this.Resources["CameraSourceDataTemplate"] as DataTemplate },
{ typeof(IFileSourceViewModel), (x) => this.Resources["FileSourceViewModel"] as DataTemplate }
};
// TODO: handle not supported type case yourself
return dataTemplateFactory[instance.GetType()]();
}
}

Binding a ContentControl to UserControl, and reuse same instance

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.

Categories

Resources