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 的類包括: ButtonBase、MenuItem 和 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就完成了,當我們輸入完畢用戶名和密碼,則按鈕自動啟用,然后點擊登錄就搞定了。
