MVVM - data layer of models and viewmodels - silverlight

I am refactoring my application to utilize MVVM. I used to keep a List<Product> variable in the Application class that I was able to bind a ListView to. This List made up my Data layer. The Page with this ListView is a master/detail layout. With MVVM, I am thinking that the List should now hold instances of the ProductModel as it is the data layer. If I should be binding to ViewModels, do I need a separate List of ViewModels too?

You might need to take a different perspective on MVVM. Your View is the page with the controls (XAML) and your ViewModel is the glue between your data model and the page. The View's entire data context will be set to the ViewModel (done either in the XAML directly or in code-behind depending on which MVVM camp you subscribe to).
In your example, you would move List<Product> onto the ViewModel as ObservableCollection<Product> and make sure that your ViewModel implements the INotifyPropertyChanged interface. INotifyPropertyChanged is the contract the View uses to know when to update it's binding. You will use an ObservableCollection<T> instead of a list because ObservableCollection<T> implements INotifyPropertyChanged itself.
Your View's DataContext property will be set to an instance of the ViewModel. On the View, the ListBox control's ItemsSource property will be set to bind to the Product collection. You can then have methods inside of your ViewModel that will be responsible for communicating with your data store to populate the observable collection.
ViewModel
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Product> _products = null;
public ObservableCollection<Product> Products
{
get { return _products; }
set
{
if( _products != value )
{
_products = value;
if( this.PropertyChanged != null )
{
this.PropertyChanged( this, new PropertyChangedEventArgs( "Products" ) );
}
}
}
}
// have code in here that loads the Products list from your data store (i.e. service call to database)
}
View Code-Behind
public MyView()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
View
<ListBox
ItemsSource={Binding Path=Products, Mode=OneWay}
SelectedItem={Binding Path=SelectedProduct, Mode=TwoWay}
...
/>

Related

Setting datacontext inside ViewModel

