A newer version of this page is available. Switch to the current version.

POCO ViewModels

  • 13 minutes to read

POCO (Plain Old CLR Objects) View Models simplify and accelerate the development process. POCO classes allow you to easily define bindable properties and methods without the need to inherit from many different classes (e.g., ViewModelBase) or implement numerous MVVM-specific interfaces, such as INotifyPropertyChanged and ICommand. POCO ViewModels allow you to define bindable properties as simple auto-implemented properties, as well as create methods that will be represented by commands at runtime. This provides clean, simple, maintainable and testable MVVM code. This is a great approach to the MVVM pattern.

All DevExpress WPF Controls, as well as other WPF Controls, are fully compatible with POCO View Models.

Basics of Generating POCO View Models

A POCO class does not implement an interface, and does not need to be inherited from a base class, such as ViewModelBase or BindableBase. To transform a POCO class into a working ViewModel, it is necessary to create it with the DevExpress.Mvvm.POCO.ViewModelSource class. See the example below.

public class LoginViewModel {
        //This property will be converted to a bindable one
        public virtual string UserName { get; set; }

        //SaveAccountSettingsCommand will be created for the SaveAccountSettings and CanSaveAccountSettings methods:
        //SaveAccountSettingsCommand = new DelegateCommand<string>(SaveAccountSettings, CanSaveAccountSettings);
        public void SaveAccountSettings(string fileName) {
            //...
        }
        public bool CanSaveAccountSettings(string fileName) {
            return !string.IsNullOrEmpty(fileName);
        }

        //We recommend that you not use public constructors to prevent creating the View Model without the ViewModelSource
        protected LoginViewModel() { }
        //This is a helper method that uses the ViewModelSource class for creating a LoginViewModel instance
        public static LoginViewModel Create() {
            return ViewModelSource.Create(() => new LoginViewModel());
        }
    }

The ViewModelSource class can also be used to create a View Model instance in XAML.

<UserControl x:Class="DXPOCO.Views.LoginView"
    xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
    xmlns:ViewModels="clr-namespace:DXPOCO.ViewModels"
    DataContext="{dxmvvm:ViewModelSource Type=ViewModels:LoginViewModel}"
    ...>
    <Grid>
        <!--...-->
    </Grid>
</UserControl>

The ViewModelSource.Create method creates a descendant of the passed ViewModel class using Reflection Emit and returns its instance at runtime. The code below is similar to the one that the ViewModelSource generates based on the LoginViewModel class.

public class LoginViewModel_EXTENSION : LoginViewModel, INotifyPropertyChanged {
        public override string UserName {
            get { return base.UserName; }
            set {
                if(base.UserName == value) return;
                base.UserName = value;
                RaisePropertyChanged("UserName");
            }
        }
        DelegateCommand<string> saveAccountSettingsCommand;
        public DelegateCommand<string> SaveAccountSettingsCommand {
            get {
                return saveAccountSettingsCommand ?? 
                    (saveAccountSettingsCommand = 
                    new DelegateCommand<string>(SaveAccountSettings, CanSaveAccountSettings));
            }
        }

        //INotifyPropertyChanged Implementation
    }

To pass parameters to the ViewModel’s constructor, you can use one of the following approaches.

  • While this approach is the most powerful and beautiful, it is also the slowest, because lambda expressions are not comparable, they remain uncached and compile anew with each method call.

    ViewModelSource.Create(() => new LoginViewModel(caption: "Login") {
        UserName = "John Smith"
    });
    
  • Since compiled delegate instances can be cached, this is the quickest approach for passing parameters to the ViewModel constructor.

    var factory = ViewModelSource.Factory((string caption) => new LoginViewModel(caption));
    factory("Login");
    

This example demonstrates how to use the POCO mechanism to create view models.

Imports System.Windows.Controls

Namespace Example.View
    Partial Public Class LoginView
        Inherits UserControl

        Public Sub New()
            InitializeComponent()
        End Sub
    End Class
End Namespace

Bindable Properties

The rule for generating bindable properties is as follows: all public auto-implemented properties with the virtual modifier and a public getter, along with a protected or public setter, become bindable.

You can define functions that are invoked when properties are changed. These function names should use the following formats: On[PropertyName]Changed and On[PropertyName]Changing.

public class LoginViewModel {
        public virtual string UserName { get; set; }
        protected void OnUserNameChanged() {
            //...
        }
    }

    public class LoginViewModel {
        public virtual string UserName { get; set; }
        protected void OnUserNameChanged(string oldValue) {
            //...
        }
        protected void OnUserNameChanging(string newValue) {
            //...
        }
    }

To define a function that does not match the convention, use the BindableProperty attribute.

public class LoginViewModel {
        [BindableProperty(isBindable: false)]
        public virtual bool IsEnabled { get; set; }

        [BindableProperty(OnPropertyChangedMethodName = "Update")]
        public virtual string UserName { get; set; }
        protected void Update() {
            //...
        }
    }

You can also take advantage of the Fluent API.

[MetadataType(typeof(Metadata))]
    public class LoginViewModel {
        public class Metadata : IMetadataProvider<LoginViewModel> {
            void IMetadataProvider<LoginViewModel>.BuildMetadata
                (MetadataBuilder<LoginViewModel> builder) {

                builder.Property(x => x.UserName).
                    OnPropertyChangedCall(x => x.Update());
                builder.Property(x => x.IsEnabled).
                    DoNotMakeBindable();
            }
        }
        public virtual bool IsEnabled { get; set; }
        public virtual string UserName { get; set; }
        protected void Update() {
            //...
        }
    }

Commands

Commands are generated for all public methods without parameters or with only one parameter. You can control the generation mechanism using the Command attribute or using the Fluent API.

public class LoginViewModel {
        [Command(isCommand: false)]
        public void SaveCore() {
            //...
        }

        [Command(CanExecuteMethodName = "CanSaveAccountSettings",
            Name = "SaveCommand",
            UseCommandManager = true)]
        public void SaveAccountSettings(string fileName) {
            //...
        }
        public bool CanSaveAccountSettings(string fileName) {
            return !string.IsNullOrEmpty(fileName);
        }
    }

    [MetadataType(typeof(Metadata))]
    public class LoginViewModel {
        public class Metadata : IMetadataProvider<LoginViewModel> {
            void IMetadataProvider<LoginViewModel>.BuildMetadata(MetadataBuilder<LoginViewModel> builder) {
                builder.CommandFromMethod(x => x.SaveCore()).
                    DoNotCreateCommand();
                builder.CommandFromMethod(x => x.SaveAccountSettings(default(string))).
                    CanExecuteMethod(x => x.CanSaveAccountSettings(default(string))).
                    CommandName("SaveCommand");
            }
        }
        public void SaveCore() {
            //...
        }
        public void SaveAccountSettings(string fileName) {
            //...
        }
        public bool CanSaveAccountSettings(string fileName) {
            return !string.IsNullOrEmpty(fileName);
        }
    }

The following topic contains more information about commands: Commands.

Services

The DevExpress MVVM Framework provides the Services mechanism. The code below is an example of how to gain access to the Message Box service.

using DevExpress.Mvvm.POCO;
...
public class LoginViewModel {
    public IMessageBoxService MessageBoxService { get { return this.GetService<IMessageBoxService>(); } }
}

Please review the following topic to learn more about accessing services: Services in POCO objects.

View Model parent-child relationships

POCO View Models can be related to each other with the parent-child relationship. This is achieved with the ISupportParentViewModel interface that is automatically implemented when you create a POCO object with the ViewModelSource class. With this interface, child View Models may access Services registered in the main View Model. The following topic contains more information on how you can set the parent-child relationship and what benefits can be derived using this mechanism: ViewModel relationships (ISupportParentViewModel).

Automatic IDataErrorInfo Implementation

The IDataErrorInfo interface is the standard mechanism for data validation in WPF. Using this interface, you can provide validation rules for each property in isolation or alternatively, on the entire object. The POCO mechanism allows you to automatically implement the IDataErrorInfo interface based on defined attributes or Fluent API.

To enable this feature, apply the POCOViewModel attribute for your View Model and set the POCOViewModel.ImplementIDataErrorInfo parameter to True.

//Attribute-based approach
[POCOViewModel(ImplementIDataErrorInfo = true)] 
public class LoginViewModel { 
    [Required(ErrorMessage = "Please enter the user name.")] 
    public virtual string UserName { get; set; }
}

//Fluent API
[POCOViewModel(ImplementIDataErrorInfo = true)]
[MetadataType(typeof(LoginViewModel.Metadata))]
public class LoginViewModel {
   public class Metadata : IMetadataProvider<LoginViewModel> {
       void IMetadataProvider<LoginViewModel>.BuildMetadata(MetadataBuilder<LoginViewModel> builder) {
           builder.Property(x => x.UserName).
               Required(() => "Please enter the user name.");
        }
    }
    public virtual string UserName { get; set; }
}

When the ViewModelSource generates a descendant of a View Model, it implements the IDataErrorInfo interface as follows.

public class LoginViewModel : IDataErrorInfo { 
    ... 
    string IDataErrorInfo.Error { 
        get { return string.Empty; } 
    } 
    string IDataErrorInfo.this[string columnName] { 
        get { return IDataErrorInfoHelper.GetErrorText(this, columnName); } 
    } 
}

The IDataErrorInfoHelper class provides the capability to get an error based on defined DataAnnotation attributes or Fluent API.

The code example below demonstrates how to use the POCO mechanism to implement the IDataErrorInfo interface.

using DevExpress.Mvvm;
using DevExpress.Mvvm.DataAnnotations;
using System.Windows.Media;

namespace Example.ViewModel {
    [POCOViewModel(ImplementIDataErrorInfo = true)]
    public class MainViewModel : ViewModelBase {
        static PropertyMetadataBuilder<MainViewModel, string> AddPasswordCheck(PropertyMetadataBuilder<MainViewModel, string> builder) {
            return builder.MatchesInstanceRule((name, vm) => vm.Password == vm.ConfirmPassword, () => "The passwords don't match.")
                .MinLength(8, () => "The password must be at least 8 characters long.")
                .MaxLength(20, () => "The password must not exceed the length of 20.");
        }
        public static void BuildMetadata(MetadataBuilder<MainViewModel> builder) {
            builder.Property(x => x.FirstName)
                .Required(() => "Please enter the first name.");
            builder.Property(x => x.LastName)
                .Required(() => "Please enter the last name.");
            builder.Property(x => x.Email)
                .EmailAddressDataType(() => "Please enter a correct email address.");
            AddPasswordCheck(builder.Property(x => x.Password))
                .Required(() => "Please enter the password.");
            AddPasswordCheck(builder.Property(x => x.ConfirmPassword))
                .Required(() => "Please confirm the password.");
        }
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }
        public virtual string Email { get; set; }
        public virtual string Password { get; set; }
        public virtual string ConfirmPassword { get; set; }
        public void OnPasswordChanged() {
            this.RaisePropertyChanged(() => ConfirmPassword);
        }
        public void OnConfirmPasswordChanged() {
            this.RaisePropertyChanged(() => Password);
        }
    }
}

If you need to extend the default IDataErrorInfo implementation, you can manually implement the IDataErrorInfo interface and use the IDataErrorInfoHelper class.