Skip to main content
A newer version of this page is available. .
All docs
V22.1

View Models Generated at Compile Time

  • 18 minutes to read

The DevExpress MVVM Framework includes a source generator that produces boilerplate code for your View Models at compile time. You need to define a stub View Model class that specifies the required logic. Our MVVM Framework analyzes your implementation and applied attributes to generate the final View Model class with all required boilerplate code. Consider the following example.

View Example: View Models Generated at Compile Time

Base View Model

Create a partial class. Add attributes to the class and its fields/methods:

using DevExpress.Mvvm.CodeGenerators;

[GenerateViewModel]
partial class ViewModel {
    [GenerateProperty]
    string username;
    [GenerateProperty]
    string status;

    [GenerateCommand]
    void Login() => Status = "User: " + Username;
    bool CanLogin() => !string.IsNullOrEmpty(Username);
}
Generated View Model

The generator inspects the base View Model and produces a partial class that complements your implementation with the following boilerplate code:

You can view and debug the generated View Model:

partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    public string? Status {
        get => status;
        set {
            if(EqualityComparer<string?>.Default.Equals(status, value)) return;
            status = value;
            RaisePropertyChanged(StatusChangedEventArgs);
        }
    }

    DelegateCommand? loginCommand;
    public DelegateCommand LoginCommand {
        get => loginCommand ??= new DelegateCommand(Login, CanLogin, true);
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
    static PropertyChangedEventArgs StatusChangedEventArgs = new PropertyChangedEventArgs(nameof(Status));
}

Prerequisites

Your project should meet the following requirements:

  • C# 9+ (VB is not supported)
  • .NET Framework v4.6.1+ or .NET Core v3.0+ (.NET 5 and newer is recommended)
  • Visual Studio v16.9.0+

Otherwise, use Runtime-generated POCO View Models instead.

Note

C# 9 is officially supported in .NET 5 and newer. You may encounter issues when you use earlier versions of .NET and .NET Framework.

Prepare Your Project

Prepare your project as outlined below to enable support for View Models generated at compile time:

  1. Add a reference to DevExpress.Mvvm.v21.1+ or install the DevExpress.Mvvm NuGet package.
  2. Install the DevExpress.Mvvm.CodeGenerators NuGet package in your project.
  3. Set the language version to 9 in the .csproject file:

    <PropertyGroup>
        <LangVersion>9</LangVersion>
    </PropertyGroup>
    

    For .NET Core projects, set the IncludePackageReferencesDuringMarkupCompilation property to true:

    <PropertyGroup>
        <IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
    </PropertyGroup>
    

NuGet Package Installation Notes

We recommend that you configure the packages.config file to install NuGet Packages. If you use PackageReference, follow the steps below to include the code generator in a .NET Framework project:

  1. Open the project file.
  2. Remove the XML code that adds the package:
    <PackageReference Include="DevExpress.Mvvm.CodeGenerators">
        <Version>XX.Y.Z</Version>
    </PackageReference>
    
  3. Download the DevExpress.Mvvm.CodeGenerators.XX.Y.Z.dll file from GitHub Releases to the preferred folder.
  4. Specify the path to the analyzer:

    <ItemGroup>
        <Analyzer Include="[PATH_TO_YOUR_FOLDER]\DevExpress.Mvvm.CodeGenerators.XX.Y.Z.dll" />
    </ItemGroup>
    
  5. Save changes and reload the project.

Review Generated View Model Class

You can access the generated code only from Visual Studio. Use the Peek Definition command (F12) or search the generated file under Dependencies in Solution Explorer.

Apply Attributes to the Base View Model Class

Declare a namespace as follows to access attributes:

using DevExpress.Mvvm.CodeGenerators;

GenerateViewModel

Applies to a class. Indicates that the source generator should process this class and produce View Model boilerplate code.