I have a MainWindowView that has a grid with 2 columns each having 1 UserControl View. MainWindowView constructor creates instance of MainWindowVM and sets the data context to this new instance.
this.DataContext = new MainWindowVM(this)
Now question is I am trying to set data context of each UserControlView to an instance of it's respective ViewModel inside MainWindowVM. How can I access the UserControlView inside MainWindowVM to do something like this
UserControl1View.DataContext= new UserControl1ViewModel()
If I can do this, it will allow me to use MainWindowVM as a common hub holding all kinds of event subscriptions from the 2 userControls.
ViewModel must not depend upon View, and both must have one-to-one relationship between them. So best is to use Binding to set DataContext and if this setting of DataContext depends upon some condition, then use Triggers.
Don't-Do-That.
A better approach is to have a ViewModel reference in he View.
Create an interface similar to this:
public interface IView<T> where T : class
{
T ViewModel;
}
Now, your Views must implement that interface
public partial class MainView : Window, IView<MainViewModel>
{
public MainViewModel ViewModel { get; set; }
And inject the ViewModel in the view constructor:
public MainView(MainViewModel vm)
{
this.ViewModel = vm;
this.DataContext = this.ViewModel;
// you can create the VMs you want for the another views
var vm1 = new UserControl1ViewModel();
// and pass it to the UserControl1View (UserControl1View implements IView<T>
var view1 = new UserControl1View(vm1);

How do I load a DataGrid with data from a database using WPF and MVVM?

I am a complete newbie to WPF and MVVM so I apologise in advance if this query is quite simple. I've searched online and havent been able to find anything which fits my requirements. Hense why I'm here!
I am currently trying to implement a table of data queried from a database using LINQ. This is the query I run:
DataContext connection = new DataContext();
var getTripInformation = from m in connection.tblTrips
where m.TripDate > DateTime.Today
select new { m.TripID, m.TripName, m.TripDate, m.ClosingDate, m.PricePerAdult, m.PricePerChild, m.Status };
Which fills my var with the relevant information which I expect.
Now, what I want to be able to do is diplay this in my View using a DataGrid. Can anyone assist me with this?
In a nutshell, you will have your View and ViewModel. The ViewModel will need to implement the INotifyPropertyChanged interface to facilitate view binding. This just provides an event that is raised when you change a property on your ViewModel. Your View will then bind to the ViewModel's properties. This works as long as the DataContext of the view is set to a ViewModel instance. Below, this is done in code-behind, but many purists do this directly in XAML. Once these relationships are defined, run your LINQ query to populate the ObservableCollection (which also implements INotifyPropertyChanged for when items are added/removed internally) and your grid will show the data.
ViewModel
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<MyRecord> _records = null;
public ObservableCollection<MyRecord> Records
{
get { return _records; }
set
{
if( _records != value )
{
_records = value;
if( this.PropertyChanged != null )
{
this.PropertyChanged( this, new PropertyChangedEventArgs( "Records" ) );
}
}
}
}
public MyViewModel()
{
this.Records = new ObservableCollection<MyRecord>();
this.LoadData();
}
private void LoadData()
{
// this populates Records using your LINQ query
}
View (Code-Behind)
public class MyView : UserControl
{
public MyView()
{
InitializeControl();
// setup datacontext - this can be done directly in XAML as well
this.DataContext = new MyViewModel();
}
}
View (XAML)
<DataGrid
ItemsSource="{Binding Path=Records, Mode=OneWay}"
...
/>
If you set AutoGenerateColumns = 'True' on your DataGrid, it will create a row for each public property of the bound item type. If you set this value to false, you will need to explicitly list the columns and what property they will map to.
If you are developing the application using MVVM then you need to do-
ViewModel Class - that will have UI logic and will implement INotifyPropertyChanged interface. You need to create Property of type collection which will gets binded with DataGrid. And on setter of this proeprty you need to call PropertyChangedEventHandler.
You need to set the DataContext of View to your ViewModel on XAML, Codebehind, ViewModel or on some mediator class.

Binding a ContentControl to a deep path in WPF

The application I'm currently writing is using MVVM with the ViewModel-first pattern. I have XAML similar to the following:
<ContentControl Content="{Binding FooViewModel.BarViewModel.View, Mode=OneWay}"/>
Every VM is a DependencyObject. Every property is a DependencyProperty. Depending upon the state of the application, the value of the BarViewModel property of the FooViewModel can change, thus changing the value of the View property. Unfortunately when this happens, the new view is not displayed, and the old one remains.
This is extremely frustrating. I thought that if any part of a path expression changed, the binding would update, but that doesn't appear to be the case. When I've used shallower path expressions, such as FooViewModel.View and I've changed the value of the FooViewModel property, that has updated the ContentControl to which it's bound, but not in this case.
If your solution is that I abandon ViewModel-first, that is not an option, though I appreciate your advice. I must get this working as is.
CLARIFICATION
This is a question about data binding, and not about MVVM or how to implement it. You can safely ignore the MVVM aspects of this if it helps you to think about the problem, or if you have a different idea about how MVVM should be implemented. This is a large, existing project in which the MVVM design pattern cannot be changed. (It is far too late for that.)
So, with that said, the correct question to be answering is the following:
Given a binding path expression in which every element is a DependencyProperty and the final property is a view bound to a ContentControl, why does a change in a property in the middle of the path not cause the binding to update?
Although I would expect this to work, there are several problems with your approach.
Firstly, your view models should not use DependencyObject or DependencyProperty, this ties them in to WPF. They should instead implement INotifyPropertyChanged. This makes your view models reusable in other presentation technologies such as Silverlight.
Secondly, your view models shouldn't have references to your views, so you shouldn't require a View property on your view models.
I would seriously consider using an MVVM framework for view composition - Caliburn.Micro, for example, makes view model first development extremely straightforward, and already provides a view model base class which implements INotifyPropertyChanged, and a mechanism for building view compositions with conventions.
I.e. you can have a conductor view model which has an ActiveItem property, and you simply place a ContentControl on your view with the same name as the property:
<ContentControl x:Name="ActiveItem" />
You can use the ActivateItem() method to change the current active item.
Caliburn.Micro also has a host of other features, such as being able to place a Button control with x:Name="Save" on your view, and your Save method on your view model will automatically be invoked when the button is clicked.
Every VM is a DependencyObject. Every property is a
DependencyProperty.
why? a viewmodel should be a simple class with INotifyPropertyChanged and the Properties should be simple properties.
and if you want your different viewmodel be rendered in a different way - you should use DataTemplate.
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyViewModelA}>
<MyViewA/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:MyViewModelB}>
<MyViewB/>
</DataTemplate>
</Windows.Resources>
<Grid>
<ContentControl Content="{Binding MyActualVM}"/>
</Grid>
</Window>
EDIT: btw you always bind to the last Property: FooViewModel.BarViewModel.View --> so the INotifyPropertyChanged (if raised) just work for the .View
EDIT2: another approach could be to get the BindingExpression of your content control and call.
System.Windows.Data.BindingExpression expr = //get it from your contentcontrol
expr.UpdateTarget();
EDIT3: and a simple mvvm way - just use INotifyPropertyChanged
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.MyFooVM = new FooVM();
this.MyFooVM.MyBarVM = new BarVM(){View = "erster"};
this.DataContext = this;
}
public FooVM MyFooVM { get; set; }
private void Button_Click(object sender, RoutedEventArgs e)
{
this.MyFooVM.MyBarVM = new BarVM(){View = "zweiter"};
}
}
public class INPC : INotifyPropertyChanged
{
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropChanged(string property)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(property));
}
#endregion
}
public class FooVM:INPC
{
private BarVM _myBarVm;
public BarVM MyBarVM
{
get { return _myBarVm; }
set { _myBarVm = value;OnPropChanged("MyBarVM"); }
}
}
public class BarVM : INPC
{
private string _view;
public string View
{
get { return _view; }
set { _view = value;OnPropChanged("View"); }
}
}

