MVVM之Event and Command


Event: 

在Silverlight和WPF中沒有使用.net的LCR事件,而是使用Routed路由事件,根本原因是因為Silverlight控件的節點樹。

一個簡單的示例:

public static readonly RoutedEvent MyRoutedEvent =EventManager.RegisterRoutedEvent("MyEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyClass));

是不是很熟悉,沒錯和定義附加屬性(依賴屬性)的方式類似,解釋下參數:

public static RoutedEvent RegisterRoutedEvent(
string name,
RoutingStrategy routingStrategy,
Type handlerType,
Type ownerType
)

Name:第一個就是事件的名字(也就是一個public,類型和handlerType一致的屬性),這個對於同一個類是唯一的;
routingStrategy:指示路由事件的路由策略。枚舉值
            Tunnel:路由事件使用隧道策略,以便事件實例通過樹向下路由(從根到源元素)。
            Bubble:路由事件使用冒泡策略,以便事件實例通過樹向上路由(從事件元素到根)。
            Direct: 路由事件不通過元素樹路由,但支持其他路由事件功能,例如類處理、EventTrigger 或 EventSetter
handlerType:事件的類型,例子為RoutedEventHandler。
ownerType:事件所屬的類,通常就是當前類。

 限制:Silverlight和WPF的路由事件的一個最大的限制,就是需要把代碼寫在后置代碼中,這樣就無法在其他類中進行操作。
         這個例子最能說明問題

<TextBox Text="{Binding Source={StaticResource myDomainObject}, Path=StringProperty}"
TextChanged="TextBox_TextChanged" />

這樣的代碼經常去寫,給TextBox添加TextChanged事件,然后在事件中去更新什么東西,可是如果這么做了就打破了MVVM的完整性。都知道默認的Binding更新數據源是在LostFocus的時候去提交,可以通過UpdateSourceTrigger(枚舉)去設置,修改后如下:

<TextBox Text=”{Binding Source={StaticResource myDomainObject}, Path=StringProperty,
UpdateSourceTrigger=PropertyChanged}” />

沒錯,修改默認的UpdateSoureTrigger為PropertyChanged即值改變后立馬提交

public string StringProperty
{
get { return _stringProperty; }
set
{
_stringProperty = value;
ProcessNewStringProperty(_stringProperty);
}
}

綁定的屬性則調用INotifyPropertyChanged接口的PropertyChanged事件進行更新通知。

Command:

ICommandSource :定義了解如何調用命令的對象,WPF 中可實現 ICommandSource 的類包括: ButtonBaseMenuItem 和 Hyperlink

屬性:
Command  獲取將在調用命令源時執行的命令。
CommandParameter 表示可在執行命令時傳遞給該命令的用戶定義的數據值。
CommandTarget      將在其上執行命令的對象。

說了這么多,主要是為了引出ICommand接口,也就是Command的類型。
 ICommand:定義一個命令。
 屬性:
 CanExecuteChanged  當出現影響是否應執行該命令的更改時發生。
 函數:
CanExecute  定義用於確定此命令是否可以在其當前狀態下執行的方法。(根據綁定方法的邏輯,來控制按鈕是否禁用狀態)
Execute       定義在調用此命令時調用的方法。 

 

接下來看張圖,RouteEvent的實現關系



RoutedCommand執行的調用的方法CommandManager,然后搜索元素樹,找出匹配的連接一個ICommand和Handler的CommandBinding。這CommandBinding階級作為一個關聯類多對多類的關系,同事出現在ICommand間發生和他們的Handler。

看下自定義Command的實現設計圖:

我們主要是在創建一個Command,同時實現Execute方法,然后綁定到Target去,這樣就完成了一個完整的觸發之定義Command。

定義Commander要求:

1.ViewModel(調用者)和Command(執行者)在不同的ViewModel和Command(自定義Command類)

2.Command作為ViewModel的成員(公有)

3.Command的Hander應該和其在同一個類(在同一個ViewModel完成Commande初始化和綁定)

4.CanExecute方法必須被實現,以完成禁用(啟用)Target

5.CanExecute該參數是可選的,即可以不指定此委托

看個Command的示例:

  public class RelayCommand:ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;

public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute,Predicate<object> canExecute)
{
if(execute==null)
throw new ArgumentNullException("execute");
_canExecute = canExecute;
_execute = execute;
}