Property Type Description
ImplementINotifyPropertyChanging bool Implements INotifyPropertyChanging.
ImplementIDataErrorInfo bool Implements IDataErrorInfo that allows you to validate data.
ImplementISupportServices bool Implements ISupportServices that allows you to include the Services mechanism to your View Model.
ISupportParentViewModel bool Implements ISupportParentViewModel that allows you to establish a parent-child relationship between View Models.
GenerateProperty

Applies to a field. The source generator produces boilerplate code for the property getter and setter based on the field declaration.

Property Type Description
IsVirtual bool Assigns a virtual modifier to the property.
OnChangedMethod string? Specifies the name of the method invoked after the property value is changed. If the property is not specified, the method’s name should follow the On[PropertyName]Changed pattern.
OnChangingMethod string? Specifies the name of the method invoked when the property value is changing. If the property is not specified, the method’s name should follow the On[PropertyName]Changing pattern.
SetterAccessModifier AccessModifier Specifies an access modifier for a set accessor. The default value is the same as a property’s modifier. Available values: Public, Private, Protected, Internal, ProtectedInternal.
GenerateCommand

Applies to a method. The source generator produces boilerplate code for a Command based on this method.

Property Type Description
AllowMultipleExecution bool Specifies the allowMultipleExecution parameter in the AsyncCommand constructor. The default value is false.
UseCommandManager bool Specifies the useCommandManager parameter in the Command constructor. The default value is true.
CanExecuteMethod string? Specifies a custom CanExecute method name. If the property is not specified, the method’s name should follow the Can[ActionName] pattern.
Name string? Specifies a custom Command name. The default value is [ActionName]Command.

Implement Interfaces

All View Models generated at compile time implement the INotifyPropertyChanged interface:

[GenerateViewModel]
public partial class ViewModel {
    [GenerateProperty]
    string username;
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
}

If you implement an interface in a base View Model class, add the interface’s members to this class.

In the code sample below, the base View Model class implements INotifyPropertyChanged. The generator analyzes the implementation and searches the PropertyChanged event to raise it from the generated View Model. If the base View Model class does not include the PropertyChanged event, the generated class is empty.

[GenerateViewModel]
public partial class ViewModel : INotifyPropertyChanged {
    [GenerateProperty]
    string username;
    public event PropertyChangedEventHandler PropertyChanged;
}
partial class ViewModel { 
    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    public string? UserName {
        get => userName;
        set {
            if(EqualityComparer<string?>.Default.Equals(userName, value)) return;
            userName = value;
            RaisePropertyChanged(UserNameChangedEventArgs);
        }
    }
}

If you want to implement INotifyPropertyChanging, IDataErrorInfo, or ISupportServices, use the GenerateViewModel attribute’s properties.

Implement INotifyPropertyChanging

Set the ImplementINotifyPropertyChanging property to true:

[GenerateViewModel(ImplementINotifyPropertyChanging = true)]
public partial class ViewModel {
    [GenerateProperty]
    string username;
}
partial class ViewModel : INotifyPropertyChanged, INotifyPropertyChanging {
    public event PropertyChangedEventHandler? PropertyChanged;
    public event PropertyChangingEventHandler? PropertyChanging;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
    protected void RaisePropertyChanging(PropertyChangingEventArgs e) => PropertyChanging?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            RaisePropertyChanging(UsernameChangingEventArgs);
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
    static PropertyChangingEventArgs UsernameChangingEventArgs = new PropertyChangingEventArgs(nameof(Username));
}

Implement IDataErrorInfo

Set the ImplementIDataErrorInfo property to true:

[GenerateViewModel(ImplementIDataErrorInfo = true)]
public partial class ViewModel {    
} 

Generated View Models implement the IDataErrorInfo interface as follows:

partial class ViewModel : INotifyPropertyChanged, IDataErrorInfo {
    public event PropertyChangedEventHandler? PropertyChanged;
    string IDataErrorInfo.Error { get => string.Empty; }
    string IDataErrorInfo.this[string columnName] { get => IDataErrorInfoHelper.GetErrorText(this, columnName); }

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
}