Silverlight UserControl and his own Viewmodel hosted in a View

I want to make a reusable UserControl as:
A VIEW with its own VIEWMODEL where is the logic data recovery
This UserControl (or View ?) has a button 'OK' to target a RELAYCOMMAND in his viewmodel
I'm hosting this 'UserControl' in another VIEW ('MainPage'), who, herself, has its viewmodel
My question is:
How can I target the properties of VIEWMODEL of my 'MainPage' with the values outlined by my UC ?
As long as your user control is contained in your main page it will inherit the main pages view model. This is the default and it applies unless you explicitly change the data context through data binding or code.
If your user control binds to it own view model then you could let the main view model contain an instance of the child view model and expose it through a public property. Now you could set the data context of your user control by binding the DataContext property to the property on the main view model.
Finally if your child view model has a reference to the main view model then they will be able to communicate as needed.
Edit:
I'll try to setup a simple example:
First the view models:
public class MainPageViewModel
{
public MainPageViewModel()
{
ChildViewModel = new ChildViewModel(this);
}
public ChildViewModel {get; private set; }
public ICommand OkCommand { get { // return the command here }}
}
public class ChildViewModel
{
private MainPageViewModel _parentViewModel;
public ChildViewModel(MainPageViewModel parentViewModel)
{
_parentViewModel = parentViewModel;
}
// Returns the command from the main page view model
public ICommand OkCommand { get { return _parentViewModel.OkCommand; } }
// Other properties as well
}
Here we have the main view model that has the child view model as a property. The child view model exposes the OkCommand that returns the value from the main view model.
Now in your main page xaml you can do the following:
<uc:MyUserControl DataContext="{Binding ChildViewModel}" />
Here you insert your user control and set it's data context to the child user control view model.

CollectionViewSource.GetDefaultView is not in Silverlight 3! What's the work-around?

The CollectionViewSource.GetDefaultView() method is not in Silverlight 3. In WPF I have this extension method:
public static void SetActiveViewModel<ViewModelType>(this ViewModelBase viewModel,
ViewModelType collectionItem,
ObservableCollection<ViewModelType> collection) where ViewModelType : ViewModelBase
{
Debug.Assert(collection.Contains(collectionItem));
ICollectionView collectionView = CollectionViewSource.GetDefaultView(collection);
if(collectionView != null) collectionView.MoveCurrentTo(collectionItem);
}
How can this be written in Silverlight 3?
Silverlight does not contain the concept of a Default view. When you ask a control in Silverlight to bind to a collection it really does bind to the collection, it does not bind to a default view.
As result I don't think there can be a direct and complete port of your extension method. Some re-engineering of your MVVM implementation will be needed. I've not come across the concept of a collection of view model instances before so I'm not exactly sure what would be appropriate in your case.
A couple of approaches I've seen with CollectionViewSource is to either have the CollectionViewSource defined in the Xaml and bind its Source to something in the ViewModel. Alternatively have a ViewModel expose a CollectionViewSource property and have the View xaml bind to its View proeprty.
One thing that you might be able to do is to manually create the CollectionViewSource, set its Source property to the collection, then get the CollectionView using the View property of the CollectionViewSource.
Something like this might work:
public static void SetActiveViewModel<ViewModelType>(this ViewModelBase viewModel,
ViewModelType collectionItem,
ObservableCollection<ViewModelType> collection) where ViewModelType : ViewModelBase
{
Debug.Assert(collection.Contains(collectionItem));
CollectionViewSource collectionViewSource = new CollectionViewSource();
collectionViewSource.Source = collection;
ICollectionView collectionView = collectionViewSource.View;
if(collectionView != null) collectionView.MoveCurrentTo(collectionItem);
}

Resources