Conventions and Attributes

  • 7 minutes to read

The MVVM Framework processes your application code and interprets specific code snippets in its own way. For instance, a property can be perceived as bindable if its syntax is correct. These syntax rules are called conventions. Conventions allow you to avoid writing extra code, since you know that the Framework will "understand" what you expect from it and automatically generate everything needed. This document gathers all MVVM Framework conventions that you need to be aware of when building an MVVM application.

Bindable Properties

All public auto-implemented virtual properties are treated as bindable.


public virtual string Test { get; set; }

To suppress bindable property generation for such properties, use the Bindable attribute as follows.


[Bindable(false)]
public virtual string Test { get; set; }

Properties with a backing field are ignored by the Framework. You can explicitly mark such properties with the BindableProperty attribute to still be able to use them for data binding.


using DevExpress.Mvvm.DataAnnotations;
//. . .
string test;
[BindableProperty]
public virtual string Test
{
    get { return test; }
    set { test = value; }
}

Property Dependencies

Properties can change their values as the application runs. To keep track of these changes and respond to them, declare property dependencies. A property dependency is a method that is automatically executed when its related property changes or is about to be changed. To implement this behavior, the method must be called On<Related_Property_Name>Changing or On<Related_Property_Name>Changed.


public virtual string Test { get; set; }
protected void OnTestChanged() {
    //do something
}

The On...Changed and On..Changing methods can also have one argument. In this case, the argument will receive an old or new property value respectively.


public virtual string Test { get; set; }
protected void OnTestChanging(string newValue) {
    //do something
}
protected void OnTestChanged(string oldValue) {
    //do something
}

The BindableProperty attribute allows you to use methods with different names as well.


[BindableProperty(OnPropertyChangingMethodName = "BeforeChange", OnPropertyChangedMethodName = "AfterChange")]
public virtual string Test { get; set; }

protected void BeforeChange() {
    //. . .
}
protected void AfterChange() {
    //. . .
}

Commands

All public void methods with zero or one parameter, declared in POCO ViewModels, are treated as commands.


public void DoSomething(object p) {
    MessageBox.Show(string.Format("The parameter passed to command is {0}.", p));
}

Methods whose names end with ...Command raise an exception. You can force the Framework to treat such methods as valid commands by marking them with the Command attribute and setting a proper name through the Name parameter.


using DevExpress.Mvvm.DataAnnotations;
[Command(Name="DoSomething")]
public void DoSomethingCommand(object p) {
    //do something
}

For each command method, the Framework generates a corresponding backing property. This property is by default, named after the related method plus the "Command" suffix. You can reserve another name for this auto-generated backing property by using the Name parameter of the Command attribute.


[Command(Name = "MyMvvmCommand")]
public void DoSomething(object p) {
    //do something
}

Commands can be accompanied by CanExecute clauses - boolean methods that allow their related commands to be executed only if true is returned. Such methods must be called Can<Related_Command_Name>.


//this command will be executed only if "p" equals 4
public void DoSomething(int p) {
    MessageBox.Show(string.Format("The parameter passed to command is {0}.", p));
}
public bool CanDoSomething(int p) {
    return (2 + 2) == p;
}

CanExecute methods with other names can still be bound to commands by using the CanExecuteMethodName parameter of the Command attribute.


[Command(CanExecuteMethodName = "DoSomethingCriteria")]
public void DoSomething(int p) {
    MessageBox.Show(string.Format("The parameter passed to command is {0}.", p));
}
public bool DoSomethingCriteria(int p) {
    return (2 + 2) == p;
}

CanExecute clauses are first checked when the command has just been bound to its target (to get the target's initial state). Later on, this criteria is recalculated each time the command's target is notified by the CanExecuteChanged event about command state changes. This event is declared at the underlying command-object level. To send such notification from the ViewModel level, call the RaiseCanExecuteChanged extension method as follows.


//a bindable property
public virtual bool IsModified { get; protected set; }

//a command
public void Save() {
    //. . .
}

//a CanExecute condition
public bool CanSave() {
    return IsModified;
}

//the OnChanged method calls the RaiseCanExecuteChanged method for the "Save" command
//this forces the command to update its CanExecute condition
public void OnIsModifiedChanged() {
    this.RaiseCanExecuteChanged(x=>x.Save());
}

Services

To resolve services, the Framework overrides virtual properties of an interface type. These properties' names must end with ...Service.


public virtual IMyNotificationService MyService {
    get { throw new NotImplementedException(); }
}

public virtual IMyNotificationService AnotherService {
    get { throw new NotImplementedException(); }
}

You can also explicitly mark service properties with other names by using the ServiceProperty attribute.


using DevExpress.Mvvm.DataAnnotations;
//. . .
[ServiceProperty]
public virtual IMyNotificationService MyProvider {
    get { throw new NotImplementedException(); }
}

When the Framework overrides a service property, it generates the corresponding GetService<> extension method call. The ServiceProperty attribute allows you to specify additional parameters for this method (e.g., a service key).


[ServiceProperty(Key="Service1")]
public virtual IMyNotificationService Service {
    get { throw new NotImplementedException(); }
}

[ServiceProperty(Key = "Service2")]
public virtual IMyNotificationService AnotherService {
    get { throw new NotImplementedException(); }
}