The IDataErrorInfoHelper class allows you to get an error based on specified DataAnnotation attributes.

Implement ISupportServices

Set the ImplementISupportServices property to true:

[GenerateViewModel(ImplementISupportServices = true)]
    public partial class ViewModel {                 
} 

Refer to the Services in Custom ViewModels topic for more information on how to use the Service mechanism in a View Model.

partial class ViewModel : INotifyPropertyChanged, ISupportServices {
    public event PropertyChangedEventHandler? PropertyChanged;
    IServiceContainer? serviceContainer;
    protected IServiceContainer ServiceContainer { get => serviceContainer ??= new ServiceContainer(this); }
    IServiceContainer ISupportServices.ServiceContainer { get => ServiceContainer; }
    protected T? GetService<T>() where T : class => ServiceContainer.GetService<T>();
    protected T GetRequiredService<T>() where T : class => ServiceContainer.GetRequiredService<T>();

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
    ...
}

Interface Implementation Notes for Inherited View Model Classes

Your View Model can inherit from another class. In this case, implementations declared in a parent class might affect a generated View Model.

If the parent class implements INotifyPropertyChanged or INotifyPropertyChanging, the child class cannot raise the PropertyChanged nor PropertyChanging events. Ensure that the parent class includes the RaisePropertyChanged or RaisePropertyChanging methods that allow the child class to raise the events:

public class DataObjectBase : INotifyPropertyChanged, INotifyPropertyChanging {
    public event PropertyChangedEventHandler PropertyChanged;
    public event PropertyChangingEventHandler PropertyChanging;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
    // or protected void RaisePropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    protected void RaisePropertyChanging(string propertyName) => PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
    // or protected void RaisePropertyChanging(PropertyChangingEventArgs e) => PropertyChanging?.Invoke(this, e);
}

[GenerateViewModel]
partial class ViewModel : DataObjectBase {
    [GenerateProperty]
    string username;
} 
partial class ViewModel {
    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            RaisePropertyChanging(nameof(Username));
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
}

View Model Parent-Child Relationships

Set the ImplementISupportParentViewModel property to true to establish a parent-child relationship between View Models. The ISupportParentViewModel interface allows you to access Services registered in the main View Model from the child View Models. You can override the OnParentViewModelChanged event in a base View Model. If a base View Model class is inherited from another class, you can override the OnParentViewModelChanged event in the ancestor.

Refer to the following topic for more information on the parent-child relationship: ViewModel relationships (ISupportParentViewModel).

Declare Fields to Generate Properties

To generate a property in your View Model, create a field in a base View Model and add the GenerateProperty attribute to the field. The field name should not start with a capital letter:

[GenerateViewModel]
public partial class ViewModel {       
    [GenerateProperty]
    string username;
}

As a result, the generated View Model includes a property, a RaisePropertyChanged method, and PropertyChangedEventArgs for the generated property:

partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
}  

If your field includes additional attributes, the generated property copies them:

[GenerateViewModel]
partial class ViewModel {
    [GenerateProperty]
    [StringLength(100, MinimumLength = 5)]
    string username;
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    [System.ComponentModel.DataAnnotations.StringLengthAttribute(100, MinimumLength = 5)]
    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
}

Property Change Notifications

You can define methods invoked when properties are changed. These methods should meet the following requirements:

  • Their names follow the On[PropertyName]Changed and On[PropertyName]Changing pattern.
  • They return void.
  • They have no parameters or a parameter of the same type as the property.
[GenerateViewModel(ImplementINotifyPropertyChanging = true)]
partial class ViewModel {
    [GenerateProperty]
    string username;

    // void OnUsernameChanged() { }
    void OnUsernameChanged(string oldUsername) {
        //...
    }
    // void OnUsernameChanging() { }
    void OnUsernameChanging(string newUsername) {
        //...
    }
}
partial class ViewModel : INotifyPropertyChanged, INotifyPropertyChanging {
    public event PropertyChangedEventHandler? PropertyChanged;
    public event PropertyChangingEventHandler? PropertyChanging;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);
    protected void RaisePropertyChanging(PropertyChangingEventArgs e) => PropertyChanging?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            RaisePropertyChanging(UsernameChangingEventArgs);
            OnUsernameChanging(value);
            var oldValue = username;
            username = value;
            RaisePropertyChanged(UsernameChangedEventArgs);
            OnUsernameChanged(oldValue);
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
    static PropertyChangingEventArgs UsernameChangingEventArgs = new PropertyChangingEventArgs(nameof(Username));
}

If you want to specify a custom name, specify the OnChangedMethod or OnChangingMethod attribute property as follows:

[GenerateViewModel]
    public partial class ViewModel {
        [GenerateProperty(OnChangedMethod = nameof(CustomName))]
        string username;

        void CustomName() { }
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    public string? Username {
        get => username;
        set {
            if(EqualityComparer<string?>.Default.Equals(username, value)) return;
            username = value;
            RaisePropertyChanged(UserNameChangedEventArgs);
            CustomName();
        }
    }

    static PropertyChangedEventArgs UsernameChangedEventArgs = new PropertyChangedEventArgs(nameof(Username));
}

Turn Methods into Commands

To generate a Command in your View Model, create a method in a base View Model and add the GenerateCommand attribute. The method should return void if you create DelegateCommands.

[GenerateViewModel]
public partial class ViewModel {
    [GenerateCommand]
    void Save() {
        //...
    }
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    DelegateCommand? saveCommand;
    public DelegateCommand SaveCommand {
        get => saveCommand ??= new DelegateCommand(Save, null, true);
    }
}

Create a CanExecute method if necessary. This method name should complete the Can[ActionName] pattern and have the same parameters as the Action method. If you require a custom name, specify the CanExecuteMethod attribute property.

If the source generator cannot find a method that matches the description, it passes null as the Command constructor’s canExecuteMethod parameter.

[GenerateViewModel]
public partial class ViewModel {
    [GenerateCommand]
    void Login(string parameter) {
        //...
    }
    bool CanLogin(string parameter) {
        //...
    }
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    DelegateCommand? loginCommand;
    public DelegateCommand LoginCommand {
        get => loginCommand ??= new DelegateCommand(Login, CanLogin, true);
    }
}

Use the attribute properties to specify the Command constructor parameters. The code sample below specifies a custom name for the Command and sets the UseCommandManager parameter to false. If you disable UseCommandManager, add the RaiseCanExecuteChanged method to the base View Model as follows:

[GenerateViewModel]
public partial class ViewModel {
    [GenerateCommand(UseCommandManager = false, Name = "CustomName")]
    void Show() {
        //...
    }
    bool CanShow() {
        //...
    }
    public void UpdateShowCommand() => CustomName.RaiseCanExecuteChanged();
} 
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    DelegateCommand? customName;
    public DelegateCommand CustomName {
        get => customName ??= new DelegateCommand(Show, CanShow, false);
    }
}

Asynchronous Commands

Declare a method that returns a Task to create an Asynchronous Command. Apply the GenerateCommand attribute.

[GenerateViewModel]
partial class ViewModel {
    [GenerateCommand]
    async Task CalculateAsync() {
        //...
    }
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    AsyncCommand? calculateAsyncCommand;
    public AsyncCommand CalculateAsyncCommand {
        get => calculateAsyncCommand ??= new AsyncCommand(CalculateAsync, null, false, true);
    }
}

Use the attribute properties to specify the Command constructor parameters. The code sample below sets the AllowMultipleExecution parameter to true.

[GenerateViewModel]
partial class ViewModel {
    [GenerateCommand (AllowMultipleExecution = true)]
    async Task CalculateAsync() {
        //...
    }
}
partial class ViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void RaisePropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e);

    AsyncCommand? calculateAsyncCommand;
    public AsyncCommand CalculateAsyncCommand {
        get => calculateAsyncCommand ??= new AsyncCommand(CalculateAsync, null, true, true);
    }
}

