Back to Devexpress

Conventions and Attributes

windowsforms-117014-cross-platform-app-development-winforms-mvvm-concepts-conventions-and-attributes.md

latest12.4 KB
Original Source

Conventions and Attributes

  • Dec 09, 2024
  • 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.

csharp
public virtual string Test { get; set; }
vb
Public Overridable Property Test() As String

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

csharp
[Bindable(false)]
public virtual string Test { get; set; }
vb
<Bindable(False)>
Public Overridable Property Test() As String

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.

csharp
using DevExpress.Mvvm.DataAnnotations;
//. . .
string test;
[BindableProperty]
public virtual string Test
{
    get { return test; }
    set { test = value; }
}
vb
Imports DevExpress.Mvvm.DataAnnotations
'. . .
Private testField As String
<BindableProperty>
Public Overridable Property Test() As String
    Get
        Return testField
    End Get
    Set(ByVal value As String)
        testField = value
    End Set
End Property

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.

csharp
public virtual string Test { get; set; }
protected void OnTestChanged() {
    //do something
}
vb
Public Overridable Property Test() As String
Protected Sub OnTestChanged()
    'do something
End Sub

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.

csharp
public virtual string Test { get; set; }
protected void OnTestChanging(string newValue) {
    //do something
}
protected void OnTestChanged(string oldValue) {
    //do something
}
vb
Public Overridable Property Test() As String
Protected Sub OnTestChanging(ByVal newValue As String)
    'do something
End Sub
Protected Sub OnTestChanged(ByVal oldValue As String)
    'do something
End Sub

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

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

protected void BeforeChange() {
    //. . .
}
protected void AfterChange() {
    //. . .
}
vb
<BindableProperty(OnPropertyChangingMethodName := "BeforeChange", OnPropertyChangedMethodName := "AfterChange")>
Public Overridable Property Test() As String

Protected Sub BeforeChange()
    '. . .
End Sub
Protected Sub AfterChange()
    '. . .
End Sub

Commands

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

csharp
public void DoSomething(object p) {
    MessageBox.Show(string.Format("The parameter passed to command is {0}.", p));
}
vb
Public Sub DoSomething(ByVal p As Object)
    MessageBox.Show(String.Format("The parameter passed to command is {0}.", p))
End Sub

You can decorate a void method with the Command(false) attribute to tell the Framework this method is not a valid MVVM command.

csharp
using DevExpress.Mvvm.DataAnnotations;

[Command(false)]
public void DoSomethingInternal(object p) {
    // TODO
}
vb
Imports DevExpress.Mvvm.DataAnnotations

<Command(false)>
Public Sub DoSomethingInternal(ByVal p As Object)
    ' TODO
End Sub

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.

csharp
using DevExpress.Mvvm.DataAnnotations;
[Command(Name="DoSomething")]
public void DoSomethingCommand(object p) {
    //do something
}
vb
Imports DevExpress.Mvvm.DataAnnotations
<Command(Name := "DoSomething")>
Public Sub DoSomethingCommand(ByVal p As Object)
    'do something
End Sub

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.

csharp
[Command(Name = "MyMvvmCommand")]
public void DoSomething(object p) {
    //do something
}
vb
<Command(Name := "MyMvvmCommand")>
Public Sub DoSomething(ByVal p As Object)
    'do something
End Sub

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>.

csharp
//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;
}
vb
'this command will be executed only if "p" equals 4
Public Sub DoSomething(ByVal p As Integer)
    MessageBox.Show(String.Format("The parameter passed to command is {0}.", p))
End Sub
Public Function CanDoSomething(ByVal p As Integer) As Boolean
    Return (2 + 2) = p
End Function

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

csharp
[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;
}
vb
<Command(CanExecuteMethodName := "DoSomethingCriteria")>
Public Sub DoSomething(ByVal p As Integer)
    MessageBox.Show(String.Format("The parameter passed to command is {0}.", p))
End Sub
Public Function DoSomethingCriteria(ByVal p As Integer) As Boolean
    Return (2 + 2) = p
End Function

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.

csharp
//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());
}
vb
'a bindable property
Private privateIsModified As Boolean
Public Overridable Property IsModified() As Boolean
    Get
        Return privateIsModified
    End Get
    Protected Set(ByVal value As Boolean)
        privateIsModified = value
    End Set
End Property

'a command
Public Sub Save()
    '. . .
End Sub

'a CanExecute condition
Public Function CanSave() As Boolean
    Return IsModified
End Function

'the OnChanged method calls the RaiseCanExecuteChanged method for the "Save" command
'this forces the command to update its CanExecute condition
Public Sub OnIsModifiedChanged()
    Me.RaiseCanExecuteChanged(Sub(x) x.Save())
End Sub

Services

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

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

public virtual IMyNotificationService AnotherService {
    get { throw new NotImplementedException(); }
}
vb
Public Overridable ReadOnly Property MyService() As IMyNotificationService
    Get
        Throw New NotImplementedException()
    End Get
End Property

Public Overridable ReadOnly Property AnotherService() As IMyNotificationService
    Get
        Throw New NotImplementedException()
    End Get
End Property

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

csharp
using DevExpress.Mvvm.DataAnnotations;
//. . .
[ServiceProperty]
public virtual IMyNotificationService MyProvider {
    get { throw new NotImplementedException(); }
}
vb
Imports DevExpress.Mvvm.DataAnnotations
'. . .
<ServiceProperty>
Public Overridable ReadOnly Property MyProvider() As IMyNotificationService
    Get
        Throw New NotImplementedException()
    End Get
End Property

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).

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

[ServiceProperty(Key = "Service2")]
public virtual IMyNotificationService AnotherService {
    get { throw new NotImplementedException(); }
}
vb
<ServiceProperty(Key:="Service1")>
Public Overridable ReadOnly Property Service() As IMyNotificationService
    Get
        Throw New NotImplementedException()
    End Get
End Property

<ServiceProperty(Key := "Service2")>
Public Overridable ReadOnly Property AnotherService() As IMyNotificationService
    Get
        Throw New NotImplementedException()
    End Get
End Property