命令行
在標(biāo)準(zhǔn)的WinForms應(yīng)用程序中,操作通常在事件處理程序中執(zhí)行。例如,要在用戶單擊按鈕時(shí)刷新數(shù)據(jù),需要處理ButtonClick事件并檢索數(shù)據(jù)源記錄。
這種標(biāo)準(zhǔn)技術(shù)不適合分離層的MVVM概念,從數(shù)據(jù)源中提取數(shù)據(jù)的代碼應(yīng)該屬于ViewModel層,而不是View層。在MVVM中,這些任務(wù)是通過(guò)封裝動(dòng)作的命令ViewModel對(duì)象來(lái)完成的,將UI元素綁定到此對(duì)象來(lái)實(shí)現(xiàn)所需的層分離:視圖代碼現(xiàn)在只有綁定代碼,而所有業(yè)務(wù)邏輯都保留在ViewModel中,并且可以安全地更改。
DevExpress MVVM框架將所有public void方法視為可綁定的命令。下面的代碼說(shuō)明了如何聲明使用Service顯示消息框的命令,您可以通過(guò)以下鏈接在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”結(jié)尾的方法將引發(fā)異?!孛祟惙椒ɑ蚴褂肅ommand屬性修飾它們。
要將按鈕鏈接到此命令,請(qǐng)使用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方法允許您同時(shí)綁定多個(gè)按鈕。
運(yùn)行演示:綁定到多個(gè)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)
可執(zhí)行條件
要指定判斷命令是否應(yīng)該運(yùn)行的條件,請(qǐng)聲明一個(gè)Boolean method,該方法的名稱以“Can”開(kāi)頭,后面跟著相關(guān)的命令名稱,這些方法被稱為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屬性手動(dòng)分配命令條件。
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元素的狀態(tài)(禁用、取消選中或隱藏該元素)。上面的代碼示例來(lái)自以下演示:運(yùn)行此演示并更改條件,使其始終返回false,“執(zhí)行命令”按鈕被禁用,因?yàn)樗南嚓P(guān)命令不能再運(yùn)行。
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
當(dāng)發(fā)生以下情況時(shí),框架會(huì)檢查CanExecute條件:
- UI命令綁定初始化。
- 調(diào)用RaiseCanExecuteChanged方法。在下面的示例中,每次SelectedEntity屬性更改時(shí),都會(huì)重新檢查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
命令與參數(shù)
DevExpress MVVM框架接受public void方法作為參數(shù)化命令,您可以使用這個(gè)參數(shù)在View和ViewModel之間傳遞數(shù)據(jù)。
運(yùn)行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條件中添加參數(shù)。
運(yùn)行demo:帶CanExecute條件的參數(shù)化命令。
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)
多參數(shù)
使用對(duì)象或元組數(shù)據(jù)結(jié)構(gòu)來(lái)傳遞多個(gè)參數(shù)。
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"})
異步命令
如果需要執(zhí)行延遲或連續(xù)的操作,請(qǐng)使用異步命令。要?jiǎng)?chuàng)建一個(gè)異步命令,聲明一個(gè)System.Threading.Tasks.Task類型的公共方法(也可以使用async/await語(yǔ)法),將UI元素綁定到命令的代碼保持不變,框架在命令運(yùn)行時(shí)禁用此元素。
執(zhí)行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))
任務(wù)支持取消標(biāo)記并允許您檢查IsCancellationRequested屬性,并在該屬性返回true時(shí)中止任務(wù)。如果將此代碼添加到async命令中,請(qǐng)使用BindCancelCommand方法創(chuàng)建一個(gè)UI元素來(lái)停止正在執(zhí)行的async命令。DevExpress MVVM框架鎖定了這個(gè)取消按鈕,只有在運(yùn)行相關(guān)的異步命令時(shí)才啟用它。
執(zhí)行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)
命令觸發(fā)器
觸發(fā)器允許您執(zhí)行與命令關(guān)聯(lián)的其他View操作。有三種觸發(fā)器類型,取決于觸發(fā)觸發(fā)器的條件:
- “Before”觸發(fā)器——允許您在目標(biāo)命令執(zhí)行之前執(zhí)行操作。
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 "觸發(fā)器——允許您在目標(biāo)命令完成后執(zhí)行操作。
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”條件觸發(fā)器——允許您在目標(biāo)命令的CanExecute條件發(fā)生變化時(shí)執(zhí)行操作。
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"))
注意,每個(gè)綁定到目標(biāo)命令的UI元素都會(huì)執(zhí)行觸發(fā)器,當(dāng)單擊任何按鈕時(shí),下面的代碼示例將顯示一個(gè)消息框。
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類命令允許您使用最直接且不會(huì)出錯(cuò)的語(yǔ)法,DevExpress MVVM框架還支持其他命令類型來(lái)確保遺留項(xiàng)目的無(wú)障礙遷移。
DevExpress delegate命令對(duì)象
委托命令是System.Windows.Input.ICommand接口的實(shí)現(xiàn)。
運(yùn)行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)
運(yùn)行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)
運(yùn)行demo:帶參數(shù)命令
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)
運(yùn)行demo:參數(shù)化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)
自定義命令類
這些對(duì)象是具有至少一個(gè)Execute方法的任意自定義類型的對(duì)象,如果需要您可以添加CanExecute方法和CanExecuteChanged事件。
運(yùn)行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
運(yùn)行demo:帶參數(shù)命令
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