public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}

public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}

public void Execute(object parameter)
{
_execute(parameter);
}
}


可以看到自定義的Command有兩個屬性,類型分別為Action<object>和 Predicate<object>,前者為一個委托,封裝一個方法,參數就是類型,當前為object;后者為表示定義一組條件並確定指定對象是否符合這些條件的方法,也是一個委托,返回值為bool。
同時我們的自定義Command還有兩個方法Execute和CanExecute ,前者就是Command的主要方法,執行綁定的函數,參數為object;后者也提到了是用來實現控件是否禁用。
其中還有一個很重要的屬性就是 CanExecuteChanged,它用來監聽用戶界面的改變,光標從一個Control移動到另一個Control這樣的改變,來確定Element的狀態。

 

寫完了Commande實現,還需實現ViewModel的代碼:

public class LogInViewModel
{
private LogInModel _logInModel;
private RelayCommand _logInCommand;

public string UserName
{
get;
set;
}

public string Password
{
get;
set;
}

public RelayCommand LogInCommand
{
get
{
return _logInCommand;
}
}
public LogInViewModel()
{
_logInModel = new LogInModel();
_logInCommand = new RelayCommand(param=>this.AttemptLogIn(),param=> this.CanAttemptLogIn());
}

private void AttemptLogIn()
{
_logInModel.LogIn(UserName, Password);
}

private bool CanAttemptLogIn()
{
return !string.IsNullOrWhiteSpace(UserName) && !string.IsNullOrWhiteSpace(Password);
}
}


其實代碼也很好理解,在ViewModel中有四個屬性,一個Model,一個Command,兩個用戶界面要輸入的屬性。

在構造函數中實例化Model和Command,在實例化Command時參數很奇怪,使用了lambda表達式,這樣在實例化Command的時候param表示一個對方法的引用而不是去執行方法。AttemptLogIn方法在Command被觸發的時候執行,所以要實現View的代碼部分:

<Window x:Class="View.Wpf.LogInView"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model
="clr-namespace:ViewModel;assembly=ViewModel"
Title
="LogInView" Height="300" Width="300">
<Window.Resources>
<model:LogInViewModel x:Key="loginModel"></model:LogInViewModel>
</Window.Resources>
<Grid DataContext="{StaticResource ResourceKey=loginModel}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Content="UserName:" Grid.Row="0" Grid.Column="0" Margin=" 0 5 0 0"></Label>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="txtUserName" Text="{Binding Path=UserName,UpdateSourceTrigger=PropertyChanged}"
Margin=" 0 5 0 0"></TextBox>
        <Label Content="Password:" Grid.Row="1" Grid.Column="0" Margin=" 0 5 0 0"></Label>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="txtPassword" Text="{Binding Path=Password, UpdateSourceTrigger=PropertyChanged}" Margin=" 0 5 0 0" ></TextBox>
<Button Content="LogIn" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Height="25" Width="200" Margin=" 0 5 0 0"
Command
="{Binding Path=LogInCommand}" />
</Grid>
</Window>


Xaml代碼也是很簡單,兩個文本框分別表示用戶名和密碼,一個按鈕用來觸發Command,綁定兩個TextBox的Text分別為UserName和Password,然后綁定按鈕的Command為LogInCommand,這樣一個完整的自定義Command就完成了,當我們輸入完畢用戶名和密碼,則按鈕自動啟用,然后點擊登錄就搞定了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM