# Runtime-generated POCO View Models | WPF Controls | DevExpress Documentation

**POCO** (**Plain Old CLR Objects**) View Models simplify and speed up the development process.

**POCO View Models** allow you to: 

- Define bindable properties as simple auto-implemented properties.
- Create methods that function as commands at runtime.
- Make properties and methods implement MVVM-specific interfaces.

This allows you to create clean, simple, maintainable, and testable MVVM code. 

The **POCO View Models** are fully compatible with any WPF control.

You can use [View Models Generated at Compile Time](/WPF/402989/mvvm-framework/viewmodels/compile-time-generated-viewmodels) to generate boilerplate code for your ViewModels at compile time.

## 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](/WPF/17351/mvvm-framework/viewmodels/viewmodelbase) or [BindableBase](/WPF/17350/mvvm-framework/viewmodels/bindablebase). To transform a POCO class into a fully functional ViewModel, create a class instance with the **DevExpress.Mvvm.POCO.ViewModelSource.Create** method. See the example below.

- C#

<section id="tabpanel_dJle7Jat7y_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">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&lt;string&gt;(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(() =&gt; new LoginViewModel());
    }
}
</code></pre></section>

You can use the **ViewModelSource** class to create a View Model instance in XAML.

- XAML

<section id="tabpanel_dJle7Jat7y-1_tabid-xaml" role="tabpanel" data-tab="tabid-xaml">
<pre><code class="lang-xaml">&lt;UserControl x:Class=&quot;DXPOCO.Views.LoginView&quot;
    xmlns:dxmvvm=&quot;http://schemas.devexpress.com/winfx/2008/xaml/mvvm&quot;
    xmlns:ViewModels=&quot;clr-namespace:DXPOCO.ViewModels&quot;
    DataContext=&quot;{dxmvvm:ViewModelSource Type=ViewModels:LoginViewModel}&quot;
    ...&gt;
    &lt;Grid&gt;
        &lt;!--...--&gt;
    &lt;/Grid&gt;
&lt;/UserControl&gt;
</code></pre></section>

The **ViewModelSource.Create** method uses [Reflection Emit](https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/3y322t50%28v=vs.100%29) to create a descendant of the specified ViewModel class and returns the descendant class instance at runtime. The code below is similar to the one that the **ViewModelSource** generates based on the *LoginViewModel* class.

- C#

<section id="tabpanel_dJle7Jat7y-2_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">public class LoginViewModel_EXTENSION : LoginViewModel, INotifyPropertyChanged {
    public override string UserName {
        get { return base.UserName; }
        set {
            if(base.UserName == value) return;
            base.UserName = value;
            RaisePropertyChanged(&quot;UserName&quot;);
        }
    }
    DelegateCommand&lt;string&gt; saveAccountSettingsCommand;
    public DelegateCommand&lt;string&gt; SaveAccountSettingsCommand {
        get {
            return saveAccountSettingsCommand ?? 
                (saveAccountSettingsCommand = 
                new DelegateCommand&lt;string&gt;(SaveAccountSettings, CanSaveAccountSettings));
        }
    }

    //INotifyPropertyChanged Implementation
}
</code></pre></section>

### Pass Parameters to the ViewModel Constructor

You can pass parameters to the ViewModel’s constructor using any of the following approaches.

- **Use lambda expressions**. Lambda expressions work slower, because they are not cached and are newly compiled with each method call.

- C#

<section id="tabpanel_dJle7Jat7y-3_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">ViewModelSource.Create(() =&gt; new LoginViewModel(caption: &quot;Login&quot;) {
    UserName = &quot;John Smith&quot;
});
</code></pre></section>
- **Use delegates**. This technique works faster than lambda expressions, because the compiled delegate instances can be cached. This is the quickest technique to pass parameters to the ViewModel constructor.

- C#

<section id="tabpanel_dJle7Jat7y-4_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">var factory = ViewModelSource.Factory((string caption) =&gt; new LoginViewModel(caption));
factory(&quot;Login&quot;);
</code></pre></section>

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

