命令行
在标准的WinForms应用程序中,操作通常在事件处理程序中执行。例如,要在用户单击按钮时刷新数据,需要处理ButtonClick事件并检索数据源记录。
这种标准技术不适合分离层的MVVM概念,从数据源中提取数据的代码应该属于ViewModel层,而不是View层。在MVVM中,这些任务是通过封装动作的命令ViewModel对象来完成的,将UI元素绑定到此对象来实现所需的层分离:视图代码现在只有绑定代码,而所有业务逻辑都保留在ViewModel中,并且可以安全地更改。
DevExpress MVVM框架将所有public void方法视为可绑定的命令。下面的代码说明了如何声明使用Service显示消息框的命令,您可以通过以下链接在DevExpress demo Center中查看完整的演示。
C#:
//POCO ViewModel public class ViewModelWithSimpleCommand { //command public void DoSomething() { var msgBoxService = this.GetService<IMessageBoxService>(); msgBoxService.ShowMessage("Hello!"); } }
VB.NET:
'POCO ViewModel Public Class ViewModelWithSimpleCommand 'command Public Sub DoSomething() Dim msgBoxService = Me.GetService(Of IMessageBoxService)() msgBoxService.ShowMessage("Hello!") End Sub End Class
注意:名称以“Command”结尾的方法将引发异常——重命名此类方法或使用Command属性修饰它们。
要将按钮链接到此命令,请使用BindCommand或WithCommand方法。
C#:
//View code mvvmContext.ViewModelType = typeof(ViewModelWithSimpleCommand); var fluent = mvvmContext.OfType<ViewModelWithSimpleCommand>(); fluent.BindCommand(commandButton, x => x.DoSomething); \\or fluent.WithCommand(x => x.DoSomething) .Bind(commandButton1);
VB.NET:
'View code mvvmContext.ViewModelType = GetType(ViewModelWithSimpleCommand) Dim fluent = mvvmContext.OfType(Of ViewModelWithSimpleCommand)() fluent.BindCommand(commandButton, Sub(x) x.DoSomething) 'or fluent.WithCommand(Sub(x) x.DoSomething) .Bind(commandButton1)
WithCommand方法允许您同时绑定多个按钮。
运行演示:绑定到多个UI元素。
C#:
//View var fluent = mvvmContext.OfType<ViewModelWithSimpleCommand>(); fluent.WithCommand(x => x.DoSomething) .Bind(commandButton1) .Bind(commandButton2);
VB.NET:
'View Dim fluent = mvvmContext.OfType(Of ViewModelWithSimpleCommand)() fluent.WithCommand(Sub(x) x.DoSomething) .Bind(commandButton1) .Bind(commandButton2)
可执行条件
要指定判断命令是否应该运行的条件,请声明一个Boolean method,该方法的名称以“Can”开头,后面跟着相关的命令名称,这些方法被称为CanExecute conditions。
C#:
//ViewModel public class ViewModelWithConditionalCommand { //Command public void DoSomething() { var msgBoxService = this.GetService<IMessageBoxService>(); msgBoxService.ShowMessage("Hello!"); } //CanExecute condition public bool CanDoSomething() { return (2 + 2) == 4; } }
VB.NET:
'ViewModel Public Class ViewModelWithConditionalCommand 'Command Public Sub DoSomething() Dim msgBoxService = Me.GetService(Of IMessageBoxService)() msgBoxService.ShowMessage("Hello!") End Sub 'CanExecute condition Public Function CanDoSomething() As Boolean Return (2 + 2) = 4 End Function End Class
您也可以忽略CanExecute名称要求,并使用Command属性手动分配命令条件。
C#:
[Command(CanExecuteMethodName = "DoSomethingCriteria")] public void DoSomething(int p) { //command }
VB.NET:
<Command(CanExecuteMethodName := "DoSomethingCriteria")> Public Sub DoSomething(ByVal p As Integer) 'command End Sub
如果CanExecute条件返回false,框架将改变链接到该命令的UI元素的状态(禁用、取消选中或隐藏该元素)。上面的代码示例来自以下演示:运行此演示并更改条件,使其始终返回false,“执行命令”按钮被禁用,因为它的相关命令不能再运行。
C#:
//ViewModel public bool CanDoSomething() { //always "false" return (2 + 2) == 5; }
VB.NET:
'ViewModel Public Function CanDoSomething() As Boolean 'always "False" Return (2 + 2) = 5 End Function
当发生以下情况时,框架会检查CanExecute条件:
- UI命令绑定初始化。
- 调用RaiseCanExecuteChanged方法。在下面的示例中,每次SelectedEntity属性更改时,都会重新检查CanDoSomething条件的返回值。
C#:
//Bindable Property public virtual MyEntity SelectedEntity{ get; set; } //OnChanged callback for the bindable property protected void OnSelectedEntityChanged(){ this.RaiseCanExecuteChanged(x=>x.DoSomething()); } //Command public void DoSomething() { //. . . } //CanExecute condition public bool CanDoSomething() { //. . . }
VB.NET:
'Bindable Property Public Overridable Property SelectedEntity() As MyEntity 'OnChanged callback for the bindable property Protected Sub OnSelectedEntityChanged() Me.RaiseCanExecuteChanged(Function(x) x.DoSomething()) End Sub 'Command Public Sub DoSomething() '. . . End Sub 'CanExecute condition Public Function CanDoSomething() As Boolean '. . . End Function
命令与参数
DevExpress MVVM框架接受public void方法作为参数化命令,您可以使用这个参数在View和ViewModel之间传递数据。
运行demo:Parameterized命令。
C#:
//ViewModel public class ViewModelWithParametrizedCommand { public void DoSomething(object p) { var msgBoxService = this.GetService<IMessageBoxService>(); msgBoxService.ShowMessage(string.Format("The parameter is {0}.", p)); } } //View mvvmContext.ViewModelType = typeof(ViewModelWithParametrizedCommand); var fluent = mvvmContext.OfType<ViewModelWithParametrizedCommand>(); object parameter = 5; fluent.BindCommand(commandButton, x => x.DoSomething, x => parameter);
VB.NET:
'ViewModel Public Class ViewModelWithParametrizedCommand Public Sub DoSomething(ByVal p As Object) Dim msgBoxService = Me.GetService(Of IMessageBoxService)() msgBoxService.ShowMessage(String.Format("The parameter is {0}.", p)) End Sub End Class 'View mvvmContext.ViewModelType = GetType(ViewModelWithParametrizedCommand) Dim fluent = mvvmContext.OfType(Of ViewModelWithParametrizedCommand)() Dim parameter As Object = 5 fluent.BindCommand(commandButton, Sub(x) x.DoSomething(Nothing), Function(x) parameter)
还可以在CanExecute条件中添加参数。
运行demo:带CanExecute条件的参数化命令。
C#:
//ViewModel public class ViewModelWithParametrizedConditionalCommand { public void DoSomething(int p) { var msgBoxService = this.GetService<IMessageBoxService>(); msgBoxService.ShowMessage(string.Format( "The parameter is {0}.", p)); } public bool CanDoSomething(int p) { return (2 + 2) == p; } } //View mvvmContext.ViewModelType = typeof(ViewModelWithParametrizedConditionalCommand); var fluent = mvvmContext.OfType<ViewModelWithParametrizedConditionalCommand>(); int parameter = 4; fluent.BindCommand(commandButton, x => x.DoSomething, x => parameter);
VB.NET:
'ViewModel Public Class ViewModelWithParametrizedConditionalCommand Public Sub DoSomething(ByVal p As Integer) Dim msgBoxService = Me.GetService(Of IMessageBoxService)() msgBoxService.ShowMessage(String.Format("The parameter is {0}.", p)) End Sub Public Function CanDoSomething(ByVal p As Integer) As Boolean Return (2 + 2) = p End Function End Class 'View mvvmContext.ViewModelType = GetType(ViewModelWithParametrizedConditionalCommand) Dim fluent = mvvmContext.OfType(Of ViewModelWithParametrizedConditionalCommand)() Dim parameter As Integer = 4 fluent.BindCommand(commandButton, Sub(x) x.DoSomething(Nothing), Function(x) parameter)
多参数
使用对象或元组数据结构来传递多个参数。
C#:
class Parameters{ public int Parameter1 { get; set } public string Parameter2 { get; set } ... } // ... mvvmContext.OfType<MouseDownAwareViewModel>() .WithEvent<MouseEventArgs>(label, "MouseDown") .EventToCommand(x => x.ReportLocation, args => new Parameters{ Parameter1 = 1, Parameter2 = "2" });
VB.NET:
Friend Class Parameters Public Property Parameter1() As Integer Get Set(ByVal value As Integer) End Set End Get public String Parameter2 Get Set(ByVal value As Integer) End Set End Get ... ' ... mvvmContext.OfType(Of MouseDownAwareViewModel)().WithEvent(Of MouseEventArgs)(label, "MouseDown").EventToCommand(Function(x) x.ReportLocation, Function(args) New Parameters With {.Parameter1 = 1, .Parameter2 = "2"})
异步命令
如果需要执行延迟或连续的操作,请使用异步命令。要创建一个异步命令,声明一个System.Threading.Tasks.Task类型的公共方法(也可以使用async/await语法),将UI元素绑定到命令的代码保持不变,框架在命令运行时禁用此元素。
执行demo:Async命令。
C#:
//ViewModel public class ViewModelWithAsyncCommand { public async Task DoSomethingAsync() { // do some work here await Task.Delay(1000); } } //View mvvmContext.ViewModelType = typeof(ViewModelWithAsyncCommand); var fluent = mvvmContext.OfType<ViewModelWithAsyncCommand>(); fluent.BindCommand(commandButton, x => x.DoSomethingAsync);
VB.NET:
'ViewModel Public Class ViewModelWithAsyncCommand Public Async Sub DoSomethingAsync() As Task ' do some work here Await Task.Delay(1000) End Sub End Class 'View mvvmContext.ViewModelType = GetType(ViewModelWithAsyncCommand) Dim fluent = mvvmContext.OfType(Of ViewModelWithAsyncCommand)() fluent.BindCommand(commandButton, Sub(x) x.DoSomethingAsync(Nothing))
任务支持取消标记并允许您检查IsCancellationRequested属性,并在该属性返回true时中止任务。如果将此代码添加到async命令中,请使用BindCancelCommand方法创建一个UI元素来停止正在执行的async命令。DevExpress MVVM框架锁定了这个取消按钮,只有在运行相关的异步命令时才启用它。
执行demo: Async取消命令。
C#:
//ViewModel public class ViewModelWithAsyncCommandAndCancellation { public async Task DoSomethingAsynchronously() { var dispatcher = this.GetService<IDispatcherService>(); var asyncCommand = this.GetAsyncCommand(x => x.DoSomethingAsynchronously()); for(int i = 0; i <= 100; i++) { if(asyncCommand.IsCancellationRequested) break; // do some work here await Task.Delay(25); await UpdateProgressOnUIThread(dispatcher, i); } await UpdateProgressOnUIThread(dispatcher, 0); } public int Progress { get; private set; } //update the "Progress" property bound to the progress bar within a View async Task UpdateProgressOnUIThread(IDispatcherService dispatcher, int progress) { await dispatcher.BeginInvoke(() => { Progress = progress; this.RaisePropertyChanged(x => x.Progress); }); } } //View mvvmContext.ViewModelType = typeof(ViewModelWithAsyncCommandAndCancellation); var fluent = mvvmContext.OfType<ViewModelWithAsyncCommandAndCancellation>(); fluent.BindCommand(commandButton, x => x.DoSomethingAsynchronously); fluent.BindCancelCommand(cancelButton, x => x.DoSomethingAsynchronously); fluent.SetBinding(progressBar, p => p.EditValue, x => x.Progress);
VB.NET:
'ViewModel Public Class ViewModelWithAsyncCommandAndCancellation Public Async Sub DoSomethingAsynchronously() As Task Dim dispatcher = Me.GetService(Of IDispatcherService)() Dim asyncCommand = Me.GetAsyncCommand(Sub(x) x.DoSomethingAsynchronously()) For i As Integer = 0 To 100 If asyncCommand.IsCancellationRequested Then Exit For End If ' do some work here Await Task.Delay(25) Await UpdateProgressOnUIThread(dispatcher, i) Next i Await UpdateProgressOnUIThread(dispatcher, 0) End Sub Private privateProgress As Integer Public Property Progress() As Integer Get Return privateProgress End Get Private Set(ByVal value As Integer) privateProgress = value End Set End Property 'update the "Progress" property bound to the progress bar within a View Private Async Sub UpdateProgressOnUIThread(ByVal dispatcher As IDispatcherService, ByVal progress As Integer) As Task Await dispatcher.BeginInvoke(Sub() Me.Progress = progress Me.RaisePropertyChanged(Sub(x) x.Progress) End Sub) End Sub End Class 'View mvvmContext.ViewModelType = GetType(ViewModelWithAsyncCommandAndCancellation) Dim fluent = mvvmContext.OfType(Of ViewModelWithAsyncCommandAndCancellation)() fluent.BindCommand(commandButton, Sub(x) x.DoSomethingAsynchronously) fluent.BindCancelCommand(cancelButton, Sub(x) x.DoSomethingAsynchronously) fluent.SetBinding(progressBar, Sub(p) p.EditValue, Sub(x) x.Progress)
WithCommand Fluent API方法还支持可取消的异步命令。
C#:
mvvmContext.ViewModelType = typeof(ViewModelWithAsyncCommandAndCancellation); // Initialize the Fluent API var fluent = mvvmContext.OfType<ViewModelWithAsyncCommandAndCancellation>(); // Binding for buttons fluent.WithCommand(x => x.DoSomethingAsynchronously) .Bind(commandButton) .BindCancel(cancelButton);
VB.NET:
mvvmContext.ViewModelType = GetType(ViewModelWithAsyncCommandAndCancellation) ' Initialize the Fluent API Dim fluent = mvvmContext.OfType(Of ViewModelWithAsyncCommandAndCancellation)() ' Binding for buttons fluent.WithCommand(Sub(x) x.DoSomethingAsynchronously).Bind(commandButton).BindCancel(cancelButton)
命令触发器
触发器允许您执行与命令关联的其他View操作。有三种触发器类型,取决于触发触发器的条件:
- “Before”触发器——允许您在目标命令执行之前执行操作。
C#:
mvvmContext.ViewModelType = typeof(ViewModelWithSimpleCommand); var fluent = mvvmContext.OfType<ViewModelWithSimpleCommand>(); fluent.BindCommand(commandButton, x => x.DoSomething); fluent.WithCommand(x => x.DoSomething) .Before(() => XtraMessageBox.Show("The target command is about to be executed"));
VB.NET:
mvvmContext.ViewModelType = GetType(ViewModelWithSimpleCommand) Dim fluent = mvvmContext.OfType(Of ViewModelWithSimpleCommand)() fluent.BindCommand(commandButton, Function(x) x.DoSomething) fluent.WithCommand(Sub(x) x.DoSomething) .Before(Function() XtraMessageBox.Show("The target command is about to be executed"))
- " After "触发器——允许您在目标命令完成后执行操作。
C#:
mvvmContext.ViewModelType = typeof(ViewModelWithSimpleCommand); var fluent = mvvmContext.OfType<ViewModelWithSimpleCommand>(); fluent.BindCommand(commandButton, x => x.DoSomething); fluent.WithCommand(x => x.DoSomething) .After(() => XtraMessageBox.Show("The target command has been executed"));
VB.NET:
mvvmContext.ViewModelType = GetType(ViewModelWithSimpleCommand) Dim fluent = mvvmContext.OfType(Of ViewModelWithSimpleCommand)() fluent.BindCommand(commandButton, Function(x) x.DoSomething) fluent.WithCommand(Function(x) x.DoSomething).After(Function() XtraMessageBox.Show("The target command has been executed"))
- “CanExecute”条件触发器——允许您在目标命令的CanExecute条件发生变化时执行操作。
C#:
var fluent = mvvmContext.OfType<ViewModelWithSimpleCommandAndCanExecute>(); fluent.BindCommand(commandButton, x => x.DoSomething); // When the CanExecute condition changes, the message shows up fluent.WithCommand(x => x.DoSomething) .OnCanExecuteChanged(() => XtraMessageBox.Show("The CanExecute condition has changed"));
VB.NET:
Dim fluent = mvvmContext.OfType(Of ViewModelWithSimpleCommandAndCanExecute)() fluent.BindCommand(commandButton, Function(x) x.DoSomething) ' When the CanExecute condition changes, the message shows up fluent.WithCommand(Function(x) x.DoSomething) .OnCanExecuteChanged(Function() XtraMessageBox.Show("The CanExecute condition has changed"))
注意,每个绑定到目标命令的UI元素都会执行触发器,当单击任何按钮时,下面的代码示例将显示一个消息框。
C#:
mvvmContext1.OfType<BulkEditViewModel>() .WithCommand(vm => vm.RemoveFields()) .Bind(button1) .Bind(button2) .After(() => MessageBox.Show("Test"));
VB.NET:
mvvmContext1.OfType(Of BulkEditViewModel)() .WithCommand(Function(vm) vm.RemoveFields()) .Bind(button1) .Bind(button2) .After(Function() MessageBox.Show("Test"))
Non-POCO命令
上面描述的POCO类命令允许您使用最直接且不会出错的语法,DevExpress MVVM框架还支持其他命令类型来确保遗留项目的无障碍迁移。
DevExpress delegate命令对象
委托命令是System.Windows.Input.ICommand接口的实现。
运行demo: Simple Commands
C#:
DelegateCommand command = new DelegateCommand(() => { XtraMessageBox.Show("Hello!"); }); commandButton.BindCommand(command);
VB.NET:
Dim command As New DelegateCommand(Sub() XtraMessageBox.Show("Hello!")) commandButton.BindCommand(command)
运行demo:带有CanExecute条件的命令
C#:
Func<bool> canExecute = () => (2 + 2 == 4); DelegateCommand command = new DelegateCommand(() => { XtraMessageBox.Show("Hello!"); }, canExecute); commandButton.BindCommand(command);
VB.NET:
Dim canExecute As Func(Of Boolean) = Function() (2 + 2 = 4) Dim command As New DelegateCommand(Sub() XtraMessageBox.Show("Hello!"), canExecute) commandButton.BindCommand(command)
运行demo:带参数命令
C#:
DelegateCommand<object> command = new DelegateCommand<object>((v) => { XtraMessageBox.Show(string.Format("The parameter is {0}.", v)); }); object parameter = 5; commandButton.BindCommand(command, () => parameter);
VB.NET:
Dim command As New DelegateCommand(Of Object)(Sub(v) XtraMessageBox.Show(String.Format("The parameter is {0}.", v))) Dim parameter As Object = 5 commandButton.BindCommand(command, Function() parameter)
运行demo:参数化CanExecute条件的命令
C#:
Func<int, bool> canExecute = (p) => (2 + 2 == p); DelegateCommand<int> command = new DelegateCommand<int>((v) => { XtraMessageBox.Show(string.Format("The parameter is {0}.", v)); }, canExecute); int parameter = 4; commandButton.BindCommand(command, () => parameter);
VB.NET:
Dim canExecute As Func(Of Integer, Boolean) = Function(p) (2 + 2 = p) Dim command As New DelegateCommand(Of Integer)(Sub(v) XtraMessageBox.Show(String.Format("The parameter is {0}.", v)), canExecute) Dim parameter As Integer = 4 commandButton.BindCommand(command, Function() parameter)
自定义命令类
这些对象是具有至少一个Execute方法的任意自定义类型的对象,如果需要您可以添加CanExecute方法和CanExecuteChanged事件。
运行demo:Simple Commands
C#:
CommandObject command = new CommandObject(); commandButton.BindCommand(command); public class CommandObject { public void Execute(object parameter) { XtraMessageBox.Show("Hello!"); } }
VB.NET:
Private command As New CommandObject() commandButton.BindCommand(command) Public Class CommandObject Public Sub Execute(ByVal parameter As Object) XtraMessageBox.Show("Hello!") End Sub End Class
运行demo:带参数命令
C#:
CommandObjectWithParameter command = new CommandObjectWithParameter(); int parameter = 4; commandButton.BindCommand(command, () => parameter); public class CommandObjectWithParameter { public void Execute(object parameter) { XtraMessageBox.Show(string.Format( "The parameter is {0}.", parameter)); } public bool CanExecute(object parameter) { return object.Equals(2 + 2, parameter); } }
VB.NET:
Dim command As New CommandObjectWithParameter() Dim parameter As Integer = 4 commandButton.BindCommand(command, Sub() parameter) Public Class CommandObjectWithParameter Public Sub Execute(ByVal parameter As Object) XtraMessageBox.Show(String.Format("The parameter is {0}.", parameter)) End Sub Public Function CanExecute(ByVal parameter As Object) As Boolean Return Object.Equals(2 + 2, parameter) End Function End Class