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.
Related
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.
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.
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>
I want to create textblock programmatically
But it seems the code behind file don't build it.
EDIT
Inside Main.cs
public Main()
{
InitializeComponent();
}
public void generateUI(TypeOne item)
{
// Create first element
TextBlock authorText = new TextBlock();
authorText.Text = "Saturday Morning";
authorText.FontSize = 12;
authorText.FontWeight = FontWeights.Bold;
gridUI.Children.Add(authorText);
}
Inside Main.xml
<Page.DataContext>
<ViewModels:MainWindowViewModel/>
</Page.DataContext>
<Grid Width="Auto" Background="WhiteSmoke" x:Name="grid">
<Grid x:Name="gridUI" Margin="0,68,0,-37">
</Grid>
</Grid>
Inside MainWindowViewModel.cs
Main genUI = new Main();
IEnumerable<TypeOne> generateUI = //query variable
from x in _txnType
where x.Description == selectedTypeOne
select x;
foreach (TypeOne ui in generateUI)
{
genUI.generateUI(ui);
}
But the public void generateUI(TypeOne item) not creating the textblock.
I supposed, the Main.xml cannot read it since the DataContext is set to MainViewModel.cs
Please help.
The fact that you want to follow the MVVM pattern doesn't exclude dynamic view creation. However I always recommend that you use "logic-only" in the view model. That means that the VM still only contains logic for the view to interact with, but has absolutely no knowledge about the view and how it behaves.
Your view is the one using the view model and should adapt the view dynamically. This means that in the code-behind of your view, you have access to your view model and can customize the view according to your current view model. You can respond to changes in the view model as well since the view model will implement INotifyPropertyChanged (which you can intercept in your code-behind just as normal bindings would).
The question you should ask yourself is whether MVVM is the right patter for you here. If the UI always comes from a database, then where are the bindings defined? Also in the database? If so, what actual logic are you implementing in the view model? In other words: what is the point of a VM if there is no custom logic. In that case I recommend to use the view-only approach. If you need custom logic, use the approach with the code-behind I described above.
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.