[View Example](https://github.com/DevExpress-Examples/wpf-mvvm-framework-use-the-poco-mechanism)

- LoginView.xaml

<section id="tabpanel_66JOUH7+jV_tabid-xamlLoginView-xaml" role="tabpanel" data-tab="tabid-xamlLoginView-xaml">
<pre><code class="lang-xaml">&lt;UserControl x:Class=&quot;Example.View.LoginView&quot;
    xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
    xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
    xmlns:ViewModel=&quot;clr-namespace:Example.ViewModel&quot;
    xmlns:dxmvvm=&quot;http://schemas.devexpress.com/winfx/2008/xaml/mvvm&quot;
    xmlns:dx=&quot;http://schemas.devexpress.com/winfx/2008/xaml/core&quot;
    xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;
    xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
    mc:Ignorable=&quot;d&quot; d:DesignHeight=&quot;500&quot; d:DesignWidth=&quot;600&quot;
    DataContext=&quot;{dxmvvm:ViewModelSource Type=ViewModel:LoginViewModel}&quot;&gt;

    &lt;dxmvvm:Interaction.Behaviors&gt;
        &lt;dx:DXMessageBoxService/&gt;
    &lt;/dxmvvm:Interaction.Behaviors&gt;

    &lt;Grid x:Name=&quot;LayoutRoot&quot; Background=&quot;White&quot;&gt;
        &lt;StackPanel Orientation=&quot;Vertical&quot;&gt;
            &lt;StackPanel Orientation=&quot;Horizontal&quot; Margin=&quot;10&quot;&gt;
                &lt;TextBlock Text=&quot;UserName: &quot; Margin=&quot;3&quot; VerticalAlignment=&quot;Center&quot;/&gt;
                &lt;TextBox Text=&quot;{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}&quot; Margin=&quot;3&quot; Width=&quot;80&quot;/&gt;
            &lt;/StackPanel&gt;
            &lt;Button Content=&quot;Login&quot; Command=&quot;{Binding LoginCommand}&quot; Margin=&quot;5&quot;
                    HorizontalAlignment=&quot;Left&quot;/&gt;
        &lt;/StackPanel&gt;
    &lt;/Grid&gt;
&lt;/UserControl&gt;
</code></pre></section>

- LoginViewModel.cs
- LoginViewModel.vb

<section id="tabpanel_66JOUH7+jV-1_tabid-csharpLoginViewModel-cs" role="tabpanel" data-tab="tabid-csharpLoginViewModel-cs">
<pre><code data-code-links="{&quot;/ (DevExpress.Mvvm)(?:;|$)/&quot;:&quot;/CoreLibraries/DevExpress.Mvvm&quot;,&quot;/ (DevExpress.Mvvm.DataAnnotations)(?:;|$)/&quot;:&quot;/CoreLibraries/DevExpress.Mvvm.DataAnnotations&quot;}" class="lang-csharp">using DevExpress.Mvvm;
using DevExpress.Mvvm.DataAnnotations;
using DevExpress.Mvvm.POCO;

namespace Example.ViewModel {
    [POCOViewModel]
    public class LoginViewModel {
        public static LoginViewModel Create() {
            return ViewModelSource.Create(() =&gt; new LoginViewModel());
        }
        protected LoginViewModel() { }

        public virtual string UserName { get; set; }
        public void Login() {
            this.GetService&lt;IMessageBoxService&gt;().Show(&quot;Login succeeded&quot;, &quot;Login&quot;, MessageButton.OK, MessageIcon.Information, MessageResult.OK);
        }
        public bool CanLogin() {
            return !string.IsNullOrEmpty(UserName);
        }
    }
}
</code></pre></section>
<section id="tabpanel_66JOUH7+jV-1_tabid-vbLoginViewModel-vb" role="tabpanel" data-tab="tabid-vbLoginViewModel-vb" aria-hidden="true" hidden="hidden">
<pre><code data-code-links="{&quot;/ (DevExpress.Mvvm)(?:;|$)/&quot;:&quot;/CoreLibraries/DevExpress.Mvvm&quot;,&quot;/ (DevExpress.Mvvm.DataAnnotations)(?:;|$)/&quot;:&quot;/CoreLibraries/DevExpress.Mvvm.DataAnnotations&quot;}" class="lang-vb">Imports DevExpress.Mvvm
Imports DevExpress.Mvvm.DataAnnotations
Imports DevExpress.Mvvm.POCO

Namespace Example.ViewModel
    &lt;POCOViewModel&gt; _
    Public Class LoginViewModel
        Public Shared Function Create() As LoginViewModel
            Return ViewModelSource.Create(Function() New LoginViewModel())
        End Function
        Protected Sub New()
        End Sub

        Public Overridable Property UserName() As String
        Public Sub Login()
            Me.GetService(Of IMessageBoxService)().Show(&quot;Login succeeded&quot;, &quot;Login&quot;, MessageButton.OK, MessageIcon.Information, MessageResult.OK)
        End Sub
        Public Function CanLogin() As Boolean
            Return Not String.IsNullOrEmpty(UserName)
        End Function
    End Class
End Namespace
</code></pre></section>

## Bindable Properties

The POCO mechanism generates **bindable properties** for properties that meet **all** the following requirements:

1. The property is public and [auto-implemented](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties).
2. The property has the [virtual (C#)](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/virtual) or [Overridable (VB)](https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/modifiers/overridable) modifier.
3. The property has a public getter, and a protected or public setter.

    If the property has no setter, you can use the **RaisePropertyChanged** extension method to explicitly raise the **PropertyChanged** event for this property. Refer to the example below for more information on how to use the **RaisePropertyChanged** extension method to explicitly raise the **PropertyChanged** event:

    [View Example](https://github.com/DevExpress-Examples/wpf-mvvm-framework-use-poco-mechanism-to-implement-idataerrorinfo-interface)

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

- C#

<section id="tabpanel_dJle7Jat7y-5_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">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) {
            //...
        }
    }
</code></pre></section>

You can use the **BindableProperty** attribute to:

- prevent the POCO mechanism from generating a bindable property for a specified property;
- specify which method should be invoked when a property value is changing or has been changed. This is useful when the method’s name does not match the **On[PropertyName]Changed** and **On[PropertyName]Changing** convention.

- C#

<section id="tabpanel_dJle7Jat7y-6_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">public class LoginViewModel {
        [BindableProperty(isBindable: false)]
        public virtual bool IsEnabled { get; set; }

        [BindableProperty(OnPropertyChangedMethodName = &quot;Update&quot;)]
        public virtual string UserName { get; set; }
        protected void Update() {
            //...
        }
    }
</code></pre></section>

You can use the **Fluent API** to control POCO ViewModel generation. 

- C#

<section id="tabpanel_dJle7Jat7y-7_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">[MetadataType(typeof(Metadata))]
public class LoginViewModel {
    public class Metadata : IMetadataProvider&lt;LoginViewModel&gt; {
        void IMetadataProvider&lt;LoginViewModel&gt;.BuildMetadata
            (MetadataBuilder&lt;LoginViewModel&gt; builder) {

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

## Commands

The POCO mechanism generates commands for all public methods that have no parameters or a single parameter. A generated command’s name follows the **[MethodName]Command** pattern. You can use the **Command** attribute or the Fluent API to control the command generation mechanism.

- C#

<section id="tabpanel_dJle7Jat7y-8_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">public class LoginViewModel {
    [Command(isCommand: false)]
    public void SaveCore() {
        //...
    }

    [Command(CanExecuteMethodName = &quot;CanSaveAccountSettings&quot;,
        Name = &quot;SaveCommand&quot;,
        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&lt;LoginViewModel&gt; {
        void IMetadataProvider&lt;LoginViewModel&gt;.BuildMetadata(MetadataBuilder&lt;LoginViewModel&gt; builder) {
            builder.CommandFromMethod(x =&gt; x.SaveCore()).
                DoNotCreateCommand();
            builder.CommandFromMethod(x =&gt; x.SaveAccountSettings(default(string))).
                CanExecuteMethod(x =&gt; x.CanSaveAccountSettings(default(string))).
                CommandName(&quot;SaveCommand&quot;);
        }
    }
    public void SaveCore() {
        //...
    }
    public void SaveAccountSettings(string fileName) {
        //...
    }
    public bool CanSaveAccountSettings(string fileName) {
        return !string.IsNullOrEmpty(fileName);
    }
}
</code></pre></section>

To update an automatically generated command in a POCO View Model, use the **RaiseCanExecuteChanged** extension method available from the **DevExpress.Mvvm.POCO.POCOViewModelExtensions** class.

- C#

<section id="tabpanel_dJle7Jat7y-9_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">[POCOViewModel]
public class ViewModel {
    public void GoBack(){
        //...
    }
    public bool CanGoBack(){
        //...
    }
    public void UpdateSaveCommand(){
        this.RaiseCanExecuteChanged(c =&gt; c.GoBack());
    }
}
</code></pre></section>

Refer to the following topic for more information: [Commands](/WPF/17441/mvvm-framework/commands).

## Services

The DevExpress MVVM Framework includes the [Services](/WPF/17414/mvvm-framework/services) mechanism. The code sample below demonstrates how to access the Message Box service.

- C#

<section id="tabpanel_dJle7Jat7y-10_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">using DevExpress.Mvvm.POCO;
...
public class LoginViewModel {
    public IMessageBoxService MessageBoxService { get { return this.GetService&lt;IMessageBoxService&gt;(); } }
}
</code></pre></section>

Review the following topic for more information about how to access services: [Services in POCO objects](/WPF/17447/mvvm-framework/services/services-in-generated-view-models).

## Dependency Injection

To bind a view to a view model, create a MarkupExtension that resolves the correct ViewModel type:

- C#

<section id="tabpanel_dJle7Jat7y-11_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">public class DISource : MarkupExtension {
    public static Func&lt;Type, object, string, object&gt; Resolver { get; set; }

    public Type Type { get; set; }
    public object Key { get; set; }
    public string Name { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider) =&gt; Resolver?.Invoke(Type, Key, Name);
}
</code></pre></section>

Register the resolver at the application startup:

- C#

<section id="tabpanel_dJle7Jat7y-12_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">protected override void OnStartup(StartupEventArgs e) {
    base.OnStartup(e);
    DISource.Resolver = Resolve;
}
object Resolve(Type type, object key, string name) {
    if(type == null)
        return null;
    if(key != null)
        return Container.ResolveKeyed(key, type);
    if(name != null)
        return Container.ResolveNamed(name, type);
    return Container.Resolve(type);
}
</code></pre></section>

Specify the DataContext in XAML in the following manner:

- XAML

<section id="tabpanel_dJle7Jat7y-13_tabid-xaml" role="tabpanel" data-tab="tabid-xaml">
<pre><code class="lang-xaml">DataContext=&quot;{common:DISource Type=common:MainViewModel}&quot;
</code></pre></section>

To use a POCO View Model in a Dependency Injection container, utilize the **ViewModelSource.GetPOCOType** method to register the POCO type generated at runtime:

- C#

<section id="tabpanel_dJle7Jat7y-14_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">container.RegisterType(typeof(IMainViewModel),
                    ViewModelSource.GetPOCOType(typeof(MainViewModel)));
</code></pre></section>

[View Example](https://github.com/DevExpress-Examples/wpf-mvvm-framework-register-poco-type-in-dependency-injection-container)

## View Model Parent-Child Relationships

POCO View Models can relate 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 to set the parent-child relationship and its advantages: [ViewModel relationships (ISupportParentViewModel)](/WPF/17449/mvvm-framework/viewmodels/viewmodel-relationships-isupportparentviewmodel).

## Automatic IDataErrorInfo Implementation

The [IDataErrorInfo](https://learn.microsoft.com/dotnet/api/system.componentmodel.idataerrorinfo) interface is the standard mechanism for data validation in WPF. You can use this interface to define validation rules for each individual property or for the entire object. The POCO mechanism allows you to automatically implement the [IDataErrorInfo](https://learn.microsoft.com/dotnet/api/system.componentmodel.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**.

- C#

<section id="tabpanel_dJle7Jat7y-15_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">//Attribute-based approach
[POCOViewModel(ImplementIDataErrorInfo = true)] 
public class LoginViewModel { 
    [Required(ErrorMessage = &quot;Please enter the user name.&quot;)] 
    public virtual string UserName { get; set; }
}

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

When the **ViewModelSource** generates a descendant of a View Model, it implements the [IDataErrorInfo](https://learn.microsoft.com/dotnet/api/system.componentmodel.idataerrorinfo) interface as follows:

- C#

<section id="tabpanel_dJle7Jat7y-16_tabid-csharp" role="tabpanel" data-tab="tabid-csharp">
<pre><code class="lang-csharp">public class LoginViewModel : IDataErrorInfo { 
    ... 
    string IDataErrorInfo.Error { 
        get { return string.Empty; } 
    } 
    string IDataErrorInfo.this[string columnName] { 
        get { return IDataErrorInfoHelper.GetErrorText(this, columnName); } 
    } 
}
</code></pre></section>

The [IDataErrorInfoHelper](/CoreLibraries/DevExpress.Mvvm.IDataErrorInfoHelper) class allows you to get an error based on specified [DataAnnotation](/WPF/16863/mvvm-framework/data-annotation-attributes) attributes or Fluent API.

The code example below demonstrates how to use the POCO mechanism to implement the [IDataErrorInfo](https://learn.microsoft.com/dotnet/api/system.componentmodel.idataerrorinfo) interface.

[View Example](https://github.com/DevExpress-Examples/wpf-mvvm-framework-use-poco-mechanism-to-implement-idataerrorinfo-interface)

- MainView.xaml

<section id="tabpanel_-6JJOW6j54_tabid-xamlMainView-xaml" role="tabpanel" data-tab="tabid-xamlMainView-xaml">
<pre><code class="lang-xaml">&lt;UserControl x:Class=&quot;Example.View.MainView&quot;
    xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
    xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
    xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;
    xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot;
    xmlns:dx=&quot;http://schemas.devexpress.com/winfx/2008/xaml/core&quot;
    xmlns:dxe=&quot;http://schemas.devexpress.com/winfx/2008/xaml/editors&quot;
    xmlns:dxmvvm=&quot;http://schemas.devexpress.com/winfx/2008/xaml/mvvm&quot;
    xmlns:ViewModel=&quot;clr-namespace:Example.ViewModel&quot;
    mc:Ignorable=&quot;d&quot; d:DesignHeight=&quot;400&quot; d:DesignWidth=&quot;400&quot;
    DataContext=&quot;{dxmvvm:ViewModelSource Type=ViewModel:MainViewModel}&quot;&gt;
    &lt;UserControl.Resources&gt;
        &lt;dxmvvm:BooleanNegationConverter x:Key=&quot;BooleanNegationConverter&quot;/&gt;
    &lt;/UserControl.Resources&gt;

    &lt;Grid&gt;
        &lt;StackPanel Orientation=&quot;Vertical&quot; Margin=&quot;10&quot; dxe:ValidationService.IsValidationContainer=&quot;True&quot; x:Name=&quot;validationContainer&quot;&gt;
            &lt;Grid&gt;
                &lt;Grid.ColumnDefinitions&gt;
                    &lt;ColumnDefinition Width=&quot;*&quot;/&gt;
                    &lt;ColumnDefinition Width=&quot;*&quot;/&gt;
                &lt;/Grid.ColumnDefinitions&gt;
                &lt;StackPanel Orientation=&quot;Vertical&quot; Margin=&quot;0,0,4,6&quot;&gt;
                    &lt;TextBlock Text=&quot;Name&quot; Margin=&quot;6,2,0,2&quot;/&gt;
                    &lt;dxe:TextEdit NullText=&quot;First&quot; EditValue=&quot;{Binding FirstName, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
                &lt;/StackPanel&gt;
                &lt;StackPanel Orientation=&quot;Vertical&quot; Margin=&quot;4,0,0,6&quot; Grid.Column=&quot;1&quot;&gt;
                    &lt;TextBlock Text=&quot; &quot; Margin=&quot;6,2,0,2&quot;/&gt;
                    &lt;dxe:TextEdit NullText=&quot;Last&quot; EditValue=&quot;{Binding LastName, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
                &lt;/StackPanel&gt;
            &lt;/Grid&gt;
            &lt;StackPanel Orientation=&quot;Vertical&quot; Margin=&quot;0,0,0,6&quot;&gt;
                &lt;TextBlock Text=&quot;Email&quot; Margin=&quot;6,2,0,2&quot;/&gt;
                &lt;dxe:TextEdit EditValue=&quot;{Binding Email, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
            &lt;/StackPanel&gt;
            &lt;StackPanel Orientation=&quot;Vertical&quot; Margin=&quot;0,0,0,6&quot;&gt;
                &lt;TextBlock Text=&quot;Password&quot; Margin=&quot;6,2,0,2&quot;/&gt;
                &lt;dxe:PasswordBoxEdit EditValue=&quot;{Binding Password, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
            &lt;/StackPanel&gt;
            &lt;StackPanel Orientation=&quot;Vertical&quot; Margin=&quot;0,0,0,6&quot;&gt;
                &lt;TextBlock Text=&quot;Confirm Password&quot; Margin=&quot;6,2,0,2&quot;/&gt;
                &lt;dxe:PasswordBoxEdit EditValue=&quot;{Binding ConfirmPassword, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}&quot;/&gt;
            &lt;/StackPanel&gt;

            &lt;Button VerticalAlignment=&quot;Top&quot; Content=&quot;Sign Up&quot; Width=&quot;150&quot; HorizontalAlignment=&quot;Right&quot; Margin=&quot;0,10&quot;
                IsEnabled=&quot;{Binding Path=(dxe:ValidationService.HasValidationError), ElementName=validationContainer, Converter={StaticResource BooleanNegationConverter}}&quot;/&gt;
        &lt;/StackPanel&gt;
    &lt;/Grid&gt;
&lt;/UserControl&gt;
</code></pre></section>

- MainViewModel.cs
- MainViewModel.vb

<section id="tabpanel_-6JJOW6j54-1_tabid-csharpMainViewModel-cs" role="tabpanel" data-tab="tabid-csharpMainViewModel-cs">
<pre><code data-code-links="{&quot;/ (DevExpress.Mvvm)(?:;|$)/&quot;:&quot;/CoreLibraries/DevExpress.Mvvm&quot;,&quot;/ (DevExpress.Mvvm.DataAnnotations)(?:;|$)/&quot;:&quot;/CoreLibraries/DevExpress.Mvvm.DataAnnotations&quot;,&quot;/ (System.Windows.Media)(?:;|$)/&quot;:&quot;https://learn.microsoft.com/dotnet/api/system.windows.media&quot;}" class="lang-csharp">using DevExpress.Mvvm;
using DevExpress.Mvvm.DataAnnotations;
using System.Windows.Media;

namespace Example.ViewModel {
    [POCOViewModel(ImplementIDataErrorInfo = true)]
    public class MainViewModel : ViewModelBase {
        static PropertyMetadataBuilder&lt;MainViewModel, string&gt; AddPasswordCheck(PropertyMetadataBuilder&lt;MainViewModel, string&gt; builder) {
            return builder.MatchesInstanceRule((name, vm) =&gt; vm.Password == vm.ConfirmPassword, () =&gt; &quot;The passwords don&#39;t match.&quot;)
                .MinLength(8, () =&gt; &quot;The password must be at least 8 characters long.&quot;)
                .MaxLength(20, () =&gt; &quot;The password must not exceed the length of 20.&quot;);
        }
        public static void BuildMetadata(MetadataBuilder&lt;MainViewModel&gt; builder) {
            builder.Property(x =&gt; x.FirstName)
                .Required(() =&gt; &quot;Please enter the first name.&quot;);
            builder.Property(x =&gt; x.LastName)
                .Required(() =&gt; &quot;Please enter the last name.&quot;);
            builder.Property(x =&gt; x.Email)
                .EmailAddressDataType(() =&gt; &quot;Please enter a correct email address.&quot;);
            AddPasswordCheck(builder.Property(x =&gt; x.Password))
                .Required(() =&gt; &quot;Please enter the password.&quot;);
            AddPasswordCheck(builder.Property(x =&gt; x.ConfirmPassword))
                .Required(() =&gt; &quot;Please confirm the password.&quot;);
        }
        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(() =&gt; ConfirmPassword);
        }
        public void OnConfirmPasswordChanged() {
            this.RaisePropertyChanged(() =&gt; Password);
        }
    }
}
</code></pre></section>
<section id="tabpanel_-6JJOW6j54-1_tabid-vbMainViewModel-vb" role="tabpanel" data-tab="tabid-vbMainViewModel-vb" aria-hidden="true" hidden="hidden">
<pre><code data-code-links="{&quot;/ (DevExpress.Mvvm)(?:;|$)/&quot;:&quot;/CoreLibraries/DevExpress.Mvvm&quot;,&quot;/ (DevExpress.Mvvm.DataAnnotations)(?:;|$)/&quot;:&quot;/CoreLibraries/DevExpress.Mvvm.DataAnnotations&quot;,&quot;/ (System.Windows.Media)(?:;|$)/&quot;:&quot;https://learn.microsoft.com/dotnet/api/system.windows.media&quot;}" class="lang-vb">Imports DevExpress.Mvvm
Imports DevExpress.Mvvm.DataAnnotations
Imports System.Windows.Media

Namespace Example.ViewModel
    &lt;POCOViewModel(ImplementIDataErrorInfo := True)&gt; _
    Public Class MainViewModel
        Inherits ViewModelBase

        Private Shared Function AddPasswordCheck(ByVal builder As PropertyMetadataBuilder(Of MainViewModel, String)) As PropertyMetadataBuilder(Of MainViewModel, String)
            Return builder.MatchesInstanceRule(Function(name, vm) vm.Password = vm.ConfirmPassword, Function() &quot;The passwords don&#39;t match.&quot;).MinLength(8, Function() &quot;The password must be at least 8 characters long.&quot;).MaxLength(20, Function() &quot;The password must not exceed the length of 20.&quot;)
        End Function
        Public Shared Sub BuildMetadata(ByVal builder As MetadataBuilder(Of MainViewModel))
            builder.Property(Function(x) x.FirstName).Required(Function() &quot;Please enter the first name.&quot;)
            builder.Property(Function(x) x.LastName).Required(Function() &quot;Please enter the last name.&quot;)
            builder.Property(Function(x) x.Email).EmailAddressDataType(Function() &quot;Please enter a correct email address.&quot;)
            AddPasswordCheck(builder.Property(Function(x) x.Password)).Required(Function() &quot;Please enter the password.&quot;)
            AddPasswordCheck(builder.Property(Function(x) x.ConfirmPassword)).Required(Function() &quot;Please confirm the password.&quot;)
        End Sub
        Public Overridable Property FirstName() As String
        Public Overridable Property LastName() As String
        Public Overridable Property Email() As String
        Public Overridable Property Password() As String
        Public Overridable Property ConfirmPassword() As String
        Public Sub OnPasswordChanged()
            Me.RaisePropertyChanged(Function() ConfirmPassword)
        End Sub
        Public Sub OnConfirmPasswordChanged()
            Me.RaisePropertyChanged(Function() Password)
        End Sub
    End Class
End Namespace
</code></pre></section>

If you need to extend the default [IDataErrorInfo](https://learn.microsoft.com/dotnet/api/system.componentmodel.idataerrorinfo) implementation, you can manually implement the [IDataErrorInfo](https://learn.microsoft.com/dotnet/api/system.componentmodel.idataerrorinfo) interface and use the [IDataErrorInfoHelper](/CoreLibraries/DevExpress.Mvvm.IDataErrorInfoHelper) class.