Add XML Comments

You can add XML comments to a field and method. The generated property or command copies these comments. The following members are not supported:

  • PropertyChanged events
  • PropertyChanging events
  • RaisePropertyChanged methods
  • RaisePropertyChanging methods
  • ParentViewModels
  • ServiceContainers

If you want to add comments to these members, declare them in a base View Model.

Add Additional Attributes

You can apply additional attributes to fields and methods. The generated properties and commands copy them. If you want to apply an attribute to the command, do not restrict the attribute usage or specify it as follows:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)]
public sealed class AttributeClassName : Attribute {
    // ...
}

You can also inherit your attribute class from another class. The child class copies the attribute usage from the parent class if AttributeUsageAttribute.Inherited is set to true.

Third-party Library Support

The View Model code generator supports third-party libraries. To use a third-party library, apply attributes defined in the corresponding namespace to a base View Model class. You can refer to only one library within your class.

Prism Library

Install the Prism.Wpf NuGet package to use the Prism Library.

View Example: WPF - View Model Code Generator for PRISM

Declare a namespace as follows to access attributes:

using DevExpress.Mvvm.CodeGenerators.Prism;

GenerateViewModel

Applies to a class. Indicates that the source generator should process this class and produce View Model boilerplate code.

Property Type Description
ImplementINotifyPropertyChanging bool Implements INotifyPropertyChanging.
ImplementIActiveAware bool Implements IActiveAware that notifies you when the View becomes active or inactive.
GenerateProperty

Applies to a field. The source generator produces boilerplate code for the property getter and setter based on the field declaration.

Property Type Description
IsVirtual bool Assigns a virtual modifier to the property.
OnChangedMethod string? Specifies the name of the method invoked after the property value is changed. If the property is not specified, the method’s name should follow the On[PropertyName]Changed pattern.
OnChangingMethod string? Specifies the name of the method invoked when the property value is changing. If the property is not specified, the method’s name should follow the On[PropertyName]Changing pattern.
SetterAccessModifier AccessModifier Specifies an access modifier for a set accessor. The default value is the same as a property’s modifier. Available values: Public, Private, Protected, Internal, ProtectedInternal.
GenerateCommand

Applies to a method. The source generator produces boilerplate code for a Command based on this method.

Property Type Description
ObservesCanExecuteProperty string? Specifies the ObservesCanExecute method for the supplied property. This method listens to property changes and uses the property as the CanExecute delegate.
ObservesProperties string[]? Specifies the ObservesProperty methods for all supplied properties. Each method calls the CanExecute method when the corresponding property value changes.
CanExecuteMethod string? Specifies a custom CanExecute method name. If the property is not specified, the method’s name should follow the Can[ActionName] pattern.
Name string? Specifies a custom Command name. The default value is [ActionName]Command.

Asynchronous Commands

Declare a method that returns a Task to create an asynchronous command. Apply the GenerateCommand attribute.

[GenerateViewModel]
partial class ViewModel {
    [GenerateCommand]
    public Task WithNoArg() => Task.CompletedTask;

    [GenerateCommand]
    public Task WithArg(int? arg) => Task.CompletedTask;
}
partial class ViewModel : INotifyPropertyChanged {
    DelegateCommand? withNoArgCommand;
    public DelegateCommand WithNoArgCommand => withNoArgCommand ??= new DelegateCommand(async () => await WithNoArg());

    DelegateCommand<int?>? withArgCommand;
    public DelegateCommand<int?> WithArgCommand => withArgCommand ??= new DelegateCommand<int?>(async (arg) => await WithArg(arg));
}

IActiveAware Implementation Notes

You can define the method that is invoked when the IsActive property is changed. This method should meet the following requirements:

  • The method has the OnIsActiveChanged name.
  • It returns void.
  • It has no parameters.

