Design-Time Features > Simplifying MVVM |
The Model-View-View-Model (MVVM) pattern is gaining in popularity as developers realize the benefits it gives their applications, making them easier to maintain and test and, particularly in the case of WPF and Silverlight applications, allowing a much clearer division of labor between the designer of the UI and the creator of the code that makes it work. However, without effective tools to aid program development based on MVVM patterns, programmers can actually find themselves working harder because of the need to implement an extra layer (the View Model) and ensure that data is properly synchronized between that and the Model. This extra burden isn’t onerous when you have a relatively small application with just a few simple collections (and best practice with MVVM is to use ObservableCollection as the datasource), but the bigger it becomes and the more collections it spawns, the worse it becomes. DataSource for Entity Framework can ease the burden.
C1DataSource lets you use live views as your view model. Just create live views over your model collections and use them as your view model. Live views are synchronized automatically with their sources (model), so you don’t need any synchronization code - it’s all automatic. And live views are much easier to create than to write your own view model classes using ObservableCollection and filling those collections manually in code. You have all the power of LINQ at your disposal to reshape model data into live views. So, not only does synchronization code disappear, but the code creating view models is dramatically simplified.
To demonstrate how easy it is to follow the MVVM pattern using live views, let’s create a form combining all features from two previous examples: the Category-Products master-detail from Working with DataSources in Code and the reshaping/filtering/ordering of data from Live Views. It will be a Category-Products view, showing non-discontinued products whose unit price is at least 30, ordered by unit price, and displaying a customized set of product fields in a master-detail form where the user can select a category to show the products of that category. We'll follow the MVVM pattern. The form (view), called CategoryProductsView, only hosts GUI controls (a combo box and a grid) and does not have any code except what is used to set the data source to the view model, like this:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
PublicClassCategoryProductsView PrivateviewModel AsCategoryProductsViewModel= NewCategoryProductsViewModel() PublicSubNew() comboBox1.DisplayMember = "CategoryName" EndSub |
To write code in C#
C# |
Copy Code
|
---|---|
publicpartialclassCategoryProductsView: Form publicCategoryProductsView() comboBox1.DisplayMember = "CategoryName"; |
All logic is in the view model class, separate from the GUI. More exactly, there are three classes: CategoryViewModel, ProductViewModel, and CategoryProductsViewModel. The first two are simple classes defining properties with no additional code:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
PublicClassCategoryViewModel
|
To write code in C#
C# |
Copy Code
|
---|---|
public classCategoryViewModel
{ public virtual int CategoryID { get; set; } public virtual string CategoryName { get; set; } } public classProductViewModel { public virtual int ProductID { get; set; } public virtual string ProductName { get; set; } public virtual int? CategoryID { get; set; } public virtual string CategoryName { get; set; } public virtual int? SupplierID { get; set; } public virtual string SupplierName { get; set; } public virtual decimal? UnitPrice { get; set; } public virtual string QuantityPerUnit { get; set; } public virtual short? UnitsInStock { get; set; } public virtual short? UnitsOnOrder { get; set; } } |
And here is the code for the CategoryProductsViewModel class:
To write code in Visual Basic
Visual Basic |
Copy Code
|
---|---|
PublicClassCategoryProductsViewModel
Private_scope AsEntityClientScope Private_categories AsBindingSource Private_products AsBindingSource PublicSubNew() DimCategoriesView AsObject= DimProductsView AsObject= EndSub |
To write code in C#
C# |
Copy Code
|
---|---|
public classCategoryProductsViewModel
{ private C1.Data.Entities.EntityClientScope _scope; publicBindingSource Categories { get; private set; } public BindingSource Products { get; private set; } public CategoryProductsViewModel() { _scope = Program.ClientCache.CreateScope(); Categories = new BindingSource( from c in _scope.GetItems<Category>() select new CategoryViewModel() { CategoryID = c.CategoryID, CategoryName = c.CategoryName }, null); Products = new BindingSource( from p in _scope.GetItems<Product>().AsFilteredBound(p => select new ProductViewModel() { ProductID = p.ProductID, ProductName = p.ProductName, CategoryID = p.CategoryID, CategoryName = p.Category.CategoryName, SupplierID = p.SupplierID, SupplierName = p.Supplier.CompanyName, UnitPrice = p.UnitPrice, QuantityPerUnit = p.QuantityPerUnit, UnitsInStock = p.UnitsInStock, UnitsOnOrder = p.UnitsOnOrder }, null); } } |
Basically, it contains just two LiveLinq statements, nothing more. The statements (creating live views, see Live Views) are wrapped in BindingSource constructors to add currency, current item, support to the Categories and Products collections exposed by the view model class. Note that using BindingSource is only necessary in WinForms, because the WinForms platform is not as well suited for MVVM as WPF or Silverlight. Note also that because we use BindingSource, you need to add the following statement to the code file (in WinForms only):
using System.Windows.Forms;
Similar to what we saw in Working with DataSources in Code, using AsFilteredBound() gives us server-side filtering. We also connected the filter key, which is the Product.CategoryID property, to the CategoryID selected by the user using a combo box event. Here we can’t do this because we must keep our code independent of the GUI. So we use a BindFilterKey method to bind the filter key to the Category.CategoryID property of the item currently selected in the Categories collection. This is one reason why we need to support currency in the Categories collection and why we wrapped it in a BindingSource to get currency support in WinForms.
The Include ("Supplier") operator is not strictly necessary; it is used here for performance optimization. Without it, Entity Framework will fetch Supplier objects lazily, one-by-one, every time the user accesses an element of the Products collection whose Supplier was not yet fetched. This can cause delays, and it’s generally much less efficient to fetch data in single rows rather than in batches, so we opted out of lazy loading here using Include("Supplier"), which tells the Entity Framework to fetch supplier information in the same query with products.