windowsforms-117014-cross-platform-app-development-winforms-mvvm-concepts-conventions-and-attributes.md
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.
All public auto-implemented virtual properties are treated as bindable.
public virtual string Test { get; set; }
Public Overridable Property Test() As String
To suppress bindable property generation for such properties, use the Bindable attribute as follows.
[Bindable(false)]
public virtual string Test { get; set; }
<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.
using DevExpress.Mvvm.DataAnnotations;
//. . .
string test;
[BindableProperty]
public virtual string Test
{
get { return test; }
set { test = value; }
}
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
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
}
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.
public virtual string Test { get; set; }
protected void OnTestChanging(string newValue) {
//do something
}
protected void OnTestChanged(string oldValue) {
//do something
}
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.
[BindableProperty(OnPropertyChangingMethodName = "BeforeChange", OnPropertyChangedMethodName = "AfterChange")]
public virtual string Test { get; set; }
protected void BeforeChange() {
//. . .
}
protected void AfterChange() {
//. . .
}
<BindableProperty(OnPropertyChangingMethodName := "BeforeChange", OnPropertyChangedMethodName := "AfterChange")>
Public Overridable Property Test() As String
Protected Sub BeforeChange()
'. . .
End Sub
Protected Sub AfterChange()
'. . .
End Sub
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));
}
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.
using DevExpress.Mvvm.DataAnnotations;
[Command(false)]
public void DoSomethingInternal(object p) {
// TODO
}
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.
using DevExpress.Mvvm.DataAnnotations;
[Command(Name="DoSomething")]
public void DoSomethingCommand(object p) {
//do something
}
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.
[Command(Name = "MyMvvmCommand")]
public void DoSomething(object p) {
//do something
}
<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>.
//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;
}
'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.
[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;
}
<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.
//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());
}
'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
To resolve services, the Framework overrides virtual properties of an interface type. Interface names must end with …Service.
public virtual IMyNotificationService MyService {
get { throw new NotImplementedException(); }
}
public virtual IMyNotificationService AnotherService {
get { throw new NotImplementedException(); }
}
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.
using DevExpress.Mvvm.DataAnnotations;
//. . .
[ServiceProperty]
public virtual IMyNotificationService MyProvider {
get { throw new NotImplementedException(); }
}
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).
[ServiceProperty(Key="Service1")]
public virtual IMyNotificationService Service {
get { throw new NotImplementedException(); }
}
[ServiceProperty(Key = "Service2")]
public virtual IMyNotificationService AnotherService {
get { throw new NotImplementedException(); }
}
<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