The generator analyzes the implementation and searches OnIsActiveChanged() to raise it from the generated View Model:

[GenerateViewModel(ImplementIActiveAware = true)]
partial class ViewModel {
    // ...
    void OnIsActiveChanged() {
        // ...
    }
}
partial class ViewModel : INotifyPropertyChanged, IActiveAware {
    // ...
    bool isActive;
    public bool IsActive {
        get => isActive;
        set {
            isActive = value;
            OnIsActiveChanged();
            IsActiveChanged?.Invoke(this, EventArgs.Empty);
        }
    }
    public event EventHandler? IsActiveChanged;
    // ...
}

MVVM Light Toolkit

Tip

If you work with the MVVM Toolkit (the official replacement for the MVVM Light Toolkit), you can use its built-in source generators that were shipped in v8.0.0: MVVM Toolkit source generators.

Install the MvvmLight NuGet package to use the MVVM Light Toolkit.

View Example: WPF - View Model Code Generator for MVVM Light

Declare a namespace as follows to access attributes:

using DevExpress.Mvvm.CodeGenerators.MvvmLight;

GenerateViewModel

Applies to a class. Indicates that the source generator should process this class and produce View Model boilerplate code.

Property Type Description
ImplementINotifyPropertyChanging bool Implements INotifyPropertyChanging.
ImplementICleanup bool Implements the ICleanup interface that allows you to clean your View Model (for example, flush its state to persistent storage, close the stream).
GenerateProperty

Applies to a field. The source generator produces boilerplate code for the property getter and setter based on the field declaration.

Property Type Description
IsVirtual bool Assigns a virtual modifier to the property.
OnChangedMethod string? Specifies the name of the method invoked after the property value is changed. If the property is not specified, the method’s name should follow the On[PropertyName]Changed pattern.
OnChangingMethod string? Specifies the name of the method invoked when the property value is changing. If the property is not specified, the method’s name should follow the On[PropertyName]Changing pattern.
SetterAccessModifier AccessModifier Specifies an access modifier for a set accessor. The default value is the same as a property’s modifier. Available values: Public, Private, Protected, Internal, ProtectedInternal.
GenerateCommand

Applies to a method. The source generator produces boilerplate code for a Command based on this method.

Property Type Description
CanExecuteMethod string? Specifies a custom CanExecute method name. If the property is not specified, the method’s name should follow the Can[ActionName] pattern.
Name string? Specifies a custom Command name. The default value is [ActionName]Command.

Asynchronous Commands

Declare a method that returns a Task to create an asynchronous command. Apply the GenerateCommand attribute.

[GenerateViewModel]
partial class ViewModel {
    [GenerateCommand]
    public Task WithNoArg() => Task.CompletedTask;

    [GenerateCommand]
    public Task WithArg(int arg) => Task.CompletedTask;
}
partial class ViewModel : INotifyPropertyChanged {
    RelayCommand? withNoArgCommand;
    public RelayCommand WithNoArgCommand => withNoArgCommand ??= new RelayCommand(async () => await WithNoArg(), null);

    RelayCommand<int>? withArgCommand;
    public RelayCommand<int> WithArgCommand => withArgCommand ??= new RelayCommand<int>(async (arg) => await WithArg(arg), null);
}

ICleanup Implementation Notes

You can define the method that is invoked when the View Model is cleaned up. This method should meet the following requirements:

  • The method has the OnCleanup name.
  • It returns void.
  • It has no parameters.

The generator analyzes the implementation and searches OnCleanup() to raise it from the generated View Model:

[GenerateViewModel(ImplementICleanup = true)]
partial class ViewModel {
    // ...
    void OnCleanup() {
        // ...
    }
}
partial class ViewModel : INotifyPropertyChanged, ICleanup {
    // ...
    public virtual void Cleanup() {
        MessengerInstance.Unregister(this);
        OnCleanup();
    }
    // ...
}