WPF進階之介面:INotifyPropertyChanged,ICommand
INotifiPropertyChanged
1. 作用:向客戶端發出某一屬性值已更改的通知。該介面包含一個PropertyChanged事件成員(MSDN的解釋)
INotifyPropertyChanged 介面用於向客戶端(通常是執行繫結的客戶端)發出某一屬性值已更改的通知。
例如,考慮一個帶有名為 FirstName 屬性的 Person 物件。若要提供一般性屬性更改通知,則 Person 型別實現NotifyPropertyChanged 介面並在 FirstName 更改時引發 PropertyChanged 事件。若要在將客戶端與資料來源進行繫結時發出更改通知,則繫結型別應具有下列任一功能:
A. 實現 INotifyPropertyChanged 介面(首選)。
B. 為繫結型別的每個屬性提供更改事件。
2. 使用:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
定義一個抽象基類,該類實現了INotifyPropertyChanged介面,所有繼承自ViewModelBase 的類,都將具有:通知繫結端,後臺屬性發生變化的能力。
public class MainViewModel : ViewModelBase
{
//.............
private LeagueList dataToShow;
public LeagueList DataToShow
{
get
{
if (dataToShow == null)
dataToShow = new LeagueList();
return dataToShow;
}
set
{
dataToShow= value;
OnPropertyChanged("DataToShow");
}
}
}
MainViewModel 繼承BaseViewModel,當屬性值發生變化的時候,即在屬性的set段中呼叫OnPropertyChanged函式,那麼就能通知UI,繫結資料來源發生了變化,UI也會自動更新資料顯示。那麼如何實現繫結呢,看看下面程式碼:
<Window x:Class="Views.Window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:ViewModels"
xmlns:View="clr-namespace:Views"
xmlns:c="clr-namespace:Commands">
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:MainViewModel}">
<View:MainView/>
</DataTemplate>
</Window.Resources>
上面程式碼的意思是,MainViewModel中定義各種資料來源和程式碼邏輯,如後這些資料按照MainView中所定義的佈局進行顯示,這也就是DataTemplate的作用,這裡不展開(後續將在資料模板中介紹)。
好了,現在把兩個類:MainViewMode(l資料,普通cs檔案),MainView(UI,即一個xaml檔案,通常該類為一個Usrcontrol)進行了繫結,那麼具體的資料怎麼實現繫結呢。簡單,看看MainView中的程式碼:
<ListBox Grid.Column="0" Name="topiclist" ItemsSource="{Binding Path=DataToShow}">
這樣就實現了,具體資料的繫結。
3. 進一步分析
(1)繫結分析:
首先定義一個抽象基類BaseViewModel實現INotifyProperChanged介面;定義MainViewModel繼承自BaseVIewModel,這樣就能使用PropertyChange函式,當屬性值發生變化的時候,在set段呼叫PropertyChange函式。
其次,定義好MainView檔案,該檔案定義介面佈局,實現UI,通過繫結MainViewModel中資料。
最後在Window.xaml中使用DataTemplate將MainViewModel和MainView進行繫結
注意在WPF中,xaml 和xam.cs檔案是自動繫結的,但是MainViewModel是普通的cs檔案,不是xaml.cs檔案,因此,僅僅在MainView中使用繫結,系統不會再MainViewModel中去尋找資料來源,,而是在MainView.xaml.cs中去尋找資料。因此需要最後一步。
(2)為什麼不在xaml.cs中定義資料來源和邏輯程式碼?
原因1:xaml.cs是控制元件的邏輯檔案,而MainViewModel需要繼承INotifyPropertyChange介面,這樣就必須讓控制元件繼承INotifyPropertyChanged,相當於是控制元件重寫了,這樣的程式設計模式,xaml.cs將越來越大,這個類的測試也將越發複雜。因此,從降低類複雜度的角度,不應在xaml.cs中定義資料來源和邏輯程式碼。
原因2:如果在xaml.cs中實現邏輯,,不利於邏輯和UI的分離,不利於UI和邏輯的分開編寫,降低的程式編寫的效率,同時增加了程式碼的耦合度。採用MainViewModel和 MainView的框架,其實就是MVVM模式(Model-View-ViewModel),該模式可以說和WPF是珠聯璧合,等我陸續闡述完,各種基礎後,,我將在WPF進階之MVVM中詳細說明,現在請大家,耐心掌握基礎。
(3)看剛才的例子,ListBox的ItemsSource通常需要一個集合型別資料,好了,我們知道ObservableCollection是一個數據集合型別,並且實現了INotifyPropertyChanged介面,那麼為什麼不繫結到一個ObservableCollection資料型別呢?
原因:說的很對通常繫結到ObservableCollection是可行的,繫結到普通資料,並實現INotifyPropertyChanged也是可行的,。他們的區別在於ObservableCollection繼承INotifyCollectionChanged, INotifyPropertyChanged那麼當Collection新增項,刪除項,重新整理時,都將傳送PropertyChanged通知,有時這部分功能是我們不需要的,因此,採用自己實現INotifyPropertyChanged的類將更具靈活性。
ICommand
定義:
大家知道在xaml中定義的事件,響應函式只能在xaml.cs中,如上所述,如果我們採用MVVM框架,那麼我們不能通過事件響應的模式,實現程式碼邏輯。那麼如何監聽事件呢。我們使用命令。且看下面實現
//不帶引數的命令型別
public class DelegateCommand : ICommand
{
public DelegateCommand(Action executeMethod) : this(executeMethod, null, false)
{
}
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
: this(executeMethod, canExecuteMethod, false) {
}
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
{
if (executeMethod == null)
{
throw new ArgumentNullException("executeMethod");
}
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
}
public bool CanExecute()
{
if (_canExecuteMethod != null)
{
return _canExecuteMethod();
}
return true;
}
public void Execute()
{
if (_executeMethod != null)
{
_executeMethod();
}
}
public bool IsAutomaticRequeryDisabled
{
get
{
return _isAutomaticRequeryDisabled;
}
set
{
if (_isAutomaticRequeryDisabled != value)
{
if (value)
{
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
}
else
{