Webinar: 03-Dec-2013 10:00 PST - What’s New in 13.2: Analytics and Dashboards (Product Features)
Announcement: DevExpress Announces its Newest Release for .NET Developers
Blog Post: DevExpress MVVM Framework. Introduction to POCO ViewModels.
Traditionally, MVVM development means writing significant ViewModel boilerplate for bindable properties and commands. Even after extending the DevExpress.Xpf.Mvvm.ViewModelBase class, you need at least five lines of code to declare a single bindable property and several more to define a command:
NOTE: Refer to the following topic to explore the ViewModelBase class: Getting Started with DevExpress MVVM Framework. Commands and View Models.
1:publicclass LoginViewModel : ViewModelBase {
2:string userName;
3:publicstring UserName {
4: get { return userName; }
5: set { SetProperty(
6:ref userName, value, () => UserName);
7: }
8: }
9:public DelegateCommand<string>
10: SaveAccountCommand { get; private set; }
11:
12:public LoginViewModel() {
13: SaveAccountCommand =
14:new DelegateCommand<string>(
15: SaveAccount, CanSaveAccount);
16: }
17:void SaveAccount(string fileName) {
18://...
19: }
20:bool CanSaveAccount(string fileName) {
21:return !string.IsNullOrEmpty(fileName);
22: }
23: }
Now imagine, instead of the preceding code, writing this:
1:publicclass LoginViewModel {
2:publicvirtualstring UserName { get; set; }
3:publicvoid SaveAccount(string fileName) {
4://...
5: }
6:publicbool CanSaveAccount(string fileName) {
7:returntrue;
8: }
9: }
The 13.2 release makes it possible, with support for POCO ViewModels. Generate a full-fledged ViewModel from your POCO class with ViewModelSource.
In code:
1:publicclass LoginViewModel {
2:protected LoginViewModel() { }
3:publicstatic LoginViewModel Create() {
4:return ViewModelSource.Create(() => new LoginViewModel());
5: }
6:
7:publicvirtualstring UserName { get; set; }
8:publicvoid SaveAccount(string fileName) {
9://...
10: }
11:publicbool CanSaveAccount(string fileName) {
12:return !string.IsNullOrEmpty(fileName);
13: }
14: }
1:<UserControlx:Class="DXPOCO.Views.LoginView"
2:xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
3:xmlns:ViewModels="clr-namespace:DXPOCO.ViewModels"
4:DataContext="{dxmvvm:ViewModelSource Type=ViewModels:LoginViewModel}"
5: ...>
6:<Grid>
7:<!--...-->
8:</Grid>
9:</UserControl>
ViewModelSource uses System.Reflection.Emit to dynamically create and return an instance of a descendant of the passed ViewModel. The approximate code of the descendant is:
1:publicclass LoginViewModelBindable : LoginViewModel, INotifyPropertyChanged {
2:publicoverridestring UserName {
3: get { returnbase.UserName; }
4: set {
5:if(base.UserName == value) return;
6:base.UserName = value;
7: RaisePropertyChanged("UserName");
8: }
9: }
10: DelegateCommand<string> saveAccountCommand;
11:public DelegateCommand<string> SaveAccountCommand {
12: get {
13:return saveAccountCommand ??
14: (saveAccountCommand =
15:new DelegateCommand<string>(SaveAccount, CanSaveAccount));
16: }
17: }
18:
19://INotifyPropertyChanged Implementation
20: }
Now let’s examine how to control ViewModel generation.
Bindable Properties
The rules for generating bindable properties are simple: ViewModelSouce creates bindable properties for public virtual properties and automatic properties with a public getter and, at the least, a protected setter.
You can define functions to invoke when a property is changed. These functions need a special name: On<PropertyName>Changed, On<PropertyName>Changing:
1:publicclass LoginViewModel {
2:publicvirtualstring UserName { get; set; }
3:protectedvoid OnUserNameChanged() {
4://...
5: }
6: }
1:publicclass LoginViewModel {
2:publicvirtualstring UserName { get; set; }
3:protectedvoid OnUserNameChanged(string oldValue) {
4://...
5: }
6:protectedvoid OnUserNameChanging(string newValue) {
7://...
8: }
9: }
Use the BindableProperty attribute to set a function not matching the convention:
1:publicclass LoginViewModel {
2: [BindableProperty(isBindable: false)]
3:publicvirtualbool IsEnabled { get; set; }
4:
5: [BindableProperty(OnPropertyChangedMethodName = "Update")]
6:publicvirtualstring UserName { get; set; }
7:protectedvoid Update() {
8://...
9: }
10: }
The disadvantage of this approach is renaming a function or property causes its handler to stop working. This is why we also implemented support for fluent declaration. Use the Fluent API as follows:
1: [MetadataType(typeof(Metadata))]
2:publicclass LoginViewModel {
3:class Metadata : IMetadataProvider<LoginViewModel> {
4:void IMetadataProvider<LoginViewModel>.BuildMetadata
5: (MetadataBuilder<LoginViewModel> builder) {
6:
7: builder.Property(x => x.UserName).
8: OnPropertyChangedCall(x => x.Update());
9: builder.Property(x => x.IsEnabled).
10: DoNotMakeBindable();
11: }
12: }
13:publicvirtualbool IsEnabled { get; set; }
14:publicvirtualstring UserName { get; set; }
15:protectedvoid Update() {
16://...
17: }
18: }
This approach avoids errors during refactoring.
NOTE: We will return to the Fluent API in an upcoming post to examine other tasks to solve with it.
Commands
A command is generated for each parameterless and single parameter public method.
1:publicclass LoginViewModel {
2://DelegateCommand<string> SaveAccountCommand =
3:// new DelegateCommand<string>(SaveAccount, CanSaveAccount);
4:publicvoid SaveAccount(string fileName) {
5://...
6: }
7:publicbool CanSaveAccount(string fileName) {
8:return !string.IsNullOrEmpty(fileName);
9: }
10:
11://DelegateCommand Close = new DelegateCommand(Close);
12:publicvoid Close() {
13://...
14: }
15: }
Likewise, command generation can be controlled with the Command attribute or Fluent API:
1:publicclass LoginViewModel {
2: [Command(isCommand: false)]
3:publicvoid SaveCore() {
4://...
5: }
6:
7: [Command(CanExecuteMethodName = "CanSaveAccount",
8: Name = "SaveCommand",
9: UseCommandManager = true)]
10:publicvoid SaveAccount(string fileName) {
11://...
12: }
13:publicbool CanSaveAccount(string fileName) {
14:return !string.IsNullOrEmpty(fileName);
15: }
16: }
1: [MetadataType(typeof(Metadata))]
2:publicclass LoginViewModel {
3:class Metadata : IMetadataProvider<LoginViewModel> {
4:void IMetadataProvider<LoginViewModel>.BuildMetadata
5: (MetadataBuilder<LoginViewModel> builder) {
6:
7: builder.CommandFromMethod(x => x.SaveCore()).
8: DoNotCreateCommand();
9: builder.CommandFromMethod(x => x.SaveAccount(default(string))).
10: CommandName("SaveCommand").
11: CanExecuteMethod(x => x.CanSaveAccount(default(string)));
12: }
13: }
14:
15:publicvoid SaveCore() {
16://...
17: }
18:
19:publicvoid SaveAccount(string fileName) {
20://...
21: }
22:publicbool CanSaveAccount(string fileName) {
23:return !string.IsNullOrEmpty(fileName);
24: }
25: }
The extension methods of the DevExpress.Xpf.Mvvm.POCO.POCOViewModelExtensions class support manually raising a PropertyChanged event or updating a command:
1:publicstaticclass POCOViewModelExtensions {
2:publicstaticbool IsInDesignMode(thisobject viewModel);
3:publicstaticvoid RaiseCanExecuteChanged<T>(
4:this T viewModel, Expression<Action<T>> methodExpression);
5:publicstaticvoid RaisePropertyChanged<T, TProperty>(
6:this T viewModel, Expression<Func<T, TProperty>> propertyExpression);
7: }
For instance:
1:publicclass LoginViewModel {
2:publicvoid Update() {
3:this.RaisePropertyChanged(x => x.UserName);
4:this.RaiseCanExecuteChanged(x => x.SaveAccount(default(string)));
5: }
6:
7:publicvirtualstring UserName { get; set; }
8:publicvoid SaveAccount(string fileName) {
9://...
10: }
11:publicbool CanSaveAccount(string fileName) {
12:return !string.IsNullOrEmpty(fileName);
13: }
14: }
Services
As you likely know, the DevExpress MVVM Framework provides a service mechanism. Previously, accessing a service required inheriting from ViewModelBase and implementing the following construction:
1:public IMessageBoxService MessageBoxService {
2: get { return GetService<IMessageBoxService>(); }
3: }
You can now write:
1:publicvirtual IMessageBoxService MessageBoxService { get { returnnull; } }
Use the ServiceProperty attribute or the Fluent API, as earlier described, to control service property generation.
Is there a performance penalty for working with POCO?
The ViewModel created by ViewModelSource is a descendant of the passed class. This descendant is generated using System.Reflection.Emit to implement INotifyPropertyChanged, override virtual properties, create commands, etc. Although these operations are handled at runtime, performance degradation is not an issue because the mechanism uses a cache -- generation is performed once only for each class and not for each instance of a class. By the way, Entity Framework 5.0+ uses the same mechanism.
ViewModelSource supports several approaches to create ViewModels.
- For a ViewModel with a public parameterless constructor, the following approach is simple and fast:
ViewModelSource.Create<LoginViewModel>();
- Since lambda expressions are not comparable, they remain uncached and compiled anew with each method call. While this approach is the most powerful and beautiful, it is also the slowest:
ViewModelSource.Create(() => new LoginViewModel(caption: "Login") {
UserName = "John Smith"
}); - Since compiled delegate instances can be cached, this is a fast approach for passing parameters to the ViewModel constructor:
var factory = ViewModelSource.Factory((string caption) => new LoginViewModel(caption));
factory("Login");
Is there a live example of POCO ViewModels?
With 13.2, we introduce a new real-life demo – Sales. This demo is built completely on the POCO technology, so you can examine its code.
Moreover, DevExpress Scaffolding Wizards now generate ViewModels as POCO. Here is a tutorial describing how to build an application with Scaffolding Wizards. You can also scaffold Views based on your POCO ViewModels - simply add the DevExpress.Xpf.Mvvm.DataAnnotations.POCOViewModel attribute to the ViewModels.
P.S. What could be better than the previous approach? Right, dropping the requirements of virtual properties and creating ViewModel instances via a special factory. We are currently working on this and will introduce a solution in the near future.
P.P.S. In fact, the POCO ViewModels functionality is a good example of the Aspect-oriented Programming paradigm. Previously, inheriting from ViewModelBase meant implementing cross-cutting concerns across ViewModels (i.e. similar code in the definitions of the bindable properties and commands). Now, you can get rid of this redundant code with aspects (e.g., conventions for defining properties and methods, attributes, and Fluent API).
OTHER RELATED ARTICLES:
- Getting Started with DevExpress MVVM Framework. Commands and View Models.
- DevExpress MVVM Framework. Introduction to Services, DXMessageBoxService and DialogService.
- DevExpress MVVM Framework. Interaction of ViewModels. IDocumentManagerService.
- THIS POST: DevExpress MVVM Framework. Introduction to POCO ViewModels.
TV Channel: PhoneJS: Localization
Learn how to HTML JS application that are built using PhoneJS can be localized. | From:Developer Express Views:
12 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:03:56 | More inScience & Technology |
TV Channel: PhoneJS: dxPivot Widget
This video goes over the basics on creating the dxPivot widget from our JavaScript Library. Learn more about PhoneJS at http://phonejs.devexpress.com/ | From:Developer Express Views:
37 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:03:45 | More inScience & Technology |
TV Channel: PhoneJS: dxPanorama Widget
This video goes over the basics on creating the dxPanorama widget from our JavaScript Library. Learn more about PhoneJS at http://phonejs.devexpress.com/ | From:Developer Express Views:
19 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:03:40 | More inScience & Technology |
TV Channel: DevExpress ASP.NET MVC: The GridLookup Control
In this video, you will learn how to add a GridLookup to your application. How to bind it to a data source. And how to customize the dropdown grid. | From:Developer Express Views:
7 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:04:32 | More inScience & Technology |
TV Channel: PhoneJS: dxRadioGroup Widget
This video goes over the basics on creating the dxRadioGroup widget from our JavaScript Library. Learn more about PhoneJS at http://phonejs.devexpress.com/ | From:Developer Express Views:
11 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:02:28 | More inScience & Technology |
TV Channel: DevExpress Win8 XAML: The PDFViewer
With the DevExpress PDF Viewer control, you can embed the display of PDF files directly in your WinRT application. This control supports zooming, scrolling, continuous page layout and text searching out-of-the-box. The DevExpress PDF Viewer utilizes the DirectX API to consume minimum resources and provide maximum performance. | From:Developer Express Views:
8 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:01:58 | More inScience & Technology |
TV Channel: DevExpress ASP.NET MVC: The Token Box Control
In this video you'll learn how to add a TokenBox to your application. How to bind it to a data source. And how to manipulate tokens on the client side. | From:Developer Express Views:
29 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:04:47 | More inScience & Technology |
TV Channel: DevExpress ASP.NET MVC: The FormLayout Control
In this video you'll learn how to add a FormLayout to your application. How to populate it with items manually or from data source and how to customize layout items and groups. | From:Developer Express Views:
12 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:05:51 | More inScience & Technology |
TV Channel: DevExpress ASP.NET: The Ribbon Control
This video shows you how to get started using the Ribbon Control for ASP.NET. | From:Developer Express Views:
30 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:03:28 | More inScience & Technology |
TV Channel: DevExpress ASP.NET MVC: The Ribbon Control
In this video you'll learn how to add a Ribbon to your application. How to populate it with items manually or from data source and how to handle button click events. | From:Developer Express Views:
21 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:05:35 | More inScience & Technology |
TV Channel: DevExpress WinForms: Document Manager WidgetView
This video demonstrates the WidgetView - a brand new View for Document Manager component that allows you to easily implement a dashboard-like UI for you application. | From:Developer Express Views:
21 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:03:48 | More inScience & Technology |
TV Channel: DevExpress WinForms: Using the Grid EditForm
This video walks you through the basics of using the WinForms grid EditForm to edit data. | From:Developer Express Views:
33 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:03:54 | More inScience & Technology |
TV Channel: DevExpress ASP.NET: Batch Editing in the Grid
This video shows you how to implement batch editing for the ASP.NET grid. Batch editing allows users to make multiple edits within different grid cells at once. | From:Developer Express Views:
43 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:03:07 | More inScience & Technology |
TV Channel: DevExpress ASP.NET: Customize Dialog Form for HTML Editor
This video walks you through the steps on how to customize dialog forms for the ASP.NET HTML Editor. | From:Developer Express Views:
46 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:02:49 | More inScience & Technology |
TV Channel: DevExpress Dashboards: Sparklines
Learn how to add sparklines to a Dashboard grid and card view. | From:Developer Express Views:
34 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:02:12 | More inScience & Technology |
TV Channel: DevExpress ASP.NET MVC: The Rating Control
In thisvideo you'll learn how to add a Rating Control to your application. Then see how to use its client-side API. | From:Developer Express Views:
22 ![]() ![]() ![]() ![]() ![]() 0ratings | |
Time:03:43 | More inScience & Technology |
TV Channel: DevExpress WinForms: Map Searching and Routing with Bing Services
This video shows how to use online Microsoft Bing Geocode, Bing Route, and Bing Search Services in the map control to locate any place on a map, see the detailed information for this place or specify a route consisting of two or more locations. | From:Developer Express Views:
21 ![]() ![]() ![]() ![]() ![]() 1ratings | |
Time:02:45 | More inScience & Technology |