I am new to WPF and the MVVM pattern and a little confused on how to achieve data-binding to pre-existing instances. I understand that in MVVM you have the Model="business logic", ViewModel="presentation logic", View="actual presentation".
Unfortunately I am currently unable to figure out how you would go about binding your View to existing instances of the ViewModel and corresponding Model. I have found for example this MVVM tutorial, which I liked, but it also just creates the Model inside the ViewModel.
So how would I go about using previously instantiated Models from my underlying application?
EDIT: So I am getting around to trying out the method proposed by Gusdor in a small test project, but cannot get it to work. I am getting an error 'WpfBindingTesting.App.MyViewModel' is a 'field', but used like a 'type' in App.xaml.cs (see below). I hope somebody catches what I am doing wrong. Here is my code:
ViewModel:
namespace WpfBindingTesting
{
class ViewModel
{
private List<string> names;
public List<string> Names
{
get { return names; }
set { names = value; }
}
public ViewModel()
{
Names = new List<string> { "string1", "string2", "string3" };
}
}
}
MainWindow.xaml.cs:
namespace WpfBindingTesting
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
MainWindow.xaml:
<Window x:Class="WpfBindingTesting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
DataContext = "{Binding RelativeSource={RelativeSource Mode=Self}}"
>
<StackPanel>
<ListBox
ItemsSource="{Binding Path=Names}"
Height="50"
>
</ListBox>
</StackPanel>
</Window>
App.xaml.cs:
namespace WpfBindingTesting
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public App(){
MainWindow View = new MainWindow();
ViewModel MyViewModel = new ViewModel();
View.DataContext = MyViewModel; // this give error: 'WpfBindingTesting.App.MyViewModel' is a 'field', but used like a 'type'
//view.DataContext = new ViewModel();
//view.Show();
}
}
}
Generally speaking, we assign view models to the view's DataContext property.
You can do this from code as long as you have a reference to the view. Here is the code:
C Sharp
Window view = new Window();
// add a view model
view.DataContext = new ViewModel() { Title = "View Model 1" };
view.Show();
// add another view model to demonstrate the binding
view.DataContext = new ViewModel() { Title = "View Model 2" };
XAML
The XAML for window might look something like this.
<Window Title={Binding Title}>
</Window>
Notice how the Source or ElementName properties remains unspecified in the binding declaration? This instructs the Binding to use the current DataContext as the source.
Model = business.
View = what the user see, and really ain't about logic (this is a lie ofcourse, but this is what you strive for).
ViewModel = what glues them together. It's your adapter that gives the View the info it needs from the Model you have.
I suggest having a look at an MVVM platform (like MVVM Light), which gives you lot of the stuff you need to do "for free" when you begin.
As a guide, here are 3 options for doing your binding:
1.1 On the XAML, set DataContext = "{Binding relativeSource={RelativeSource self}}
1.2 have a property on the code behind (your xaml.cs)
1.3 Initialize the property BEFORE the InitializeComponent() in the constructor.
2.1 Igonre DataContext on the XAML
2.2 In the code behind, start with InitializeComponent();
2.3 have a line like DataContext = this; in the constructor
2.4 Initialize your properties
3.1 Give the window or UserControl a name: Name = "MyWindow" (in the xaml)
3.2 Bind to an element: ItemSource={Binding ElementName = MyWindow, path =... }"
3.3 Initialize the property BEFORE the InitializeComponent() in the constructor.
And last but not least, have a look at my article "The Big MVVM Template", which you can follow and will give you a good handle of the basic, plus you'll have a running fully documented example on top :) .
Your question is more about design then coding. I think if you have existing instances of viewmodels already and you just want to reuse/reassign them in new views. then probably you need to have a look at IOC Containers( as actually you want some sort of dependency injection here)
To be very simple use an IOC container to maintain the lifetime of your viewModel.
Configure the container to give you the required ViewModel according to your need i.e a new object or an existing view model object.
Unity Container
In unity Container you can just register your objects and resolve them at future as you need them. using below syntax simplay:
Register:
1.MyUnityConatiner.RegisterInstance<IMyService>(myDataService);
or
2.MyContainer.RegisterInstance<IMyService>("Email", myEmailService);
Resolve :
1.MyUnityConatiner.Resolve<IMyService>();
or
2.MyConatiner.Resolve<IMyService>("Email");
I know this will seem to you a little deviated solution but if you learn to use this you will have a different perspective on your problem and how to reuse the objects(model/viewmodel).
Related
Overview:
I have one MainWindow and two Views. One representing a Login Screen and the other one is representing the Dashboard. The LoginView is shown on StartUp. When the User presses the LoginButton the DashboardView should be shown.
Here's my Problem:
The underlying Command is beeing executed. The Constructor of DashboardViewModel is beeing called. And the View and ViewModel for Dashboard are connected via a DataTemplate. But the View or the InitializeComponent Method are not beeing called.
The LoginCommand looks like this:
LoginMainCommands is a RelayCommand Class derived from ICommand.
public class ButtonViewModel : LoginMainViewModel
{
public ButtonViewModel()
{
_loginCommand = new LoginMainCommands(Login);
}
private LoginMainCommands _loginCommand;
public LoginMainCommands LoginCommand
{
get { return _loginCommand; }
}
public void Login()
{
ViewModelLocator ObjViewModelLocator = new ViewModelLocator();
ObjViewModelLocator.MWInstance.SwitchViewModel();
}
}
The connection between the Views and the ViewModels is in ManWindow.xaml as follows:
<Window.Resources>
<local:ViewModelLocator x:Key="ViewModelLocator"/>
<DataTemplate DataType="{x:Type loginViewModel:LoginMainViewModel}">
<loginView:LoginMainViewUserControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type dashboardViewModel:DashboardMainViewModel}">
<dashboardViews:DashboardMainViewUserControl/>
</DataTemplate>
</Window.Resources>
To switch between the Views I added this Method in MainWindowViewModel:
public void SwitchViewModel()
{
if (!isLoginView)
{
isLoginView = true;
ViewModel = new LoginMainViewModel();
}
else
{
isLoginView = false;
ViewModel = new DashboardMainViewModel();
}
}
What I've tried so far:
I did almost everything this answer suggests. But I can't connect the Views and ViewModels in the App.xaml, cause then I can't use my ResourceDictionaries for Icons and Logos. But they are connected in MainWindow.xaml.
Later on I recognized in order for this to work only one Instance of MainWindowViewModel could exist because otherwise ViewModel would be null everytime a new Object is created. That's why I created ViewModelLocator like this answer suggests.
The weird part for me is when I change the if bracket in the SwitchViewModel Method to this:
if (!isLoginView)
{
isLoginView = true;
ViewModel = new DashboardMainViewModel();
}
Now DashboardMainViewModel is the default View to show and it does excatcly that it shows up.
I have no clue why the Dashboard Screen is not beeing shown.
I want to thank everybody in advance for your help and patience!
The problem is this line in the Login method in ButtonViewModel.
ViewModelLocator ObjViewModelLocator = new ViewModelLocator();
In your view model you create a new instance of the ViewModelLocator, which in turn creates a new instance of MainWindowViewModel on which you then call SwitchViewModel. That is the wrong instance.
You could solve this problem in many different ways, e.g.:
Make ViewModelLocator a singleton.
You would set the DataContext like this, without creating an instance in the Window.Resources:
DataContext="{Binding MainWindowViewModel, Source={x:Static local:ViewModelLocator.Instance}}"
Then in ButtonViewModel you can directly call the SwitchViewModel through ViewModelLocator:
ViewModelLocator.Instance.MWInstance.SwitchViewModel();
Pass a reference of the MainWindowViewModel to the other view models
Passing the instance of MainWindowViewModel to the other view models in order to call SwitchViewModel works, but makes them tightly coupled and exposes more of the view model than necessary.
Alternatively, you could create an interface ISwitchViewModels with a method SwitchViewModel that is implemented by MainWindowViewModel and pass it via the interface instead to hide the actual implementation.
Use events to communicate between view models
Many WPF MVVM frameworks like Prism or Caliburn.Micro feature something called an Event Aggregator to communicate via events between view models and enable loose coupling. In this case you would not have to depend on a reference from ButtonViewModel to MainWindowViewModel, instead you would send a message to request changing the current view.
Use dependency injection to register the ViewModelLocator as singleton and pass it to view models
I guess you are not familiar with dependency injection and it might be beyond the scope of this question, so this is just for further reading.
Note that in this case the ViewModelLocator is not implemented as singleton, but only a single instance of it is created by a container and passed to all other types that specify this dependency.
Understanding dependency injection
What is dependency injection?
The weird part for me is when I change the if bracket in the SwitchViewModel Method to this: [...]
Now DashboardMainViewModel is the default View to show and it does excatcly that it shows up.
That is not weird at all, since you directly assign the ViewModel property on the right instance of MainWindowViewModel without even using the ViewModelLocator.
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 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.
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.